using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; 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 BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using CompanionKit.Core; using ForgeKit; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.AI; using UnityEngine.Events; using UnityEngine.SceneManagement; [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.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0+294094e110e9aca56665cc10cce0047db95418d1")] [assembly: AssemblyProduct("CompanionKit")] [assembly: AssemblyTitle("CompanionKit")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace CompanionKit { [HarmonyPatch(typeof(AmbienceSound), "OnUpdate")] public static class AmbienceSoundGuard { [HarmonyFinalizer] private static Exception Finalizer(Exception __exception, AmbienceSound __instance) { return AmbienceGuard.Handle(__exception, __instance); } } [HarmonyPatch(typeof(AmbienceZone), "OnUpdate")] public static class AmbienceZoneGuard { [HarmonyFinalizer] private static Exception Finalizer(Exception __exception, AmbienceZone __instance) { return AmbienceGuard.Handle(__exception, (AmbienceSound)(object)__instance); } } [HarmonyPatch(typeof(GlobalAudioManager), "ClearPlayers")] public static class ClearPlayersGuard { [HarmonyFinalizer] private static Exception Finalizer(Exception __exception) { if (__exception == null) { return null; } CompanionRuntime.Log.LogWarning((object)("[AMBIENCE-GUARD] GlobalAudioManager.ClearPlayers threw (" + __exception.GetType().Name + ": " + __exception.Message + ") — swallowing so it can't abort a scene load/area switch.")); return null; } } [HarmonyPatch(typeof(GlobalAudioManager), "GetUsableSoundInstance")] public static class GetUsableSoundInstanceGuard { private static readonly HashSet _detailed = new HashSet(); private static int _count; private static float _heartbeatAt; [HarmonyFinalizer] private static Exception Finalizer(Exception __exception, GlobalAudioManager __instance, Sounds _sound, ref SplitableSoundSource __result) { //IL_0019: 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_00aa: Unknown result type (might be due to invalid IL or missing references) if (__exception == null) { return null; } __result = null; _count++; if (_detailed.Add(_sound)) { CompanionRuntime.Log.LogWarning((object)$"[AMBIENCE-RECON] GetUsableSoundInstance threw ({__exception.GetType().Name}: {__exception.Message}) for sound '{_sound}'. {DescribeSoundState(__instance, _sound)}\n{__exception.StackTrace}"); _heartbeatAt = Time.unscaledTime; } else if (Time.unscaledTime - _heartbeatAt > 10f) { _heartbeatAt = Time.unscaledTime; CompanionRuntime.Log.LogWarning((object)$"[AMBIENCE-GUARD] GetUsableSoundInstance still failing — {_count} swallowed so far; latest sound '{_sound}'."); } return null; } private static string DescribeSoundState(GlobalAudioManager mgr, Sounds sound) { //IL_0016: 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_0052: 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_0060: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)mgr == (Object)null) { return "(no manager instance)"; } try { string arg = ((!mgr.m_soundPrefabs.ContainsKey(sound)) ? "absent" : (((Object)(object)mgr.m_soundPrefabs[sound] == (Object)null) ? "DESTROYED" : "ok")); string arg2 = "absent"; if (mgr.m_soundInstances.ContainsKey(sound)) { List list = mgr.m_soundInstances[sound]; int num = 0; foreach (SplitableSoundSource item in list) { if (item != null && (Object)(object)item == (Object)null) { num++; } } arg2 = $"{list.Count} (dead {num})"; } return $"state: prefab={arg} busyKey={mgr.BusySounds.ContainsKey(sound)} instances={arg2} " + $"mixerKey={mgr.MixerGroupPerSound != null && mgr.MixerGroupPerSound.ContainsKey(sound)} poolAlive={(Object)(object)mgr.m_soundPool != (Object)null}"; } catch (Exception ex) { return "(state probe failed: " + ex.Message + ")"; } } } public static class AmbienceGuard { public static Exception Handle(Exception exception, AmbienceSound instance) { if (exception == null) { return null; } CompanionRuntime.Log.LogWarning((object)("[AMBIENCE-GUARD] '" + PathOf((Component)(object)instance) + "' (" + ((object)instance)?.GetType().Name + ") OnUpdate threw (" + exception.GetType().Name + ": " + exception.Message + ") — disabling it so it stops retrying every frame.")); try { if ((Object)(object)instance != (Object)null) { ((Behaviour)instance).enabled = false; } } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[AMBIENCE-GUARD] disable failed: " + ex.Message)); } try { if ((Object)(object)instance != (Object)null && DonorHarvest.RemoveFromAudioRegistries(instance)) { CompanionRuntime.Log.LogWarning((object)("[AMBIENCE-GUARD] '" + PathOf((Component)(object)instance) + "' also unregistered from GlobalAudioManager — manager-driven .Play() calls would otherwise keep retrying it after the disable.")); } } catch (Exception ex2) { CompanionRuntime.Log.LogWarning((object)("[AMBIENCE-GUARD] unregister failed: " + ex2.Message)); } return null; } public static string PathOf(Component c) { //IL_0053: 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) try { if ((Object)(object)c == (Object)null) { return ""; } string text = ((Object)c.gameObject).name; Transform parent = c.transform.parent; while ((Object)(object)parent != (Object)null) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } Scene scene = c.gameObject.scene; return ((Scene)(ref scene)).name + ":" + text; } catch { return ""; } } } 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 { DamageList damage = currentWeapon.Damage; if (damage != null) { damage.Clear(); } object? obj = typeof(Weapon).GetField("m_baseDamage", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(currentWeapon); DamageList val2 = (DamageList)((obj is DamageList) ? obj : null); if (val2 != null) { val2.Clear(); } 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 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 _vitalsHost; 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 AnchorStats(Func current, Func cfg) { _current = current; _cfg = cfg; } public void ForgetAnchor() { _statsHost = null; _statBaseline = null; _appliedWant = null; } public void ApplyCreatureStats(CreatureAttributes eff) { //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Expected O, but got Unknown if (!HasLiveAnchor) { return; } CharacterStats stats = Current.Stats; if ((Object)(object)stats == (Object)null) { return; } Stat[] array = Targets(stats); if (_statsHost != Current) { _statsHost = Current; _appliedWant = null; _statBaseline = new float[array.Length]; for (int i = 0; i < array.Length; i++) { _statBaseline[i] = ((array[i] != null) ? array[i].BaseValue : 0f); } } if (eff == null) { if (_appliedWant == null) { return; } foreach (Stat obj in array) { if (obj != null) { obj.RemoveStack("CK_SpeciesStats", false); } } _appliedWant = null; CompanionRuntime.Log.LogMessage((object)(TagStats + " species stats cleared from the anchor (ghost defaults restored).")); return; } float[] array2 = Wants(eff); if (_appliedWant != null && ApproxSame(array2, _appliedWant)) { return; } int num = 0; for (int k = 0; k < array.Length; k++) { if (array[k] != null) { float num2 = array2[k] - _statBaseline[k]; array[k].RemoveStack("CK_SpeciesStats", false); if (Mathf.Abs(num2) > 0.001f) { array[k].AddStack(new StatStack("CK_SpeciesStats", num2, (Tag[])null), false); num++; } } } _appliedWant = array2; CompanionRuntime.Log.LogMessage((object)$"{TagStats} species defense applied to the anchor ({num} stat stack(s)): {AttributeCapture.Describe(eff)}"); } private static Stat[] Targets(CharacterStats st) { int num = 9; Stat[] array = (Stat[])(object)new Stat[2 * num + 3]; 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; return array; } private static float[] Wants(CreatureAttributes eff) { int num = 9; float[] array = new float[2 * num + 3]; 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; 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() { if (!HasLiveAnchor || (Object)(object)Current.Stats == (Object)null) { CompanionRuntime.Log.LogMessage((object)(TagStats + " no live anchor to dump.")); return; } if (_appliedWant == null || _statsHost != Current) { CompanionRuntime.Log.LogMessage((object)(TagStats + " no species stats applied to this anchor (ghost defaults).")); 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" : "barrier")))); CompanionRuntime.Log.LogMessage((object)$"{TagStats} {text}: baseline={_statBaseline[i]:F1} + stack={_appliedWant[i] - _statBaseline[i]:F1} -> live={array2[i].CurrentValue:F1}"); } } } public void ApplyVitals(float maxHealth) { if (!HasLiveAnchor || maxHealth <= 0f) { return; } CharacterStats stats = Current.Stats; if ((Object)(object)stats == (Object)null) { return; } 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; stats.SetHealth(maxHealth); if (!Mathf.Approximately(currentHealth, maxHealth)) { CompanionRuntime.Log.LogMessage((object)$"{TagStats} fresh anchor vitals: {currentHealth:F0} -> {maxHealth:F0} (full — bug-23 first-apply reset)."); } } else if (stats.CurrentHealth > maxHealth) { stats.SetHealth(maxHealth); } } 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; 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) { return false; } Current.Stats.SetHealth(Current.Stats.ActiveMaxHealth); return true; } public bool HealAmount(float amount, out bool reArmCrit) { 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; } 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; current = stats.CurrentHealth; max = stats.MaxHealth; return true; } } 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) { if ((Object)(object)src == (Object)null) { return null; } try { CreatureAttributes val = Read(src); if (val != null) { CompanionRuntime.Log.LogMessage((object)("[STATS] captured '" + src.Name + "': " + Describe(val))); } return val; } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[STATS] 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_0099: 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) }; 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 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}"); } 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 static class BodyFactory { public enum EquipmentStripMode { Components, GameObjects } private static Character _ghostPrefab; private static bool _ghostSearched; 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", "CharAIDisable", "CharacterBarManager" }; private static GameObject _holder; 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_007b: 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) 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 && (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() { //IL_0086: 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_00ae: 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_00eb: Invalid comparison between Unknown and I4 if (_ghostSearched) { return _ghostPrefab; } 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) { 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; } public 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(); CompanionRuntime.Log.LogMessage((object)$"[GHOST] spawned active '{((Object)val3).name}' (Character={(Object)(object)component != (Object)null}); waiting for visuals."); return component; } public static bool GhostVisualReady(Character ghost) { if ((Object)(object)ghost != (Object)null && (Object)(object)ghost.Visuals != (Object)null) { return ghost.Visuals.DefaultVisualsInitialized; } return false; } public static void NudgeGhostActive(Character ghost) { if (!((Object)(object)ghost == (Object)null)) { ghost.DisableAfterInit = false; if (!((Component)ghost).gameObject.activeSelf) { ((Component)ghost).gameObject.SetActive(true); } } } public static bool ForceGhostVisuals(Character ghost) { //IL_00da: 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_00f2: 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}"); 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) { CompanionRuntime.Log.LogWarning((object)"[GHOST] VisualData is null — can't build default visuals (would NRE)."); } else if (!val.DefaultVisualsInitialized) { val.InitDefaultVisuals(); } Animator component = gameObject.GetComponent(); if ((Object)(object)component != (Object)null) { component.Rebind(); } } } catch (Exception ex) { 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}"); return num2 > 0; } public static CompanionBody FinishGhostPuppet(Character ghost, Character player) { //IL_001d: 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 { CharacterManager.Instance.Characters.Remove(UID.op_Implicit(ghost.UID)); } catch { } DestroyImmediateIfPresent(gameObject); CharacterAI component = gameObject.GetComponent(); if ((Object)(object)component != (Object)null) { try { component.m_aiStatesRoot = null; } catch { } Object.DestroyImmediate((Object)(object)component); } DestroyImmediateIfPresent(gameObject); try { Object.DestroyImmediate((Object)(object)ghost); } catch { } if ((Object)(object)gameObject.GetComponent() == (Object)null) { gameObject.AddComponent(); } CompanionBody companionBody = FinishPuppet(gameObject, "Spirit", player); if ((Object)(object)companionBody != (Object)null) { companionBody.YawOffset = 0f; } return companionBody; } public static CompanionBody BuildHumanoidPuppet(Character src, Character player, EquipmentStripMode equipStrip = EquipmentStripMode.Components) { //IL_0019: 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_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_0038: 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_0061: 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_004e: 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_0088: Unknown result type (might be due to invalid IL or missing references) SweepHolderOrphans(); string name = src.Name; CreatureAttributes capturedStats = AttributeCapture.From(src); 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, Holder().transform); try { val2.transform.SetPositionAndRotation(val, ((Component)src).transform.rotation); DestroyImmediateIfPresent(val2); CharacterAI component = val2.GetComponent(); if ((Object)(object)component != (Object)null) { try { component.m_aiStatesRoot = null; } catch { } Object.DestroyImmediate((Object)(object)component); } DestroyImmediateIfPresent(val2); CharacterBarManager[] componentsInChildren = val2.GetComponentsInChildren(true); foreach (CharacterBarManager val3 in componentsInChildren) { Object.DestroyImmediate((Object)(object)val3); } if (equipStrip == EquipmentStripMode.Components) { StripEquipmentGameplay(val2); } else { StripEquippedItems(val2); } StripHumanoidMachinery(val2); DestroyImmediateIfPresent(val2); val2.transform.SetParent((Transform)null, true); CompanionBody companionBody = FinishPuppet(val2, name, player, equipStrip); if ((Object)(object)companionBody != (Object)null) { if ((Object)(object)val2.GetComponent() == (Object)null) { val2.AddComponent(); } companionBody.YawOffset = 0f; companionBody.CapturedStats = capturedStats; } return companionBody; } catch { 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) { //IL_0039: 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_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_0058: 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_0060: Unknown result type (might be due to invalid IL or missing references) SweepHolderOrphans(); string name = src.Name; CreatureAttributes capturedStats = precaptured ?? AttributeCapture.From(src); ProjectileCapture.RangedAttackRig rangedAttackRig = ((rangedProjectileFilter != null) ? ProjectileCapture.From(src, rangedProjectileFilter, Holder().transform, rangedSkillPrefabId) : null); Vector3 pos = ((Component)player).transform.position + ((Component)player).transform.forward * 2f; GameObject go = null; try { go = (consume ? CloneConsuming(src, pos) : CloneNonConsuming(src, pos)); CompanionBody companionBody = FinishPuppet(go, name, player); if ((Object)(object)companionBody != (Object)null) { companionBody.CapturedStats = capturedStats; ProjectileCapture.Attach(rangedAttackRig, companionBody); } return companionBody; } catch { DestroyStranded(go, "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; } } private static CompanionBody FinishPuppet(GameObject go, string speciesId, Character player, EquipmentStripMode? humanoidEquipStrip = null) { StripBrain(go, humanoidEquipStrip); CompanionBody companionBody = go.AddComponent(); go.AddComponent(); companionBody.Target = ((Component)player).transform; companionBody.SpeciesId = speciesId; Object.DontDestroyOnLoad((Object)(object)go); player.ResetCombat(); int num = go.GetComponentsInChildren(true).Length; int num2 = CountRenderReady(go); CompanionRuntime.Log.LogMessage((object)$"[CLONE] '{speciesId}' puppet ready (Animator={(Object)(object)go.GetComponent() != (Object)null}, renderers={num}, drawReady={num2})."); if (num > 0 && num2 == 0) { CompanionRuntime.Log.LogWarning((object)("[CLONE] '" + speciesId + "' has NO draw-ready renderer (bug-8 signature) — dumping state + attempting auto-repair.")); VisDump(go, "bug-8 pre-repair"); VisRepair(go); } return companionBody; } 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; } public 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; } public 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))); ManualLogSource 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}")); } } } public 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; } 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_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) GameObject val = Object.Instantiate(((Component)src).gameObject, Holder().transform); try { val.transform.SetPositionAndRotation(pos, ((Component)src).transform.rotation); DestroyImmediateIfPresent(val); CharacterAI component = val.GetComponent(); if ((Object)(object)component != (Object)null) { try { component.m_aiStatesRoot = null; } catch { } Object.DestroyImmediate((Object)(object)component); } DestroyImmediateIfPresent(val); CharacterBarManager[] componentsInChildren = val.GetComponentsInChildren(true); foreach (CharacterBarManager val2 in componentsInChildren) { Object.DestroyImmediate((Object)(object)val2); } StripEquippedItems(val); DestroyImmediateIfPresent(val); val.transform.SetParent((Transform)null, true); return val; } catch { DestroyStranded(val, "no-consume clone"); throw; } } private 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 { } } } } private 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 { } } } if (num > 0) { CompanionRuntime.Log.LogMessage((object)$"[CLONE] humanoid equipment strip: {num} Item component(s) removed (GameObjects kept)."); } } private 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 { } } } CompanionRuntime.Log.LogMessage((object)((list.Count > 0) ? ("[CLONE] humanoid machinery strip: " + string.Join(", ", list.ToArray()) + ".") : "[CLONE] humanoid machinery strip: nothing matched (clean NPC).")); } private static void StripBrain(GameObject go, EquipmentStripMode? humanoidEquipStrip = null) { CharacterAI component = go.GetComponent(); if ((Object)(object)component != (Object)null) { try { component.m_aiStatesRoot = null; } catch { } Object.Destroy((Object)(object)component); } DestroyIfPresent(go); DestroyIfPresent(go); Rigidbody component2 = go.GetComponent(); if ((Object)(object)component2 != (Object)null) { Object.Destroy((Object)(object)component2); } if (humanoidEquipStrip == EquipmentStripMode.Components) { StripEquipmentGameplay(go); } else { StripEquippedItems(go); } if (humanoidEquipStrip.HasValue) { StripHumanoidMachinery(go); } Character component3 = go.GetComponent(); if ((Object)(object)component3 != (Object)null) { ((Behaviour)component3).enabled = false; } CharacterController component4 = go.GetComponent(); if ((Object)(object)component4 != (Object)null) { ((Collider)component4).enabled = false; } AdvancedMover component5 = go.GetComponent(); if ((Object)(object)component5 != (Object)null) { ((Behaviour)component5).enabled = false; } RigidbodySuspender component6 = go.GetComponent(); if ((Object)(object)component6 != (Object)null) { ((Behaviour)component6).enabled = false; } Collider[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Collider val in componentsInChildren) { val.enabled = false; } Component[] componentsInChildren2 = go.GetComponentsInChildren(true); foreach (Component val2 in componentsInChildren2) { if (!((Object)(object)val2 == (Object)null) && GameplayStrip.Contains(((object)val2).GetType().Name)) { try { Object.DestroyImmediate((Object)(object)val2); } catch { } } } } private 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; } private 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); } } } private 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); } } private static void DestroyIfPresent(GameObject go) where T : Component { T component = go.GetComponent(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } } private static void DestroyImmediateIfPresent(GameObject go) where T : Component { T component = go.GetComponent(); if ((Object)(object)component != (Object)null) { Object.DestroyImmediate((Object)(object)component); } } } public sealed class BodyTemplate { public GameObject Dormant; public string Key; public string SpeciesId; public CreatureAttributes Captured; public bool Substituted; } public static class BodyTemplateCache { private static readonly Dictionary _cache = new Dictionary(StringComparer.OrdinalIgnoreCase); private const int CacheWarnThreshold = 24; private static GameObject _holder; private static readonly HashSet _warnedSubs = new HashSet(StringComparer.OrdinalIgnoreCase); public static int Count { get { Prune(); return _cache.Count; } } private 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_BodyTemplateHolder"); _holder.SetActive(false); Object.DontDestroyOnLoad((Object)(object)_holder); } return _holder; } public static bool TryResolve(string speciesId, out BodyTemplate template) { Prune(); template = null; if (string.IsNullOrEmpty(speciesId)) { return false; } string text = default(string); if (!SpeciesTable.TryResolveKey(_cache, speciesId, ref text, ref template, (string)null) || template == null) { return false; } if (!StatAdoption.IsSameSpecies(speciesId, template.SpeciesId)) { WarnSubstitutionOnce(speciesId, template.SpeciesId); } return true; } public static bool TryResolveExact(string speciesId, out BodyTemplate template) { Prune(); template = null; if (string.IsNullOrEmpty(speciesId)) { return false; } if (!_cache.TryGetValue(speciesId.Trim(), out template) || template == null) { return false; } if (template.Substituted) { WarnSubstitutionOnce(speciesId, template.SpeciesId); template = null; return false; } return true; } private static void WarnSubstitutionOnce(string asked, string got) { if (_warnedSubs.Add(asked + "→" + got)) { CompanionRuntime.Log.LogWarning((object)("[TEMPLATE] SUBSTITUTION: asked for '" + asked + "', but the cached body is a '" + got + "' — NOT the same creature (different stats, possibly a different model; e.g. Elite Gargoyle Mage 1700hp vs Gargoyle 750hp). A PET may still wear it as a fallback body, but it will never be recorded as its identity (Bug 31). A SPAWN refuses it outright. If '" + asked + "' matters, it needs its own donor row + a capture; see Bug 28.")); } } public static BodyTemplate Capture(Character src, string key) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)src == (Object)null || string.IsNullOrEmpty(key)) { return null; } GameObject val = null; try { val = Object.Instantiate(((Component)src).gameObject, Holder().transform); ((Object)val).name = "CK_BodyTemplate_" + key; val.transform.localPosition = Vector3.zero; PhotonView component = val.GetComponent(); if ((Object)(object)component != (Object)null) { component.viewID = 0; } AISquadMember[] componentsInChildren = val.GetComponentsInChildren(true); foreach (AISquadMember val2 in componentsInChildren) { if ((Object)(object)val2 != (Object)null && (Object)(object)val2.AISquad != (Object)null) { val2.AISquad = null; } } string text = RagdollRig.RestoreJoints(((Component)src).gameObject, val, delegate(string w) { CompanionRuntime.Log.LogWarning((object)("[TEMPLATE] ragdoll ('" + key + "'): " + w)); }); if (text != null) { CompanionRuntime.Log.LogMessage((object)("[TEMPLATE] ragdoll ('" + key + "'): " + text + ".")); } RigCheck(((Component)src).gameObject, val, key); string text2 = DonorHarvest.IdentityFor(key, src); BodyTemplate bodyTemplate = new BodyTemplate { Dormant = val, Key = key, SpeciesId = text2, Captured = AttributeCapture.From(src), Substituted = !StatAdoption.IsSameSpecies(key, text2) }; if (_cache.TryGetValue(key, out var value) && (Object)(object)value?.Dormant != (Object)null && (Object)(object)value.Dormant != (Object)(object)val) { Object.Destroy((Object)(object)value.Dormant); } _cache[key] = bodyTemplate; CompanionRuntime.Log.LogMessage((object)("[TEMPLATE] cached body template '" + key + "' (species '" + bodyTemplate.SpeciesId + "'" + (bodyTemplate.Substituted ? (" — SUBSTITUTE: this is NOT a '" + key + "'") : "") + ", stats " + ((bodyTemplate.Captured != null) ? "captured" : "UNREADABLE — config fallback at build") + ").")); if (bodyTemplate.Substituted) { CompanionRuntime.Log.LogWarning((object)("[TEMPLATE] '" + key + "' was captured from a '" + text2 + "' donor — the two are different creatures. A spawn of this key will now REFUSE the cache and harvest honestly; a pet may still wear it as a fallback body but will never record it as its identity (Bug 31). Fix: give '" + key + "' its own donor row, or pin it (Bug 28).")); } if (_cache.Count > 0 && _cache.Count % 24 == 0) { CompanionRuntime.Log.LogWarning((object)($"[TEMPLATE] cache now holds {_cache.Count} resident body template(s) — " + "each pins its mesh/texture assets in memory. 'templateclear' frees them (safe when no re-form is mid-build).")); } return bodyTemplate; } catch (Exception ex) { CompanionRuntime.Log.LogError((object)("[TEMPLATE] capture of '" + key + "' threw — destroying the orphan clone: " + ex.Message)); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } return null; } } private static void RigCheck(GameObject donor, GameObject clone, string key) { Action warn = delegate(string w) { CompanionRuntime.Log.LogWarning((object)("[RIG] ('" + key + "') " + w)); }; SkeletonRig.Report report = SkeletonRig.Audit(donor, clone, warn); CompanionRuntime.Log.LogMessage((object)("[TEMPLATE] rig ('" + key + "'): " + report.Summary + ".")); if (report.Healthy) { return; } SkeletonRig.LogForensics(report, donor, warn); if (!SkeletonRig.RepairEnabled) { CompanionRuntime.Log.LogWarning((object)("[TEMPLATE] rig ('" + key + "'): external references present and [Rig] RepairSkinnedBones=false (log-only) — puppets from this template will mis-render.")); return; } string text = SkeletonRig.Repair(donor, clone, report, warn); SkeletonRig.Report report2 = SkeletonRig.Audit(null, clone, warn); CompanionRuntime.Log.LogMessage((object)("[TEMPLATE] rig repair ('" + key + "'): " + text + " — post: " + report2.Summary + ".")); if (!report2.Healthy) { CompanionRuntime.Log.LogWarning((object)("[TEMPLATE] rig ('" + key + "'): still unhealthy after the repair pass — keeping the template anyway (no chain to advance here; a mis-skinned body beats none).")); } } public static CompanionBody PuppetFrom(BodyTemplate template, Character player, string rangedProjectileFilter = null, int rangedSkillPrefabId = 0) { if ((Object)(object)template?.Dormant == (Object)null || (Object)(object)player == (Object)null) { 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.")); 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; } CompanionRuntime.Log.LogMessage((object)("[TEMPLATE] 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(); _warnedSubs.Clear(); return result; } public static int Clear() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown _cache.Clear(); if ((Object)(object)_holder == (Object)null) { return 0; } List list = new List(); foreach (Transform item in _holder.transform) { Transform val = item; list.Add(((Component)val).gameObject); } foreach (GameObject item2 in list) { if ((Object)(object)item2 != (Object)null) { Object.Destroy((Object)(object)item2); } } return list.Count; } public static string Dump() { Prune(); if (_cache.Count == 0) { return "[TEMPLATE] body-template cache empty."; } StringBuilder stringBuilder = new StringBuilder($"[TEMPLATE] {_cache.Count} cached body template(s):"); foreach (KeyValuePair item in _cache) { stringBuilder.Append("\n '" + item.Key + "' -> species '" + item.Value.SpeciesId + "' stats=" + ((item.Value.Captured != null) ? "captured" : "none") + (item.Value.Substituted ? (" *** SUBSTITUTE — NOT a '" + item.Key + "' (spawns refuse it; Bug 31) ***") : "")); } return stringBuilder.ToString(); } public static string Probe() { Prune(); if (_cache.Count == 0) { return "[TEMPLATE] body-template cache empty — nothing to probe."; } StringBuilder stringBuilder = new StringBuilder($"[TEMPLATE] mesh probe over {_cache.Count} cached template(s):"); foreach (KeyValuePair item in _cache) { string text; try { SkinnedMeshRenderer val = (((Object)(object)item.Value.Dormant != (Object)null) ? item.Value.Dormant.GetComponentInChildren(true) : null); text = (((Object)(object)val == (Object)null) ? "no SkinnedMeshRenderer" : (((Object)(object)val.sharedMesh == (Object)null) ? "SkinnedMeshRenderer with no sharedMesh" : $"mesh '{((Object)val.sharedMesh).name}' isReadable={val.sharedMesh.isReadable}")); } catch (Exception ex) { text = "probe threw: " + ex.GetType().Name + ": " + ex.Message; } stringBuilder.Append("\n '" + item.Key + "' (species '" + item.Value.SpeciesId + "') -> " + text); } return stringBuilder.ToString(); } private static void Drop(string key) { _cache.Remove(key); } private static void Prune() { List list = null; foreach (KeyValuePair item in _cache) { if ((Object)(object)item.Value?.Dormant == (Object)null) { List obj = list ?? new List(); list = obj; obj.Add(item.Key); } } if (list == null) { return; } foreach (string item2 in list) { _cache.Remove(item2); CompanionRuntime.Log.LogWarning((object)("[TEMPLATE] cached body template '" + item2 + "' was destroyed — dropped from the cache.")); } } } [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)) { ManualLogSource 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) { ManualLogSource log2 = CompanionRuntime.Log; if (log2 != null) { log2.LogWarning((object)("[CHAR-GUARD] prefix failed (" + ex.Message + ") — falling through to vanilla.")); } return true; } } private 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 "?"; } } } public static class CkConfig { public static class Rig { public static ConfigEntry RepairSkinnedBones; internal static void Bind(ConfigFile cfg) { RepairSkinnedBones = cfg.Bind("Rig", "RepairSkinnedBones", true, "Repair SkinnedMeshRenderer bones that reference transforms OUTSIDE the harvested creature's own hierarchy (they die with the donor scene and the spawn renders as a stretched vertical line — finding F2, Elder Medyse). Runs at template capture while the donor is still loaded: rebind to a same-named internal bone where one exists, else graft the foreign skeleton subtree under the clone. false = log-only (the '[TEMPLATE] rig' audit line still fires)."); } } public static class Expedition { public static ConfigEntry CaptureOnSceneEntry; public static ConfigEntry AutoWarmAtBoot; public static ConfigEntry AlwaysWarmSpecies; public static ConfigEntry AutoWarmRetrySeconds; public static ConfigEntry ReturnRetrySeconds; internal static void Bind(ConfigFile cfg) { CaptureOnSceneEntry = cfg.Bind("Expedition", "CaptureOnSceneEntry", false, "When you enter an OVERSIZED scene (region terrain/town — the expedition tier) that still has uncached donor-table species, run the expedition capture payload IN PLACE at player-ready (the scene is already loaded, so it costs zero loading screens) and record the scene in ck_expeditions.txt. Never fires for ordinary scenes (one string check); a fully cached region costs one key sweep. DEFAULT OFF as of the public release: this is the only path on which the kit does heavy work the player never asked for (it clones every uncached donor species in the region on EVERY region/town entry), and it is the suspected source of the un-root-caused 'mystery live Pearlbird' (a live donor engaging the player in combat ~1.2 km away — spawnkit-testplan.md watch-for). Turn it on to pay expedition costs opportunistically instead of via an explicit 'expedition '."); AutoWarmAtBoot = cfg.Bind("Expedition", "AutoWarmAtBoot", "needed", "One-time template-cache warm at the FIRST gameplay player-ready of each launch. 'needed' = only when the active companion's species has expedition-tier donors, NO additive donor, and no cached template — one hands-off round trip to its first candidate (a species with any wild/additive source never triggers anything). 'all' = re-dump every ck_expeditions.txt scene that still has >=1 uncached species (sequential trips). 'off' = never. Unknown values warn and behave as off. AlwaysWarmSpecies is evaluated on top of this, ungated by the mode."); AlwaysWarmSpecies = cfg.Bind("Expedition", "AlwaysWarmSpecies", "", "Comma-separated species warmed at the same once-per-launch boot evaluation REGARDLESS of AutoWarmAtBoot (the release-packaging knob — e.g. 'Pearlbird', whose only donor scene is expedition-tier, ships listed here so a body is waiting before its first re-form). Each listed species with expedition-tier donors and no cached template gets its first candidate scene warmed; species sharing a scene share ONE trip (deduped against the mode's trips too); a species with an additive donor is skipped with a notice (the normal harvest chain covers it); an unrecognized name warns loudly."); AutoWarmRetrySeconds = cfg.Bind("Expedition", "AutoWarmRetrySeconds", 60f, "The boot auto-warm can fire while a vanilla area load or a companion donor harvest is still in flight, and an expedition trip can't START then. Rather than abandoning the whole launch's warm pass, the auto-warm polls (once a second, unscaled) until the loader settles, then re-attempts — for at most this many seconds. 0 = never wait (blocked at boot = give up, the pre-fix behavior); the once-per-launch latch always means ONE pass (a settled launch OR an exhausted wait), never one attempt."); ReturnRetrySeconds = cfg.Bind("Expedition", "ReturnRetrySeconds", 20f, "Safety net for the expedition's RETURN leg (Bug 25 — an expedition that fails to come home leaves you in the donor region, and the game bakes that into the save). RequestSwitchArea has no failure signal, so if the return was asked for and this many seconds later no load has started and we are still standing in the donor scene, the return is re-requested (twice at most, then the watchdog makes one final rescue attempt). This measures request → load-START, not load duration, so a healthy 15-60s region load never trips it. 0 = never retry (not recommended; the watchdog rescue still applies)."); } } } public class Companion { public CompanionBody Body; public CompanionAnchor Anchor; public CommandStance Stance; private readonly ICompanionSettings _settings; private readonly OwnerRef _owner; private ICompanionSettings Cfg => _settings ?? CompanionRuntime.DefaultSettings; public Companion(ICompanionSettings settings = null) { _settings = settings; _owner = new OwnerRef(); Anchor = new CompanionAnchor(settings); Stance = new CommandStance(); } public CompanionCombat AdoptBody(CompanionBody body, bool enableCombat) { if ((Object)(object)body == (Object)null) { return null; } Body = body; body.Owner = _owner.Get; if (body.Settings == null) { body.Settings = _settings; } CompanionCombat companionCombat = null; if (enableCombat) { companionCombat = ((Component)body).gameObject.AddComponent(); companionCombat.Anchor = Anchor; companionCombat.Stance = Stance; companionCombat.Settings = _settings; companionCombat.Owner = _owner.Get; } Anchor.AttachBody(body); return companionCombat; } public void Tick(Character player, MonoBehaviour host) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Invalid comparison between Unknown and I4 //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Body == (Object)null)) { bool bodyFighting = (Object)(object)Body.CombatTarget != (Object)null; Anchor.Upkeep(player, host, bodyFighting); Anchor.ApplyVoice(Body); Transform val = (Anchor.HasLiveAnchor ? ((Component)Anchor.Current).transform : null); Transform val2 = (((int)Cfg.GlueMode == 2) ? null : val); if ((Object)(object)Body.FollowOverride != (Object)(object)val2) { Body.FollowOverride = val2; } if ((int)Cfg.GlueMode == 0 && (Object)(object)Body.CombatTarget != (Object)null) { Anchor.PinTo(((Component)Body).transform.position); } } } public void Despawn() { Anchor.DetachBody(); Anchor.DestroyCurrent(); Stance.Reset(); } } public sealed class CommandStance { private Character _commanded; public bool Passive { get; private set; } public Character CommandedTarget { get { if ((Object)(object)_commanded != (Object)null && _commanded.Alive) { return _commanded; } _commanded = null; return null; } } public void CommandEngage(Character target) { Passive = false; _commanded = target; } public void CommandDisengage() { Passive = true; _commanded = null; } public void Reset() { Passive = false; _commanded = null; } public void DropCommanded() { _commanded = null; } } public sealed class OwnerRef { private readonly Func _resolve; private Character _cached; public OwnerRef(Func resolve = null) { _resolve = resolve ?? ((Func)delegate { CharacterManager instance = CharacterManager.Instance; return (instance == null) ? null : instance.GetFirstLocalCharacter(); }); } public Character Get() { if ((Object)(object)_cached == (Object)null) { _cached = _resolve(); } return _cached; } } public sealed class CompanionAnchor { private const int InstantiationTypeNetwork = 3; private readonly ICompanionSettings _cfg; private readonly AnchorStats _stats; private readonly AnchorDressing _dressing; private readonly AnchorPhysics _physics; public Action OnAnchorDeath; public Action OnAnchorCriticallyHurt; private float _deadUntil; private bool _critFired; private bool _wasInCombat; private float _leashReconAt; private CharacterAI _ai; private bool _glueWasEngaged; private bool _agentWarpNoted; private float _glueJumpLog; private Character _assertedLock; private float _unifyLogAt; private Character _unifySkipLogged; private float _lastHp; private float _hpDriftLogAt; private CompanionBody _body; private ICompanionSettings Cfg => _cfg ?? CompanionRuntime.DefaultSettings; private string TagAnchor => CompanionRuntime.Tag("ANCHOR", Cfg); private string TagGlue => CompanionRuntime.Tag("GLUE", 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 => _assertedLock; public CompanionAnchor(ICompanionSettings cfg = null) { _cfg = cfg; _stats = new AnchorStats(() => Current, () => Cfg); _dressing = new AnchorDressing(() => Current, () => Cfg); _physics = new AnchorPhysics(() => Current, () => Cfg); } 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.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) { //IL_0010: 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_0049: 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_0088: 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_0269: 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_027e: Unknown result type (might be due to invalid IL or missing references) //IL_0283: 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_028a: Unknown result type (might be due to invalid IL or missing references) //IL_02a0: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || !CompanionRuntime.IsSanePosition(((Component)player).transform.position)) { return; } if (HasLiveAnchor) { _physics.Sync(); float num = Vector3.Distance(((Component)Current).transform.position, ((Component)player).transform.position); CharacterAI aI = AI; Character val = LockedEnemy(aI); 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) { CompanionRuntime.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)_assertedLock == (Object)(object)val) { _assertedLock = null; } CalmAnchor(aI); val = null; } bool flag = Current.InCombat || (Object)(object)val != (Object)null; bool flag2 = AnchorGlue.Engaged(Cfg.GlueMode, true, flag || bodyFighting); if ((Object)(object)val != (Object)null) { player.AddCombatEngagement(val); } float num3 = (flag ? Cfg.CombatLeashDistance : Cfg.AnchorLeashDistance); if (flag != _wasInCombat) { _wasInCombat = flag; CompanionRuntime.Log.LogMessage((object)string.Format("{0} combat {1}: {2} playerDist={3:F1} glued={4}", TagAnchor, flag ? "ENTER" : "EXIT", DescribeAI(), num, flag2)); } if (!flag2 && flag && num > Cfg.AnchorLeashDistance && Time.time - _leashReconAt > 3f) { _leashReconAt = Time.time; CompanionRuntime.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 (!flag2 && num > num3) { if (flag) { CompanionRuntime.Log.LogMessage((object)$"{TagAnchor} player left the fight ({num:F0}m away) — calming the anchor and recalling it (bug-4 fix)."); CalmAnchor(aI); } Vector3 reference = ((Component)player).transform.position + ((Component)player).transform.forward * 0.5f; if (NavProbe.SampleAtFeet(reference, 1.5f, out var pos)) { Current.Teleport(pos, Quaternion.identity); } } float currentHealth = Current.Stats.CurrentHealth; float maxHealth = Current.Stats.MaxHealth; if (_lastHp > 0f && currentHealth < _lastHp - 0.25f && !flag && 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 ex) { text = "probe-failed:" + ex.Message; } CompanionRuntime.Log.LogMessage((object)$"{TagAnchor} hp drift OUT OF COMBAT: {_lastHp:F1} -> {currentHealth:F1} ({currentHealth - _lastHp:F1}); statuses=[{text}]"); } _lastHp = currentHealth; if (!_critFired && maxHealth > 0f && currentHealth < 0.2f * maxHealth) { _critFired = true; OnAnchorCriticallyHurt?.Invoke(); } else if (_critFired && maxHealth > 0f && currentHealth > 0.5f * maxHealth) { _critFired = false; } } else if (!((Object)(object)Current != (Object)null) && !(Time.time < _deadUntil)) { Spawn(player, host, Cfg.AnchorInvisible); } } public bool Spawn(Character player, MonoBehaviour host, bool hide) { //IL_0039: 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_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_0058: 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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0082: 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_0098: 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_00b4: 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_01c2: Expected O, but got Unknown //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Expected O, but got Unknown if (PhotonNetwork.isNonMasterClientInRoom) { return false; } if ((Object)(object)Current != (Object)null) { CompanionRuntime.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; } string text = (Cfg.AnchorLinkSummonSlot ? UID.op_Implicit(player.UID) : string.Empty); GameObject val2; try { val2 = CharacterManager.Instance.InstantiateNetworkCharacter(Cfg.GhostPrefabName, val, Quaternion.identity, 3, text, (string)null, -1); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)(TagAnchor + " spawn threw: " + ex)); return false; } if ((Object)(object)val2 == (Object)null) { CompanionRuntime.Log.LogWarning((object)(TagAnchor + " spawn returned null.")); return false; } Character component = val2.GetComponent(); if ((Object)(object)component == (Object)null) { CompanionRuntime.Log.LogWarning((object)(TagAnchor + " spawned object has no Character.")); Object.Destroy((Object)(object)val2); return false; } component.Lifetime = -1f; CharacterAI component2 = val2.GetComponent(); if ((Object)(object)component2 != (Object)null) { AIState currentAiState = component2.CurrentAiState; AISWander val3 = (AISWander)(object)((currentAiState is AISWander) ? currentAiState : null); if (val3 != null) { val3.FollowTransform = ((Component)player).transform; goto IL_01a9; } } CompanionRuntime.Log.LogWarning((object)(TagAnchor + " expected AISWander as the initial state — follow not wired.")); goto IL_01a9; IL_01a9: component.OnDeath = (UnityAction)Delegate.Combine((Delegate?)(object)component.OnDeath, (Delegate?)new UnityAction(HandleDeath)); Current = component; _ai = component2; _lastHp = 0f; _critFired = false; _wasInCombat = false; _assertedLock = null; _agentWarpNoted = false; _dressing.ResetVoiceSource(); if ((Object)(object)host != (Object)null) { host.StartCoroutine(_physics.StampWhenReady(component)); } if (hide && (Object)(object)host != (Object)null) { host.StartCoroutine(_dressing.HideSweep(component)); } if (!Cfg.AnchorDealsDamage && (Object)(object)host != (Object)null) { host.StartCoroutine(_dressing.NeuterWeaponWhenReady(component)); } if (Cfg.SpeciesVoice && (Object)(object)host != (Object)null) { host.StartCoroutine(_dressing.MuteSweep(component)); } _dressing.ApplyHealthBarConfig(component); CompanionRuntime.Log.LogMessage((object)$"{TagAnchor} spawned (hp={(((Object)(object)component.Stats != (Object)null) ? component.Stats.CurrentHealth : (-1f)):F0}, linked={Cfg.AnchorLinkSummonSlot}, invisible={hide})."); return true; } private void HandleDeath() { CompanionRuntime.Log.LogMessage((object)$"{TagAnchor} anchor died — auto-respawn in {Cfg.AnchorRespawnSeconds:F0}s."); _deadUntil = Time.time + Cfg.AnchorRespawnSeconds; Character current = Current; Current = null; _ai = null; _assertedLock = null; _stats.ForgetAnchor(); _physics.Forget(); _dressing.PlayDeathVocal(current); _dressing.ResetVoiceSource(); if ((Object)(object)current != (Object)null) { try { current.StartDestroy(); } catch { } } OnAnchorDeath?.Invoke(); } private 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; } public void Calm() { ClearAssertedLock(); _unifySkipLogged = null; if (HasLiveAnchor) { CalmAnchor(AI); } } private void CalmAnchor(CharacterAI ai) { try { if ((Object)(object)ai != (Object)null && (Object)(object)ai.TargetingSystem != (Object)null) { ai.TargetingSystem.SetLockingPoint((LockingPoint)null); if (ai.CurrentAiState is AISCombat) { ai.SwitchAiState(0); } } 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)); } } public 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).")); } } public void GlueTick(CompanionBody body) { //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_0267: 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_00c7: 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_00e3: 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_00f7: 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_00a3: 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_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) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0167: 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_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_018a: 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_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: 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_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Expected I4, but got Unknown //IL_020f: 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_0227: Unknown result type (might be due to invalid IL or missing references) //IL_0229: 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) if ((Object)(object)body == (Object)null) { return; } _physics.Sync(); bool hasLiveAnchor = HasLiveAnchor; CharacterAI val = (hasLiveAnchor ? AI : null); bool flag = (Object)(object)body.CombatTarget != (Object)null || (hasLiveAnchor && (Object)(object)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) { return; } 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); Vector3 val3 = default(Vector3); ((Vector3)(ref val3))..ctor(num2, num3, num4); Quaternion val4 = ((((Vector3)(ref facingDir)).sqrMagnitude > 1E-06f) ? Quaternion.LookRotation(((Vector3)(ref facingDir)).normalized, Vector3.up) : ((Component)Current).transform.rotation); switch (val2 - 1) { case 0: ((Component)Current).transform.SetPositionAndRotation(val3, val4); _agentWarpNoted = false; break; case 1: try { Current.Internal_SendTeleport(val3, val4); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)(TagGlue + " Internal_SendTeleport failed (" + ex.Message + ") — plain write instead.")); ((Component)Current).transform.SetPositionAndRotation(val3, val4); } _agentWarpNoted = false; _physics.Sync(); if (Time.time - _glueJumpLog > 2f) { _glueJumpLog = Time.time; CompanionRuntime.Log.LogMessage((object)$"{TagGlue} closed a {num: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(val3); } 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.")); } break; } } public void ClearAssertedLock() { _assertedLock = null; } public 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; } 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) { return; } aI.TargetingSystem.SetLockingPoint(target.LockingPoint); if (!(aI.CurrentAiState is AISCombat)) { int num = FindCombatStateIndex(aI); if (num >= 0) { aI.SwitchAiState(num); } } _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)); } } private static int FindCombatStateIndex(CharacterAI ai) { AIState[] array = (((Object)(object)ai != (Object)null) ? ai.AiStates : null); if (array == null) { return -1; } for (int i = 0; i < array.Length; i++) { if (array[i] is AISCombat) { return i; } } return -1; } 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 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 Heal() { if (!_stats.Heal()) { return false; } _critFired = false; return true; } public bool HealAmount(float amount) { bool reArmCrit; bool result = _stats.HealAmount(amount, out reArmCrit); 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_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown if (!((Object)(object)Current == (Object)null)) { Character current = Current; Current = null; _ai = null; _assertedLock = null; _unifySkipLogged = null; _stats.ForgetAnchor(); _physics.Forget(); _dressing.ResetVoiceSource(); _dressing.ResetBodySound(); current.OnDeath = (UnityAction)Delegate.Remove((Delegate?)(object)current.OnDeath, (Delegate?)new UnityAction(HandleDeath)); try { current.StartDestroy(); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)(TagAnchor + " destroy failed: " + ex.Message)); } CompanionRuntime.Log.LogMessage((object)(TagAnchor + " destroyed.")); } } 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 class CompanionBody : MonoBehaviour { public Transform Target; public string SpeciesId = ""; public float Speed = 4.5f; public float StopDistance = 2.5f; private const float LoafArriveDistance = 0.6f; public float LeashDistance = 14f; public Transform CombatTarget; public Transform FollowOverride; public float YawOffset = float.NaN; public float FollowSpeedFloor; public CreatureAttributes CapturedStats; public ProjectileCapture.RangedAttackRig RangedRig; public ICompanionSettings Settings; public Func Owner; public Func CompanionRef; private Animator _anim; private NavMeshAgent _agent; private float _repath; private float _diag; private float _warpCd; private float _lastVoidLogAt = -999f; private Vector3 _faceDir = Vector3.forward; private bool _planted; private RigStabilizer _rig; private float _warpBlockedSince = -1f; private float _leashGraceUntil = -1f; private Vector3 _lastDest = Vector3.positiveInfinity; private Vector3 _facePoint; private float _faceUntil = -1f; private float _velAnomalyLog; private float _desyncLog; private float _roofLog; private readonly IdleFacingPolicy _idleFacing = new IdleFacingPolicy(); private readonly LoafTracker _loaf = new LoafTracker(); private ICompanionSettings Cfg => Settings ?? CompanionRuntime.DefaultSettings; public Vector3 FacingDir => _faceDir; private float DriveSpeed { get { if (!((Object)(object)CombatTarget == (Object)null)) { return Speed; } return Mathf.Max(Speed, FollowSpeedFloor); } } public event Action OnAfterMove; private void Start() { _anim = ((Component)this).GetComponent(); 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); } } } SkinnedMeshRenderer[] componentsInChildren = ((Component)this).GetComponentsInChildren(true); foreach (SkinnedMeshRenderer val2 in componentsInChildren) { val2.updateWhenOffscreen = true; ((Renderer)val2).enabled = true; } _rig = ((Component)this).GetComponent(); _agent = ((Component)this).GetComponent(); if ((Object)(object)_agent != (Object)null) { CompanionRuntime.Log.LogMessage((object)($"[PUPPET] creature natural agent speed={_agent.speed:F2} m/s; driving at {Speed:F2} " + $"(agent radius={_agent.radius:F2} height={_agent.height:F2}).")); ((Behaviour)_agent).enabled = true; _agent.speed = DriveSpeed; _agent.angularSpeed = 720f; _agent.acceleration = 40f; _agent.stoppingDistance = StopDistance; _agent.autoTraverseOffMeshLink = true; _agent.updatePosition = true; _agent.updateRotation = false; _agent.obstacleAvoidanceType = (ObstacleAvoidanceType)0; } CompanionRuntime.Log.LogMessage((object)$"[PUPPET] agent={(Object)(object)_agent != (Object)null} onNavMesh={(Object)(object)_agent != (Object)null && _agent.isOnNavMesh} anim={(Object)(object)_anim != (Object)null}"); } private void Update() { //IL_0065: 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_0113: 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_009d: 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_015d: 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_00af: 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_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_026b: 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_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: 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_01d7: 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_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0210: 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_032e: 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) //IL_022a: 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_02bb: 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_02e0: 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_0377: Unknown result type (might be due to invalid IL or missing references) //IL_03e8: Unknown result type (might be due to invalid IL or missing references) //IL_0411: Unknown result type (might be due to invalid IL or missing references) //IL_0416: Unknown result type (might be due to invalid IL or missing references) //IL_042a: Unknown result type (might be due to invalid IL or missing references) //IL_042f: Unknown result type (might be due to invalid IL or missing references) //IL_0462: Unknown result type (might be due to invalid IL or missing references) //IL_0464: Unknown result type (might be due to invalid IL or missing references) //IL_0389: Unknown result type (might be due to invalid IL or missing references) //IL_046a: Unknown result type (might be due to invalid IL or missing references) //IL_047a: Unknown result type (might be due to invalid IL or missing references) //IL_0481: Unknown result type (might be due to invalid IL or missing references) //IL_0495: Unknown result type (might be due to invalid IL or missing references) //IL_0497: Unknown result type (might be due to invalid IL or missing references) //IL_05d5: Unknown result type (might be due to invalid IL or missing references) //IL_05e0: Unknown result type (might be due to invalid IL or missing references) //IL_05a3: Unknown result type (might be due to invalid IL or missing references) //IL_05ac: Unknown result type (might be due to invalid IL or missing references) //IL_05ae: Unknown result type (might be due to invalid IL or missing references) //IL_0580: Unknown result type (might be due to invalid IL or missing references) //IL_0583: Unknown result type (might be due to invalid IL or missing references) //IL_0588: Unknown result type (might be due to invalid IL or missing references) //IL_058d: Unknown result type (might be due to invalid IL or missing references) //IL_0645: Unknown result type (might be due to invalid IL or missing references) //IL_064b: Invalid comparison between Unknown and I4 //IL_06db: Unknown result type (might be due to invalid IL or missing references) //IL_06e0: Unknown result type (might be due to invalid IL or missing references) //IL_0843: Unknown result type (might be due to invalid IL or missing references) //IL_0848: Unknown result type (might be due to invalid IL or missing references) //IL_084a: Unknown result type (might be due to invalid IL or missing references) //IL_0852: Unknown result type (might be due to invalid IL or missing references) //IL_0857: Unknown result type (might be due to invalid IL or missing references) //IL_085c: Unknown result type (might be due to invalid IL or missing references) //IL_0870: Unknown result type (might be due to invalid IL or missing references) //IL_087b: Unknown result type (might be due to invalid IL or missing references) //IL_0880: Unknown result type (might be due to invalid IL or missing references) //IL_0885: Unknown result type (might be due to invalid IL or missing references) //IL_0894: Unknown result type (might be due to invalid IL or missing references) //IL_08a6: Unknown result type (might be due to invalid IL or missing references) //IL_0836: Unknown result type (might be due to invalid IL or missing references) //IL_0831: Unknown result type (might be due to invalid IL or missing references) //IL_082a: Unknown result type (might be due to invalid IL or missing references) //IL_075c: Unknown result type (might be due to invalid IL or missing references) //IL_0763: Unknown result type (might be due to invalid IL or missing references) //IL_07a0: Unknown result type (might be due to invalid IL or missing references) //IL_07a5: Unknown result type (might be due to invalid IL or missing references) //IL_07b3: Unknown result type (might be due to invalid IL or missing references) //IL_07b6: Unknown result type (might be due to invalid IL or missing references) //IL_07bb: Unknown result type (might be due to invalid IL or missing references) //IL_07c0: Unknown result type (might be due to invalid IL or missing references) //IL_07f5: 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_0940: Unknown result type (might be due to invalid IL or missing references) //IL_098f: Unknown result type (might be due to invalid IL or missing references) //IL_09a7: Unknown result type (might be due to invalid IL or missing references) object obj; if (Owner == null) { CharacterManager instance = CharacterManager.Instance; obj = ((instance != null) ? instance.GetFirstLocalCharacter() : null); } else { obj = Owner(); } Character val = (Character)obj; if ((Object)(object)val != (Object)null) { Target = ((Component)val).transform; } if ((Object)(object)Target == (Object)null || (Object)(object)_agent == (Object)null) { Idle(); return; } if (!CompanionRuntime.IsSanePosition(Target.position)) { if (Time.unscaledTime - _lastVoidLogAt > 5f) { _lastVoidLogAt = Time.unscaledTime; Vector3 position = Target.position; ManualLogSource log = CompanionRuntime.Log; if (log != null) { log.LogWarning((object)($"[PUPPET] 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).")); } } Idle(); return; } float num = Vector3.Distance(((Component)this).transform.position, Target.position); Transform val2 = (((Object)(object)CombatTarget != (Object)null) ? CombatTarget : (((Object)(object)FollowOverride != (Object)null) ? FollowOverride : Target)); float num2 = Vector3.Distance(((Component)this).transform.position, val2.position); bool flag = false; Vector3 zero = Vector3.zero; if ((Object)(object)CombatTarget == (Object)null && (Object)(object)FollowOverride == (Object)null && Time.time >= _faceUntil) { Vector3 forward = Target.forward; LoafPoint val3 = _loaf.Resolve(Target.position.x, Target.position.z, forward.x, forward.z, Time.deltaTime, Cfg.LoafDistanceMin, Cfg.LoafDistanceMax, Cfg.LoafRepickDistance); if (val3.Active) { ((Vector3)(ref zero))..ctor(val3.X, Target.position.y, val3.Z); flag = true; } } else { _loaf.Clear(); } Vector3 pos; bool flag2 = NavProbe.SampleAtFeet(val2.position, 2f, out pos); Vector3 val4; if (flag2 && pos.y - val2.position.y > 1.5f && Time.time - _roofLog > 2f) { _roofLog = Time.time; ManualLogSource log2 = CompanionRuntime.Log; object[] obj2 = new object[5] { pos.y - val2.position.y, ((Object)val2).name, null, null, null }; val4 = val2.position; obj2[2] = ((Vector3)(ref val4)).ToString("F1"); obj2[3] = ((Vector3)(ref pos)).ToString("F1"); obj2[4] = (Object)(object)CombatTarget != (Object)null; log2.LogMessage((object)string.Format("[DESYNC] goalGround {0:+0.0}m ABOVE goal '{1}': goal={2} sampled={3} combat={4}", obj2)); } ReconDesync(val2); if (!flag2) { DirectDrive(val2.position, (Object)(object)CombatTarget != (Object)null); return; } if (!((Behaviour)_agent).enabled) { ((Behaviour)_agent).enabled = true; if (!_agent.isOnNavMesh && !_agent.Warp(((Component)this).transform.position)) { _agent.Warp(pos); } CompanionRuntime.Log.LogMessage((object)"[PUPPET] direct-drive ended — navmesh back under the goal; agent re-armed."); } if (!_agent.isOnNavMesh || num > 50f) { if (Time.time - _warpCd > 0.4f) { _warpCd = Time.time; ((Behaviour)_agent).enabled = false; ((Component)this).transform.position = pos; ((Behaviour)_agent).enabled = true; ManualLogSource log3 = CompanionRuntime.Log; object arg = num; val4 = ((Component)this).transform.position; string arg2 = ((Vector3)(ref val4)).ToString("F0"); val4 = Target.position; log3.LogMessage((object)string.Format("[PUPPET] zone re-place onto goal polygon: dist={0:F0} me={1} target={2}", arg, arg2, ((Vector3)(ref val4)).ToString("F0"))); } Idle(); return; } _agent.speed = DriveSpeed; bool flag3 = false; Vector3 val5 = pos; if (flag) { if (NavProbe.SampleAtFeet(zero, 2f, out var pos2) && Mathf.Abs(pos2.y - pos.y) <= 1.5f) { val5 = pos2; flag3 = true; } else { _loaf.Reject(); } } _agent.stoppingDistance = (flag3 ? 0.6f : StopDistance); if ((Object)(object)CombatTarget != (Object)null && num2 <= Cfg.AttackRange) { _planted = true; } else if ((Object)(object)CombatTarget == (Object)null || num2 > Cfg.AttackRange + 1.5f) { _planted = false; } bool flag4 = (Object)(object)CombatTarget != (Object)null && _planted; bool flag5 = (Object)(object)CombatTarget == (Object)null && Time.time < _faceUntil; _agent.isStopped = flag4 || flag5; if (!flag4 && Time.time - _repath > 0.2f) { if (_agent.hasPath) { val4 = val5 - _lastDest; if (!(((Vector3)(ref val4)).sqrMagnitude > 0.16f)) { goto IL_05be; } } _agent.SetDestination(val5); _lastDest = val5; _repath = Time.time; } goto IL_05be; IL_05be: float num3 = (((Object)(object)FollowOverride != (Object)null) ? Vector3.Distance(((Component)this).transform.position, FollowOverride.position) : num); bool flag6 = (Object)(object)CombatTarget == (Object)null && Time.time < _leashGraceUntil; float num4 = (((Object)(object)CombatTarget != (Object)null || flag6) ? Cfg.CombatLeashDistance : LeashDistance); bool flag7 = !_agent.pathPending && (int)_agent.pathStatus > 0; if (flag6 && num3 <= LeashDistance) { _leashGraceUntil = -1f; flag6 = false; } if ((num3 > num4 || ((Object)(object)CombatTarget == (Object)null && !flag6 && flag7 && num3 > StopDistance + 2f)) && Time.time - _warpCd > 0.75f) { TryWarp(8f, force: false); } else if (num3 <= num4 && !flag7) { _warpBlockedSince = -1f; } Vector3 velocity = _agent.velocity; bool flag8 = ((Vector3)(ref velocity)).magnitude > 0.25f; if (((Vector3)(ref velocity)).magnitude > DriveSpeed * 2f + 1f && Time.time - _velAnomalyLog > 2f) { _velAnomalyLog = Time.time; Transform val6 = CompanionRef?.Invoke(); float num5 = (((Object)(object)val6 != (Object)null) ? Vector3.Distance(((Component)this).transform.position, val6.position) : (-1f)); ManualLogSource log4 = CompanionRuntime.Log; string text = $"[VEL] anomaly: vel={((Vector3)(ref velocity)).magnitude:F1} at drive speed {DriveSpeed:F1} — "; val4 = _agent.desiredVelocity; object arg3 = ((Vector3)(ref val4)).magnitude; val4 = pos - _lastDest; log4.LogWarning((object)(text + $"desVel={arg3:F1} destΔ={((Vector3)(ref val4)).magnitude:F2} " + $"anchorSep={num5:F2} radius={_agent.radius:F2} avoid={_agent.obstacleAvoidanceType}")); } Vector3 val7 = (((Object)(object)CombatTarget != (Object)null) ? CombatTarget.position : (flag5 ? _facePoint : (flag3 ? val5 : Target.position))); Vector3 heading = val7 - ((Component)this).transform.position; heading.y = 0f; Vector3 ownerDelta = Target.position - ((Component)this).transform.position; ownerDelta.y = 0f; DriveFacingAndAnim(heading, ((Vector3)(ref heading)).magnitude, flag8, ((Vector3)(ref velocity)).magnitude, ownerDelta, (Object)(object)CombatTarget == (Object)null && !flag5); if (Time.time - _diag > 2f) { _diag = Time.time; float num6 = (((Object)(object)_anim != (Object)null) ? _anim.GetFloat("moveForward") : (-99f)); ManualLogSource log5 = CompanionRuntime.Log; object[] obj3 = new object[8] { num, ((Vector3)(ref velocity)).magnitude, null, null, null, null, null, null }; val4 = _agent.desiredVelocity; obj3[2] = ((Vector3)(ref val4)).magnitude; obj3[3] = flag8; obj3[4] = num6; obj3[5] = (Object)(object)_anim != (Object)null && _anim.applyRootMotion; obj3[6] = ((Component)this).transform.position.y; obj3[7] = _agent.pathStatus; log5.LogMessage((object)string.Format("[PUPPET] dist={0:F1} vel={1:F2} desVel={2:F2} moving={3} mF={4:F2} rootMotion={5} y={6:F2} path={7}", obj3)); } } 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_0068: 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_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_013d: 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) 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; ManualLogSource log = CompanionRuntime.Log; object arg = num; Vector3 position = ((Component)this).transform.position; string arg2 = ((Vector3)(ref position)).ToString("F1"); position = FollowOverride.position; log.LogMessage((object)(string.Format("[DESYNC] sep={0:F1}m puppet={1} anchor={2} ", arg, arg2, ((Vector3)(ref position)).ToString("F1")) + 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() { _rig?.PinTick(); this.OnAfterMove?.Invoke(this); } private void DriveFacingAndAnim(Vector3 heading, float headingDist, bool isMoving, float animValue, Vector3 ownerDelta, bool idleReAimEligible) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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) //IL_009b: 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_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_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_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_0101: 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_0115: 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_00b8: 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_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) //IL_00d6: 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_00f2: 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_00fa: 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_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_0180: 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_018a: 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_019d: 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_01af: Unknown result type (might be due to invalid IL or missing references) Vector3 val; if (headingDist > 2f && ((Vector3)(ref heading)).sqrMagnitude > 0.01f) { val = Vector3.Slerp(_faceDir, ((Vector3)(ref heading)).normalized, Time.deltaTime * 6f); _faceDir = ((Vector3)(ref val)).normalized; _idleFacing.Reset(); } else if (idleReAimEligible) { IdleFacingDecision val2 = _idleFacing.Decide(_faceDir.x, _faceDir.z, ownerDelta.x, ownerDelta.z, 40f, 8f, 1f, 120f); if (val2.ReAim && ((Vector3)(ref _faceDir)).sqrMagnitude > 1E-06f) { Vector3 val3 = default(Vector3); ((Vector3)(ref val3))..ctor(val2.DirX, 0f, val2.DirZ); val = Vector3.RotateTowards(_faceDir, val3, val2.TurnRateDegPerSec * ((float)Math.PI / 180f) * Time.deltaTime, 0f); _faceDir = ((Vector3)(ref val)).normalized; } else if (val2.ReAim) { _faceDir = new Vector3(val2.DirX, 0f, val2.DirZ); } } else { _idleFacing.Reset(); } if (((Vector3)(ref _faceDir)).sqrMagnitude > 0.01f) { float num = (float.IsNaN(YawOffset) ? Cfg.ModelYawOffset : YawOffset); Quaternion val4 = Quaternion.LookRotation(_faceDir, Vector3.up) * Quaternion.Inverse(Quaternion.Euler(0f, num, 0f)); ((Component)this).transform.rotation = Quaternion.RotateTowards(((Component)this).transform.rotation, val4, 540f * Time.deltaTime); } if ((Object)(object)_anim != (Object)null) { _anim.SetBool("IsMoving", isMoving); _anim.SetFloat("moveForward", isMoving ? Mathf.Clamp(animValue, 0f, 3f) : 0f); _anim.SetFloat("moveSide", 0f); } } public void FindDrift() { ((MonoBehaviour)this).StartCoroutine(DriftScan()); } private IEnumerator DriftScan() { Transform[] all = ((Component)this).GetComponentsInChildren(true); Vector3[] start = all.Select((Transform t) => ((Component)this).transform.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(((Component)this).transform.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)((Component)this).transform)) { val = val.parent; continue; } break; } } private void DirectDrive(Vector3 targetPos, bool combat) { //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_0034: 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) //IL_003b: 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_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_0089: 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_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_00a9: 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_00f1: 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_0117: 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_0105: 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_012c: 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_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) if (((Behaviour)_agent).enabled) { ((Behaviour)_agent).enabled = false; CompanionRuntime.Log.LogMessage((object)"[PUPPET] direct-drive: no baked navmesh at the goal — transform drive with raycast grounding (agent re-arms when mesh returns)."); } Vector3 val = ((Component)this).transform.position; Vector3 heading = targetPos - val; heading.y = 0f; float magnitude = ((Vector3)(ref heading)).magnitude; float num = (combat ? (Cfg.AttackRange * 0.9f) : StopDistance); float num2 = ((magnitude > num) ? DriveSpeed : 0f); if (num2 > 0f) { val += ((Vector3)(ref heading)).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; Vector3 ownerDelta = (((Object)(object)Target != (Object)null) ? (Target.position - val) : Vector3.zero); ownerDelta.y = 0f; DriveFacingAndAnim(heading, magnitude, num2 > 0f, num2, ownerDelta, !combat && (Object)(object)Target != (Object)null); if (Time.time - _diag > 2f) { _diag = Time.time; ManualLogSource log = CompanionRuntime.Log; object[] obj = new object[4] { 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[3] = obj2; log.LogMessage((object)string.Format("[PUPPET] direct-drive: goalDist={0:F1} speed={1:F1} y={2:F2} anchor={3}", 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_000f: 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_0026: 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) ((Behaviour)_agent).enabled = false; spot.y = GroundY(spot); ((Component)this).transform.position = spot; if (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("[PUPPET] placed at {0} (onMesh={1}).", ((Vector3)(ref spot)).ToString("F1"), ((Behaviour)_agent).enabled)); } private void Idle() { if ((Object)(object)_anim != (Object)null) { _anim.SetBool("IsMoving", false); _anim.SetFloat("moveForward", 0f); } } 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 void Resummon() { TryWarp(20f, force: true); } private void TryWarp(float searchRadius, bool force) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_006b: 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_00b0: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_agent == (Object)null) && !((Object)(object)Target == (Object)null)) { float radius = (force ? searchRadius : ((_warpBlockedSince >= 0f && Time.time - _warpBlockedSince > 6f) ? 3f : 1.5f)); if (NavProbe.SampleAtFeet(Target.position, radius, out var pos)) { _agent.Warp(pos); _warpCd = Time.time; _warpBlockedSince = -1f; CompanionRuntime.Log.LogMessage((object)string.Format("[PUPPET] warped to player (force={0}) spot={1} Δy={2:+0.0}", force, ((Vector3)(ref pos)).ToString("F1"), pos.y - Target.position.y)); } else if (force) { CompanionRuntime.Log.LogWarning((object)"[PUPPET] resummon: no navmesh near the player to warp onto."); } else if (_warpBlockedSince < 0f) { _warpBlockedSince = Time.time; CompanionRuntime.Log.LogMessage((object)"[PUPPET] leash-warp blocked: no navmesh at the player's feet (r=1.5m); retrying (widens to 3m after 6s)."); } } } public void DumpPositions() { ((MonoBehaviour)this).StartCoroutine(PosDump()); } private IEnumerator PosDump() { for (int i = 0; i < 90; i++) { Vector3 position = ((Component)this).transform.position; Vector3 val = (((Object)(object)_agent != (Object)null) ? _agent.nextPosition : position); ManualLogSource log = CompanionRuntime.Log; object[] obj = new object[8] { i, position.x, position.z, Vector3.Distance(position, val), ((Component)this).transform.eulerAngles.y, null, null, null }; float num; if (!((Object)(object)_agent != (Object)null)) { num = 0f; } else { Vector3 velocity = _agent.velocity; num = ((Vector3)(ref velocity)).magnitude; } obj[5] = num; obj[6] = (Object)(object)_agent != (Object)null && _agent.isStopped; obj[7] = (((Object)(object)_anim != (Object)null) ? _anim.GetFloat("moveForward") : (-1f)); log.LogMessage((object)string.Format("[POS] f{0} tf=({1:F2},{2:F2}) gap={3:F2} rotY={4:F0} vel={5:F2} stop={6} mF={7:F1}", obj)); yield return null; } } } public class CompanionCombat : MonoBehaviour { public Action OnEnemyDefeated; public float DamageMultiplier = 1f; public CompanionAnchor Anchor; public CommandStance Stance; public ICompanionSettings Settings; public Func Owner; public bool WeaponPosture; private CommandStance _localStance; private bool _stanceFallbackWarned; 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 Character _target; private float _nextScan; private float _nextAttack; private Character _prevTarget; private bool _prevHadTarget; private PunctualDamage _hit; private MethodInfo _activateLocally; private bool _pipelineBroken; private bool _loggedPipeline; private bool _weaponDrawn; private ICompanionSettings Cfg => Settings ?? CompanionRuntime.DefaultSettings; private string TagCombat => CompanionRuntime.Tag("PETCOMBAT", Cfg); private CommandStance St { get { if (Stance != null) { return Stance; } if (!_stanceFallbackWarned) { _stanceFallbackWarned = true; CompanionRuntime.Log.LogWarning((object)(TagCombat + " no CommandStance injected — using a private local stance (AdoptBody should have injected the bond's stance; this masks a wiring bug).")); } return _localStance ?? (_localStance = new CommandStance()); } } public Character CurrentTarget => _target; public Character SpecialAttackTarget => _target ?? St.CommandedTarget; public float BaseDamage => ((_dmgProfileTotal > 0f) ? _dmgProfileTotal : Cfg.AttackDamage) * Mathf.Max(0f, DamageMultiplier); private Character ResolveOwner() { if (Owner == null) { CharacterManager instance = CharacterManager.Instance; if (instance == null) { return null; } return instance.GetFirstLocalCharacter(); } 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(); _anim = ((Component)this).GetComponent(); _sound = ((Component)this).GetComponent(); if (!((Object)(object)_anim != (Object)null)) { return; } AnimatorControllerParameter[] parameters = _anim.parameters; foreach (AnimatorControllerParameter val in parameters) { if (val.name == "Attack1") { _hasAttackAnim = true; break; } } } private void Update() { //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_038b: Unknown result type (might be due to invalid IL or missing references) //IL_039b: 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_0208: 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) //IL_020c: 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_0213: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Expected I4, but got Unknown //IL_02df: Unknown result type (might be due to invalid IL or missing references) //IL_02f7: Unknown result type (might be due to invalid IL or missing references) //IL_02fc: 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(); return; } if ((Object)(object)_target != (Object)null && !_target.Alive) { OnEnemyDefeated?.Invoke(); _target = null; } if (St.Passive) { if ((Object)(object)_target != (Object)null || (Object)(object)_follower.CombatTarget != (Object)null) { Disengage(); Anchor?.Calm(); CompanionRuntime.Log.LogMessage((object)(TagCombat + " stance: PASSIVE — returning to the player until re-engaged.")); } return; } if (Time.time - _nextScan > 0.3f) { _nextScan = Time.time; Character commandedTarget = St.CommandedTarget; Character val2 = AnchorLockCandidate(); List engagedCharacters = val.EngagedCharacters; Character val3 = NearestEngagedAI(val, engagedCharacters); CombatTargetFacts val4 = new CombatTargetFacts { StancePassive = St.Passive, CommandedExists = ((Object)(object)commandedTarget != (Object)null), CommandedDistance = (((Object)(object)commandedTarget != (Object)null) ? DistTo(commandedTarget) : 0f), AnchorDefendExists = ((Object)(object)val2 != (Object)null), AnchorDefendIsEcho = ((Object)(object)val2 != (Object)null && (Object)(object)val2 == (Object)(object)Anchor?.LastAssertedTarget), AnchorDefendDistance = (((Object)(object)val2 != (Object)null) ? DistTo(val2) : 0f), PlayerInCombat = (val.InCombat && engagedCharacters != null), PlayerEngagedExists = ((Object)(object)val3 != (Object)null), PlayerEngagedDistance = (((Object)(object)val3 != (Object)null) ? DistTo(val3) : 0f), AggroRange = Cfg.AggroRange, CombatLeashDistance = Cfg.CombatLeashDistance }; CombatTargetDecision val5 = CombatTargetPolicy.Decide(val4); if (val5.DropCommandedOrder) { St.DropCommanded(); } CombatTargetSource source = val5.Source; switch (source - 1) { case 0: _target = commandedTarget; break; case 1: _target = val2; break; case 2: _target = val3; break; default: _target = null; break; } if ((Object)(object)_target != (Object)(object)_prevTarget || (Object)(object)_target != (Object)null != _prevHadTarget) { ManualLogSource log = CompanionRuntime.Log; string[] obj = new string[10] { TagCombat, " target: ", NameOf(_prevTarget), " -> ", NameOf(_target), " (", val5.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); float num = Vector3.Distance(((Component)this).transform.position, ((Component)_target).transform.position); if (num <= Cfg.AttackRange && Time.time - _nextAttack > Cfg.AttackInterval) { _nextAttack = 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 static string NameOf(Character c) { if (!((Object)(object)c != (Object)null)) { return "none"; } return c.Name; } private Character AnchorLockCandidate() { if (Anchor == null || !Anchor.HasLiveAnchor) { return null; } 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 null; } return val; } 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_0032: 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_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_0058: Unknown result type (might be due to invalid IL or missing references) if (_hasAttackAnim) { _anim.SetTrigger("Attack1"); } 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 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; CompanionRuntime.Log.LogWarning((object)(TagCombat + " species attack vocal failed (won't retry-log): " + ex.Message)); } } } public void DealDamage(Character target, float dmg, Vector3 dir) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) DealDamageWith(target, dmg, dir, BuildDamages(dmg)); } 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) { //IL_0062: 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_003a: Unknown result type (might be due to invalid IL or missing references) Character val = ResolveOwner(); bool flag = Anchor != null && Anchor.HasLiveAnchor; Character val2 = (flag ? Anchor.Current : val); if (!_pipelineBroken && TryPipelineHit(target, dir, damages)) { if (flag) { ((Component)target).SendMessage("CharHurt", (object)Anchor.Current, (SendMessageOptions)1); } } else { target.ReceiveHit((Weapon)null, dmg, dir, target.CenterPosition, 45f, 1f, val2, _impact); } } private bool TryPipelineHit(Character target, Vector3 dir, DamageType[] damages) { //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: 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]; _activateLocally = typeof(PunctualDamage).GetMethod("ActivateLocally", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[2] { typeof(Character), typeof(object[]) }, null); } if (_activateLocally == null) { return false; } _hit.Damages = damages; _hit.Knockback = _impact; _activateLocally.Invoke(_hit, new object[2] { target, new object[2] { ((Component)this).transform.position, dir } }); if (!_loggedPipeline) { _loggedPipeline = true; CompanionRuntime.Log.LogMessage((object)(TagCombat + " dealing damage via DealHit pipeline (CombatHUD-visible).")); } return true; } catch (Exception ex) { _pipelineBroken = true; CompanionRuntime.Log.LogWarning((object)(TagCombat + " DealHit pipeline failed; using direct ReceiveHit instead: " + ex.Message)); return false; } } private void SetWeaponDrawn(bool drawn) { if (!WeaponPosture || (Object)(object)_anim == (Object)null || _weaponDrawn == drawn) { return; } _weaponDrawn = drawn; AnimatorControllerParameter[] parameters = _anim.parameters; foreach (AnimatorControllerParameter val in parameters) { if (val.name == "Sheathed") { _anim.SetBool("Sheathed", !drawn); } if (drawn && val.name == "Unsheathe") { _anim.SetTrigger("Unsheathe"); } if (!drawn && val.name == "Sheathe") { _anim.SetTrigger("Sheathe"); } } CompanionRuntime.Log.LogMessage((object)(TagCombat + " weapon posture: " + (drawn ? "drawn" : "sheathed") + ".")); } 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(); } } public static class CompanionRuntime { public static ManualLogSource Log; public static ICompanionSettings DefaultSettings; 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 string Tag(string name, ICompanionSettings cfg) { string text = cfg?.LogTagSuffix; if (!string.IsNullOrEmpty(text)) { return "[" + name + "/" + text + "]"; } return "[" + name + "]"; } } public interface ICompanionSettings { float AttackDamage { get; } float AttackInterval { get; } float AggroRange { get; } float AttackRange { get; } float CombatLeashDistance { get; } float DisengageRunHomeSeconds { get; } bool AttackVocals { get; } bool AnchorInvisible { get; } bool AnchorShowHealthBar { get; } bool AnchorLinkSummonSlot { get; } float AnchorLeashDistance { get; } float AnchorRespawnSeconds { get; } bool AnchorDealsDamage { get; } bool SpeciesVoice { get; } AnchorGlueMode GlueMode { get; } float GlueOffsetBehind { get; } bool UnifyTargets { get; } string GhostPrefabName { get; } AnchorCollisionMode AnchorPlayerCollision { get; } float ModelYawOffset { 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 => 40f; public virtual float DisengageRunHomeSeconds => 12f; public virtual bool AttackVocals => true; public virtual bool AnchorInvisible => true; public virtual bool AnchorShowHealthBar => false; public virtual bool AnchorLinkSummonSlot => true; public virtual float AnchorLeashDistance => 20f; public virtual float AnchorRespawnSeconds => 60f; public virtual bool AnchorDealsDamage => false; 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 float ModelYawOffset => 180f; public virtual float LoafDistanceMin => 2f; public virtual float LoafDistanceMax => 0f; public virtual float LoafRepickDistance => 3f; public virtual string LogTagSuffix => null; } public static class DonorHarvest { private static Dictionary> _donorScenes; private static Dictionary _donorPins; private static int _harvestCount; private const float LoadHangWarnSeconds = 30f; private static FieldInfo _timelessField; private static FieldInfo _startTimesField; private static FieldInfo _endTimesField; private static FieldInfo _zonesWaitingField; private static bool _reflectionReady; public static Dictionary> DonorScenes { get { if (_donorScenes == null) { LoadDonorScenes(); } return _donorScenes; } } public static Dictionary DonorPins { get { if (_donorScenes == null) { LoadDonorScenes(); } return _donorPins; } } public static string PinFor(string tableKey) { if (string.IsNullOrEmpty(tableKey)) { return null; } if (!DonorPins.TryGetValue(tableKey.Trim(), out var value)) { return null; } return value; } public static string IdentityFor(string tableKey, Character donor) { string result = (((Object)(object)donor != (Object)null) ? donor.Name : null); if (string.IsNullOrEmpty(tableKey)) { return result; } if (PinFor(tableKey) == null) { return result; } return tableKey.Trim(); } private static void LoadDonorScenes() { string text = ReadEmbeddedDonorScenes(); Dictionary> dictionary = DonorTable.Parse(text); Dictionary dictionary2 = DonorTable.ParsePins(text); CompanionRuntime.Log.LogMessage((object)(string.Format("[HARVEST] loaded {0} built-in donor species entr{1}", dictionary.Count, (dictionary.Count == 1) ? "y" : "ies") + ((dictionary2.Count > 0) ? $", {dictionary2.Count} of them pinned to a donor GameObject name" : "") + ".")); try { string path = Path.Combine(Paths.ConfigPath, "DonorScenes.txt"); if (File.Exists(path)) { string text2 = File.ReadAllText(path); Dictionary> dictionary3 = DonorTable.Parse(text2); dictionary = DonorTable.Merge(dictionary, dictionary3); dictionary2 = DonorTable.MergePins(dictionary2, DonorTable.ParsePins(text2)); CompanionRuntime.Log.LogMessage((object)string.Format("[HARVEST] merged {0} donor species entr{1} from config override (tried first).", dictionary3.Count, (dictionary3.Count == 1) ? "y" : "ies")); } } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[HARVEST] DonorScenes.txt override load failed: " + ex.Message)); } _donorScenes = dictionary; _donorPins = dictionary2; } private static string ReadEmbeddedDonorScenes() { Assembly executingAssembly = Assembly.GetExecutingAssembly(); string text = Array.Find(executingAssembly.GetManifestResourceNames(), (string n) => n.EndsWith("DonorScenes.txt", StringComparison.OrdinalIgnoreCase)); if (text == null) { CompanionRuntime.Log.LogWarning((object)"[HARVEST] embedded DonorScenes.txt not found."); return ""; } using Stream stream = executingAssembly.GetManifestResourceStream(text); using StreamReader streamReader = new StreamReader(stream); return streamReader.ReadToEnd(); } public static bool TryGetDonorScenes(string speciesId, out List sceneNames, out string searchTerm) { //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) sceneNames = null; List list = default(List); if (!SpeciesTable.TryResolveKey>(DonorScenes, speciesId, ref searchTerm, ref list, (string)null) || list == null || list.Count == 0) { return false; } List list3 = default(List); List list2 = DonorTable.FilterViable((IEnumerable)list, ref list3); if (list3.Count > 0) { CompanionRuntime.Log.LogWarning((object)string.Format("[HARVEST] '{0}': skipped {1} OVERSIZED donor candidate(s) ({2}) — region terrains/towns are never loaded as donors (global-state stomp + crash risk; find a dungeon donor via donorspot).", searchTerm, list3.Count, string.Join(", ", list3))); } if (list2.Count == 0) { CompanionRuntime.Log.LogWarning((object)("[HARVEST] '" + searchTerm + "' has NO additive donor — every candidate is an oversized region/town scene (" + string.Join(", ", list3) + "). Reach it via an EXPEDITION instead: run 'expedition " + searchTerm + "' (or add a dungeon donor to its manifest).")); return false; } Scene activeScene = SceneManager.GetActiveScene(); sceneNames = DonorTable.OrderCandidates((IEnumerable)list2, ((Scene)(ref activeScene)).name); return true; } public static bool TryGetExpeditionScenes(string speciesId, out List sceneNames, out string searchTerm) { sceneNames = null; List list = default(List); if (!SpeciesTable.TryResolveKey>(DonorScenes, speciesId, ref searchTerm, ref list, (string)null) || list == null || list.Count == 0) { return false; } List list2 = DonorTable.ExpeditionCandidates((IEnumerable)list); if (list2.Count == 0) { return false; } sceneNames = list2; return true; } public static IEnumerator HarvestChain(List sceneNames, string creatureName, Func use, Action onResult) { object result = null; string fallbackScene = null; for (int i = 0; i < sceneNames.Count; i++) { if (result != null) { break; } if (sceneNames.Count > 1) { CompanionRuntime.Log.LogMessage((object)$"[HARVEST] chain {i + 1}/{sceneNames.Count} for '{creatureName}': '{sceneNames[i]}'."); } bool acceptSubstring = DonorChain.AcceptSubstringAt(i, sceneNames.Count, fallbackScene != null); object r = null; int rank = 0; yield return HarvestRanked(sceneNames[i], creatureName, use, acceptSubstring, delegate(object res, int rk) { r = res; rank = rk; }); if (r != null) { result = r; break; } if (rank == 1 && fallbackScene == null) { fallbackScene = sceneNames[i]; } } if (result == null && fallbackScene != null) { CompanionRuntime.Log.LogMessage((object)("[HARVEST] no exact-name '" + creatureName + "' donor in the chain — falling back to the substring donor in '" + fallbackScene + "'.")); object r2 = null; yield return HarvestRanked(fallbackScene, creatureName, use, acceptSubstring: true, delegate(object res, int rk) { r2 = res; }); result = r2; } if (result == null && sceneNames.Count > 1) { CompanionRuntime.Log.LogWarning((object)("[HARVEST] chain exhausted — no candidate yielded a live '" + creatureName + "'.")); } onResult(result); } private static IEnumerator HarvestRanked(string sceneName, string creatureName, Func use, bool acceptSubstring, Action onResult) { int foundRank = 0; yield return HarvestScene(sceneName, "creature='" + creatureName + "'", delegate(Scene donor) { //IL_0000: 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_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) int rank; Character val = FindLiveInScene(donor, creatureName, out rank); foundRank = rank; if ((Object)(object)val == (Object)null) { CompanionRuntime.Log.LogWarning((object)("[HARVEST] no live '" + creatureName + "' found among donor scene roots.")); DumpSceneContents(donor); return (object)null; } if (rank < 2 && !acceptSubstring) { CompanionRuntime.Log.LogMessage((object)("[HARVEST] '" + sceneName + "' has only a substring donor '" + val.Name + "' for '" + creatureName + "' — deferring in case a later scene holds an exact-name '" + creatureName + "'.")); return (object)null; } if (rank < 2) { CompanionRuntime.Log.LogMessage((object)("[HARVEST] no exact-name donor for '" + creatureName + "' — using nearest substring match '" + val.Name + "' in '" + sceneName + "'.")); } ManualLogSource log = CompanionRuntime.Log; string[] obj = new string[5] { "[HARVEST] found donor '", val.Name, "' at ", null, null }; Vector3 position = ((Component)val).transform.position; obj[3] = ((Vector3)(ref position)).ToString("F1"); obj[4] = " — running payload."; log.LogMessage((object)string.Concat(obj)); return use(val); }, delegate(object r) { onResult.Invoke(r, foundRank); }); } public static IEnumerator HarvestChain(List sceneNames, string creatureName, Character player, Action onBody, string rangedProjectileFilter = null, int rangedSkillPrefabId = 0) { return HarvestChain(sceneNames, creatureName, (Character src) => BodyFactory.BuildPuppet(src, player, consume: false, rangedProjectileFilter, rangedSkillPrefabId), delegate(object r) { onBody((CompanionBody)r); }); } public static string ResolveBuildScene(string want, out List similar) { similar = new List(); int sceneCountInBuildSettings = SceneManager.sceneCountInBuildSettings; for (int i = 0; i < sceneCountInBuildSettings; i++) { string scenePathByBuildIndex = SceneUtility.GetScenePathByBuildIndex(i); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(scenePathByBuildIndex); if (string.Equals(fileNameWithoutExtension, want, StringComparison.OrdinalIgnoreCase)) { return fileNameWithoutExtension; } if (similar.Count < 8 && (fileNameWithoutExtension.IndexOf(want, StringComparison.OrdinalIgnoreCase) >= 0 || want.IndexOf(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) >= 0)) { similar.Add(fileNameWithoutExtension); } } return null; } public static void DumpBuildScenes() { int sceneCountInBuildSettings = SceneManager.sceneCountInBuildSettings; CompanionRuntime.Log.LogMessage((object)$"[SCENEDUMP] {sceneCountInBuildSettings} scenes in build settings."); for (int i = 0; i < sceneCountInBuildSettings; i++) { string scenePathByBuildIndex = SceneUtility.GetScenePathByBuildIndex(i); CompanionRuntime.Log.LogMessage((object)$"[SCENEDUMP] {i}: {scenePathByBuildIndex}"); } } public static IEnumerator Harvest(string sceneName, string creatureName, Character player, Action onBody, string rangedProjectileFilter = null, int rangedSkillPrefabId = 0) { return Harvest(sceneName, creatureName, (Character src) => BodyFactory.BuildPuppet(src, player, consume: false, rangedProjectileFilter, rangedSkillPrefabId), delegate(object r) { onBody((CompanionBody)r); }); } public static IEnumerator Harvest(string sceneName, string creatureName, Func use, Action onResult) { return HarvestScene(sceneName, "creature='" + creatureName + "'", delegate(Scene donor) { //IL_0000: 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_006e: 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) Character val = FindLiveInScene(donor, creatureName); if ((Object)(object)val == (Object)null) { CompanionRuntime.Log.LogWarning((object)("[HARVEST] no live '" + creatureName + "' found among donor scene roots.")); DumpSceneContents(donor); return (object)null; } ManualLogSource log = CompanionRuntime.Log; string[] obj = new string[5] { "[HARVEST] found donor '", val.Name, "' at ", null, null }; Vector3 position = ((Component)val).transform.position; obj[3] = ((Vector3)(ref position)).ToString("F1"); obj[4] = " — running payload."; log.LogMessage((object)string.Concat(obj)); return use(val); }, onResult); } public static IEnumerator HarvestScene(string sceneName, string label, Func useScene, Action onResult) { CompanionRuntime.Log.LogMessage((object)("[HARVEST] start: scene='" + sceneName + "' " + label)); float t0 = Time.realtimeSinceStartup; Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name == sceneName) { CompanionRuntime.Log.LogWarning((object)("[HARVEST] donor scene '" + sceneName + "' IS the current scene — refusing (re-form from a wild one instead).")); onResult(null); yield break; } if (DonorPhotonGuard.WindowActive) { CompanionRuntime.Log.LogMessage((object)("[HARVEST] another harvest's guard window is open — '" + sceneName + "' waiting its turn (harvests are serialized).")); while (DonorPhotonGuard.WindowActive) { yield return null; } CompanionRuntime.Log.LogMessage((object)$"[HARVEST] '{sceneName}' proceeding after {Time.realtimeSinceStartup - t0:F2}s wait."); } int guardToken = DonorPhotonGuard.Begin(sceneName); GlobalAudioManager realAudio = Global.AudioManager; GlobalCombatManager realCombat = Global.CombatManager; EnvironmentConditions realEnv = EnvironmentConditions.Instance; AISquadManager realSquads = AISquadManager.Instance; Material realSkybox = RenderSettings.skybox; SkySnapshot.Log("pre-harvest '" + sceneName + "'"); int frozenAtLoad = 0; UnityAction freezeOnLoad = delegate(Scene s, LoadSceneMode m) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 if ((int)m == 1 && !(((Scene)(ref s)).name != sceneName)) { GameObject[] rootGameObjects4 = ((Scene)(ref s)).GetRootGameObjects(); foreach (GameObject val6 in rootGameObjects4) { val6.SetActive(false); frozenAtLoad++; } } }; SceneManager.sceneLoaded += freezeOnLoad; object result = null; try { AsyncOperation load; try { load = SceneManager.LoadSceneAsync(sceneName, (LoadSceneMode)1); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[HARVEST] LoadSceneAsync threw: " + ex.Message)); onResult(null); yield break; } if (load == null) { CompanionRuntime.Log.LogWarning((object)("[HARVEST] LoadSceneAsync returned null — bad scene name '" + sceneName + "'?")); onResult(null); yield break; } float hangWarnAt = Time.realtimeSinceStartup + 30f; while (!load.isDone) { DonorPhotonGuard.Refresh(guardToken); if (hangWarnAt > 0f && Time.realtimeSinceStartup >= hangWarnAt) { CompanionRuntime.Log.LogWarning((object)$"[HARVEST] donor scene '{sceneName}' load still not done after {30f:F0}s — possible hang (check output_log.txt for Unity-side load errors)."); hangWarnAt = -1f; } yield return null; } DonorPhotonGuard.Refresh(guardToken); RestoreAudioManager(realAudio); RestoreCombatManager(realCombat); RestoreEnvironment(realEnv); RestoreSquadManager(realSquads); RestoreSkybox(realSkybox, "load-done"); Scene sceneByName = SceneManager.GetSceneByName(sceneName); if (!((Scene)(ref sceneByName)).IsValid()) { CompanionRuntime.Log.LogWarning((object)("[HARVEST] donor scene '" + sceneName + "' not valid after load.")); onResult(null); yield break; } CompanionRuntime.Log.LogMessage((object)$"[HARVEST] donor scene loaded in {Time.realtimeSinceStartup - t0:F2}s ({((Scene)(ref sceneByName)).rootCount} roots)."); int num = 0; GameObject[] rootGameObjects = ((Scene)(ref sceneByName)).GetRootGameObjects(); foreach (GameObject val in rootGameObjects) { num += val.GetComponentsInChildren(true).Length; } CompanionRuntime.Log.LogMessage((object)$"[PHOTON-GUARD] donor scene carries {num} PhotonView(s)."); GameObject[] rootGameObjects2 = ((Scene)(ref sceneByName)).GetRootGameObjects(); foreach (GameObject val2 in rootGameObjects2) { Camera[] componentsInChildren = val2.GetComponentsInChildren(true); foreach (Camera val3 in componentsInChildren) { CompanionRuntime.Log.LogMessage((object)$"[DISPLAY-FIX] donor scene carries a Camera '{((Object)val3).name}' (active={((Component)val3).gameObject.activeInHierarchy}, CameraQuality={(Object)(object)((Component)val3).GetComponent() != (Object)null}) — potential display-chain clobberer."); } } try { DetachAmbience(sceneByName); GameObject[] rootGameObjects3 = ((Scene)(ref sceneByName)).GetRootGameObjects(); foreach (GameObject val4 in rootGameObjects3) { val4.SetActive(false); } result = useScene(sceneByName); } catch (Exception ex2) { CompanionRuntime.Log.LogWarning((object)("[HARVEST] processing threw: " + ex2.Message + "\n" + ex2.StackTrace)); } float u0 = Time.realtimeSinceStartup; DonorPhotonGuard.Refresh(guardToken); AsyncOperation val5 = SceneManager.UnloadSceneAsync(sceneByName); if (val5 != null) { yield return val5; } CompanionRuntime.Log.LogMessage((object)$"[HARVEST] donor scene unloaded in {Time.realtimeSinceStartup - u0:F2}s."); float realtimeSinceStartup = Time.realtimeSinceStartup; try { LightProbes.Tetrahedralize(); CompanionRuntime.Log.LogMessage((object)$"[PROBE-FIX] light probes re-tetrahedralized in {Time.realtimeSinceStartup - realtimeSinceStartup:F2}s (LightProbesManager crash mitigation)."); } catch (Exception ex3) { CompanionRuntime.Log.LogWarning((object)("[PROBE-FIX] Tetrahedralize failed: " + ex3.Message)); } RestoreSkybox(realSkybox, "post-unload"); RepairDisplayChain("post-unload"); SkySnapshot.Log("post-unload"); if ((Object)(object)Plugin.Instance != (Object)null) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(DeferredSkyCheck(realSkybox)); } } finally { SceneManager.sceneLoaded -= freezeOnLoad; DonorPhotonGuard.End(guardToken); } if (frozenAtLoad > 0) { CompanionRuntime.Log.LogMessage((object)$"[HARVEST] froze {frozenAtLoad} donor root(s) at sceneLoaded — donor Start/Update never ran (global-state protection)."); } else { CompanionRuntime.Log.LogWarning((object)"[HARVEST] sceneLoaded freeze did NOT fire — donor scripts may have run Start/Update (watch for [DISPLAY-FIX]/global-state symptoms)."); } PruneDeadSoundPlayers(); SoulSpotGuard.PruneDead(); AuditAudioRegistries("post-harvest"); CompanionRuntime.Log.LogMessage((object)string.Format("[HARVEST] done in {0:F2}s total — result={1}.", Time.realtimeSinceStartup - t0, (result != null) ? "OK" : "FAILED")); onResult(result); } private static void RestoreAudioManager(GlobalAudioManager original) { //IL_0056: 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) GlobalAudioManager audioManager = Global.AudioManager; if ((Object)(object)original == (Object)null) { if ((Object)(object)audioManager != (Object)null) { CompanionRuntime.Log.LogWarning((object)"[AMBIENCE-FIX] no audio manager existed pre-load but one does now — leaving it in place."); } } else if (!((Object)(object)audioManager == (Object)(object)original)) { Global.AudioManager = original; ManualLogSource log = CompanionRuntime.Log; object obj; if (!((Object)(object)audioManager == (Object)null)) { Scene scene = ((Component)audioManager).gameObject.scene; obj = "'" + ((Scene)(ref scene)).name + "'s"; } else { obj = "already destroyed"; } log.LogMessage((object)("[AMBIENCE-FIX] donor GlobalAudioManager hijacked the singleton (found " + (string?)obj + ") — restored the real one (bug-2 fix).")); } } private static void RestoreCombatManager(GlobalCombatManager original) { //IL_0056: 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) GlobalCombatManager combatManager = Global.CombatManager; if ((Object)(object)original == (Object)null) { if ((Object)(object)combatManager != (Object)null) { CompanionRuntime.Log.LogWarning((object)"[COMBAT-FIX] no combat manager existed pre-load but one does now — leaving it in place."); } } else if (!((Object)(object)combatManager == (Object)(object)original)) { Global.CombatManager = original; ManualLogSource log = CompanionRuntime.Log; object obj; if (!((Object)(object)combatManager == (Object)null)) { Scene scene = ((Component)combatManager).gameObject.scene; obj = "'" + ((Scene)(ref scene)).name + "'s"; } else { obj = "already destroyed"; } log.LogMessage((object)("[COMBAT-FIX] donor GlobalCombatManager hijacked the singleton (found " + (string?)obj + ") — restored the real one (bug-12 fix).")); } } private static void RestoreEnvironment(EnvironmentConditions original) { //IL_0056: 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) EnvironmentConditions instance = EnvironmentConditions.Instance; if ((Object)(object)original == (Object)null) { if ((Object)(object)instance != (Object)null) { CompanionRuntime.Log.LogWarning((object)"[ENV-FIX] no EnvironmentConditions existed pre-load but one does now — leaving it in place."); } } else if (!((Object)(object)instance == (Object)(object)original)) { EnvironmentConditions.Instance = original; ManualLogSource log = CompanionRuntime.Log; object obj; if (!((Object)(object)instance == (Object)null)) { Scene scene = ((Component)instance).gameObject.scene; obj = "'" + ((Scene)(ref scene)).name + "'s"; } else { obj = "already destroyed"; } log.LogMessage((object)("[ENV-FIX] donor EnvironmentConditions hijacked the singleton (found " + (string?)obj + ") — restored the real one (bug-12 true root cause).")); } } private static void RestoreSquadManager(AISquadManager original) { //IL_0056: 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) AISquadManager instance = AISquadManager.Instance; if ((Object)(object)original == (Object)null) { if ((Object)(object)instance != (Object)null) { CompanionRuntime.Log.LogWarning((object)"[SQUAD-FIX] no AISquadManager existed pre-load but one does now — leaving it in place."); } } else if (!((Object)(object)instance == (Object)(object)original)) { AISquadManager.Instance = original; ManualLogSource log = CompanionRuntime.Log; object obj; if (!((Object)(object)instance == (Object)null)) { Scene scene = ((Component)instance).gameObject.scene; obj = "'" + ((Scene)(ref scene)).name + "'s"; } else { obj = "already destroyed"; } log.LogMessage((object)("[SQUAD-FIX] donor AISquadManager hijacked the singleton (found " + (string?)obj + ") — restored the real one (bug-16 fix).")); } } private static void RestoreSkybox(Material original, string phase) { bool flag = phase != "load-done"; Material skybox = RenderSettings.skybox; if ((Object)(object)original == (Object)null) { if ((Object)(object)skybox != (Object)null && !flag) { CompanionRuntime.Log.LogWarning((object)"[SKY-FIX] no skybox material existed pre-load but one does now — leaving it in place."); } return; } if ((Object)(object)skybox == (Object)(object)original) { if (!flag) { CompanionRuntime.Log.LogMessage((object)$"[SKY-FIX] no hijack at {phase} (skybox #{((Object)original).GetInstanceID()} unchanged) — but the donor's Start() may not have run yet; later passes re-check."); } return; } RenderSettings.skybox = original; if (flag) { CompanionRuntime.Log.LogWarning((object)("[SKY-FIX-LATE] skybox was clobbered AFTER the load-done restore (caught at " + phase + ") — Start()-timing hole CONFIRMED; real material restored.")); } else { CompanionRuntime.Log.LogMessage((object)"[SKY-FIX] donor EnvironmentConditions clobbered the global skybox material — restored the real one (black-screen fix)."); } } private static IEnumerator DeferredSkyCheck(Material original) { yield return (object)new WaitForSecondsRealtime(2f); RepairDisplayChain("deferred+2s"); if ((Object)(object)original != (Object)null) { RestoreSkybox(original, "deferred+2s"); SkySnapshot.Log("deferred+2s"); } int harvestNo = ++_harvestCount; TerrainGuard.Snapshot($"harvest #{harvestNo} pre-purge"); bool purgeRan = TerrainGuard.ShouldPurgeThisHarvest(harvestNo); if (purgeRan) { float p0 = Time.realtimeSinceStartup; yield return Resources.UnloadUnusedAssets(); CompanionRuntime.Log.LogMessage((object)$"[HARVEST] post-harvest unused-asset purge done in {Time.realtimeSinceStartup - p0:F2}s (harvest #{harvestNo})."); } else { CompanionRuntime.Log.LogMessage((object)$"[HARVEST] unused-asset purge SKIPPED for harvest #{harvestNo} ({TerrainGuard.UnloadModeDescription()}) — donor assets stay resident (terrain-hole mitigation, docs/terrain-hole-plan.md)."); } TerrainGuard.PostHarvestMaintenance($"harvest #{harvestNo}", purgeRan); } private static void RepairDisplayChain(string phase) { try { if (!SkySnapshot.TryGetDisplayDesync(out var cam, out var quality, out var detail)) { return; } if ((Object)(object)quality == (Object)null) { CompanionRuntime.Log.LogWarning((object)("[DISPLAY-FIX] " + detail + " (caught at " + phase + ") — but the main camera has no CameraQuality, cannot repair.")); return; } CompanionRuntime.Log.LogWarning((object)("[DISPLAY-FIX] " + detail + " (caught at " + phase + ") — rebuilding via CameraQuality.ProcessRenderToTexture() (black-screen fix).")); Camera main = Camera.main; RenderTexture val = (RenderTexture)(((Object)(object)GameDisplayInUI.Instance != (Object)null && GameDisplayInUI.Instance.Screens.Length != 0 && (Object)(object)GameDisplayInUI.Instance.Screens[0] != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null); RenderTexture val2 = (((Object)(object)main != (Object)null) ? main.targetTexture : null); quality.ProcessRenderToTexture(); RenderTexture val3 = (((Object)(object)main != (Object)null) ? main.targetTexture : null); int num = 0; if ((Object)(object)val != (Object)null && val != val3) { val.Release(); Object.Destroy((Object)(object)val); num++; } if ((Object)(object)val2 != (Object)null && val2 != val3 && val2 != val) { val2.Release(); Object.Destroy((Object)(object)val2); num++; } SkySnapshot.TryGetDisplayDesync(out cam, out var _, out var detail2); CompanionRuntime.Log.LogMessage((object)$"[DISPLAY-FIX] after repair: {detail2} (released {num} orphaned RT(s))."); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[DISPLAY-FIX] repair failed at " + phase + ": " + ex.Message)); } } public static void PruneDeadSoundPlayers() { try { List players = GlobalAudioManager.m_players; if (players != null) { int count = players.Count; players.RemoveAll((SoundPlayer p) => (Object)(object)p == (Object)null); int num = count - players.Count; if (num > 0) { CompanionRuntime.Log.LogMessage((object)string.Format("[AMBIENCE-FIX] pruned {0} dead SoundPlayer entr{1} from GlobalAudioManager.m_players ({2} live remain).", num, (num == 1) ? "y" : "ies", players.Count)); } } } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[AMBIENCE-FIX] SoundPlayer prune failed: " + ex.Message)); } } public static void AuditAudioRegistries(string context) { GlobalAudioManager audioManager = Global.AudioManager; if ((Object)(object)audioManager == (Object)null) { CompanionRuntime.Log.LogMessage((object)("[AMBIENCE-RECON] (" + context + ") GlobalAudioManager is " + ((audioManager != null) ? "DESTROYED — dangling singleton (bug-2 hijack signature)!" : "null (never set)") + ".")); return; } try { EnsureReflection(); List list = _timelessField?.GetValue(audioManager) as List; List list2 = _zonesWaitingField?.GetValue(audioManager) as List; List originalAmbienceZones = audioManager.m_originalAmbienceZones; List players = GlobalAudioManager.m_players; CompanionRuntime.Log.LogMessage((object)("[AMBIENCE-RECON] (" + context + ") timeless=" + Census(list) + " zonesWaiting=" + Census(list2) + " originalZones=" + Census(originalAmbienceZones) + " players=" + Census(players))); LogDeadEntries("timeless", list); LogDeadEntries("zonesWaiting", list2); LogDeadEntries("originalZones", originalAmbienceZones); LogDeadEntries("players", players); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[AMBIENCE-RECON] (" + context + ") audit failed: " + ex.Message)); } } private static string Census(List list) where T : Object { if (list == null) { return "?"; } int num = 0; foreach (T item in list) { if (item != null && (Object)(object)item == (Object)null) { num++; } } return $"{list.Count} (dead {num})"; } private static void LogDeadEntries(string label, List list) where T : Object { if (list == null) { return; } for (int i = 0; i < list.Count; i++) { T val = list[i]; if (val != null && (Object)(object)val == (Object)null) { CompanionRuntime.Log.LogWarning((object)$"[AMBIENCE-RECON] DEAD entry in {label}[{i}] (type {((object)val).GetType().Name}) — this is the leak family behind wedged audio."); } } } private static void EnsureReflection() { if (!_reflectionReady) { _reflectionReady = true; Type typeFromHandle = typeof(GlobalAudioManager); _timelessField = typeFromHandle.GetField("m_timelessAmbientSounds", BindingFlags.Instance | BindingFlags.NonPublic); _startTimesField = typeFromHandle.GetField("m_ambienceStartTimes", BindingFlags.Instance | BindingFlags.NonPublic); _endTimesField = typeFromHandle.GetField("m_ambienceEndTimes", BindingFlags.Instance | BindingFlags.NonPublic); _zonesWaitingField = typeFromHandle.GetField("m_ambienceZonesWaitingForTarget", BindingFlags.Instance | BindingFlags.NonPublic); if (_timelessField == null || _startTimesField == null || _endTimesField == null) { CompanionRuntime.Log.LogWarning((object)"[HARVEST] GlobalAudioManager ambience field(s) not found by reflection — audio-leak fix may be incomplete."); } } } private static void DetachAmbience(Scene donor) { GlobalAudioManager audioManager = Global.AudioManager; if ((Object)(object)audioManager == (Object)null) { return; } int num = 0; GameObject[] rootGameObjects = ((Scene)(ref donor)).GetRootGameObjects(); foreach (GameObject val in rootGameObjects) { AmbienceSound[] componentsInChildren = val.GetComponentsInChildren(true); foreach (AmbienceSound val2 in componentsInChildren) { if (!((Object)(object)val2 == (Object)null)) { RemoveFromAudioRegistries(val2); num++; } } } if (num > 0) { CompanionRuntime.Log.LogMessage((object)$"[HARVEST] detached {num} ambience source(s) from GlobalAudioManager before touching the donor."); } } public static bool RemoveFromAudioRegistries(AmbienceSound a) { if ((Object)(object)a == (Object)null) { return false; } GlobalAudioManager audioManager = Global.AudioManager; if ((Object)(object)audioManager == (Object)null) { return false; } EnsureReflection(); List list = _timelessField?.GetValue(audioManager) as List; object obj = _startTimesField?.GetValue(audioManager); object obj2 = _endTimesField?.GetValue(audioManager); List list2 = _zonesWaitingField?.GetValue(audioManager) as List; MethodInfo methodInfo = obj?.GetType().GetMethod("Remove"); try { if (a.IsPlaying) { audioManager.LockAmbience(a); } } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[HARVEST] LockAmbience failed: " + ex.Message)); } list?.Remove(a); methodInfo?.Invoke(obj, new object[1] { a }); methodInfo?.Invoke(obj2, new object[1] { a }); audioManager.m_originalAmbienceZones.RemoveAll((AmbienceZone z) => (Object)(object)z == (Object)(object)a); list2?.RemoveAll((AmbienceZone z) => (Object)(object)z == (Object)(object)a); return true; } public static Character FindLiveInScene(Scene scene, string creatureName) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) int rank; Character val = FindLiveInScene(scene, creatureName, out rank); if (rank == 1 && (Object)(object)val != (Object)null) { CompanionRuntime.Log.LogMessage((object)("[HARVEST] no exact-name donor for '" + creatureName + "' in '" + ((Scene)(ref scene)).name + "' — using nearest substring match '" + val.Name + "'.")); } return val; } public static Character FindLiveInScene(Scene scene, string creatureName, out int rank) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) string text = PinFor(creatureName); if (text != null) { return FindPinnedInScene(scene, creatureName, text, out rank); } Character val = null; GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); foreach (GameObject val2 in rootGameObjects) { Character[] componentsInChildren = val2.GetComponentsInChildren(true); foreach (Character val3 in componentsInChildren) { if ((Object)(object)val3 == (Object)null || !val3.IsAI) { continue; } int num = Species.MatchRank(val3.Name, creatureName); if (num == 0) { continue; } bool? flag = SafeAlive(val3); if (flag != true) { CompanionRuntime.Log.LogWarning((object)("[HARVEST] donor '" + val3.Name + "' matched but is " + ((flag == false) ? "DEAD" : "uninitialized (inactive/never awoke — stats unreadable)") + " in '" + ((Scene)(ref scene)).name + "' — skipping (bug-6).")); } else { if (num == 2) { rank = 2; return val3; } if ((Object)(object)val == (Object)null) { val = val3; } } } } rank = (((Object)(object)val != (Object)null) ? 1 : 0); return val; } private static Character FindPinnedInScene(Scene scene, string creatureName, string pin, out int rank) { GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); foreach (GameObject val in rootGameObjects) { Character[] componentsInChildren = val.GetComponentsInChildren(true); foreach (Character val2 in componentsInChildren) { if (!((Object)(object)val2 == (Object)null) && val2.IsAI && DonorTable.PinMatches(((Object)((Component)val2).gameObject).name, pin)) { bool? flag = SafeAlive(val2); if (flag == true) { CompanionRuntime.Log.LogMessage((object)("[HARVEST] '" + creatureName + "' → pinned donor GameObject '" + ((Object)((Component)val2).gameObject).name + "' in '" + ((Scene)(ref scene)).name + "' (its frozen Character.Name reads '" + val2.Name + "' — bug-28 pin).")); rank = 3; return val2; } CompanionRuntime.Log.LogWarning((object)("[HARVEST] pinned donor '" + pin + "' for '" + creatureName + "' matched but is " + ((flag == false) ? "DEAD" : "uninitialized (inactive/never awoke — stats unreadable)") + " in '" + ((Scene)(ref scene)).name + "' — skipping (bug-6).")); } } } CompanionRuntime.Log.LogWarning((object)("[HARVEST] '" + creatureName + "' is PINNED to donor GameObject '" + pin + "', which is not in '" + ((Scene)(ref scene)).name + "' — no name fallback (a pin exists precisely because the names lie here). Check the pin against the scene roster below / 'donorspot'.")); rank = 0; return null; } private static void DumpSceneContents(Scene scene) { GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); foreach (GameObject val in rootGameObjects) { CompanionRuntime.Log.LogMessage((object)$"[HARVEST] root: '{((Object)val).name}' active={val.activeSelf} children={val.transform.childCount}"); } int num = 0; GameObject[] rootGameObjects2 = ((Scene)(ref scene)).GetRootGameObjects(); foreach (GameObject val2 in rootGameObjects2) { Character[] componentsInChildren = val2.GetComponentsInChildren(true); foreach (Character val3 in componentsInChildren) { num++; string text; try { bool? flag = SafeAlive(val3); text = string.Format("name='{0}' go='{1}' isAI={2} active={3} alive={4}", val3.Name, ((Object)((Component)val3).gameObject).name, val3.IsAI, ((Component)val3).gameObject.activeInHierarchy, flag.HasValue ? flag.Value.ToString() : "?"); } catch (Exception ex) { text = "go='" + (((Object)(object)val3 != (Object)null && (Object)(object)((Component)val3).gameObject != (Object)null) ? ((Object)((Component)val3).gameObject).name : "?") + "' — state unreadable (" + ex.GetType().Name + ")"; } CompanionRuntime.Log.LogMessage((object)("[HARVEST] Character: " + text)); } } CompanionRuntime.Log.LogMessage((object)$"[HARVEST] {num} Character component(s) total in donor scene."); } private static bool? SafeAlive(Character c) { try { return c.Alive; } catch { return null; } } } [HarmonyPatch(typeof(NetworkingPeer), "RegisterPhotonView")] public static class RegisterPhotonViewGuard { [HarmonyPrefix] private static bool Prefix(NetworkingPeer __instance, PhotonView netView) { return DonorPhotonGuard.OnRegister(__instance, netView); } } public static class DonorPhotonGuard { private const float ExpirySeconds = 30f; private static readonly GuardWindow _window = new GuardWindow(30.0); private static string _scene; private static int _skipped; private static bool Active => _window.Active((double)Time.realtimeSinceStartup); public static bool WindowActive => Active; public static int Begin(string donorScene) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (_window.Owner != 0) { CompanionRuntime.Log.LogWarning((object)("[PHOTON-GUARD] window for '" + _scene + "' still open at Begin — " + (_window.Expired((double)realtimeSinceStartup) ? "EXPIRED (wedged harvest; taking over — its late End will be ignored)." : "NOT expired: harvest serialization broke, overlapping windows!"))); } int num = _window.Begin((double)realtimeSinceStartup); _scene = donorScene; _skipped = 0; CompanionRuntime.Log.LogMessage((object)$"[PHOTON-GUARD] window OPEN for donor scene '{donorScene}' (token {num})."); return num; } public static void Refresh(int token) { _window.Refresh(token, (double)Time.realtimeSinceStartup); } public static void End(int token) { //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_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 GuardEndResult val = _window.End(token); if ((int)val != 1) { if ((int)val == 2) { CompanionRuntime.Log.LogWarning((object)$"[PHOTON-GUARD] stale End (token {token}) ignored — the window is now owned by token {_window.Owner} for '{_scene}'."); return; } string text = (((double)Time.realtimeSinceStartup >= _window.ExpireAt) ? " (deadline LAPSED before close — the tail of this harvest ran unguarded; heartbeat gap?)" : ""); int num = ((PhotonNetwork.networkingPeer != null && PhotonNetwork.networkingPeer.photonViewList != null) ? PhotonNetwork.networkingPeer.photonViewList.Count : (-1)); CompanionRuntime.Log.LogMessage((object)$"[PHOTON-GUARD] window CLOSED for '{_scene}'{text}: suppressed {_skipped} donor view registration(s); registry holds {num} view(s)."); _scene = null; _skipped = 0; } } public static bool OnRegister(NetworkingPeer peer, PhotonView netView) { //IL_001d: 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_00eb: 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_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)netView == (Object)null) { return true; } Scene scene; if (Active) { scene = ((Component)netView).gameObject.scene; if (((Scene)(ref scene)).name == _scene) { netView.removedFromLocalViewList = true; _skipped++; CompanionRuntime.Log.LogMessage((object)$"[PHOTON-GUARD] suppressed donor view: id={netView.viewID} go='{((Object)((Component)netView).gameObject).name}'."); return false; } } if (peer != null && peer.photonViewList != null && peer.photonViewList.TryGetValue(netView.viewID, out var value) && (Object)(object)value != (Object)(object)netView) { object obj; if (!((Object)(object)value != (Object)null)) { obj = ""; } else { string[] obj2 = new string[5] { "'", ((Object)((Component)value).gameObject).name, "' (scene '", null, null }; scene = ((Component)value).gameObject.scene; obj2[3] = ((Scene)(ref scene)).name; obj2[4] = "')"; obj = string.Concat(obj2); } string text = (string)obj; ManualLogSource log = CompanionRuntime.Log; object[] obj3 = new object[4] { netView.viewID, ((Object)((Component)netView).gameObject).name, null, null }; scene = ((Component)netView).gameObject.scene; obj3[2] = ((Scene)(ref scene)).name; obj3[3] = text; log.LogMessage((object)string.Format("[PHOTON-RECON] duplicate view id {0}: new='{1}' (scene '{2}') will destroy old={3}.", obj3)); } } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[PHOTON-GUARD] prefix error (registration allowed): " + ex.Message)); } return true; } public static void DumpRegistry() { //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) NetworkingPeer networkingPeer = PhotonNetwork.networkingPeer; if (networkingPeer == null || networkingPeer.photonViewList == null) { CompanionRuntime.Log.LogMessage((object)"[PHOTON-RECON] no networking peer / view list."); return; } int num = networkingPeer.photonViewList.Values.Count((PhotonView v) => (Object)(object)v == (Object)null); CompanionRuntime.Log.LogMessage((object)$"[PHOTON-RECON] registry: {networkingPeer.photonViewList.Count} view(s), {num} destroyed-but-still-listed."); foreach (KeyValuePair item in networkingPeer.photonViewList.OrderBy((KeyValuePair k) => k.Key).Take(40)) { object obj; if (!((Object)(object)item.Value != (Object)null)) { obj = ""; } else { string name = ((Object)((Component)item.Value).gameObject).name; Scene scene = ((Component)item.Value).gameObject.scene; obj = $"go='{name}' scene='{((Scene)(ref scene)).name}' active={((Component)item.Value).gameObject.activeInHierarchy}"; } string arg = (string)obj; CompanionRuntime.Log.LogMessage((object)$"[PHOTON-RECON] id={item.Key} {arg}"); } if (networkingPeer.photonViewList.Count > 40) { CompanionRuntime.Log.LogMessage((object)$"[PHOTON-RECON] … {networkingPeer.photonViewList.Count - 40} more (photondump shows the low/baked id range)."); } } } [HarmonyPatch(typeof(SaveManager), "Save", new Type[] { typeof(bool), typeof(bool) })] public static class DonorSaveGuard { [HarmonyPrefix] private static bool Prefix(SaveManager __instance, bool _async, bool _forceSaveEnvironment) { try { if (!DonorPhotonGuard.WindowActive) { return true; } __instance.SetSaveRequired(); CompanionRuntime.Log.LogWarning((object)$"[SAVE-GUARD] save requested mid-harvest (async={_async}, forceEnv={_forceSaveEnvironment}) — deferred via SetSaveRequired; vanilla re-fires it after the window closes."); return false; } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[SAVE-GUARD] prefix error (save allowed): " + ex.Message)); return true; } } } public static class ExpeditionHarvest { private sealed class PlayerMark { public Character Ch; public string Uid; public Vector3 Pos; public Quaternion Rot; } private const float WatchdogSeconds = 300f; private static string _donorScene; private static string _homeScene; private static readonly List _marks = new List(); private static Func _payload; private static Action _onDone; private static object _result; private static int _leg; private static bool _payloadRan; private static int _gameplayDoneLeg; private static NetworkLevelLoader _loader; private static int _generation; private static bool _returnRequested; private static bool _restoring; private static int _returnRetries; private static bool _rescued; private static int _resumeCount; private static int _lastResumeFrame; private static string _endScene; private static string _endReason; public static float ReturnLegDelaySeconds = 2f; private const float OutboundGraceSeconds = 30f; private static int _sceneDoneCount; private static int _gameplayDoneCount; private const int MaxReturnRetries = 2; private const float RescueGraceSeconds = 60f; public static bool InProgress { get; private set; } public static bool LastTripEndedHome { get; private set; } = true; public static float ReturnRetrySeconds { get { if (CkConfig.Expedition.ReturnRetrySeconds == null) { return 20f; } return Mathf.Max(0f, CkConfig.Expedition.ReturnRetrySeconds.Value); } } public static bool Begin(string donorScene, Func useScene, Action onDone) { //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_007e: 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_027d: Expected O, but got Unknown //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Expected O, but got Unknown //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Expected O, but got Unknown //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: Expected O, but got Unknown //IL_02bf: Unknown result type (might be due to invalid IL or missing references) //IL_02c9: Expected O, but got Unknown //IL_02c9: Unknown result type (might be due to invalid IL or missing references) //IL_02d3: Expected O, but got Unknown //IL_017c: 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_0198: 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_01aa: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(donorScene) || useScene == null) { CompanionRuntime.Log.LogWarning((object)"[EXPEDITION] Begin: bad arguments."); return false; } if (InProgress) { CompanionRuntime.Log.LogWarning((object)$"[EXPEDITION] already running ('{_donorScene}', leg {_leg}) — refusing a second one."); return false; } Scene activeScene = SceneManager.GetActiveScene(); if (string.Equals(((Scene)(ref activeScene)).name, donorScene, StringComparison.OrdinalIgnoreCase)) { CompanionRuntime.Log.LogMessage((object)("[EXPEDITION] '" + donorScene + "' is the ACTIVE scene — running the payload in place (no loading screens).")); LastTripEndedHome = true; object obj = RunPayload(activeScene, useScene); try { onDone?.Invoke(obj); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[EXPEDITION] onDone threw: " + ex.Message)); } return true; } NetworkLevelLoader instance = NetworkLevelLoader.Instance; if ((Object)(object)instance == (Object)null) { CompanionRuntime.Log.LogWarning((object)"[EXPEDITION] no NetworkLevelLoader yet."); return false; } if (instance.InLoading) { CompanionRuntime.Log.LogWarning((object)"[EXPEDITION] a load is already in progress — try again once it settles."); return false; } if (PhotonNetwork.isNonMasterClientInRoom) { CompanionRuntime.Log.LogWarning((object)"[EXPEDITION] only the HOST can run an expedition (it drives two area switches for the whole party) — ask the host."); return false; } if ((Object)(object)Plugin.Instance == (Object)null) { CompanionRuntime.Log.LogWarning((object)"[EXPEDITION] no CompanionKit plugin host for coroutines."); return false; } _marks.Clear(); CharacterManager instance2 = CharacterManager.Instance; if ((Object)(object)instance2 != (Object)null) { for (int i = 0; i < instance2.PlayerCharacters.Count; i++) { Character character = instance2.GetCharacter(instance2.PlayerCharacters.Values[i]); if (!((Object)(object)character == (Object)null)) { _marks.Add(new PlayerMark { Ch = character, Uid = UID.op_Implicit(character.UID), Pos = ((Component)character).transform.position, Rot = ((Component)character).transform.rotation }); } } } if (_marks.Count == 0) { CompanionRuntime.Log.LogWarning((object)"[EXPEDITION] no player characters to mark — not starting."); return false; } _donorScene = donorScene; _homeScene = ((Scene)(ref activeScene)).name; _payload = useScene; _onDone = onDone; _result = null; _payloadRan = false; _gameplayDoneLeg = 0; _leg = 1; _returnRequested = false; _restoring = false; _returnRetries = 0; _rescued = false; _resumeCount = 0; _lastResumeFrame = 0; LastTripEndedHome = false; _loader = instance; _generation++; InProgress = true; try { NetworkLevelLoader loader = _loader; loader.onSceneLoadingDone = (UnityAction)Delegate.Combine((Delegate?)(object)loader.onSceneLoadingDone, (Delegate?)new UnityAction(HandleSceneLoadingDone)); NetworkLevelLoader loader2 = _loader; loader2.onGameplayLoadingDone = (UnityAction)Delegate.Combine((Delegate?)(object)loader2.onGameplayLoadingDone, (Delegate?)new UnityAction(HandleGameplayLoadingDone)); NetworkLevelLoader loader3 = _loader; loader3.OnResumeAfterLoading = (UnityAction)Delegate.Combine((Delegate?)(object)loader3.OnResumeAfterLoading, (Delegate?)new UnityAction(HandleResumeAfterLoading)); ((MonoBehaviour)Plugin.Instance).StartCoroutine(AutoContinue(_generation)); ((MonoBehaviour)Plugin.Instance).StartCoroutine(Watchdog(_generation)); CompanionRuntime.Log.LogMessage((object)("[EXPEDITION] departing '" + _homeScene + "' → '" + _donorScene + "' " + $"({_marks.Count} player position(s) marked; the game saves on each leg, vanilla door behavior).")); _loader.RequestSwitchArea(_donorScene, 0, 1.5f, false); ((MonoBehaviour)Plugin.Instance).StartCoroutine(OutboundWatch(_generation)); return true; } catch (Exception arg) { CompanionRuntime.Log.LogError((object)($"[EXPEDITION] failed to launch '{_donorScene}': {arg}. " + "Releasing the expedition guard — nobody left home, so nothing is stranded.")); _onDone = null; Finish("launch threw"); return false; } } private static IEnumerator OutboundWatch(int gen) { float t0 = Time.unscaledTime; while (Time.unscaledTime - t0 < 30f) { if (!InProgress || gen != _generation) { yield break; } yield return (object)new WaitForSeconds(0.5f); } if (InProgress && gen == _generation && _leg == 1 && _gameplayDoneLeg == 0 && !LoaderBusy() && IsActiveScene(_homeScene)) { CompanionRuntime.Log.LogWarning((object)($"[EXPEDITION] the switch to '{_donorScene}' was requested {30f:F0}s ago " + "but no load ever started and we are still in '" + _homeScene + "' — the donor scene never loaded. Aborting (nothing was moved).")); Finish("the outbound to '" + _donorScene + "' never started"); } } public static string Status() { return (InProgress ? ($"expedition IN PROGRESS: '{_homeScene}' → '{_donorScene}' leg {_leg}/2, payload ran={_payloadRan}, " + $"gate phase reached for leg {_gameplayDoneLeg}, returnRequested={_returnRequested}, " + $"restoring={_restoring}, resumes={_resumeCount}, returnRetries={_returnRetries}") : $"no expedition in progress (last trip ended home: {LastTripEndedHome})") + $" (return-leg delay {ReturnLegDelaySeconds:F1}s, return retry {ReturnRetrySeconds:F0}s)"; } public static string ForceReset() { if (!InProgress) { return "[EXPEDITION] no expedition in progress — nothing to reset."; } string text = $"'{_homeScene}' → '{_donorScene}' leg {_leg}/2"; _onDone = null; Finish("force reset (expeditionreset verb)"); return "[EXPEDITION] FORCE RESET a stuck expedition (" + text + "). The guard is open again. If the party is not home, recover with 'goto '. Please report the log — Begin is supposed to release the guard on its own."; } private static bool LoaderBusy() { NetworkLevelLoader instance = NetworkLevelLoader.Instance; if ((Object)(object)instance == (Object)null) { return false; } try { return instance.InLoading || instance.m_prepingLoadLevel; } catch { return false; } } private static void HandleSceneLoadingDone() { //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 (InProgress) { ManualLogSource log = CompanionRuntime.Log; string text = $"[EXPEDITION] phase: scene-loading-done #{++_sceneDoneCount} "; object arg = _leg; Scene activeScene = SceneManager.GetActiveScene(); log.LogMessage((object)(text + $"(leg {arg}, scene '{((Scene)(ref activeScene)).name}', frame {Time.frameCount}).")); } } private static void HandleGameplayLoadingDone() { //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) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) if (!InProgress) { return; } Scene activeScene = SceneManager.GetActiveScene(); string b = ((_leg == 1) ? _donorScene : _homeScene); CompanionRuntime.Log.LogMessage((object)($"[EXPEDITION] phase: gameplay-loading-done #{++_gameplayDoneCount} " + $"(leg {_leg}, scene '{((Scene)(ref activeScene)).name}', frame {Time.frameCount}).")); if (string.Equals(((Scene)(ref activeScene)).name, b, StringComparison.OrdinalIgnoreCase)) { _gameplayDoneLeg = _leg; if (_leg == 1 && !_payloadRan) { _payloadRan = true; _result = RunPayload(activeScene, _payload); } } } private static void HandleResumeAfterLoading() { //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) //IL_0010: 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_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0082: 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_0089: 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_0090: 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_00a5: Expected I4, but got Unknown //IL_0132: 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_015b: Unknown result type (might be due to invalid IL or missing references) if (!InProgress) { return; } Scene activeScene = SceneManager.GetActiveScene(); State val = new State { Leg = _leg, ActiveScene = ((Scene)(ref activeScene)).name, HomeScene = _homeScene, DonorScene = _donorScene, ReturnRequested = _returnRequested, Restoring = _restoring, PayloadRan = _payloadRan, LoaderBusy = LoaderBusy() }; Decision val2 = ExpeditionResume.Decide(val); LogResume(val, val2); Action action = val2.Action; switch (action - 1) { case 0: if (!_payloadRan) { CompanionRuntime.Log.LogWarning((object)"[EXPEDITION] payload window was missed — running it at resume instead."); _payloadRan = true; _result = RunPayload(activeScene, _payload); } _leg = 2; CompanionRuntime.Log.LogMessage((object)("[EXPEDITION] donor leg done — returning to '" + _homeScene + "'.")); ((MonoBehaviour)Plugin.Instance).StartCoroutine(ReturnHomeNextFrame(_generation)); break; case 1: _restoring = true; ((MonoBehaviour)Plugin.Instance).StartCoroutine(RestoreAndFinish(_generation)); break; case 2: CompanionRuntime.Log.LogWarning((object)("[EXPEDITION] " + val2.Reason + " — aborting where we stand" + ((_leg == 2) ? " (delivering the payload result without position restore)." : " (no teleports)."))); Finish(val2.Reason); break; } } private static void LogResume(State s, Decision d) { //IL_0059: 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_0099: 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_00af: 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_00ec: 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) _resumeCount++; int frameCount = Time.frameCount; bool flag = _resumeCount > 1 && frameCount == _lastResumeFrame; _lastResumeFrame = frameCount; NetworkLevelLoader loader = NetworkLevelLoader.Instance; CompanionRuntime.Log.LogMessage((object)($"[EXPEDITION][RESUME] #{_resumeCount} leg={s.Leg} scene='{s.ActiveScene}'" + string.Format(" frame={0}{1}", frameCount, flag ? " (SAME FRAME as the previous resume — a duplicate delegate invoke)" : "") + $" loaderBusy={s.LoaderBusy} returnRequested={s.ReturnRequested} restoring={s.Restoring}" + " lastLoaded='" + Safe(delegate { NetworkLevelLoader obj = loader; return (obj == null) ? null : obj.TargetScene; }) + "'" + $" → {d.Action}: {d.Reason}")); try { CompanionRuntime.Log.LogMessage((object)($"[EXPEDITION][RESUME] #{_resumeCount} invoked from:\n" + new StackTrace(fNeedFileInfo: false))); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[EXPEDITION][RESUME] stack trace unavailable (" + ex.GetType().Name + ").")); } static string Safe(Func f) { try { return f()?.ToString() ?? "null"; } catch (Exception ex2) { return "threw:" + ex2.GetType().Name; } } } private static IEnumerator ReturnHomeNextFrame(int gen) { yield return null; CompanionRuntime.Log.LogMessage((object)$"[EXPEDITION] return leg: dwelling {ReturnLegDelaySeconds:F1}s before requesting '{_homeScene}'."); if (ReturnLegDelaySeconds > 0f) { yield return (object)new WaitForSeconds(ReturnLegDelaySeconds); } if (!InProgress || gen != _generation) { bool flag = string.Equals(_endScene, _donorScene, StringComparison.OrdinalIgnoreCase); string text = "(ended in '" + _endScene + "' — " + _endReason + "); the trip home was never requested."; if (flag) { CompanionRuntime.Log.LogWarning((object)("[EXPEDITION] return leg: the dwell expired but this expedition was already torn down IN THE DONOR SCENE " + text + " This is the Bug-25 shape — the resume ladder let a teardown through. Report the log.")); } else { CompanionRuntime.Log.LogMessage((object)("[EXPEDITION] return leg: the dwell expired but the trip had already ended " + text + " Nothing to do — this is the expected tail of a deliberate abort, not Bug 25.")); } } else { NetworkLevelLoader instance = NetworkLevelLoader.Instance; if ((Object)(object)instance == (Object)null) { CompanionRuntime.Log.LogError((object)"[EXPEDITION] NetworkLevelLoader vanished before the return leg — aborting in the donor scene."); Finish("the level loader vanished before the return leg"); } else { RequestReturn(instance, "return leg"); ((MonoBehaviour)Plugin.Instance).StartCoroutine(ReturnWatch(gen)); } } } private static void RequestReturn(NetworkLevelLoader loader, string why) { _returnRequested = true; CompanionRuntime.Log.LogMessage((object)("[EXPEDITION] " + why + ": requesting the switch back to '" + _homeScene + "'.")); loader.RequestSwitchArea(_homeScene, 0, 1.5f, false); } private static IEnumerator ReturnWatch(int gen) { while (ReturnRetrySeconds > 0f && _returnRetries < 2) { float t0 = Time.unscaledTime; while (Time.unscaledTime - t0 < ReturnRetrySeconds) { if (!InProgress || gen != _generation) { yield break; } yield return (object)new WaitForSeconds(0.5f); } if (!InProgress || gen != _generation || _leg != 2 || !IsActiveScene(_donorScene)) { break; } if (!LoaderBusy()) { NetworkLevelLoader instance = NetworkLevelLoader.Instance; if ((Object)(object)instance == (Object)null) { break; } _returnRetries++; CompanionRuntime.Log.LogWarning((object)($"[EXPEDITION] the return to '{_homeScene}' was requested {ReturnRetrySeconds:F0}s ago " + "but no load ever started and we are still in '" + _donorScene + "' — re-requesting " + $"(retry {_returnRetries}/{2}). Leaving the player in a donor region is the Bug-25 failure.")); RequestReturn(instance, $"return retry {_returnRetries}"); } } } private static bool IsActiveScene(string scene) { //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) if (!string.IsNullOrEmpty(scene)) { Scene activeScene = SceneManager.GetActiveScene(); return string.Equals(((Scene)(ref activeScene)).name, scene, StringComparison.OrdinalIgnoreCase); } return false; } private static IEnumerator RestoreAndFinish(int gen) { float t0 = Time.unscaledTime; while (Time.unscaledTime - t0 < 30f) { if (!InProgress || gen != _generation) { yield break; } Character val = ResolveMark(_marks[0]); if ((Object)(object)val != (Object)null && CompanionRuntime.IsSanePosition(((Component)val).transform.position)) { break; } yield return (object)new WaitForSeconds(0.5f); } if (InProgress && gen == _generation) { int num = RestoreMarks(); CompanionRuntime.Log.LogMessage((object)$"[EXPEDITION] home in '{_homeScene}' — restored {num}/{_marks.Count} player position(s)."); Finish($"landed home, restored {num}/{_marks.Count}"); } } private static int RestoreMarks() { //IL_004a: 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) int num = 0; foreach (PlayerMark mark in _marks) { Character val = ResolveMark(mark); if ((Object)(object)val == (Object)null) { CompanionRuntime.Log.LogWarning((object)("[EXPEDITION] player '" + mark.Uid + "' not found after the return — skipping their restore.")); continue; } val.Teleport(mark.Pos, mark.Rot); num++; } return num; } private static IEnumerator AutoContinue(int gen) { int passedLeg = 0; float lastDump = -999f; while (InProgress && gen == _generation) { try { NetworkLevelLoader instance = NetworkLevelLoader.Instance; bool flag = _gameplayDoneLeg == _leg; if ((Object)(object)instance != (Object)null && flag && !instance.ContinueAfterLoading && !instance.IsGameplayLoading && instance.AllPlayerDoneLoading && (Object)(object)MenuManager.Instance != (Object)null && MenuManager.Instance.IsMasterLoadingDisplayed) { instance.SetContinueAfterLoading(); if (passedLeg != _leg) { passedLeg = _leg; CompanionRuntime.Log.LogMessage((object)$"[EXPEDITION] auto-passed the continue gate (leg {_leg}) — no keypress needed."); } } else if ((Object)(object)instance != (Object)null && Time.unscaledTime - lastDump >= 10f) { lastDump = Time.unscaledTime; DumpGateState(instance, flag); } } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[EXPEDITION] gate poll threw (poll continues): " + ex.GetType().Name + ": " + ex.Message)); } yield return (object)new WaitForSeconds(0.25f); } } private static void DumpGateState(NetworkLevelLoader loader, bool legGateReached) { CompanionRuntime.Log.LogMessage((object)("[EXPEDITION][GATE] leg=" + _leg + " legGateReached=" + legGateReached + " continue=" + Safe(() => loader.ContinueAfterLoading) + " gameplayLoading=" + Safe(() => loader.IsGameplayLoading) + " sceneLoading=" + Safe(() => loader.IsSceneLoading) + " allPlayersDone=" + Safe(() => loader.AllPlayerDoneLoading) + " waitingOthers=" + Safe(() => loader.m_waitingForOtherPlayers) + " masterLoadingUI=" + Safe(() => (Object)(object)MenuManager.Instance != (Object)null && MenuManager.Instance.IsMasterLoadingDisplayed) + " overallDone=" + Safe(() => loader.IsOverallLoadingDone) + " charsInit=" + Safe(() => (Object)(object)CharacterManager.Instance != (Object)null && CharacterManager.Instance.IsAllCharactersInitialized) + " itemsSynced=" + Safe(() => (Object)(object)ItemManager.Instance != (Object)null && ItemManager.Instance.IsAllItemSynced) + " scene='" + Safe(delegate { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); return ((Scene)(ref activeScene)).name; }) + "'")); static string Safe(Func f) { try { return f()?.ToString() ?? "null"; } catch (Exception ex) { return "threw:" + ex.GetType().Name; } } } private static IEnumerator Watchdog(int gen) { float t0 = Time.unscaledTime; string name; while (true) { if (InProgress && gen == _generation && Time.unscaledTime - t0 < 300f) { yield return (object)new WaitForSeconds(1f); continue; } if (!InProgress || gen != _generation) { yield break; } Scene activeScene = SceneManager.GetActiveScene(); name = ((Scene)(ref activeScene)).name; if (_leg != 2 || _rescued || LoaderBusy() || !string.Equals(name, _donorScene, StringComparison.OrdinalIgnoreCase)) { break; } NetworkLevelLoader instance = NetworkLevelLoader.Instance; if (!((Object)(object)instance != (Object)null)) { break; } _rescued = true; CompanionRuntime.Log.LogError((object)($"[EXPEDITION] watchdog: wedged on the return leg after {300f:F0}s and still in " + "'" + name + "' — one last rescue attempt to get home to '" + _homeScene + "' before tearing down " + $"({60f:F0}s grace). Tearing down here would leave you in a donor region (Bug 25).")); RequestReturn(instance, "watchdog rescue"); t0 = Time.unscaledTime - 300f + 60f; } int num = (string.Equals(name, _homeScene, StringComparison.OrdinalIgnoreCase) ? RestoreMarks() : (-1)); CompanionRuntime.Log.LogError((object)($"[EXPEDITION] watchdog: still on leg {_leg} after {300f:F0}s — tearing down where we stand " + "(active scene '" + name + "'" + ((num >= 0) ? $"; restored {num}/{_marks.Count} player position(s)" : "; no restore — not home") + "). The payload result" + ((_result != null) ? " IS" : " is NOT") + " delivered.")); Finish($"watchdog timeout on leg {_leg}"); } private static object RunPayload(Scene donor, Func payload) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) try { object obj = payload(donor); CompanionRuntime.Log.LogMessage((object)string.Format("[EXPEDITION] payload done in '{0}' (result={1}).", ((Scene)(ref donor)).name, obj ?? "null")); return obj; } catch (Exception arg) { CompanionRuntime.Log.LogError((object)$"[EXPEDITION] payload threw in '{((Scene)(ref donor)).name}': {arg}"); return null; } } private static Character ResolveMark(PlayerMark mark) { if ((Object)(object)mark.Ch != (Object)null) { return mark.Ch; } CharacterManager instance = CharacterManager.Instance; if (!((Object)(object)instance != (Object)null)) { return null; } return instance.GetCharacter(mark.Uid); } private static void Finish(string reason) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: 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_00f3: Expected O, but got Unknown //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Expected O, but got Unknown //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Expected O, but got Unknown //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Expected O, but got Unknown //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Expected O, but got Unknown //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Expected O, but got Unknown Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; bool flag = (LastTripEndedHome = string.Equals(name, _homeScene, StringComparison.OrdinalIgnoreCase)); _endScene = name; _endReason = reason; Character val = ((_marks.Count > 0) ? ResolveMark(_marks[0]) : null); if (flag) { CompanionRuntime.Log.LogMessage((object)$"[EXPEDITION] teardown in '{name}' (leg {_leg}) — {reason}."); } else { CompanionRuntime.Log.LogError((object)($"[EXPEDITION] teardown in '{name}' (leg {_leg}) — {reason}. " + "THE PARTY IS NOT HOME ('" + _homeScene + "'): the game saves on area changes, so this scene may be baked into the save. Recover with 'goto " + _homeScene + "'. (Bug-25 class — please report the log.)")); } if ((Object)(object)_loader != (Object)null) { NetworkLevelLoader loader = _loader; loader.onSceneLoadingDone = (UnityAction)Delegate.Remove((Delegate?)(object)loader.onSceneLoadingDone, (Delegate?)new UnityAction(HandleSceneLoadingDone)); NetworkLevelLoader loader2 = _loader; loader2.onGameplayLoadingDone = (UnityAction)Delegate.Remove((Delegate?)(object)loader2.onGameplayLoadingDone, (Delegate?)new UnityAction(HandleGameplayLoadingDone)); NetworkLevelLoader loader3 = _loader; loader3.OnResumeAfterLoading = (UnityAction)Delegate.Remove((Delegate?)(object)loader3.OnResumeAfterLoading, (Delegate?)new UnityAction(HandleResumeAfterLoading)); } Action onDone = _onDone; object result = _result; InProgress = false; _generation++; _loader = null; _payload = null; _onDone = null; _result = null; _marks.Clear(); _leg = 0; _gameplayDoneLeg = 0; _returnRequested = false; _restoring = false; _returnRetries = 0; _rescued = false; _resumeCount = 0; _lastResumeFrame = 0; _sceneDoneCount = 0; _gameplayDoneCount = 0; if (!flag) { try { Notify.Player(val, "The expedition could not bring you home. You are in " + name + "."); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[EXPEDITION] toast failed: " + ex.Message)); } } try { onDone?.Invoke(result); } catch (Exception ex2) { CompanionRuntime.Log.LogWarning((object)("[EXPEDITION] onDone threw: " + ex2.Message)); } } } public static class ExpeditionManifest { private static List _scenes; public static IReadOnlyList Scenes { get { EnsureLoaded(); return _scenes; } } private static string PathFor() { return Path.Combine(Paths.ConfigPath, "ck_expeditions.txt"); } private static string LegacyPathFor() { return Path.Combine(Paths.ConfigPath, "bw_expeditions.txt"); } private static void EnsureLoaded() { if (_scenes != null) { return; } try { string path = PathFor(); string path2 = LegacyPathFor(); if (File.Exists(path)) { _scenes = ExpeditionLog.Parse(File.ReadAllText(path)); if (File.Exists(path2)) { CompanionRuntime.Log.LogMessage((object)"[EXPEDITION] manifest: ck_expeditions.txt is authoritative — the stray legacy bw_expeditions.txt is ignored (safe to delete)."); } } else if (File.Exists(path2)) { _scenes = ExpeditionLog.Parse(File.ReadAllText(path2)); File.WriteAllText(path, ExpeditionLog.Format((IEnumerable)_scenes)); CompanionRuntime.Log.LogWarning((object)($"[EXPEDITION] manifest migrated: {_scenes.Count} scene(s) copied from legacy bw_expeditions.txt " + "into ck_expeditions.txt (CompanionKit owns the expedition manifest now — docs/expedition-ownership-plan.md). The legacy file is left in place but no longer read.")); } else { _scenes = new List(); } } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[EXPEDITION] manifest read failed: " + ex.Message)); _scenes = new List(); } } public static void Record(string scene) { EnsureLoaded(); if (!ExpeditionLog.Append(_scenes, scene)) { return; } try { File.WriteAllText(PathFor(), ExpeditionLog.Format((IEnumerable)_scenes)); CompanionRuntime.Log.LogMessage((object)$"[EXPEDITION] manifest: recorded '{scene}' ({_scenes.Count} scene(s) in ck_expeditions.txt)."); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[EXPEDITION] manifest write failed: " + ex.Message)); } } public static string Dump() { EnsureLoaded(); if (_scenes.Count != 0) { return string.Format("[EXPEDITION] manifest ({0} dumped scene(s)): {1}", _scenes.Count, string.Join(", ", _scenes)); } return "[EXPEDITION] manifest: no dumped scenes recorded yet (ck_expeditions.txt)."; } } public static class ExpeditionOrchestrator { public struct TripResult { public string Scene; public bool PayloadRan; public int Built; public bool EndedHome; public bool Ok { get { if (PayloadRan) { return EndedHome; } return false; } } } public static Func ActiveSpeciesProvider; public static bool AutoWarmDeferred; private static bool _autoWarmEvaluated; private static string _autoWarmSummary = "not yet evaluated"; private static readonly HashSet _uncapturable = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly HashSet _sweptScenes = new HashSet(StringComparer.OrdinalIgnoreCase); private static string ActiveSpecies { get { try { return ActiveSpeciesProvider?.Invoke(); } catch { return null; } } } private static Character LocalPlayer { get { CharacterManager instance = CharacterManager.Instance; if (instance == null) { return null; } return instance.GetFirstLocalCharacter(); } } private static ManualLogSource Log => CompanionRuntime.Log; public static bool BeginTrip(string scene, Action onDone) { return ExpeditionHarvest.Begin(scene, (Scene donor) => CapturePayload(donor), delegate(object result) { TripResult obj = new TripResult { Scene = scene, PayloadRan = (result is int), Built = ((result is int num) ? num : 0), EndedHome = ExpeditionHarvest.LastTripEndedHome }; try { if (obj.PayloadRan) { ExpeditionManifest.Record(scene); } } catch (Exception ex) { Log.LogWarning((object)("[EXPEDITION] recording '" + scene + "' in the manifest threw: " + ex.Message)); } try { onDone?.Invoke(obj); } catch (Exception ex2) { Log.LogWarning((object)("[EXPEDITION] BeginTrip onDone threw: " + ex2.Message)); } }); } public static void RunVerb(string[] parts) { //IL_0007: 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_000d: 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_0013: 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_0028: Expected I4, but got Unknown //IL_00e7: 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_0122: Unknown result type (might be due to invalid IL or missing references) Command val = ExpeditionVerb.Parse(parts); Kind kind = val.Kind; switch ((int)kind) { case 0: Log.LogMessage((object)("[EXPEDITION] " + ExpeditionHarvest.Status())); Log.LogMessage((object)BodyTemplateCache.Dump()); Log.LogMessage((object)ExpeditionManifest.Dump()); Log.LogMessage((object)($"[EXPEDITION] config: CaptureOnSceneEntry={CkConfig.Expedition.CaptureOnSceneEntry.Value}, " + "AutoWarmAtBoot=" + CkConfig.Expedition.AutoWarmAtBoot.Value + ", AlwaysWarmSpecies='" + CkConfig.Expedition.AlwaysWarmSpecies.Value + "'; boot auto-warm this launch: " + _autoWarmSummary + ".")); Log.LogMessage((object)"[EXPEDITION] usage: expedition — two loading screens there and back; host/offline only; caches body templates for every species the scene donates."); return; case 1: ExpeditionHarvest.ReturnLegDelaySeconds = val.DelaySeconds; Log.LogMessage((object)$"[EXPEDITION] return-leg delay set to {val.DelaySeconds:F1}s (session only; 0 = next-frame chain)."); return; case 2: Log.LogWarning((object)"[EXPEDITION] usage: expedition delay "); return; } string target = val.Target; List similar; string scene = DonorHarvest.ResolveBuildScene(target, out similar); if (scene == null) { if (!DonorHarvest.TryGetExpeditionScenes(target, out var sceneNames, out var searchTerm)) { if (DonorHarvest.TryGetDonorScenes(target, out var sceneNames2, out var searchTerm2)) { Log.LogWarning((object)("[EXPEDITION] '" + searchTerm2 + "' has ADDITIVE donors (" + string.Join(", ", sceneNames2) + ") — the normal harvest chain covers it with no loading screens. Pass a scene name to force a trip anyway.")); } else { Log.LogWarning((object)("[EXPEDITION] '" + target + "' is neither a build-settings scene (" + ((similar.Count > 0) ? ("did you mean: " + string.Join(", ", similar)) : "see scenedump") + ") nor a species with donor-table entries.")); } return; } scene = sceneNames[0]; Log.LogMessage((object)("[EXPEDITION] species '" + searchTerm + "' → expedition donor '" + scene + "'" + ((sceneNames.Count > 1) ? $" (+{sceneNames.Count - 1} more candidate(s) in the manifest)" : "") + ".")); } if (!BeginTrip(scene, delegate(TripResult trip) { Log.LogMessage((object)($"[EXPEDITION] complete: {trip.Built} NEW body template(s) cached from '{scene}'." + ((trip.Built > 0) ? " A bodiless/ghost companion re-forms from the cache within ~2s." : " (0 new — a warm cache also lands here; 'expedition' shows the census.)"))); })) { Log.LogWarning((object)"[EXPEDITION] not started — see the refusal line above."); } } public static object CapturePayload(Scene donor) { //IL_00e7: Unknown result type (might be due to invalid IL or missing references) List list = DonorTable.KeysForScene(DonorHarvest.DonorScenes, ((Scene)(ref donor)).name, true); string activeSpecies = ActiveSpecies; if (!string.IsNullOrEmpty(activeSpecies)) { bool flag = false; foreach (string item in list) { if (Species.NameMatches(activeSpecies, item) || Species.NameMatches(item, activeSpecies)) { flag = true; break; } } if (!flag) { list.Add(activeSpecies); } } if (list.Count == 0) { Log.LogWarning((object)("[EXPEDITION] no donor-table species lists '" + ((Scene)(ref donor)).name + "' and no active companion to serve — nothing to capture.")); return 0; } int num = 0; int num2 = 0; List list2 = new List(); _sweptScenes.Add(((Scene)(ref donor)).name); foreach (string item2 in list) { if (BodyTemplateCache.TryResolveExact(item2, out var _)) { num2++; continue; } Character val = DonorHarvest.FindLiveInScene(donor, item2); if ((Object)(object)val == (Object)null) { list2.Add(item2); _uncapturable.Add(MissKey(((Scene)(ref donor)).name, item2)); } else if (BodyTemplateCache.Capture(val, item2) != null) { num++; } else { list2.Add(item2); _uncapturable.Add(MissKey(((Scene)(ref donor)).name, item2)); } } Log.LogMessage((object)($"[EXPEDITION] '{((Scene)(ref donor)).name}': {num} captured, {num2} already covered by the cache, " + $"{list2.Count} missing of {list.Count} key(s)" + ((list2.Count > 0) ? (" — missing: " + string.Join(", ", list2)) : "") + ".")); return num; } public static void OpportunisticCapture(string scene) { //IL_001d: 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) if (!CkConfig.Expedition.CaptureOnSceneEntry.Value || !DonorTable.IsOversized(scene) || ExpeditionHarvest.InProgress) { return; } Scene activeScene = SceneManager.GetActiveScene(); if (string.Equals(((Scene)(ref activeScene)).name, scene, StringComparison.OrdinalIgnoreCase) && SceneHasUncachedKey(scene)) { Log.LogMessage((object)("[EXPEDITION] entered expedition-tier scene '" + scene + "' with uncached species — capturing in place (zero loading screens).")); if (!BeginTrip(scene, delegate { })) { Log.LogWarning((object)"[EXPEDITION] in-place capture did not start — see the refusal line above."); } } } public static void AutoWarm() { //IL_004f: 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_00c1: 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) if (_autoWarmEvaluated) { return; } _autoWarmEvaluated = true; string value = CkConfig.Expedition.AutoWarmAtBoot.Value; Mode val = default(Mode); if (!ExpeditionWarm.TryParseMode(value, ref val)) { Log.LogWarning((object)("[EXPEDITION] AutoWarmAtBoot='" + value + "' is not off|needed|all — treating as off (AlwaysWarmSpecies still applies).")); val = (Mode)0; } List list = ExpeditionWarm.ParseSpeciesList(CkConfig.Expedition.AlwaysWarmSpecies.Value); if ((int)val == 0 && list.Count == 0) { _autoWarmSummary = "off"; } else if (PhotonNetwork.isNonMasterClientInRoom) { _autoWarmSummary = "skipped (non-master client — expeditions are host-only)"; Log.LogMessage((object)"[EXPEDITION] auto-warm skipped — only the HOST runs expeditions (client bodies come from the host's trips)."); } else if ((Object)(object)Plugin.Instance == (Object)null) { if (ExpeditionHarvest.InProgress) { _autoWarmSummary = "skipped (an expedition was already in progress; no coroutine host to retry)"; Log.LogMessage((object)"[EXPEDITION] auto-warm skipped — an expedition is already in progress and there is no plugin host to defer on."); } else { EvaluateAndLaunch(val, list); } } else { ((MonoBehaviour)Plugin.Instance).StartCoroutine(WarmPass(val, list)); } } private static IEnumerator WarmPass(Mode mode, List listed) { //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) WarmRetryPolicy policy = new WarmRetryPolicy { MaxWaitSeconds = Mathf.Max(0f, CkConfig.Expedition.AutoWarmRetrySeconds.Value), PollSeconds = 1f }; WarmRetryState state = default(WarmRetryState); bool everDeferred = false; string why; while (true) { bool flag = WarmBlockedTransiently(out why); WarmGate val = ExpeditionWarm.WarmRetryStep(ref state, flag, ref policy); if ((int)val == 0) { if (everDeferred) { Log.LogMessage((object)$"[EXPEDITION] auto-warm: the load settled after {state.ElapsedSeconds:F0}s — warming now."); } EvaluateAndLaunch(mode, listed); yield break; } if ((int)val == 2) { break; } if (!everDeferred) { everDeferred = true; _autoWarmSummary = "deferring (waiting for an in-flight load to settle: " + why + ")"; Log.LogMessage((object)$"[EXPEDITION] auto-warm: {why} — deferring, will retry each 1s up to {policy.MaxWaitSeconds:F0}s."); } else if (state.Polls % 10 == 0) { Log.LogMessage((object)$"[EXPEDITION] auto-warm: still waiting for the load to settle ({state.ElapsedSeconds:F0}s/{policy.MaxWaitSeconds:F0}s, {why})."); } yield return (object)new WaitForSecondsRealtime(policy.PollSeconds); state = ExpeditionWarm.WarmRetryAdvance(ref state, policy.PollSeconds); } _autoWarmSummary = $"gave up after waiting {state.ElapsedSeconds:F0}s for an in-flight load to settle ({why})"; Log.LogWarning((object)($"[EXPEDITION] auto-warm: {why} stayed for the whole {policy.MaxWaitSeconds:F0}s retry budget — " + "abandoning this launch's warm pass (raise [Expedition] AutoWarmRetrySeconds if boot loads run longer).")); } private static bool WarmBlockedTransiently(out string why) { if (ExpeditionHarvest.InProgress) { why = "an expedition is already in progress"; return true; } NetworkLevelLoader instance = NetworkLevelLoader.Instance; if ((Object)(object)instance == (Object)null) { why = "the level loader isn't ready yet"; return true; } if (instance.InLoading) { why = "a vanilla load is in progress"; return true; } why = null; return false; } private static void EvaluateAndLaunch(Mode mode, List listed) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //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) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0071: 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_00b6: 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_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected I4, but got Unknown //IL_00ec: 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_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0146: 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_0205: 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_01ac: Unknown result type (might be due to invalid IL or missing references) PlanInput val = new PlanInput(); val.Mode = mode; val.ManifestScenes = ExpeditionManifest.Scenes; val.SceneHasUncachedKey = SceneHasUncachedKey; val.AlwaysWarm = new List(); PlanInput val2 = val; string activeSpecies = ActiveSpecies; if (!string.IsNullOrEmpty(activeSpecies)) { val2.ActivePet = ResolveForWarm(activeSpecies); } foreach (string item in listed) { val2.AlwaysWarm.Add(ResolveForWarm(item)); } Plan val3 = ExpeditionWarm.Decide(val2); foreach (ListDecision listDecision in val3.ListDecisions) { ListOutcome outcome = listDecision.Outcome; switch ((int)outcome) { case 0: Log.LogMessage((object)("[EXPEDITION] AlwaysWarmSpecies: '" + listDecision.Species + "' → warming '" + listDecision.Scene + "'.")); break; case 1: Log.LogMessage((object)("[EXPEDITION] AlwaysWarmSpecies: '" + listDecision.Species + "' has an ADDITIVE donor — the normal harvest chain covers it, no trip.")); break; case 2: Log.LogMessage((object)("[EXPEDITION] AlwaysWarmSpecies: '" + listDecision.Species + "' is already covered by the template cache.")); break; default: Log.LogWarning((object)("[EXPEDITION] AlwaysWarmSpecies: '" + listDecision.Species + "' resolves to NOTHING in the donor table — check the spelling (see 'expedition' / DonorScenes.txt).")); break; } } if (val3.Scenes.Count == 0) { _autoWarmSummary = string.Format("mode={0}{1}: nothing to warm", mode, (listed.Count > 0) ? $", list={listed.Count}" : ""); Log.LogMessage((object)("[EXPEDITION] auto-warm (" + _autoWarmSummary + ").")); } else { _autoWarmSummary = string.Format("mode={0}: warming {1}", mode, string.Join(", ", val3.Scenes)); Log.LogMessage((object)string.Format("[EXPEDITION] auto-warm (mode={0}): {1} trip(s) — {2}.", mode, val3.Scenes.Count, string.Join(", ", val3.Scenes))); WarmChain(val3.Scenes, 0); } } private static void WarmChain(List scenes, int index) { if (index >= scenes.Count) { Log.LogMessage((object)"[EXPEDITION] auto-warm chain complete."); return; } string scene = scenes[index]; Notify.Player(LocalPlayer, "Your companion's kin dwell far away — fetching a body (" + scene + ")..."); if (!BeginTrip(scene, delegate(TripResult trip) { Log.LogMessage((object)($"[EXPEDITION] auto-warm: '{scene}' done ({trip.Built} new template(s)), " + $"{scenes.Count - index - 1} trip(s) remaining.")); if (!trip.EndedHome) { Log.LogError((object)("[EXPEDITION] auto-warm: '" + scene + "' did not bring the party home — STOPPING the chain " + $"({scenes.Count - index - 1} scene(s) unwarmed). Continuing would treat the current scene as " + "'home' for the next trip and strand you there permanently. See the teardown line above.")); } else { ((MonoBehaviour)Plugin.Instance).StartCoroutine(WarmChainNext(scenes, index + 1)); } })) { Log.LogWarning((object)("[EXPEDITION] auto-warm: '" + scene + "' would not start (see the refusal line above) — " + $"chain stopped, {scenes.Count - index} scene(s) unwarmed.")); } } private static IEnumerator WarmChainNext(List scenes, int index) { yield return (object)new WaitForSeconds(Mathf.Max(2f, ExpeditionHarvest.ReturnLegDelaySeconds)); WarmChain(scenes, index); } internal static SpeciesResolve ResolveForWarm(string species) { //IL_0002: 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_0087: Unknown result type (might be due to invalid IL or missing references) SpeciesResolve result = new SpeciesResolve { Species = species }; string text = default(string); List list = default(List); if (SpeciesTable.TryResolveKey>(DonorHarvest.DonorScenes, species, ref text, ref list, (string)null) && list != null && list.Count > 0) { List list3 = default(List); List list2 = DonorTable.FilterViable((IEnumerable)list, ref list3); result.HasAdditiveDonor = list2.Count > 0; result.HasExpeditionScenes = list3.Count > 0; result.FirstExpeditionScene = ((list3.Count > 0) ? list3[0] : null); } result.HasCachedTemplate = BodyTemplateCache.TryResolve(species, out var _); return result; } private static string MissKey(string scene, string key) { return scene + "\0" + key; } public static void ForgetMisses() { _uncapturable.Clear(); _sweptScenes.Clear(); } private static bool SceneHasUncachedKey(string scene) { bool flag = _sweptScenes.Contains(scene); foreach (string item in DonorTable.KeysForScene(DonorHarvest.DonorScenes, scene, true)) { if (!BodyTemplateCache.TryResolveExact(item, out var _) && (!flag || !_uncapturable.Contains(MissKey(scene, item)))) { return true; } } return false; } } 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; } } [BepInPlugin("cobalt.companionkit", "CompanionKit", "0.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string GUID = "cobalt.companionkit"; public const string NAME = "CompanionKit"; public const string VERSION = "0.1.0"; internal static ManualLogSource Log; internal static Plugin Instance; private CommandRegistry _commands; private CommandChannel _channel; internal void Awake() { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown //IL_0087: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; if (CompanionRuntime.Log == null) { CompanionRuntime.Log = Log; } if (Notify.Log == null) { Notify.Log = Log; } CkConfig.Expedition.Bind(((BaseUnityPlugin)this).Config); CkConfig.Rig.Bind(((BaseUnityPlugin)this).Config); RegisterVerbs(); _channel = new CommandChannel("ck_cmd.txt", Log, _commands, 0.5f, true, true); SceneManager.sceneLoaded += OnSceneLoaded; new Harmony("cobalt.companionkit").PatchAll(); Log.LogMessage((object)"CompanionKit 0.1.0 loaded."); } internal void Update() { _channel.Tick(); } private void RegisterVerbs() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown _commands = new CommandRegistry(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 (logs the count freed).", (Action)delegate { Log.LogMessage((object)$"[TEMPLATE] cleared {BodyTemplateCache.ClearAllAndForgetMisses()} cached body template(s)."); }); _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()); }); _commands.Register("terraindump", "Snapshot terrain health now ([TERRAIN] meshTiles/brokenMesh/lod2Missing).", (Action)delegate { TerrainGuard.Dump("terraindump"); }); _commands.Register("terrainfix", "Force blanked terrain tiles to the COARSE Lod2 mesh. WARNING: draws the ground at the wrong height (Bug 32) — a zone reload is the clean fix.", (Action)delegate { TerrainGuard.RepairNow("terrainfix"); }); _commands.Register("templateprobe", "Per cached template: species + first SkinnedMeshRenderer sharedMesh.isReadable (defensive read).", (Action)delegate { Log.LogMessage((object)BodyTemplateCache.Probe()); }); } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_000e: 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) //IL_0016: Invalid comparison between Unknown and I4 if ((int)mode != 1 && Scenes.IsGameplay(((Scene)(ref scene)).name)) { ((MonoBehaviour)this).StartCoroutine(Lifecycle.WhenPlayerReady((Func)delegate { CharacterManager instance = CharacterManager.Instance; return (instance == null) ? null : instance.GetFirstLocalCharacter(); }, (Action)delegate { OnGameplayPlayerReady(((Scene)(ref scene)).name); }, (Action)delegate(string reason) { Log.LogMessage((object)("[EXPEDITION] player not ready in '" + ((Scene)(ref scene)).name + "' (" + reason + ") — kit capture/auto-warm hook skipped.")); }, 30f, (object)this)); } } private void OnGameplayPlayerReady(string scene) { ExpeditionOrchestrator.OpportunisticCapture(scene); if (!ExpeditionOrchestrator.AutoWarmDeferred) { ExpeditionOrchestrator.AutoWarm(); } } } 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(); 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; 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; } NeutralizePayload(val2); BoltHitRelay boltHitRelay = ((Component)val2).gameObject.GetComponent() ?? ((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) + "].")); return num > 0; } 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) { //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_00ca: 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) 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; } 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; } _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") + ".")); return true; } 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); } private static RangedAttackRig BuildRig(ShootProjectile chosen, ShooterCandidate meta, string srcLabel, Transform inactiveHolder) { //IL_00b1: 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_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Expected O, but got Unknown GameObject val = null; try { val = Object.Instantiate(((Component)chosen).gameObject, inactiveHolder); ((Object)val).name = "CK_RangedRig"; 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 { } } } 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 val3 = new GameObject("CK_BoltMuzzle"); val3.transform.SetParent(val.transform, false); rangedAttackRig.Muzzle = val3.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 ex) { CompanionRuntime.Log.LogWarning((object)("[BOLT] capture threw: " + ex.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})."); } } 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; } } public static class RagdollRig { public static string RestoreJoints(GameObject donorRoot, GameObject cloneRoot, Action warn) { //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_019f: 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_01d2: 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_01ee: 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_020a: 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) if ((Object)(object)donorRoot == (Object)null || (Object)(object)cloneRoot == (Object)null) { return null; } try { CharacterJoint[] componentsInChildren = cloneRoot.GetComponentsInChildren(true); if (componentsInChildren.Length != 0) { return $"authored joints intact ({componentsInChildren.Length})"; } CharacterJointManager[] componentsInChildren2 = donorRoot.GetComponentsInChildren(true); if (componentsInChildren2.Length == 0) { return null; } List list = new List(); CharacterJointManager[] array = componentsInChildren2; foreach (CharacterJointManager val in array) { if ((Object)(object)val != (Object)null && val.m_hasJoint) { list.Add(val); } } if (list.Count == 0) { warn?.Invoke($"donor has {componentsInChildren2.Length} CharacterJointManager(s) but NO joint caches — " + "the donor is itself a broken clone; spawns of this template will still gumby on death."); return "donor caches empty — NOT restored"; } int num = 0; int num2 = 0; List path = new List(); foreach (CharacterJointManager item in list) { Transform val2 = Map(donorRoot.transform, cloneRoot.transform, ((Component)item).transform, path); if ((Object)(object)val2 == (Object)null || ((Object)val2).name != ((Object)((Component)item).transform).name) { num2++; continue; } Rigidbody val3 = null; if ((Object)(object)item.m_connectedBody != (Object)null) { Transform val4 = Map(donorRoot.transform, cloneRoot.transform, ((Component)item.m_connectedBody).transform, path); if ((Object)(object)val4 != (Object)null) { val3 = ((Component)val4).GetComponent(); } if ((Object)(object)val3 == (Object)null) { num2++; continue; } } val2.localPosition = item.m_initPos; val2.localRotation = item.m_initRot; CharacterJoint val5 = ((Component)val2).gameObject.AddComponent(); ((Joint)val5).connectedBody = val3; ((Joint)val5).anchor = item.m_anchor; ((Joint)val5).axis = item.m_axis; val5.swingAxis = item.m_swingAxis; val5.lowTwistLimit = item.m_lowTwistLimit; val5.highTwistLimit = item.m_highTwistLimit; val5.swing1Limit = item.m_swing1Limit; val5.swing2Limit = item.m_swing2Limit; num++; } int num3 = 0; CharacterJointManager[] componentsInChildren3 = cloneRoot.GetComponentsInChildren(true); foreach (CharacterJointManager val6 in componentsInChildren3) { if ((Object)(object)val6 != (Object)null) { Object.DestroyImmediate((Object)(object)val6); num3++; } } if (num2 > 0) { warn?.Invoke($"{num2} ragdoll bone(s) could not be mapped donor->clone (hierarchy drift?) — restored the other {num}."); } return $"restored {num} joint(s), stripped {num3} stale manager(s)"; } catch (Exception ex) { warn?.Invoke("joint restore threw (" + ex.GetType().Name + ": " + ex.Message + ") — template kept, may gumby on death."); return "restore FAILED (see warning)"; } } internal static Transform Map(Transform donorRoot, Transform cloneRoot, Transform donorNode, List path) { path.Clear(); Transform val = donorNode; while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)donorRoot) { path.Add(val.GetSiblingIndex()); val = val.parent; } if ((Object)(object)val != (Object)(object)donorRoot) { return null; } Transform val2 = cloneRoot; for (int num = path.Count - 1; num >= 0; num--) { if (path[num] < 0 || path[num] >= val2.childCount) { return null; } val2 = val2.GetChild(path[num]); } return val2; } } public class RigStabilizer : MonoBehaviour { private NavMeshAgent _agent; private Transform _rootBone; private Vector3 _rootBonePin; private SkinnedMeshRenderer _sinkSmr; private float _sinkLog; private void Start() { _agent = ((Component)this).GetComponent(); ((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; while (n < 90) { if ((Object)(object)_agent != (Object)null) { Vector3 velocity = _agent.velocity; if (((Vector3)(ref velocity)).magnitude > 1f) { if (start == null) { start = all.Select((Transform t) => ((Component)this).transform.InverseTransformPoint(t.position)).ToArray(); } for (int num = 0; num < all.Length; num++) { Vector3 val = ((Component)this).transform.InverseTransformPoint(all[num].position); float num2 = Vector3.Distance(val, start[num]); if (num2 > maxd[num]) { maxd[num] = num2; } ref Vector3 reference = ref sumRoot[num]; reference += val; } n++; } } yield return null; } Transform val2 = null; int num3 = int.MaxValue; int num4 = -1; List list = new List(); for (int num5 = 0; num5 < all.Length; num5++) { if (!(maxd[num5] < 0.3f)) { int num6 = 0; Transform val3 = all[num5]; while ((Object)(object)val3 != (Object)null && (Object)(object)val3 != (Object)(object)((Component)this).transform) { num6++; val3 = val3.parent; } if (list.Count < 8) { list.Add($"{((Object)all[num5]).name}(d{num6},{maxd[num5]:F1}m)"); } if (num6 < num3) { num3 = num6; val2 = all[num5]; num4 = num5; } } } if ((Object)(object)val2 != (Object)null) { _rootBone = val2; _rootBonePin = sumRoot[num4] / (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)($"[ROOTBONE] auto-pinning '{((Object)val2).name}' (depth {num3}, drift {maxd[num4]: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)"[ROOTBONE] 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_007a: 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) 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 y = ((Component)this).transform.position.y; float num = ((Bounds)(ref bounds)).max.y - y; float num2 = y - ((Bounds)(ref bounds)).min.y; if (num < 0.25f || num2 > 1f) { CompanionRuntime.Log.LogWarning((object)($"[SINK] skinned body vs feet plane: top {num:+0.00;-0.00}m, " + $"bottom {0f - num2:+0.00;-0.00}m (feet y={y:F2}) — body sunk relative to the " + "transform (bug 8/9 signature).")); } } } public static class SkeletonRig { public sealed class ExternalBone { public SkinnedMeshRenderer CloneSmr; public int Slot; public Transform DonorBone; public bool RefEqualToDonor; public bool Repaired; } public sealed class Report { public int Smrs; public int Slots; public int Internal; public int External; public int ExternalOther; public int NullBones; public int DeadBones; public int RootBonesExternal; public int JointsExternal; public int Cloth; public readonly List Externals = new List(); public readonly List ForeignJoints = new List(); public bool Healthy => RigSurgery.IsHealthy(External + ExternalOther + DeadBones, NullBones, RootBonesExternal, JointsExternal); public string Summary => RigSurgery.Summarize(Smrs, Slots, Internal, External + ExternalOther, NullBones + DeadBones, RootBonesExternal, JointsExternal, Cloth); } public static bool RepairEnabled => CkConfig.Rig.RepairSkinnedBones?.Value ?? true; public static Report Audit(GameObject donorRoot, GameObject cloneRoot, Action warn) { Report report = new Report(); if ((Object)(object)cloneRoot == (Object)null) { return report; } try { SkinnedMeshRenderer[] componentsInChildren = cloneRoot.GetComponentsInChildren(true); SkinnedMeshRenderer[] array = (((Object)(object)donorRoot != (Object)null) ? donorRoot.GetComponentsInChildren(true) : null); report.Smrs = componentsInChildren.Length; if (array != null && array.Length != componentsInChildren.Length) { warn?.Invoke($"SMR count differs donor={array.Length} clone={componentsInChildren.Length} — pairing by index up to the shorter."); } for (int i = 0; i < componentsInChildren.Length; i++) { SkinnedMeshRenderer val = componentsInChildren[i]; if ((Object)(object)val == (Object)null) { continue; } SkinnedMeshRenderer val2 = ((array != null && i < array.Length) ? array[i] : null); if ((Object)(object)val2 != (Object)null && ((Object)val2).name != ((Object)val).name) { warn?.Invoke($"SMR pairing name mismatch at index {i}: donor '{((Object)val2).name}' vs clone '{((Object)val).name}' — classifying anyway (descent checks don't depend on the pairing)."); } Transform[] bones = val.bones; Transform[] array2 = (((Object)(object)val2 != (Object)null) ? val2.bones : null); report.Slots += bones.Length; for (int j = 0; j < bones.Length; j++) { ClassifySlot(report, val, j, bones[j], (array2 != null && j < array2.Length) ? array2[j] : null, array2 != null, cloneRoot.transform); } if (val.rootBone != null) { int count = report.Externals.Count; int deadBones = report.DeadBones; ClassifySlot(report, val, -1, val.rootBone, ((Object)(object)val2 != (Object)null) ? val2.rootBone : null, array2 != null, cloneRoot.transform); if (report.Externals.Count > count || report.DeadBones > deadBones) { report.RootBonesExternal++; } } } Joint[] componentsInChildren2 = cloneRoot.GetComponentsInChildren(true); foreach (Joint val3 in componentsInChildren2) { if (!((Object)(object)val3 == (Object)null) && !((Object)(object)val3.connectedBody == (Object)null) && !IsUnder(((Component)val3.connectedBody).transform, cloneRoot.transform)) { report.JointsExternal++; report.ForeignJoints.Add(val3); } } report.Cloth = cloneRoot.GetComponentsInChildren(true).Length; } catch (Exception ex) { warn?.Invoke("rig audit threw (" + ex.GetType().Name + ": " + ex.Message + ") — report is partial."); } return report; } private static void ClassifySlot(Report rep, SkinnedMeshRenderer smr, int slot, Transform bone, Transform donorBone, bool havePairing, Transform cloneRoot) { if (bone == null) { if (havePairing && donorBone != null && slot >= 0) { rep.NullBones++; } return; } if ((Object)(object)bone == (Object)null) { rep.DeadBones++; return; } if (IsUnder(bone, cloneRoot)) { if (slot >= 0) { rep.Internal++; } return; } bool flag = havePairing && bone == donorBone; if (slot >= 0) { if (flag) { rep.External++; } else { rep.ExternalOther++; } } rep.Externals.Add(new ExternalBone { CloneSmr = smr, Slot = slot, DonorBone = (flag ? bone : (((Object)(object)donorBone != (Object)null) ? donorBone : bone)), RefEqualToDonor = flag }); } public static void LogForensics(Report rep, GameObject donorRoot, Action warn) { //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0183: 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_019f: Unknown result type (might be due to invalid IL or missing references) if (warn == null || rep == null || rep.Externals.Count == 0) { return; } HashSet hashSet = (((Object)(object)donorRoot != (Object)null) ? RigSurgery.RootChain(donorRoot.transform, (Func)((Transform t) => t.parent)) : null); foreach (ExternalBone external in rep.Externals) { Transform donorBone = external.DonorBone; string text = ((external.Slot < 0) ? "rootBone" : $"bones[{external.Slot}]"); if ((Object)(object)donorBone == (Object)null) { string[] obj = new string[5] { "external ", text, " of '", null, null }; SkinnedMeshRenderer cloneSmr = external.CloneSmr; obj[3] = ((cloneSmr != null) ? ((Object)cloneSmr).name : null); obj[4] = "': target unreachable (destroyed or unpaired)."; warn(string.Concat(obj)); continue; } Transform val = null; if (hashSet != null) { Transform val2 = donorBone; while ((Object)(object)val2 != (Object)null && (Object)(object)val == (Object)null) { if (hashSet.Contains(val2)) { val = val2; } val2 = val2.parent; } } string[] obj2 = new string[17] { "external ", text, " of '", null, null, null, null, null, null, null, null, null, null, null, null, null, null }; SkinnedMeshRenderer cloneSmr2 = external.CloneSmr; obj2[3] = ((cloneSmr2 != null) ? ((Object)cloneSmr2).name : null); obj2[4] = "': bone '"; obj2[5] = ((Object)donorBone).name; obj2[6] = "' path='"; obj2[7] = PathOf(donorBone); obj2[8] = "' scene='"; Scene scene = ((Component)donorBone).gameObject.scene; obj2[9] = ((Scene)(ref scene)).name; obj2[10] = "' pos="; Vector3 position = donorBone.position; obj2[11] = ((Vector3)(ref position)).ToString("F1"); obj2[12] = " refEqual="; obj2[13] = (external.RefEqualToDonor ? "Y" : "N"); obj2[14] = " lcaWithDonor='"; obj2[15] = (((Object)(object)val != (Object)null) ? ((Object)val).name : "none"); obj2[16] = "'"; warn(string.Concat(obj2)); } } public static string Repair(GameObject donorRoot, GameObject cloneRoot, Report audit, Action warn) { if ((Object)(object)cloneRoot == (Object)null || audit == null) { return "nothing to repair"; } if (audit.Externals.Count == 0 && audit.ForeignJoints.Count == 0) { return "nothing to repair"; } try { int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; HashSet hashSet = new HashSet(); Dictionary dictionary = UniqueNameIndex(cloneRoot.transform); foreach (ExternalBone external in audit.Externals) { string text = (((Object)(object)external.DonorBone != (Object)null) ? ((Object)external.DonorBone).name : null); if (text != null && dictionary.TryGetValue(text, out var value)) { WriteSlot(external, value, hashSet); external.Repaired = true; num++; } } List list = new List(); foreach (ExternalBone external2 in audit.Externals) { if (!external2.Repaired) { list.Add(external2); } } if (list.Count > 0 && (Object)(object)donorRoot != (Object)null) { HashSet hashSet2 = RigSurgery.RootChain(donorRoot.transform, (Func)((Transform t) => t.parent)); Dictionary dictionary2 = new Dictionary(); List list2 = new List(); foreach (ExternalBone item in list) { Transform val = (((Object)(object)item.DonorBone != (Object)null) ? RigSurgery.FindGraftRoot(item.DonorBone, hashSet2, (Func)((Transform t) => t.parent)) : null); if ((Object)(object)val == (Object)null) { num4++; continue; } dictionary2[item] = val; list2.Add(val); } Dictionary dictionary3 = new Dictionary(); foreach (Transform item2 in RigSurgery.DedupeGraftRoots((IEnumerable)list2, (Func)((Transform t) => t.parent))) { GameObject val2 = Object.Instantiate(((Component)item2).gameObject, cloneRoot.transform); ((Object)val2).name = ((Object)item2).name; PoseLikeDonor(val2.transform, item2, donorRoot.transform, warn); StripToTransforms(val2, warn); dictionary3[item2] = val2.transform; num2++; } List path = new List(); foreach (KeyValuePair item3 in dictionary2) { ExternalBone key = item3.Key; Transform cloneGraftRoot; Transform val3 = OutermostGraft(item3.Value, dictionary3, out cloneGraftRoot); Transform val4 = (((Object)(object)val3 != (Object)null) ? RagdollRig.Map(val3, cloneGraftRoot, key.DonorBone, path) : null); if ((Object)(object)val4 == (Object)null) { num4++; continue; } WriteSlot(key, val4, hashSet); key.Repaired = true; num3++; } } else if (list.Count > 0) { num4 += list.Count; } foreach (SkinnedMeshRenderer item4 in hashSet) { if ((Object)(object)item4 != (Object)null) { item4.updateWhenOffscreen = true; } } foreach (Joint foreignJoint in audit.ForeignJoints) { if (!((Object)(object)foreignJoint == (Object)null)) { if (warn != null) { string[] obj = new string[5] { "joint on '", ((Object)((Component)foreignJoint).gameObject).name, "' pinned to foreign body '", null, null }; Rigidbody connectedBody = foreignJoint.connectedBody; obj[3] = ((connectedBody != null) ? ((Object)connectedBody).name : null); obj[4] = "' — destroying it (partial ragdoll beats world-pinned)."; warn(string.Concat(obj)); } Object.DestroyImmediate((Object)(object)foreignJoint); num5++; } } return $"renamed {num}, grafted {num2} subtree(s), rebound {num3}, " + $"joints cut {num5}, unrepairable {num4}"; } catch (Exception ex) { warn?.Invoke("rig repair threw (" + ex.GetType().Name + ": " + ex.Message + ") — template may still be mis-skinned."); return "repair FAILED (see warning)"; } } private static void WriteSlot(ExternalBone eb, Transform target, HashSet touched) { SkinnedMeshRenderer cloneSmr = eb.CloneSmr; if ((Object)(object)cloneSmr == (Object)null) { return; } if (eb.Slot < 0) { cloneSmr.rootBone = target; } else { Transform[] bones = cloneSmr.bones; if (eb.Slot >= bones.Length) { return; } bones[eb.Slot] = target; cloneSmr.bones = bones; } touched.Add(cloneSmr); } private static void PoseLikeDonor(Transform graft, Transform donorSubtree, Transform donorRoot, Action warn) { //IL_0003: 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) //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_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_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_0036: 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_003d: 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: 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_005f: 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_007a: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_0096: 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_008e: 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_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: 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_00bc: Unknown result type (might be due to invalid IL or missing references) graft.localPosition = donorRoot.InverseTransformPoint(donorSubtree.position); graft.localRotation = Quaternion.Inverse(donorRoot.rotation) * donorSubtree.rotation; Vector3 lossyScale = donorSubtree.lossyScale; Vector3 lossyScale2 = donorRoot.lossyScale; graft.localScale = new Vector3((lossyScale2.x != 0f) ? (lossyScale.x / lossyScale2.x) : lossyScale.x, (lossyScale2.y != 0f) ? (lossyScale.y / lossyScale2.y) : lossyScale.y, (lossyScale2.z != 0f) ? (lossyScale.z / lossyScale2.z) : lossyScale.z); if (!Approximately(lossyScale, lossyScale.x) || !Approximately(lossyScale2, lossyScale2.x)) { warn?.Invoke("graft '" + ((Object)graft).name + "': non-uniform scale (subtree " + ((Vector3)(ref lossyScale)).ToString("F2") + ", donor root " + ((Vector3)(ref lossyScale2)).ToString("F2") + ") — TRS approximation, shear is not representable."); } } private static bool Approximately(Vector3 v, float s) { //IL_0000: 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) if (Mathf.Approximately(v.x, s) && Mathf.Approximately(v.y, s)) { return Mathf.Approximately(v.z, s); } return false; } private static void StripToTransforms(GameObject graft, Action warn) { Joint[] componentsInChildren = graft.GetComponentsInChildren(true); foreach (Joint val in componentsInChildren) { if ((Object)(object)val != (Object)null) { Object.DestroyImmediate((Object)(object)val); } } int num = 0; for (int j = 0; j < 3; j++) { num = 0; Component[] componentsInChildren2 = graft.GetComponentsInChildren(true); foreach (Component val2 in componentsInChildren2) { if (!((Object)(object)val2 == (Object)null) && !(val2 is Transform) && !(val2 is Collider) && !(val2 is Rigidbody)) { try { Object.DestroyImmediate((Object)(object)val2); } catch { } if ((Object)(object)val2 != (Object)null) { num++; } } } Collider[] componentsInChildren3 = graft.GetComponentsInChildren(true); foreach (Collider val3 in componentsInChildren3) { if ((Object)(object)val3 != (Object)null) { try { Object.DestroyImmediate((Object)(object)val3); } catch { num++; } } } Rigidbody[] componentsInChildren4 = graft.GetComponentsInChildren(true); foreach (Rigidbody val4 in componentsInChildren4) { if ((Object)(object)val4 != (Object)null) { try { Object.DestroyImmediate((Object)(object)val4); } catch { num++; } } } if (num == 0) { break; } } if (num > 0) { warn?.Invoke($"graft '{((Object)graft).name}': {num} component(s) refused to strip — they ride the template."); } } private static Dictionary UniqueNameIndex(Transform root) { Dictionary dictionary = new Dictionary(); HashSet hashSet = new HashSet(); Transform[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); foreach (Transform val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && !hashSet.Contains(((Object)val).name)) { if (dictionary.ContainsKey(((Object)val).name)) { dictionary.Remove(((Object)val).name); hashSet.Add(((Object)val).name); } else { dictionary[((Object)val).name] = val; } } } return dictionary; } private static Transform OutermostGraft(Transform donorGraftRoot, Dictionary graftClone, out Transform cloneGraftRoot) { Transform val = donorGraftRoot; while ((Object)(object)val != (Object)null) { if (graftClone.TryGetValue(val, out cloneGraftRoot)) { return val; } val = val.parent; } cloneGraftRoot = null; return null; } public static string Census(GameObject root) { //IL_01bd: 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_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: 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) if ((Object)(object)root == (Object)null) { return " (no object)"; } StringBuilder stringBuilder = new StringBuilder(); try { SkinnedMeshRenderer[] componentsInChildren = root.GetComponentsInChildren(true); if (componentsInChildren.Length == 0) { return " no SkinnedMeshRenderers"; } SkinnedMeshRenderer[] array = componentsInChildren; foreach (SkinnedMeshRenderer val in array) { if ((Object)(object)val == (Object)null) { continue; } Transform[] bones = val.bones; int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; List list = new List(); Transform[] array2 = bones; foreach (Transform val2 in array2) { if (val2 == null) { num4++; continue; } if ((Object)(object)val2 == (Object)null) { num++; continue; } if (IsUnder(val2, root.transform)) { num3++; continue; } num2++; if (list.Count < 4) { string name = ((Object)val2).name; Scene scene = ((Component)val2).gameObject.scene; list.Add("'" + name + "'@" + ((Scene)(ref scene)).name); } } string text = ((val.rootBone == null) ? "null" : (((Object)(object)val.rootBone == (Object)null) ? "DEAD" : (IsUnder(val.rootBone, root.transform) ? "OK" : "EXTERNAL"))); if (stringBuilder.Length > 0) { stringBuilder.Append('\n'); } string text2 = $" SMR '{((Object)val).name}' bones={bones.Length} internal={num3} dead={num} "; object[] obj = new object[4] { num2, num4, text, null }; Bounds bounds = ((Renderer)val).bounds; Vector3 size = ((Bounds)(ref bounds)).size; obj[3] = ((Vector3)(ref size)).ToString("F1"); stringBuilder.Append(text2 + string.Format("external={0} null={1} rootBone={2} bounds={3}", obj)); if (list.Count > 0) { stringBuilder.Append(" externalBones=[" + string.Join(", ", list) + ((num2 > list.Count) ? ", …" : "") + "]"); } } } catch (Exception ex) { stringBuilder.Append(" census threw: " + ex.GetType().Name + ": " + ex.Message); } return stringBuilder.ToString(); } private static bool IsUnder(Transform t, Transform root) { Transform val = t; while ((Object)(object)val != (Object)null) { if ((Object)(object)val == (Object)(object)root) { return true; } val = val.parent; } return false; } private static string PathOf(Transform t) { StringBuilder stringBuilder = new StringBuilder(((Object)t).name); Transform parent = t.parent; while ((Object)(object)parent != (Object)null) { stringBuilder.Insert(0, ((Object)parent).name + "/"); parent = parent.parent; } return stringBuilder.ToString(); } } public static class SkySnapshot { public static void Log(string tag) { try { LogInner(tag); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[SKY] (" + tag + ") snapshot failed: " + ex.Message)); } } private static void LogInner(string tag) { //IL_011e: 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_0189: 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) Material skybox = RenderSettings.skybox; EnvironmentConditions instance = EnvironmentConditions.Instance; Material val = (((Object)(object)instance != (Object)null) ? instance.m_skyboxMat : null); string text = Describe(skybox); string text2 = (((Object)(object)skybox == (Object)null || (Object)(object)instance == (Object)null) ? "n/a" : ((skybox == val) ? "ecClone" : "todAsset")); TOD_Sky instance2 = TOD_Sky.Instance; Light sun = RenderSettings.sun; CompanionRuntime.Log.LogMessage((object)("[SKY] (" + tag + ") skybox=" + text + " ecSkyMat=" + Describe(val) + " (" + text2 + ") | EC=" + SceneOf((Component)(object)instance) + " TOD_Sky=" + SceneOf((Component)(object)instance2) + " | sun=" + DescribeLight(sun))); Camera main = Camera.main; string arg = ((!((Object)(object)main == (Object)null)) ? $"'{((Object)main).name}' on={((Behaviour)main).enabled} clear={main.clearFlags} mask=0x{main.cullingMask:X8} far={main.farClipPlane:F0}" : ((main != null) ? "DESTROYED" : "NULL — no enabled MainCamera-tagged camera")); CompanionRuntime.Log.LogMessage((object)($"[SKY] ({tag}) ambient={RenderSettings.ambientMode} int={RenderSettings.ambientIntensity:F2} " + $"light={Rgb(RenderSettings.ambientLight)} | fog={RenderSettings.fog} color={Rgb(RenderSettings.fogColor)} " + $"density={RenderSettings.fogDensity:F4} | cam={arg}")); CompanionRuntime.Log.LogMessage((object)("[SKY] (" + tag + ") display: " + DescribeDisplayChain())); } public static bool TryGetDisplayDesync(out Camera cam, out CameraQuality quality, out string detail) { cam = null; quality = null; detail = null; GameDisplayInUI instance = GameDisplayInUI.Instance; SplitScreenManager instance2 = SplitScreenManager.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null || !instance2.RenderInImage) { detail = "direct-to-screen mode (no RT display chain)"; return false; } if (instance.Screens == null || instance.Screens.Length == 0 || (Object)(object)instance.Screens[0] == (Object)null) { detail = "no display RawImage"; return false; } cam = Camera.main; if ((Object)(object)cam == (Object)null) { detail = "no main camera"; return false; } quality = ((Component)cam).GetComponent(); Texture texture = instance.Screens[0].texture; RenderTexture targetTexture = cam.targetTexture; string text = (((Object)(object)texture == (Object)null) ? "null" : ("#" + ((Object)texture).GetInstanceID())); string text2 = (((Object)(object)targetTexture == (Object)null) ? "null" : ("#" + ((Object)targetTexture).GetInstanceID())); bool flag = (object)texture != targetTexture; detail = "imageTex=" + text + " camTargetTex=" + text2 + " " + ((!flag) ? "in sync" : (((Object)(object)targetTexture == (Object)null) ? "not linked yet (camera target null — normal during early load init; repaired post-harvest if it persists)" : "DESYNC — the screen is showing a texture the camera is not rendering into (black-world signature)")); return flag; } private static string DescribeDisplayChain() { try { TryGetDisplayDesync(out var _, out var _, out var detail); return detail; } catch (Exception ex) { return "unreadable (" + ex.Message + ")"; } } private static string Describe(Material m) { if (m == null) { return "null"; } if ((Object)(object)m == (Object)null) { return $"DESTROYED#{((Object)m).GetInstanceID()}"; } return $"'{((Object)m).name}'#{((Object)m).GetInstanceID()}"; } private static string DescribeLight(Light l) { //IL_0060: 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) if (l == null) { return "null"; } if ((Object)(object)l == (Object)null) { return "DESTROYED — RenderSettings.sun points at a dead light (donor leak?)"; } object[] obj = new object[4] { ((Object)l).name, ((Behaviour)l).enabled && ((Component)l).gameObject.activeInHierarchy, l.intensity, null }; Scene scene = ((Component)l).gameObject.scene; obj[3] = ((Scene)(ref scene)).name; return string.Format("'{0}' on={1} int={2:F2} scene='{3}'", obj); } private static string SceneOf(Component c) { //IL_0023: 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) if (c == null) { return "null"; } if ((Object)(object)c == (Object)null) { return "DESTROYED"; } Scene scene = c.gameObject.scene; return "'" + ((Scene)(ref scene)).name + "'"; } private static string Rgb(Color c) { //IL_0005: 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_001b: Unknown result type (might be due to invalid IL or missing references) return $"({c.r:F2},{c.g:F2},{c.b:F2})"; } } [HarmonyPatch(typeof(EnvironmentConditions), "RegisterSoulSpot")] internal static class SoulSpotGuard { private static bool Prefix(UID _uid, SoulSpot _soulSpot) { try { if (!EnvironmentConditions.s_soulSpots.ContainsKey(((UID)(ref _uid)).Value)) { return true; } ManualLogSource log = CompanionRuntime.Log; if (log != null) { log.LogWarning((object)("[SOULSPOT-GUARD] duplicate soul-spot UID '" + ((UID)(ref _uid)).Value + "' — kept the existing registration instead of throwing mid-Awake (donor-scene collision; the vanilla Add would have crashed the Character's activation).")); } return false; } catch (Exception ex) { ManualLogSource log2 = CompanionRuntime.Log; if (log2 != null) { log2.LogWarning((object)("[SOULSPOT-GUARD] prefix failed (" + ex.Message + ") — falling through to vanilla.")); } return true; } } internal static void PruneDead() { try { DictionaryExt s_soulSpots = EnvironmentConditions.s_soulSpots; if (s_soulSpots == null) { return; } List list = new List(); foreach (string key in s_soulSpots.Keys) { if (s_soulSpots[key] != null && (Object)(object)s_soulSpots[key] == (Object)null) { list.Add(key); } } foreach (string item in list) { s_soulSpots.Remove(item); } if (list.Count > 0) { CompanionRuntime.Log.LogMessage((object)string.Format("[SOULSPOT-GUARD] pruned {0} dead soul-spot entr{1} from the static registry ({2} live remain).", list.Count, (list.Count == 1) ? "y" : "ies", s_soulSpots.Count)); } } catch (Exception ex) { ManualLogSource log = CompanionRuntime.Log; if (log != null) { log.LogWarning((object)("[SOULSPOT-GUARD] prune failed: " + ex.Message)); } } } } public static class TerrainGuard { public struct Health { public int UnityTerrains; public int MeshTiles; public int BrokenMeshTiles; public int Lod2Missing; } public static Func UnloadModeProvider; public static Func UnloadEveryNProvider; public static Func FlushAfterPurgeProvider; private static UnloadAssetsMode UnloadMode { get { //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (UnloadModeProvider == null) { return (UnloadAssetsMode)1; } return UnloadModeProvider(); } } private static int UnloadEveryN { get { if (UnloadEveryNProvider == null) { return 5; } return UnloadEveryNProvider(); } } private static bool FlushAfterPurge { get { if (FlushAfterPurgeProvider != null) { return FlushAfterPurgeProvider(); } return true; } } public static bool ShouldPurgeThisHarvest(int harvestCount) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return TerrainMaintenance.ShouldUnload(UnloadMode, UnloadEveryN, harvestCount); } public static string UnloadModeDescription() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_000d: Unknown result type (might be due to invalid IL or missing references) if ((int)UnloadMode != 1) { return $"mode={UnloadMode}"; } return $"mode=EveryN N={UnloadEveryN}"; } public static Health Snapshot(string context) { //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) Health result = default(Health); try { Terrain[] activeTerrains = Terrain.activeTerrains; result.UnityTerrains = ((activeTerrains != null) ? activeTerrains.Length : 0); if (activeTerrains != null) { Terrain[] array = activeTerrains; foreach (Terrain val in array) { if (!((Object)(object)val == (Object)null)) { TerrainData o = null; Material o2 = null; try { o = val.terrainData; } catch { } try { o2 = val.materialTemplate; } catch { } ManualLogSource log = CompanionRuntime.Log; string[] obj3 = new string[9] { "[TERRAIN] (", context, ") unity '", ((Object)val).name, "' scene='", null, null, null, null }; Scene scene = ((Component)val).gameObject.scene; obj3[5] = ((Scene)(ref scene)).name; obj3[6] = "' "; obj3[7] = $"data={FakeNull((Object)(object)o)} material={FakeNull((Object)(object)o2)} basemapDist={val.basemapDistance:F0} "; obj3[8] = $"drawHeightmap={val.drawHeightmap} active={((Component)val).gameObject.activeInHierarchy && ((Behaviour)val).enabled}"; log.LogMessage((object)string.Concat(obj3)); } } } TerrainMeshRenderControl[] array2 = Object.FindObjectsOfType(); result.MeshTiles = ((array2 != null) ? array2.Length : 0); if (array2 != null) { TerrainMeshRenderControl[] array3 = array2; foreach (TerrainMeshRenderControl val2 in array3) { if (!((Object)(object)val2 == (Object)null)) { MeshFilter val3 = ResolveMeshFilter(val2); bool flag = (Object)(object)val3 == (Object)null || (Object)(object)val3.sharedMesh == (Object)null; if (flag) { result.BrokenMeshTiles++; } if (flag && (Object)(object)val2.Lod2 == (Object)null) { result.Lod2Missing++; } } } } CompanionRuntime.Log.LogMessage((object)($"[TERRAIN] ({context}) meshTiles={result.MeshTiles} brokenMesh={result.BrokenMeshTiles} " + $"lod2Missing={result.Lod2Missing}" + ((result.BrokenMeshTiles > 0) ? " <-- RENDER HOLE (invisible but WALKABLE tile(s) — the collider is fine). Change zone to fix. Do NOT run terrainfix: it swaps in a coarse mesh at the wrong height (Bug 32)." : ""))); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[TERRAIN] (" + context + ") snapshot failed: " + ex.Message)); } return result; } public static int FlushActiveTerrains(string context) { int num = 0; try { Terrain[] activeTerrains = Terrain.activeTerrains; if (activeTerrains == null) { return 0; } Terrain[] array = activeTerrains; foreach (Terrain val in array) { if (!((Object)(object)val == (Object)null)) { try { val.Flush(); num++; } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[TERRAIN] (" + context + ") Flush('" + ((Object)val).name + "') threw: " + ex.Message)); } } } if (num > 0) { CompanionRuntime.Log.LogMessage((object)$"[TERRAIN] ({context}) flushed {num} Unity terrain(s) (basemap/patch re-arm)."); } } catch (Exception ex2) { CompanionRuntime.Log.LogWarning((object)("[TERRAIN] (" + context + ") flush failed: " + ex2.Message)); } return num; } public static int RepairMeshTiles(string context) { int num = 0; try { TerrainMeshRenderControl[] array = Object.FindObjectsOfType(); if (array == null) { return 0; } TerrainMeshRenderControl[] array2 = array; foreach (TerrainMeshRenderControl val in array2) { if (!((Object)(object)val == (Object)null)) { MeshFilter val2 = ResolveMeshFilter(val); if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.sharedMesh != (Object)null) && !((Object)(object)val.Lod2 == (Object)null)) { val2.sharedMesh = val.Lod2; num++; } } } if (num > 0) { CompanionRuntime.Log.LogWarning((object)($"[TERRAIN] ({context}) forced {num} blanked tile(s) to the COARSE Lod2 mesh. " + "The ground is visible again but is drawn at the WRONG HEIGHT (you may appear sunk into it) and the LOD streamer will NOT re-upgrade it — Bug 32. A zone reload is the clean fix. Snapshots will now read brokenMesh=0 for these tiles, which is why that number alone cannot be trusted after a terrainfix.")); } } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[TERRAIN] (" + context + ") tile repair failed: " + ex.Message)); } return num; } public static void PostHarvestMaintenance(string context, bool purgeRan) { if (purgeRan && FlushAfterPurge) { FlushActiveTerrains(context); } Snapshot(context + (purgeRan ? " post-purge" : " no-purge")); } public static void Dump(string context) { Snapshot(context); } public static void RepairNow(string context) { FlushActiveTerrains(context); if (RepairMeshTiles(context) == 0) { CompanionRuntime.Log.LogMessage((object)("[TERRAIN] (" + context + ") no blanked tiles found (nothing to repair).")); } Snapshot(context + " post-fix"); } private static MeshFilter ResolveMeshFilter(TerrainMeshRenderControl tile) { MeshFilter val = tile.MeshFilter; if ((Object)(object)val == (Object)null) { val = ((Component)tile).GetComponent(); } return val; } private static string FakeNull(Object o) { if (o == null) { return "null"; } if (o == (Object)null) { return "DESTROYED"; } return "OK"; } } }