using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using TheShyGuy; using UnityEngine; using UnityEngine.AI; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("TheShyGuy")] [assembly: AssemblyDescription("Adds a custom SCP-096 style enemy to BONELAB that rages when a player looks at its face.")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("TheShyGuy")] [assembly: AssemblyFileVersion("1.0.0")] [assembly: MelonInfo(typeof(TheShyGuyMod), "TheShyGuy", "1.0.0", "ShyGuyMod", null)] [assembly: MelonGame("Stress Level Zero", "BONELAB")] [assembly: MelonOptionalDependencies(new string[] { "BoneLib", "LabFusion" })] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace TheShyGuy { internal sealed class ShyGuyRig { private readonly List _generated = new List(); internal Transform Root { get; private set; } internal Transform FaceDetectionPoint { get; private set; } internal Transform HeadBone { get; private set; } internal Animator Animator { get; private set; } internal Animation LegacyAnimation { get; private set; } internal NavMeshAgent Agent { get; private set; } internal Collider MainCollider { get; private set; } internal Collider TriggerCollider { get; private set; } internal AudioSource TriggerAudio { get; private set; } internal AudioSource ChaseAudio { get; private set; } internal AudioSource AttackAudio { get; private set; } internal AudioSource AmbientAudio { get; private set; } internal bool FacePointWasGenerated { get; private set; } internal static ShyGuyRig Resolve(GameObject instance, ShyGuyPrefabConfig config) { ShyGuyRig rig = new ShyGuyRig(); if ((Object)(object)instance == (Object)null) { ShyGuyLog.Error("ShyGuyRig.Resolve was handed a null GameObject; nothing to wire up."); return rig; } if (config == null) { config = new ShyGuyPrefabConfig(); } rig.Root = instance.transform; ShyGuyLog.Guard("rig resolve", delegate { rig.ResolveAnimation(config); rig.ResolveHeadAndFace(config); rig.ResolveColliders(config); rig.ResolveAgent(config); rig.ResolveAudio(config); rig.ApplyScale(config); }); rig.ReportGenerated(); return rig; } private void ResolveAnimation(ShyGuyPrefabConfig config) { Animator = ShyGuyHierarchy.ResolveComponent(Root, config.animatorPath, (IEnumerable)new string[4] { "Visuals", "CharacterModel", "Model", "Armature" }); LegacyAnimation = ShyGuyHierarchy.ResolveComponent(Root, config.animatorPath, (IEnumerable)new string[4] { "Visuals", "CharacterModel", "Model", "Armature" }); if ((Object)(object)Animator == (Object)null && (Object)(object)LegacyAnimation == (Object)null) { ShyGuyLog.Warn("Neither an Animator nor a legacy Animation component was found anywhere under the creature. It will move and kill but never animate. Add an Animator to the CharacterModel object (see the prefab setup instructions)."); return; } if (config.useLegacyAnimation && (Object)(object)LegacyAnimation == (Object)null) { ShyGuyLog.Warn("The prefab config asks for legacy animation but no Animation component exists; using the Animator instead."); } if ((Object)(object)Animator != (Object)null) { try { Animator.cullingMode = (AnimatorCullingMode)0; } catch (Exception ex) { ShyGuyLog.Debug("Setting the Animator culling mode threw: " + ex.Message); } } } private void ResolveHeadAndFace(ShyGuyPrefabConfig config) { //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Expected O, but got Unknown //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_013f: 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_0102: 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) HeadBone = ShyGuyHierarchy.FindByPath(Root, config.headBonePath) ?? ShyGuyHierarchy.FindFirstByNames(Root, ShyGuyPrefabConfig.HeadBoneNames); if ((Object)(object)HeadBone != (Object)null) { ShyGuyLog.Debug("Head bone: " + ShyGuyHierarchy.GetPath(Root, HeadBone)); } FaceDetectionPoint = ShyGuyHierarchy.FindByPath(Root, config.faceDetectionPointPath) ?? ShyGuyHierarchy.FindFirstByNames(Root, ShyGuyPrefabConfig.FacePointNames); if ((Object)(object)FaceDetectionPoint != (Object)null) { ShyGuyLog.Msg("Face detection point: " + ShyGuyHierarchy.GetPath(Root, FaceDetectionPoint)); return; } FacePointWasGenerated = true; Transform val = HeadBone ?? Root; GameObject val2 = new GameObject("FaceDetectionPoint"); val2.transform.SetParent(val, false); if ((Object)(object)HeadBone != (Object)null) { val2.transform.localPosition = Vector3.forward * config.fallbackFaceForward; val2.transform.localRotation = Quaternion.identity; } else { val2.transform.localPosition = Vector3.up * config.fallbackFaceHeight; val2.transform.localRotation = Quaternion.identity; } FaceDetectionPoint = val2.transform; _generated.Add("FaceDetectionPoint (parented to " + ((Object)val).name + ")"); ShyGuyLog.Warn("No FaceDetectionPoint was found on the prefab, so one was generated " + (((Object)(object)HeadBone != (Object)null) ? ("in front of the '" + ((Object)HeadBone).name + "' bone.") : ("at " + config.fallbackFaceHeight.ToString("F2") + "m above the root.")) + " Gaze detection will work but the position is a guess - add a real FaceDetectionPoint to the prefab for accurate results."); } private void ResolveColliders(ShyGuyPrefabConfig config) { //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_00f4: Unknown result type (might be due to invalid IL or missing references) MainCollider = ShyGuyHierarchy.ResolveComponent(Root, config.mainColliderPath, (IEnumerable)new string[3] { "MainCollider", "BodyCollider", "Capsule" }); Transform val = ShyGuyHierarchy.FindByPath(Root, config.triggerColliderPath) ?? ShyGuyHierarchy.FindByName(Root, "TriggerCollider"); if ((Object)(object)val != (Object)null) { Collider component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null && (Object)(object)component != (Object)(object)MainCollider) { TriggerCollider = component; } } if ((Object)(object)MainCollider == (Object)null) { GameObject val2 = new GameObject("MainCollider"); val2.transform.SetParent(Root, false); CapsuleCollider val3 = val2.AddComponent(); val3.height = Mathf.Max(1f, config.height); val3.radius = 0.35f; val3.center = new Vector3(0f, config.height * 0.5f, 0f); ((Collider)val3).isTrigger = false; MainCollider = (Collider)(object)val3; _generated.Add("MainCollider (capsule, " + config.height.ToString("F1") + "m tall)"); ShyGuyLog.Warn("The prefab had no collider, so a capsule was generated. Without a collider the gaze raycast has nothing to hit and the creature can never be seen, so this is important to fix properly in the prefab."); } if ((Object)(object)TriggerCollider == (Object)null) { ShyGuyLog.Debug("No separate TriggerCollider found. Attack range is measured by distance, which does not need one, so this is optional."); } if (!((Object)(object)MainCollider != (Object)null)) { return; } try { Rigidbody component2 = ((Component)Root).GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.isKinematic = true; component2.useGravity = false; ShyGuyLog.Debug("Set the creature's Rigidbody to kinematic so physics cannot fight movement."); } } catch (Exception ex) { ShyGuyLog.Debug("Configuring the Rigidbody threw: " + ex.Message); } } private void ResolveAgent(ShyGuyPrefabConfig config) { Agent = ((Component)Root).GetComponent(); if ((Object)(object)Agent == (Object)null) { Agent = ShyGuyHierarchy.ResolveComponent(Root, "NavMeshAgent", (IEnumerable)new string[1] { "NavMeshAgent" }); } if ((Object)(object)Agent == (Object)null) { try { Agent = ((Component)Root).gameObject.AddComponent(); ((Behaviour)Agent).enabled = false; _generated.Add("NavMeshAgent"); ShyGuyLog.Debug("Added a NavMeshAgent to the creature root."); return; } catch (Exception ex) { ShyGuyLog.Warn("Could not add a NavMeshAgent (" + ex.Message + "); the creature will rely on direct movement."); return; } } if ((Object)(object)((Component)Agent).transform != (Object)(object)Root) { ShyGuyLog.Warn("The NavMeshAgent is on '" + ((Object)Agent).name + "' rather than the prefab root. The agent moves the object it is attached to, so it must be on the root to move the creature. It is being ignored; direct movement will be used instead."); Agent = null; } } private void ResolveAudio(ShyGuyPrefabConfig config) { TriggerAudio = ResolveAudioSource(config.triggerAudioSourcePath, "TriggerAudioSource"); ChaseAudio = ResolveAudioSource(config.chaseAudioSourcePath, "ChaseAudioSource"); AttackAudio = ResolveAudioSource(config.attackAudioSourcePath, "AttackAudioSource"); AmbientAudio = ResolveAudioSource(config.ambientAudioSourcePath, "AmbientAudioSource"); } private AudioSource ResolveAudioSource(string path, string conventionalName) { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) Transform val = ShyGuyHierarchy.FindByPath(Root, path) ?? ShyGuyHierarchy.FindByName(Root, conventionalName); if ((Object)(object)val != (Object)null) { AudioSource component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null) { return component; } try { AudioSource result = ((Component)val).gameObject.AddComponent(); _generated.Add(conventionalName + " (AudioSource component on the existing object)"); return result; } catch (Exception ex) { ShyGuyLog.Debug("Adding an AudioSource to '" + ((Object)val).name + "' threw: " + ex.Message); return null; } } try { Transform val2 = ShyGuyHierarchy.FindFirstByNames(Root, ShyGuyPrefabConfig.AudioParentNames) ?? ShyGuyHierarchy.EnsureChild(Root, "Audio"); GameObject val3 = new GameObject(conventionalName); val3.transform.SetParent(val2, false); val3.transform.localPosition = Vector3.zero; AudioSource result2 = val3.AddComponent(); _generated.Add(conventionalName); return result2; } catch (Exception ex2) { ShyGuyLog.Warn("Could not create the audio source " + conventionalName + ": " + ex2.Message); return null; } } private void ApplyScale(ShyGuyPrefabConfig config) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (Mathf.Approximately(config.modelScale, 1f)) { return; } try { Root.localScale = Vector3.one * config.modelScale; ShyGuyLog.Debug("Applied a model scale of " + config.modelScale.ToString("F3") + "."); } catch (Exception ex) { ShyGuyLog.Debug("Applying the model scale threw: " + ex.Message); } } private void ReportGenerated() { if (_generated.Count == 0) { ShyGuyLog.Msg("Prefab wiring complete; every reference was found on the prefab."); return; } ShyGuyLog.Msg("Prefab wiring complete. " + _generated.Count + " item(s) were missing and had to be generated at runtime: " + string.Join("; ", _generated) + ". Add them to TheShyGuy.prefab for full control over their placement."); } internal string DescribeState() { return "Rig references:\n Root: " + (((Object)(object)Root != (Object)null) ? ((Object)Root).name : "MISSING") + "\n FacePoint: " + (((Object)(object)FaceDetectionPoint != (Object)null) ? (ShyGuyHierarchy.GetPath(Root, FaceDetectionPoint) + (FacePointWasGenerated ? " (generated)" : "")) : "MISSING") + "\n HeadBone: " + (((Object)(object)HeadBone != (Object)null) ? ((Object)HeadBone).name : "not identified") + "\n Animator: " + (((Object)(object)Animator != (Object)null) ? "yes" : "no") + "\n LegacyAnim: " + (((Object)(object)LegacyAnimation != (Object)null) ? "yes" : "no") + "\n NavMeshAgent:" + (((Object)(object)Agent != (Object)null) ? "yes" : "no") + "\n MainCollider:" + (((Object)(object)MainCollider != (Object)null) ? ((Object)MainCollider).name : "MISSING") + "\n TriggerCol: " + (((Object)(object)TriggerCollider != (Object)null) ? ((Object)TriggerCollider).name : "none") + "\n Generated: " + ((_generated.Count == 0) ? "nothing" : string.Join("; ", _generated)); } } [RegisterTypeInIl2Cpp] public sealed class TheShyGuyBehaviour : MonoBehaviour { private static readonly List Instances = new List(4); private ShyGuyRig _rig; private ShyGuyPrefabConfig _prefabConfig; private ShyGuyFaceDetector _detector; private ShyGuyNavigation _navigation; private ShyGuyAnimationController _animation; private ShyGuyAudioController _audio; private ShyGuyState _state; private ShyGuyTarget _target; private readonly HashSet _triggeredKeys = new HashSet(StringComparer.Ordinal); private bool _triggerPlayedThisCycle; private float _triggerEndsAt; private float _introEndsAt; private float _resetEndsAt; private float _lastAttackTime = -999f; private float _attackLandsAt; private ShyGuyTarget _attackTarget; private float _lastDoorReaction = -999f; private bool _shuttingDown; private bool _ready; private Action _targetLostHandler; private Action _targetRespawnedHandler; private Action _doorBlockedHandler; internal static IReadOnlyList All => Instances; internal ShyGuyState State => _state; internal ShyGuyTarget CurrentTarget => _target; public TheShyGuyBehaviour(IntPtr pointer) : base(pointer) { } internal void Initialise(ShyGuyPrefabConfig prefabConfig) { //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) if (_ready) { return; } _prefabConfig = prefabConfig ?? ShyGuyAssetLoader.GetPrefabConfig(); _rig = ShyGuyRig.Resolve(((Component)this).gameObject, _prefabConfig); if ((Object)(object)_rig.Root == (Object)null) { ShyGuyLog.Error("Could not resolve the creature's own transform; disabling this instance."); ((Behaviour)this).enabled = false; return; } _detector = new ShyGuyFaceDetector(((Component)this).transform); _navigation = new ShyGuyNavigation(((Component)this).transform, _rig.Agent, _prefabConfig.height); _doorBlockedHandler = OnDoorBlocked; _navigation.DoorBlocked += _doorBlockedHandler; _animation = new ShyGuyAnimationController(_rig.Animator, _rig.LegacyAnimation, _prefabConfig); _audio = new ShyGuyAudioController(((Component)this).transform, _prefabConfig, _rig); ShyGuyMaterialFixer.FixHierarchy(((Component)this).transform); ShyGuyPlayerFinder playerFinder = TheShyGuyMod.PlayerFinder; if (playerFinder != null) { _targetLostHandler = OnPlayerFinderTargetLost; _targetRespawnedHandler = OnPlayerFinderTargetRespawned; playerFinder.TargetLost += _targetLostHandler; playerFinder.TargetRespawned += _targetRespawnedHandler; } if (!Instances.Contains(this)) { Instances.Add(this); } _navigation.EvaluateNavMeshAvailability(); EnterSpawning(); _ready = true; string[] obj = new string[7] { "The Shy Guy is active at ", null, null, null, null, null, null }; Vector3 position = ((Component)this).transform.position; obj[1] = ((Vector3)(ref position)).ToString("F1"); obj[2] = ". Animation backend: "; obj[3] = _animation.Backend; obj[4] = ". Movement: "; obj[5] = (_navigation.UsingNavMesh ? "NavMesh" : "direct"); obj[6] = "."; ShyGuyLog.Msg(string.Concat(obj)); } private void Update() { if (!_shuttingDown && _ready) { ShyGuyLog.Guard("behaviour tick", Tick); } } private void OnDestroy() { _shuttingDown = true; try { _audio?.StopAll(); } catch { } try { _animation?.StopAll(); } catch { } if (_navigation != null) { if (_doorBlockedHandler != null) { _navigation.DoorBlocked -= _doorBlockedHandler; } _navigation.Dispose(); } ShyGuyPlayerFinder playerFinder = TheShyGuyMod.PlayerFinder; if (playerFinder != null) { if (_targetLostHandler != null) { playerFinder.TargetLost -= _targetLostHandler; } if (_targetRespawnedHandler != null) { playerFinder.TargetRespawned -= _targetRespawnedHandler; } } _targetLostHandler = null; _targetRespawnedHandler = null; _doorBlockedHandler = null; _target = null; _attackTarget = null; _triggeredKeys.Clear(); Instances.Remove(this); ShyGuyLog.Debug("A Shy Guy instance was destroyed and cleaned up. " + Instances.Count + " remain."); } private void Tick() { float deltaTime = Time.deltaTime; if (deltaTime <= 0f) { return; } if (!ShyGuyConfig.Enabled) { if (_state != ShyGuyState.Passive) { EnterPassive(); } _navigation.Stop(); return; } ShyGuyPlayerFinder playerFinder = TheShyGuyMod.PlayerFinder; if (playerFinder == null) { ShyGuyLog.DebugThrottled("tick:nofinder", "No player finder is available this frame.", 10f); return; } List livingPlayers = playerFinder.GetLivingPlayers(); PruneTriggeredKeys(playerFinder); switch (_state) { case ShyGuyState.Spawning: TickSpawning(deltaTime); break; case ShyGuyState.Passive: TickPassive(livingPlayers, deltaTime); break; case ShyGuyState.Triggering: TickTriggering(deltaTime); break; case ShyGuyState.Chasing: TickChasing(deltaTime); break; case ShyGuyState.Attacking: TickAttacking(deltaTime); break; case ShyGuyState.Resetting: TickResetting(deltaTime); break; default: ShyGuyLog.Warn("Unknown state " + _state.ToString() + "; returning to passive."); EnterPassive(); break; } } private void EnterSpawning() { //IL_0042: Unknown result type (might be due to invalid IL or missing references) _state = ShyGuyState.Spawning; _target = null; _attackTarget = null; _attackLandsAt = 0f; _navigation.Freeze(); _navigation.ResetStuckTracking(); _navigation.SetHome(((Component)this).transform.position); _audio.StopChase(); _audio.StopTrigger(); _audio.StartAmbient(); _animation.SetSpeedMultiplier(1f); bool num = _animation.Play("intro", restart: true); float introHoldDuration = _animation.GetIntroHoldDuration(); if (!num) { ShyGuyLog.Msg("No Intro animation found; entering passive immediately."); EnterPassive(announce: false); } else { _introEndsAt = Time.time + introHoldDuration; ShyGuyLog.Msg("Playing Intro (" + introHoldDuration.ToString("F1") + "s). Cannot be triggered until it finishes."); } } private void TickSpawning(float deltaTime) { _navigation.Freeze(); _animation.Play("intro"); if (!(Time.time < _introEndsAt)) { ShyGuyLog.Debug("Intro finished; entering passive wander."); EnterPassive(); } } private void TickPassive(List living, float deltaTime) { bool flag = _navigation.TickPassive(deltaTime); if (ShyGuyConfig.PassiveBehaviour == PassiveMode.Stationary) { _animation.Play("idle"); _animation.SetSpeedMultiplier(0.01f); } else { _animation.SetSpeedMultiplier(1f); _animation.Play(flag ? "walk" : "idle"); } _audio.StartAmbient(); ShyGuyTarget shyGuyTarget = _detector.Evaluate(living, _rig.FaceDetectionPoint, deltaTime); if (shyGuyTarget != null) { BeginTrigger(shyGuyTarget); } } private void BeginTrigger(ShyGuyTarget target) { if (_state != ShyGuyState.Passive) { ShyGuyLog.Debug("Ignoring a face detection: already in state " + _state.ToString() + "."); return; } if (_triggerPlayedThisCycle) { ShyGuyLog.Debug("Trigger already played this rage cycle; going straight to the chase."); _target = target; EnterChasing(); return; } if (target == null || !target.IsAlive) { ShyGuyLog.Debug("Trigger requested for an invalid target; ignoring."); return; } _triggeredKeys.Clear(); _triggeredKeys.Add(target.Key); target.HasTriggered = true; _target = target; _triggerPlayedThisCycle = true; _state = ShyGuyState.Triggering; _animation.SetSpeedMultiplier(1f); _navigation.Freeze(); _audio.StopAmbient(); _audio.StopChase(); _audio.PlayTrigger(); _animation.Play("trigger", restart: true); float triggerHoldDuration = _animation.GetTriggerHoldDuration(); _triggerEndsAt = Time.time + triggerHoldDuration; ShyGuyLog.Msg("The Shy Guy has been triggered by " + target.DisplayName + ". Raging for " + triggerHoldDuration.ToString("F1") + "s before the chase begins."); } private void TickTriggering(float deltaTime) { _navigation.Freeze(); _animation.Play("trigger"); if (!(Time.time < _triggerEndsAt)) { ShyGuyLog.Debug("Trigger animation finished."); if (_target == null || !_target.IsAlive) { ShyGuyLog.Debug("The triggering player is no longer valid after the trigger animation."); HandleTargetLost("became invalid during the trigger animation"); } else { EnterChasing(); } } } private void EnterChasing() { _state = ShyGuyState.Chasing; _navigation.ResetStuckTracking(); _navigation.ResumeChase(); _navigation.EvaluateNavMeshAvailability(); _audio.StopAmbient(); _audio.StopTrigger(); _audio.StartChase(); _animation.Play("run"); ShyGuyLog.Msg("The Shy Guy is now chasing " + ((_target != null) ? _target.DisplayName : "nobody") + "."); } private void TickChasing(float deltaTime) { //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_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) if (ValidateTarget()) { _target.RefreshLiveHead(); _audio.StartChase(); _animation.Play("run"); Vector3 navigationPosition = _target.NavigationPosition; float num = _navigation.Chase(navigationPosition, deltaTime); if (Vector3.Distance(((Component)this).transform.position, _target.EyePosition) <= ShyGuyConfig.AttackDistance || num <= ShyGuyConfig.AttackDistance) { EnterAttacking(); } } } private void EnterAttacking() { _state = ShyGuyState.Attacking; _navigation.Stop(); ShyGuyLog.Debug("In attack range of " + ((_target != null) ? _target.DisplayName : "nobody") + "."); } private void TickAttacking(float deltaTime) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0069: 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_0079: Unknown result type (might be due to invalid IL or missing references) if (!ValidateTarget()) { return; } _target.RefreshLiveHead(); if (Vector3.Distance(((Component)this).transform.position, _target.EyePosition) > ShyGuyConfig.AttackDistance * 1.4f) { ShyGuyLog.Debug(_target.DisplayName + " escaped attack range; resuming the chase."); EnterChasing(); return; } _navigation.RotateTowards(_target.EyePosition - ((Component)this).transform.position, deltaTime); if (_attackLandsAt > 0f) { if (Time.time >= _attackLandsAt) { LandAttack(); } } else if (Time.time - _lastAttackTime < ShyGuyConfig.AttackCooldown) { _animation.Play("run"); } else if (ShyGuyConfig.AttackRequiresLineOfSight && !HasLineOfSightTo(_target)) { ShyGuyLog.DebugThrottled("attack:nolos", "In range of " + _target.DisplayName + " but there is no clear line to them; not attacking through geometry."); EnterChasing(); } else { BeginAttackSwing(); } } private void BeginAttackSwing() { _attackTarget = _target; _attackLandsAt = Time.time + ShyGuyConfig.AttackWindup; _audio.StopChase(); _audio.PlayAttack(); _animation.Play("attack", restart: true); ShyGuyLog.Debug("Attack swing started against " + _attackTarget.DisplayName + "."); } private void LandAttack() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) _attackLandsAt = 0f; ShyGuyTarget attackTarget = _attackTarget; _attackTarget = null; if (attackTarget == null || !attackTarget.IsAlive) { ShyGuyLog.Debug("The attack landed on nobody: the target became invalid mid-swing."); _lastAttackTime = Time.time; return; } float num = Vector3.Distance(((Component)this).transform.position, attackTarget.EyePosition); if (num > ShyGuyConfig.AttackDistance * 1.6f) { ShyGuyLog.Debug(attackTarget.DisplayName + " dodged the attack by moving " + num.ToString("F1") + "m away."); _lastAttackTime = Time.time; EnterChasing(); return; } bool num2 = ShyGuyDamage.Attack(attackTarget); _lastAttackTime = Time.time; if (!num2) { EnterChasing(); return; } ShyGuyLog.Msg(attackTarget.DisplayName + " was killed by The Shy Guy; returning to idle."); HandleTargetLost("killed by The Shy Guy"); } private bool HasLineOfSightTo(ShyGuyTarget target) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) if (target == null) { return false; } Vector3 val = (((Object)(object)_rig.FaceDetectionPoint != (Object)null) ? _rig.FaceDetectionPoint.position : (((Component)this).transform.position + Vector3.up * 1.5f)); Vector3 val2 = target.EyePosition - val; float magnitude = ((Vector3)(ref val2)).magnitude; if (magnitude < 0.05f) { return true; } GameObject victimRig = target.RigRoot; if (!ShyGuyPhysics.Raycast(val, val2 / magnitude, magnitude, _detector.GetOcclusionMask(), out var nearest, (Collider collider) => (Object)(object)collider == (Object)null || ShyGuyHierarchy.IsPartOf(((Component)this).transform, ((Component)collider).transform) || ((Object)(object)victimRig != (Object)null && ShyGuyHierarchy.IsPartOf(victimRig.transform, ((Component)collider).transform)))) { return true; } ShyGuyLog.DebugThrottled("attack:blocked", "Attack line of sight to " + target.DisplayName + " is blocked by '" + (((Object)(object)((RaycastHit)(ref nearest)).collider != (Object)null) ? ((Object)((RaycastHit)(ref nearest)).collider).name : "unknown") + "'."); return false; } private void EnterResetting() { ShyGuyLog.Msg("Rage cycle ended; returning to passive (no calm-down pose)."); EnterPassive(announce: false); } private void TickResetting(float deltaTime) { EnterPassive(announce: false); } private void EnterPassive(bool announce = true) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) _state = ShyGuyState.Passive; _target = null; _attackTarget = null; _attackLandsAt = 0f; _triggerPlayedThisCycle = false; ClearAllLookState(); _triggeredKeys.Clear(); _navigation.Stop(); _navigation.ResetStuckTracking(); _navigation.SetHome(((Component)this).transform.position); _audio.StopChase(); _audio.StopTrigger(); _audio.StartAmbient(); _animation.SetSpeedMultiplier(1f); _animation.StopAll(); _animation.Play("idle", restart: true); if (announce) { ShyGuyLog.Msg("The Shy Guy is passive again. Do not look at its face."); } } private void ClearAllLookState() { ShyGuyPlayerFinder playerFinder = TheShyGuyMod.PlayerFinder; if (playerFinder == null) { return; } List livingPlayers = playerFinder.GetLivingPlayers(); for (int i = 0; i < livingPlayers.Count; i++) { ShyGuyTarget shyGuyTarget = livingPlayers[i]; if (shyGuyTarget != null) { shyGuyTarget.ClearLookState(); shyGuyTarget.HasTriggered = false; } } } private bool ValidateTarget() { if (_target == null) { HandleTargetLost("no target"); return false; } if (!_target.IsValid) { HandleTargetLost(_target.InvalidReason ?? "rig became invalid"); return false; } if (!_target.IsAlive) { HandleTargetLost("died"); return false; } if (!_target.IsLocal && _target.FusionId >= 0 && !ShyGuyFusionBridge.IsStillConnected(_target.FusionId)) { HandleTargetLost("left the Fusion session"); return false; } return true; } private void HandleTargetLost(string reason) { string text = ((_target != null) ? _target.DisplayName : "the target"); if (_target != null) { _triggeredKeys.Remove(_target.Key); _target.HasTriggered = false; _target.ClearLookState(); } _triggeredKeys.Clear(); _target = null; _attackTarget = null; _attackLandsAt = 0f; _audio.StopChase(); ShyGuyLog.Msg("Lost " + text + " (" + reason + "). Returning to passive; another player may trigger."); EnterResetting(); } private void PruneTriggeredKeys(ShyGuyPlayerFinder finder) { if (_triggeredKeys.Count == 0) { return; } List list = null; foreach (string triggeredKey in _triggeredKeys) { ShyGuyTarget shyGuyTarget = finder.Find(triggeredKey); if (shyGuyTarget == null || !shyGuyTarget.IsAlive) { (list ?? (list = new List())).Add(triggeredKey); } } if (list != null) { for (int i = 0; i < list.Count; i++) { _triggeredKeys.Remove(list[i]); ShyGuyLog.Debug("Removed '" + list[i] + "' from the triggered list: no longer a living player."); } } } private void OnPlayerFinderTargetLost(ShyGuyTarget lost, string reason) { if (_shuttingDown || lost == null) { return; } _triggeredKeys.Remove(lost.Key); if (_target != null && string.Equals(_target.Key, lost.Key, StringComparison.Ordinal)) { ShyGuyLog.Guard("target lost event", delegate { HandleTargetLost(reason ?? "disconnected"); }); } } private void OnPlayerFinderTargetRespawned(ShyGuyTarget respawned) { if (_shuttingDown || respawned == null) { return; } _triggeredKeys.Remove(respawned.Key); respawned.HasTriggered = false; respawned.ClearLookState(); if (_target != null && string.Equals(_target.Key, respawned.Key, StringComparison.Ordinal)) { ShyGuyLog.Guard("target respawn event", delegate { HandleTargetLost("respawned"); }); } } private void OnDoorBlocked(Collider door) { if (!_shuttingDown && _state == ShyGuyState.Chasing && !(Time.time - _lastDoorReaction < 2.5f)) { _lastDoorReaction = Time.time; ShyGuyLog.Debug("Forcing past the door '" + (((Object)(object)door != (Object)null) ? ((Object)door).name : "unknown") + "'."); _audio.PlayDoor(); _animation.Play("door", restart: true); } } internal void ForceTrigger(ShyGuyTarget target) { if (_ready) { if (_state != ShyGuyState.Passive) { ShyGuyLog.Msg("Cannot force a trigger: already in state " + _state.ToString() + "."); return; } if (target == null || !target.IsAlive) { ShyGuyLog.Warn("Cannot force a trigger: no valid living player was supplied."); return; } ShyGuyLog.Msg("Forcing a trigger against " + target.DisplayName + "."); BeginTrigger(target); } } internal void ForceCalm() { if (_ready) { ShyGuyLog.Msg("Forcing The Shy Guy to calm down."); EnterResetting(); } } internal string Describe() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) List list = new List(); Vector3 position = ((Component)this).transform.position; list.Add("=== The Shy Guy @ " + ((Vector3)(ref position)).ToString("F1") + " ==="); list.Add("State: " + _state.ToString() + " | target: " + ((_target != null) ? _target.ToString() : "none") + " | triggerPlayedThisCycle: " + _triggerPlayedThisCycle); list.Add("Triggered players (" + _triggeredKeys.Count + "): " + ((_triggeredKeys.Count == 0) ? "none" : string.Join(", ", _triggeredKeys))); list.Add((_rig != null) ? _rig.DescribeState() : "Rig: not resolved"); list.Add((_navigation != null) ? _navigation.DescribeState() : "Navigation: not resolved"); list.Add((_animation != null) ? _animation.DescribeState() : "Animation: not resolved"); list.Add((_audio != null) ? _audio.DescribeState() : "Audio: not resolved"); List list2 = list; ShyGuyPlayerFinder playerFinder = TheShyGuyMod.PlayerFinder; if (playerFinder != null && _detector != null && _rig != null) { list2.Add("Gaze evaluation:"); list2.Add(_detector.DescribeGaze(playerFinder.GetLivingPlayers(), _rig.FaceDetectionPoint)); } return string.Join("\n", list2); } internal static void DestroyAll() { List list = new List(Instances); Instances.Clear(); foreach (TheShyGuyBehaviour item in list) { if ((Object)(object)item == (Object)null) { continue; } try { GameObject gameObject = ((Component)item).gameObject; item._shuttingDown = true; if ((Object)(object)gameObject != (Object)null) { Object.Destroy((Object)(object)gameObject); } } catch (Exception ex) { ShyGuyLog.Debug("Destroying a Shy Guy instance threw: " + ex.Message); } } if (list.Count > 0) { ShyGuyLog.Msg("Destroyed " + list.Count + " Shy Guy instance(s)."); } } internal static int PurgeDestroyedInstances() { int num = 0; for (int num2 = Instances.Count - 1; num2 >= 0; num2--) { TheShyGuyBehaviour theShyGuyBehaviour = Instances[num2]; if ((Object)(object)theShyGuyBehaviour == (Object)null || (Object)(object)((Component)theShyGuyBehaviour).gameObject == (Object)null) { Instances.RemoveAt(num2); num++; } } if (num > 0) { ShyGuyLog.Debug("Purged " + num + " destroyed Shy Guy reference(s)."); } return num; } } internal enum PassiveMode { Stationary, IdleAnimation, Wander } internal enum TargetLostAction { ReturnToPassive, ChaseAnotherTriggeredPlayer, PreferAnotherThenPassive } internal static class ShyGuyConfig { internal const string CategoryId = "TheShyGuy"; internal const string CategoryDisplay = "The Shy Guy"; private static MelonPreferences_Category _category; private static bool _created; private static MelonPreferences_Entry _enabled; private static MelonPreferences_Entry _detectionDistance; private static MelonPreferences_Entry _detectionAngle; private static MelonPreferences_Entry _faceVisibilityRaycast; private static MelonPreferences_Entry _requiredLookDuration; private static MelonPreferences_Entry _triggerAnimationDurationFallback; private static MelonPreferences_Entry _chaseSpeed; private static MelonPreferences_Entry _chaseAcceleration; private static MelonPreferences_Entry _turningSpeed; private static MelonPreferences_Entry _navMeshRepathInterval; private static MelonPreferences_Entry _attackDistance; private static MelonPreferences_Entry _attackCooldown; private static MelonPreferences_Entry _damageAmount; private static MelonPreferences_Entry _instantKillEnabled; private static MelonPreferences_Entry _returnToPassiveAfterTargetDeath; private static MelonPreferences_Entry _chaseAnotherTriggeredPlayer; private static MelonPreferences_Entry _directMovementFallback; private static MelonPreferences_Entry _stuckDetectionTimeout; private static MelonPreferences_Entry _debugLogging; private static MelonPreferences_Entry _fusionSupportEnabled; private static MelonPreferences_Entry _faceFacingAngle; private static MelonPreferences_Entry _faceHitRadius; private static MelonPreferences_Entry _minimumDetectionDistance; private static MelonPreferences_Entry _lookMemoryDecay; private static MelonPreferences_Entry _occlusionIgnoreLayers; private static MelonPreferences_Entry _passiveMode; private static MelonPreferences_Entry _passiveWanderRadius; private static MelonPreferences_Entry _passiveWanderSpeed; private static MelonPreferences_Entry _passiveWanderInterval; private static MelonPreferences_Entry _passiveAmbientAudio; private static MelonPreferences_Entry _maxTriggerDuration; private static MelonPreferences_Entry _useClipLengthForTrigger; private static MelonPreferences_Entry _stoppingDistance; private static MelonPreferences_Entry _obstacleAvoidanceQuality; private static MelonPreferences_Entry _obstacleAvoidanceRadius; private static MelonPreferences_Entry _stuckMoveThreshold; private static MelonPreferences_Entry _stuckAccelerationBoost; private static MelonPreferences_Entry _stuckTeleportRecovery; private static MelonPreferences_Entry _stuckTeleportDistance; private static MelonPreferences_Entry _directFallbackGravity; private static MelonPreferences_Entry _directFallbackStepHeight; private static MelonPreferences_Entry _breakDoors; private static MelonPreferences_Entry _attackRequiresLineOfSight; private static MelonPreferences_Entry _attackWindup; private static MelonPreferences_Entry _resetStateDuration; private static MelonPreferences_Entry _spawnKey; private static MelonPreferences_Entry _despawnKey; private static MelonPreferences_Entry _spawnDistance; private static MelonPreferences_Entry _maxInstances; private static MelonPreferences_Entry _autoFindPlacedPrefab; private static MelonPreferences_Entry _autoSpawnOnLevelLoad; private static MelonPreferences_Entry _bundleFileName; private static MelonPreferences_Entry _prefabAssetName; private static MelonPreferences_Entry _fixMaterialsAtRuntime; internal static bool Enabled => _enabled?.Value ?? true; internal static float DetectionDistance => Mathf.Clamp(_detectionDistance?.Value ?? 45f, 1f, 500f); internal static float DetectionAngle => Mathf.Clamp(_detectionAngle?.Value ?? 60f, 1f, 179f); internal static bool FaceVisibilityRaycast => _faceVisibilityRaycast?.Value ?? true; internal static float RequiredLookDuration => Mathf.Max(0f, _requiredLookDuration?.Value ?? 0.35f); internal static float TriggerAnimationDurationFallback => Mathf.Clamp(_triggerAnimationDurationFallback?.Value ?? 30f, 0.1f, 120f); internal static float ChaseSpeed => Mathf.Clamp(_chaseSpeed?.Value ?? 14f, 0.5f, 100f); internal static float ChaseAcceleration => Mathf.Clamp(_chaseAcceleration?.Value ?? 22f, 0.5f, 500f); internal static float TurningSpeed => Mathf.Clamp(_turningSpeed?.Value ?? 540f, 15f, 5000f); internal static float NavMeshRepathInterval => Mathf.Clamp(_navMeshRepathInterval?.Value ?? 0.15f, 0.02f, 5f); internal static float AttackDistance => Mathf.Clamp(_attackDistance?.Value ?? 1.9f, 0.2f, 20f); internal static float AttackCooldown => Mathf.Clamp(_attackCooldown?.Value ?? 2.5f, 0.05f, 60f); internal static float DamageAmount => Mathf.Clamp(_damageAmount?.Value ?? 200f, 0f, 100000f); internal static bool InstantKillEnabled => _instantKillEnabled?.Value ?? true; internal static bool ReturnToPassiveAfterTargetDeath => _returnToPassiveAfterTargetDeath?.Value ?? true; internal static bool ChaseAnotherTriggeredPlayer => _chaseAnotherTriggeredPlayer?.Value ?? false; internal static bool DirectMovementFallback => _directMovementFallback?.Value ?? true; internal static float StuckDetectionTimeout => Mathf.Clamp(_stuckDetectionTimeout?.Value ?? 1.25f, 0.2f, 30f); internal static bool DebugLogging => _debugLogging?.Value ?? false; internal static bool FusionSupportEnabled => _fusionSupportEnabled?.Value ?? true; internal static float FaceFacingAngle => Mathf.Clamp(_faceFacingAngle?.Value ?? 100f, 5f, 360f); internal static float FaceHitRadius => Mathf.Clamp(_faceHitRadius?.Value ?? 0.55f, 0.05f, 10f); internal static float MinimumDetectionDistance => Mathf.Clamp(_minimumDetectionDistance?.Value ?? 0.35f, 0f, 10f); internal static float LookMemoryDecay => Mathf.Clamp(_lookMemoryDecay?.Value ?? 2f, 0f, 50f); internal static string OcclusionIgnoreLayers => _occlusionIgnoreLayers?.Value ?? string.Empty; internal static PassiveMode PassiveBehaviour { get { if ((_passiveMode?.Value ?? 2) == 0) { return PassiveMode.Stationary; } return PassiveMode.Wander; } } internal static float PassiveWanderRadius => Mathf.Clamp(_passiveWanderRadius?.Value ?? 6f, 0.5f, 200f); internal static float PassiveWanderSpeed => Mathf.Clamp(_passiveWanderSpeed?.Value ?? 1.1f, 0.05f, 20f); internal static float PassiveWanderInterval => Mathf.Clamp(_passiveWanderInterval?.Value ?? 6f, 0.5f, 120f); internal static bool PassiveAmbientAudio => _passiveAmbientAudio?.Value ?? true; internal static float MaxTriggerDuration => Mathf.Max(0f, _maxTriggerDuration?.Value ?? 35f); internal static bool UseClipLengthForTrigger => _useClipLengthForTrigger?.Value ?? true; internal static float StoppingDistance => Mathf.Clamp(_stoppingDistance?.Value ?? 1.2f, 0f, 20f); internal static int ObstacleAvoidanceQuality => Mathf.Clamp(_obstacleAvoidanceQuality?.Value ?? 2, 0, 4); internal static float ObstacleAvoidanceRadius => Mathf.Clamp(_obstacleAvoidanceRadius?.Value ?? 0.45f, 0.05f, 5f); internal static float StuckMoveThreshold => Mathf.Clamp(_stuckMoveThreshold?.Value ?? 0.35f, 0.01f, 10f); internal static float StuckAccelerationBoost => Mathf.Clamp(_stuckAccelerationBoost?.Value ?? 2.5f, 1f, 20f); internal static bool StuckTeleportRecovery => _stuckTeleportRecovery?.Value ?? false; internal static float StuckTeleportDistance => Mathf.Clamp(_stuckTeleportDistance?.Value ?? 0.4f, 0.01f, 2f); internal static float DirectFallbackGravity => Mathf.Clamp(_directFallbackGravity?.Value ?? 22f, 0f, 200f); internal static float DirectFallbackStepHeight => Mathf.Clamp(_directFallbackStepHeight?.Value ?? 0.95f, 0f, 5f); internal static bool BreakDoors => _breakDoors?.Value ?? true; internal static bool AttackRequiresLineOfSight => _attackRequiresLineOfSight?.Value ?? true; internal static float AttackWindup => Mathf.Clamp(_attackWindup?.Value ?? 0.25f, 0f, 10f); internal static float ResetStateDuration => Mathf.Clamp(_resetStateDuration?.Value ?? 5.5f, 0f, 60f); internal static string SpawnKeyName => _spawnKey?.Value ?? "F8"; internal static string DespawnKeyName => _despawnKey?.Value ?? "F9"; internal static float SpawnDistance => Mathf.Clamp(_spawnDistance?.Value ?? 6f, 0.5f, 50f); internal static int MaxInstances => Mathf.Clamp(_maxInstances?.Value ?? 1, 1, 16); internal static bool AutoFindPlacedPrefab => _autoFindPlacedPrefab?.Value ?? true; internal static bool AutoSpawnOnLevelLoad => _autoSpawnOnLevelLoad?.Value ?? false; internal static string BundleFileName { get { if (!string.IsNullOrWhiteSpace(_bundleFileName?.Value)) { return _bundleFileName.Value.Trim(); } return "theshyguy.bundle"; } } internal static string PrefabAssetName { get { if (!string.IsNullOrWhiteSpace(_prefabAssetName?.Value)) { return _prefabAssetName.Value.Trim(); } return "TheShyGuy"; } } internal static bool FixMaterialsAtRuntime => _fixMaterialsAtRuntime?.Value ?? true; internal static TargetLostAction TargetLost { get { bool chaseAnotherTriggeredPlayer = ChaseAnotherTriggeredPlayer; bool returnToPassiveAfterTargetDeath = ReturnToPassiveAfterTargetDeath; if (chaseAnotherTriggeredPlayer && returnToPassiveAfterTargetDeath) { return TargetLostAction.PreferAnotherThenPassive; } if (chaseAnotherTriggeredPlayer) { return TargetLostAction.ChaseAnotherTriggeredPlayer; } return TargetLostAction.ReturnToPassive; } } internal static KeyCode SpawnKey => ParseKey(SpawnKeyName, "SpawnKey"); internal static KeyCode DespawnKey => ParseKey(DespawnKeyName, "DespawnKey"); internal static void Initialise() { if (!_created) { _created = true; _category = MelonPreferences.CreateCategory("TheShyGuy", "The Shy Guy"); _category.SetFilePath(Path.Combine(MelonEnvironment.UserDataDirectory, "TheShyGuy.cfg"), true, false); _enabled = Make("Enabled", value: true, "Master switch. When false the creature stays passive and never triggers."); _detectionDistance = Make("DetectionDistance", 45f, "Metres. Beyond this range a player looking at the face is ignored."); _detectionAngle = Make("DetectionAngle", 60f, "Full cone angle in degrees, measured from the player's view direction. The face must fall inside this cone. 60 means +/-30 degrees off-centre."); _faceVisibilityRaycast = Make("FaceVisibilityRaycast", value: true, "Require an unobstructed line of sight to the face. Turn off only for debugging - with this off, walls and doors stop blocking detection."); _requiredLookDuration = Make("RequiredLookDuration", 0.35f, "Seconds a player must hold their gaze on the face before it triggers. 0 triggers on the first frame."); _triggerAnimationDurationFallback = Make("TriggerAnimationDurationFallback", 30f, "Seconds to hold the trigger pose when the clip length cannot be read. Defaults to the Rage take length."); _chaseSpeed = Make("ChaseSpeed", 14f, "Metres per second at full sprint. SCP-096 is meant to be faster than a player can run; 14 is roughly three times player sprint speed."); _chaseAcceleration = Make("ChaseAcceleration", 22f, "Metres per second squared. Lower values make it visibly wind up to top speed instead of snapping to it."); _turningSpeed = Make("TurningSpeed", 540f, "Degrees per second of yaw rotation while chasing."); _navMeshRepathInterval = Make("NavMeshRepathInterval", 0.15f, "Seconds between destination updates. Low values follow moving players tightly at some CPU cost."); _attackDistance = Make("AttackDistance", 1.9f, "Metres from the creature's centre to the target's head at which the attack fires."); _attackCooldown = Make("AttackCooldown", 2.5f, "Minimum seconds between two attacks. Prevents the attack firing every frame."); _damageAmount = Make("DamageAmount", 200f, "Damage applied per attack when InstantKillEnabled is false."); _instantKillEnabled = Make("InstantKillEnabled", value: true, "Kill the player outright instead of applying DamageAmount."); _returnToPassiveAfterTargetDeath = Make("ReturnToPassiveAfterTargetDeath", value: true, "When the victim dies, respawns or disconnects, calm down and go back to passive."); _chaseAnotherTriggeredPlayer = Make("ChaseAnotherTriggeredPlayer", value: false, "Legacy option. Ignored: only one player may trigger per rage cycle, and losing them always calms The Shy Guy down."); _directMovementFallback = Make("DirectMovementFallback", value: true, "If the level has no baked NavMesh, or pathfinding fails, steer directly towards the target instead of standing still. Required for most BONELAB levels - see the README."); _stuckDetectionTimeout = Make("StuckDetectionTimeout", 1.25f, "Seconds of trying to move without making progress before stuck recovery kicks in."); _debugLogging = Make("DebugLogging", value: false, "Verbose console output for detection, pathing and state changes. Repeating messages are rate limited."); _fusionSupportEnabled = Make("FusionSupportEnabled", value: true, "Use LabFusion for remote player discovery and identity when Fusion is installed. Has no effect when Fusion is absent."); _faceFacingAngle = Make("FaceFacingAngle", 100f, "Full cone angle in degrees around the face's own forward axis. The player must be standing inside this cone for the gaze to count, which is what stops the back and sides of the head from triggering it."); _faceHitRadius = Make("FaceHitRadius", 0.55f, "Metres. A line-of-sight hit on the creature only counts as 'saw the face' if it lands within this distance of the face point. This is what stops looking at the body or legs from triggering it."); _minimumDetectionDistance = Make("MinimumDetectionDistance", 0.35f, "Metres. Gazes closer than this are ignored so a camera clipping through the head does not self-trigger."); _lookMemoryDecay = Make("LookMemoryDecay", 2f, "How fast accumulated look time drains when a player looks away, as a multiple of real time. 2 means half a second of looking is forgotten in a quarter second."); _occlusionIgnoreLayers = Make("OcclusionIgnoreLayers", "Player,PlayerAvatar,Trigger,NoCollide,Hand,Interactable,UI,IgnoreRaycast", "Comma separated Unity layer names excluded from the line-of-sight raycast. Names that do not exist in the loaded level are skipped silently."); _passiveMode = Make("PassiveMode", 2, "0 = stand perfectly still, 1 = stand still and play the idle animation, 2 = wander slowly near the spawn point (default)."); _passiveWanderRadius = Make("PassiveWanderRadius", 6f, "Metres from the spawn point when PassiveMode is 2 (Wander)."); _passiveWanderSpeed = Make("PassiveWanderSpeed", 1.1f, "Metres per second while wandering passively."); _passiveWanderInterval = Make("PassiveWanderInterval", 6f, "Seconds between picking a new passive wander destination."); _passiveAmbientAudio = Make("PassiveAmbientAudio", value: true, "Loop the ambience clip on the ambient audio source while passive."); _maxTriggerDuration = Make("MaxTriggerDuration", 35f, "Hard cap in seconds on how long the creature stays frozen in the trigger animation. Covers the full Rage take (~30s). Set to 0 to disable the cap."); _useClipLengthForTrigger = Make("UseClipLengthForTrigger", value: true, "Read the real length of the trigger animation and wait exactly that long (still clamped by MaxTriggerDuration). When false, always use TriggerAnimationDurationFallback."); _stoppingDistance = Make("StoppingDistance", 1.2f, "NavMeshAgent stopping distance. Keep below AttackDistance or it will stop short of attack range."); _obstacleAvoidanceQuality = Make("ObstacleAvoidanceQuality", 2, "NavMeshAgent avoidance: 0 = none, 1 = low, 2 = medium, 3 = good, 4 = high. Lower is cheaper and less prone to the agent refusing to move in tight corridors."); _obstacleAvoidanceRadius = Make("ObstacleAvoidanceRadius", 0.45f, "Metres. Agent radius, and the sweep radius used by the direct-movement fallback."); _stuckMoveThreshold = Make("StuckMoveThreshold", 0.35f, "Metres of progress required within StuckDetectionTimeout to be considered 'not stuck'."); _stuckAccelerationBoost = Make("StuckAccelerationBoost", 2.5f, "Acceleration multiplier applied for one second during stuck recovery stage 2."); _stuckTeleportRecovery = Make("StuckTeleportRecovery", value: false, "Allow stuck recovery stage 4: a very small teleport along the desired direction. Off by default - it feels like sliding."); _stuckTeleportDistance = Make("StuckTeleportDistance", 0.4f, "Metres of last-resort teleport. Deliberately tiny - this nudges through a lip of geometry, it is not a blink."); _directFallbackGravity = Make("DirectFallbackGravity", 22f, "Metres per second squared of downward pull used by the direct-movement fallback to keep the creature on the floor."); _directFallbackStepHeight = Make("DirectFallbackStepHeight", 0.95f, "Metres. Maximum ledge/stair the direct-movement fallback will step up."); _breakDoors = Make("BreakDoors", value: true, "Play the door-forcing animation and sound when a door-like collider blocks the path during a chase."); _attackRequiresLineOfSight = Make("AttackRequiresLineOfSight", value: true, "Require a clear line to the target before the attack lands, so it cannot kill through a wall it is pressed against."); _attackWindup = Make("AttackWindup", 0.25f, "Seconds between the attack animation starting and the damage landing."); _resetStateDuration = Make("ResetStateDuration", 5.5f, "Seconds spent in the Resetting state calming down before returning to passive. Defaults to the length of the supplied calmdown clip."); _spawnKey = Make("SpawnKey", "F8", "UnityEngine.KeyCode name. Spawns The Shy Guy in front of the local player. Empty string disables the shortcut."); _despawnKey = Make("DespawnKey", "F9", "UnityEngine.KeyCode name. Despawns every Shy Guy. Empty string disables the shortcut."); _spawnDistance = Make("SpawnDistance", 6f, "Metres in front of the local player to spawn at when using the shortcut or console command."); _maxInstances = Make("MaxInstances", 1, "How many Shy Guys may exist at once. Spawning past this limit despawns the oldest, which is what stops duplicates accumulating across level loads."); _autoFindPlacedPrefab = Make("AutoFindPlacedPrefab", value: true, "After a level loads, look for a GameObject named TheShyGuy that was placed in the level itself and adopt it."); _autoSpawnOnLevelLoad = Make("AutoSpawnOnLevelLoad", value: false, "Spawn one automatically shortly after every level loads. Off by default - use the keybind instead."); _bundleFileName = Make("BundleFileName", "theshyguy.bundle", "File name inside UserData/TheShyGuy/ to load the prefab from. Ignored when the bundle is embedded in the DLL."); _prefabAssetName = Make("PrefabAssetName", "TheShyGuy", "Asset name to load from the bundle. Matches TheShyGuy.prefab."); _fixMaterialsAtRuntime = Make("FixMaterialsAtRuntime", value: true, "Re-point bundle materials at the shaders present in the running game. Fixes the magenta 'missing shader' look when the bundle was built against a different render pipeline."); ShyGuyLog.Msg("Preferences ready (" + MelonEnvironment.UserDataDirectory + "\\TheShyGuy.cfg)."); } } private static MelonPreferences_Entry Make(string id, T value, string description) { return _category.CreateEntry(id, value, id, description, false, false, (ValueValidator)null, (string)null); } internal static void Save() { try { MelonPreferences_Category category = _category; if (category != null) { category.SaveToFile(false); } } catch (Exception ex) { ShyGuyLog.Debug("Could not save preferences: " + ex.Message); } } private static KeyCode ParseKey(string name, string settingId) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(name)) { return (KeyCode)0; } if (Enum.TryParse(name.Trim(), ignoreCase: true, out KeyCode result)) { return result; } ShyGuyLog.WarnOnce("badkey:" + settingId, "Preference " + settingId + " is set to '" + name + "', which is not a UnityEngine.KeyCode name. The shortcut is disabled. Valid examples: F8, Backslash, Keypad0."); return (KeyCode)0; } } internal sealed class ShyGuyPrefabConfig { internal const string TextAssetName = "TheShyGuyConfig"; public string faceDetectionPointPath = "FaceDetectionPoint"; public string headBonePath = ""; public string animatorPath = ""; public string mainColliderPath = "MainCollider"; public string triggerColliderPath = "TriggerCollider"; public string triggerAudioSourcePath = "Audio/TriggerAudioSource"; public string chaseAudioSourcePath = "Audio/ChaseAudioSource"; public string attackAudioSourcePath = "Audio/AttackAudioSource"; public string ambientAudioSourcePath = "Audio/AmbientAudioSource"; public bool useLegacyAnimation; public string idleState = "Idle"; public string triggerState = "Panic"; public string runState = "Run"; public string attackState = "Kill"; public string resetState = "Idle"; public string doorState = "ForceOpenDoor"; public string walkState = "Walk"; public string introState = "Intro"; public int animatorLayer; public float animationCrossFade = 0.12f; public float triggerClipLength; public string triggerClipName = "voicemod_scream"; public string chaseClipName = "scream"; public string attackClipName = "096 kill"; public string ambientClipName = "ambiance"; public string resetClipName = "calmdown"; public string doorClipName = "dooropen"; public float triggerVolume = 1f; public float chaseVolume = 1f; public float attackVolume = 1f; public float ambientVolume = 0.45f; public float audioMaxDistance = 60f; public float modelScale = 1f; public float height = 2.4f; public float fallbackFaceHeight = 2.05f; public float fallbackFaceForward = 0.16f; internal static readonly string[] FacePointNames = new string[6] { "FaceDetectionPoint", "FacePoint", "Face", "FaceDetection", "HeadPoint", "EyePoint" }; internal static readonly string[] HeadBoneNames = new string[6] { "Head", "head", "Bip01 Head", "mixamorig:Head", "Neck", "Skull" }; internal static readonly string[] AudioParentNames = new string[3] { "Audio", "Sounds", "SFX" }; internal static readonly Dictionary StateNameCandidates = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "idle", new string[4] { "Idle", "idle", "@idle", "scp096.mdl/@idle" } }, { "trigger", new string[7] { "Panic", "Rage", "@Rage", "scp096.mdl/@Rage", "Trigger", "intro", "@intro" } }, { "run", new string[5] { "Run", "run", "@run", "scp096.mdl/@run", "Chase" } }, { "attack", new string[4] { "Kill", "@Kill", "scp096.mdl/@Kill", "Attack" } }, { "reset", new string[3] { "Idle", "idle", "@idle" } }, { "door", new string[4] { "ForceOpenDoor", "@ForceOpenDoor", "scp096.mdl/@ForceOpenDoor", "DoorOpen" } }, { "walk", new string[4] { "Walk", "walk", "@walk", "scp096.mdl/@walk" } }, { "intro", new string[4] { "Intro", "intro", "@intro", "scp096.mdl/@intro" } } }; internal static ShyGuyPrefabConfig Parse(string json, string sourceDescription) { ShyGuyPrefabConfig shyGuyPrefabConfig = new ShyGuyPrefabConfig(); if (string.IsNullOrWhiteSpace(json)) { ShyGuyLog.Debug("No baked prefab config found (" + sourceDescription + "); using built-in defaults."); return shyGuyPrefabConfig; } try { Dictionary dictionary = ShyGuyJson.ParseFlatObject(json); if (dictionary.Count == 0) { ShyGuyLog.Warn("The baked prefab config at " + sourceDescription + " contained no readable values; using defaults."); return shyGuyPrefabConfig; } ShyGuyPrefabConfig shyGuyPrefabConfig2 = new ShyGuyPrefabConfig { faceDetectionPointPath = ShyGuyJson.GetString(dictionary, "faceDetectionPointPath", shyGuyPrefabConfig.faceDetectionPointPath), headBonePath = ShyGuyJson.GetString(dictionary, "headBonePath", shyGuyPrefabConfig.headBonePath), animatorPath = ShyGuyJson.GetString(dictionary, "animatorPath", shyGuyPrefabConfig.animatorPath), mainColliderPath = ShyGuyJson.GetString(dictionary, "mainColliderPath", shyGuyPrefabConfig.mainColliderPath), triggerColliderPath = ShyGuyJson.GetString(dictionary, "triggerColliderPath", shyGuyPrefabConfig.triggerColliderPath), triggerAudioSourcePath = ShyGuyJson.GetString(dictionary, "triggerAudioSourcePath", shyGuyPrefabConfig.triggerAudioSourcePath), chaseAudioSourcePath = ShyGuyJson.GetString(dictionary, "chaseAudioSourcePath", shyGuyPrefabConfig.chaseAudioSourcePath), attackAudioSourcePath = ShyGuyJson.GetString(dictionary, "attackAudioSourcePath", shyGuyPrefabConfig.attackAudioSourcePath), ambientAudioSourcePath = ShyGuyJson.GetString(dictionary, "ambientAudioSourcePath", shyGuyPrefabConfig.ambientAudioSourcePath), useLegacyAnimation = ShyGuyJson.GetBool(dictionary, "useLegacyAnimation", shyGuyPrefabConfig.useLegacyAnimation), idleState = ShyGuyJson.GetString(dictionary, "idleState", shyGuyPrefabConfig.idleState), triggerState = ShyGuyJson.GetString(dictionary, "triggerState", shyGuyPrefabConfig.triggerState), runState = ShyGuyJson.GetString(dictionary, "runState", shyGuyPrefabConfig.runState), attackState = ShyGuyJson.GetString(dictionary, "attackState", shyGuyPrefabConfig.attackState), resetState = ShyGuyJson.GetString(dictionary, "resetState", shyGuyPrefabConfig.resetState), doorState = ShyGuyJson.GetString(dictionary, "doorState", shyGuyPrefabConfig.doorState), walkState = ShyGuyJson.GetString(dictionary, "walkState", shyGuyPrefabConfig.walkState), introState = ShyGuyJson.GetString(dictionary, "introState", shyGuyPrefabConfig.introState), animatorLayer = ShyGuyJson.GetInt(dictionary, "animatorLayer", shyGuyPrefabConfig.animatorLayer), animationCrossFade = ShyGuyJson.GetFloat(dictionary, "animationCrossFade", shyGuyPrefabConfig.animationCrossFade), triggerClipLength = ShyGuyJson.GetFloat(dictionary, "triggerClipLength", shyGuyPrefabConfig.triggerClipLength), triggerClipName = ShyGuyJson.GetString(dictionary, "triggerClipName", shyGuyPrefabConfig.triggerClipName), chaseClipName = ShyGuyJson.GetString(dictionary, "chaseClipName", shyGuyPrefabConfig.chaseClipName), attackClipName = ShyGuyJson.GetString(dictionary, "attackClipName", shyGuyPrefabConfig.attackClipName), ambientClipName = ShyGuyJson.GetString(dictionary, "ambientClipName", shyGuyPrefabConfig.ambientClipName), resetClipName = ShyGuyJson.GetString(dictionary, "resetClipName", shyGuyPrefabConfig.resetClipName), doorClipName = ShyGuyJson.GetString(dictionary, "doorClipName", shyGuyPrefabConfig.doorClipName), triggerVolume = ShyGuyJson.GetFloat(dictionary, "triggerVolume", shyGuyPrefabConfig.triggerVolume), chaseVolume = ShyGuyJson.GetFloat(dictionary, "chaseVolume", shyGuyPrefabConfig.chaseVolume), attackVolume = ShyGuyJson.GetFloat(dictionary, "attackVolume", shyGuyPrefabConfig.attackVolume), ambientVolume = ShyGuyJson.GetFloat(dictionary, "ambientVolume", shyGuyPrefabConfig.ambientVolume), audioMaxDistance = ShyGuyJson.GetFloat(dictionary, "audioMaxDistance", shyGuyPrefabConfig.audioMaxDistance), modelScale = ShyGuyJson.GetFloat(dictionary, "modelScale", shyGuyPrefabConfig.modelScale), height = ShyGuyJson.GetFloat(dictionary, "height", shyGuyPrefabConfig.height), fallbackFaceHeight = ShyGuyJson.GetFloat(dictionary, "fallbackFaceHeight", shyGuyPrefabConfig.fallbackFaceHeight), fallbackFaceForward = ShyGuyJson.GetFloat(dictionary, "fallbackFaceForward", shyGuyPrefabConfig.fallbackFaceForward) }; shyGuyPrefabConfig2.Sanitise(shyGuyPrefabConfig); ShyGuyLog.Msg("Loaded baked prefab config from " + sourceDescription + " (" + dictionary.Count + " value(s))."); return shyGuyPrefabConfig2; } catch (Exception ex) { ShyGuyLog.Warn("Could not parse the baked prefab config at " + sourceDescription + " (" + ex.Message + "). Using built-in defaults instead."); return shyGuyPrefabConfig; } } private void Sanitise(ShyGuyPrefabConfig d) { if (modelScale <= 0.0001f) { modelScale = d.modelScale; } if (height <= 0.1f) { height = d.height; } if (animatorLayer < 0) { animatorLayer = 0; } if (animationCrossFade < 0f) { animationCrossFade = 0f; } if (triggerClipLength < 0f) { triggerClipLength = 0f; } if (audioMaxDistance <= 0.5f) { audioMaxDistance = d.audioMaxDistance; } if (string.IsNullOrWhiteSpace(triggerClipName)) { triggerClipName = "voicemod_scream"; } if (string.IsNullOrWhiteSpace(chaseClipName) || chaseClipName.IndexOf("voicemod", StringComparison.OrdinalIgnoreCase) >= 0) { chaseClipName = "scream"; } if (string.Equals(resetState, "HoldItIn", StringComparison.OrdinalIgnoreCase)) { resetState = "Idle"; } if (string.IsNullOrWhiteSpace(introState)) { introState = "Intro"; } triggerVolume = Mathf.Clamp01((triggerVolume <= 0f) ? d.triggerVolume : triggerVolume); chaseVolume = Mathf.Clamp01((chaseVolume <= 0f) ? d.chaseVolume : chaseVolume); attackVolume = Mathf.Clamp01((attackVolume <= 0f) ? d.attackVolume : attackVolume); ambientVolume = Mathf.Clamp01((ambientVolume < 0f) ? d.ambientVolume : ambientVolume); faceDetectionPointPath = Trim(faceDetectionPointPath); headBonePath = Trim(headBonePath); animatorPath = Trim(animatorPath); mainColliderPath = Trim(mainColliderPath); triggerColliderPath = Trim(triggerColliderPath); triggerAudioSourcePath = Trim(triggerAudioSourcePath); chaseAudioSourcePath = Trim(chaseAudioSourcePath); attackAudioSourcePath = Trim(attackAudioSourcePath); ambientAudioSourcePath = Trim(ambientAudioSourcePath); } private static string Trim(string s) { if (!string.IsNullOrWhiteSpace(s)) { return s.Trim(); } return string.Empty; } } internal static class ShyGuyHierarchy { internal static Transform FindByPath(Transform root, string path) { if ((Object)(object)root == (Object)null || string.IsNullOrWhiteSpace(path)) { return null; } string text = path.Trim().Trim('/'); if (text.Length == 0) { return null; } Transform val = null; try { val = root.Find(text); } catch (Exception ex) { ShyGuyLog.Debug("Transform.Find('" + text + "') threw: " + ex.Message); } if ((Object)(object)val != (Object)null) { return val; } int num = text.LastIndexOf('/'); string name = ((num >= 0) ? text.Substring(num + 1) : text); Transform val2 = FindByName(root, name); if ((Object)(object)val2 != (Object)null) { ShyGuyLog.Debug("Resolved '" + path + "' by name search to '" + GetPath(root, val2) + "'."); } return val2; } internal static Transform FindByName(Transform root, string name) { if ((Object)(object)root == (Object)null || string.IsNullOrWhiteSpace(name)) { return null; } return Search(root, name, StringComparison.Ordinal) ?? Search(root, name, StringComparison.OrdinalIgnoreCase); } internal static Transform FindFirstByNames(Transform root, IEnumerable names) { if ((Object)(object)root == (Object)null || names == null) { return null; } foreach (string name in names) { Transform val = FindByName(root, name); if ((Object)(object)val != (Object)null) { return val; } } return null; } private static Transform Search(Transform current, string name, StringComparison mode) { if ((Object)(object)current == (Object)null) { return null; } int childCount = current.childCount; for (int i = 0; i < childCount; i++) { Transform val = null; try { val = current.GetChild(i); } catch { continue; } if (!((Object)(object)val == (Object)null)) { if (string.Equals(((Object)val).name, name, mode)) { return val; } Transform val2 = Search(val, name, mode); if ((Object)(object)val2 != (Object)null) { return val2; } } } return null; } internal static string GetPath(Transform root, Transform target) { if ((Object)(object)target == (Object)null) { return "(null)"; } if ((Object)(object)root == (Object)null || (Object)(object)target == (Object)(object)root) { return ((Object)target).name; } List list = new List(); Transform val = target; int num = 0; while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)root && num++ < 64) { list.Add(((Object)val).name); val = val.parent; } list.Reverse(); return string.Join("/", list); } internal static Transform EnsureChild(Transform parent, string name) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)parent == (Object)null) { return null; } Transform val = FindByName(parent, name); if ((Object)(object)val != (Object)null) { return val; } GameObject val2 = new GameObject(name); val2.transform.SetParent(parent, false); val2.transform.localPosition = Vector3.zero; val2.transform.localRotation = Quaternion.identity; val2.transform.localScale = Vector3.one; ShyGuyLog.Debug("Created missing child '" + name + "' under '" + ((Object)parent).name + "'."); return val2.transform; } internal static T ResolveComponent(Transform root, string path, IEnumerable nameCandidates) where T : Component { if ((Object)(object)root == (Object)null) { return default(T); } Transform val = FindByPath(root, path); if ((Object)(object)val != (Object)null) { T component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null) { return component; } } if (nameCandidates != null) { foreach (string nameCandidate in nameCandidates) { Transform val2 = FindByName(root, nameCandidate); if (!((Object)(object)val2 == (Object)null)) { T component2 = ((Component)val2).GetComponent(); if ((Object)(object)component2 != (Object)null) { return component2; } } } } try { T componentInChildren = ((Component)root).GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null) { return componentInChildren; } } catch (Exception ex) { ShyGuyLog.Debug("GetComponentInChildren<" + typeof(T).Name + "> threw: " + ex.Message); } return default(T); } internal static bool IsPartOf(Transform root, Transform candidate) { if ((Object)(object)root == (Object)null || (Object)(object)candidate == (Object)null) { return false; } Transform val = candidate; int num = 0; while ((Object)(object)val != (Object)null && num++ < 128) { if ((Object)(object)val == (Object)(object)root) { return true; } val = val.parent; } return false; } internal static Camera FindCamera(Transform root) { if ((Object)(object)root == (Object)null) { return null; } try { Il2CppArrayBase componentsInChildren = ((Component)root).GetComponentsInChildren(false); if (componentsInChildren == null) { return null; } for (int i = 0; i < componentsInChildren.Length; i++) { Camera val = componentsInChildren[i]; if ((Object)(object)val != (Object)null && ((Behaviour)val).enabled && ((Component)val).gameObject.activeInHierarchy) { return val; } } } catch (Exception ex) { ShyGuyLog.Debug("FindCamera threw: " + ex.Message); } return null; } internal static void DumpHierarchy(Transform root, int maxDepth = 4) { if (!((Object)(object)root == (Object)null)) { Dump(root, 0, maxDepth); } } private static void Dump(Transform t, int depth, int maxDepth) { if ((Object)(object)t == (Object)null || depth > maxDepth) { return; } List list = new List(); try { Il2CppArrayBase components = ((Component)t).GetComponents(); if (components != null) { foreach (Component item in components) { if ((Object)(object)item == (Object)null) { list.Add(""); } else { list.Add(((object)item).GetType().Name); } } } } catch { } ShyGuyLog.Msg(new string(' ', depth * 2) + "- " + ((Object)t).name + " [" + string.Join(", ", list) + "]"); for (int i = 0; i < t.childCount; i++) { Dump(t.GetChild(i), depth + 1, maxDepth); } } } internal static class ShyGuyInput { private static bool _resolved; private static bool _hasInputSystem; private static Type _keyboardType; private static PropertyInfo _keyboardCurrent; private static Type _keyEnum; private static void EnsureResolved() { if (_resolved) { return; } _resolved = true; try { _keyboardType = ShyGuyReflect.FindType("UnityEngine.InputSystem.Keyboard", new string[1] { "UnityEngine.InputSystem.Keyboard" }); if (!(_keyboardType == null)) { _keyboardCurrent = _keyboardType.GetProperty("current", BindingFlags.Static | BindingFlags.Public); _keyEnum = ShyGuyReflect.FindType("UnityEngine.InputSystem.Key", new string[1] { "UnityEngine.InputSystem.Key" }); _hasInputSystem = _keyboardCurrent != null && _keyEnum != null; if (_hasInputSystem) { ShyGuyLog.Msg("Input System detected - spawn shortcuts will also use Keyboard.current."); } } } catch (Exception ex) { ShyGuyLog.Debug("Input System probe failed: " + ex.Message); _hasInputSystem = false; } } internal static bool GetKeyDown(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) if ((int)key == 0) { return false; } try { if (Input.GetKeyDown(key)) { return true; } } catch { } return GetKeyDownInputSystem(key); } internal static bool GetKey(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) if ((int)key == 0) { return false; } try { if (Input.GetKey(key)) { return true; } } catch { } return GetKeyInputSystem(key, wasPressedOnly: false); } private static bool GetKeyDownInputSystem(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return GetKeyInputSystem(key, wasPressedOnly: true); } private static bool GetKeyInputSystem(KeyCode key, bool wasPressedOnly) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) EnsureResolved(); if (!_hasInputSystem) { return false; } try { object value = _keyboardCurrent.GetValue(null); if (value == null) { return false; } if (!TryMapKeyCode(key, out var inputSystemKey)) { return false; } object obj = null; PropertyInfo property = _keyboardType.GetProperty("Item", new Type[1] { _keyEnum }); if (property != null) { obj = property.GetValue(value, new object[1] { inputSystemKey }); } if (obj == null) { return false; } string name = (wasPressedOnly ? "wasPressedThisFrame" : "isPressed"); PropertyInfo property2 = obj.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public); if (property2 == null) { return false; } object value2 = property2.GetValue(obj); bool flag = default(bool); int num; if (value2 is bool) { flag = (bool)value2; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch (Exception ex) { ShyGuyLog.DebugThrottled("input:system", "Input System key read failed: " + ex.Message, 30f); return false; } } private unsafe static bool TryMapKeyCode(KeyCode key, out object inputSystemKey) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Invalid comparison between Unknown and I4 //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Invalid comparison between Unknown and I4 //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Invalid comparison between Unknown and I4 //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Invalid comparison between Unknown and I4 //IL_0050: 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_00c4: Expected I4, but got Unknown //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Invalid comparison between Unknown and I4 inputSystemKey = null; if (_keyEnum == null) { return false; } string text; if ((int)key <= 256) { if ((int)key != 92) { if ((int)key != 112) { if ((int)key != 256) { goto IL_019b; } text = "Numpad0"; } else { text = "P"; } } else { text = "Backslash"; } } else if ((int)key <= 293) { if ((int)key != 257) { switch (key - 268) { case 14: break; case 15: goto IL_00ec; case 16: goto IL_00f7; case 17: goto IL_0102; case 18: goto IL_010d; case 19: goto IL_0118; case 20: goto IL_0123; case 21: goto IL_012b; case 22: goto IL_0133; case 23: goto IL_013b; case 24: goto IL_0143; case 25: goto IL_014b; case 9: goto IL_0163; case 0: goto IL_017b; case 2: goto IL_0183; default: goto IL_019b; } text = "F1"; } else { text = "Numpad1"; } } else if ((int)key != 303) { if ((int)key != 304) { goto IL_019b; } text = "LeftShift"; } else { text = "RightShift"; } goto IL_01a9; IL_0118: text = "F6"; goto IL_01a9; IL_012b: text = "F8"; goto IL_01a9; IL_0133: text = "F9"; goto IL_01a9; IL_01a9: string value = text; try { inputSystemKey = Enum.Parse(_keyEnum, value, ignoreCase: true); return true; } catch { return false; } IL_019b: text = ((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString(); goto IL_01a9; IL_010d: text = "F5"; goto IL_01a9; IL_013b: text = "F10"; goto IL_01a9; IL_0143: text = "F11"; goto IL_01a9; IL_014b: text = "F12"; goto IL_01a9; IL_017b: text = "NumpadMultiply"; goto IL_01a9; IL_0183: text = "NumpadPlus"; goto IL_01a9; IL_0163: text = "Insert"; goto IL_01a9; IL_0123: text = "F7"; goto IL_01a9; IL_00ec: text = "F2"; goto IL_01a9; IL_00f7: text = "F3"; goto IL_01a9; IL_0102: text = "F4"; goto IL_01a9; } } internal static class ShyGuyJson { internal static Dictionary ParseFlatObject(string json) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrWhiteSpace(json)) { return dictionary; } int i = 0; SkipWhitespace(json, ref i); if (i >= json.Length || json[i] != '{') { ShyGuyLog.Debug("JSON does not start with '{'; ignoring it."); return dictionary; } i++; while (i < json.Length) { SkipWhitespace(json, ref i); if (i >= json.Length || json[i] == '}') { break; } if (json[i] == ',') { i++; continue; } if (json[i] != '"') { ShyGuyLog.Debug("Unexpected character '" + json[i] + "' at index " + i + " while reading a JSON key."); break; } string text = ReadString(json, ref i); if (text == null) { break; } SkipWhitespace(json, ref i); if (i >= json.Length || json[i] != ':') { ShyGuyLog.Debug("Missing ':' after the JSON key '" + text + "'."); break; } i++; SkipWhitespace(json, ref i); if (i >= json.Length) { break; } switch (json[i]) { case '"': break; case '[': case '{': SkipContainer(json, ref i); continue; default: { string text2 = ReadLiteral(json, ref i); if (text2.Length > 0) { dictionary[text] = text2; } continue; } } string text3 = ReadString(json, ref i); if (text3 == null) { break; } dictionary[text] = text3; } return dictionary; } private static void SkipWhitespace(string s, ref int i) { while (i < s.Length && char.IsWhiteSpace(s[i])) { i++; } } private static string ReadString(string s, ref int i) { if (i >= s.Length || s[i] != '"') { return null; } i++; StringBuilder stringBuilder = new StringBuilder(); while (i < s.Length) { char c = s[i]; switch (c) { case '\\': { i++; if (i >= s.Length) { return null; } char c2 = s[i]; switch (c2) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; case 'u': { if (i + 4 < s.Length && ushort.TryParse(s.Substring(i + 1, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)) { stringBuilder.Append((char)result); i += 4; } break; } default: stringBuilder.Append(c2); break; } i++; break; } case '"': i++; return stringBuilder.ToString(); default: stringBuilder.Append(c); i++; break; } } return null; } private static string ReadLiteral(string s, ref int i) { int num = i; while (i < s.Length) { char c = s[i]; if (c == ',' || c == '}' || c == ']' || char.IsWhiteSpace(c)) { break; } i++; } return s.Substring(num, i - num).Trim(); } private static void SkipContainer(string s, ref int i) { char c = s[i]; char c2 = ((c == '{') ? '}' : ']'); int num = 0; while (i < s.Length) { char c3 = s[i]; if (c3 == '"') { ReadString(s, ref i); continue; } if (c3 == c) { num++; } else if (c3 == c2) { num--; if (num == 0) { i++; break; } } i++; } } internal static string GetString(Dictionary map, string key, string fallback) { if (map == null || !map.TryGetValue(key, out var value)) { return fallback; } if (value == null || string.Equals(value, "null", StringComparison.OrdinalIgnoreCase)) { return fallback; } return value; } internal static bool GetBool(Dictionary map, string key, bool fallback) { if (map == null || !map.TryGetValue(key, out var value)) { return fallback; } if (bool.TryParse(value, out var result)) { return result; } if (value == "1") { return true; } if (value == "0") { return false; } ShyGuyLog.Debug("Could not read '" + key + "' as a boolean (value: '" + value + "'); using " + fallback + "."); return fallback; } internal static float GetFloat(Dictionary map, string key, float fallback) { if (map == null || !map.TryGetValue(key, out var value)) { return fallback; } if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return result; } ShyGuyLog.Debug("Could not read '" + key + "' as a number (value: '" + value + "'); using " + fallback + "."); return fallback; } internal static int GetInt(Dictionary map, string key, int fallback) { if (map == null || !map.TryGetValue(key, out var value)) { return fallback; } if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result; } if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2)) { return (int)result2; } ShyGuyLog.Debug("Could not read '" + key + "' as an integer (value: '" + value + "'); using " + fallback + "."); return fallback; } } internal static class ShyGuyLog { private static Instance _logger; private static readonly HashSet OnceKeys = new HashSet(StringComparer.Ordinal); private static readonly Dictionary ThrottleTimes = new Dictionary(StringComparer.Ordinal); private const float DefaultThrottleSeconds = 5f; private static bool DebugEnabled { get { try { return ShyGuyConfig.DebugLogging; } catch { return false; } } } internal static void Attach(Instance logger) { _logger = logger; } internal static void ResetOnceState() { OnceKeys.Clear(); ThrottleTimes.Clear(); } internal static void Msg(string message) { if (_logger != null) { _logger.Msg(message); } else { MelonLogger.Msg("[TheShyGuy] " + message); } } internal static void Warn(string message) { if (_logger != null) { _logger.Warning(message); } else { MelonLogger.Warning("[TheShyGuy] " + message); } } internal static void Error(string message) { if (_logger != null) { _logger.Error(message); } else { MelonLogger.Error("[TheShyGuy] " + message); } } internal static void Error(string message, Exception ex) { string text = message + " :: " + ((ex == null) ? "(no exception)" : ex.ToString()); if (_logger != null) { _logger.Error(text); } else { MelonLogger.Error("[TheShyGuy] " + text); } } internal static void Debug(string message) { if (DebugEnabled) { Msg("[dbg] " + message); } } internal static void WarnOnce(string key, string message) { if (OnceKeys.Add(key)) { Warn(message + " (this warning is only shown once)"); } } internal static void MsgOnce(string key, string message) { if (OnceKeys.Add(key)) { Msg(message); } } internal static void DebugThrottled(string key, string message, float minIntervalSeconds = 5f) { if (DebugEnabled) { float unscaledTime = Time.unscaledTime; if (!ThrottleTimes.TryGetValue(key, out var value) || !(unscaledTime - value < minIntervalSeconds)) { ThrottleTimes[key] = unscaledTime; Msg("[dbg] " + message); } } } internal static void WarnThrottled(string key, string message, float minIntervalSeconds = 30f) { float unscaledTime = Time.unscaledTime; if (!ThrottleTimes.TryGetValue(key, out var value) || !(unscaledTime - value < minIntervalSeconds)) { ThrottleTimes[key] = unscaledTime; Warn(message); } } internal static void Guard(string context, Action action) { if (action == null) { return; } try { action(); } catch (Exception ex) { WarnThrottled("guard:" + context, "Recovered from an error in " + context + ": " + ex.Message); Debug("Full trace for " + context + ": " + ex); } } } internal static class ShyGuyPhysics { private const int BufferSize = 16; private static readonly Il2CppStructArray Buffer = new Il2CppStructArray(16L); internal const int AllLayers = -1; internal static readonly Comparison ByDistance = (RaycastHit a, RaycastHit b) => ((RaycastHit)(ref a)).distance.CompareTo(((RaycastHit)(ref b)).distance); internal static bool Raycast(Vector3 origin, Vector3 direction, float maxDistance, int layerMask, out RaycastHit nearest, Func ignore = null) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) nearest = default(RaycastHit); if (((Vector3)(ref direction)).sqrMagnitude < 1E-06f || maxDistance <= 0f) { return false; } int count; try { count = Physics.RaycastNonAlloc(new Ray(origin, ((Vector3)(ref direction)).normalized), Buffer, maxDistance, layerMask, (QueryTriggerInteraction)1); } catch (Exception ex) { ShyGuyLog.DebugThrottled("physics:ray", "RaycastNonAlloc threw: " + ex.Message, 30f); return false; } return SelectNearest(count, ignore, out nearest); } internal static bool SphereCast(Vector3 origin, float radius, Vector3 direction, float maxDistance, int layerMask, out RaycastHit nearest, Func ignore = null) { //IL_0002: 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_002d: Unknown result type (might be due to invalid IL or missing references) nearest = default(RaycastHit); if (((Vector3)(ref direction)).sqrMagnitude < 1E-06f || maxDistance <= 0f || radius <= 0f) { return false; } int count; try { count = Physics.SphereCastNonAlloc(origin, radius, ((Vector3)(ref direction)).normalized, Buffer, maxDistance, layerMask, (QueryTriggerInteraction)1); } catch (Exception ex) { ShyGuyLog.DebugThrottled("physics:sphere", "SphereCastNonAlloc threw: " + ex.Message, 30f); return false; } return SelectNearest(count, ignore, out nearest); } private static bool SelectNearest(int count, Func ignore, out RaycastHit nearest) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: 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) nearest = default(RaycastHit); if (count <= 0) { return false; } bool result = false; float num = float.MaxValue; int num2 = ((count < 16) ? count : 16); for (int i = 0; i < num2; i++) { RaycastHit val = ((Il2CppArrayBase)(object)Buffer)[i]; Collider collider = ((RaycastHit)(ref val)).collider; if ((Object)(object)collider == (Object)null) { continue; } if (ignore != null) { bool flag; try { flag = ignore(collider); } catch (Exception ex) { ShyGuyLog.DebugThrottled("physics:ignore", "A collider filter threw: " + ex.Message, 30f); flag = false; } if (flag) { continue; } } if (((RaycastHit)(ref val)).distance <= 0f) { Vector3 normal = ((RaycastHit)(ref val)).normal; if (((Vector3)(ref normal)).sqrMagnitude < 1E-06f) { continue; } } if (((RaycastHit)(ref val)).distance < num) { num = ((RaycastHit)(ref val)).distance; nearest = val; result = true; } } return result; } internal static int RaycastAllSorted(Vector3 origin, Vector3 direction, float maxDistance, int layerMask, List results) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) if (results == null) { return 0; } results.Clear(); if (((Vector3)(ref direction)).sqrMagnitude < 1E-06f || maxDistance <= 0f) { return 0; } int num; try { num = Physics.RaycastNonAlloc(new Ray(origin, ((Vector3)(ref direction)).normalized), Buffer, maxDistance, layerMask, (QueryTriggerInteraction)1); } catch (Exception ex) { ShyGuyLog.DebugThrottled("physics:rayall", "RaycastNonAlloc threw: " + ex.Message, 30f); return 0; } int num2 = ((num < 16) ? num : 16); for (int i = 0; i < num2; i++) { results.Add(((Il2CppArrayBase)(object)Buffer)[i]); } results.Sort(ByDistance); return results.Count; } internal static bool CheckSphere(Vector3 position, float radius, int layerMask = -1) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) try { return Physics.CheckSphere(position, radius, layerMask, (QueryTriggerInteraction)1); } catch (Exception ex) { ShyGuyLog.DebugThrottled("physics:checksphere", "CheckSphere threw: " + ex.Message, 30f); return true; } } } internal static class ShyGuyReflect { private const BindingFlags AnyInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private const BindingFlags AnyStatic = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private const BindingFlags Any = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy; private static readonly Dictionary TypeCache = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary MemberCache = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary AssemblyPresence = new Dictionary(StringComparer.OrdinalIgnoreCase); private static Assembly[] _assemblies; private static Assembly[] Assemblies { get { if (_assemblies == null || _assemblies.Length == 0) { try { _assemblies = AppDomain.CurrentDomain.GetAssemblies(); } catch { _assemblies = Array.Empty(); } } return _assemblies; } } internal static void InvalidateCaches() { TypeCache.Clear(); MemberCache.Clear(); AssemblyPresence.Clear(); _assemblies = null; } internal static bool HasAssembly(string simpleName) { if (string.IsNullOrEmpty(simpleName)) { return false; } if (AssemblyPresence.TryGetValue(simpleName, out var value)) { return value; } bool flag = false; try { flag = Assemblies.Any(delegate(Assembly a) { try { return string.Equals(a.GetName().Name, simpleName, StringComparison.OrdinalIgnoreCase); } catch { return false; } }); if (!flag) { _assemblies = null; flag = Assemblies.Any(delegate(Assembly a) { try { return string.Equals(a.GetName().Name, simpleName, StringComparison.OrdinalIgnoreCase); } catch { return false; } }); } } catch (Exception ex) { ShyGuyLog.Debug("HasAssembly(" + simpleName + ") failed: " + ex.Message); } AssemblyPresence[simpleName] = flag; return flag; } internal static Type FindType(string cacheKey, string[] fullNameCandidates, string simpleNameFallback = null) { if (TypeCache.TryGetValue(cacheKey, out var value)) { return value; } Type type = null; if (fullNameCandidates != null) { foreach (string text in fullNameCandidates) { if (string.IsNullOrEmpty(text)) { continue; } Assembly[] assemblies = Assemblies; foreach (Assembly assembly in assemblies) { try { type = assembly.GetType(text, throwOnError: false); if (type != null) { break; } } catch { } } if (type != null) { break; } } } if (type == null && !string.IsNullOrEmpty(simpleNameFallback)) { type = ScanForSimpleName(simpleNameFallback); } TypeCache[cacheKey] = type; if (type == null) { ShyGuyLog.Debug("Reflection: type '" + cacheKey + "' not found in " + Assemblies.Length + " loaded assemblies."); } else { ShyGuyLog.Debug("Reflection: resolved '" + cacheKey + "' -> " + type.FullName); } return type; } private static Type ScanForSimpleName(string simpleName) { List list = new List(); Assembly[] assemblies = Assemblies; foreach (Assembly assembly in assemblies) { Type[] array; try { array = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { array = ex.Types?.Where((Type t) => t != null).ToArray() ?? Array.Empty(); } catch { continue; } Type[] array2 = array; foreach (Type type in array2) { if (type != null && string.Equals(type.Name, simpleName, StringComparison.Ordinal)) { list.Add(type); } } } if (list.Count == 0) { return null; } if (list.Count == 1) { return list[0]; } return (from t in list orderby t.IsNested ? 1 : 0, (t.FullName ?? t.Name).Length select t).First(); } internal static object GetMember(object instance, Type type, string[] names, out bool found) { found = false; Type type2 = type ?? instance?.GetType(); if (type2 == null || names == null) { return null; } foreach (string text in names) { if (string.IsNullOrEmpty(text)) { continue; } string key = type2.FullName + "|m|" + text; if (!MemberCache.TryGetValue(key, out var value)) { value = (MemberInfo)(((object)FindProperty(type2, text)) ?? ((object)FindField(type2, text))); MemberCache[key] = value; } if (value == null) { continue; } try { object? value2; if (!(value is PropertyInfo propertyInfo)) { value2 = ((FieldInfo)value).GetValue(((FieldInfo)value).IsStatic ? null : instance); } else { MethodInfo? getMethod = propertyInfo.GetGetMethod(nonPublic: true); value2 = propertyInfo.GetValue(((object)getMethod != null && getMethod.IsStatic) ? null : instance); } found = true; return value2; } catch (Exception ex) { ShyGuyLog.Debug("Reflection: reading " + type2.Name + "." + text + " threw " + ex.GetBaseException().Message); } } return null; } internal static T GetMemberAs(object instance, Type type, string[] names, T fallback = default(T)) { bool found; object member = GetMember(instance, type, names, out found); if (!found || member == null) { return fallback; } if (member is T) { return (T)member; } try { return (T)Convert.ChangeType(member, typeof(T)); } catch { return fallback; } } internal static object GetStatic(Type type, string[] names, out bool found) { return GetMember(null, type, names, out found); } internal static bool TryInvoke(object instance, Type type, string[] names, object[] args, out object result) { result = null; Type type2 = type ?? instance?.GetType(); if (type2 == null || names == null) { return false; } int argc = ((args != null) ? args.Length : 0); foreach (string text in names) { if (string.IsNullOrEmpty(text)) { continue; } string key = type2.FullName + "|f|" + text + "|" + argc; if (!MemberCache.TryGetValue(key, out var value)) { value = FindMethod(type2, text, argc, args); MemberCache[key] = value; } if (value is MethodInfo methodInfo) { try { result = methodInfo.Invoke(methodInfo.IsStatic ? null : instance, CoerceArgs(methodInfo, args)); return true; } catch (Exception ex) { ShyGuyLog.Debug("Reflection: calling " + type2.Name + "." + text + " threw " + ex.GetBaseException().Message); } } } return false; } private static object[] CoerceArgs(MethodInfo method, object[] args) { ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length == 0) { return Array.Empty(); } if (args == null) { return new object[parameters.Length]; } object[] array = new object[parameters.Length]; for (int i = 0; i < parameters.Length; i++) { object obj = ((i < args.Length) ? args[i] : null); Type parameterType = parameters[i].ParameterType; if (obj == null) { array[i] = (parameterType.IsValueType ? Activator.CreateInstance(parameterType) : null); continue; } if (parameterType.IsInstanceOfType(obj)) { array[i] = obj; continue; } try { array[i] = Convert.ChangeType(obj, parameterType); } catch { array[i] = obj; } } return array; } private static PropertyInfo FindProperty(Type t, string name) { Type type = t; while (type != null && type != typeof(object)) { try { PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); if (property != null && property.CanRead) { return property; } } catch { } type = type.BaseType; } return null; } private static FieldInfo FindField(Type t, string name) { Type type = t; while (type != null && type != typeof(object)) { try { FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); if (field != null) { return field; } } catch { } type = type.BaseType; } return null; } private static MethodInfo FindMethod(Type t, string name, int argc, object[] args) { for (Type type = t; type != null && type != typeof(object); type = type.BaseType) { MethodInfo[] methods; try { methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); } catch { continue; } MethodInfo methodInfo = methods.FirstOrDefault((MethodInfo m) => string.Equals(m.Name, name, StringComparison.Ordinal) && m.GetParameters().Length == argc && !m.IsGenericMethodDefinition && ParametersAcceptable(m, args)); if ((object)methodInfo == null) { methodInfo = methods.FirstOrDefault((MethodInfo m) => string.Equals(m.Name, name, StringComparison.Ordinal) && !m.IsGenericMethodDefinition && m.GetParameters().Length == argc); } if (methodInfo != null) { return methodInfo; } } return null; } private static bool ParametersAcceptable(MethodInfo m, object[] args) { if (args == null) { return m.GetParameters().Length == 0; } ParameterInfo[] parameters = m.GetParameters(); for (int i = 0; i < parameters.Length && i < args.Length; i++) { if (args[i] == null) { if (parameters[i].ParameterType.IsValueType) { return false; } continue; } Type parameterType = parameters[i].ParameterType; if (!parameterType.IsInstanceOfType(args[i]) && (!IsNumeric(parameterType) || !IsNumeric(args[i].GetType()))) { return false; } } return true; } private static bool IsNumeric(Type t) { if (t == null) { return false; } if (!(t == typeof(float)) && !(t == typeof(double)) && !(t == typeof(int)) && !(t == typeof(long)) && !(t == typeof(short)) && !(t == typeof(byte)) && !(t == typeof(uint)) && !(t == typeof(ulong)) && !(t == typeof(ushort)) && !(t == typeof(sbyte))) { return t == typeof(decimal); } return true; } internal static IEnumerable Enumerate(object collection) { if (collection == null) { yield break; } if (collection is IEnumerable enumerable && !(collection is string)) { IEnumerator e; try { e = enumerable.GetEnumerator(); } catch { e = null; } if (e != null) { while (true) { object current; try { if (!e.MoveNext()) { break; } current = e.Current; } catch { break; } if (current != null) { yield return current; } } yield break; } } Type t = collection.GetType(); if (TryInvoke(collection, t, new string[1] { "GetEnumerator" }, null, out var enumerator) && enumerator != null && enumerator != collection) { Type et = enumerator.GetType(); int guard = 0; object result; while (guard++ < 512 && TryInvoke(enumerator, et, new string[1] { "MoveNext" }, null, out result) && result is bool && (bool)result) { bool found; object member = GetMember(enumerator, et, new string[2] { "Current", "get_Current" }, out found); if (found && member != null) { yield return member; } } yield break; } int count = -1; bool found2; object member2 = GetMember(collection, t, new string[4] { "Count", "Length", "count", "_size" }, out found2); if (found2 && member2 != null) { try { count = Convert.ToInt32(member2); } catch { count = -1; } } if (count < 0) { if (!TryInvoke(collection, t, new string[1] { "ToArray" }, null, out var result2) || result2 == null || result2 == collection) { yield break; } foreach (object item in Enumerate(result2)) { yield return item; } yield break; } for (int guard = 0; guard < count && guard < 512; guard++) { object obj4 = null; if (TryInvoke(collection, t, new string[3] { "get_Item", "Item", "ElementAt" }, new object[1] { guard }, out var result3)) { obj4 = result3; } if (obj4 != null) { yield return obj4; } } } } internal enum ShyGuyState { Passive, Triggering, Chasing, Attacking, Resetting, Spawning } internal static class ShyGuyDamage { private static Type _healthType; private static bool _resolvedHealthType; private static readonly string[] CurrentHealthMembers = new string[6] { "curr_Health", "currentHealth", "current_Health", "health", "Health", "life_Health" }; private static readonly string[] DeadFlagMembers = new string[4] { "isDead", "IsDead", "dead", "deathIsImminent" }; private static readonly string[] AliveFlagMembers = new string[3] { "alive", "IsAlive", "isAlive" }; private static readonly string[] KillMethods = new string[6] { "KILLPLAYER", "Kill", "KillPlayer", "Death", "DEATH", "Die" }; private static readonly string[] DamageMethods = new string[5] { "TAKEDAMAGE", "TakeDamage", "Damage", "ApplyDamage", "Hurt" }; internal static void InvalidateTypeCache() { _resolvedHealthType = false; _healthType = null; } private static Type ResolveHealthType() { if (_resolvedHealthType) { return _healthType; } _resolvedHealthType = true; _healthType = ShyGuyReflect.FindType("Bonelab.PlayerHealth", new string[6] { "Il2CppSLZ.Marrow.Player_Health", "Il2CppSLZ.Bonelab.Player_Health", "SLZ.Marrow.Player_Health", "SLZ.Bonelab.Player_Health", "Il2CppSLZ.Marrow.PlayerHealth", "SLZ.Marrow.PlayerHealth" }, "Player_Health"); if (_healthType == null) { _healthType = ShyGuyReflect.FindType("Bonelab.PlayerHealthAlt", null, "PlayerHealth"); } if (_healthType == null) { ShyGuyLog.WarnOnce("damage:notype", "Could not locate BONELAB's player health component by reflection. The creature will still chase players, but cannot kill them, and cannot tell a dead player from a living one. See the troubleshooting section on invalid player rigs."); } else { ShyGuyLog.Debug("Resolved player health type: " + _healthType.FullName); } return _healthType; } private static object GetHealthComponent(ShyGuyTarget target) { if (target == null) { return null; } if (target.HealthComponent != null) { object healthComponent = target.HealthComponent; Component val = (Component)((healthComponent is Component) ? healthComponent : null); if (val == null || !((Object)(object)val == (Object)null)) { return target.HealthComponent; } target.SetHealthComponent(null, null); } if ((Object)(object)target.RigRoot == (Object)null) { return null; } Type type = ResolveHealthType(); if (type == null) { return null; } try { Component componentInChildren = target.RigRoot.GetComponentInChildren(Il2CppType.From(type), true); if ((Object)(object)componentInChildren == (Object)null) { Transform parent = target.RigRoot.transform.parent; if ((Object)(object)parent != (Object)null) { componentInChildren = ((Component)parent).GetComponentInChildren(Il2CppType.From(type), true); } } if ((Object)(object)componentInChildren != (Object)null) { target.SetHealthComponent(componentInChildren, ((object)componentInChildren).GetType()); ShyGuyLog.Debug("Found health component for " + target.DisplayName + " (" + ((object)componentInChildren).GetType().Name + ")."); return componentInChildren; } ShyGuyLog.DebugThrottled("damage:nohealth:" + target.Key, "No health component under the rig of " + target.DisplayName + ".", 30f); } catch (Exception ex) { ShyGuyLog.DebugThrottled("damage:findhealth:" + target.Key, "Health component lookup failed for " + target.DisplayName + ": " + ex.Message, 30f); } return null; } internal static bool IsAlive(ShyGuyTarget target) { if (target == null) { return false; } if ((Object)(object)target.RigRoot == (Object)null) { return false; } object healthComponent = GetHealthComponent(target); if (healthComponent == null) { return true; } Type type = target.HealthComponentType ?? healthComponent.GetType(); bool found; object member = ShyGuyReflect.GetMember(healthComponent, type, DeadFlagMembers, out found); if (found && member is bool flag) { return !flag; } bool found2; object member2 = ShyGuyReflect.GetMember(healthComponent, type, AliveFlagMembers, out found2); if (found2 && member2 is bool) { return (bool)member2; } bool found3; object member3 = ShyGuyReflect.GetMember(healthComponent, type, CurrentHealthMembers, out found3); if (found3 && member3 != null) { try { return Convert.ToSingle(member3) > 0.0001f; } catch { } } ShyGuyLog.DebugThrottled("damage:unreadable:" + target.Key, "Could not read health for " + target.DisplayName + "; assuming alive.", 60f); return true; } internal static bool Attack(ShyGuyTarget target) { if (target == null || !target.IsValid) { return false; } if (!target.IsLocal && !ShyGuyFusionBridge.HasAuthority) { ShyGuyLog.DebugThrottled("damage:noauth", "Skipping damage to remote player " + target.DisplayName + " because this machine is not the Fusion host.", 10f); return false; } object healthComponent = GetHealthComponent(target); if (healthComponent == null) { ShyGuyLog.WarnThrottled("damage:cannotharm", "The creature reached " + target.DisplayName + " but no health component could be found, so no damage was applied."); return false; } Type type = target.HealthComponentType ?? healthComponent.GetType(); object result; if (ShyGuyConfig.InstantKillEnabled) { if (ShyGuyReflect.TryInvoke(healthComponent, type, KillMethods, null, out result)) { ShyGuyLog.Msg("The Shy Guy killed " + target.DisplayName + "."); return true; } if (ShyGuyReflect.TryInvoke(healthComponent, type, DamageMethods, new object[1] { 100000f }, out result)) { ShyGuyLog.Msg("The Shy Guy killed " + target.DisplayName + " (via lethal damage - no kill method available)."); return true; } ShyGuyLog.WarnThrottled("damage:nokill", "Neither a kill method nor a damage method could be called on " + type.Name + ". InstantKillEnabled cannot be honoured on this game version.", 60f); return false; } float damageAmount = ShyGuyConfig.DamageAmount; if (damageAmount <= 0f) { ShyGuyLog.DebugThrottled("damage:zero", "DamageAmount is 0 and InstantKillEnabled is off; attack does nothing.", 30f); return true; } if (ShyGuyReflect.TryInvoke(healthComponent, type, DamageMethods, new object[1] { damageAmount }, out result)) { ShyGuyLog.Debug("Applied " + damageAmount.ToString("F0") + " damage to " + target.DisplayName + "."); return true; } ShyGuyLog.WarnThrottled("damage:nodamage", "No usable damage method found on " + type.Name + "; falling back to an instant kill.", 60f); if (ShyGuyReflect.TryInvoke(healthComponent, type, KillMethods, null, out result)) { ShyGuyLog.Msg("The Shy Guy killed " + target.DisplayName + " (damage method unavailable)."); return true; } return false; } internal static string DescribeState() { Type type = ResolveHealthType(); if (!(type == null)) { return "Player health type: " + type.FullName; } return "Player health type: NOT RESOLVED (players cannot be killed)."; } } internal static class ShyGuyFusionBridge { private const string FusionAssemblyName = "LabFusion"; private static bool _checkedInstall; private static bool _installed; private static Type _networkInfoType; private static Type _playerIdManagerType; private static Type _playerRepManagerType; private static bool _resolvedTypes; private static bool _loggedUnavailableDetail; private static float _lastPeerRefresh; private static readonly HashSet ConnectedIds = new HashSet(); private static bool _peerCacheValid; private const float PeerRefreshInterval = 0.5f; internal static bool IsInstalled { get { if (!ShyGuyConfig.FusionSupportEnabled) { return false; } if (!_checkedInstall) { _checkedInstall = true; _installed = ShyGuyReflect.HasAssembly("LabFusion"); ShyGuyLog.Msg(_installed ? "LabFusion detected - multiplayer support is active." : "LabFusion not present - running in single player mode. This is not an error."); } return _installed; } } internal static bool InSession { get { if (!IsInstalled) { return false; } ResolveTypes(); if (_networkInfoType == null) { return false; } bool found; object obj = ShyGuyReflect.GetStatic(_networkInfoType, new string[4] { "HasServer", "IsServer", "IsClient", "HasClient" }, out found); bool flag = default(bool); int num; if (found) { if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } } internal static bool HasAuthority { get { if (!IsInstalled) { return true; } ResolveTypes(); if (_networkInfoType == null) { return true; } bool found; object obj = ShyGuyReflect.GetStatic(_networkInfoType, new string[3] { "IsServer", "IsHost", "IsOwner" }, out found); if (found && obj is bool) { return (bool)obj; } bool found2; object obj2 = ShyGuyReflect.GetStatic(_networkInfoType, new string[1] { "HasServer" }, out found2); if (found2 && obj2 is bool flag) { return !flag; } return true; } } internal static int LocalSmallId { get { if (!IsInstalled) { return -1; } ResolveTypes(); if (_playerIdManagerType == null) { return -1; } bool found; object obj = ShyGuyReflect.GetStatic(_playerIdManagerType, new string[3] { "LocalSmallId", "LocalId", "LocalPlayerId" }, out found); if (!found || obj == null) { return -1; } return ExtractSmallId(obj); } } internal static void Reset() { _lastPeerRefresh = 0f; _peerCacheValid = false; ConnectedIds.Clear(); } internal static void HardReset() { Reset(); _checkedInstall = false; _resolvedTypes = false; _installed = false; _networkInfoType = null; _playerIdManagerType = null; _playerRepManagerType = null; _loggedUnavailableDetail = false; } private static void ResolveTypes() { if (_resolvedTypes) { return; } _resolvedTypes = true; if (IsInstalled) { _networkInfoType = ShyGuyReflect.FindType("Fusion.NetworkInfo", new string[2] { "LabFusion.Network.NetworkInfo", "LabFusion.Networking.NetworkInfo" }, "NetworkInfo"); _playerIdManagerType = ShyGuyReflect.FindType("Fusion.PlayerIdManager", new string[3] { "LabFusion.Player.PlayerIdManager", "LabFusion.Representation.PlayerIdManager", "LabFusion.Player.LocalPlayer" }, "PlayerIdManager"); _playerRepManagerType = ShyGuyReflect.FindType("Fusion.PlayerRepManager", new string[3] { "LabFusion.Player.PlayerRepManager", "LabFusion.Representation.PlayerRepManager", "LabFusion.Player.PlayerRepUtilities" }, "PlayerRepManager"); if (_networkInfoType == null && !_loggedUnavailableDetail) { _loggedUnavailableDetail = true; ShyGuyLog.Warn("LabFusion is loaded but its NetworkInfo type could not be located. Remote players will still be found by rig scanning, but per-player identity and disconnect cleanup will fall back to heuristics. This usually means a Fusion version newer than this mod - please report it."); } } } internal static int GetSmallIdForRig(GameObject rigRoot) { if (!IsInstalled || (Object)(object)rigRoot == (Object)null) { return -1; } ResolveTypes(); if (_playerRepManagerType == null) { return -1; } try { bool found; object obj = ShyGuyReflect.GetStatic(_playerRepManagerType, new string[4] { "PlayerReps", "Reps", "playerReps", "AllPlayerReps" }, out found); if (!found || obj == null) { return -1; } foreach (object item in ShyGuyReflect.Enumerate(obj)) { object rep = UnwrapKeyValuePair(item); GameObject repRigRoot = GetRepRigRoot(rep); if (!((Object)(object)repRigRoot == (Object)null) && (((Object)repRigRoot).GetInstanceID() == ((Object)rigRoot).GetInstanceID() || ShyGuyHierarchy.IsPartOf(repRigRoot.transform, rigRoot.transform) || ShyGuyHierarchy.IsPartOf(rigRoot.transform, repRigRoot.transform))) { int repSmallId = GetRepSmallId(rep); if (repSmallId >= 0) { return repSmallId; } } } } catch (Exception ex) { ShyGuyLog.DebugThrottled("fusion:smallid", "GetSmallIdForRig failed: " + ex.Message, 30f); } return -1; } internal static string GetDisplayName(int smallId) { if (!IsInstalled || smallId < 0) { return null; } ResolveTypes(); if (_playerRepManagerType == null) { return null; } try { bool found; object obj = ShyGuyReflect.GetStatic(_playerRepManagerType, new string[4] { "PlayerReps", "Reps", "playerReps", "AllPlayerReps" }, out found); if (!found || obj == null) { return null; } foreach (object item in ShyGuyReflect.Enumerate(obj)) { object obj2 = UnwrapKeyValuePair(item); if (GetRepSmallId(obj2) == smallId) { bool found2; object member = ShyGuyReflect.GetMember(obj2, obj2.GetType(), new string[3] { "PlayerId", "PlayerID", "playerId" }, out found2); object obj3 = ((found2 && member != null) ? member : obj2); string memberAs = ShyGuyReflect.GetMemberAs(obj3, obj3.GetType(), new string[5] { "Username", "username", "DisplayName", "Nickname", "Name" }); if (!string.IsNullOrWhiteSpace(memberAs)) { return memberAs; } } } } catch (Exception ex) { ShyGuyLog.DebugThrottled("fusion:name", "GetDisplayName failed: " + ex.Message, 30f); } return null; } internal static bool IsStillConnected(int smallId) { if (!IsInstalled || smallId < 0) { return true; } if (!InSession) { return true; } RefreshPeerCache(); if (!_peerCacheValid) { return true; } return ConnectedIds.Contains(smallId); } private static void RefreshPeerCache() { float unscaledTime = Time.unscaledTime; if (_peerCacheValid && unscaledTime - _lastPeerRefresh < 0.5f) { return; } _lastPeerRefresh = unscaledTime; ResolveTypes(); if (_playerRepManagerType == null && _playerIdManagerType == null) { _peerCacheValid = false; return; } HashSet hashSet = new HashSet(); bool flag = false; if (_playerIdManagerType != null) { bool found; object obj = ShyGuyReflect.GetStatic(_playerIdManagerType, new string[3] { "PlayerIds", "playerIds", "AllPlayerIds" }, out found); if (found && obj != null) { foreach (object item in ShyGuyReflect.Enumerate(obj)) { int num = ExtractSmallId(UnwrapKeyValuePair(item)); if (num >= 0) { hashSet.Add(num); flag = true; } } } } if (!flag && _playerRepManagerType != null) { bool found2; object obj2 = ShyGuyReflect.GetStatic(_playerRepManagerType, new string[4] { "PlayerReps", "Reps", "playerReps", "AllPlayerReps" }, out found2); if (found2 && obj2 != null) { foreach (object item2 in ShyGuyReflect.Enumerate(obj2)) { int repSmallId = GetRepSmallId(UnwrapKeyValuePair(item2)); if (repSmallId >= 0) { hashSet.Add(repSmallId); flag = true; } } } } int localSmallId = LocalSmallId; if (localSmallId >= 0) { hashSet.Add(localSmallId); flag = true; } if (flag) { ConnectedIds.Clear(); foreach (int item3 in hashSet) { ConnectedIds.Add(item3); } _peerCacheValid = true; } else { _peerCacheValid = false; ShyGuyLog.DebugThrottled("fusion:peers", "Could not read the Fusion peer list; disconnect cleanup is relying on rig destruction instead.", 60f); } } private static object UnwrapKeyValuePair(object candidate) { if (candidate == null) { return null; } Type type = candidate.GetType(); if (!type.IsGenericType) { return candidate; } if (!type.Name.StartsWith("KeyValuePair", StringComparison.Ordinal)) { return candidate; } bool found; object member = ShyGuyReflect.GetMember(candidate, type, new string[1] { "Value" }, out found); if (!found || member == null) { return candidate; } return member; } private static int ExtractSmallId(object idOrRep) { if (idOrRep == null) { return -1; } if (idOrRep is byte) { return (byte)idOrRep; } if (idOrRep is int) { return (int)idOrRep; } Type type = idOrRep.GetType(); bool found; object member = ShyGuyReflect.GetMember(idOrRep, type, new string[4] { "SmallId", "smallId", "PlayerId", "Id" }, out found); if (found && member != null) { if (member is byte) { return (byte)member; } if (member is int) { return (int)member; } if (!member.GetType().IsPrimitive) { bool found2; object member2 = ShyGuyReflect.GetMember(member, member.GetType(), new string[3] { "SmallId", "smallId", "Id" }, out found2); if (found2 && member2 != null) { try { return Convert.ToInt32(member2); } catch { } } } try { return Convert.ToInt32(member); } catch { return -1; } } return -1; } private static int GetRepSmallId(object rep) { return ExtractSmallId(rep); } private static GameObject GetRepRigRoot(object rep) { if (rep == null) { return null; } Type type = rep.GetType(); bool found; GameObject val = AsGameObject(ShyGuyReflect.GetMember(rep, type, new string[2] { "RigManager", "rigManager" }, out found)); if (found && (Object)(object)val != (Object)null) { return val; } bool found2; object member = ShyGuyReflect.GetMember(rep, type, new string[4] { "RigReferences", "RigRefs", "rigRefs", "References" }, out found2); if (found2 && member != null) { bool found3; GameObject val2 = AsGameObject(ShyGuyReflect.GetMember(member, member.GetType(), new string[3] { "RigManager", "rigManager", "Rig" }, out found3)); if (found3 && (Object)(object)val2 != (Object)null) { return val2; } } return null; } private static GameObject AsGameObject(object value) { if (value != null) { GameObject val = (GameObject)((value is GameObject) ? value : null); if (val == null) { Component val2 = (Component)((value is Component) ? value : null); if (val2 != null) { if (!((Object)(object)val2 != (Object)null)) { return null; } return val2.gameObject; } return null; } if (!((Object)(object)val != (Object)null)) { return null; } return val; } return null; } internal static string DescribeState() { if (!ShyGuyConfig.FusionSupportEnabled) { return "Fusion support disabled by preference."; } if (!IsInstalled) { return "LabFusion not installed."; } ResolveTypes(); RefreshPeerCache(); return "LabFusion installed. InSession=" + InSession + ", HasAuthority=" + HasAuthority + ", LocalSmallId=" + LocalSmallId + ", knownPeers=" + (_peerCacheValid ? ConnectedIds.Count.ToString() : "unknown") + ", types[NetworkInfo=" + (_networkInfoType != null) + ", PlayerIdManager=" + (_playerIdManagerType != null) + ", PlayerRepManager=" + (_playerRepManagerType != null) + "]"; } } internal sealed class ShyGuyNavigation { private readonly Transform _root; private readonly NavMeshAgent _agent; private readonly float _height; private bool _navMeshUsable; private bool _modeDecided; private float _lastModeEvaluation = -999f; private const float ModeRecheckInterval = 2f; private bool _anyNavMeshCached; private float _anyNavMeshCachedAt = -999f; private const float TriangulationCacheSeconds = 10f; private float _lastRepath; private float _currentSpeed; private float _verticalVelocity; private Vector3 _stuckAnchor; private float _stuckTimer; private int _stuckStage; private float _accelBoostUntil; private Vector3 _sidestepDirection; private float _sidestepUntil; private float _lastTeleport = -999f; private const float TeleportCooldown = 1.5f; private Vector3 _homePosition; private Vector3 _wanderTarget; private float _nextWanderTime; private bool _hasWanderTarget; private Vector3 _detourWaypoint; private bool _hasDetour; private float _detourUntil; private int _detourSide = 1; private readonly NavMeshPath _path = new NavMeshPath(); private const float PathEndTolerance = 2f; private const int SweepBufferSize = 8; private readonly Il2CppStructArray _sweepHits = new Il2CppStructArray(8L); internal bool PathFailed { get; private set; } internal bool IsRecovering => _stuckStage > 0; internal int StuckStage => _stuckStage; internal bool UsingNavMesh => _navMeshUsable; internal Collider BlockingCollider { get; private set; } internal event Action DoorBlocked; internal ShyGuyNavigation(Transform root, NavMeshAgent agent, float height) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008b: 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) _root = root; _agent = agent; _height = Mathf.Max(0.5f, height); if ((Object)(object)_root != (Object)null) { _homePosition = _root.position; _stuckAnchor = _root.position; } } internal void Dispose() { this.DoorBlocked = null; Stop(); } internal void SetHome(Vector3 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) _homePosition = position; _stuckAnchor = position; _hasWanderTarget = false; } internal void EvaluateNavMeshAvailability() { float unscaledTime = Time.unscaledTime; if (_modeDecided && unscaledTime - _lastModeEvaluation < 2f) { return; } _lastModeEvaluation = unscaledTime; bool flag = (Object)(object)_agent != (Object)null; bool flag2 = flag && LevelHasNavMesh(); bool flag3 = flag && flag2; if (_modeDecided && flag3 == _navMeshUsable) { return; } _navMeshUsable = flag3; _modeDecided = true; if (flag3) { ConfigureAgent(); ShyGuyLog.Msg("Navigation: using NavMeshAgent (a baked NavMesh was found in this level)."); return; } if (flag) { try { ((Behaviour)_agent).enabled = false; } catch (Exception ex) { ShyGuyLog.Debug("Disabling the NavMeshAgent threw: " + ex.Message); } } if (!flag) { ShyGuyLog.WarnOnce("nav:noagent", "No NavMeshAgent was found on the prefab. Falling back to direct movement. Add a NavMeshAgent to the prefab root if you want NavMesh pathfinding where a NavMesh exists."); } else { ShyGuyLog.MsgOnce("nav:nonavmesh", "Navigation: this level has no baked Unity NavMesh, so the creature is using direct movement. This is expected in stock BONELAB levels and is not an error - see 'Known limitations'. " + (ShyGuyConfig.DirectMovementFallback ? "DirectMovementFallback is on, so it will still chase." : "DirectMovementFallback is OFF, so it will not be able to move. Turn it on in the config.")); } } private bool LevelHasNavMesh() { //IL_0061: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_agent == (Object)null || (Object)(object)_root == (Object)null) { return false; } try { if (!((Behaviour)_agent).enabled) { ((Behaviour)_agent).enabled = true; } if (_agent.isOnNavMesh) { return true; } if (!LevelContainsAnyNavMesh()) { return false; } if (_agent.Warp(_root.position) && _agent.isOnNavMesh) { ShyGuyLog.Debug("Placed the agent onto the level's NavMesh."); return true; } ShyGuyLog.Debug("A NavMesh exists in this level but does not cover the creature's position."); return false; } catch (Exception ex) { ShyGuyLog.WarnOnce("nav:sampfail", "Testing for a baked NavMesh failed (" + ex.Message + "); assuming there is none and using direct movement."); return false; } } private bool LevelContainsAnyNavMesh() { float unscaledTime = Time.unscaledTime; if (unscaledTime - _anyNavMeshCachedAt < 10f) { return _anyNavMeshCached; } _anyNavMeshCachedAt = unscaledTime; try { Il2CppStructArray vertices = NavMesh.CalculateTriangulation().vertices; _anyNavMeshCached = vertices != null && ((Il2CppArrayBase)(object)vertices).Length > 0; } catch (Exception ex) { ShyGuyLog.Debug("CalculateTriangulation threw: " + ex.Message); _anyNavMeshCached = false; } return _anyNavMeshCached; } private int PathCornerCount() { try { NavMeshPath path = _path; return ((Il2CppArrayBase)(object)((path != null) ? path.corners : null))?.Length ?? 0; } catch (Exception ex) { ShyGuyLog.DebugThrottled("nav:corners", "Reading path corners threw: " + ex.Message, 30f); return 0; } } private bool PathReaches(Vector3 destination) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0055: 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) try { NavMeshPath path = _path; Il2CppStructArray val = ((path != null) ? path.corners : null); if (val == null || ((Il2CppArrayBase)(object)val).Length == 0) { return false; } Vector3 val2 = ((Il2CppArrayBase)(object)val)[((Il2CppArrayBase)(object)val).Length - 1]; val2.y = 0f; Vector3 val3 = default(Vector3); ((Vector3)(ref val3))..ctor(destination.x, 0f, destination.z); return Vector3.Distance(val2, val3) <= 2f; } catch (Exception ex) { ShyGuyLog.DebugThrottled("nav:reaches", "Checking the path endpoint threw: " + ex.Message, 30f); return false; } } private void ConfigureAgent() { if ((Object)(object)_agent == (Object)null) { return; } try { ((Behaviour)_agent).enabled = true; _agent.speed = ShyGuyConfig.ChaseSpeed; _agent.acceleration = ShyGuyConfig.ChaseAcceleration; _agent.angularSpeed = ShyGuyConfig.TurningSpeed; _agent.stoppingDistance = ShyGuyConfig.StoppingDistance; _agent.radius = ShyGuyConfig.ObstacleAvoidanceRadius; _agent.height = _height; _agent.autoBraking = false; _agent.autoRepath = true; _agent.updateRotation = false; _agent.obstacleAvoidanceType = (ObstacleAvoidanceType)ShyGuyConfig.ObstacleAvoidanceQuality; _agent.avoidancePriority = 30; } catch (Exception ex) { ShyGuyLog.Warn("Could not fully configure the NavMeshAgent: " + ex.Message + ". Falling back to direct movement."); _navMeshUsable = false; } } internal void Freeze() { //IL_0046: Unknown result type (might be due to invalid IL or missing references) _currentSpeed = 0f; ResetStuckTracking(); if (!((Object)(object)_agent != (Object)null) || !_navMeshUsable) { return; } try { if (_agent.isOnNavMesh) { _agent.isStopped = true; _agent.velocity = Vector3.zero; _agent.ResetPath(); } } catch (Exception ex) { ShyGuyLog.Debug("Freeze on the agent threw: " + ex.Message); } } internal void ResumeChase() { //IL_0071: Unknown result type (might be due to invalid IL or missing references) _currentSpeed = 0f; _lastRepath = -999f; PathFailed = false; ResetStuckTracking(); if (!((Object)(object)_agent != (Object)null)) { return; } try { if (!((Behaviour)_agent).enabled) { ((Behaviour)_agent).enabled = true; } if (_navMeshUsable && _agent.isOnNavMesh) { _agent.isStopped = false; _agent.velocity = Vector3.zero; _agent.ResetPath(); } } catch (Exception ex) { ShyGuyLog.Debug("ResumeChase on the agent threw: " + ex.Message); } } internal void Stop() { //IL_0047: Unknown result type (might be due to invalid IL or missing references) _currentSpeed = 0f; _hasDetour = false; if (!((Object)(object)_agent != (Object)null) || !_navMeshUsable) { return; } try { if (_agent.isOnNavMesh) { _agent.isStopped = true; _agent.velocity = Vector3.zero; } } catch (Exception ex) { ShyGuyLog.Debug("Stop on the agent threw: " + ex.Message); } } internal float Chase(Vector3 destination, float deltaTime) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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_004c: 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_0092: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return float.MaxValue; } EvaluateNavMeshAvailability(); float result = Vector3.Distance(_root.position, destination); bool flag = false; if (_navMeshUsable) { if (ChaseWithAgent(destination, deltaTime)) { flag = true; } else if (ShyGuyConfig.DirectMovementFallback) { MoveDirect(destination, ShyGuyConfig.ChaseSpeed, deltaTime, chaseHard: true); flag = true; } } else { if (!ShyGuyConfig.DirectMovementFallback) { ShyGuyLog.DebugThrottled("nav:cannotmove", "No NavMesh and DirectMovementFallback is off, so the creature cannot move.", 10f); return result; } MoveDirect(destination, ShyGuyConfig.ChaseSpeed, deltaTime, chaseHard: true); flag = true; } if (flag) { TrackStuck(destination, deltaTime); } return Vector3.Distance(_root.position, destination); } internal bool TickPassive(float deltaTime) { //IL_0198: 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_0063: 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_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: 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_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) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return false; } if (ShyGuyConfig.PassiveBehaviour != PassiveMode.Wander) { Stop(); _hasWanderTarget = false; return false; } EvaluateNavMeshAvailability(); float time = Time.time; if (!_hasWanderTarget || time >= _nextWanderTime || Vector3.Distance(_root.position, _wanderTarget) < 0.75f) { _wanderTarget = PickWanderPoint(); _hasWanderTarget = true; _nextWanderTime = time + ShyGuyConfig.PassiveWanderInterval; } float passiveWanderSpeed = ShyGuyConfig.PassiveWanderSpeed; Vector3 direction = _wanderTarget - _root.position; direction.y = 0f; RotateTowardsSmooth(direction, deltaTime, wander: true); if (_navMeshUsable) { try { _agent.speed = passiveWanderSpeed; _agent.acceleration = Mathf.Max(2f, passiveWanderSpeed * 4f); _agent.angularSpeed = Mathf.Max(90f, ShyGuyConfig.TurningSpeed * 0.35f); _agent.isStopped = false; _agent.SetDestination(_wanderTarget); Vector3 desiredVelocity = _agent.desiredVelocity; if (((Vector3)(ref desiredVelocity)).sqrMagnitude > 0.05f) { RotateTowardsSmooth(_agent.desiredVelocity, deltaTime, wander: true); } } catch (Exception ex) { ShyGuyLog.DebugThrottled("nav:wander", "Passive wander via the agent failed: " + ex.Message, 30f); } } else { if (!ShyGuyConfig.DirectMovementFallback) { return false; } MoveDirect(_wanderTarget, passiveWanderSpeed, deltaTime, chaseHard: false, smoothTurn: true); } return Vector3.Distance(_root.position, _wanderTarget) > 0.75f; } private Vector3 PickWanderPoint() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_00d4: 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_0050: 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_006b: 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_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: 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_00c6: Unknown result type (might be due to invalid IL or missing references) float passiveWanderRadius = ShyGuyConfig.PassiveWanderRadius; for (int i = 0; i < 8; i++) { Vector2 val = Random.insideUnitCircle * passiveWanderRadius; Vector3 val2 = _homePosition + new Vector3(val.x, 0f, val.y); if (_navMeshUsable) { return val2; } Vector3 origin = _root.position + Vector3.up * (_height * 0.5f); Vector3 val3 = val2 - _root.position; val3.y = 0f; float magnitude = ((Vector3)(ref val3)).magnitude; if (!(magnitude < 0.5f) && !ShyGuyPhysics.SphereCast(origin, ShyGuyConfig.ObstacleAvoidanceRadius, val3 / magnitude, magnitude, -1, out var _, IsOwnCollider)) { return val2; } } return _homePosition; } private bool ChaseWithAgent(Vector3 destination, float deltaTime) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_0308: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Invalid comparison between Unknown and I4 //IL_01b6: Unknown result type (might be due to invalid IL or missing references) try { if (!_agent.isOnNavMesh) { if (!_agent.Warp(_root.position) || !_agent.isOnNavMesh) { ShyGuyLog.DebugThrottled("nav:offmesh", "The agent is off the NavMesh and no nearby mesh position exists; using direct movement this frame.", 10f); PathFailed = true; return false; } ShyGuyLog.DebugThrottled("nav:warpback", "Agent was off the NavMesh; warped it back on."); } Vector3 desiredVelocity = _agent.desiredVelocity; desiredVelocity.y = 0f; if (((Vector3)(ref desiredVelocity)).sqrMagnitude > 0.01f) { RotateTowardsSmooth(desiredVelocity, deltaTime, wander: false); float num = Mathf.Clamp01(Vector3.Dot(_root.forward, ((Vector3)(ref desiredVelocity)).normalized)); float num2 = ((num < 0.45f) ? 0.12f : ((num < 0.8f) ? (0.3f + num * 0.7f) : 1f)); _agent.speed = ShyGuyConfig.ChaseSpeed * num2; } else { _agent.speed = ShyGuyConfig.ChaseSpeed; } _agent.acceleration = ShyGuyConfig.ChaseAcceleration * CurrentAccelerationMultiplier(); _agent.angularSpeed = 0f; _agent.stoppingDistance = Mathf.Min(0.45f, ShyGuyConfig.StoppingDistance); _agent.isStopped = false; _agent.autoRepath = true; _agent.updateRotation = false; float time = Time.time; bool num3 = _stuckStage >= 1 || PathFailed; float num4 = Mathf.Min(0.08f, ShyGuyConfig.NavMeshRepathInterval); if (num3 || time - _lastRepath >= num4) { _lastRepath = time; try { _agent.SetDestination(destination); } catch (Exception ex) { ShyGuyLog.DebugThrottled("nav:setdest", "SetDestination threw: " + ex.Message, 10f); } bool flag = _agent.CalculatePath(destination, _path); int num5 = PathCornerCount(); if (!flag || num5 <= 0 || !PathReaches(destination)) { if (flag && num5 > 0) { _agent.SetPath(_path); PathFailed = true; ShyGuyLog.DebugThrottled("nav:pathpartial", "Only a partial NavMesh path to the target (" + num5 + " corner(s), ending outside the " + 2f + "m tolerance). Following it as far as it goes."); return true; } PathFailed = true; ShyGuyLog.DebugThrottled("nav:pathfail", "No NavMesh path to the target (CalculatePath " + (flag ? "succeeded but produced no corners" : "failed") + "). " + (ShyGuyConfig.DirectMovementFallback ? "Steering directly instead." : "DirectMovementFallback is off, so the creature will wait.")); return false; } _agent.SetPath(_path); PathFailed = false; } if (_agent.pathPending) { return true; } if ((int)_agent.pathStatus == 2) { PathFailed = true; ShyGuyLog.DebugThrottled("nav:invalid", "The agent's current path is invalid."); return false; } return true; } catch (Exception ex2) { ShyGuyLog.WarnThrottled("nav:agentthrow", "The NavMeshAgent threw during a chase update (" + ex2.Message + "); switching to direct movement."); _navMeshUsable = false; return false; } } private void MoveDirect(Vector3 destination, float targetSpeed, float deltaTime, bool chaseHard, bool smoothTurn = false) { //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_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0091: 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_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: 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_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: 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_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root == (Object)null) && !(deltaTime <= 0f)) { Vector3 position = _root.position; Vector3 val = ResolveDetourTarget(position, destination) - position; val.y = 0f; float num = Vector3.Distance(new Vector3(position.x, 0f, position.z), new Vector3(destination.x, 0f, destination.z)); Vector3 val2 = ((((Vector3)(ref val)).sqrMagnitude > 0.0001f) ? ((Vector3)(ref val)).normalized : _root.forward); if (Time.time < _sidestepUntil && ((Vector3)(ref _sidestepDirection)).sqrMagnitude > 0.001f) { Vector3 val3 = Vector3.Slerp(val2, _sidestepDirection, 0.55f); val2 = ((Vector3)(ref val3)).normalized; } else { val2 = AvoidObstacles(position, val2); } if (smoothTurn) { RotateTowardsSmooth(val2, deltaTime, wander: true); } else { RotateTowardsSmooth(val2, deltaTime, wander: false); } Vector3 val4 = _root.forward; val4.y = 0f; if (((Vector3)(ref val4)).sqrMagnitude < 0.0001f) { val4 = val2; } else { ((Vector3)(ref val4)).Normalize(); } float num2 = Mathf.Clamp01(Vector3.Dot(val4, val2)); float num3 = ShyGuyConfig.ChaseAcceleration * CurrentAccelerationMultiplier(); float num4 = (chaseHard ? 0.35f : Mathf.Max(0.35f, ShyGuyConfig.StoppingDistance)); float num5 = ((num <= num4) ? 0f : targetSpeed); if (num2 < 0.45f) { num5 *= 0.08f; } else if (num2 < 0.8f) { num5 *= 0.25f + num2 * 0.75f; } if ((Object)(object)BlockingCollider != (Object)null) { num5 *= 0.4f; } _currentSpeed = Mathf.MoveTowards(_currentSpeed, num5, num3 * deltaTime); Vector3 val5 = val4 * (_currentSpeed * deltaTime); val5.y = ResolveVertical(position, val5, deltaTime); _root.position = position + val5; } } private Vector3 ResolveDetourTarget(Vector3 position, Vector3 destination) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_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) //IL_0084: 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_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: 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_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013a: 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_0141: 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_014b: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0158: 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_0164: 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_0189: 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) if (HasClearGroundPath(position, destination)) { _hasDetour = false; return destination; } if (_hasDetour && Time.time < _detourUntil) { if (HorizontalDistance(position, _detourWaypoint) > 0.6f && HasClearGroundPath(position, _detourWaypoint)) { return _detourWaypoint; } _detourSide = -_detourSide; } Vector3 val = destination - position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { return destination; } Vector3 normalized = ((Vector3)(ref val)).normalized; Vector3 val2 = Vector3.Cross(Vector3.up, normalized); Vector3 normalized2 = ((Vector3)(ref val2)).normalized; float[] array = new float[3] { 1.5f, 2.75f, 4.25f }; float[] array2 = new float[3] { 55f, 90f, 125f }; Vector3 val3 = destination; float num = float.MinValue; for (int i = 0; i < 2; i++) { int num2 = ((i == 0) ? _detourSide : (-_detourSide)); for (int j = 0; j < array2.Length; j++) { for (int k = 0; k < array.Length; k++) { float num3 = array2[j] * ((float)Math.PI / 180f); val2 = normalized * Mathf.Cos(num3) + normalized2 * (float)num2 * Mathf.Sin(num3); Vector3 normalized3 = ((Vector3)(ref val2)).normalized; Vector3 val4 = position + normalized3 * array[k]; if (HasClearGroundPath(position, val4)) { float num4 = 0f - HorizontalDistance(val4, destination); float num5 = (HasClearGroundPath(val4, destination) ? 8f : 0f) + num4; if (num5 > num) { num = num5; val3 = val4; } } } } } if (num > float.MinValue) { _detourWaypoint = val3; _hasDetour = true; _detourUntil = Time.time + 1.4f; return val3; } return destination; } private bool HasClearGroundPath(Vector3 from, Vector3 to) { //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_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_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_004c: 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_005f: 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_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) Vector3 val = to - from; val.y = 0f; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude < 0.15f) { return true; } if (!ShyGuyPhysics.SphereCast(from + Vector3.up * Mathf.Max(0.4f, _height * 0.35f), direction: val / magnitude, radius: ShyGuyConfig.ObstacleAvoidanceRadius * 0.75f, maxDistance: magnitude, layerMask: -1, nearest: out var nearest, ignore: (Collider c) => (Object)(object)c != (Object)null && (ShyGuyHierarchy.IsPartOf(_root, ((Component)c).transform) || IsPlayerCollider(c)))) { return true; } if (((RaycastHit)(ref nearest)).normal.y > 0.45f) { return true; } float num = ((RaycastHit)(ref nearest)).point.y - from.y; if (num > -0.05f && num <= ShyGuyConfig.DirectFallbackStepHeight) { return true; } return false; } private static float HorizontalDistance(Vector3 a, Vector3 b) { //IL_0018: 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) a.y = 0f; b.y = 0f; return Vector3.Distance(a, b); } private Vector3 AvoidObstacles(Vector3 position, Vector3 desired) { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_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_003d: 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_022f: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01db: 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_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_0103: 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_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0186: 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_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) BlockingCollider = null; float obstacleAvoidanceRadius = ShyGuyConfig.ObstacleAvoidanceRadius; if (ShyGuyConfig.ObstacleAvoidanceQuality <= 0) { return desired; } Vector3 val = position + Vector3.up * Mathf.Max(0.35f, _height * 0.35f); float num = Mathf.Max(0.85f, _currentSpeed * 0.25f + obstacleAvoidanceRadius * 1.6f); int num2; try { num2 = Physics.SphereCastNonAlloc(val, obstacleAvoidanceRadius * 0.7f, desired, _sweepHits, num, -1, (QueryTriggerInteraction)1); } catch (Exception ex) { ShyGuyLog.DebugThrottled("nav:sweep", "Obstacle sweep failed: " + ex.Message, 30f); return desired; } if (num2 <= 0) { return desired; } Vector3 val2 = Vector3.zero; int num3 = 0; for (int i = 0; i < num2 && i < 8; i++) { RaycastHit val3 = ((Il2CppArrayBase)(object)_sweepHits)[i]; Collider collider = ((RaycastHit)(ref val3)).collider; if ((Object)(object)collider == (Object)null || ShyGuyHierarchy.IsPartOf(_root, ((Component)collider).transform) || IsPlayerCollider(collider) || ((RaycastHit)(ref val3)).normal.y > 0.45f) { continue; } float directFallbackStepHeight = ShyGuyConfig.DirectFallbackStepHeight; float num4 = ((RaycastHit)(ref val3)).point.y - position.y; if (num4 > -0.05f && num4 <= directFallbackStepHeight && ((RaycastHit)(ref val3)).distance < obstacleAvoidanceRadius * 2.5f) { continue; } BlockingCollider = collider; if (ShyGuyConfig.BreakDoors && LooksLikeADoor(collider)) { this.DoorBlocked?.Invoke(collider); continue; } Vector3 normal = ((RaycastHit)(ref val3)).normal; normal.y = 0f; if (!(((Vector3)(ref normal)).sqrMagnitude < 0.0001f)) { val2 += ((Vector3)(ref normal)).normalized; num3++; } } if (num3 == 0) { return desired; } val2 /= (float)num3; Vector3 val4 = desired * 0.35f + ((Vector3)(ref val2)).normalized * 0.65f; val4.y = 0f; if (((Vector3)(ref val4)).sqrMagnitude < 0.0001f) { val4 = Vector3.Cross(Vector3.up, desired); } return ((Vector3)(ref val4)).normalized; } private float ResolveVertical(Vector3 position, Vector3 horizontalMotion, float deltaTime) { //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_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: 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_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_00a1: 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_00ff: 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_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_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_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_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) float directFallbackGravity = ShyGuyConfig.DirectFallbackGravity; float directFallbackStepHeight = ShyGuyConfig.DirectFallbackStepHeight; Vector3 val = horizontalMotion; if (((Vector3)(ref val)).sqrMagnitude < 0.0001f && (Object)(object)_root != (Object)null) { Vector3 forward = _root.forward; forward.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude > 0.0001f) { val = ((Vector3)(ref forward)).normalized * 0.25f; } } Vector3 probeOrigin = position + val + Vector3.up * (directFallbackStepHeight + 0.15f); float probeLength = directFallbackStepHeight + 0.35f + Mathf.Max(0.5f, directFallbackGravity * deltaTime * deltaTime + 1f); if (!TryGround(probeOrigin, probeLength, out var ground) && !TryGround(position + Vector3.up * (directFallbackStepHeight + 0.15f), probeLength, out ground)) { _verticalVelocity = Mathf.Max(-40f, _verticalVelocity - directFallbackGravity * deltaTime); return _verticalVelocity * deltaTime; } float num = ((RaycastHit)(ref ground)).point.y - position.y; if (num > 0f && num <= directFallbackStepHeight) { _verticalVelocity = 0f; return num; } if (num > directFallbackStepHeight && TryGround(position + Vector3.up * 0.3f, 1.2f, out var ground2)) { float result = ((RaycastHit)(ref ground2)).point.y - position.y; _verticalVelocity = 0f; return result; } if (num <= 0f) { _verticalVelocity -= directFallbackGravity * deltaTime; float num2 = _verticalVelocity * deltaTime; if (num2 < num) { _verticalVelocity = 0f; return num; } return num2; } _verticalVelocity = Mathf.Max(-40f, _verticalVelocity - directFallbackGravity * deltaTime); return _verticalVelocity * deltaTime; } private bool TryGround(Vector3 probeOrigin, float probeLength, out RaycastHit ground) { //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) return ShyGuyPhysics.Raycast(probeOrigin, Vector3.down, probeLength, -1, out ground, IsOwnCollider); } private static bool IsPlayerCollider(Collider collider) { if ((Object)(object)collider == (Object)null) { return false; } ShyGuyPlayerFinder playerFinder = TheShyGuyMod.PlayerFinder; if (playerFinder == null) { return false; } List livingPlayers = playerFinder.GetLivingPlayers(); for (int i = 0; i < livingPlayers.Count; i++) { GameObject val = livingPlayers[i]?.RigRoot; if (!((Object)(object)val == (Object)null) && ShyGuyHierarchy.IsPartOf(val.transform, ((Component)collider).transform)) { return true; } } return false; } private static bool LooksLikeADoor(Collider collider) { if ((Object)(object)collider == (Object)null) { return false; } string name = ((Object)collider).name; if (string.IsNullOrEmpty(name)) { return false; } if (name.IndexOf("door", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("gate", StringComparison.OrdinalIgnoreCase) < 0) { return name.IndexOf("hatch", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } internal void RotateTowards(Vector3 direction, float deltaTime) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) RotateTowardsSmooth(direction, deltaTime, wander: false); } internal void RotateTowardsSmooth(Vector3 direction, float deltaTime, bool wander) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_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_003d: 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_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return; } Vector3 val = direction; val.y = 0f; if (!(((Vector3)(ref val)).sqrMagnitude < 0.01f)) { Quaternion val2 = Quaternion.LookRotation(((Vector3)(ref val)).normalized, Vector3.up); if (wander) { float num = 1f - Mathf.Exp(-3.2f * Mathf.Max(0f, deltaTime)); _root.rotation = Quaternion.Slerp(_root.rotation, val2, num); } else { _root.rotation = Quaternion.RotateTowards(_root.rotation, val2, ShyGuyConfig.TurningSpeed * deltaTime); } } } internal void SnapLookAt(Vector3 worldPosition) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root == (Object)null)) { Vector3 val = worldPosition - _root.position; val.y = 0f; if (!(((Vector3)(ref val)).sqrMagnitude < 0.01f)) { _root.rotation = Quaternion.LookRotation(((Vector3)(ref val)).normalized, Vector3.up); } } } private float CurrentAccelerationMultiplier() { if (!(Time.time < _accelBoostUntil)) { return 1f; } return ShyGuyConfig.StuckAccelerationBoost; } internal void ResetStuckTracking() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) _stuckTimer = 0f; _stuckStage = 0; _sidestepUntil = 0f; _accelBoostUntil = 0f; _hasDetour = false; if ((Object)(object)_root != (Object)null) { _stuckAnchor = _root.position; } } private void TrackStuck(Vector3 destination, float deltaTime) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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_0036: 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_004a: 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_00b2: 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_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return; } Vector3 position = _root.position; if (Vector3.Distance(position, destination) <= ShyGuyConfig.AttackDistance + 0.2f) { ResetStuckTracking(); return; } if (Vector3.Distance(position, _stuckAnchor) >= ShyGuyConfig.StuckMoveThreshold) { _stuckAnchor = position; _stuckTimer = 0f; if (_stuckStage != 0) { ShyGuyLog.Debug("Stuck recovery succeeded at stage " + _stuckStage + "; resuming normal chase."); _stuckStage = 0; } return; } _stuckTimer += deltaTime; if (!(_stuckTimer < ShyGuyConfig.StuckDetectionTimeout)) { _stuckTimer = 0f; _stuckAnchor = position; _stuckStage = Mathf.Min(_stuckStage + 1, 4); EscalateRecovery(destination, position); } } private void EscalateRecovery(Vector3 destination, Vector3 position) { //IL_00ef: 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_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) string text = (((Object)(object)BlockingCollider != (Object)null) ? ((Object)BlockingCollider).name : "unknown"); switch (_stuckStage) { case 1: ShyGuyLog.DebugThrottled("nav:stuck1", "Stuck (blocked by '" + text + "'); forcing an immediate repath.", 3f); _lastRepath = -999f; if (!_navMeshUsable || !((Object)(object)_agent != (Object)null)) { break; } try { if (_agent.isOnNavMesh) { _agent.ResetPath(); } break; } catch (Exception ex) { ShyGuyLog.Debug("ResetPath during recovery threw: " + ex.Message); break; } case 2: ShyGuyLog.DebugThrottled("nav:stuck2", "Still stuck; boosting acceleration for one second.", 3f); _accelBoostUntil = Time.time + 1f; _lastRepath = -999f; break; case 3: _sidestepDirection = PickSidestep(destination, position); _sidestepUntil = Time.time + 0.9f; ShyGuyLog.DebugThrottled("nav:stuck3", "Still stuck; sidestepping around '" + text + "'.", 3f); break; case 4: AttemptTeleportRecovery(destination, position); break; } } private Vector3 PickSidestep(Vector3 destination, Vector3 position) { //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_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0035: 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_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_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_006e: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: 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_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) Vector3 val = destination - position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.001f) { val = _root.forward; } ((Vector3)(ref val)).Normalize(); Vector3 val2 = Vector3.Cross(Vector3.up, val); Vector3 normalized = ((Vector3)(ref val2)).normalized; Vector3 origin = position + Vector3.up * (_height * 0.5f); float distance = 2.5f; bool flag = !ProbeBlocked(origin, normalized, distance); bool flag2 = !ProbeBlocked(origin, -normalized, distance); if (flag && !flag2) { return normalized; } if (flag2 && !flag) { return -normalized; } if (Time.frameCount / 60 % 2 != 0) { return -normalized; } return normalized; } private bool ProbeBlocked(Vector3 origin, Vector3 direction, float distance) { //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) RaycastHit nearest; return ShyGuyPhysics.SphereCast(origin, ShyGuyConfig.ObstacleAvoidanceRadius, direction, distance, -1, out nearest, IsOwnCollider); } private bool IsOwnCollider(Collider collider) { if ((Object)(object)collider != (Object)null) { return ShyGuyHierarchy.IsPartOf(_root, ((Component)collider).transform); } return false; } private void AttemptTeleportRecovery(Vector3 destination, Vector3 position) { //IL_0120: 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) //IL_0141: 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_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_003e: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0071: 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_007b: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008e: 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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: 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) if (!ShyGuyConfig.StuckTeleportRecovery) { ShyGuyLog.DebugThrottled("nav:stuck4off", "Stuck at the final recovery stage but StuckTeleportRecovery is off; giving up and retrying from stage 1."); _stuckStage = 0; } else { if (Time.time - _lastTeleport < 1.5f) { return; } Vector3 val = destination - position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.001f) { _stuckStage = 0; return; } ((Vector3)(ref val)).Normalize(); float stuckTeleportDistance = ShyGuyConfig.StuckTeleportDistance; Vector3 val2 = position + val * stuckTeleportDistance; if (ShyGuyPhysics.CheckSphere(val2 + Vector3.up * (_height * 0.5f), ShyGuyConfig.ObstacleAvoidanceRadius * 0.8f)) { val2 = position + Vector3.up * Mathf.Min(stuckTeleportDistance, 0.3f); } _lastTeleport = Time.time; if (_navMeshUsable && (Object)(object)_agent != (Object)null) { try { if (!_agent.Warp(val2)) { _root.position = val2; } } catch (Exception ex) { ShyGuyLog.Debug("Agent.Warp during recovery threw: " + ex.Message); _root.position = val2; } } else { _root.position = val2; } _verticalVelocity = 0f; _stuckAnchor = val2; _stuckStage = 0; ShyGuyLog.DebugThrottled("nav:stuck4", "Applied a " + stuckTeleportDistance.ToString("F2") + "m last-resort nudge to escape geometry.", 3f); } } internal string DescribeState() { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) string text = (_navMeshUsable ? "NavMeshAgent" : (ShyGuyConfig.DirectMovementFallback ? "direct movement" : "IMMOBILE (no NavMesh, fallback off)")); string text2 = "no agent"; if ((Object)(object)_agent != (Object)null) { try { text2 = "enabled=" + ((Behaviour)_agent).enabled + ", onNavMesh=" + _agent.isOnNavMesh + ", pathStatus=" + ((object)_agent.pathStatus/*cast due to .constrained prefix*/).ToString() + ", remaining=" + _agent.remainingDistance.ToString("F1") + "m"; } catch (Exception ex) { text2 = "agent query failed: " + ex.Message; } } return "Movement mode: " + text + "\n Agent: " + text2 + "\n Speed: " + _currentSpeed.ToString("F1") + " / " + ShyGuyConfig.ChaseSpeed + "\n PathFailed: " + PathFailed + ", stuckStage: " + _stuckStage + ", blockedBy: " + (((Object)(object)BlockingCollider != (Object)null) ? ((Object)BlockingCollider).name : "nothing"); } } internal sealed class ShyGuyAnimationController { internal const string StateIdle = "idle"; internal const string StateTrigger = "trigger"; internal const string StateRun = "run"; internal const string StateAttack = "attack"; internal const string StateReset = "reset"; internal const string StateDoor = "door"; internal const string StateWalk = "walk"; internal const string StateIntro = "intro"; private readonly Animator _animator; private readonly Animation _legacy; private readonly ShyGuyPrefabConfig _config; private readonly bool _useLegacy; private readonly Dictionary _resolved = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _lengths = new Dictionary(StringComparer.Ordinal); private readonly List _availableNames = new List(); private string _current; internal bool HasAnyAnimation { get { if (!((Object)(object)_animator != (Object)null)) { return (Object)(object)_legacy != (Object)null; } return true; } } internal string Backend { get { if (!_useLegacy) { if (!((Object)(object)_animator != (Object)null)) { return "none"; } return "Animator"; } return "legacy Animation"; } } internal ShyGuyAnimationController(Animator animator, Animation legacy, ShyGuyPrefabConfig config) { _config = config ?? new ShyGuyPrefabConfig(); _animator = animator; _legacy = legacy; _useLegacy = (_config.useLegacyAnimation && (Object)(object)legacy != (Object)null) || (Object)(object)animator == (Object)null; if (_useLegacy && (Object)(object)legacy == (Object)null && (Object)(object)animator == (Object)null) { ShyGuyLog.Warn("The creature has neither an Animator nor a legacy Animation component. It will function but never animate. See the troubleshooting section on missing animations."); return; } CollectAvailableNames(); InjectMissingClipsFromBundle(); CollectAvailableNames(); ShyGuyLog.Msg("Animation backend: " + Backend + " (" + _availableNames.Count + " clip/state name(s) discovered)."); if (ShyGuyConfig.DebugLogging && _availableNames.Count > 0) { ShyGuyLog.Debug("Discovered animation names: " + string.Join(", ", _availableNames)); } } private void InjectMissingClipsFromBundle() { if (_useLegacy && !((Object)(object)_legacy == (Object)null)) { TryInject("Walk", true, true, true, "scp096.mdl/@walk", "@walk", "Walk", "walk"); TryInject("Idle", true, true, true, "scp096.mdl/@idle", "@idle", "Idle", "idle"); TryInject("Intro", true, false, false, "Intro", "intro", "@intro"); TryInject("Panic", true, false, false, "Panic", "Rage", "@Rage", "rage"); } } private void TryInject(string playName, bool requiredLogical, bool loop, bool replaceExisting, params string[] hints) { try { AnimationClip clip = _legacy.GetClip(playName); if ((Object)(object)clip != (Object)null && !replaceExisting) { return; } AnimationClip val = ShyGuyAssetLoader.FindAnimationClip(hints); if ((Object)(object)val == (Object)null) { if (requiredLogical) { ShyGuyLog.Debug("No bundle AnimationClip matched '" + playName + "' (" + string.Join(", ", hints) + ")."); } } else if (IsBannedLocomotionName(((Object)val).name)) { ShyGuyLog.Debug("Skipping banned crouch/crawl clip '" + ((Object)val).name + "' for '" + playName + "'."); } else { if ((Object)(object)clip != (Object)null && clip == val) { return; } val.wrapMode = (WrapMode)((!loop) ? 1 : 2); if ((Object)(object)clip != (Object)null) { try { _legacy.RemoveClip(playName); } catch { } } _legacy.AddClip(val, playName); ShyGuyLog.Msg((((Object)(object)clip != (Object)null) ? "Replaced" : "Injected") + " legacy clip '" + playName + "' from bundle clip '" + ((Object)val).name + "' (" + val.length.ToString("F1") + "s)."); } } catch (Exception ex) { ShyGuyLog.Debug("Injecting clip '" + playName + "' failed: " + ex.Message); } } private void CollectAvailableNames() { _availableNames.Clear(); try { if (_useLegacy && (Object)(object)_legacy != (Object)null) { foreach (object item in ShyGuyReflect.Enumerate(_legacy)) { AnimationState val = (AnimationState)((item is AnimationState) ? item : null); if ((TrackedReference)(object)val == (TrackedReference)null) { Object val2 = (Object)((item is Object) ? item : null); if (val2 != null) { val = ((Il2CppObjectBase)val2).TryCast(); } } if ((TrackedReference)(object)val == (TrackedReference)null) { continue; } AnimationClip clip = val.clip; if (!((Object)(object)clip == (Object)null)) { string name = ((Object)clip).name; if (!string.IsNullOrEmpty(name) && !_availableNames.Contains(name)) { _availableNames.Add(name); } } } if (_availableNames.Count == 0) { ShyGuyLog.WarnOnce("anim:nolegacyclips", "The legacy Animation component reported no clips. Add the FBX takes to its Animations array in the Inspector, and make sure the FBX importer's Animation Type is set to Legacy."); } } else { if (!((Object)(object)_animator != (Object)null)) { return; } Il2CppReferenceArray val3 = (((Object)(object)_animator.runtimeAnimatorController != (Object)null) ? _animator.runtimeAnimatorController.animationClips : null); if (val3 != null) { for (int i = 0; i < ((Il2CppArrayBase)(object)val3).Length; i++) { AnimationClip val4 = ((Il2CppArrayBase)(object)val3)[i]; if ((Object)(object)val4 != (Object)null && !string.IsNullOrEmpty(((Object)val4).name) && !_availableNames.Contains(((Object)val4).name)) { _availableNames.Add(((Object)val4).name); } } } else { ShyGuyLog.WarnOnce("anim:nocontroller", "The Animator on the prefab has no runtimeAnimatorController assigned, so no animation will play. Create an Animator Controller, drag the FBX takes into it as states, and assign it to the Animator. See the prefab setup instructions."); } } } catch (Exception ex) { ShyGuyLog.Debug("Enumerating animation names failed: " + ex.Message); } } private string Resolve(string logicalKey) { if (_resolved.TryGetValue(logicalKey, out var value)) { return value; } string text = null; foreach (string item in BuildCandidates(logicalKey)) { if (!string.IsNullOrWhiteSpace(item) && !IsBannedLocomotionName(item)) { if (Exists(item)) { text = item; break; } string text2 = FuzzyMatch(item); if (text2 != null && !IsBannedLocomotionName(text2)) { text = text2; break; } } } _resolved[logicalKey] = text; if (text == null) { ShyGuyLog.WarnOnce("anim:missing:" + logicalKey, "No animation found for the '" + logicalKey + "' state. Tried: " + string.Join(", ", BuildCandidates(logicalKey)) + ". " + ((_availableNames.Count > 0) ? ("Available names are: " + string.Join(", ", _availableNames) + ".") : "No animation names could be enumerated at all.") + " The creature will skip this animation. Set the matching name in the prefab config to fix it."); } else { ShyGuyLog.Debug("Animation '" + logicalKey + "' resolved to '" + text + "'."); } return text; } private IEnumerable BuildCandidates(string logicalKey) { string text = logicalKey switch { "idle" => _config.idleState, "trigger" => _config.triggerState, "run" => _config.runState, "attack" => _config.attackState, "reset" => _config.resetState, "door" => _config.doorState, "walk" => _config.walkState, "intro" => _config.introState, _ => null, }; if (!string.IsNullOrWhiteSpace(text)) { yield return text.Trim(); } if (ShyGuyPrefabConfig.StateNameCandidates.TryGetValue(logicalKey, out var value)) { string[] array = value; for (int i = 0; i < array.Length; i++) { yield return array[i]; } } } private bool Exists(string name) { try { if (_useLegacy && (Object)(object)_legacy != (Object)null) { return (Object)(object)_legacy.GetClip(name) != (Object)null; } if ((Object)(object)_animator != (Object)null) { return _animator.HasState(_config.animatorLayer, Animator.StringToHash(name)); } } catch (Exception ex) { ShyGuyLog.Debug("Checking for animation '" + name + "' threw: " + ex.Message); } return false; } private string FuzzyMatch(string candidate) { if (_availableNames.Count == 0) { return null; } string text = candidate.TrimStart('@'); if (text.Length < 3) { return null; } string text2 = null; string text3 = null; foreach (string availableName in _availableNames) { if (string.IsNullOrEmpty(availableName) || IsBannedLocomotionName(availableName)) { continue; } if (availableName.EndsWith(text, StringComparison.OrdinalIgnoreCase)) { if (text2 == null || availableName.Length < text2.Length) { text2 = availableName; } } else if (availableName.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0 && (text3 == null || availableName.Length < text3.Length)) { text3 = availableName; } } string text4 = text2; if (text4 == null && text3 != null && !IsBannedLocomotionName(text3)) { text4 = text3; } if (text4 == null) { return null; } if (!_useLegacy && (Object)(object)_animator != (Object)null && !Exists(text4)) { ShyGuyLog.DebugThrottled("anim:clipnotstate:" + text4, "Clip '" + text4 + "' exists but there is no Animator state with that name; add a state named after the clip in the Animator Controller.", 60f); return null; } ShyGuyLog.Debug("Fuzzy-matched animation '" + candidate + "' to '" + text4 + "'."); return text4; } private static bool IsBannedLocomotionName(string name) { if (string.IsNullOrEmpty(name)) { return false; } if (name.IndexOf("crouch", StringComparison.OrdinalIgnoreCase) < 0) { return name.IndexOf("crawl", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } private string ResolveStandingWalkName() { _resolved.Remove("walk"); string text = Resolve("walk"); if (text != null && !IsBannedLocomotionName(text)) { return text; } if (_useLegacy && (Object)(object)_legacy != (Object)null) { AnimationClip clip = _legacy.GetClip("Walk"); if ((Object)(object)clip != (Object)null && !IsBannedLocomotionName(((Object)clip).name)) { return "Walk"; } } return null; } internal bool Play(string logicalKey, bool restart = false) { if (!HasAnyAnimation) { return false; } if (!restart && string.Equals(_current, logicalKey, StringComparison.Ordinal)) { return true; } string text = Resolve(logicalKey); if (text != null && IsBannedLocomotionName(text)) { ShyGuyLog.Debug("Replacing banned clip '" + text + "' (" + logicalKey + ") with Walk."); if (!string.Equals(logicalKey, "walk", StringComparison.Ordinal)) { return Play("walk", restart); } text = ResolveStandingWalkName() ?? text; } if (text == null) { _current = logicalKey; return false; } float num = Mathf.Max(0f, _config.animationCrossFade); try { if (_useLegacy && (Object)(object)_legacy != (Object)null) { ConfigureLegacyWrapMode(logicalKey, text); if (restart) { try { _legacy.Stop(); } catch { } _legacy.Play(text); } else if (num > 0f) { _legacy.CrossFade(text, num); } else { _legacy.Play(text); } } else if ((Object)(object)_animator != (Object)null) { if (num > 0f && !restart) { _animator.CrossFade(text, num, _config.animatorLayer); } else { _animator.Play(text, _config.animatorLayer, 0f); } } _current = logicalKey; ShyGuyLog.Debug("Playing animation '" + text + "' for state '" + logicalKey + "'."); return true; } catch (Exception ex) { ShyGuyLog.WarnThrottled("anim:playfail:" + logicalKey, "Could not play the '" + logicalKey + "' animation ('" + text + "'): " + ex.Message); _current = logicalKey; return false; } } private void ConfigureLegacyWrapMode(string logicalKey, string clipName) { if ((Object)(object)_legacy == (Object)null) { return; } bool flag = logicalKey == "idle" || logicalKey == "run" || logicalKey == "walk"; try { AnimationClip clip = _legacy.GetClip(clipName); if ((Object)(object)clip != (Object)null) { clip.wrapMode = (WrapMode)((!flag) ? 1 : 2); } } catch (Exception ex) { ShyGuyLog.Debug("Setting the wrap mode for '" + clipName + "' threw: " + ex.Message); } } internal void StopAll() { _current = null; try { if (_useLegacy && (Object)(object)_legacy != (Object)null) { _legacy.Stop(); } } catch (Exception ex) { ShyGuyLog.Debug("StopAll threw: " + ex.Message); } } internal float GetLength(string logicalKey) { if (_lengths.TryGetValue(logicalKey, out var value)) { return value; } float num = -1f; string text = Resolve(logicalKey); if (text != null) { try { if (_useLegacy && (Object)(object)_legacy != (Object)null) { AnimationClip clip = _legacy.GetClip(text); if ((Object)(object)clip != (Object)null) { num = clip.length; } } else if ((Object)(object)_animator != (Object)null && (Object)(object)_animator.runtimeAnimatorController != (Object)null) { Il2CppReferenceArray animationClips = _animator.runtimeAnimatorController.animationClips; if (animationClips != null) { for (int i = 0; i < ((Il2CppArrayBase)(object)animationClips).Length; i++) { AnimationClip val = ((Il2CppArrayBase)(object)animationClips)[i]; if ((Object)(object)val != (Object)null && string.Equals(((Object)val).name, text, StringComparison.Ordinal)) { num = val.length; break; } } } } } catch (Exception ex) { ShyGuyLog.Debug("Reading the length of '" + text + "' threw: " + ex.Message); } } if (num <= 0f && logicalKey == "trigger" && _config.triggerClipLength > 0f) { num = _config.triggerClipLength; } _lengths[logicalKey] = num; if (num > 0f) { ShyGuyLog.Debug("Animation '" + logicalKey + "' is " + num.ToString("F2") + "s long."); } return num; } internal float GetTriggerHoldDuration() { float triggerAnimationDurationFallback = ShyGuyConfig.TriggerAnimationDurationFallback; float num = triggerAnimationDurationFallback; if (ShyGuyConfig.UseClipLengthForTrigger) { float length = GetLength("trigger"); if (length > 0f) { ShyGuyLog.Debug("Trigger hold using measured clip length: " + length.ToString("F1") + "s."); return Mathf.Max(0.05f, length); } ShyGuyLog.DebugThrottled("anim:triggerlen", "Could not read the trigger animation length; using the " + triggerAnimationDurationFallback.ToString("F1") + "s fallback from TriggerAnimationDurationFallback.", 60f); } float maxTriggerDuration = ShyGuyConfig.MaxTriggerDuration; if (maxTriggerDuration > 0f && num > maxTriggerDuration) { ShyGuyLog.DebugThrottled("anim:triggercap", "Trigger fallback is " + num.ToString("F1") + "s; clamping to MaxTriggerDuration (" + maxTriggerDuration.ToString("F1") + "s).", 60f); num = maxTriggerDuration; } return Mathf.Max(0.05f, num); } internal float GetIntroHoldDuration() { float length = GetLength("intro"); if (length > 0f) { return Mathf.Max(0.05f, length); } return 8.96f; } internal void SetSpeedMultiplier(float multiplier) { if ((Object)(object)_animator == (Object)null || _useLegacy) { return; } try { _animator.speed = Mathf.Clamp(multiplier, 0.01f, 4f); } catch (Exception ex) { ShyGuyLog.Debug("Setting the Animator speed threw: " + ex.Message); } } internal string DescribeState() { List list = new List { "Animation backend: " + Backend, " Currently playing: " + (_current ?? "(nothing)") }; string[] array = new string[8] { "idle", "trigger", "run", "attack", "reset", "door", "walk", "intro" }; foreach (string text in array) { string text2 = Resolve(text); float length = GetLength(text); list.Add(" " + text.PadRight(8) + " -> " + (text2 ?? "MISSING") + ((length > 0f) ? (" (" + length.ToString("F2") + "s)") : "")); } if (_availableNames.Count > 0) { list.Add(" Available: " + string.Join(", ", _availableNames)); } return string.Join("\n", list); } } internal sealed class ShyGuyAudioController { private readonly Transform _root; private readonly ShyGuyPrefabConfig _config; private readonly AudioSource _trigger; private readonly AudioSource _chase; private readonly AudioSource _attack; private readonly AudioSource _ambient; private readonly HashSet _warned = new HashSet(StringComparer.Ordinal); internal ShyGuyAudioController(Transform root, ShyGuyPrefabConfig config, ShyGuyRig rig) { _root = root; _config = config ?? new ShyGuyPrefabConfig(); _trigger = rig?.TriggerAudio; _chase = rig?.ChaseAudio; _attack = rig?.AttackAudio; _ambient = rig?.AmbientAudio; string clipName = (string.IsNullOrWhiteSpace(_config.triggerClipName) ? "voicemod_scream" : _config.triggerClipName.Trim()); string clipName2 = SanitizeClipName(_config.chaseClipName, "scream"); Configure(_trigger, "TriggerAudioSource", clipName, _config.triggerVolume, loop: false, allowVoicemod: true); Configure(_chase, "ChaseAudioSource", clipName2, _config.chaseVolume, loop: true, allowVoicemod: false); Configure(_attack, "AttackAudioSource", _config.attackClipName, _config.attackVolume, loop: false, allowVoicemod: false); Configure(_ambient, "AmbientAudioSource", _config.ambientClipName, _config.ambientVolume, loop: true, allowVoicemod: false); } private static string SanitizeClipName(string configured, string preferred) { if (string.IsNullOrWhiteSpace(configured)) { return preferred; } if (configured.IndexOf("voicemod", StringComparison.OrdinalIgnoreCase) >= 0) { return preferred; } return configured.Trim(); } private void Configure(AudioSource source, string label, string clipName, float volume, bool loop, bool allowVoicemod) { if ((Object)(object)source == (Object)null) { ShyGuyLog.Debug("Audio source '" + label + "' is absent; the sounds it would play are skipped."); return; } try { source.playOnAwake = false; source.loop = loop; source.volume = Mathf.Clamp01(volume); source.spatialBlend = 1f; source.rolloffMode = (AudioRolloffMode)1; source.minDistance = 2f; source.maxDistance = Mathf.Max(5f, _config.audioMaxDistance); source.dopplerLevel = 0.2f; source.priority = 64; if (!allowVoicemod && (Object)(object)source.clip != (Object)null && ((Object)source.clip).name.IndexOf("voicemod", StringComparison.OrdinalIgnoreCase) >= 0) { ShyGuyLog.Debug("Clearing Voicemod clip from " + label + "; using '" + clipName + "' instead."); source.clip = null; } if (allowVoicemod) { AudioClip voicemodScream = ShyGuyWavLoader.GetVoicemodScream(); if ((Object)(object)voicemodScream != (Object)null) { source.clip = voicemodScream; ShyGuyLog.Debug("Assigned embedded Voicemod scream to " + label + "."); return; } } if ((Object)(object)source.clip == (Object)null) { AudioClip val = ShyGuyAssetLoader.LoadAudioClip(clipName); if ((Object)(object)val != (Object)null) { source.clip = val; ShyGuyLog.Debug("Assigned bundle clip '" + ((Object)val).name + "' to " + label + "."); } else if (!string.IsNullOrWhiteSpace(clipName)) { WarnOnce(label, "No AudioClip is assigned to " + label + " and '" + clipName + "' could not be found in the asset bundle. That sound will not play. Assign the clip in the prefab Inspector to fix it."); } } else { ShyGuyLog.Debug(label + " is using its prefab-assigned clip '" + ((Object)source.clip).name + "'."); } } catch (Exception ex) { ShyGuyLog.Warn("Configuring the audio source " + label + " failed: " + ex.Message); } } private void WarnOnce(string key, string message) { if (_warned.Add(key)) { ShyGuyLog.Warn(message); } } internal void PlayTrigger() { StopChase(); StopAmbient(); if ((Object)(object)_trigger == (Object)null) { return; } try { AudioClip voicemodScream = ShyGuyWavLoader.GetVoicemodScream(); if ((Object)(object)voicemodScream != (Object)null) { _trigger.clip = voicemodScream; } if ((Object)(object)_trigger.clip == (Object)null) { WarnOnce("TriggerAudioSource:noclip", "No Voicemod/trigger clip available; the Panic scream will not play."); return; } _trigger.loop = false; _trigger.volume = Mathf.Clamp01(_config.triggerVolume); _trigger.Play(); ShyGuyLog.Debug("Trigger Voicemod played once (clip '" + ((Object)_trigger.clip).name + "')."); } catch (Exception ex) { ShyGuyLog.WarnThrottled("audio:oneshot:TriggerAudioSource", "Could not play TriggerAudioSource: " + ex.Message); } } internal void StopTrigger() { if ((Object)(object)_trigger == (Object)null) { return; } try { if (_trigger.isPlaying) { _trigger.Stop(); } } catch (Exception ex) { ShyGuyLog.Debug("Stopping the trigger audio threw: " + ex.Message); } } internal void StartChase() { if ((Object)(object)_chase == (Object)null) { return; } try { StopTrigger(); if (!_chase.isPlaying) { if ((Object)(object)_chase.clip == (Object)null) { WarnOnce("chase:noclip", "The chase audio source has no clip, so the chase scream will not play."); return; } _chase.loop = true; _chase.volume = Mathf.Clamp01(_config.chaseVolume); _chase.Play(); ShyGuyLog.Debug("Chase SCREAM started (clip '" + ((Object)_chase.clip).name + "')."); } } catch (Exception ex) { ShyGuyLog.WarnThrottled("audio:chasefail", "Could not start the chase audio: " + ex.Message); } } internal void StopChase() { if ((Object)(object)_chase == (Object)null) { return; } try { if (_chase.isPlaying) { _chase.Stop(); ShyGuyLog.Debug("Chase audio stopped."); } } catch (Exception ex) { ShyGuyLog.Debug("Stopping the chase audio threw: " + ex.Message); } } internal void PlayAttack() { PlayOneShot(_attack, "AttackAudioSource"); } internal void PlayReset() { } internal void PlayDoor() { AudioClip val = ShyGuyAssetLoader.LoadAudioClip(string.IsNullOrWhiteSpace(_config.doorClipName) ? "dooropen" : _config.doorClipName); if ((Object)(object)val != (Object)null) { PlayClipOn(_attack, val, "DoorAudio"); } else { PlayOneShot(_attack, "AttackAudioSource"); } } internal void StartAmbient() { if (!ShyGuyConfig.PassiveAmbientAudio || (Object)(object)_ambient == (Object)null) { return; } try { if (!_ambient.isPlaying && !((Object)(object)_ambient.clip == (Object)null)) { _ambient.loop = true; _ambient.volume = Mathf.Clamp01(_config.ambientVolume); _ambient.Play(); } } catch (Exception ex) { ShyGuyLog.Debug("Starting the ambient audio threw: " + ex.Message); } } internal void StopAmbient() { if ((Object)(object)_ambient == (Object)null) { return; } try { if (_ambient.isPlaying) { _ambient.Stop(); } } catch (Exception ex) { ShyGuyLog.Debug("Stopping the ambient audio threw: " + ex.Message); } } internal void StopAll() { StopChase(); StopTrigger(); StopAmbient(); if (!((Object)(object)_attack != (Object)null)) { return; } try { if (_attack.isPlaying) { _attack.Stop(); } } catch { } } private void PlayOneShot(AudioSource source, string label) { if ((Object)(object)source == (Object)null) { return; } try { if ((Object)(object)source.clip == (Object)null) { WarnOnce(label + ":noclip", "The audio source " + label + " has no clip, so that sound will not play."); return; } source.loop = false; source.Play(); } catch (Exception ex) { ShyGuyLog.WarnThrottled("audio:oneshot:" + label, "Could not play " + label + ": " + ex.Message); } } private void PlayClipOn(AudioSource source, AudioClip clip, string label) { if ((Object)(object)source == (Object)null || (Object)(object)clip == (Object)null) { return; } try { source.loop = false; source.clip = clip; source.Play(); } catch (Exception ex) { ShyGuyLog.WarnThrottled("audio:clipon:" + label, "Could not play a clip on " + label + ": " + ex.Message); } } internal string DescribeState() { return "Audio:\n Trigger: " + DescribeSource(_trigger) + "\n Chase: " + DescribeSource(_chase) + "\n Attack: " + DescribeSource(_attack) + "\n Ambient: " + DescribeSource(_ambient); } private static string DescribeSource(AudioSource source) { if ((Object)(object)source == (Object)null) { return "(missing)"; } return (((Object)(object)source.clip != (Object)null) ? ((Object)source.clip).name : "(no clip)") + (source.isPlaying ? " [playing]" : "") + (source.loop ? " loop" : ""); } } internal static class ShyGuyMaterialFixer { private static readonly string[] LitShaderCandidates = new string[5] { "Universal Render Pipeline/Lit", "Universal Render Pipeline/Simple Lit", "Universal Render Pipeline/Unlit", "Standard", "Sprites/Default" }; private static readonly string[] AlbedoProperties = new string[2] { "_BaseMap", "_MainTex" }; private static readonly string[] ColorProperties = new string[2] { "_BaseColor", "_Color" }; private static readonly string[] NormalProperties = new string[2] { "_BumpMap", "_NormalMap" }; private static Shader _cachedShader; private static bool _searchedShader; internal static void InvalidateCache() { _searchedShader = false; _cachedShader = null; } internal static void FixHierarchy(Transform root) { if ((Object)(object)root == (Object)null) { return; } if (!ShyGuyConfig.FixMaterialsAtRuntime) { ShyGuyLog.Debug("Runtime material fixup is disabled by preference."); return; } Renderer[] array; try { array = Il2CppArrayBase.op_Implicit(((Component)root).GetComponentsInChildren(true)); } catch (Exception ex) { ShyGuyLog.Warn("Could not enumerate renderers for material fixup: " + ex.Message); return; } if (array == null || array.Length == 0) { ShyGuyLog.WarnOnce("mat:norenderers", "The creature has no Renderer components, so it will be invisible. Check that the character model is a child of the prefab and that its SkinnedMeshRenderer survived the bundle build."); return; } int num = 0; int num2 = 0; HashSet hashSet = new HashSet(); foreach (Renderer val in array) { if ((Object)(object)val == (Object)null) { continue; } Material[] array2; try { array2 = Il2CppArrayBase.op_Implicit((Il2CppArrayBase)(object)val.materials); } catch (Exception ex2) { ShyGuyLog.Debug("Reading materials from '" + ((Object)val).name + "' threw: " + ex2.Message); continue; } if (array2 == null) { continue; } foreach (Material val2 in array2) { if (!((Object)(object)val2 == (Object)null) && hashSet.Add(((Object)val2).GetInstanceID())) { num2++; if (Repair(val2)) { num++; } } } } if (num > 0) { ShyGuyLog.Msg("Repaired " + num + " of " + num2 + " material(s) whose shader did not survive the asset bundle. If the model still looks wrong, rebuild the bundle with the Marrow SDK's Unity version."); } else { ShyGuyLog.Debug("Material check: " + num2 + " material(s) inspected, none needed repair."); } } private static bool Repair(Material material) { //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_00cf: Unknown result type (might be due to invalid IL or missing references) if (!NeedsRepair(material)) { return false; } Shader val = FindLitShader(); if ((Object)(object)val == (Object)null) { ShyGuyLog.WarnOnce("mat:noshader", "No usable replacement shader could be found in the running game, so broken materials cannot be repaired. The model will render magenta. Rebuild the asset bundle against the Marrow SDK."); return false; } Texture val2 = FindTexture(material, AlbedoProperties); Texture val3 = FindTexture(material, NormalProperties); Color color = FindColor(material, ColorProperties); string text = "unknown"; try { text = (((Object)(object)material.shader != (Object)null) ? ((Object)material.shader).name : "null"); } catch { } try { material.shader = val; } catch (Exception ex) { ShyGuyLog.Debug("Assigning the replacement shader to '" + ((Object)material).name + "' threw: " + ex.Message); return false; } ApplyTexture(material, AlbedoProperties, val2); ApplyTexture(material, NormalProperties, val3); ApplyColor(material, ColorProperties, color); TryEnableAlpha(material, val2); ShyGuyLog.Debug("Repaired material '" + ((Object)material).name + "': " + text + " -> " + ((Object)val).name + " (albedo=" + (((Object)(object)val2 != (Object)null) ? ((Object)val2).name : "none") + ", normal=" + (((Object)(object)val3 != (Object)null) ? ((Object)val3).name : "none") + ")."); return true; } private static bool NeedsRepair(Material material) { try { Shader shader = material.shader; if ((Object)(object)shader == (Object)null) { return true; } string text = ((Object)shader).name ?? string.Empty; if (text.IndexOf("InternalErrorShader", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (text.IndexOf("Hidden/InternalError", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (!shader.isSupported) { return true; } return false; } catch (Exception ex) { ShyGuyLog.Debug("Shader inspection threw for a material: " + ex.Message + "; treating it as broken."); return true; } } private static Shader FindLitShader() { if (_searchedShader) { return _cachedShader; } _searchedShader = true; string[] litShaderCandidates = LitShaderCandidates; foreach (string text in litShaderCandidates) { try { Shader val = Shader.Find(text); if ((Object)(object)val != (Object)null && val.isSupported) { _cachedShader = val; ShyGuyLog.Debug("Using '" + text + "' as the material repair shader."); return _cachedShader; } } catch (Exception ex) { ShyGuyLog.Debug("Shader.Find('" + text + "') threw: " + ex.Message); } } return null; } private static Texture FindTexture(Material material, string[] properties) { foreach (string text in properties) { try { if (material.HasProperty(text)) { Texture texture = material.GetTexture(text); if ((Object)(object)texture != (Object)null) { return texture; } } } catch { } } return null; } private static Color FindColor(Material material, string[] properties) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) foreach (string text in properties) { try { if (!material.HasProperty(text)) { continue; } return material.GetColor(text); } catch { } } return Color.white; } private static void ApplyTexture(Material material, string[] properties, Texture texture) { if ((Object)(object)texture == (Object)null) { return; } foreach (string text in properties) { try { if (material.HasProperty(text)) { material.SetTexture(text, texture); } } catch { } } } private static void ApplyColor(Material material, string[] properties, Color color) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) foreach (string text in properties) { try { if (material.HasProperty(text)) { material.SetColor(text, color); } } catch { } } } private static void TryEnableAlpha(Material material, Texture albedo) { if ((Object)(object)albedo == (Object)null) { return; } try { if (material.HasProperty("_AlphaClip")) { material.SetFloat("_AlphaClip", 1f); } if (material.HasProperty("_Cutoff")) { material.SetFloat("_Cutoff", 0.35f); } material.EnableKeyword("_ALPHATEST_ON"); } catch (Exception ex) { ShyGuyLog.Debug("Enabling alpha clipping threw: " + ex.Message); } } } internal static class ShyGuyWavLoader { private static AudioClip _cached; internal static AudioClip GetVoicemodScream() { if ((Object)(object)_cached != (Object)null) { return _cached; } try { byte[] array = ReadEmbedded("TheShyGuy.Resources.voicemod_scream.wav"); if (array == null || array.Length < 44) { ShyGuyLog.Debug("Embedded voicemod_scream.wav is missing."); return null; } _cached = DecodePcmWav(array, "voicemod_scream"); if ((Object)(object)_cached != (Object)null) { ShyGuyLog.Msg("Loaded embedded Voicemod scream (" + _cached.length.ToString("F1") + "s, " + _cached.channels + "ch @ " + _cached.frequency + " Hz)."); } } catch (Exception ex) { ShyGuyLog.Warn("Could not load the embedded Voicemod scream: " + ex.Message); } return _cached; } private static byte[] ReadEmbedded(string logicalName) { Assembly assembly = typeof(ShyGuyWavLoader).Assembly; using Stream stream = assembly.GetManifestResourceStream(logicalName); if (stream == null) { string[] manifestResourceNames = assembly.GetManifestResourceNames(); foreach (string text in manifestResourceNames) { if (!text.EndsWith("voicemod_scream.wav", StringComparison.OrdinalIgnoreCase)) { continue; } using Stream stream2 = assembly.GetManifestResourceStream(text); if (stream2 == null) { continue; } using MemoryStream memoryStream = new MemoryStream(); stream2.CopyTo(memoryStream); return memoryStream.ToArray(); } return null; } using MemoryStream memoryStream2 = new MemoryStream(); stream.CopyTo(memoryStream2); return memoryStream2.ToArray(); } private static AudioClip DecodePcmWav(byte[] data, string clipName) { if (data[0] != 82 || data[1] != 73 || data[2] != 70 || data[3] != 70) { throw new InvalidDataException("Not a RIFF file."); } if (data[8] != 87 || data[9] != 65 || data[10] != 86 || data[11] != 69) { throw new InvalidDataException("Not a WAVE file."); } int num = 12; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = -1; int num6 = 0; while (num + 8 <= data.Length) { string text = Encoding.ASCII.GetString(data, num, 4); int num7 = BitConverter.ToInt32(data, num + 4); int num8 = num + 8; if (text == "fmt ") { short num9 = BitConverter.ToInt16(data, num8); if (num9 != 1) { throw new InvalidDataException("Only PCM WAV is supported (format=" + num9 + ")."); } num2 = BitConverter.ToInt16(data, num8 + 2); num3 = BitConverter.ToInt32(data, num8 + 4); num4 = BitConverter.ToInt16(data, num8 + 14); } else if (text == "data") { num5 = num8; num6 = num7; break; } num = num8 + num7 + (num7 & 1); } if (num5 < 0 || num2 <= 0 || num3 <= 0 || num4 != 16) { throw new InvalidDataException("Unsupported or incomplete WAV (need 16-bit PCM)."); } int num10 = num6 / (num4 / 8); int num11 = num10 / num2; float[] array = new float[num10]; int num12 = num5; for (int i = 0; i < num10; i++) { short num13 = BitConverter.ToInt16(data, num12); num12 += 2; array[i] = (float)num13 / 32768f; } AudioClip obj = AudioClip.Create(clipName, num11, num2, num3, false); obj.SetData(Il2CppStructArray.op_Implicit(array), 0); return obj; } internal static void Invalidate() { if ((Object)(object)_cached != (Object)null) { try { Object.Destroy((Object)(object)_cached); } catch { } _cached = null; } } } internal static class ShyGuyBuildInfo { public const string Name = "TheShyGuy"; public const string DisplayName = "The Shy Guy"; public const string Description = "Adds a custom SCP-096 style enemy to BONELAB that rages when a player looks at its face."; public const string Author = "ShyGuyMod"; public const string Company = ""; public const string Version = "1.0.0"; public const string DownloadLink = null; } internal static class ShyGuyAssetLoader { private const string EmbeddedResourceName = "TheShyGuy.Resources.theshyguy.bundle"; private static AssetBundle _bundle; private static bool _attempted; private static string _bundleSource = "(not loaded)"; private static string _tempBundlePath; private static GameObject _prefab; private static ShyGuyPrefabConfig _prefabConfig; private static readonly Dictionary ClipCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private static List _animationClips; private static readonly List AssetNames = new List(); internal static bool BundleLoaded => (Object)(object)_bundle != (Object)null; internal static string BundleSource => _bundleSource; internal static string UserDataFolder => Path.Combine(MelonEnvironment.UserDataDirectory, "TheShyGuy"); internal static bool EnsureBundle() { if ((Object)(object)_bundle != (Object)null) { return true; } if (_attempted) { return false; } _attempted = true; ShyGuyLog.Guard("bundle load", delegate { string text = ExtractEmbeddedBundle(); if (text == null) { string text2 = Path.Combine(UserDataFolder, ShyGuyConfig.BundleFileName); if (!File.Exists(text2)) { ShyGuyLog.Warn("No asset bundle found. Looked for an embedded 'TheShyGuy.Resources.theshyguy.bundle' and for the file '" + text2 + "'. The mod will still adopt a TheShyGuy prefab placed in a level, but the spawn command has nothing to spawn. See the asset bundle build instructions."); EnsureUserDataFolder(); return; } text = text2; _bundleSource = text2; } _bundle = AssetBundle.LoadFromFile(text); if ((Object)(object)_bundle == (Object)null) { ShyGuyLog.Error("AssetBundle.LoadFromFile returned null for '" + text + "'. The bundle is either corrupt or was built for a different Unity version or platform than this game uses. Rebuild it as StandaloneWindows64 with the Marrow SDK's Unity version."); } else { CacheAssetNames(); ShyGuyLog.Msg("Asset bundle loaded from " + _bundleSource + " (" + AssetNames.Count + " asset(s))."); if (ShyGuyConfig.DebugLogging && AssetNames.Count > 0) { ShyGuyLog.Debug("Bundle contents: " + string.Join(", ", AssetNames)); } } }); return (Object)(object)_bundle != (Object)null; } private static void EnsureUserDataFolder() { try { if (!Directory.Exists(UserDataFolder)) { Directory.CreateDirectory(UserDataFolder); ShyGuyLog.Msg("Created " + UserDataFolder + " - drop theshyguy.bundle in there."); } } catch (Exception ex) { ShyGuyLog.Debug("Could not create the UserData folder: " + ex.Message); } } private static string ExtractEmbeddedBundle() { try { using Stream stream = typeof(ShyGuyAssetLoader).Assembly.GetManifestResourceStream("TheShyGuy.Resources.theshyguy.bundle"); if (stream == null) { ShyGuyLog.Debug("No embedded asset bundle in the DLL; will look on disk instead."); return null; } string text = Path.Combine(MelonEnvironment.UserDataDirectory, "TheShyGuy", "cache"); Directory.CreateDirectory(text); string text2 = Path.Combine(text, "theshyguy.embedded.bundle"); bool flag = true; if (File.Exists(text2)) { try { flag = new FileInfo(text2).Length != stream.Length; } catch { flag = true; } } if (flag) { using FileStream destination = File.Create(text2); stream.CopyTo(destination); ShyGuyLog.Debug("Extracted the embedded asset bundle to " + text2 + "."); } _tempBundlePath = text2; _bundleSource = "embedded resource (cached at " + text2 + ")"; return text2; } catch (Exception ex) { ShyGuyLog.Warn("Could not extract the embedded asset bundle: " + ex.Message + ". Falling back to a bundle on disk."); return null; } } private static void CacheAssetNames() { AssetNames.Clear(); if ((Object)(object)_bundle == (Object)null) { return; } try { Il2CppStringArray allAssetNames = _bundle.GetAllAssetNames(); if (allAssetNames == null) { return; } for (int i = 0; i < ((Il2CppArrayBase)(object)allAssetNames).Length; i++) { string text = ((Il2CppArrayBase)(object)allAssetNames)[i]; if (!string.IsNullOrEmpty(text)) { AssetNames.Add(text); } } } catch (Exception ex) { ShyGuyLog.Debug("Enumerating bundle asset names threw: " + ex.Message); } } internal static GameObject GetPrefab() { if ((Object)(object)_prefab != (Object)null) { return _prefab; } if (!EnsureBundle()) { return null; } string prefabAssetName = ShyGuyConfig.PrefabAssetName; _prefab = ShyGuyAssetLoader.LoadAsset(prefabAssetName); if ((Object)(object)_prefab == (Object)null) { ShyGuyLog.Error("The asset bundle does not contain a GameObject named '" + prefabAssetName + "'. " + ((AssetNames.Count > 0) ? ("It contains: " + string.Join(", ", AssetNames) + ". ") : "It appears to contain no assets at all. ") + "Check that TheShyGuy.prefab was assigned to the bundle and that PrefabAssetName matches its name."); return null; } try { ((Object)_prefab).hideFlags = (HideFlags)32; } catch (Exception ex) { ShyGuyLog.Debug("Setting prefab hideFlags threw: " + ex.Message); } ShyGuyLog.Msg("Loaded prefab '" + ((Object)_prefab).name + "' from the asset bundle."); return _prefab; } internal static ShyGuyPrefabConfig GetPrefabConfig() { if (_prefabConfig != null) { return _prefabConfig; } string json = null; string sourceDescription = "built-in defaults"; if (EnsureBundle()) { TextAsset val = ShyGuyAssetLoader.LoadAsset("TheShyGuyConfig"); if ((Object)(object)val != (Object)null) { try { json = val.text; sourceDescription = "bundle TextAsset '" + ((Object)val).name + "'"; } catch (Exception ex) { ShyGuyLog.Debug("Reading the config TextAsset threw: " + ex.Message); } } } try { string text = Path.Combine(UserDataFolder, "TheShyGuyConfig.json"); if (File.Exists(text)) { json = File.ReadAllText(text); sourceDescription = text; ShyGuyLog.Msg("Using the prefab config override at " + text + "."); } } catch (Exception ex2) { ShyGuyLog.Debug("Reading the prefab config override threw: " + ex2.Message); } _prefabConfig = ShyGuyPrefabConfig.Parse(json, sourceDescription); return _prefabConfig; } internal static AudioClip LoadAudioClip(string name) { if (string.IsNullOrWhiteSpace(name)) { return null; } if (ClipCache.TryGetValue(name, out var value)) { return value; } AudioClip val = ShyGuyAssetLoader.LoadAsset(name); ClipCache[name] = val; if ((Object)(object)val == (Object)null) { ShyGuyLog.Debug("No AudioClip named '" + name + "' in the bundle." + ((AssetNames.Count > 0) ? (" Bundle contains: " + string.Join(", ", AssetNames)) : "")); } return val; } internal static AnimationClip FindAnimationClip(params string[] hints) { if (hints == null || hints.Length == 0) { return null; } if (!EnsureBundle()) { return null; } EnsureAnimationClipCache(); if (_animationClips == null || _animationClips.Count == 0) { return null; } foreach (string text in hints) { if (string.IsNullOrWhiteSpace(text)) { continue; } string text2 = text.Trim().TrimStart('@'); bool flag = text2.Equals("walk", StringComparison.OrdinalIgnoreCase); bool flag2 = text2.Equals("idle", StringComparison.OrdinalIgnoreCase); AnimationClip val = null; for (int j = 0; j < _animationClips.Count; j++) { AnimationClip val2 = _animationClips[j]; if ((Object)(object)val2 == (Object)null || string.IsNullOrEmpty(((Object)val2).name)) { continue; } string name = ((Object)val2).name; if (IsBannedAnimationName(name)) { continue; } bool flag3 = string.Equals(name, text, StringComparison.OrdinalIgnoreCase) || string.Equals(name, text2, StringComparison.OrdinalIgnoreCase) || name.EndsWith("/@" + text2, StringComparison.OrdinalIgnoreCase) || name.EndsWith("@" + text2, StringComparison.OrdinalIgnoreCase) || (name.EndsWith(text2, StringComparison.OrdinalIgnoreCase) && name.Length <= text2.Length + 1); if (!flag3 && name.EndsWith(text2, StringComparison.OrdinalIgnoreCase) && !IsBannedAnimationName(name)) { flag3 = string.Equals(name, "Walk", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "Idle", StringComparison.OrdinalIgnoreCase); } if (flag3) { if ((Object)(object)val == (Object)null) { val = val2; } else if (flag && val2.length < val.length - 0.01f) { val = val2; } else if (flag2 && val2.length > val.length + 0.01f) { val = val2; } else if (!flag && !flag2 && name.Length < ((Object)val).name.Length) { val = val2; } } } if ((Object)(object)val != (Object)null) { return val; } } return null; } private static bool IsBannedAnimationName(string name) { if (string.IsNullOrEmpty(name)) { return false; } if (name.IndexOf("crouch", StringComparison.OrdinalIgnoreCase) < 0) { return name.IndexOf("crawl", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } private static void EnsureAnimationClipCache() { if (_animationClips != null) { return; } _animationClips = new List(32); try { string text = null; for (int i = 0; i < AssetNames.Count; i++) { string text2 = AssetNames[i]; if (text2 != null && text2.EndsWith(".fbx", StringComparison.OrdinalIgnoreCase)) { text = text2; break; } } Object[] array = null; if (text != null) { array = InvokeBundleLoadArray("LoadAssetWithSubAssets", text); } if (array == null || array.Length == 0) { array = InvokeBundleLoadArray("LoadAllAssets", null); } CollectClips(array); if (_animationClips.Count == 0 && text != null) { GameObject val = _bundle.LoadAsset(text); if ((Object)(object)val != (Object)null) { Animation componentInChildren = val.GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null) { foreach (object item in ShyGuyReflect.Enumerate(componentInChildren)) { AnimationState val2 = (AnimationState)((item is AnimationState) ? item : null); if ((TrackedReference)(object)val2 == (TrackedReference)null) { Object val3 = (Object)((item is Object) ? item : null); if (val3 != null) { val2 = ((Il2CppObjectBase)val3).TryCast(); } } if ((Object)(object)((val2 != null) ? val2.clip : null) != (Object)null) { _animationClips.Add(val2.clip); } } } } } ShyGuyLog.Debug("Cached " + _animationClips.Count + " AnimationClip(s) from the asset bundle."); } catch (Exception ex) { ShyGuyLog.Warn("Enumerating AnimationClips from the bundle failed: " + ex.Message); } } private static Object[] InvokeBundleLoadArray(string methodName, string pathOrNull) { if ((Object)(object)_bundle == (Object)null) { return null; } try { MethodInfo[] methods = typeof(AssetBundle).GetMethods(BindingFlags.Instance | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if (!string.Equals(methodInfo.Name, methodName, StringComparison.Ordinal)) { continue; } ParameterInfo[] parameters = methodInfo.GetParameters(); object result; if (pathOrNull != null && parameters.Length == 1 && parameters[0].ParameterType == typeof(string)) { result = methodInfo.Invoke(_bundle, new object[1] { pathOrNull }); } else { if (pathOrNull != null || parameters.Length != 0) { continue; } result = methodInfo.Invoke(_bundle, null); } return ToObjectArray(result); } } catch (Exception ex) { ShyGuyLog.Debug("AssetBundle." + methodName + " via reflection failed: " + ex.Message); } return null; } private static Object[] ToObjectArray(object result) { if (result == null) { return null; } if (result is Object[] result2) { return result2; } try { List list = new List(64); foreach (object item in ShyGuyReflect.Enumerate(result)) { Object val = (Object)((item is Object) ? item : null); if (val != null) { list.Add(val); continue; } Object val2 = (Object)((item is Object) ? item : null); if (val2 != null) { Object val3 = ((Il2CppObjectBase)val2).TryCast(); if (val3 != (Object)null) { list.Add(val3); } } } return (list.Count > 0) ? list.ToArray() : null; } catch (Exception ex) { ShyGuyLog.Debug("Converting bundle load result failed: " + ex.Message); return null; } } private static void CollectClips(Object[] objects) { if (objects == null) { return; } for (int i = 0; i < objects.Length; i++) { Object obj = objects[i]; AnimationClip val = (AnimationClip)(object)((obj is AnimationClip) ? obj : null); if ((Object)(object)val == (Object)null) { Object val2 = (Object)(object)objects[i]; if (val2 != null) { val = ((Il2CppObjectBase)val2).TryCast(); } } if (!((Object)(object)val == (Object)null) && (((Object)val).name == null || !((Object)val).name.StartsWith("__preview", StringComparison.Ordinal))) { _animationClips.Add(val); } } } internal static T LoadAsset(string name) where T : Object { if (!EnsureBundle() || string.IsNullOrWhiteSpace(name)) { return default(T); } string text = name.Trim(); try { T val = _bundle.LoadAsset(text); if ((Object)(object)val != (Object)null) { return val; } } catch (Exception ex) { ShyGuyLog.Debug("LoadAsset<" + typeof(T).Name + ">('" + text + "') threw: " + ex.Message); } string text2 = FindAssetPath(text); if (text2 == null) { return default(T); } try { T val2 = _bundle.LoadAsset(text2); if ((Object)(object)val2 != (Object)null) { ShyGuyLog.Debug("Resolved '" + text + "' to bundle path '" + text2 + "'."); return val2; } } catch (Exception ex2) { ShyGuyLog.Debug("LoadAsset<" + typeof(T).Name + ">('" + text2 + "') threw: " + ex2.Message); } return default(T); } private static string FindAssetPath(string wanted) { if (AssetNames.Count == 0) { return null; } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(wanted); foreach (string assetName in AssetNames) { if (string.Equals(Path.GetFileNameWithoutExtension(assetName), fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) { return assetName; } } string text = Normalise(fileNameWithoutExtension); foreach (string assetName2 in AssetNames) { string text2 = Normalise(Path.GetFileNameWithoutExtension(assetName2)); if (text2.Contains(text) || text.Contains(text2)) { return assetName2; } } return null; } private static string Normalise(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } return value.Replace(" ", "").Replace("_", "").Replace("-", "") .ToLowerInvariant(); } internal static void ClearCaches() { _prefab = null; _prefabConfig = null; ClipCache.Clear(); } internal static void Unload() { ClearCaches(); AssetNames.Clear(); if ((Object)(object)_bundle != (Object)null) { try { _bundle.Unload(true); ShyGuyLog.Debug("Asset bundle unloaded."); } catch (Exception ex) { ShyGuyLog.Debug("Unloading the asset bundle threw: " + ex.Message); } _bundle = null; } _attempted = false; _bundleSource = "(not loaded)"; if (!string.IsNullOrEmpty(_tempBundlePath)) { ShyGuyLog.Debug("Leaving the extracted bundle cache at " + _tempBundlePath + "."); } } internal static string DescribeState() { return "Asset bundle: " + (((Object)(object)_bundle != (Object)null) ? "loaded" : "NOT loaded") + "\n Source: " + _bundleSource + "\n Prefab: " + (((Object)(object)_prefab != (Object)null) ? ((Object)_prefab).name : "not resolved") + "\n Assets: " + ((AssetNames.Count == 0) ? "none listed" : string.Join(", ", AssetNames)); } } internal static class ShyGuyNetworkSpawner { private static bool _loggedApis; internal static bool TrySpawnNetworked(Vector3 position, Quaternion rotation, Action onSpawned) { //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_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) if (!ShyGuyConfig.FusionSupportEnabled && !WarehouseHasSpawnable()) { return false; } object obj = TryCreateSpawnable("Deakins.TheShyGuy.Spawnable.TheShyGuy"); if (obj == null) { ShyGuyLog.Debug("Warehouse spawnable 'Deakins.TheShyGuy.Spawnable.TheShyGuy' not constructible; using local Instantiation."); return false; } LogApisOnce(); if (ShyGuyFusionBridge.IsInstalled && ShyGuyFusionBridge.InSession) { if (TryNetworkAssetSpawner(obj, position, rotation, onSpawned)) { ShyGuyLog.Msg("Spawned via LabFusion NetworkAssetSpawner (synced to session)."); return true; } ShyGuyLog.Debug("NetworkAssetSpawner unavailable; trying Marrow AssetSpawner."); } if (TryMarrowAssetSpawner(obj, position, rotation, onSpawned)) { ShyGuyLog.Msg("Spawned via Marrow AssetSpawner."); return true; } return false; } private static bool WarehouseHasSpawnable() { try { Type type = ShyGuyReflect.FindType("marrow.warehouse", new string[2] { "SLZ.Marrow.Warehouse.AssetWarehouse", "Il2CppSLZ.Marrow.Warehouse.AssetWarehouse" }, "AssetWarehouse"); if (type == null) { return false; } object obj = type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy)?.GetValue(null); if (obj == null) { return false; } MethodInfo methodInfo = null; MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public); foreach (MethodInfo methodInfo2 in methods) { if ((!(methodInfo2.Name != "TryGetCrate") || !(methodInfo2.Name != "TryGetScannable")) && methodInfo2.GetParameters().Length >= 2) { methodInfo = methodInfo2; break; } } if (methodInfo == null) { return false; } object obj2 = CreateBarcode("Deakins.TheShyGuy.Spawnable.TheShyGuy"); if (obj2 == null) { return false; } object[] array = new object[methodInfo.GetParameters().Length]; array[0] = obj2; if (methodInfo.Invoke(obj, array) is bool result) { return result; } return array.Length > 1 && array[1] != null; } catch (Exception ex) { ShyGuyLog.Debug("WarehouseHasSpawnable failed: " + ex.Message); return false; } } private static object TryCreateSpawnable(string barcodeId) { try { object obj = CreateBarcode(barcodeId); if (obj == null) { return null; } Type type = ShyGuyReflect.FindType("marrow.spawnablecrateref", new string[2] { "SLZ.Marrow.Warehouse.SpawnableCrateReference", "Il2CppSLZ.Marrow.Warehouse.SpawnableCrateReference" }, "SpawnableCrateReference"); if (type == null) { return null; } object obj2 = null; ConstructorInfo[] constructors = type.GetConstructors(); foreach (ConstructorInfo constructorInfo in constructors) { ParameterInfo[] parameters = constructorInfo.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType.IsInstanceOfType(obj)) { obj2 = constructorInfo.Invoke(new object[1] { obj }); break; } if (parameters.Length == 1 && parameters[0].ParameterType == typeof(string)) { obj2 = constructorInfo.Invoke(new object[1] { barcodeId }); break; } } if (obj2 == null) { obj2 = Activator.CreateInstance(type); } Type type2 = ShyGuyReflect.FindType("marrow.spawnable", new string[2] { "SLZ.Marrow.Data.Spawnable", "Il2CppSLZ.Marrow.Data.Spawnable" }, "Spawnable"); if (type2 == null) { return null; } object obj3 = Activator.CreateInstance(type2); FieldInfo field = type2.GetField("crateRef", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { field.SetValue(obj3, obj2); } else { type2.GetProperty("crateRef", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(obj3, obj2); } return obj3; } catch (Exception ex) { ShyGuyLog.Debug("TryCreateSpawnable failed: " + ex.Message); return null; } } private static object CreateBarcode(string id) { Type type = ShyGuyReflect.FindType("marrow.barcode", new string[2] { "SLZ.Marrow.Warehouse.Barcode", "Il2CppSLZ.Marrow.Warehouse.Barcode" }, "Barcode"); if (type == null) { return null; } ConstructorInfo[] constructors = type.GetConstructors(); foreach (ConstructorInfo constructorInfo in constructors) { ParameterInfo[] parameters = constructorInfo.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType == typeof(string)) { return constructorInfo.Invoke(new object[1] { id }); } } object obj = Activator.CreateInstance(type); PropertyInfo propertyInfo = type.GetProperty("ID") ?? type.GetProperty("Id"); if (propertyInfo != null && propertyInfo.CanWrite) { propertyInfo.SetValue(obj, id); } return obj; } private static bool TryNetworkAssetSpawner(object spawnable, Vector3 position, Quaternion rotation, Action onSpawned) { //IL_0043: 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) Type type = ShyGuyReflect.FindType("fusion.networkassetspawner", new string[4] { "LabFusion.Marrow.NetworkAssetSpawner", "LabFusion.Entities.NetworkAssetSpawner", "LabFusion.Network.NetworkAssetSpawner", "LabFusion.Bonelab.NetworkAssetSpawner" }, "NetworkAssetSpawner"); if (type == null) { return false; } return TryInvokeSpawnerType(type, spawnable, position, rotation, onSpawned); } private static bool TryMarrowAssetSpawner(object spawnable, Vector3 position, Quaternion rotation, Action onSpawned) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) Type type = ShyGuyReflect.FindType("marrow.assetspawner", new string[2] { "SLZ.Marrow.Pool.AssetSpawner", "Il2CppSLZ.Marrow.Pool.AssetSpawner" }, "AssetSpawner"); if (type == null) { return false; } MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if ((!(methodInfo.Name != "Spawn") || !(methodInfo.Name != "SpawnAsync")) && TryInvokeSpawnMethod(methodInfo, null, spawnable, position, rotation, onSpawned)) { return true; } } return false; } private static bool TryInvokeSpawnerType(Type type, object spawnable, Vector3 position, Quaternion rotation, Action onSpawned) { //IL_0055: 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_0195: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) Type[] nestedTypes = type.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic); MethodInfo[] methods; foreach (Type type2 in nestedTypes) { if (type2.Name.IndexOf("Spawn", StringComparison.OrdinalIgnoreCase) < 0 && type2.Name.IndexOf("Request", StringComparison.OrdinalIgnoreCase) < 0 && type2.Name.IndexOf("Info", StringComparison.OrdinalIgnoreCase) < 0) { continue; } object obj = CreateSpawnInfo(type2, spawnable, position, rotation, onSpawned); if (obj == null) { continue; } methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "Spawn") || !(methodInfo.Name != "SpawnAsync") || !(methodInfo.Name != "SpawnSpawnable") || !(methodInfo.Name != "SpawnSpawnableAsync") || !(methodInfo.Name != "NetworkedSpawnSpawnable")) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType.IsInstanceOfType(obj)) { methodInfo.Invoke(null, new object[1] { obj }); return true; } } } } methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo2 in methods) { if ((!(methodInfo2.Name != "Spawn") || !(methodInfo2.Name != "SpawnAsync") || !(methodInfo2.Name != "SpawnSpawnable") || !(methodInfo2.Name != "SpawnSpawnableAsync")) && TryInvokeSpawnMethod(methodInfo2, null, spawnable, position, rotation, onSpawned)) { return true; } } return false; } private static object CreateSpawnInfo(Type infoType, object spawnable, Vector3 position, Quaternion rotation, Action _) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0056: 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) try { object? obj = (infoType.IsValueType ? Activator.CreateInstance(infoType) : Activator.CreateInstance(infoType)); SetMember(obj, "spawnable", spawnable); SetMember(obj, "Spawnable", spawnable); SetMember(obj, "position", position); SetMember(obj, "Position", position); SetMember(obj, "rotation", rotation); SetMember(obj, "Rotation", rotation); return obj; } catch (Exception ex) { ShyGuyLog.Debug("CreateSpawnInfo(" + infoType.Name + ") failed: " + ex.Message); return null; } } private static bool TryInvokeSpawnMethod(MethodInfo method, object target, object spawnable, Vector3 position, Quaternion rotation, Action onSpawned) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) try { ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length < 3) { return false; } object[] array = new object[parameters.Length]; bool flag = false; bool flag2 = false; bool flag3 = false; for (int i = 0; i < parameters.Length; i++) { Type parameterType = parameters[i].ParameterType; if (!flag && (parameterType.IsInstanceOfType(spawnable) || parameterType.Name.IndexOf("Spawnable", StringComparison.OrdinalIgnoreCase) >= 0)) { array[i] = spawnable; flag = true; } else if (!flag2 && parameterType == typeof(Vector3)) { array[i] = position; flag2 = true; } else if (!flag3 && parameterType == typeof(Quaternion)) { array[i] = rotation; flag3 = true; } else if (parameterType == typeof(bool)) { array[i] = true; } else if (typeof(Delegate).IsAssignableFrom(parameterType) && onSpawned != null) { try { array[i] = Delegate.CreateDelegate(parameterType, onSpawned.Target, onSpawned.Method, throwOnBindFailure: false) ?? onSpawned; } catch { array[i] = null; } } else if (parameters[i].HasDefaultValue) { array[i] = parameters[i].DefaultValue; } else if (parameterType.IsValueType) { array[i] = Activator.CreateInstance(parameterType); } else { array[i] = null; } } if (!flag || !flag2) { return false; } method.Invoke(target, array); if (onSpawned != null) { MelonCoroutines.Start(DeferredBinderScan()); } return true; } catch (Exception ex) { ShyGuyLog.Debug("TryInvokeSpawnMethod " + method.Name + " failed: " + ex.Message); return false; } } private static IEnumerator DeferredBinderScan() { for (int i = 0; i < 30; i++) { yield return null; ShyGuySpawnableBinder.ScanOnce(); } } private static void SetMember(object target, string name, object value) { FieldInfo field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && (value == null || field.FieldType.IsInstanceOfType(value) || IsAssignableVectorish(field.FieldType, value))) { field.SetValue(target, value); return; } PropertyInfo property = target.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && (value == null || property.PropertyType.IsInstanceOfType(value) || IsAssignableVectorish(property.PropertyType, value))) { property.SetValue(target, value); } } private static bool IsAssignableVectorish(Type targetType, object value) { if (!(targetType == typeof(Vector3)) || !(value is Vector3)) { if (targetType == typeof(Quaternion)) { return value is Quaternion; } return false; } return true; } private static void LogApisOnce() { if (!_loggedApis && ShyGuyConfig.DebugLogging) { _loggedApis = true; Type type = ShyGuyReflect.FindType("fusion.networkassetspawner", null, "NetworkAssetSpawner"); Type type2 = ShyGuyReflect.FindType("marrow.assetspawner", null, "AssetSpawner"); ShyGuyLog.Debug("NetworkAssetSpawner: " + ((type != null) ? type.FullName : "missing")); ShyGuyLog.Debug("AssetSpawner: " + ((type2 != null) ? type2.FullName : "missing")); } } } internal static class ShyGuySpawnableBinder { internal const string SpawnableBarcode = "Deakins.TheShyGuy.Spawnable.TheShyGuy"; internal const string PalletBarcode = "Deakins.TheShyGuy"; private static readonly string[] NameHints = new string[4] { "TheShyGuy", "The Shy Guy", "TheShyGuySpawnable", "TheShyGuySpawnableMarker" }; private static float _nextScan; private const float ScanInterval = 0.35f; internal static void Tick() { if (!(Time.unscaledTime < _nextScan)) { _nextScan = Time.unscaledTime + 0.35f; ShyGuyLog.Guard("spawnable binder", ScanOnce); } } internal static void ScanOnce() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) List list = new List(32); try { for (int i = 0; i < SceneManager.sceneCount; i++) { Scene sceneAt = SceneManager.GetSceneAt(i); if (!((Scene)(ref sceneAt)).IsValid() || !((Scene)(ref sceneAt)).isLoaded) { continue; } GameObject[] array = Il2CppArrayBase.op_Implicit((Il2CppArrayBase)(object)((Scene)(ref sceneAt)).GetRootGameObjects()); if (array == null) { continue; } for (int j = 0; j < array.Length; j++) { if ((Object)(object)array[j] != (Object)null) { list.Add(array[j]); } } } } catch (Exception ex) { ShyGuyLog.Debug("Spawnable binder scene walk failed: " + ex.Message); return; } for (int k = 0; k < list.Count; k++) { ConsiderHierarchy(list[k].transform, 0); } } private static void ConsiderHierarchy(Transform root, int depth) { if (!((Object)(object)root == (Object)null) && depth <= 10) { if (LooksLikeShyGuy(root)) { TryBind(((Component)root).gameObject); } for (int i = 0; i < root.childCount; i++) { ConsiderHierarchy(root.GetChild(i), depth + 1); } } } private static bool LooksLikeShyGuy(Transform t) { if ((Object)(object)t == (Object)null) { return false; } string name = ((Object)t).name; if (string.IsNullOrEmpty(name)) { return false; } if (name.IndexOf("Deakins.TheShyGuy.Spawnable.TheShyGuy", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (name.IndexOf("Deakins.TheShyGuy", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (name.IndexOf("TheShyGuy", StringComparison.OrdinalIgnoreCase) >= 0 && name.IndexOf("Spawnable", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } for (int i = 0; i < NameHints.Length; i++) { if (string.Equals(name, NameHints[i], StringComparison.OrdinalIgnoreCase)) { return true; } if (name.IndexOf(NameHints[i], StringComparison.OrdinalIgnoreCase) >= 0 && NameHints[i].Length >= 8) { return true; } } return false; } private static void TryBind(GameObject go) { if ((Object)(object)go == (Object)null) { return; } GameObject val = go; if (string.Equals(((Object)go).name, "TheShyGuySpawnableMarker", StringComparison.OrdinalIgnoreCase) && (Object)(object)go.transform.parent != (Object)null) { val = ((Component)go.transform.parent).gameObject; } try { if ((Object)(object)val.GetComponent() != (Object)null || (Object)(object)val.GetComponentInParent() != (Object)null) { return; } } catch { } if (TheShyGuyBehaviour.All.Count < ShyGuyConfig.MaxInstances && (Object)(object)ShyGuySpawner.AttachTo(val) != (Object)null) { ShyGuyLog.Msg("Bound spawn-gun / Fusion Shy Guy instance '" + ((Object)val).name + "'."); } } } internal static class ShyGuySpawner { private static readonly HashSet AdoptedInstanceIds = new HashSet(); private static readonly string[] PlacedNames = new string[3] { "TheShyGuy", "The Shy Guy", "TheShyGuy.prefab" }; internal static int LiveCount => TheShyGuyBehaviour.All.Count; internal static TheShyGuyBehaviour SpawnInFrontOfLocalPlayer() { //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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_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_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009a: 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_0069: Unknown result type (might be due to invalid IL or missing references) ShyGuyPlayerFinder playerFinder = TheShyGuyMod.PlayerFinder; ShyGuyTarget shyGuyTarget = playerFinder?.GetLocalPlayer(); if (shyGuyTarget == null) { playerFinder?.ForceRescan(); shyGuyTarget = playerFinder?.GetLocalPlayer(); } if (shyGuyTarget == null) { ShyGuyLog.Error("Cannot spawn: the local player could not be found. Load into a level first - spawning does not work from the main menu."); return null; } Vector3 eyePosition = shyGuyTarget.EyePosition; Vector3 val = shyGuyTarget.LookDirection; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { val = Vector3.forward; } ((Vector3)(ref val)).Normalize(); Vector3 position = SnapToGround(eyePosition + val * ShyGuyConfig.SpawnDistance, eyePosition); Quaternion rotation = Quaternion.LookRotation(-val, Vector3.up); return SpawnAt(position, rotation); } private static Vector3 SnapToGround(Vector3 position, Vector3 referenceEyePosition) { //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_004c: 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_005a: 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_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_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) if (ShyGuyPhysics.Raycast(position + Vector3.up * 1.5f, Vector3.down, 12f, -1, out var nearest)) { return ((RaycastHit)(ref nearest)).point + Vector3.up * 0.05f; } float num = 1.6f; return new Vector3(position.x, referenceEyePosition.y - num, position.z); } internal static TheShyGuyBehaviour SpawnAt(Vector3 position, Quaternion rotation) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) EnforceInstanceCap(makingRoomForOne: true); bool flag = false; try { flag = ShyGuyNetworkSpawner.TrySpawnNetworked(position, rotation, delegate(GameObject go) { if (!((Object)(object)go == (Object)null)) { ((Object)go).name = "TheShyGuy"; Prepare(go); } }); } catch (Exception ex) { ShyGuyLog.Debug("Networked spawn threw; falling back to local Instantiation: " + ex.Message); flag = false; } if (flag) { ShyGuySpawnableBinder.ScanOnce(); return null; } GameObject prefab = ShyGuyAssetLoader.GetPrefab(); if ((Object)(object)prefab == (Object)null) { ShyGuyLog.Error("Cannot spawn: no prefab is available. Either embed theshyguy.bundle in the DLL, place it in " + ShyGuyAssetLoader.UserDataFolder + ", or place a TheShyGuy prefab in the level for the mod to adopt. See the asset bundle build instructions."); return null; } GameObject val; try { val = Object.Instantiate(prefab, position, rotation); } catch (Exception ex2) { ShyGuyLog.Error("Instantiating the prefab failed.", ex2); return null; } if ((Object)(object)val == (Object)null) { ShyGuyLog.Error("Instantiating the prefab returned null."); return null; } ((Object)val).name = "TheShyGuy"; val.SetActive(true); TheShyGuyBehaviour theShyGuyBehaviour = Prepare(val); if ((Object)(object)theShyGuyBehaviour == (Object)null) { ShyGuyLog.Error("The prefab was instantiated but the behaviour could not be attached; destroying it again."); Object.Destroy((Object)(object)val); return null; } if (ShyGuyFusionBridge.IsInstalled && ShyGuyFusionBridge.InSession) { ShyGuyLog.Warn("Local Instantiation in a Fusion session is NOT synced to other players. Install the Deakins.TheShyGuy pallet on all clients and spawn from the spawn gun, or ensure NetworkAssetSpawner can see barcode Deakins.TheShyGuy.Spawnable.TheShyGuy."); } ShyGuyLog.Msg("Spawned The Shy Guy at " + ((Vector3)(ref position)).ToString("F1") + "."); return theShyGuyBehaviour; } internal static TheShyGuyBehaviour AttachTo(GameObject target) { if ((Object)(object)target == (Object)null) { ShyGuyLog.Warn("AttachTo was given a null GameObject."); return null; } return Prepare(target); } private static TheShyGuyBehaviour Prepare(GameObject instance) { if ((Object)(object)instance == (Object)null) { return null; } TheShyGuyBehaviour theShyGuyBehaviour = null; try { theShyGuyBehaviour = instance.GetComponent(); } catch (Exception ex) { ShyGuyLog.Debug("GetComponent threw: " + ex.Message); } if ((Object)(object)theShyGuyBehaviour != (Object)null) { ShyGuyLog.Debug("'" + ((Object)instance).name + "' already has the behaviour; not adding a second one."); return theShyGuyBehaviour; } try { theShyGuyBehaviour = instance.AddComponent(); } catch (Exception ex2) { ShyGuyLog.Error("AddComponent failed. This normally means the type was not registered with IL2CPP - check that MelonLoader processed the [RegisterTypeInIl2Cpp] attribute at load time.", ex2); return null; } if ((Object)(object)theShyGuyBehaviour == (Object)null) { ShyGuyLog.Error("AddComponent returned null."); return null; } try { theShyGuyBehaviour.Initialise(ShyGuyAssetLoader.GetPrefabConfig()); return theShyGuyBehaviour; } catch (Exception ex3) { ShyGuyLog.Error("Initialising the behaviour failed.", ex3); return null; } } internal static int AdoptPlacedInstances() { if (!ShyGuyConfig.AutoFindPlacedPrefab) { return 0; } int num = 0; string[] placedNames = PlacedNames; foreach (string text in placedNames) { GameObject val; try { val = GameObject.Find(text); } catch (Exception ex) { ShyGuyLog.Debug("GameObject.Find('" + text + "') threw: " + ex.Message); continue; } if ((Object)(object)val == (Object)null) { continue; } int instanceID = ((Object)val).GetInstanceID(); if (AdoptedInstanceIds.Contains(instanceID)) { continue; } TheShyGuyBehaviour theShyGuyBehaviour = null; try { theShyGuyBehaviour = val.GetComponent(); } catch { } if ((Object)(object)theShyGuyBehaviour != (Object)null) { AdoptedInstanceIds.Add(instanceID); continue; } ShyGuyLog.Msg("Found a '" + text + "' placed in this level; adopting it."); if ((Object)(object)Prepare(val) != (Object)null) { AdoptedInstanceIds.Add(instanceID); num++; } } return num; } internal static void DespawnAll() { List list = new List(TheShyGuyBehaviour.All); int num = 0; int num2 = 0; foreach (TheShyGuyBehaviour item in list) { if ((Object)(object)item == (Object)null) { continue; } GameObject val = null; try { val = ((Component)item).gameObject; } catch { } if ((Object)(object)val == (Object)null) { continue; } bool flag = AdoptedInstanceIds.Contains(((Object)val).GetInstanceID()); try { if (flag) { Object.Destroy((Object)(object)item); num2++; } else { Object.Destroy((Object)(object)val); num++; } } catch (Exception ex) { ShyGuyLog.Debug("Despawning an instance threw: " + ex.Message); } } AdoptedInstanceIds.Clear(); TheShyGuyBehaviour.PurgeDestroyedInstances(); if (num + num2 > 0) { ShyGuyLog.Msg("Despawned " + num + " spawned Shy Guy(s)" + ((num2 > 0) ? (" and detached from " + num2 + " level-placed one(s).") : ".")); } else { ShyGuyLog.Msg("No Shy Guy to despawn."); } } private static void EnforceInstanceCap(bool makingRoomForOne) { int maxInstances = ShyGuyConfig.MaxInstances; int num = (makingRoomForOne ? (maxInstances - 1) : maxInstances); TheShyGuyBehaviour.PurgeDestroyedInstances(); List list = new List(TheShyGuyBehaviour.All); if (list.Count <= num) { return; } int num2 = list.Count - num; ShyGuyLog.Msg("Instance cap is " + maxInstances + "; removing " + num2 + " older Shy Guy(s)."); for (int i = 0; i < num2 && i < list.Count; i++) { TheShyGuyBehaviour theShyGuyBehaviour = list[i]; if ((Object)(object)theShyGuyBehaviour == (Object)null) { continue; } try { GameObject gameObject = ((Component)theShyGuyBehaviour).gameObject; if (!((Object)(object)gameObject == (Object)null)) { if (AdoptedInstanceIds.Contains(((Object)gameObject).GetInstanceID())) { Object.Destroy((Object)(object)theShyGuyBehaviour); } else { Object.Destroy((Object)(object)gameObject); } } } catch (Exception ex) { ShyGuyLog.Debug("Removing an old instance threw: " + ex.Message); } } TheShyGuyBehaviour.PurgeDestroyedInstances(); } internal static void OnLevelUnloading() { AdoptedInstanceIds.Clear(); TheShyGuyBehaviour.DestroyAll(); } internal static void OnLevelLoaded() { TheShyGuyBehaviour.PurgeDestroyedInstances(); AdoptedInstanceIds.Clear(); int num = AdoptPlacedInstances(); if (ShyGuyConfig.AutoSpawnOnLevelLoad && num == 0) { ShyGuyLog.Msg("AutoSpawnOnLevelLoad is on; spawning one."); SpawnInFrontOfLocalPlayer(); } } internal static string DescribeState() { return "Spawner: " + LiveCount + " live instance(s), cap " + ShyGuyConfig.MaxInstances + ", " + AdoptedInstanceIds.Count + " adopted from the level.\n" + ShyGuyAssetLoader.DescribeState(); } } internal sealed class ShyGuyFaceDetector { private readonly Transform _creatureRoot; private int _occlusionMask; private string _occlusionMaskSource; private bool _maskValid; private const int HitBufferSize = 16; private readonly Il2CppStructArray _hits = new Il2CppStructArray(16L); private static readonly Comparison ByDistance = (RaycastHit a, RaycastHit b) => ((RaycastHit)(ref a)).distance.CompareTo(((RaycastHit)(ref b)).distance); private readonly List _sortedHits = new List(16); internal string LastRejectReason { get; private set; } = "(nothing evaluated yet)"; internal ShyGuyFaceDetector(Transform creatureRoot) { _creatureRoot = creatureRoot; } internal ShyGuyTarget Evaluate(List candidates, Transform facePoint, float deltaTime) { if ((Object)(object)facePoint == (Object)null) { ShyGuyLog.WarnThrottled("detector:noface", "The face detection transform is missing, so gaze detection is disabled. Assign FaceDetectionPoint on the prefab (see the prefab setup instructions) or the creature will never rage.", 60f); LastRejectReason = "no face transform"; return null; } if (candidates == null || candidates.Count == 0) { LastRejectReason = "no living players found"; return null; } ShyGuyTarget shyGuyTarget = null; float num = float.MaxValue; float requiredLookDuration = ShyGuyConfig.RequiredLookDuration; for (int i = 0; i < candidates.Count; i++) { ShyGuyTarget shyGuyTarget2 = candidates[i]; if (shyGuyTarget2 == null || !shyGuyTarget2.IsAlive) { continue; } if (IsLookingAtFace(shyGuyTarget2, facePoint, out var distance, out var reason)) { shyGuyTarget2.LookTime += deltaTime; shyGuyTarget2.LastLookTime = Time.unscaledTime; if (shyGuyTarget2.LookTime >= requiredLookDuration && distance < num) { shyGuyTarget = shyGuyTarget2; num = distance; } } else { if (shyGuyTarget2.LookTime > 0f) { shyGuyTarget2.LookTime = Mathf.Max(0f, shyGuyTarget2.LookTime - deltaTime * ShyGuyConfig.LookMemoryDecay); } if (candidates.Count == 1) { LastRejectReason = reason; } } } if (shyGuyTarget != null) { LastRejectReason = "(triggered)"; ShyGuyLog.Debug("Gaze threshold reached by " + shyGuyTarget.DisplayName + " at " + num.ToString("F1") + "m after " + shyGuyTarget.LookTime.ToString("F2") + "s of looking."); } return shyGuyTarget; } internal bool IsLookingAtFace(ShyGuyTarget target, Transform facePoint, out float distance, out string reason) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_003a: 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_00d2: 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_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00de: 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_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) distance = float.MaxValue; reason = "unknown"; if (target == null || (Object)(object)facePoint == (Object)null) { reason = "null target or face"; return false; } Vector3 position = facePoint.position; Vector3 eyePosition = target.EyePosition; Vector3 val = position - eyePosition; distance = ((Vector3)(ref val)).magnitude; if (distance > ShyGuyConfig.DetectionDistance) { reason = "too far (" + distance.ToString("F1") + "m > " + ShyGuyConfig.DetectionDistance + "m)"; return false; } if (distance < ShyGuyConfig.MinimumDetectionDistance) { reason = "too close (" + distance.ToString("F2") + "m, inside the self-trigger guard)"; return false; } if (distance < 0.0001f) { reason = "degenerate distance"; return false; } Vector3 val2 = val / distance; Vector3 lookDirection = target.LookDirection; if (((Vector3)(ref lookDirection)).sqrMagnitude < 0.0001f) { reason = "player look direction is zero"; return false; } float num = Vector3.Angle(((Vector3)(ref lookDirection)).normalized, val2); float num2 = ShyGuyConfig.DetectionAngle * 0.5f; if (num > num2) { reason = "outside view cone (" + num.ToString("F0") + " deg > " + num2.ToString("F0") + " deg)"; return false; } Vector3 forward = facePoint.forward; if (((Vector3)(ref forward)).sqrMagnitude > 0.0001f) { float num3 = Vector3.Angle(((Vector3)(ref forward)).normalized, -val2); float num4 = ShyGuyConfig.FaceFacingAngle * 0.5f; if (num3 > num4) { reason = "looking at the back/side of the head (" + num3.ToString("F0") + " deg off the face axis, limit " + num4.ToString("F0") + " deg)"; return false; } } else { ShyGuyLog.WarnOnce("detector:facefwd", "The face detection transform has a zero forward vector, so the back-of-head test is being skipped. Rotate FaceDetectionPoint so its blue Z axis points out of the face."); } if (ShyGuyConfig.FaceVisibilityRaycast) { if (!IsFaceVisible(eyePosition, position, distance, out var blockedBy, out var hitOffsetFromFace)) { reason = blockedBy; return false; } if (hitOffsetFromFace > ShyGuyConfig.FaceHitRadius) { reason = "line of sight landed on the body, " + hitOffsetFromFace.ToString("F2") + "m from the face (limit " + ShyGuyConfig.FaceHitRadius.ToString("F2") + "m)"; return false; } } reason = "looking at the face"; return true; } private bool IsFaceVisible(Vector3 eyePos, Vector3 facePos, float distance, out string blockedBy, out float hitOffsetFromFace) { //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_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_0032: 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_0105: 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_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) blockedBy = "line of sight blocked"; hitOffsetFromFace = float.MaxValue; Vector3 val = (facePos - eyePos) / Mathf.Max(distance, 0.0001f); float num = distance + 0.5f; int num2; try { num2 = Physics.RaycastNonAlloc(new Ray(eyePos, val), _hits, num, GetOcclusionMask(), (QueryTriggerInteraction)1); } catch (Exception ex) { ShyGuyLog.WarnThrottled("detector:raycast", "Face visibility raycast failed: " + ex.Message + ". Treating the face as visible for now."); blockedBy = "raycast failed"; hitOffsetFromFace = 0f; return true; } if (num2 <= 0) { ShyGuyLog.DebugThrottled("detector:nohits", "The visibility raycast hit nothing, including the creature itself. Check that the creature has colliders and that their layer is not listed in OcclusionIgnoreLayers.", 20f); blockedBy = "raycast hit nothing (creature has no colliders on a traced layer)"; return false; } _sortedHits.Clear(); for (int i = 0; i < num2 && i < 16; i++) { _sortedHits.Add(((Il2CppArrayBase)(object)_hits)[i]); } _sortedHits.Sort(ByDistance); for (int j = 0; j < _sortedHits.Count; j++) { RaycastHit val2 = _sortedHits[j]; Collider collider = ((RaycastHit)(ref val2)).collider; if ((Object)(object)collider == (Object)null) { continue; } Transform transform = ((Component)collider).transform; if (!((Object)(object)transform == (Object)null)) { if (ShyGuyHierarchy.IsPartOf(_creatureRoot, transform)) { hitOffsetFromFace = Vector3.Distance(((RaycastHit)(ref val2)).point, facePos); blockedBy = "visible"; return true; } if (!IsPartOfAnyPlayerRig(transform)) { blockedBy = "line of sight blocked by '" + ((Object)collider).name + "' at " + ((RaycastHit)(ref val2)).distance.ToString("F1") + "m"; return false; } } } blockedBy = "creature not reached by the visibility ray"; return false; } private static bool IsPartOfAnyPlayerRig(Transform candidate) { ShyGuyPlayerFinder playerFinder = TheShyGuyMod.PlayerFinder; if (playerFinder == null) { return false; } List livingPlayers = playerFinder.GetLivingPlayers(); for (int i = 0; i < livingPlayers.Count; i++) { GameObject val = livingPlayers[i]?.RigRoot; if (!((Object)(object)val == (Object)null) && ShyGuyHierarchy.IsPartOf(val.transform, candidate)) { return true; } } return false; } internal int GetOcclusionMask() { string text = ShyGuyConfig.OcclusionIgnoreLayers ?? string.Empty; if (_maskValid && string.Equals(text, _occlusionMaskSource, StringComparison.Ordinal)) { return _occlusionMask; } int num = -1; List list = new List(); string[] array = text.Split(','); for (int i = 0; i < array.Length; i++) { string text2 = array[i]?.Trim(); if (!string.IsNullOrEmpty(text2)) { int num2; try { num2 = LayerMask.NameToLayer(text2); } catch { continue; } if (num2 >= 0 && num2 <= 31) { num &= ~(1 << num2); list.Add(text2 + "(" + num2 + ")"); } } } _occlusionMask = num; _occlusionMaskSource = text; _maskValid = true; ShyGuyLog.Debug("Occlusion mask rebuilt; excluding " + ((list.Count == 0) ? "nothing" : string.Join(", ", list)) + "."); return _occlusionMask; } internal void InvalidateMask() { _maskValid = false; } internal string DescribeGaze(List candidates, Transform facePoint) { if ((Object)(object)facePoint == (Object)null) { return "No face transform assigned."; } if (candidates == null || candidates.Count == 0) { return "No living players to evaluate."; } List list = new List(); foreach (ShyGuyTarget candidate in candidates) { if (candidate != null) { IsLookingAtFace(candidate, facePoint, out var distance, out var reason); list.Add(" " + candidate.DisplayName + ": " + reason + " | dist=" + distance.ToString("F1") + "m | lookTime=" + candidate.LookTime.ToString("F2") + "s/" + ShyGuyConfig.RequiredLookDuration.ToString("F2") + "s"); } } return string.Join("\n", list); } } internal sealed class ShyGuyPlayerFinder { private readonly Dictionary _targets = new Dictionary(StringComparer.Ordinal); private readonly List _living = new List(8); private readonly List _pruneScratch = new List(8); private static Type _rigManagerType; private static bool _resolvedRigType; private static Type _boneLibPlayerType; private static bool _resolvedBoneLib; private float _lastScan = -999f; private int _livingFrame = -1; private const float ScanInterval = 0.25f; private const float StaleTargetTimeout = 3f; internal int TrackedCount => _targets.Count; internal event Action TargetLost; internal event Action TargetRespawned; internal void Clear() { _targets.Clear(); _living.Clear(); _livingFrame = -1; _lastScan = -999f; this.TargetLost = null; this.TargetRespawned = null; } internal static void InvalidateTypeCache() { _resolvedRigType = false; _rigManagerType = null; _resolvedBoneLib = false; _boneLibPlayerType = null; } internal List GetLivingPlayers() { int frameCount = Time.frameCount; if (_livingFrame == frameCount) { return _living; } _livingFrame = frameCount; float unscaledTime = Time.unscaledTime; if (unscaledTime - _lastScan >= 0.25f) { _lastScan = unscaledTime; ShyGuyLog.Guard("player scan", Scan); } Prune(unscaledTime); _living.Clear(); foreach (ShyGuyTarget value in _targets.Values) { if (value.IsAlive) { _living.Add(value); } } return _living; } internal List ForceRescan() { _livingFrame = -1; _lastScan = -999f; return GetLivingPlayers(); } internal ShyGuyTarget GetLocalPlayer() { foreach (ShyGuyTarget value in _targets.Values) { if (value.IsLocal && value.IsValid) { return value; } } return null; } internal ShyGuyTarget Find(string key) { if (string.IsNullOrEmpty(key)) { return null; } if (!_targets.TryGetValue(key, out var value)) { return null; } return value; } private void Scan() { List list = FindRigRoots(); if (list.Count == 0) { ShyGuyLog.DebugThrottled("finder:norigs", "No player rigs found in the loaded scene. If this persists during gameplay, the RigManager type could not be resolved - see the troubleshooting section on invalid player rigs.", 15f); return; } for (int num = list.Count - 1; num >= 0; num--) { for (int i = 0; i < list.Count; i++) { if (num != i && !((Object)(object)list[num] == (Object)null) && !((Object)(object)list[i] == (Object)null) && ShyGuyHierarchy.IsPartOf(list[i].transform, list[num].transform) && (Object)(object)list[num] != (Object)(object)list[i]) { list.RemoveAt(num); break; } } } GameObject localRig = ResolveLocalRig(list); foreach (GameObject rig in list) { if (!((Object)(object)rig == (Object)null)) { ShyGuyLog.Guard("adopt rig", delegate { AdoptRig(rig, (Object)(object)rig == (Object)(object)localRig); }); } } } private List FindRigRoots() { List list = new List(4); GameObject val = TryGetBoneLibRig(); if ((Object)(object)val != (Object)null) { list.Add(val); } Type type = ResolveRigManagerType(); if (type != null) { foreach (GameObject item in FindAllOfType(type)) { if ((Object)(object)item != (Object)null && !list.Contains(item)) { list.Add(item); } } } else { ShyGuyLog.WarnOnce("finder:norigtype", "Could not locate the BONELAB RigManager type by reflection. Player detection will fall back to cameras only, which finds the local player but not Fusion peers. This normally means a BONELAB update moved the type - see the troubleshooting section."); GameObject val2 = TryGetRigFromCamera(); if ((Object)(object)val2 != (Object)null && !list.Contains(val2)) { list.Add(val2); } } return list; } private static IEnumerable FindAllOfType(Type componentType) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) List list = new List(4); try { Il2CppReferenceArray val = Resources.FindObjectsOfTypeAll(Il2CppType.From(componentType)); if (val == null) { return list; } for (int i = 0; i < ((Il2CppArrayBase)(object)val).Length; i++) { Object val2 = ((Il2CppArrayBase)(object)val)[i]; if (val2 == (Object)null) { continue; } Component val3 = ((Il2CppObjectBase)val2).TryCast(); GameObject val4 = (((Object)(object)val3 != (Object)null) ? val3.gameObject : ((Il2CppObjectBase)val2).TryCast()); if (!((Object)(object)val4 == (Object)null)) { Scene scene = val4.scene; if (((Scene)(ref scene)).IsValid()) { list.Add(val4); } } } } catch (Exception ex) { ShyGuyLog.DebugThrottled("finder:findall", "Resources.FindObjectsOfTypeAll(" + componentType.Name + ") failed: " + ex.Message, 30f); } return list; } private static Type ResolveRigManagerType() { if (_resolvedRigType) { return _rigManagerType; } _resolvedRigType = true; _rigManagerType = ShyGuyReflect.FindType("Bonelab.RigManager", new string[4] { "Il2CppSLZ.Marrow.RigManager", "Il2CppSLZ.Rig.RigManager", "SLZ.Marrow.RigManager", "SLZ.Rig.RigManager" }, "RigManager"); return _rigManagerType; } private static GameObject TryGetBoneLibRig() { if (!_resolvedBoneLib) { _resolvedBoneLib = true; if (ShyGuyReflect.HasAssembly("BoneLib")) { _boneLibPlayerType = ShyGuyReflect.FindType("BoneLib.Player", new string[1] { "BoneLib.Player" }); ShyGuyLog.MsgOnce("bonelib:present", (_boneLibPlayerType != null) ? "BoneLib detected - using it to resolve the local player rig." : "BoneLib is loaded but BoneLib.Player was not found; falling back to rig scanning."); } else { ShyGuyLog.MsgOnce("bonelib:absent", "BoneLib not installed - resolving the local player by rig scanning instead. This works, but installing BoneLib is recommended."); } } if (_boneLibPlayerType == null) { return null; } try { bool found; object obj = ShyGuyReflect.GetStatic(_boneLibPlayerType, new string[2] { "RigManager", "rigManager" }, out found); if (!found || obj == null) { return null; } Component val = (Component)((obj is Component) ? obj : null); if (val != null) { return ((Object)(object)val != (Object)null) ? val.gameObject : null; } GameObject val2 = (GameObject)((obj is GameObject) ? obj : null); if (val2 != null) { return ((Object)(object)val2 != (Object)null) ? val2 : null; } } catch (Exception ex) { ShyGuyLog.DebugThrottled("finder:bonelib", "BoneLib rig lookup failed: " + ex.Message, 30f); } return null; } private static GameObject TryGetRigFromCamera() { Camera val = Camera.main; if ((Object)(object)val == (Object)null) { Camera[] array = Il2CppArrayBase.op_Implicit((Il2CppArrayBase)(object)Camera.allCameras); if (array != null && array.Length != 0) { val = array[0]; } } if ((Object)(object)val == (Object)null) { return null; } Transform val2 = ((Component)val).transform; for (int i = 0; i < 4; i++) { if (!((Object)(object)val2.parent != (Object)null)) { break; } val2 = val2.parent; } return ((Component)val2).gameObject; } private GameObject ResolveLocalRig(List rigs) { GameObject val = TryGetBoneLibRig(); if ((Object)(object)val != (Object)null) { return val; } foreach (GameObject rig in rigs) { if (!((Object)(object)rig == (Object)null) && (Object)(object)ShyGuyHierarchy.FindCamera(rig.transform) != (Object)null) { return rig; } } if (rigs.Count != 1) { return null; } return rigs[0]; } private void AdoptRig(GameObject rig, bool isLocal) { Camera camera; Transform val = ResolveHead(rig, isLocal, out camera); if ((Object)(object)val == (Object)null) { ShyGuyLog.DebugThrottled("finder:nohead:" + ((Object)rig).GetInstanceID(), "Rig '" + ((Object)rig).name + "' has no resolvable head transform; skipping it.", 20f); return; } int num = (ShyGuyFusionBridge.IsInstalled ? ShyGuyFusionBridge.GetSmallIdForRig(rig) : (-1)); string key = (isLocal ? "local" : ((num >= 0) ? ("fusion:" + num) : ("rig:" + ((Object)rig).GetInstanceID()))); string displayName = (isLocal ? "Local player" : ((num >= 0) ? (ShyGuyFusionBridge.GetDisplayName(num) ?? ("Player " + num)) : ((Object)rig).name)); if (_targets.TryGetValue(key, out var value)) { if (value.AdoptRig(rig, val, camera, displayName)) { value.ClearLookState(); ShyGuyLog.Debug("Rig for " + value.DisplayName + " was rebuilt; treating as respawn."); this.TargetRespawned?.Invoke(value); } } else { ShyGuyTarget shyGuyTarget = new ShyGuyTarget(key, displayName, isLocal, num, rig, val, camera); _targets[key] = shyGuyTarget; ShyGuyLog.Debug("Tracking new player target: " + shyGuyTarget); } } private static Transform ResolveHead(GameObject rig, bool isLocal, out Camera camera) { camera = ShyGuyHierarchy.FindCamera(rig.transform); if ((Object)(object)camera != (Object)null && (Object)(object)((Component)camera).transform != (Object)null) { return ((Component)camera).transform; } Transform val = TryGetHeadFromRigComponent(rig); if ((Object)(object)val != (Object)null) { return val; } Transform val2 = ShyGuyHierarchy.FindFirstByNames(rig.transform, new string[5] { "Head", "head", "PlayerHead", "HeadPosition", "Neck" }); if ((Object)(object)val2 != (Object)null) { ShyGuyLog.DebugThrottled("finder:headbyname:" + ((Object)rig).GetInstanceID(), "Resolved the head of rig '" + ((Object)rig).name + "' by name fallback ('" + ((Object)val2).name + "').", 30f); return val2; } if (isLocal && (Object)(object)Camera.main != (Object)null) { camera = Camera.main; return ((Component)Camera.main).transform; } return null; } private static Transform TryGetHeadFromRigComponent(GameObject rig) { Type type = ResolveRigManagerType(); if (type == null) { return null; } Component val = null; try { val = rig.GetComponent(Il2CppType.From(type)); } catch (Exception ex) { ShyGuyLog.Debug("GetComponent(RigManager) failed: " + ex.Message); } if ((Object)(object)val == (Object)null) { return null; } bool found; object member = ShyGuyReflect.GetMember(val, ((object)val).GetType(), new string[2] { "physicsRig", "PhysicsRig" }, out found); if (found && member != null) { bool found2; Transform val2 = AsTransform(ShyGuyReflect.GetMember(member, member.GetType(), new string[3] { "m_head", "head", "Head" }, out found2)); if (found2 && (Object)(object)val2 != (Object)null) { return val2; } } bool found3; object member2 = ShyGuyReflect.GetMember(val, ((object)val).GetType(), new string[3] { "ControllerRig", "controllerRig", "openControllerRig" }, out found3); if (found3 && member2 != null) { bool found4; Transform val3 = AsTransform(ShyGuyReflect.GetMember(member2, member2.GetType(), new string[4] { "m_head", "head", "Head", "headset" }, out found4)); if (found4 && (Object)(object)val3 != (Object)null) { return val3; } } return null; } private static Transform AsTransform(object value) { if (value != null) { Transform val = (Transform)((value is Transform) ? value : null); if (val == null) { GameObject val2 = (GameObject)((value is GameObject) ? value : null); if (val2 == null) { Component val3 = (Component)((value is Component) ? value : null); if (val3 != null) { if (!((Object)(object)val3 != (Object)null)) { return null; } return val3.transform; } return null; } if (!((Object)(object)val2 != (Object)null)) { return null; } return val2.transform; } if (!((Object)(object)val != (Object)null)) { return null; } return val; } return null; } private void Prune(float now) { _pruneScratch.Clear(); foreach (KeyValuePair target in _targets) { ShyGuyTarget value = target.Value; if (!value.IsValid) { _pruneScratch.Add(target.Key); continue; } if (!value.IsLocal && value.FusionId >= 0 && !ShyGuyFusionBridge.IsStillConnected(value.FusionId)) { value.Invalidate("left the Fusion session"); _pruneScratch.Add(target.Key); continue; } if (value.PollRespawn()) { value.ClearLookState(); this.TargetRespawned?.Invoke(value); } if (now - value.LastSeenAlive > 3f && !value.IsAlive) { value.Invalidate("stale - not confirmed alive for " + 3f + "s"); _pruneScratch.Add(target.Key); } } for (int i = 0; i < _pruneScratch.Count; i++) { string key = _pruneScratch[i]; if (_targets.TryGetValue(key, out var value2)) { _targets.Remove(key); string text = value2.InvalidReason ?? "no longer a valid living player"; ShyGuyLog.Debug("Dropped player target " + value2.DisplayName + ": " + text); this.TargetLost?.Invoke(value2, text); } } } internal string DescribeState() { if (_targets.Count == 0) { return "No player targets tracked."; } return _targets.Count + " tracked: " + string.Join(", ", _targets.Values.Select((ShyGuyTarget t) => t.DisplayName + "[" + (t.IsAlive ? "alive" : (t.IsValid ? "dead" : "invalid")) + (t.HasTriggered ? ",triggered" : "") + "]")); } } internal sealed class ShyGuyTarget { internal float LookTime; internal float LastLookTime; internal float LastSeenAlive; internal bool HasTriggered; internal bool WasObservedDead; internal Vector3 LastHeadPosition; private bool _hasPositionSample; private const float TeleportDistanceThreshold = 6f; private bool _invalidated; internal string Key { get; private set; } internal string DisplayName { get; private set; } internal bool IsLocal { get; private set; } internal int FusionId { get; private set; } internal GameObject RigRoot { get; private set; } internal Transform Head { get; private set; } internal Camera Camera { get; private set; } internal object HealthComponent { get; private set; } internal Type HealthComponentType { get; private set; } internal int RigInstanceId { get; private set; } internal string InvalidReason { get; private set; } internal bool IsValid { get { if (_invalidated) { return false; } if ((Object)(object)RigRoot == (Object)null) { Invalidate("rig destroyed"); return false; } if ((Object)(object)Head == (Object)null) { Invalidate("head transform destroyed"); return false; } if (!RigRoot.activeInHierarchy) { return false; } return true; } } internal bool IsAlive { get { if (IsValid) { return ShyGuyDamage.IsAlive(this); } return false; } } internal Vector3 EyePosition { get { //IL_0014: 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_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_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) if ((Object)(object)Head != (Object)null) { return Head.position; } if ((Object)(object)RigRoot != (Object)null) { return RigRoot.transform.position + Vector3.up * 1.6f; } return Vector3.zero; } } internal Vector3 LookDirection { get { //IL_0044: 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_0024: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Camera != (Object)null) { Transform transform = ((Component)Camera).transform; if ((Object)(object)transform != (Object)null) { return transform.forward; } } if ((Object)(object)Head != (Object)null) { return Head.forward; } return Vector3.forward; } } internal Vector3 NavigationPosition { get { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_003f: 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_0051: 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) Vector3 eyePosition = EyePosition; if (((Vector3)(ref eyePosition)).sqrMagnitude > 0.0001f) { if (ShyGuyPhysics.Raycast(eyePosition + Vector3.up * 0.2f, Vector3.down, 5f, -1, out var nearest)) { return new Vector3(eyePosition.x, ((RaycastHit)(ref nearest)).point.y, eyePosition.z); } return new Vector3(eyePosition.x, eyePosition.y - 1.65f, eyePosition.z); } return Vector3.zero; } } internal ShyGuyTarget(string key, string displayName, bool isLocal, int fusionId, GameObject rigRoot, Transform head, Camera camera) { Key = key; DisplayName = (string.IsNullOrEmpty(displayName) ? key : displayName); IsLocal = isLocal; FusionId = fusionId; RigRoot = rigRoot; Head = head; Camera = camera; RigInstanceId = (((Object)(object)rigRoot != (Object)null) ? ((Object)rigRoot).GetInstanceID() : 0); LastSeenAlive = Time.unscaledTime; } internal bool AdoptRig(GameObject rigRoot, Transform head, Camera camera, string displayName) { bool num = (Object)(object)rigRoot != (Object)null && ((Object)rigRoot).GetInstanceID() != RigInstanceId; RigRoot = rigRoot; Head = head; Camera = camera; RigInstanceId = (((Object)(object)rigRoot != (Object)null) ? ((Object)rigRoot).GetInstanceID() : 0); if (!string.IsNullOrEmpty(displayName)) { DisplayName = displayName; } LastSeenAlive = Time.unscaledTime; _invalidated = false; InvalidReason = null; if (num) { HealthComponent = null; HealthComponentType = null; _hasPositionSample = false; } return num; } internal void SetHealthComponent(object component, Type componentType) { HealthComponent = component; HealthComponentType = componentType; } internal void Invalidate(string reason) { if (!_invalidated) { _invalidated = true; InvalidReason = reason ?? "unspecified"; ShyGuyLog.Debug("Target " + DisplayName + " invalidated: " + InvalidReason); } } internal void RefreshLiveHead() { if (IsLocal) { Camera main = Camera.main; if ((Object)(object)main != (Object)null && (Object)(object)((Component)main).transform != (Object)null) { Camera = main; Head = ((Component)main).transform; return; } } Transform val = FindLiveHeadUnderRig(); if ((Object)(object)val != (Object)null) { Head = val; } else if (!((Object)(object)Head != (Object)null) && !((Object)(object)RigRoot == (Object)null)) { Transform val2 = ShyGuyHierarchy.FindFirstByNames(RigRoot.transform, new string[5] { "Head", "head", "PlayerHead", "HeadPosition", "Neck" }); if ((Object)(object)val2 != (Object)null) { Head = val2; } } } private Transform FindLiveHeadUnderRig() { if ((Object)(object)RigRoot == (Object)null) { return null; } Transform val = ShyGuyHierarchy.FindFirstByNames(RigRoot.transform, new string[2] { "PhysicsRig", "physicsRig" }); return ShyGuyHierarchy.FindFirstByNames(((Object)(object)val != (Object)null) ? val : RigRoot.transform, new string[7] { "Head", "head", "m_head", "PlayerHead", "HeadPosition", "Neck", "headset" }); } internal bool PollRespawn() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0073: 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_001b: 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) if (!IsValid) { return false; } bool result = false; Vector3 eyePosition = EyePosition; if (_hasPositionSample) { float num = Vector3.Distance(eyePosition, LastHeadPosition); if (num > 6f) { result = true; ShyGuyLog.Debug("Target " + DisplayName + " moved " + num.ToString("F1") + "m in one refresh - treating as a respawn/teleport."); } } LastHeadPosition = eyePosition; _hasPositionSample = true; bool num2 = ShyGuyDamage.IsAlive(this); if (!num2) { WasObservedDead = true; } else if (WasObservedDead) { WasObservedDead = false; result = true; ShyGuyLog.Debug("Target " + DisplayName + " came back from dead - treating as a respawn."); } if (num2) { LastSeenAlive = Time.unscaledTime; } return result; } internal float DistanceToEyes(Vector3 point) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) return Vector3.Distance(EyePosition, point); } internal void ClearLookState() { LookTime = 0f; LastLookTime = 0f; } public override string ToString() { return DisplayName + " (" + Key + (IsLocal ? ", local" : ", remote") + ")"; } } public sealed class TheShyGuyMod : MelonMod { private static readonly string[] MenuSceneHints = new string[4] { "menu", "loading", "bootstrap", "void" }; private bool _transitioning; private const int PostLoadGraceFrames = 90; private int _graceFramesRemaining; internal static ShyGuyPlayerFinder PlayerFinder { get; private set; } public override void OnInitializeMelon() { ShyGuyLog.Attach(((MelonBase)this).LoggerInstance); ShyGuyLog.Msg("The Shy Guy 1.0.0 is loading."); ShyGuyConfig.Initialise(); PlayerFinder = new ShyGuyPlayerFinder(); ShyGuyLog.Msg(ShyGuyFusionBridge.DescribeState()); ShyGuyLog.Msg("Loaded. Press " + ShyGuyConfig.SpawnKeyName + " (or Insert) in a level to spawn The Shy Guy, " + ShyGuyConfig.DespawnKeyName + " to remove it. Or open BoneMenu → The Shy Guy → Spawn. Settings: UserData/TheShyGuy.cfg."); } public override void OnLateInitializeMelon() { ShyGuyReflect.InvalidateCaches(); ShyGuyFusionBridge.HardReset(); ShyGuyLog.Msg(ShyGuyFusionBridge.DescribeState()); if (ShyGuyAssetLoader.EnsureBundle()) { ShyGuyLog.Msg("Asset bundle is ready."); } ShyGuyBoneMenu.TryRegister(); } public override void OnSceneWasUnloaded(int buildIndex, string sceneName) { _transitioning = true; ShyGuyLog.Debug("Scene unloaded: '" + sceneName + "'. Cleaning up."); ShyGuyLog.Guard("scene unload cleanup", delegate { ShyGuySpawner.OnLevelUnloading(); PlayerFinder?.Clear(); ShyGuyFusionBridge.Reset(); ShyGuyPlayerFinder.InvalidateTypeCache(); ShyGuyDamage.InvalidateTypeCache(); ShyGuyMaterialFixer.InvalidateCache(); ShyGuyAssetLoader.ClearCaches(); ShyGuyLog.ResetOnceState(); }); } public override void OnSceneWasInitialized(int buildIndex, string sceneName) { ShyGuyLog.Debug("Scene initialised: '" + sceneName + "' (build index " + buildIndex + ")."); _transitioning = false; _graceFramesRemaining = 90; if (IsMenuScene(sceneName)) { ShyGuyLog.Debug("'" + sceneName + "' looks like a menu or loading scene; not spawning here."); } else { MelonCoroutines.Start(AfterLevelLoad(sceneName)); } } private IEnumerator AfterLevelLoad(string sceneName) { for (int frame = 0; frame < 90; frame++) { yield return null; if (_transitioning) { yield break; } if (PlayerFinder?.GetLocalPlayer() != null) { break; } PlayerFinder?.ForceRescan(); } if (!_transitioning) { ShyGuyLog.Guard("post-level-load", delegate { bool flag = PlayerFinder?.GetLocalPlayer() != null; ShyGuyLog.Debug("Post-load setup for '" + sceneName + "'. Local player found: " + flag + "."); ShyGuyReflect.InvalidateCaches(); ShyGuyFusionBridge.HardReset(); ShyGuyLog.Msg(ShyGuyFusionBridge.DescribeState()); ShyGuySpawner.OnLevelLoaded(); ShyGuySpawnableBinder.ScanOnce(); }); } } public override void OnUpdate() { if (!_transitioning) { if (_graceFramesRemaining > 0) { _graceFramesRemaining--; } ShyGuyLog.Guard("shortcut polling", PollShortcuts); if (!_transitioning && _graceFramesRemaining <= 0) { ShyGuySpawnableBinder.Tick(); } } } public override void OnDeinitializeMelon() { ShyGuyLog.Msg("The Shy Guy is unloading; cleaning up."); ShyGuyLog.Guard("mod teardown", delegate { ShyGuySpawner.OnLevelUnloading(); PlayerFinder?.Clear(); PlayerFinder = null; ShyGuyFusionBridge.HardReset(); ShyGuyPlayerFinder.InvalidateTypeCache(); ShyGuyDamage.InvalidateTypeCache(); ShyGuyMaterialFixer.InvalidateCache(); ShyGuyWavLoader.Invalidate(); ShyGuyAssetLoader.Unload(); ShyGuyConfig.Save(); ShyGuyLog.ResetOnceState(); }); } private void PollShortcuts() { //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_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_000f: 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_00b6: Unknown result type (might be due to invalid IL or missing references) KeyCode spawnKey = ShyGuyConfig.SpawnKey; KeyCode despawnKey = ShyGuyConfig.DespawnKey; if (((int)spawnKey != 0 && ShyGuyInput.GetKeyDown(spawnKey)) || ShyGuyInput.GetKeyDown((KeyCode)277)) { if (ShyGuyInput.GetKey((KeyCode)304) || ShyGuyInput.GetKey((KeyCode)303)) { ShyGuyLog.Guard("debug dump shortcut", Dump); } else if (_graceFramesRemaining > 0) { ShyGuyLog.Msg("Still loading; wait a moment before spawning."); } else { ShyGuyLog.Msg("Spawn key pressed."); ShyGuyLog.Guard("spawn shortcut", delegate { Spawn(); }); } } if ((int)despawnKey != 0 && ShyGuyInput.GetKeyDown(despawnKey)) { ShyGuyLog.Guard("despawn shortcut", Despawn); } } private static bool IsMenuScene(string sceneName) { if (string.IsNullOrEmpty(sceneName)) { return true; } string text = sceneName.ToLowerInvariant(); string[] menuSceneHints = MenuSceneHints; foreach (string value in menuSceneHints) { if (text.Contains(value)) { return true; } } return false; } public static bool Spawn() { if (PlayerFinder == null) { ShyGuyLog.Error("The mod is not initialised; cannot spawn."); return false; } int liveCount = ShyGuySpawner.LiveCount; if ((Object)(object)ShyGuySpawner.SpawnInFrontOfLocalPlayer() != (Object)null) { return true; } ShyGuySpawnableBinder.ScanOnce(); return ShyGuySpawner.LiveCount > liveCount; } public static bool SpawnAt(float x, float y, float z) { //IL_0016: 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) if (PlayerFinder == null) { ShyGuyLog.Error("The mod is not initialised; cannot spawn."); return false; } return (Object)(object)ShyGuySpawner.SpawnAt(new Vector3(x, y, z), Quaternion.identity) != (Object)null; } public static void Despawn() { ShyGuySpawner.DespawnAll(); } public static void ForceTrigger() { ShyGuyTarget shyGuyTarget = PlayerFinder?.GetLocalPlayer(); if (shyGuyTarget == null) { ShyGuyLog.Error("Cannot force a trigger: the local player was not found."); return; } if (TheShyGuyBehaviour.All.Count == 0) { ShyGuyLog.Error("Cannot force a trigger: no Shy Guy is spawned."); return; } foreach (TheShyGuyBehaviour item in TheShyGuyBehaviour.All) { if (!((Object)(object)item == (Object)null)) { item.ForceTrigger(shyGuyTarget); break; } } } public static void Calm() { if (TheShyGuyBehaviour.All.Count == 0) { ShyGuyLog.Msg("No Shy Guy is spawned."); return; } foreach (TheShyGuyBehaviour item in TheShyGuyBehaviour.All) { item?.ForceCalm(); } } public static void Dump() { ShyGuyLog.Msg("================ The Shy Guy diagnostics ================"); ShyGuyLog.Msg("Version: 1.0.0 | Enabled: " + ShyGuyConfig.Enabled); ShyGuyLog.Msg(ShyGuyFusionBridge.DescribeState()); ShyGuyLog.Msg(ShyGuyDamage.DescribeState()); ShyGuyLog.Msg(ShyGuySpawner.DescribeState()); ShyGuyLog.Msg(ShyGuyBoneMenu.DescribeState()); ShyGuyLog.Msg("Players: " + ((PlayerFinder != null) ? PlayerFinder.DescribeState() : "no finder")); if (TheShyGuyBehaviour.All.Count == 0) { ShyGuyLog.Msg("No Shy Guy is currently spawned."); } else { foreach (TheShyGuyBehaviour item in TheShyGuyBehaviour.All) { if (!((Object)(object)item == (Object)null)) { ShyGuyLog.Msg(item.Describe()); } } } ShyGuyLog.Msg("========================================================="); } public static void DumpHierarchy() { if (TheShyGuyBehaviour.All.Count == 0) { ShyGuyLog.Msg("No Shy Guy is spawned; nothing to dump."); return; } foreach (TheShyGuyBehaviour item in TheShyGuyBehaviour.All) { if (!((Object)(object)item == (Object)null)) { ShyGuyLog.Msg("--- Hierarchy of " + ((Object)item).name + " ---"); ShyGuyHierarchy.DumpHierarchy(((Component)item).transform, 5); break; } } } } internal static class ShyGuyBoneMenu { private static bool _registered; private static int _attempts; internal static void TryRegister() { if (!_registered) { MelonCoroutines.Start(RegisterWhenReady()); } } private static IEnumerator RegisterWhenReady() { for (int i = 0; i < 120; i++) { if (TryRegisterNow()) { yield break; } yield return null; } if (!_registered) { ShyGuyLog.Msg("BoneMenu page was not registered. Use the spawn keybind (F8) instead."); } } private static bool TryRegisterNow() { //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) if (_registered) { return true; } _attempts++; if (!ShyGuyReflect.HasAssembly("BoneLib")) { if (_attempts == 1) { ShyGuyLog.Msg("BoneLib not installed - no BoneMenu page. Use the spawn keybind instead."); } return true; } try { Type type = ShyGuyReflect.FindType("BoneLib.BoneMenu.Page", new string[1] { "BoneLib.BoneMenu.Page" }); if (type == null) { return false; } object obj = type.GetProperty("Root", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (obj == null) { return false; } MethodInfo methodInfo = FindMethod(type, "CreatePage", typeof(string), typeof(Color)); if (methodInfo == null) { return false; } object obj2 = InvokeFlexible(methodInfo, obj, "The Shy Guy", (object)new Color(0.55f, 0.75f, 0.95f), 0, true); if (obj2 == null) { return false; } AddFunction(obj2, "Spawn", new Color(0.3f, 0.9f, 0.4f), delegate { if (!TheShyGuyMod.Spawn()) { ShyGuyLog.Msg("BoneMenu spawn failed - check the error above."); } }); AddFunction(obj2, "Despawn All", new Color(0.95f, 0.35f, 0.35f), TheShyGuyMod.Despawn); AddFunction(obj2, "Force Trigger", new Color(1f, 0.55f, 0.15f), TheShyGuyMod.ForceTrigger); AddFunction(obj2, "Calm Down", new Color(0.7f, 0.85f, 1f), TheShyGuyMod.Calm); AddFunction(obj2, "Diagnostics Dump", new Color(0.75f, 0.75f, 0.75f), TheShyGuyMod.Dump); _registered = true; ShyGuyLog.Msg("BoneMenu ready: BoneMenu → The Shy Guy → Spawn. For Fusion peers: install Deakins.TheShyGuy pallet on ALL clients and prefer the spawn gun (NetworkAssetSpawner). Melon Instantiation alone does not sync."); return true; } catch (Exception ex) { ShyGuyLog.DebugThrottled("bonemenu:err", "BoneMenu try failed: " + ex.Message); return false; } } private static void AddFunction(object page, string name, Color color, Action action) { //IL_0099: Unknown result type (might be due to invalid IL or missing references) MethodInfo methodInfo = FindMethod(page.GetType(), "CreateFunction", typeof(string), typeof(Color)); if (methodInfo == null) { ShyGuyLog.Warn("BoneMenu CreateFunction missing; button '" + name + "' skipped."); return; } ParameterInfo[] parameters = methodInfo.GetParameters(); object obj = action; if (parameters.Length >= 3) { Type parameterType = parameters[2].ParameterType; if (!parameterType.IsInstanceOfType(action)) { try { obj = Delegate.CreateDelegate(parameterType, action.Target, action.Method); } catch { obj = action; } } } InvokeFlexible(methodInfo, page, name, color, obj); } private static MethodInfo FindMethod(Type type, string name, params Type[] firstParamTypes) { MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name != name) { continue; } ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length < firstParamTypes.Length) { continue; } bool flag = true; for (int j = 0; j < firstParamTypes.Length; j++) { if (parameters[j].ParameterType != firstParamTypes[j]) { flag = false; break; } } if (flag) { return methodInfo; } } return null; } private static object InvokeFlexible(MethodInfo method, object target, params object[] provided) { ParameterInfo[] parameters = method.GetParameters(); object[] array = new object[parameters.Length]; for (int i = 0; i < parameters.Length; i++) { if (i < provided.Length && provided[i] != null && (parameters[i].ParameterType.IsInstanceOfType(provided[i]) || IsCompatibleDelegate(parameters[i].ParameterType, provided[i]))) { array[i] = provided[i]; } else if (i < provided.Length && provided[i] != null && parameters[i].ParameterType == typeof(Color) && provided[i] is Color) { array[i] = provided[i]; } else if (i < provided.Length && parameters[i].ParameterType == typeof(string) && provided[i] is string) { array[i] = provided[i]; } else if (i < provided.Length && parameters[i].ParameterType == typeof(int) && provided[i] is int) { array[i] = provided[i]; } else if (i < provided.Length && parameters[i].ParameterType == typeof(bool) && provided[i] is bool) { array[i] = provided[i]; } else if (i < provided.Length && provided[i] != null) { array[i] = provided[i]; } else if (parameters[i].HasDefaultValue) { array[i] = parameters[i].DefaultValue; } else if (parameters[i].ParameterType.IsValueType) { array[i] = Activator.CreateInstance(parameters[i].ParameterType); } else { array[i] = null; } } return method.Invoke(target, array); } private static bool IsCompatibleDelegate(Type expected, object value) { if (value is Delegate) { return typeof(Delegate).IsAssignableFrom(expected); } return false; } internal static string DescribeState() { if (!_registered) { return "BoneMenu: not registered"; } return "BoneMenu: registered (BoneMenu → The Shy Guy)"; } } }