using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using LiteNetLib; using LiteNetLib.Layers; using LiteNetLib.Utils; using Microsoft.CodeAnalysis; using PerfectRandom.Sulfur.Core; using PerfectRandom.Sulfur.Core.CharacterStats; using PerfectRandom.Sulfur.Core.Items; using PerfectRandom.Sulfur.Core.LevelGeneration; using PerfectRandom.Sulfur.Core.Stats; using PerfectRandom.Sulfur.Core.UI; using PerfectRandom.Sulfur.Core.Units; using PerfectRandom.Sulfur.Core.Utilities; using PerfectRandom.Sulfur.Core.Weapons; using PerfectRandom.Sulfur.Core.World; using Ryuka.Sulfur.NativeUI; using SULFURTogether.Config; using SULFURTogether.Logging; using SULFURTogether.Networking; using SULFURTogether.Networking.Gameplay; using SULFURTogether.Networking.Gameplay.Boss; using SULFURTogether.Patches; using SULFURTogether.ReverseProbe; using SULFURTogether.UI; using SULFURTogether.UI.DownedRescueOverlay; using SULFURTogether.UI.RunStatsOverlay; using Steamworks; using TMPro; using Unity.Collections; using Unity.Mathematics; using UnityEngine; using UnityEngine.AI; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 SULFURTogether { internal static class ModInfo { public const string GUID = "com.ryuka.sulfur.together"; public const string Name = "SULFUR Together"; public const string Version = "1.0.0"; public const string Author = "ryuka"; } [BepInPlugin("com.ryuka.sulfur.together", "SULFUR Together", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { private readonly Stopwatch _updateProf = new Stopwatch(); public static Plugin Instance { get; private set; } public static STLogger Log { get; private set; } public static CoopConfig Cfg { get; private set; } private void Awake() { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Expected O, but got Unknown //IL_0357: Unknown result type (might be due to invalid IL or missing references) //IL_06b7: Unknown result type (might be due to invalid IL or missing references) Instance = this; Cfg = new CoopConfig(((BaseUnityPlugin)this).Config); Log = new STLogger(((BaseUnityPlugin)this).Logger, Cfg); Log.Info("v1.0.0 by ryuka loading..."); Log.Info("[Build] CfgCleanup batch 11 (HostDrivenProxy pt2: proxy/attack-phase/interest-mgmt/target-proxy/coalescing + DisablePauseInMultiplayer): 30 functional/tuning flags hardcoded (Fixed), removed from .cfg. Log*/Debug diagnostics kept. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 10 (HostDrivenProxy pt1: health-sync + hit-request + terminal/flash/pending-death): 25 functional/tuning flags hardcoded (Fixed), removed from .cfg. 6 Log* kept. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 9 (NetworkEnemyTargetExperimental): 18 enemy projectile-visual + ranged-damage + target/combat-authority functional/tuning flags hardcoded (Fixed), removed from .cfg. 3 Log* kept. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 8 (NetworkEnemyStateExperimental): 28 enemy state/animation/snapshot/delta-compression functional+tuning flags hardcoded (Fixed), removed from .cfg. 4 Log* kept. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 7 (NetworkEnemyIntentExperimental): 8 intent-motion + host-authorized-intent functional/tuning flags hardcoded (Fixed; intent-driven motion stays OFF), removed from .cfg. 2 Log* kept. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 6 (PlayerWeapon): 16 weapon-sync + remote-player body/weapon appearance flags hardcoded (Fixed, appearance finalized), removed from .cfg. 5 Log* kept. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 5 (NetworkPlayerLifeExperimental): 8 downed/revive functional+tuning flags hardcoded (Fixed), removed from .cfg. LogPlayerLifeSync + PlayerReviveHoldKey (keybind) kept. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 4 (NetworkEnemy + NetworkGameplaySyncExperimental): 11 runtime-spawn + enemy-death-mirror functional/tuning flags hardcoded (Fixed), removed from .cfg. Log* kept. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 3 (NetworkVisualProxy): 9 remote-visual-proxy functional/tuning flags hardcoded (Fixed), removed from .cfg. 2026-06-30"); Log.Info("[Build] CfgCleanup batch 2 (NetworkRunState+NetworkLevelSeed): EnableRunStateNegotiation/RunStateBroadcastIntervalSeconds/EnableLevelSeedAuthority/RequireSameLevelSeedForSceneMatch/ApplyHostLevelSeedOnManualFollow/HideRemoteVisualWhenLevelSeedMismatch/SyncHostUsedSetsOnManualFollow hardcoded (Fixed), removed from .cfg. Warn*/Log* kept. 2026-06-30"); Log.Info("[Build] UI-CleanRole: networking role is runtime-only (NetworkMode/EnableNetworking dropped from the .cfg); connection settings (name/IP/port/key/maxplayers/version + EnableCoopToasts) moved to a private JSON store (CoopSettingsStore) so they stay out of Gale; retired .cfg keys pruned via OrphanedEntries reflection. + REGRESSION FIX: promoted Plan B targeting flags EnableRemotePlayerInPlayersList + EnableGhostPlayerHitbox into the dev-defaults (were local-cfg-only; a fresh/deleted config left enemies idle = 站桩). 2026-06-30"); PatchBootstrap.ApplyAll(new Harmony("com.ryuka.sulfur.together")); WireCoopUi(); CoopConnection.Initialize(); bool isAvailable = SteamNetworkingSupport.IsAvailable; Log.Info($"[Config] SteamNetworkingAvailable={isAvailable}"); SteamRichPresenceJoin.Initialize(); SteamRelayBridge.Initialize(); Log.Info("[ConfigPolicy] Private development build: active experimental gameplay defaults are forced on load; connection settings such as HostAddress/HostPort/PlayerName are left user-owned. The networking role is runtime-only (not persisted)."); Log.Info($"[Config] EnableDebugLog={Cfg.EnableDebugLog.Value} | EnableReverseProbe={Cfg.EnableReverseProbe.Value} | NetMode={NetConfig.GetMode()}"); Log.Info($"[Config] EnableInventorySerializationProbe={Cfg.EnableInventorySerializationProbe.Value} | EnableAiUpdateTargetProbe={Cfg.EnableAiUpdateTargetProbe.Value} | EnableAiSetDestinationProbe={Cfg.EnableAiSetDestinationProbe.Value}"); Log.Info($"[Config] EnableVerbosePickupProbe={Cfg.EnableVerbosePickupProbe.Value} | EnableVerboseLootProbe={Cfg.EnableVerboseLootProbe.Value} | CompactPickupLogs={Cfg.CompactPickupLogs.Value} | CompactLootLogs={Cfg.CompactLootLogs.Value}"); Log.Info($"[Config] EnableRunStateNegotiation={Cfg.EnableRunStateNegotiation.Value} | RunStateBroadcastIntervalSeconds={Cfg.RunStateBroadcastIntervalSeconds.Value} | WarnOnRunStateMismatch={Cfg.WarnOnRunStateMismatch.Value}"); Log.Info($"[Config] EnableHostSceneAuthority={Cfg.EnableHostSceneAuthority.Value} | WarnOnClientSceneDrift={Cfg.WarnOnClientSceneDrift.Value}"); Log.Info($"[Config] EnableHostSceneRequestProtocol={Cfg.EnableHostSceneRequestProtocol.Value} | AutoSendHostSceneRequestOnDrift={Cfg.AutoSendHostSceneRequestOnDrift.Value} | HostSceneRequestIntervalSeconds={Cfg.HostSceneRequestIntervalSeconds.Value}"); Log.Info($"[Config] EnableManualClientSceneFollow={Cfg.EnableManualClientSceneFollow.Value} | ManualClientSceneFollowKey={Cfg.ManualClientSceneFollowKey.Value} | ManualClientSceneFollowRequiresHostRequest={Cfg.ManualClientSceneFollowRequiresHostRequest.Value}"); Log.Info($"[Config] EnableLevelSeedAuthority={Cfg.EnableLevelSeedAuthority.Value} | RequireSameLevelSeedForSceneMatch={Cfg.RequireSameLevelSeedForSceneMatch.Value} | ApplyHostLevelSeedOnManualFollow={Cfg.ApplyHostLevelSeedOnManualFollow.Value} | HideRemoteVisualWhenLevelSeedMismatch={Cfg.HideRemoteVisualWhenLevelSeedMismatch.Value}"); Log.Info($"[Config] EnableRemotePlayerVisualProxy={Cfg.EnableRemotePlayerVisualProxy.Value} | RemotePlayerTransformSendRateHz={Cfg.RemotePlayerTransformSendRateHz.Value} | RemotePlayerVisualTimeoutSeconds={Cfg.RemotePlayerVisualTimeoutSeconds.Value} | RemotePlayerVisualInterpolationSpeed={Cfg.RemotePlayerVisualInterpolationSpeed.Value} | RemotePlayerVisualSnapDistance={Cfg.RemotePlayerVisualSnapDistance.Value}"); Log.Info($"[Config] EnableGameplayEntityProbe={Cfg.EnableGameplayEntityProbe.Value} | GameplayEntityProbeSummaryIntervalSeconds={Cfg.GameplayEntityProbeSummaryIntervalSeconds.Value} | LogGameplayEntitySpawn={Cfg.LogGameplayEntitySpawn.Value} | LogGameplayEntityDamage={Cfg.LogGameplayEntityDamage.Value} | LogGameplayEntityDeath={Cfg.LogGameplayEntityDeath.Value} | RequireStableSceneAndSeedForGameplayProbe={Cfg.RequireStableSceneAndSeedForGameplayProbe.Value}"); Log.Info($"[Config] EnableHostEnemyDeathEventMirror={Cfg.EnableHostEnemyDeathEventMirror.Value} | LogReceivedEnemyDeathEvents={Cfg.LogReceivedEnemyDeathEvents.Value} | ApplyReceivedEnemyDeathEvents={Cfg.ApplyReceivedEnemyDeathEvents.Value} | EnemyDeathMirrorPositionTolerance={Cfg.EnemyDeathMirrorPositionTolerance.Value} | EnemyDeathMirrorUseHorizontalPositionTolerance={Cfg.EnemyDeathMirrorUseHorizontalPositionTolerance.Value}"); Log.Info($"[Config] EnableClientEnemyDeathClaim={Cfg.EnableClientEnemyDeathClaim.Value} | LogReceivedClientEnemyDeathClaims={Cfg.LogReceivedClientEnemyDeathClaims.Value} | ApplyReceivedClientEnemyDeathClaimsOnHost={Cfg.ApplyReceivedClientEnemyDeathClaimsOnHost.Value}"); Log.Info($"[Config] EnableCoopPlayerDownedRevive={Cfg.EnableCoopPlayerDownedRevive.Value} | PlayerDownedRescueTimeoutSeconds={Cfg.PlayerDownedRescueTimeoutSeconds.Value} | PlayerReviveHoldSeconds={Cfg.PlayerReviveHoldSeconds.Value} | PlayerReviveDistance={Cfg.PlayerReviveDistance.Value} | PlayerReviveHoldKey={Cfg.PlayerReviveHoldKey.Value} | PlayerReviveHealthRatio={Cfg.PlayerReviveHealthRatio.Value} | RequireReviveDistanceValidationOnHost={Cfg.RequireReviveDistanceValidationOnHost.Value}"); Log.Info($"[Config] EnableHostEnemyStateSnapshotMirror={Cfg.EnableHostEnemyStateSnapshotMirror.Value} | EnemyStateSnapshotSendRateHz={Cfg.EnemyStateSnapshotSendRateHz.Value} | EnemyStateSnapshotMaxEnemiesPerPacket={Cfg.EnemyStateSnapshotMaxEnemiesPerPacket.Value} | OnlySendAliveEnemyStateSnapshots={Cfg.OnlySendAliveEnemyStateSnapshots.Value} | LogReceivedEnemyStateSnapshots={Cfg.LogReceivedEnemyStateSnapshots.Value} | ApplyReceivedEnemyStateSnapshots={Cfg.ApplyReceivedEnemyStateSnapshots.Value} | EnemyStateSnapshotPositionTolerance={Cfg.EnemyStateSnapshotPositionTolerance.Value}"); Log.Info($"[Config] EnemyStateSnapshotInterpolationSpeed={Cfg.EnemyStateSnapshotInterpolationSpeed.Value} | EnemyStateSnapshotPlaybackDurationMultiplier={Cfg.EnemyStateSnapshotPlaybackDurationMultiplier.Value} | EnemyStateSnapshotSnapDistance={Cfg.EnemyStateSnapshotSnapDistance.Value} | EnemyStateSnapshotApplyRotationY={Cfg.EnemyStateSnapshotApplyRotationY.Value} | EnableClientEnemyAiSuppressionExperiment={Cfg.EnableClientEnemyAiSuppressionExperiment.Value} | SuppressClientEnemyAiWhenStateMirrorEnabled={Cfg.SuppressClientEnemyAiWhenStateMirrorEnabled.Value} | EnableClientEnemyPuppetMode={Cfg.EnableClientEnemyPuppetMode.Value} | ClientEnemyPuppetStaleReleaseSeconds={Cfg.ClientEnemyPuppetStaleReleaseSeconds.Value} | LogClientEnemyPuppetMode={Cfg.LogClientEnemyPuppetMode.Value}"); Log.Info($"[Config] EnableHostEnemyAnimationMirror={Cfg.EnableHostEnemyAnimationMirror.Value} | ApplyReceivedEnemyAnimationMirror={Cfg.ApplyReceivedEnemyAnimationMirror.Value} | LogEnemyAnimationMirror={Cfg.LogEnemyAnimationMirror.Value} | EnemyAnimationMirrorCrossFadeSeconds={Cfg.EnemyAnimationMirrorCrossFadeSeconds.Value} | EnemyAnimationMirrorNormalizedTimeTolerance={Cfg.EnemyAnimationMirrorNormalizedTimeTolerance.Value} | EnemyAnimationMirrorApplyAnimatorStatePlayback={Cfg.EnemyAnimationMirrorApplyAnimatorStatePlayback.Value} | EnemyAnimationMirrorApplyHostCombatStatePlayback={Cfg.EnemyAnimationMirrorApplyHostCombatStatePlayback.Value} | EnemyAnimationMirrorReplayHostCombatMethods={Cfg.EnemyAnimationMirrorReplayHostCombatMethods.Value} | EnemyAnimationMirrorApplyCombatAnimatorFallback={Cfg.EnemyAnimationMirrorApplyCombatAnimatorFallback.Value} | EnemyAnimationMirrorHostCombatActionHoldSeconds={Cfg.EnemyAnimationMirrorHostCombatActionHoldSeconds.Value}"); Log.Info($"[Config] EnableHostOnlyEnemyTargetAuthority={Cfg.EnableHostOnlyEnemyTargetAuthority.Value} | EnemyProjectileVisualMirrorEnabled={Cfg.EnemyProjectileVisualMirrorEnabled.Value} | EnemyProjectileVisualMirrorUseNativeShootReplay={Cfg.EnemyProjectileVisualMirrorUseNativeShootReplay.Value} | EnableGenericHostCombatAnimatorStateMirror={Cfg.EnableGenericHostCombatAnimatorStateMirror.Value} | EnableHostAuthoritativeEnemyRangedDamage={Cfg.EnableHostAuthoritativeEnemyRangedDamage.Value} | EnableClientEnemyIntentDrivenMotion={Cfg.EnableClientEnemyIntentDrivenMotion.Value} | EnemyIntentCorrectionDistance={Cfg.EnemyIntentCorrectionDistance.Value} | EnemyIntentHardSnapDistance={Cfg.EnemyIntentHardSnapDistance.Value} | LogEnemyTargetAuthority={Cfg.LogEnemyTargetAuthority.Value} | EnemyTargetAuthorityProbeIntervalSeconds={Cfg.EnemyTargetAuthorityProbeIntervalSeconds.Value} | EnableEnemyCombatProbe={Cfg.EnableEnemyCombatProbe.Value} | LogEnemyCombatProbe={Cfg.LogEnemyCombatProbe.Value} | EnemyHostProjectileHitRadius={Cfg.EnemyHostProjectileHitRadius.Value} | EnemyHostProjectileDamage={Cfg.EnemyHostProjectileDamage.Value}"); Log.Info($"[Config] EnableEnemyStateSnapshotDeltaCompression={Cfg.EnableEnemyStateSnapshotDeltaCompression.Value} | EnemyStateSnapshotHeartbeatSeconds={Cfg.EnemyStateSnapshotHeartbeatSeconds.Value} | EnemyStateSnapshotPositionDeltaThreshold={Cfg.EnemyStateSnapshotPositionDeltaThreshold.Value} | EnemyStateSnapshotRotationDeltaThresholdDegrees={Cfg.EnemyStateSnapshotRotationDeltaThresholdDegrees.Value} | EnemyStateSnapshotAnimationTimeDeltaThreshold={Cfg.EnemyStateSnapshotAnimationTimeDeltaThreshold.Value}"); Log.Info("Ready."); } private void WireCoopUi() { try { CoopLoc.Wire(((BaseUnityPlugin)this).Info.Location); Type type = AccessTools.TypeByName("Ryuka.Sulfur.NativeUI.SulfurPopupApi"); if (type == null) { Log.Info("[ArenaLockdown] SULFUR Native UI Lib not present — confirm prompt will be logged only (UI optional)."); return; } MethodInfo show = AccessTools.Method(type, "ShowBanner", new Type[1] { typeof(string) }, (Type[])null); MethodInfo hide = AccessTools.Method(type, "HideBanner", Type.EmptyTypes, (Type[])null); if (show == null || hide == null) { Log.Warn("[ArenaLockdown] SulfurPopupApi found but ShowBanner/HideBanner missing — prompt logged only."); return; } ArenaLockdownManager.ShowPrompt = delegate(string text) { show.Invoke(null, new object[1] { text }); }; ArenaLockdownManager.HidePrompt = delegate { hide.Invoke(null, null); }; Log.Info("[ArenaLockdown] confirm prompt wired to SULFUR Native UI Lib banner (SulfurPopupApi)."); Type type2 = AccessTools.TypeByName("Ryuka.Sulfur.NativeUI.SulfurToastApi"); MethodInfo showToast = ((type2 == null) ? null : AccessTools.Method(type2, "Show", new Type[2] { typeof(string), typeof(string) }, (Type[])null)); if (showToast != null) { CoopToasts.Wire(ArenaLockdownManager.ShowToast = delegate(string title, string msg) { showToast.Invoke(null, new object[2] { title, msg }); }); Log.Info("[CoopUi] toasts wired to SULFUR Native UI Lib (SulfurToastApi)."); } else { Log.Info("[CoopUi] SulfurToastApi not present — toasts logged only."); } if (AccessTools.TypeByName("Ryuka.Sulfur.NativeUI.SulfurOptionsApi") != null) { CoopConnectPage.Register(); Log.Info("[CoopUi] connect page registered (SulfurOptionsApi)."); } else { Log.Info("[CoopUi] SulfurOptionsApi not present — connect page unavailable."); } } catch (Exception ex) { Log.Warn("[CoopUi] failed to wire Native UI Lib surfaces: " + ex.Message); } } private void Update() { _updateProf.Restart(); ReverseProbeSummary.Tick(); PlayerVisualDiscoveryProbe.TryDumpOnce(); PlayerSpriteAssetScanProbe.TryScanOnce(); long elapsedMilliseconds = _updateProf.ElapsedMilliseconds; NetGameplayProbeManager.Tick(); long num = _updateProf.ElapsedMilliseconds - elapsedMilliseconds; NetPlayerLifeManager.Tick(); NetBossEncounterManager.Tick(); long bossMs = _updateProf.ElapsedMilliseconds - elapsedMilliseconds - num; EmperorWormDiagnostics.FrameWatchdogTick(); BossDynamicSpawnManifest.TickReleaseStaleGated(); ArenaLockdownManager.Tick(); RunStatsOverlayManager.Tick(); DownedRescueOverlayManager.Tick(); CoopConnection.Tick(); PauseControlPatches.Tick(); CoopConnectPage.Tick(); EmperorWormDiagnostics.ReportUpdateProfile(_updateProf.ElapsedMilliseconds, num, bossMs); NetGameplayProbeManager.ClientFrameHitchTick(_updateProf.ElapsedMilliseconds, num); } private void FixedUpdate() { CoopConnection.FixedTick(); } private void OnDestroy() { try { CoopConnectPage.Unregister(); } catch { } try { RunStatsOverlayManager.Shutdown(); } catch { } try { DownedRescueOverlayManager.Shutdown(); } catch { } CoopConnection.Stop("plugin destroyed"); } } } namespace SULFURTogether.UI { internal static class CoopConnectPage { private const string PageId = "ryuka.sulfur.together"; private const string RepoUrl = "https://github.com/ryuka-dev/SULFUR-Together"; private const string KoFiUrl = "https://ko-fi.com/ryukadev"; private static readonly Color ErrorColor = new Color(1f, 0.45f, 0.45f, 1f); private static readonly Color OkColor = new Color(0.55f, 1f, 0.65f, 1f); private static readonly Color NeutralColor = new Color(0.8f, 0.82f, 0.88f, 1f); private static bool _registered; private static SulfurOptionsContext _ctx; private static SulfurTextHandle _statusHandle; private static SulfurTextHandle _joinFeedbackHandle; private static SulfurTextHandle _gateHintHandle; private static SulfurTextHandle _lanIpHandle; private static SulfurButtonHandle _createHandle; private static SulfurButtonHandle _joinHandle; private static SulfurButtonHandle _closeHandle; private static SulfurListHandle _playerListHandle; private static string _lastPlayerSig = "\0"; private static SulfurTextHandle _steamUnavailableHandle; private static SulfurTextHandle _yourSteamIdHandle; private static SulfurButtonHandle _steamInviteHandle; private static SulfurButtonHandle _steamJoinHandle; private static SulfurTextHandle _steamPendingInviteHandle; private static TMP_InputField _steamIdInputField; private static string _autoFilledSteamId; private static SulfurTextHandle _ffSessionHandle; private static OptionsScreenOption _ffToggleOption; private static bool _ffLockApplied; private static readonly FieldInfo FfCheckboxField = typeof(OptionsScreenOption).GetField("checkboxToggle", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly FieldInfo FfIsLockedField = typeof(OptionsScreenOption).GetField("IsLocked", BindingFlags.Instance | BindingFlags.NonPublic); private static string _draftName; private static string _draftAddress; private static string _draftPort; private static string _draftKey; private static string _draftSteamId; private static string _lastSavedSig; private static float _nextTick; private static bool _closeMenuOnJoinSuccess; public static void Register() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown if (!_registered) { SulfurOptionsApi.RegisterPage(new SulfurOptionsPage { PageId = "ryuka.sulfur.together", DisplayName = "SULFUR Together", SortOrder = 1000, BuildPage = BuildPage }); _registered = true; } } public static void Unregister() { if (_registered) { SulfurOptionsApi.UnregisterPage("ryuka.sulfur.together"); _registered = false; ResetHandles(); } } public static void Tick() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) if (!_registered || _ctx == null || _statusHandle == null) { return; } float unscaledTime = Time.unscaledTime; if (unscaledTime < _nextTick) { return; } _nextTick = unscaledTime + 0.4f; try { AutoSaveDrafts(); string text = StatusLine(); _statusHandle.SetText(text); _statusHandle.SetColor(StatusColor()); ApplyButtonStates(); ApplyJoinFeedback(); ApplyHostLanIp(); ApplySteamState(); ApplySessionFriendlyFireControl(); RefreshPlayerList(); PollJoinClose(); } catch { } } private static void BuildPage(SulfurOptionsContext ctx) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_0301: Unknown result type (might be due to invalid IL or missing references) //IL_0369: Unknown result type (might be due to invalid IL or missing references) //IL_036e: Unknown result type (might be due to invalid IL or missing references) //IL_03ff: Unknown result type (might be due to invalid IL or missing references) //IL_0404: Unknown result type (might be due to invalid IL or missing references) ResetHandles(); _ctx = ctx; LoadDraftsFromConfig(); ctx.AddSection("SULFUR Together"); ctx.AddDescription(CoopLoc.Get("connect.desc.intro", "Early-preview co-op. Set up your connection below.")); _statusHandle = ctx.AddTextRow(StatusLine()); _statusHandle.SetColor(StatusColor()); ctx.AddSection(CoopLoc.Get("connect.section.player", "Player")); ctx.AddInlineTextInput(CoopLoc.Get("connect.label.playerName", "Player name"), _draftName, (Action)delegate(string v) { _draftName = v; }, 160f, false); ctx.AddSection(CoopLoc.Get("connect.section.connection", "Connection")); ctx.AddDescription(CoopLoc.Get("connect.desc.connection", "Host a co-op session or join one. Your settings save automatically as you edit them. Close room (host) / Leave (client) ends the session for you.")); ctx.AddInlineTextInput(CoopLoc.Get("connect.label.hostAddress", "Host address (IP)"), _draftAddress, (Action)delegate(string v) { _draftAddress = v; }, 160f, false); ctx.AddInlineTextInput(CoopLoc.Get("connect.label.port", "Port"), _draftPort, (Action)delegate(string v) { _draftPort = v; }, 160f, false); ctx.AddInlineTextInput(CoopLoc.Get("connect.label.connectionKey", "Connection key"), _draftKey, (Action)delegate(string v) { _draftKey = v; }, 160f, false); IReadOnlyList handles = ctx.AddButtonRow((SulfurButton[])(object)new SulfurButton[3] { new SulfurButton(CoopLoc.Get("connect.button.create", "Create game"), (Action)OnCreate, 170f), new SulfurButton(CoopLoc.Get("connect.button.join", "Join game"), (Action)OnJoin, 170f), new SulfurButton(CoopLoc.Get("connect.button.closeRoom", "Close room"), (Action)OnCloseRoom, 150f) }); _createHandle = Handle(handles, 0); _joinHandle = Handle(handles, 1); _closeHandle = Handle(handles, 2); _gateHintHandle = ctx.AddTextRow(""); _gateHintHandle.SetVisible(false); _lanIpHandle = ctx.AddTextRow(""); _lanIpHandle.SetVisible(false); _joinFeedbackHandle = ctx.AddTextRow(""); _joinFeedbackHandle.SetVisible(false); ctx.AddSection("Steam"); ctx.AddDescription(CoopLoc.Get("connect.desc.steam", "Connect over Steam instead of a typed IP — no port forwarding needed. Works alongside Direct IP: a host can accept both at once. Create a game first — you can only invite friends while hosting.")); _steamUnavailableHandle = ctx.AddTextRow(CoopLoc.Get("connect.steam.unavailable", "Steam is not available — connect method disabled.")); _steamUnavailableHandle.SetColor(NeutralColor); _steamUnavailableHandle.SetVisible(false); _yourSteamIdHandle = ctx.AddTextRow(""); _yourSteamIdHandle.SetVisible(false); _steamInviteHandle = Handle(ctx.AddButtonRow((SulfurButton[])(object)new SulfurButton[1] { new SulfurButton(CoopLoc.Get("connect.button.inviteFriends", "Invite Friends via Steam"), (Action)OnInviteFriends, 220f) }), 0); _steamIdInputField = ctx.AddInlineTextInput(CoopLoc.Get("connect.label.steamIdToJoin", "Steam ID to join"), _draftSteamId, (Action)delegate(string v) { _draftSteamId = v; }, 160f, false); _steamJoinHandle = Handle(ctx.AddButtonRow((SulfurButton[])(object)new SulfurButton[1] { new SulfurButton(CoopLoc.Get("connect.button.joinViaSteam", "Join via Steam"), (Action)OnJoinViaSteam, 170f) }), 0); _steamPendingInviteHandle = ctx.AddTextRow(""); _steamPendingInviteHandle.SetVisible(false); ctx.AddSection(CoopLoc.Get("connect.section.players", "Players in session")); _playerListHandle = ctx.AddList(); _lastPlayerSig = "\0"; ctx.AddSection(CoopLoc.Get("connect.section.localPrefs", "Local preferences (only affect you)")); ctx.AddToggle(CoopLoc.Get("connect.label.showToasts", "Show player join/leave notifications"), CoopLoc.Get("connect.desc.showToasts", "Brief top-right toasts when a player joins or leaves."), ReadBool(() => Plugin.Cfg.EnableCoopToasts.Value, fallback: true), (Action)delegate(bool v) { try { Plugin.Cfg.EnableCoopToasts.Value = v; } catch { } }); ctx.AddReadonlyText(CoopLoc.Get("connect.label.showHudStatus", "Show network status on HUD"), CoopLoc.Get("connect.value.comingSoon", "Coming soon")); ctx.AddReadonlyText(CoopLoc.Get("connect.label.showNames", "Show other players' names"), CoopLoc.Get("connect.value.comingSoon", "Coming soon")); ctx.AddReadonlyText(CoopLoc.Get("connect.label.rescueKey", "Rescue key"), KeyText(() => ((object)Plugin.Cfg.PlayerReviveHoldKey.Value/*cast due to .constrained prefix*/).ToString())); ctx.AddReadonlyText(CoopLoc.Get("connect.label.confirmEnterKey", "Confirm-enter-boss-room key"), KeyText(() => ((object)Plugin.Cfg.ArenaEnterConfirmKey.Value/*cast due to .constrained prefix*/).ToString())); ctx.AddSection(CoopLoc.Get("connect.section.sessionSettings", "Session settings (host)")); ctx.AddReadonlyText(CoopLoc.Get("connect.label.lootMode", "Loot mode"), CoopLoc.Get("connect.value.lootIndependent", "Independent (Shared coming soon)")); ctx.AddReadonlyText(CoopLoc.Get("connect.label.clientMayStart", "Client may start next level"), CoopLoc.Get("connect.value.comingSoon", "Coming soon")); _ffToggleOption = ctx.AddToggle(CoopLoc.Get("session.friendlyFire.label", "Friendly fire"), CoopLoc.Get("connect.desc.friendlyFire", "Players can damage each other. The host's setting applies to the whole session."), ReadBool(() => Plugin.Cfg.FriendlyFire.Value, fallback: false), (Action)OnFriendlyFireToggled); _ffSessionHandle = ctx.AddTextRow(""); _ffSessionHandle.SetVisible(false); ctx.AddSection(CoopLoc.Get("connect.section.about", "About")); ctx.AddReadonlyText(CoopLoc.Get("connect.label.version", "Version"), "1.0.0"); ctx.AddSmallButton(CoopLoc.Get("connect.button.repo", "Open-source repo"), (Action)delegate { OpenUrl("https://github.com/ryuka-dev/SULFUR-Together"); }, 260f); ctx.AddSmallButton(CoopLoc.Get("connect.button.kofi", "Support on Ko-fi"), (Action)delegate { OpenUrl("https://ko-fi.com/ryukadev"); }, 260f); _lastSavedSig = DraftSig(); ApplyButtonStates(); ApplyJoinFeedback(); ApplyHostLanIp(); ApplySteamState(); ApplySessionFriendlyFireControl(); RefreshPlayerList(); } private static string StatusLine() { NetService service = CoopConnection.Service; if (service == null) { return "● " + CoopLoc.Get("connect.status.notConnected", "Not connected"); } if (NetConnectFeedback.Connecting) { return "◌ " + CoopLoc.Get("connect.status.connecting", "Connecting…"); } return "● " + service.GetConnectionSummary(); } private static Color StatusColor() { //IL_0007: 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_0014: Unknown result type (might be due to invalid IL or missing references) if (CoopConnection.Service == null) { return NeutralColor; } if (!NetConnectFeedback.Connecting) { return OkColor; } return NeutralColor; } private static void ApplyButtonStates() { //IL_00be: Unknown result type (might be due to invalid IL or missing references) NetMode currentMode = CoopConnection.CurrentMode; bool flag = IsInGame(); SulfurButtonHandle createHandle = _createHandle; if (createHandle != null) { createHandle.SetInteractable(flag && currentMode != NetMode.Client); } SulfurButtonHandle joinHandle = _joinHandle; if (joinHandle != null) { joinHandle.SetInteractable(flag && currentMode == NetMode.Off); } SulfurButtonHandle closeHandle = _closeHandle; if (closeHandle != null) { closeHandle.SetInteractable(currentMode != NetMode.Off); } SulfurButtonHandle closeHandle2 = _closeHandle; if (closeHandle2 != null) { closeHandle2.SetLabel((currentMode == NetMode.Client) ? CoopLoc.Get("connect.button.leave", "Leave") : CoopLoc.Get("connect.button.closeRoom", "Close room")); } if (_gateHintHandle != null) { bool flag2 = !flag && currentMode == NetMode.Off; if (flag2) { _gateHintHandle.SetText(CoopLoc.Get("connect.gateHint", "Load a save first — co-op is hosted / joined from inside the game.")); _gateHintHandle.SetColor(NeutralColor); } _gateHintHandle.SetVisible(flag2); } } private static void ApplyJoinFeedback() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (_joinFeedbackHandle != null) { string lastError = NetConnectFeedback.LastError; if (string.IsNullOrEmpty(lastError)) { _joinFeedbackHandle.SetVisible(false); return; } _joinFeedbackHandle.SetText("⚠ " + lastError); _joinFeedbackHandle.SetColor(ErrorColor); _joinFeedbackHandle.SetVisible(true); } } private static void ApplyHostLanIp() { //IL_009c: Unknown result type (might be due to invalid IL or missing references) if (_lanIpHandle == null) { return; } if (CoopConnection.CurrentMode == NetMode.Host && NetLocalAddress.TryGetLanIPv4(out string ip)) { int num = ReadInt(() => Plugin.Cfg.HostPort.Value, 9050); _lanIpHandle.SetText(CoopLoc.Format("connect.lanAddress", "Your LAN address: {ip}:{port} (others on your network join with this)", ("ip", ip.ToString()), ("port", num.ToString()))); _lanIpHandle.SetColor(OkColor); _lanIpHandle.SetVisible(true); } else { _lanIpHandle.SetVisible(false); } } private static void ApplySteamState() { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) bool flag = SteamNetworkingSupportAvailable(); if (_steamUnavailableHandle != null) { _steamUnavailableHandle.SetVisible(!flag); } if (_yourSteamIdHandle != null) { if (flag && TryGetLocalSteamId(out var steamId)) { _yourSteamIdHandle.SetText(CoopLoc.Format("connect.steam.yourId", "Your Steam ID: {id} (share this, or use Invite Friends while hosting)", ("id", steamId.ToString()))); _yourSteamIdHandle.SetColor(NeutralColor); _yourSteamIdHandle.SetVisible(true); } else { _yourSteamIdHandle.SetVisible(false); } } NetMode currentMode = CoopConnection.CurrentMode; bool flag2 = IsInGame(); SulfurButtonHandle steamInviteHandle = _steamInviteHandle; if (steamInviteHandle != null) { steamInviteHandle.SetInteractable(flag && flag2 && currentMode == NetMode.Host); } SulfurButtonHandle steamInviteHandle2 = _steamInviteHandle; if (steamInviteHandle2 != null) { steamInviteHandle2.SetLabel(CoopConnection.SteamHostingEnabled ? CoopLoc.Get("connect.button.inviteMoreFriends", "Invite more friends via Steam") : CoopLoc.Get("connect.button.inviteFriends", "Invite Friends via Steam")); } SulfurButtonHandle steamJoinHandle = _steamJoinHandle; if (steamJoinHandle != null) { steamJoinHandle.SetInteractable(flag && flag2 && currentMode == NetMode.Off); } if (_steamPendingInviteHandle == null) { return; } CSteamID? pendingInviteHostId = SteamRichPresenceJoin.PendingInviteHostId; if (pendingInviteHostId.HasValue && currentMode == NetMode.Off) { string text = pendingInviteHostId.Value.m_SteamID.ToString(); string pendingInviteFriendName = SteamRichPresenceJoin.PendingInviteFriendName; if (flag2 && _autoFilledSteamId != text) { _autoFilledSteamId = text; _draftSteamId = text; if ((Object)(object)_steamIdInputField != (Object)null) { _steamIdInputField.text = text; } _steamPendingInviteHandle.SetText(string.IsNullOrEmpty(pendingInviteFriendName) ? CoopLoc.Get("connect.steam.joiningFriend", "Joining a Steam friend's game…") : CoopLoc.Format("connect.steam.joiningNamed", "Joining {name}'s SULFUR Together game…", ("name", pendingInviteFriendName))); _steamPendingInviteHandle.SetColor(OkColor); _steamPendingInviteHandle.SetVisible(true); JoinViaSteam(pendingInviteHostId.Value, "steam-invite-auto-join"); } else if (!flag2) { _steamPendingInviteHandle.SetText(string.IsNullOrEmpty(pendingInviteFriendName) ? CoopLoc.Get("connect.steam.invitedLoadSave", "A Steam friend invited you — load a save to join automatically.") : CoopLoc.Format("connect.steam.invitedLoadSaveNamed", "{name} invited you — load a save to join automatically.", ("name", pendingInviteFriendName))); _steamPendingInviteHandle.SetColor(OkColor); _steamPendingInviteHandle.SetVisible(true); } } else { _steamPendingInviteHandle.SetVisible(false); } } private static bool SteamNetworkingSupportAvailable() { try { return SteamNetworkingSupport.IsAvailable; } catch { return false; } } private static bool TryGetLocalSteamId(out ulong steamId64) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) steamId64 = 0uL; try { if (!SteamNetworkingSupport.TryGetLocalSteamId(out var id)) { return false; } steamId64 = id.m_SteamID; return true; } catch { return false; } } private static void RefreshPlayerList() { if (_playerListHandle == null) { return; } NetService service = CoopConnection.Service; IReadOnlyList readOnlyList2; if (service == null) { IReadOnlyList readOnlyList = Array.Empty(); readOnlyList2 = readOnlyList; } else { readOnlyList2 = service.GetPlayerRows(); } IReadOnlyList rows = readOnlyList2; string text = string.Join("|", rows); if (text == _lastPlayerSig) { return; } _lastPlayerSig = text; _playerListHandle.Update((Action)delegate(SulfurOptionsContext c) { if (rows.Count == 0) { c.AddTextRow(CoopLoc.Get("connect.players.none", "No players connected.")); return; } foreach (string item in rows) { c.AddTextRow(item); } }); } private static void OnCreate() { if (IsInGame()) { SaveSettings(); CoopConnection.Apply(NetMode.Host, "ui-create"); ApplyButtonStates(); } } private static void OnJoin() { if (IsInGame()) { _closeMenuOnJoinSuccess = true; SaveSettings(); CoopConnection.Apply(NetMode.Client, "ui-join"); NetLinkState.SetClientLinked(on: true, "ui-join"); ApplyButtonStates(); ApplyJoinFeedback(); } } private static void PollJoinClose() { if (_closeMenuOnJoinSuccess && !NetConnectFeedback.Connecting) { bool num = CoopConnection.CurrentMode == NetMode.Client && string.IsNullOrEmpty(NetConnectFeedback.LastError); _closeMenuOnJoinSuccess = false; if (num) { CoopMenu.CloseIfOpen("ui-join-success"); } } } private static void OnInviteFriends() { if (IsInGame() && CoopConnection.CurrentMode == NetMode.Host) { CoopConnection.EnableSteamHosting("ui-steam-invite"); ApplySteamState(); } } private static void OnJoinViaSteam() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (IsInGame()) { if (!ulong.TryParse((_draftSteamId ?? "").Trim(), out var result) || result == 0L) { NetConnectFeedback.ReportError(CoopLoc.Get("connect.error.invalidSteamId", "Enter a valid Steam ID (numbers only) — or use Invite Friends / accept a Steam invite.")); } else { JoinViaSteam(new CSteamID(result), "ui-join-steam"); } } } private static void JoinViaSteam(CSteamID hostId, string reason) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) Plugin.Cfg.LastSteamIdToJoin.Value = hostId.m_SteamID.ToString(); SteamRichPresenceJoin.ConsumePendingInvite(); _closeMenuOnJoinSuccess = true; CoopConnection.ApplySteamClient(hostId, reason); NetLinkState.SetClientLinked(on: true, reason); ApplyButtonStates(); ApplyJoinFeedback(); } private static void OnCloseRoom() { _closeMenuOnJoinSuccess = false; CoopConnection.Stop("ui-close-room"); ApplyButtonStates(); } private static void LoadDraftsFromConfig() { _draftName = Plugin.Cfg.PlayerName.Value; _draftAddress = Plugin.Cfg.HostAddress.Value; _draftPort = Plugin.Cfg.HostPort.Value.ToString(); _draftKey = Plugin.Cfg.ConnectionKey.Value; _draftSteamId = Plugin.Cfg.LastSteamIdToJoin.Value; if ((string.IsNullOrWhiteSpace(_draftName) || _draftName == "Player") && SteamIdentity.TryGetPersonaName(out string name)) { _draftName = name; } } private static void SaveSettings() { Plugin.Cfg.PlayerName.Value = (string.IsNullOrWhiteSpace(_draftName) ? "Player" : _draftName.Trim()); Plugin.Cfg.HostAddress.Value = (string.IsNullOrWhiteSpace(_draftAddress) ? "127.0.0.1" : _draftAddress.Trim()); if (int.TryParse(_draftPort, out var result) && result > 0 && result < 65536) { Plugin.Cfg.HostPort.Value = result; } Plugin.Cfg.ConnectionKey.Value = _draftKey ?? ""; Plugin.Cfg.LastSteamIdToJoin.Value = _draftSteamId ?? ""; } private static string DraftSig() { return _draftName + "\0" + _draftAddress + "\0" + _draftPort + "\0" + _draftKey + "\0" + _draftSteamId; } private static void AutoSaveDrafts() { string text = DraftSig(); if (!(text == _lastSavedSig)) { _lastSavedSig = text; SaveSettings(); } } private static void ResetHandles() { _ctx = null; _statusHandle = null; _joinFeedbackHandle = null; _gateHintHandle = null; _lanIpHandle = null; _createHandle = null; _joinHandle = null; _playerListHandle = null; _lastPlayerSig = "\0"; _steamUnavailableHandle = null; _yourSteamIdHandle = null; _steamInviteHandle = null; _steamJoinHandle = null; _steamPendingInviteHandle = null; _steamIdInputField = null; _ffSessionHandle = null; _ffToggleOption = null; _ffLockApplied = false; } private static void OnFriendlyFireToggled(bool value) { if (CoopConnection.CurrentMode == NetMode.Client) { ApplySessionFriendlyFireControl(); return; } bool flag = false; try { flag = Plugin.Cfg.FriendlyFire.Value != value; Plugin.Cfg.FriendlyFire.Value = value; } catch { } try { if (CoopConnection.CurrentMode == NetMode.Host) { CoopConnection.Service?.BroadcastSessionSettings("ui-toggle"); if (flag) { CoopToasts.NotifySessionSetting(CoopLoc.Get("session.friendlyFire.label", "Friendly fire"), value); } } } catch (Exception ex) { Plugin.Log?.Warn("[CoopUi] friendly-fire broadcast failed: " + ex.Message); } } private static void ApplySessionFriendlyFireControl() { //IL_00f6: Unknown result type (might be due to invalid IL or missing references) bool flag = CoopConnection.CurrentMode == NetMode.Client; if ((Object)(object)_ffToggleOption != (Object)null) { object? obj = FfCheckboxField?.GetValue(_ffToggleOption); Toggle val = (Toggle)((obj is Toggle) ? obj : null); if (flag != _ffLockApplied) { ApplyFfRowLock(flag); _ffLockApplied = flag; } bool flag2 = (flag ? NetSessionSettings.FriendlyFireEnabled : ReadBool(() => Plugin.Cfg.FriendlyFire.Value, fallback: false)); if ((Object)(object)val != (Object)null && val.isOn != flag2) { val.SetIsOnWithoutNotify(flag2); } } if (_ffSessionHandle != null) { if (flag) { _ffSessionHandle.SetText(CoopLoc.Format("connect.ffSession", "Session friendly fire: {state} (set by host)", ("state", NetSessionSettings.FriendlyFireEnabled ? CoopLoc.Get("common.onUpper", "ON") : CoopLoc.Get("common.offUpper", "OFF")))); _ffSessionHandle.SetColor(NeutralColor); _ffSessionHandle.SetVisible(true); } else { _ffSessionHandle.SetVisible(false); } } } private static void ApplyFfRowLock(bool locked) { if (!((Object)(object)_ffToggleOption == (Object)null)) { try { FfIsLockedField?.SetValue(_ffToggleOption, locked); } catch { } GameObject gameObject = ((Component)_ffToggleOption).gameObject; CanvasGroup val = gameObject.GetComponent(); if ((Object)(object)val == (Object)null) { val = gameObject.AddComponent(); } val.alpha = (locked ? 0.45f : 1f); val.interactable = !locked; val.blocksRaycasts = !locked; } } private static SulfurButtonHandle Handle(IReadOnlyList handles, int index) { if (handles == null || index < 0 || index >= handles.Count) { return null; } return handles[index]; } private static bool ReadBool(Func read, bool fallback) { try { return read(); } catch { return fallback; } } private static int ReadInt(Func read, int fallback) { try { return read(); } catch { return fallback; } } private static string KeyText(Func read) { try { return read(); } catch { return "(unset)"; } } private static void OpenUrl(string url) { try { Application.OpenURL(url); } catch (Exception ex) { Plugin.Log?.Warn("[CoopUi] open URL failed: " + ex.Message); } } private static bool IsInGame() { //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) try { Scene activeScene = SceneManager.GetActiveScene(); return string.Equals(((Scene)(ref activeScene)).name ?? "", "GameScene", StringComparison.OrdinalIgnoreCase); } catch { return false; } } } internal static class CoopLoc { private static Func? _get; private static Func? _languageVersion; public static int LanguageVersion { get { Func languageVersion = _languageVersion; if (languageVersion == null) { return 0; } try { return languageVersion(); } catch { return 0; } } } public static void Wire(string pluginAssemblyLocation) { try { Type type = AccessTools.TypeByName("Ryuka.Sulfur.NativeUI.SulfurLocalization"); if (type == null) { Plugin.Log?.Info("[CoopLoc] SulfurLocalization not present — player-facing text stays English."); return; } MethodInfo methodInfo = AccessTools.Method(type, "LoadPluginLocalization", new Type[2] { typeof(string), typeof(string) }, (Type[])null); if (methodInfo != null && !string.IsNullOrEmpty(pluginAssemblyLocation)) { methodInfo.Invoke(null, new object[2] { "com.ryuka.sulfur.together", pluginAssemblyLocation }); } else { Plugin.Log?.Warn("[CoopLoc] LoadPluginLocalization missing or no assembly location — lang files not loaded."); } MethodInfo methodInfo2 = AccessTools.Method(type, "Get", new Type[3] { typeof(string), typeof(string), typeof(string) }, (Type[])null); if (methodInfo2 != null) { _get = (Func)Delegate.CreateDelegate(typeof(Func), methodInfo2); } MethodInfo methodInfo3 = AccessTools.PropertyGetter(type, "LanguageVersion"); if (methodInfo3 != null) { _languageVersion = (Func)Delegate.CreateDelegate(typeof(Func), methodInfo3); } Plugin.Log?.Info($"[CoopLoc] localization wired (lookup={_get != null}, languageVersion={_languageVersion != null})."); } catch (Exception ex) { Plugin.Log?.Warn("[CoopLoc] wire failed — player-facing text stays English: " + ex.Message); } } public static string Get(string key, string fallback) { Func get = _get; if (get == null) { return fallback; } try { return get("com.ryuka.sulfur.together", key, fallback); } catch { return fallback; } } public static string Format(string key, string fallback, params (string token, string value)[] args) { string text = Get(key, fallback); if (args != null) { for (int i = 0; i < args.Length; i++) { (string token, string value) tuple = args[i]; string item = tuple.token; string item2 = tuple.value; text = text.Replace("{" + item + "}", item2 ?? ""); } } return text; } } internal static class CoopToasts { private static Action _show; public static void Wire(Action showToast) { _show = showToast; } public static void Notify(string message) { Notify(null, message); } public static void NotifySessionSetting(string settingLabel, bool enabled) { Notify(null, CoopLoc.Format("session.settingChanged", "{label}: {state}", ("label", settingLabel), ("state", enabled ? CoopLoc.Get("common.on", "On") : CoopLoc.Get("common.off", "Off"))), respectPreference: false); } public static void Notify(string title, string message) { Notify(title, message, respectPreference: true); } private static void Notify(string title, string message, bool respectPreference) { if (string.IsNullOrEmpty(message)) { return; } if (respectPreference) { bool flag = true; try { flag = Plugin.Cfg.EnableCoopToasts.Value; } catch { } if (!flag) { return; } } string text = (string.IsNullOrEmpty(title) ? CoopLoc.Get("toast.title.default", "Together") : title); Plugin.Log?.Info("[CoopToast] " + text + ": " + message); try { _show?.Invoke(text, message); } catch (Exception ex) { Plugin.Log?.Warn("[CoopToast] toast show failed: " + ex.Message); } } } internal static class NativeFontSampler { public static TMP_FontAsset? ResolveNativeFont() { try { TextMeshProUGUI[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (TextMeshProUGUI val in array) { if (!((Object)(object)val == (Object)null) && !((Object)(object)((TMP_Text)val).font == (Object)null) && ((Component)val).gameObject.activeInHierarchy) { return ((TMP_Text)val).font; } } } catch { } try { return TMP_Settings.defaultFontAsset; } catch { return null; } } } } namespace SULFURTogether.UI.RunStatsOverlay { internal sealed class RunStatsBestMarks { public const int StatCount = 7; private static readonly bool[] LowerIsBetter = new bool[7] { false, false, false, true, false, true, false }; private readonly int[] _bestValue = new int[7]; private readonly bool[] _marked = new bool[7]; public static readonly RunStatsBestMarks None = new RunStatsBestMarks(); private RunStatsBestMarks() { } public static RunStatsBestMarks Compute(IReadOnlyList players) { RunStatsBestMarks runStatsBestMarks = new RunStatsBestMarks(); if (players.Count == 0) { return runStatsBestMarks; } for (int i = 0; i < 7; i++) { int num = int.MaxValue; int num2 = int.MinValue; foreach (NetRunStats player in players) { int stat = GetStat(player, i); if (stat < num) { num = stat; } if (stat > num2) { num2 = stat; } } runStatsBestMarks._bestValue[i] = (LowerIsBetter[i] ? num : num2); runStatsBestMarks._marked[i] = num != num2; } return runStatsBestMarks; } public bool IsBest(int statIndex, int value) { if (_marked[statIndex]) { return value == _bestValue[statIndex]; } return false; } private static int GetStat(NetRunStats stats, int statIndex) { return statIndex switch { 0 => stats.ShotsFired, 1 => stats.DamageDealt, 2 => stats.Kills, 3 => stats.TimesDowned, 4 => stats.Rescues, 5 => stats.DamageTaken, _ => stats.DestructiblesDestroyed, }; } } internal static class RunStatsCanvasBuilder { public const float CardWidth = 340f; public const float ViewportWidth = 1600f; public static float ActiveCardSpacing = 44f; private const float VerticalCenterBiasPx = 60f; private const float ViewportHeightPx = 560f; public static GameObject BuildRoot(out RectTransform viewport, out RectTransform track) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected O, but got Unknown //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Expected O, but got Unknown //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("RunStatsOverlayCanvas", new Type[4] { typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster) }); Canvas component = val.GetComponent(); component.renderMode = (RenderMode)0; component.sortingOrder = 0; CanvasScaler component2 = val.GetComponent(); component2.uiScaleMode = (ScaleMode)1; component2.referenceResolution = new Vector2(1920f, 1080f); component2.screenMatchMode = (ScreenMatchMode)0; component2.matchWidthOrHeight = 0.85f; GameObject val2 = new GameObject("Viewport", new Type[2] { typeof(RectTransform), typeof(RectMask2D) }); val2.transform.SetParent(val.transform, false); RectTransform val3 = (RectTransform)val2.transform; val3.anchorMin = new Vector2(0.5f, 0.5f); val3.anchorMax = new Vector2(0.5f, 0.5f); val3.pivot = new Vector2(0.5f, 0.5f); val3.anchoredPosition = new Vector2(0f, 60f); val3.sizeDelta = new Vector2(1600f, 560f); GameObject val4 = new GameObject("Track", new Type[3] { typeof(RectTransform), typeof(HorizontalLayoutGroup), typeof(ContentSizeFitter) }); val4.transform.SetParent(val2.transform, false); RectTransform val5 = (RectTransform)val4.transform; val5.anchorMin = new Vector2(0f, 0.5f); val5.anchorMax = new Vector2(0f, 0.5f); val5.pivot = new Vector2(0f, 0.5f); val5.anchoredPosition = Vector2.zero; HorizontalLayoutGroup component3 = val4.GetComponent(); ((LayoutGroup)component3).childAlignment = (TextAnchor)3; ((HorizontalOrVerticalLayoutGroup)component3).spacing = ActiveCardSpacing; ((HorizontalOrVerticalLayoutGroup)component3).childForceExpandWidth = false; ((HorizontalOrVerticalLayoutGroup)component3).childForceExpandHeight = false; ((HorizontalOrVerticalLayoutGroup)component3).childControlWidth = false; ((HorizontalOrVerticalLayoutGroup)component3).childControlHeight = false; ContentSizeFitter component4 = val4.GetComponent(); component4.horizontalFit = (FitMode)2; component4.verticalFit = (FitMode)2; viewport = val3; track = val5; return val; } } internal sealed class RunStatsCardHoverAnimator { private const float MaxLeanDegrees = 3.5f; private const float MaxSquash = 0.045f; private const float HotScale = 1.05f; private const float SlideTowardCursor = 5f; private const float LiftUp = 8f; private const float ShadowCounterSlide = 4f; private readonly RectTransform _rect; private readonly Shadow _shadow; private readonly Image _titleBandImage; private readonly Vector2 _baselinePos; private readonly Vector2 _shadowBaseDistance; private readonly Vector3 _restScale; private RunStatsSpring _aimX; private RunStatsSpring _aimY; private RunStatsSpring _hot; public RunStatsCardHoverAnimator(RunStatsCardView card) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_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_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) _rect = card.Rect; _shadow = card.ShadowFx; _titleBandImage = card.TitleBandImage; _baselinePos = _rect.anchoredPosition; _shadowBaseDistance = _shadow.effectDistance; _restScale = ((Transform)_rect).localScale; } public bool IsPointerOver(Vector2 pointerPosition) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return RectTransformUtility.RectangleContainsScreenPoint(_rect, pointerPosition, (Camera)null); } public void Tick(float deltaTime, bool isHot, Vector2 pointerPosition) { //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0184: 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_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: 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_021a: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_004e: 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_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) float target = 0f; float target2 = 0f; float target3 = 0f; Vector2 val = default(Vector2); if (isHot && RectTransformUtility.ScreenPointToLocalPointInRectangle(_rect, pointerPosition, (Camera)null, ref val)) { Rect rect = _rect.rect; target = ((((Rect)(ref rect)).width > 0.01f) ? Mathf.Clamp((val.x - ((Rect)(ref rect)).center.x) / (((Rect)(ref rect)).width * 0.5f), -1f, 1f) : 0f); target2 = ((((Rect)(ref rect)).height > 0.01f) ? Mathf.Clamp((val.y - ((Rect)(ref rect)).center.y) / (((Rect)(ref rect)).height * 0.5f), -1f, 1f) : 0f); target3 = 1f; } float num = _aimX.Tick(target, deltaTime); float num2 = _aimY.Tick(target2, deltaTime); float num3 = Mathf.Clamp01(_hot.Tick(target3, deltaTime)); ((Transform)_rect).localRotation = Quaternion.Euler(0f, 0f, num * 3.5f); float num4 = 1f + 0.049999952f * num3; ((Transform)_rect).localScale = new Vector3(_restScale.x * num4 * (1f - 0.045f * Mathf.Abs(num)), _restScale.y * num4 * (1f - 0.045f * Mathf.Abs(num2)), _restScale.z); _rect.anchoredPosition = _baselinePos + new Vector2(num * 5f, num2 * 5f + 8f * num3); _shadow.effectDistance = _shadowBaseDistance + new Vector2((0f - num) * 4f, (0f - num2) * 4f); Color color = ((Graphic)_titleBandImage).color; color.a = Mathf.Lerp(0.1f, 0.16f, num3); ((Graphic)_titleBandImage).color = color; } } internal sealed class RunStatsCardView { private static readonly (string key, string en)[] LabelDefs = new(string, string)[7] { ("runstats.stat.shotsFired", "Shots Fired"), ("runstats.stat.damageDealt", "Damage Dealt"), ("runstats.stat.kills", "Kills"), ("runstats.stat.timesDowned", "Times Downed"), ("runstats.stat.rescues", "Rescues"), ("runstats.stat.damageTaken", "Damage Taken"), ("runstats.stat.destructiblesDestroyed", "Destructibles Destroyed") }; private static readonly Color BodyColor = new Color(0.085f, 0.065f, 0.05f, 0.97f); private static readonly Color InnerTint = new Color(1f, 0.58f, 0.28f, 0.045f); private static readonly Color NormalBorder = new Color(0.72f, 0.71f, 0.66f, 0.95f); private static readonly Color LocalBorder = new Color(0.99f, 0.72f, 0.18f, 1f); private static readonly Color LocalGlow = new Color(1f, 0.55f, 0.15f, 0.3f); private static readonly Color DropShadow = new Color(0f, 0f, 0f, 0.55f); private static readonly Color TitleBandColor = new Color(1f, 0.5f, 0.2f, 1f); private static readonly Color LabelColor = new Color(0.84f, 0.81f, 0.76f, 1f); private static readonly Color ValueColor = new Color(0.99f, 0.97f, 0.94f, 1f); private static readonly Color BestValueColor = new Color(1f, 0.6f, 0.25f, 1f); private static readonly Color NameColor = Color.white; private static readonly Color LocalNameColor = new Color(1f, 0.8f, 0.28f, 1f); public const float RestHighlightAlpha = 0.1f; public const float HotHighlightAlpha = 0.16f; private const float TitleBandHeight = 62f; private readonly GameObject _root; private readonly RectTransform _rect; private readonly TextMeshProUGUI _nameText; private readonly TextMeshProUGUI[] _valueTexts; private readonly Outline _border; private readonly Outline _glow; public GameObject Root => _root; public RectTransform Rect => _rect; public Shadow ShadowFx { get; } public RectTransform TitleBandRect { get; } public Image TitleBandImage { get; } private RunStatsCardView(GameObject root, TextMeshProUGUI nameText, TextMeshProUGUI[] valueTexts, Outline border, Outline glow, Shadow shadow, RectTransform titleBandRect, Image titleBandImage) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown _root = root; _rect = (RectTransform)root.transform; _nameText = nameText; _valueTexts = valueTexts; _border = border; _glow = glow; ShadowFx = shadow; TitleBandRect = titleBandRect; TitleBandImage = titleBandImage; } public static RunStatsCardView Create(Transform parent, TMP_FontAsset? font) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown //IL_0065: 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_0080: 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_00ad: 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_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Expected O, but got Unknown //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: 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) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_0364: Unknown result type (might be due to invalid IL or missing references) //IL_0369: Unknown result type (might be due to invalid IL or missing references) //IL_037b: Unknown result type (might be due to invalid IL or missing references) //IL_038b: Unknown result type (might be due to invalid IL or missing references) //IL_03b7: Unknown result type (might be due to invalid IL or missing references) //IL_0401: Unknown result type (might be due to invalid IL or missing references) //IL_043e: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("RunStatsCard", new Type[4] { typeof(RectTransform), typeof(Image), typeof(VerticalLayoutGroup), typeof(ContentSizeFitter) }); val.transform.SetParent(parent, false); RectTransform val2 = (RectTransform)val.transform; val2.sizeDelta = new Vector2(340f, val2.sizeDelta.y); Image component = val.GetComponent(); ((Graphic)component).color = BodyColor; ((Graphic)component).raycastTarget = false; Outline val3 = val.AddComponent(); ((Shadow)val3).effectColor = NormalBorder; ((Shadow)val3).effectDistance = new Vector2(2f, -2f); ((Shadow)val3).useGraphicAlpha = false; Outline val4 = val.AddComponent(); ((Shadow)val4).effectColor = LocalGlow; ((Shadow)val4).effectDistance = new Vector2(4.5f, -4.5f); ((Shadow)val4).useGraphicAlpha = false; ((Behaviour)val4).enabled = false; Shadow val5 = val.AddComponent(); val5.effectColor = DropShadow; val5.effectDistance = new Vector2(5f, -5f); val5.useGraphicAlpha = false; VerticalLayoutGroup component2 = val.GetComponent(); ((LayoutGroup)component2).padding = new RectOffset(20, 20, 18, 18); ((HorizontalOrVerticalLayoutGroup)component2).spacing = 6f; ((LayoutGroup)component2).childAlignment = (TextAnchor)1; ((HorizontalOrVerticalLayoutGroup)component2).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)component2).childForceExpandHeight = false; ((HorizontalOrVerticalLayoutGroup)component2).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)component2).childControlHeight = true; ContentSizeFitter component3 = val.GetComponent(); component3.horizontalFit = (FitMode)0; component3.verticalFit = (FitMode)2; (RectTransform rect, Image image) tuple = CreateDecoration(val.transform, "InnerPanel", InnerTint); tuple.rect.anchorMin = Vector2.zero; tuple.rect.anchorMax = Vector2.one; tuple.rect.offsetMin = new Vector2(5f, 5f); tuple.rect.offsetMax = new Vector2(-5f, -5f); (RectTransform, Image) tuple2 = CreateDecoration(val.transform, "TitleBand", new Color(TitleBandColor.r, TitleBandColor.g, TitleBandColor.b, 0.1f)); tuple2.Item1.anchorMin = new Vector2(0f, 1f); tuple2.Item1.anchorMax = new Vector2(1f, 1f); tuple2.Item1.pivot = new Vector2(0.5f, 1f); tuple2.Item1.offsetMin = new Vector2(5f, -62f); tuple2.Item1.offsetMax = new Vector2(-5f, -5f); TextMeshProUGUI nameText = CreateText(val.transform, font, 30f, (FontStyles)1, (TextAlignmentOptions)514, 38f); GameObject val6 = new GameObject("NameSeparator", new Type[2] { typeof(RectTransform), typeof(LayoutElement) }); val6.transform.SetParent(val.transform, false); val6.GetComponent().minHeight = 6f; TextMeshProUGUI[] array = (TextMeshProUGUI[])(object)new TextMeshProUGUI[LabelDefs.Length]; for (int i = 0; i < LabelDefs.Length; i++) { GameObject val7 = new GameObject($"Row_{i}", new Type[3] { typeof(RectTransform), typeof(HorizontalLayoutGroup), typeof(LayoutElement) }); val7.transform.SetParent(val.transform, false); val7.GetComponent().minHeight = 30f; HorizontalLayoutGroup component4 = val7.GetComponent(); ((HorizontalOrVerticalLayoutGroup)component4).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)component4).childForceExpandHeight = true; ((HorizontalOrVerticalLayoutGroup)component4).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)component4).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)component4).spacing = 14f; TextMeshProUGUI obj = CreateText(val7.transform, font, 19f, (FontStyles)0, (TextAlignmentOptions)513, 28f); ((TMP_Text)obj).text = CoopLoc.Get(LabelDefs[i].key, LabelDefs[i].en); ((Graphic)obj).color = LabelColor; ((Component)obj).gameObject.AddComponent().flexibleWidth = 1f; TextMeshProUGUI val8 = CreateText(val7.transform, font, 23f, (FontStyles)1, (TextAlignmentOptions)516, 28f); ((Graphic)val8).color = ValueColor; ((Component)val8).gameObject.AddComponent().minWidth = 70f; array[i] = val8; } return new RunStatsCardView(val, nameText, array, val3, val4, val5, tuple2.Item1, tuple2.Item2); } private static (RectTransform rect, Image image) CreateDecoration(Transform parent, string name, Color color) { //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_0040: 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_0054: 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_0071: Expected O, but got Unknown GameObject val = new GameObject(name, new Type[3] { typeof(RectTransform), typeof(Image), typeof(LayoutElement) }); val.transform.SetParent(parent, false); val.GetComponent().ignoreLayout = true; Image component = val.GetComponent(); ((Graphic)component).color = color; ((Graphic)component).raycastTarget = false; return (rect: (RectTransform)val.transform, image: component); } private static TextMeshProUGUI CreateText(Transform parent, TMP_FontAsset? font, float size, FontStyles style, TextAlignmentOptions align, float minHeight) { //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_0037: 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_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) GameObject val = new GameObject("Text", new Type[2] { typeof(RectTransform), typeof(TextMeshProUGUI) }); val.transform.SetParent(parent, false); TextMeshProUGUI component = val.GetComponent(); if ((Object)(object)font != (Object)null) { ((TMP_Text)component).font = font; } ((TMP_Text)component).fontSize = size; ((TMP_Text)component).fontStyle = style; ((TMP_Text)component).alignment = align; ((Graphic)component).color = Color.white; ((Graphic)component).raycastTarget = false; ((TMP_Text)component).enableAutoSizing = true; ((TMP_Text)component).fontSizeMin = size * 0.7f; ((TMP_Text)component).fontSizeMax = size; val.AddComponent().minHeight = minHeight; return component; } public void Bind(NetRunStats stats, bool isLocalPlayer, RunStatsBestMarks best) { //IL_0064: 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_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) string text = (string.IsNullOrWhiteSpace(stats.PlayerName) ? stats.PeerId : stats.PlayerName); ((TMP_Text)_nameText).text = (isLocalPlayer ? CoopLoc.Format("runstats.youSuffix", "{name} (You)", ("name", text)) : text); ((Graphic)_nameText).color = (isLocalPlayer ? LocalNameColor : NameColor); ((Shadow)_border).effectColor = (isLocalPlayer ? LocalBorder : NormalBorder); ((Behaviour)_glow).enabled = isLocalPlayer; SetValue(0, stats.ShotsFired, best); SetValue(1, stats.DamageDealt, best); SetValue(2, stats.Kills, best); SetValue(3, stats.TimesDowned, best); SetValue(4, stats.Rescues, best); SetValue(5, stats.DamageTaken, best); SetValue(6, stats.DestructiblesDestroyed, best); } public void SetEmpty() { //IL_0016: 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_005a: Unknown result type (might be due to invalid IL or missing references) ((TMP_Text)_nameText).text = "…"; ((Graphic)_nameText).color = NameColor; ((Shadow)_border).effectColor = NormalBorder; ((Behaviour)_glow).enabled = false; for (int i = 0; i < _valueTexts.Length; i++) { ((TMP_Text)_valueTexts[i]).text = "…"; ((Graphic)_valueTexts[i]).color = ValueColor; } } private void SetValue(int index, int value, RunStatsBestMarks best) { //IL_002d: 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) ((TMP_Text)_valueTexts[index]).text = value.ToString(); ((Graphic)_valueTexts[index]).color = (best.IsBest(index, value) ? BestValueColor : ValueColor); } } internal sealed class RunStatsCarouselController { private const int VisibleCards = 4; private const float OverflowLeftPad = 24f; private RectTransform? _track; private int _cardCount; private int _focusIndex; private RunStatsSpring _x; public void SetTrack(RectTransform track) { _track = track; } public void ResetForNewData(int cardCount) { _cardCount = Mathf.Max(0, cardCount); _focusIndex = 0; _x.Snap(TargetX()); Apply(); } public void MoveFocus(int delta) { _focusIndex = Mathf.Clamp(_focusIndex + delta, 0, MaxFocus()); } public void Tick(float deltaTime) { _x.Tick(TargetX(), deltaTime); Apply(); } private int MaxFocus() { return Mathf.Max(0, _cardCount - 4); } private float TargetX() { float activeCardSpacing = RunStatsCanvasBuilder.ActiveCardSpacing; float num = (float)_cardCount * 340f + (float)Mathf.Max(0, _cardCount - 1) * activeCardSpacing; return Mathf.Max(24f, (1600f - num) * 0.5f) - (float)_focusIndex * (340f + activeCardSpacing); } private void Apply() { //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_0033: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_track == (Object)null)) { Vector2 anchoredPosition = _track.anchoredPosition; anchoredPosition.x = _x.Value; _track.anchoredPosition = anchoredPosition; } } } internal static class RunStatsCursorControl { private static bool _held; private static bool _savedVisible; private static CursorLockMode _savedLockState; public static void Acquire() { //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) if (!_held) { _savedVisible = Cursor.visible; _savedLockState = Cursor.lockState; _held = true; Enforce(); } } public static void Enforce() { if (_held) { Cursor.visible = true; Cursor.lockState = (CursorLockMode)0; } } public static void Release() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (_held) { _held = false; Cursor.visible = _savedVisible; Cursor.lockState = _savedLockState; } } } internal static class RunStatsDevInjector { private static readonly List _simulated = new List(); private static readonly Random _rng = new Random(); private static int _nextId = 1; private static bool _keyboardBroken; public static int Version { get; private set; } public static IReadOnlyList Simulated => _simulated; public static void Poll() { if (_keyboardBroken || !GameManager.DeveloperMode) { return; } try { if (!EndKeyPressedThisFrame()) { return; } } catch (Exception ex) { _keyboardBroken = true; NetLogger.Warn("[RunStatsOverlay] dev-injector keyboard poll failed — End key disabled: " + ex.GetType().Name + ": " + ex.Message); return; } int num = _nextId++; _simulated.Add(new NetRunStats { PeerId = $"sim-{num}", PlayerName = $"Tester {num}", ShotsFired = _rng.Next(0, 500), DamageDealt = _rng.Next(0, 8000), Kills = _rng.Next(0, 60), TimesDowned = _rng.Next(0, 6), Rescues = _rng.Next(0, 6), DamageTaken = _rng.Next(0, 2000), DestructiblesDestroyed = _rng.Next(0, 40) }); Version++; NetLogger.Info($"[RunStatsOverlay] dev-injector added simulated player {num} (total {_simulated.Count})"); } public static void Clear() { if (_simulated.Count != 0) { _simulated.Clear(); _nextId = 1; Version++; } } [MethodImpl(MethodImplOptions.NoInlining)] private static bool EndKeyPressedThisFrame() { Keyboard current = Keyboard.current; if (current != null) { return ((ButtonControl)current.endKey).wasPressedThisFrame; } return false; } } internal static class RunStatsInputReader { private static bool _mouseBroken; private static bool _gamepadBroken; public static int PollDelta() { if (!_mouseBroken) { try { float num = ReadMouseScrollY(); if (num > 0.01f) { return -1; } if (num < -0.01f) { return 1; } } catch (Exception ex) { _mouseBroken = true; NetLogger.Warn("[RunStatsOverlay] mouse poll failed — mouse input disabled: " + ex.GetType().Name + ": " + ex.Message); } } if (!_gamepadBroken) { try { int num2 = PollGamepadDelta(); if (num2 != 0) { return num2; } } catch (Exception ex2) { _gamepadBroken = true; NetLogger.Warn("[RunStatsOverlay] gamepad poll failed — gamepad navigation disabled: " + ex2.GetType().Name + ": " + ex2.Message); } } return 0; } public static bool TryGetPointerPosition(out Vector2 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) position = default(Vector2); if (_mouseBroken) { return false; } try { return ReadMousePosition(out position); } catch (Exception ex) { _mouseBroken = true; NetLogger.Warn("[RunStatsOverlay] mouse poll failed — mouse input disabled: " + ex.GetType().Name + ": " + ex.Message); return false; } } [MethodImpl(MethodImplOptions.NoInlining)] private static float ReadMouseScrollY() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) Mouse current = Mouse.current; if (current == null) { return 0f; } return ((InputControl)(object)current.scroll).ReadValue().y; } [MethodImpl(MethodImplOptions.NoInlining)] private static bool ReadMousePosition(out Vector2 position) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) Mouse current = Mouse.current; if (current == null) { position = default(Vector2); return false; } position = ((InputControl)(object)((Pointer)current).position).ReadValue(); return true; } [MethodImpl(MethodImplOptions.NoInlining)] private static int PollGamepadDelta() { Gamepad current = Gamepad.current; if (current == null) { return 0; } if (current.dpad.right.wasPressedThisFrame || current.rightShoulder.wasPressedThisFrame) { return 1; } if (current.dpad.left.wasPressedThisFrame || current.leftShoulder.wasPressedThisFrame) { return -1; } return 0; } } internal static class RunStatsOverlayManager { private static GameObject? _root; private static RectTransform? _viewport; private static RectTransform? _track; private static readonly RunStatsCarouselController _carousel = new RunStatsCarouselController(); private static readonly List _cards = new List(); private static readonly List _hoverAnimators = new List(); private static IReadOnlyList? _boundList; private static int _boundSimVersion = -1; private static bool _shownLastTick; private static string _lastWarned = ""; public static void Tick() { bool flag; try { flag = ApplyLifecycle(); } catch (Exception ex) { WarnOnce("lifecycle failed: " + ex.GetType().Name + ": " + ex.Message); return; } try { if (IsInGameScene()) { RunStatsDevInjector.Poll(); } } catch (Exception ex2) { WarnOnce("dev injector failed: " + ex2.GetType().Name + ": " + ex2.Message); } if (!flag) { return; } try { TickInteraction(Time.unscaledDeltaTime); } catch (Exception ex3) { WarnOnce("interaction failed (cards stay shown, animation skipped): " + ex3.GetType().Name + ": " + ex3.Message); } } private static bool ApplyLifecycle() { NetRunState state; bool flag = NetRunStateBridge.TryGetLocalRunState(out state) && state.IsLoadingLikeState; bool pendingRunEndDisplay = NetRunStatsClientCache.PendingRunEndDisplay; bool flag2 = pendingRunEndDisplay && flag && IsNetworkSessionActive() && IsInGameScene(); if (flag2) { EnsureBuilt(); if ((Object)(object)_root != (Object)null && !_root.activeSelf) { _root.SetActive(true); ApplyTopmostSortingOrder(); NetLogger.Info("[RunStatsOverlay] show"); } RunStatsCursorControl.Acquire(); RunStatsCursorControl.Enforce(); RefreshIfChanged(); } else if (_shownLastTick) { NetLogger.Info($"[RunStatsOverlay] hide (loading={flag} pending={pendingRunEndDisplay} networkActive={IsNetworkSessionActive()} inGameScene={IsInGameScene()})"); HideAndClear(); } _shownLastTick = flag2; return flag2; } private static void WarnOnce(string message) { if (!(message == _lastWarned)) { _lastWarned = message; NetLogger.Warn("[RunStatsOverlay] " + message); } } public static void Shutdown() { if (_shownLastTick) { HideAndClear(); } _shownLastTick = false; } private static void TickInteraction(float dt) { //IL_003a: 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_0057: Unknown result type (might be due to invalid IL or missing references) int num = RunStatsInputReader.PollDelta(); if (num != 0) { _carousel.MoveFocus(num); } _carousel.Tick(dt); Vector2 position; bool num2 = RunStatsInputReader.TryGetPointerPosition(out position) && (Object)(object)_viewport != (Object)null && RectTransformUtility.RectangleContainsScreenPoint(_viewport, position, (Camera)null); int num3 = -1; if (num2) { for (int i = 0; i < _hoverAnimators.Count; i++) { if (_hoverAnimators[i].IsPointerOver(position)) { num3 = i; break; } } } for (int j = 0; j < _hoverAnimators.Count; j++) { _hoverAnimators[j].Tick(dt, j == num3, position); } } private static bool IsNetworkSessionActive() { return NetConfig.GetMode() switch { NetMode.Host => true, NetMode.Client => NetClientJoinFlow.SessionJoinedHost, _ => false, }; } private static bool IsInGameScene() { //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) try { Scene activeScene = SceneManager.GetActiveScene(); return string.Equals(((Scene)(ref activeScene)).name, "GameScene", StringComparison.OrdinalIgnoreCase); } catch { return false; } } private static void EnsureBuilt() { if (!((Object)(object)_root != (Object)null)) { _root = RunStatsCanvasBuilder.BuildRoot(out RectTransform viewport, out RectTransform track); _viewport = viewport; _track = track; _carousel.SetTrack(track); Object.DontDestroyOnLoad((Object)(object)_root); _root.SetActive(false); } } private static void ApplyTopmostSortingOrder() { Canvas component = _root.GetComponent(); int num = 0; Canvas[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (Canvas val in array) { if (!((Object)(object)val == (Object)(object)component) && val.sortingOrder > num) { num = val.sortingOrder; } } component.sortingOrder = num + 100; } private static void RefreshIfChanged() { IReadOnlyList lastFinalized = NetRunStatsClientCache.LastFinalized; int version = RunStatsDevInjector.Version; if (lastFinalized != _boundList || version != _boundSimVersion) { _boundList = lastFinalized; _boundSimVersion = version; RebuildCards(ComposeDisplayList(lastFinalized)); } } private static IReadOnlyList? ComposeDisplayList(IReadOnlyList? real) { IReadOnlyList simulated = RunStatsDevInjector.Simulated; if (simulated.Count == 0) { return real; } List list = new List(); if (real != null) { list.AddRange(real); } list.AddRange(simulated); return list; } private static void RebuildCards(IReadOnlyList? list) { foreach (RunStatsCardView card in _cards) { card.Root.SetActive(false); Object.Destroy((Object)(object)card.Root); } _cards.Clear(); _hoverAnimators.Clear(); if ((Object)(object)_track == (Object)null) { return; } ApplyResponsiveSpacing(); TMP_FontAsset font = NativeFontSampler.ResolveNativeFont(); if (list == null || list.Count == 0) { RunStatsCardView runStatsCardView = RunStatsCardView.Create((Transform)(object)_track, font); runStatsCardView.SetEmpty(); _cards.Add(runStatsCardView); } else { RunStatsBestMarks best = RunStatsBestMarks.Compute(list); NetRunState state; string text = (NetRunStateBridge.TryGetLocalRunState(out state) ? state.PeerId : ""); foreach (NetRunStats item in list) { RunStatsCardView runStatsCardView2 = RunStatsCardView.Create((Transform)(object)_track, font); runStatsCardView2.Bind(item, item.PeerId == text, best); _cards.Add(runStatsCardView2); } } _carousel.ResetForNewData(_cards.Count); foreach (RunStatsCardView card2 in _cards) { LayoutRebuilder.ForceRebuildLayoutImmediate(card2.Rect); } LayoutRebuilder.ForceRebuildLayoutImmediate(_track); foreach (RunStatsCardView card3 in _cards) { _hoverAnimators.Add(new RunStatsCardHoverAnimator(card3)); } } private static void ApplyResponsiveSpacing() { float num = ((Screen.height > 0) ? ((float)Screen.width / (float)Screen.height) : 1.7777778f); float spacing = (RunStatsCanvasBuilder.ActiveCardSpacing = Mathf.Lerp(26f, 44f, Mathf.InverseLerp(1.25f, 1.7f, num))); HorizontalLayoutGroup val = (((Object)(object)_track != (Object)null) ? ((Component)_track).GetComponent() : null); if ((Object)(object)val != (Object)null) { ((HorizontalOrVerticalLayoutGroup)val).spacing = spacing; } } private static void HideAndClear() { if ((Object)(object)_root != (Object)null) { _root.SetActive(false); } RunStatsCursorControl.Release(); NetRunStatsClientCache.ConsumeAndClear(); RunStatsDevInjector.Clear(); _boundList = null; _boundSimVersion = -1; } } internal struct RunStatsSpring { private const float AngularFrequency = 14f; private const float DampingRatio = 0.75f; private const float MaxStep = 1f / 30f; public float Value; private float _velocity; public void Snap(float value) { Value = value; _velocity = 0f; } public float Tick(float target, float deltaTime) { float num = Mathf.Min(deltaTime, 1f / 30f); _velocity += (-21f * _velocity - 196f * (Value - target)) * num; Value += _velocity * num; return Value; } } } namespace SULFURTogether.UI.DownedRescueOverlay { internal sealed class DownedRescueBorderProgress : MaskableGraphic { public float Thickness = 4f; public float Inset; private float _progress01 = 1f; private readonly Vector2[] _corners = (Vector2[])(object)new Vector2[6]; private readonly float[] _segLen = new float[5]; public void SetProgress(float value) { value = Mathf.Clamp01(value); if (!Mathf.Approximately(value, _progress01)) { _progress01 = value; ((Graphic)this).SetVerticesDirty(); } } protected override void OnPopulateMesh(VertexHelper vh) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_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_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_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_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0147: 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_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_019c: 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_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) vh.Clear(); Rect pixelAdjustedRect = ((Graphic)this).GetPixelAdjustedRect(); float num = ((Rect)(ref pixelAdjustedRect)).width * 0.5f - Inset; float num2 = ((Rect)(ref pixelAdjustedRect)).height * 0.5f - Inset; if (num <= 0f || num2 <= 0f) { return; } _corners[0] = new Vector2(0f, num2); _corners[1] = new Vector2(num, num2); _corners[2] = new Vector2(num, 0f - num2); _corners[3] = new Vector2(0f - num, 0f - num2); _corners[4] = new Vector2(0f - num, num2); _corners[5] = new Vector2(0f, num2); _segLen[0] = num; _segLen[1] = 2f * num2; _segLen[2] = 2f * num; _segLen[3] = 2f * num2; _segLen[4] = num; float num3 = _segLen[0] + _segLen[1] + _segLen[2] + _segLen[3] + _segLen[4]; float num4 = _progress01 * num3; Color32 c = Color32.op_Implicit(((Graphic)this).color); float num5 = 0f; for (int i = 0; i < 5; i++) { float num6 = num5; float num7 = num5 + _segLen[i]; if (!(num4 <= num6)) { Vector2 val = _corners[i]; Vector2 val2 = _corners[i + 1]; bool num8 = num4 >= num7; Vector2 b = (num8 ? val2 : Vector2.Lerp(val, val2, (num4 - num6) / _segLen[i])); AddSegmentQuad(vh, val, b, c); if (num8 && i < 4) { AddCornerSquare(vh, val2, c); } num5 = num7; if (!num8) { break; } continue; } break; } } private void AddSegmentQuad(VertexHelper vh, Vector2 a, Vector2 b, Color32 c) { //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_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_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_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_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //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_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_009b: 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_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_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_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_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) Vector2 val = b - a; if (!(((Vector2)(ref val)).sqrMagnitude < 0.0001f)) { val = b - a; Vector2 normalized = ((Vector2)(ref val)).normalized; Vector2 val2 = new Vector2(0f - normalized.y, normalized.x) * (Thickness * 0.5f); int currentVertCount = vh.currentVertCount; vh.AddVert(Vector2.op_Implicit(a - val2), c, Vector4.op_Implicit(Vector2.zero)); vh.AddVert(Vector2.op_Implicit(a + val2), c, Vector4.op_Implicit(Vector2.zero)); vh.AddVert(Vector2.op_Implicit(b + val2), c, Vector4.op_Implicit(Vector2.zero)); vh.AddVert(Vector2.op_Implicit(b - val2), c, Vector4.op_Implicit(Vector2.zero)); vh.AddTriangle(currentVertCount, currentVertCount + 1, currentVertCount + 2); vh.AddTriangle(currentVertCount, currentVertCount + 2, currentVertCount + 3); } } private void AddCornerSquare(VertexHelper vh, Vector2 center, Color32 c) { //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_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003a: 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_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: 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_005e: 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_0070: 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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_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_0094: 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_009a: Unknown result type (might be due to invalid IL or missing references) float num = Thickness * 0.5f; int currentVertCount = vh.currentVertCount; vh.AddVert(Vector2.op_Implicit(center + new Vector2(0f - num, 0f - num)), c, Vector4.op_Implicit(Vector2.zero)); vh.AddVert(Vector2.op_Implicit(center + new Vector2(0f - num, num)), c, Vector4.op_Implicit(Vector2.zero)); vh.AddVert(Vector2.op_Implicit(center + new Vector2(num, num)), c, Vector4.op_Implicit(Vector2.zero)); vh.AddVert(Vector2.op_Implicit(center + new Vector2(num, 0f - num)), c, Vector4.op_Implicit(Vector2.zero)); vh.AddTriangle(currentVertCount, currentVertCount + 1, currentVertCount + 2); vh.AddTriangle(currentVertCount, currentVertCount + 2, currentVertCount + 3); } } internal static class DownedRescueCanvasBuilder { public const float PanelWidth = 620f; public const float PanelHeight = 150f; private const float VerticalCenterOffsetPx = -220f; public static GameObject BuildRoot(out RectTransform panel) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Expected O, but got Unknown //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("DownedRescueOverlayCanvas", new Type[4] { typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster) }); Canvas component = val.GetComponent(); component.renderMode = (RenderMode)0; component.sortingOrder = 0; CanvasScaler component2 = val.GetComponent(); component2.uiScaleMode = (ScaleMode)1; component2.referenceResolution = new Vector2(1920f, 1080f); component2.screenMatchMode = (ScreenMatchMode)0; component2.matchWidthOrHeight = 0.5f; GameObject val2 = new GameObject("Panel", new Type[1] { typeof(RectTransform) }); val2.transform.SetParent(val.transform, false); RectTransform val3 = (RectTransform)val2.transform; val3.anchorMin = new Vector2(0.5f, 0.5f); val3.anchorMax = new Vector2(0.5f, 0.5f); val3.pivot = new Vector2(0.5f, 0.5f); val3.anchoredPosition = new Vector2(0f, -220f); val3.sizeDelta = new Vector2(620f, 150f); panel = val3; return val; } } internal static class DownedRescueOverlayManager { private enum EndFadePhase { None, Hold, Fade } private const float CompletedHoldSeconds = 0.6f; private const float CompletedFadeSeconds = 0.4f; private const float CancelledFadeSeconds = 0.3f; private static GameObject? _root; private static DownedRescuePanelView? _panel; private static DownedRescueVitalityAnimator? _animator; private static bool _shownLastTick; private static EndFadePhase _endPhase = EndFadePhase.None; private static float _endPhaseStartedAt; private static string _endMainText = ""; private static string _endSubText = ""; private static float _currentProgressForAnimation; private static string _lastWarned = ""; private static bool _wasActiveLastTick; private static bool rescueInvolvedLocalLastTick; private static string _lastTargetPeerId = ""; public static void Tick() { bool flag; try { flag = ApplyLifecycle(); } catch (Exception ex) { WarnOnce("lifecycle failed: " + ex.GetType().Name + ": " + ex.Message); return; } if (!flag) { return; } try { _animator.Tick(_currentProgressForAnimation, Time.unscaledDeltaTime); } catch (Exception ex2) { WarnOnce("animation failed (panel stays shown, animation skipped): " + ex2.GetType().Name + ": " + ex2.Message); } } public static void Shutdown() { if ((Object)(object)_root != (Object)null) { _root.SetActive(false); } _shownLastTick = false; _endPhase = EndFadePhase.None; } private static void WarnOnce(string message) { if (!(message == _lastWarned)) { _lastWarned = message; NetLogger.Warn("[DownedRescueOverlay] " + message); } } private static bool ApplyLifecycle() { //IL_0351: Unknown result type (might be due to invalid IL or missing references) //IL_0356: Unknown result type (might be due to invalid IL or missing references) //IL_035a: Unknown result type (might be due to invalid IL or missing references) //IL_035f: Unknown result type (might be due to invalid IL or missing references) //IL_02a4: Unknown result type (might be due to invalid IL or missing references) //IL_02a9: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Unknown result type (might be due to invalid IL or missing references) string localPeerId = NetPlayerLifeManager.LocalPeerId; NetPlayerLifeManager.RescueDisplayState currentRescueDisplay = NetPlayerLifeManager.CurrentRescueDisplay; bool flag = NetPlayerLifeManager.ShouldSuppressLocalPlayerControls(); bool flag2 = currentRescueDisplay.RescuerPeerId == localPeerId || currentRescueDisplay.TargetPeerId == localPeerId; if (_endPhase != EndFadePhase.None && currentRescueDisplay.Active && flag2) { _endPhase = EndFadePhase.None; } if (_endPhase == EndFadePhase.None && _wasActiveLastTick && !currentRescueDisplay.Active && rescueInvolvedLocalLastTick) { _endPhase = ((currentRescueDisplay.LastEndReason == NetPlayerLifeManager.RescueEndReason.Completed) ? EndFadePhase.Hold : EndFadePhase.Fade); _endPhaseStartedAt = Time.unscaledTime; _endMainText = ((_lastTargetPeerId == localPeerId) ? CoopLoc.Get("rescue.complete.restored", "Restored") : CoopLoc.Get("rescue.complete.rescuer", "Rescue complete")); _endSubText = ""; } _wasActiveLastTick = currentRescueDisplay.Active; rescueInvolvedLocalLastTick = flag2; _lastTargetPeerId = currentRescueDisplay.TargetPeerId; if (_endPhase != EndFadePhase.None) { return TickEndFade(currentRescueDisplay); } string peerId = ""; string playerName = ""; float distance = 0f; bool flag3 = !flag && NetPlayerLifeManager.TryGetNearestDownedPeerHint(out peerId, out playerName, out distance); bool flag4 = !flag && currentRescueDisplay.Active && currentRescueDisplay.RescuerPeerId == localPeerId; bool flag5 = flag && currentRescueDisplay.Active && currentRescueDisplay.TargetPeerId == localPeerId; bool flag6 = flag && !flag5; bool flag7 = !flag && !flag4 && flag3; if (!(flag4 || flag5 || flag6 || flag7)) { if (_shownLastTick) { Hide(); } return false; } EnsureBuilt(); if (!_root.activeSelf) { _root.SetActive(true); ApplyTopmostSortingOrder(); _animator.Reset(); } _panel.CanvasGroup.alpha = 1f; KeyboardShortcut value; if (flag5) { string knownPeerDisplayName = NetPlayerLifeManager.GetKnownPeerDisplayName(currentRescueDisplay.RescuerPeerId); _panel.SetMainText(CoopLoc.Format("rescue.downed.active", "{name} is rescuing you", ("name", knownPeerDisplayName))); _panel.SetSubText(CoopLoc.Get("rescue.downed.hangOn", "Hang on")); _panel.SetBorderProgress(currentRescueDisplay.Progress); _currentProgressForAnimation = currentRescueDisplay.Progress; } else if (flag6) { _panel.SetMainText(CoopLoc.Get("rescue.downed.waiting", "Waiting for a teammate to revive you")); _panel.SetSubText(""); _panel.SetBorderProgress(0f); _currentProgressForAnimation = 0f; } else if (flag4) { string knownPeerDisplayName2 = NetPlayerLifeManager.GetKnownPeerDisplayName(currentRescueDisplay.TargetPeerId); value = Plugin.Cfg.PlayerReviveHoldKey.Value; string item = ((object)((KeyboardShortcut)(ref value)).MainKey/*cast due to .constrained prefix*/).ToString(); _panel.SetMainText(CoopLoc.Format("rescue.rescuer.active", "Rescuing {name}", ("name", knownPeerDisplayName2))); _panel.SetSubText(CoopLoc.Format("rescue.hold", "Hold [{key}]", ("key", item))); _panel.SetBorderProgress(currentRescueDisplay.Progress); _currentProgressForAnimation = currentRescueDisplay.Progress; } else { value = Plugin.Cfg.PlayerReviveHoldKey.Value; string item2 = ((object)((KeyboardShortcut)(ref value)).MainKey/*cast due to .constrained prefix*/).ToString(); _panel.SetMainText(CoopLoc.Format("rescue.rescuer.idle", "Rescue {name}", ("name", playerName))); _panel.SetSubText(CoopLoc.Format("rescue.hold", "Hold [{key}]", ("key", item2))); _panel.SetBorderProgress(0f); _currentProgressForAnimation = 0f; } _shownLastTick = true; return true; } private static bool TickEndFade(NetPlayerLifeManager.RescueDisplayState rescue) { EnsureBuilt(); float num = Time.unscaledTime - _endPhaseStartedAt; if (_endPhase == EndFadePhase.Hold) { if (!_root.activeSelf) { _root.SetActive(true); ApplyTopmostSortingOrder(); } _panel.CanvasGroup.alpha = 1f; _panel.SetMainText(_endMainText); _panel.SetSubText(_endSubText); _panel.SetBorderProgress(1f); _currentProgressForAnimation = 1f; if (num >= 0.6f) { _endPhase = EndFadePhase.Fade; _endPhaseStartedAt = Time.unscaledTime; } _shownLastTick = true; return true; } float num2 = ((_endMainText.Length > 0) ? 0.4f : 0.3f); float num3 = ((num2 > 0f) ? Mathf.Clamp01(num / num2) : 1f); if (!_root.activeSelf) { _root.SetActive(true); ApplyTopmostSortingOrder(); } _panel.CanvasGroup.alpha = 1f - num3; if (num3 >= 1f) { Hide(); _endPhase = EndFadePhase.None; _endMainText = ""; return false; } _shownLastTick = true; return true; } private static void Hide() { if ((Object)(object)_root != (Object)null) { _root.SetActive(false); } _shownLastTick = false; _currentProgressForAnimation = 0f; } private static void EnsureBuilt() { if (!((Object)(object)_root != (Object)null)) { _root = DownedRescueCanvasBuilder.BuildRoot(out RectTransform panel); _panel = DownedRescuePanelView.Create((Transform)(object)panel, NativeFontSampler.ResolveNativeFont()); _animator = new DownedRescueVitalityAnimator(_panel); Object.DontDestroyOnLoad((Object)(object)_root); _root.SetActive(false); } } private static void ApplyTopmostSortingOrder() { Canvas component = _root.GetComponent(); int num = 0; Canvas[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (Canvas val in array) { if (!((Object)(object)val == (Object)(object)component) && val.sortingOrder > num) { num = val.sortingOrder; } } component.sortingOrder = num + 100; } } internal sealed class DownedRescuePanelView { private static readonly Color BodyColor = new Color(0.05f, 0.05f, 0.055f, 0.92f); private static readonly Color InnerTintDim = new Color(0.4f, 0.42f, 0.46f, 0.035f); private static readonly Color InnerTintAlive = new Color(1f, 0.55f, 0.15f, 0.06f); private static readonly Color DropShadow = new Color(0f, 0f, 0f, 0.55f); private static readonly Color BaseStrokeColor = new Color(0.42f, 0.41f, 0.44f, 0.4f); private static readonly Color ProgressStrokeDim = new Color(0.55f, 0.54f, 0.56f, 0.55f); private static readonly Color ProgressStrokeAlive = new Color(1f, 0.6f, 0.25f, 1f); private static readonly Color TextColorDim = new Color(0.55f, 0.54f, 0.52f, 0.75f); private static readonly Color TextColorAlive = new Color(0.99f, 0.97f, 0.94f, 1f); private static readonly Color SubTextColorDim = new Color(0.45f, 0.44f, 0.42f, 0.6f); private static readonly Color SubTextColorAlive = new Color(0.84f, 0.81f, 0.76f, 1f); private readonly GameObject _root; private readonly RectTransform _rect; private readonly CanvasGroup _canvasGroup; private readonly Image _innerTint; private readonly DownedRescueBorderProgress _progressStroke; private readonly TextMeshProUGUI _mainText; private readonly TextMeshProUGUI _subText; public GameObject Root => _root; public RectTransform Rect => _rect; public CanvasGroup CanvasGroup => _canvasGroup; private DownedRescuePanelView(GameObject root, CanvasGroup canvasGroup, Image innerTint, DownedRescueBorderProgress progressStroke, TextMeshProUGUI mainText, TextMeshProUGUI subText) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown _root = root; _rect = (RectTransform)root.transform; _canvasGroup = canvasGroup; _innerTint = innerTint; _progressStroke = progressStroke; _mainText = mainText; _subText = subText; } public static DownedRescuePanelView Create(Transform parent, TMP_FontAsset? font) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_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) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0067: 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_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008e: 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_00ba: 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_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: 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_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Expected O, but got Unknown GameObject val = new GameObject("DownedRescuePanel", new Type[4] { typeof(RectTransform), typeof(CanvasGroup), typeof(Image), typeof(Shadow) }); val.transform.SetParent(parent, false); RectTransform val2 = (RectTransform)val.transform; val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; Image component = val.GetComponent(); ((Graphic)component).color = BodyColor; ((Graphic)component).raycastTarget = false; Shadow component2 = val.GetComponent(); component2.effectColor = DropShadow; component2.effectDistance = new Vector2(4f, -4f); component2.useGraphicAlpha = false; CanvasGroup component3 = val.GetComponent(); component3.blocksRaycasts = false; component3.interactable = false; Image innerTint = CreateFullRectImage(val.transform, "InnerTint", InnerTintDim); CreateBorderStroke(val.transform, "BorderBase", BaseStrokeColor, 3f, 0f).SetProgress(1f); DownedRescueBorderProgress downedRescueBorderProgress = CreateBorderStroke(val.transform, "BorderProgress", ProgressStrokeDim, 5f, 4f); downedRescueBorderProgress.SetProgress(0f); TextMeshProUGUI val3 = CreateText(val.transform, font, 30f, (FontStyles)1, (TextAlignmentOptions)514); RectTransform rectTransform = ((TMP_Text)val3).rectTransform; rectTransform.anchorMin = new Vector2(0f, 0.5f); rectTransform.anchorMax = new Vector2(1f, 1f); rectTransform.offsetMin = new Vector2(24f, 0f); rectTransform.offsetMax = new Vector2(-24f, -18f); TextMeshProUGUI val4 = CreateText(val.transform, font, 19f, (FontStyles)0, (TextAlignmentOptions)514); RectTransform rectTransform2 = ((TMP_Text)val4).rectTransform; rectTransform2.anchorMin = new Vector2(0f, 0f); rectTransform2.anchorMax = new Vector2(1f, 0.5f); rectTransform2.offsetMin = new Vector2(24f, 14f); rectTransform2.offsetMax = new Vector2(-24f, 0f); return new DownedRescuePanelView(val, component3, innerTint, downedRescueBorderProgress, val3, val4); } private static Image CreateFullRectImage(Transform parent, string name, Color color) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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_003f: 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_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_0073: 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) GameObject val = new GameObject(name, new Type[2] { typeof(RectTransform), typeof(Image) }); val.transform.SetParent(parent, false); RectTransform val2 = (RectTransform)val.transform; val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = new Vector2(6f, 6f); val2.offsetMax = new Vector2(-6f, -6f); Image component = val.GetComponent(); ((Graphic)component).color = color; ((Graphic)component).raycastTarget = false; return component; } private static DownedRescueBorderProgress CreateBorderStroke(Transform parent, string name, Color color, float thickness, float inset) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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_003f: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[2] { typeof(RectTransform), typeof(DownedRescueBorderProgress) }); val.transform.SetParent(parent, false); RectTransform val2 = (RectTransform)val.transform; val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; DownedRescueBorderProgress component = val.GetComponent(); ((Graphic)component).color = color; component.Thickness = thickness; component.Inset = inset; ((Graphic)component).raycastTarget = false; return component; } private static TextMeshProUGUI CreateText(Transform parent, TMP_FontAsset? font, float size, FontStyles style, TextAlignmentOptions align) { //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_0055: 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_0064: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Text", new Type[2] { typeof(RectTransform), typeof(TextMeshProUGUI) }); val.transform.SetParent(parent, false); TextMeshProUGUI component = val.GetComponent(); if ((Object)(object)font != (Object)null) { ((TMP_Text)component).font = font; } ((TMP_Text)component).fontSize = size; ((TMP_Text)component).fontStyle = style; ((TMP_Text)component).alignment = align; ((Graphic)component).color = Color.white; ((Graphic)component).raycastTarget = false; ((TMP_Text)component).enableAutoSizing = true; ((TMP_Text)component).fontSizeMin = size * 0.6f; ((TMP_Text)component).fontSizeMax = size; return component; } public void SetMainText(string text) { ((TMP_Text)_mainText).text = text; } public void SetSubText(string text) { ((TMP_Text)_subText).text = text; } public void SetBorderProgress(float progress01) { _progressStroke.SetProgress(progress01); } public void SetVitality(float vitality01) { //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_0018: 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_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004e: 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_0069: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Clamp01(vitality01); ((Graphic)_progressStroke).color = Color.Lerp(ProgressStrokeDim, ProgressStrokeAlive, num); ((Graphic)_mainText).color = Color.Lerp(TextColorDim, TextColorAlive, num); ((Graphic)_subText).color = Color.Lerp(SubTextColorDim, SubTextColorAlive, num); ((Graphic)_innerTint).color = Color.Lerp(InnerTintDim, InnerTintAlive, num); } } internal sealed class DownedRescueVitalityAnimator { private const float IdleFloor = 0.05f; private readonly DownedRescuePanelView _panel; private readonly Vector2 _basePosition; private RunStatsSpring _floatSpring; private RunStatsSpring _breatheSpring; private RunStatsSpring _tiltSpring; private float _phase; public DownedRescueVitalityAnimator(DownedRescuePanelView panel) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) _panel = panel; _basePosition = panel.Rect.anchoredPosition; Reset(); } public void Reset() { //IL_0047: 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_0071: Unknown result type (might be due to invalid IL or missing references) _floatSpring.Snap(0f); _breatheSpring.Snap(1f); _tiltSpring.Snap(0f); _phase = 0f; _panel.Rect.anchoredPosition = _basePosition; ((Transform)_panel.Rect).localScale = Vector3.one; ((Transform)_panel.Rect).localRotation = Quaternion.identity; } public void Tick(float progress01, float dt) { //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Max(0.05f, Ease(progress01)); _panel.SetVitality(num); _phase += dt * Mathf.Lerp(0.6f, 2.2f, num); float num2 = Mathf.Lerp(2f, 10f, num); _floatSpring.Tick(Mathf.Sin(_phase) * num2, dt); float num3 = Mathf.Lerp(0.01f, 0.06f, num); _breatheSpring.Tick(1f + Mathf.Sin(_phase * 0.8f + 1.3f) * num3, dt); float num4 = Mathf.Lerp(0.5f, 3.5f, num); _tiltSpring.Tick(Mathf.Sin(_phase * 0.5f + 0.6f) * num4, dt); _panel.Rect.anchoredPosition = _basePosition + new Vector2(0f, _floatSpring.Value); ((Transform)_panel.Rect).localScale = new Vector3(_breatheSpring.Value, _breatheSpring.Value, 1f); ((Transform)_panel.Rect).localRotation = Quaternion.Euler(0f, 0f, _tiltSpring.Value); } private static float Ease(float t) { t = Mathf.Clamp01(t); return t * t * (3f - 2f * t); } } } namespace SULFURTogether.ReverseProbe { internal static class PlayerSpriteAssetScanProbe { private static bool _done; private static float _playerSeenAt = -1f; private static readonly string[] Keywords = new string[26] { "player", "character", "body", "front", "back", "side", "walk", "idle", "run", "move", "portrait", "hero", "protag", "doll", "billboard", "third", "coop", "co-op", "ghost", "prisoner", "inmate", "straitjacket", "patient", "guy", "dude", "avatar" }; public static void TryScanOnce() { try { if (!_done && Plugin.Cfg.LogPlayerSpriteAssetScan.Value) { float realtimeSinceStartup = Time.realtimeSinceStartup; if ((Object)(object)ResolveLocalPlayer() == (Object)null) { _playerSeenAt = -1f; } else if (_playerSeenAt < 0f) { _playerSeenAt = realtimeSinceStartup; } else if (!(realtimeSinceStartup - _playerSeenAt < 3f)) { _done = true; Scan(); } } } catch (Exception ex) { Plugin.Log.Error("[PSAS] scan failed: " + ex.Message); } } private static Component ResolveLocalPlayer() { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); if (type == null) { return null; } object obj = AccessTools.Property(type, "Instance")?.GetValue(null, null); if (obj == null) { return null; } object? obj2 = AccessTools.Property(type, "PlayerScript")?.GetValue(obj, null); return (Component)(((obj2 is Component) ? obj2 : null) ?? ((object)/*isinst with value type is only supported in some contexts*/)); } private static bool Matches(string name) { if (string.IsNullOrEmpty(name)) { return false; } string text = name.ToLowerInvariant(); string[] keywords = Keywords; foreach (string value in keywords) { if (text.Contains(value)) { return true; } } return false; } private static void Scan() { STLogger log = Plugin.Log; log.Info("==================== [PSAS] PLAYER SPRITE ASSET SCAN BEGIN ===================="); ScanSprites(log); ScanTextures(log); ScanPrefabs(log); ScanAnimators(log); ScanAddressables(log); log.Info("==================== [PSAS] PLAYER SPRITE ASSET SCAN END ===================="); } private static void ScanSprites(STLogger Log) { //IL_00ae: 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_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) Sprite[] array = Resources.FindObjectsOfTypeAll(); int num = 0; Log.Info($"---- [PSAS] SPRITES total={array.Length} (keyword matches below) ----"); Sprite[] array2 = array; foreach (Sprite val in array2) { if ((Object)(object)val == (Object)null) { continue; } string text = (((Object)(object)val.texture != (Object)null) ? ((Object)val.texture).name : ""); if (Matches(((Object)val).name) || Matches(text)) { if (num++ >= 400) { Log.Info("[PSAS][Sprite] ... (truncated)"); break; } object[] obj = new object[4] { ((Object)val).name, text, null, null }; Rect rect = val.rect; obj[2] = ((Rect)(ref rect)).width; rect = val.rect; obj[3] = ((Rect)(ref rect)).height; Log.Info(string.Format("[PSAS][Sprite] name={0} tex={1} rect={2}x{3}", obj)); } } Log.Info($"---- [PSAS] SPRITES matched={num} ----"); } private static void ScanTextures(STLogger Log) { Texture2D[] array = Resources.FindObjectsOfTypeAll(); int num = 0; Log.Info($"---- [PSAS] TEXTURES total={array.Length} (keyword matches below) ----"); Texture2D[] array2 = array; foreach (Texture2D val in array2) { if (!((Object)(object)val == (Object)null) && Matches(((Object)val).name)) { if (num++ >= 300) { Log.Info("[PSAS][Tex] ... (truncated)"); break; } Log.Info($"[PSAS][Tex] name={((Object)val).name} {((Texture)val).width}x{((Texture)val).height}"); } } Log.Info($"---- [PSAS] TEXTURES matched={num} ----"); } private static void ScanPrefabs(STLogger Log) { //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) GameObject[] array = Resources.FindObjectsOfTypeAll(); int num = 0; Log.Info($"---- [PSAS] GAMEOBJECTS total={array.Length} (keyword matches; scene=false = likely prefab/asset) ----"); GameObject[] array2 = array; foreach (GameObject val in array2) { if (!((Object)(object)val == (Object)null) && Matches(((Object)val).name) && ((Object)val).name.IndexOf("SULFUR Together", StringComparison.OrdinalIgnoreCase) < 0) { bool flag = (Object)(object)val.GetComponentInChildren(true) != (Object)null; if (num++ >= 300) { Log.Info("[PSAS][GO] ... (truncated)"); break; } string name = ((Object)val).name; Scene scene = val.scene; Log.Info($"[PSAS][GO] name={name} inScene={((Scene)(ref scene)).IsValid()} hasSpriteRenderer={flag}"); } } Log.Info($"---- [PSAS] GAMEOBJECTS matched={num} ----"); } private static void ScanAnimators(STLogger Log) { int num = 0; Log.Info("---- [PSAS] ANIMATOR CONTROLLERS / CLIPS (keyword matches) ----"); RuntimeAnimatorController[] array = Resources.FindObjectsOfTypeAll(); foreach (RuntimeAnimatorController val in array) { if (!((Object)(object)val == (Object)null) && Matches(((Object)val).name)) { if (num++ >= 200) { break; } Log.Info("[PSAS][Ctrl] name=" + ((Object)val).name); } } AnimationClip[] array2 = Resources.FindObjectsOfTypeAll(); foreach (AnimationClip val2 in array2) { if (!((Object)(object)val2 == (Object)null) && Matches(((Object)val2).name)) { if (num++ >= 400) { Log.Info("[PSAS][Clip] ... (truncated)"); break; } Log.Info("[PSAS][Clip] name=" + ((Object)val2).name); } } Log.Info($"---- [PSAS] ANIMATORS matched={num} ----"); } private static void ScanAddressables(STLogger Log) { Log.Info("---- [PSAS] ADDRESSABLES KEYS (keyword matches) ----"); try { Type type = AccessTools.TypeByName("UnityEngine.AddressableAssets.Addressables"); if (type == null) { Log.Info("[PSAS][Addr] Addressables type not found"); return; } PropertyInfo propertyInfo = AccessTools.Property(type, "ResourceLocators"); if (propertyInfo == null) { Log.Info("[PSAS][Addr] ResourceLocators not found"); return; } if (!(propertyInfo.GetValue(null, null) is IEnumerable enumerable)) { Log.Info("[PSAS][Addr] ResourceLocators null"); return; } int num = 0; int num2 = 0; foreach (object item in enumerable) { if (item == null) { continue; } PropertyInfo property = item.GetType().GetProperty("Keys", BindingFlags.Instance | BindingFlags.Public); if (property == null || !(property.GetValue(item, null) is IEnumerable enumerable2)) { continue; } foreach (object item2 in enumerable2) { if (!(item2 is string text)) { continue; } num2++; if (Matches(text)) { if (num++ >= 400) { Log.Info("[PSAS][Addr] ... (truncated)"); break; } Log.Info("[PSAS][Addr] key=" + text); } } if (num >= 400) { break; } } Log.Info($"---- [PSAS] ADDRESSABLES totalStringKeys={num2} matched={num} ----"); } catch (Exception ex) { Log.Warn("[PSAS][Addr] enumeration failed: " + ex.Message); } } } internal static class PlayerVisualDiscoveryProbe { private static bool _done; private static float _nextTryAt; public static void TryDumpOnce() { try { if (_done || !Plugin.Cfg.LogPlayerVisualDiscovery.Value) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup < _nextTryAt)) { _nextTryAt = realtimeSinceStartup + 1f; Component val = ResolveLocalPlayer(); if (!((Object)(object)val == (Object)null)) { _done = true; Dump(val); } } } catch (Exception ex) { Plugin.Log.Error("[PVD] discovery failed: " + ex.Message); } } private static Component ResolveLocalPlayer() { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); if (type == null) { return null; } object obj = AccessTools.Property(type, "Instance")?.GetValue(null, null); if (obj == null) { return null; } object? obj2 = AccessTools.Property(type, "PlayerScript")?.GetValue(obj, null); Component val = (Component)((obj2 is Component) ? obj2 : null); if ((Object)(object)val != (Object)null) { return val; } object? obj3 = AccessTools.Property(type, "PlayerUnit")?.GetValue(obj, null); return (Component)((obj3 is Component) ? obj3 : null); } private static void Dump(Component player) { //IL_02c0: Unknown result type (might be due to invalid IL or missing references) STLogger log = Plugin.Log; GameObject gameObject = player.gameObject; log.Info("==================== [PVD] PLAYER VISUAL DISCOVERY BEGIN ===================="); log.Info($"[PVD] root={((Object)gameObject).name} active={gameObject.activeSelf} layer={gameObject.layer} playerType={((object)player).GetType().FullName}"); DumpPlayerVisuals(player); log.Info("---- [PVD] SPRITE RENDERERS (incl inactive) ----"); SpriteRenderer[] componentsInChildren = gameObject.GetComponentsInChildren(true); foreach (SpriteRenderer val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { log.Info($"[PVD][Sprite] path={Path(((Component)val).transform)} active={((Component)val).gameObject.activeSelf} enabled={((Renderer)val).enabled} layer={((Component)val).gameObject.layer} " + "sprite=" + (((Object)(object)val.sprite != (Object)null) ? ((Object)val.sprite).name : "") + " tex=" + (((Object)(object)val.sprite != (Object)null && (Object)(object)val.sprite.texture != (Object)null) ? ((Object)val.sprite.texture).name : "") + " mat=" + (((Object)(object)((Renderer)val).sharedMaterial != (Object)null) ? ((Object)((Renderer)val).sharedMaterial).name : "")); } } log.Info("---- [PVD] ANIMATORS (incl inactive) ----"); Animator[] componentsInChildren2 = gameObject.GetComponentsInChildren(true); foreach (Animator val2 in componentsInChildren2) { if ((Object)(object)val2 == (Object)null) { continue; } RuntimeAnimatorController runtimeAnimatorController = val2.runtimeAnimatorController; log.Info(string.Format("[PVD][Animator] path={0} active={1} enabled={2} controller={3}", Path(((Component)val2).transform), ((Component)val2).gameObject.activeSelf, ((Behaviour)val2).enabled, ((Object)(object)runtimeAnimatorController != (Object)null) ? ((Object)runtimeAnimatorController).name : "")); if ((Object)(object)runtimeAnimatorController != (Object)null && runtimeAnimatorController.animationClips != null) { AnimationClip[] animationClips = runtimeAnimatorController.animationClips; foreach (AnimationClip val3 in animationClips) { if ((Object)(object)val3 != (Object)null) { log.Info("[PVD][Animator] clip=" + ((Object)val3).name); } } } try { AnimatorControllerParameter[] parameters = val2.parameters; foreach (AnimatorControllerParameter val4 in parameters) { log.Info($"[PVD][Animator] param={val4.name} ({val4.type})"); } } catch { } } log.Info("---- [PVD] ALL RENDERERS (incl inactive) ----"); Renderer[] componentsInChildren3 = gameObject.GetComponentsInChildren(true); foreach (Renderer val5 in componentsInChildren3) { if (!((Object)(object)val5 == (Object)null)) { log.Info($"[PVD][Renderer] type={((object)val5).GetType().Name} path={Path(((Component)val5).transform)} active={((Component)val5).gameObject.activeSelf} enabled={val5.enabled} layer={((Component)val5).gameObject.layer}"); } } log.Info("---- [PVD] FULL HIERARCHY (incl inactive) ----"); DumpHierarchy(gameObject.transform, 0); log.Info("==================== [PVD] PLAYER VISUAL DISCOVERY END ===================="); } private static void DumpPlayerVisuals(Component player) { STLogger log = Plugin.Log; try { FieldInfo fieldInfo = AccessTools.Field(((object)player).GetType(), "playerVisuals"); if (fieldInfo == null) { log.Info("[PVD] playerVisuals field NOT FOUND on " + ((object)player).GetType().Name); return; } if (!(fieldInfo.GetValue(player) is Renderer[] array)) { log.Info("[PVD] playerVisuals == null"); return; } log.Info($"---- [PVD] playerVisuals[{array.Length}] ----"); Renderer[] array2 = array; foreach (Renderer val in array2) { if ((Object)(object)val == (Object)null) { log.Info("[PVD][pv] "); continue; } string text = ""; SpriteRenderer val2 = (SpriteRenderer)(object)((val is SpriteRenderer) ? val : null); if (val2 != null) { text = " sprite=" + (((Object)(object)val2.sprite != (Object)null) ? ((Object)val2.sprite).name : "") + " tex=" + (((Object)(object)val2.sprite != (Object)null && (Object)(object)val2.sprite.texture != (Object)null) ? ((Object)val2.sprite.texture).name : ""); } log.Info($"[PVD][pv] type={((object)val).GetType().Name} path={Path(((Component)val).transform)} active={((Component)val).gameObject.activeSelf} enabled={val.enabled} layer={((Component)val).gameObject.layer}{text}"); } } catch (Exception ex) { log.Warn("[PVD] playerVisuals dump failed: " + ex.Message); } } private static void DumpHierarchy(Transform t, int depth) { string text = ((depth > 0) ? new string(' ', depth * 2) : ""); StringBuilder stringBuilder = new StringBuilder(); Component[] components = ((Component)t).GetComponents(); foreach (Component val in components) { if (!((Object)(object)val == (Object)null)) { stringBuilder.Append(((object)val).GetType().Name); stringBuilder.Append(' '); } } Plugin.Log.Info($"[PVD][H] {text}{((Object)t).name} [active={((Component)t).gameObject.activeSelf} layer={((Component)t).gameObject.layer}] :: {stringBuilder}"); for (int j = 0; j < t.childCount; j++) { DumpHierarchy(t.GetChild(j), depth + 1); } } private static string Path(Transform t) { StringBuilder stringBuilder = new StringBuilder(((Object)t).name); Transform parent = t.parent; while ((Object)(object)parent != (Object)null) { stringBuilder.Insert(0, ((Object)parent).name + "/"); parent = parent.parent; } return stringBuilder.ToString(); } } public static class ReverseProbeFormatter { public static string FormatInstance(object? obj) { if (obj == null) { return ""; } string name = obj.GetType().Name; string text = TryGetString(obj, "name") ?? TryGetString(obj, "Name") ?? "?"; string instanceId = GetInstanceId(obj); return name + "(name=" + text + ", id=" + instanceId + ")"; } public static string FormatItemName(object? obj) { if (obj == null) { return ""; } try { if (obj is string text && !string.IsNullOrWhiteSpace(text)) { return text.Trim(); } string raw = TryGetString(obj, "DisplayName") ?? TryGetString(obj, "displayName") ?? TryGetString(obj, "ItemName") ?? TryGetString(obj, "itemName") ?? TryGetString(obj, "Title") ?? TryGetString(obj, "title"); raw = CleanItemName(raw); if (raw != null) { return raw; } string text2 = CleanItemName(obj.ToString()); string text3 = obj.GetType().FullName ?? obj.GetType().Name; if (text2 != null && text2 != text3 && !text2.Contains("(") && !text2.Contains("PerfectRandom.")) { return text2; } string text4 = CleanItemName(TryGetString(obj, "name") ?? TryGetString(obj, "Name")); if (text4 != null) { return text4; } } catch { } return ""; } public static string GetInstanceId(object? obj) { if (obj == null) { return "null"; } return TryCallMethod(obj, "GetInstanceID") ?? obj.GetHashCode().ToString(); } public static string FormatVector3(Vector3 v) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) return $"({v.x:F2},{v.y:F2},{v.z:F2})"; } public static string FormatQuat(Quaternion q) { //IL_0007: 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_0029: Unknown result type (might be due to invalid IL or missing references) return $"euler({((Quaternion)(ref q)).eulerAngles.x:F1},{((Quaternion)(ref q)).eulerAngles.y:F1},{((Quaternion)(ref q)).eulerAngles.z:F1})"; } private static string? TryGetString(object obj, string memberName) { try { PropertyInfo property = obj.GetType().GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property.GetValue(obj)?.ToString(); } FieldInfo field = obj.GetType().GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(obj)?.ToString(); } } catch { } return null; } private static string? TryCallMethod(object obj, string methodName) { try { return obj.GetType().GetMethod(methodName, Type.EmptyTypes)?.Invoke(obj, null)?.ToString(); } catch { return null; } } private static string? CleanItemName(string? raw) { if (raw == null) { return null; } string text = raw.Trim(); if (text.Length == 0) { return null; } switch (text) { case "?": case "": case "null": return null; default: if (text.Contains("UnityEngine.") || text.Contains("System.")) { return null; } if (text == "Currency_SulfCoin") { return "Cash Money Coin"; } if (text == "Currency_SulfTwenny") { return "Cash Money Twenny"; } return text; } } } public static class ReverseProbeKnownObjects { public class UnitInfo { public string InstanceId = ""; public string Name = ""; public string TypeCategory = ""; public float LastSeenTime; public int SpawnCount; public int DamageCount; } public class PickupInfo { public string InstanceId = ""; public string ObjectName = ""; public string ItemName = ""; public float LastSeenTime; } private static readonly Dictionary _players = new Dictionary(); private static readonly Dictionary _npcs = new Dictionary(); private static readonly Dictionary _units = new Dictionary(); private static readonly Dictionary _pickups = new Dictionary(); public static int KnownPlayerCount => _players.Count; public static int KnownNpcCount => _npcs.Count; public static int KnownPickupCount => _pickups.Count; public static int KnownUnitCount => _units.Count; public static void RegisterPlayer(string instanceId, string name) { if (instanceId != null) { if (!_players.TryGetValue(instanceId, out UnitInfo value)) { Dictionary players = _players; UnitInfo obj = new UnitInfo { InstanceId = instanceId, TypeCategory = "Player" }; value = obj; players[instanceId] = obj; } value.Name = name; value.LastSeenTime = Time.realtimeSinceStartup; value.SpawnCount++; } } public static void RegisterNpc(string instanceId, string name) { if (instanceId != null) { if (!_npcs.TryGetValue(instanceId, out UnitInfo value)) { Dictionary npcs = _npcs; UnitInfo obj = new UnitInfo { InstanceId = instanceId, TypeCategory = "Npc" }; value = obj; npcs[instanceId] = obj; } value.Name = name; value.LastSeenTime = Time.realtimeSinceStartup; value.SpawnCount++; } } public static void RegisterSpawn(string instanceId, string name, string category) { if (instanceId == null) { return; } if (category == "Player") { RegisterPlayer(instanceId, name); return; } if (category == "Npc") { RegisterNpc(instanceId, name); return; } if (!_units.TryGetValue(instanceId, out UnitInfo value)) { Dictionary units = _units; UnitInfo obj = new UnitInfo { InstanceId = instanceId, TypeCategory = category }; value = obj; units[instanceId] = obj; } value.Name = name; value.LastSeenTime = Time.realtimeSinceStartup; value.SpawnCount++; } public static void RegisterDamage(string instanceId) { if (instanceId != null) { float realtimeSinceStartup = Time.realtimeSinceStartup; UnitInfo value2; UnitInfo value3; if (_players.TryGetValue(instanceId, out UnitInfo value)) { value.DamageCount++; value.LastSeenTime = realtimeSinceStartup; } else if (_npcs.TryGetValue(instanceId, out value2)) { value2.DamageCount++; value2.LastSeenTime = realtimeSinceStartup; } else if (_units.TryGetValue(instanceId, out value3)) { value3.DamageCount++; value3.LastSeenTime = realtimeSinceStartup; } } } public static void RegisterDeath(string instanceId) { if (instanceId != null) { _players.Remove(instanceId); _npcs.Remove(instanceId); _units.Remove(instanceId); } } public static void RegisterPickupSpawned(string instanceId, string objectName) { RegisterPickupSpawned(instanceId, objectName, ""); } public static void RegisterPickupSpawned(string instanceId, string objectName, string itemName) { if (instanceId != null) { _pickups[instanceId] = new PickupInfo { InstanceId = instanceId, ObjectName = (objectName ?? ""), ItemName = (string.IsNullOrWhiteSpace(itemName) ? "" : itemName), LastSeenTime = Time.realtimeSinceStartup }; } } public static bool TryGetPickupItemName(string instanceId, out string itemName) { itemName = ""; if (instanceId == null) { return false; } if (!_pickups.TryGetValue(instanceId, out PickupInfo value)) { return false; } itemName = (string.IsNullOrWhiteSpace(value.ItemName) ? "" : value.ItemName); value.LastSeenTime = Time.realtimeSinceStartup; return true; } public static string GetPickupItemNameOrFallback(string instanceId, string fallback) { if (!TryGetPickupItemName(instanceId, out string itemName)) { return fallback; } return itemName; } public static void RegisterPickupRemoved(string instanceId) { if (instanceId != null) { _pickups.Remove(instanceId); } } public static void RegisterPickupExecuted(string instanceId) { if (instanceId != null) { _pickups.Remove(instanceId); } } public static void ClearLevelScopedObjects() { _players.Clear(); _npcs.Clear(); _units.Clear(); _pickups.Clear(); } public static void ClearPickups() { _pickups.Clear(); } } public static class ReverseProbeState { public static float LastNpcUpdateLog = -9999f; public const float NpcUpdateInterval = 10f; private static readonly Dictionary _keyLastTime = new Dictionary(); private static readonly Dictionary _keyCount = new Dictionary(); private static readonly Dictionary _aiTargets = new Dictionary(); private static readonly HashSet _reportedSignatures = new HashSet(); public static bool ShouldLog(string key, float intervalSeconds, int maxPerWindow = 0) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (_keyLastTime.TryGetValue(key, out var value) && realtimeSinceStartup - value < intervalSeconds) { if (maxPerWindow <= 0) { return false; } int value2; int num = (_keyCount.TryGetValue(key, out value2) ? value2 : 0); if (num >= maxPerWindow) { return false; } _keyCount[key] = num + 1; return true; } _keyLastTime[key] = realtimeSinceStartup; _keyCount[key] = 1; return true; } public static bool HasAiTargetChanged(string agentId, string newTargetId) { if (_aiTargets.TryGetValue(agentId, out string value) && value == newTargetId) { return false; } _aiTargets[agentId] = newTargetId; return true; } public static bool ShouldReportSignature(string sig) { return _reportedSignatures.Add(sig); } } public static class ReverseProbeSummary { private static float _lastSummaryTime = -9999f; private static int _gmEvents; private static int _playersAdded; private static int _unitSpawns; private static int _npcSpawns; private static int _inventorySetups; private static int _pickupExecutes; private static int _pickupSpawns; private static int _lootSpawns; private static int _levelGenEvents; private static int _unitDamageCount; private static int _npcDamageCount; private static int _breakableDamageCount; private static int _unitDeaths; private static int _npcDeaths; private static int _breakableDeaths; private static int _inventoryGetSerializedCount; private static int _inventoryDestroyCount; private static int _inventoryTransferCount; private static int _inventoryDropCount; private static int _inventoryMoveToPlayerCount; private static float _lastPickupBurstFlush = -9999f; private static int _burstPickupSpawnedCount; private static int _burstPickupExecutedCount; private static int _burstPickupRemovedCount; private static readonly Dictionary _burstPickupItemCounts = new Dictionary(); private static float _lastLootBurstFlush = -9999f; private static int _burstLootRegisteredCount; private static int _burstLootSpawnedCount; private static readonly Dictionary _burstLootItemCounts = new Dictionary(); public static void IncrementGmEvent() { _gmEvents++; } public static void IncrementPlayerAdded() { _playersAdded++; } public static void IncrementUnitSpawn() { _unitSpawns++; } public static void IncrementNpcSpawn() { _npcSpawns++; } public static void IncrementInventorySetup() { _inventorySetups++; } public static void IncrementPickupExecute() { _pickupExecutes++; } public static void IncrementPickupSpawn() { _pickupSpawns++; } public static void IncrementLootSpawn() { _lootSpawns++; } public static void IncrementLevelGen() { _levelGenEvents++; } public static void IncrementDamage(string category) { if (category == "Npc") { _npcDamageCount++; } else if (category == "Breakable") { _breakableDamageCount++; } else { _unitDamageCount++; } } public static void IncrementDeath(string category) { if (category == "Npc") { _npcDeaths++; } else if (category == "Breakable") { _breakableDeaths++; } else { _unitDeaths++; } } public static void IncrementInventoryGetSerialized() { _inventoryGetSerializedCount++; } public static void IncrementInventoryDestroy() { _inventoryDestroyCount++; } public static void IncrementInventoryTransfer() { _inventoryTransferCount++; } public static void IncrementInventoryDrop() { _inventoryDropCount++; } public static void IncrementInventoryMoveToPlayer() { _inventoryMoveToPlayerCount++; } public static void AddPickupSpawnedBurst(string itemName) { _burstPickupSpawnedCount++; AddItemName(_burstPickupItemCounts, itemName); } public static void AddPickupExecutedBurst(string itemName) { _burstPickupExecutedCount++; AddItemName(_burstPickupItemCounts, itemName); } public static void AddPickupRemovedBurst() { _burstPickupRemovedCount++; } public static void AddLootRegisteredBurst(string itemName) { _burstLootRegisteredCount++; AddItemName(_burstLootItemCounts, itemName); } public static void AddLootSpawnedBurst() { _burstLootSpawnedCount++; } private static void AddItemName(Dictionary dict, string name) { if (string.IsNullOrEmpty(name)) { name = ""; } dict.TryGetValue(name, out var value); dict[name] = value + 1; } public static void Tick() { if (Plugin.Cfg.EnableProbeSummary.Value) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup - _lastSummaryTime >= Plugin.Cfg.ProbeSummaryIntervalSeconds.Value) { _lastSummaryTime = realtimeSinceStartup; Flush(); } if (Plugin.Cfg.CompactPickupLogs.Value && realtimeSinceStartup - _lastPickupBurstFlush >= Plugin.Cfg.PickupBurstSummaryIntervalSeconds.Value) { _lastPickupBurstFlush = realtimeSinceStartup; FlushPickupBurst(); } if (Plugin.Cfg.CompactLootLogs.Value && realtimeSinceStartup - _lastLootBurstFlush >= Plugin.Cfg.LootBurstSummaryIntervalSeconds.Value) { _lastLootBurstFlush = realtimeSinceStartup; FlushLootBurst(); } } } private static void FlushPickupBurst() { if (_burstPickupSpawnedCount != 0 || _burstPickupExecutedCount != 0 || _burstPickupRemovedCount != 0) { string text = FormatTopItems(_burstPickupItemCounts); Plugin.Log.Info($"[PickupSummary] spawned={_burstPickupSpawnedCount} executed={_burstPickupExecutedCount} removed={_burstPickupRemovedCount} topItems={text}"); _burstPickupSpawnedCount = (_burstPickupExecutedCount = (_burstPickupRemovedCount = 0)); _burstPickupItemCounts.Clear(); } } private static void FlushLootBurst() { if (_burstLootRegisteredCount != 0 || _burstLootSpawnedCount != 0) { string arg = FormatTopItems(_burstLootItemCounts); Plugin.Log.Info($"[LootSummary] registered={_burstLootRegisteredCount} spawned={_burstLootSpawnedCount} topItems={arg}"); _burstLootRegisteredCount = (_burstLootSpawnedCount = 0); _burstLootItemCounts.Clear(); } } private static string FormatTopItems(Dictionary itemCounts, int maxItems = 5) { if (itemCounts.Count == 0) { return "(none)"; } List> list = new List>(itemCounts); list.Sort((KeyValuePair a, KeyValuePair b) => b.Value.CompareTo(a.Value)); StringBuilder stringBuilder = new StringBuilder(); for (int num = 0; num < list.Count && num < maxItems; num++) { if (num > 0) { stringBuilder.Append(", "); } stringBuilder.Append(list[num].Key).Append(" x").Append(list[num].Value); } return stringBuilder.ToString(); } private static void Flush() { if (_gmEvents != 0 || _playersAdded != 0 || _unitSpawns != 0 || _npcSpawns != 0 || _inventorySetups != 0 || _pickupExecutes != 0 || _pickupSpawns != 0 || _lootSpawns != 0 || _levelGenEvents != 0 || _unitDamageCount != 0 || _npcDamageCount != 0 || _breakableDamageCount != 0 || _unitDeaths != 0 || _npcDeaths != 0 || _breakableDeaths != 0 || _inventoryGetSerializedCount != 0 || _inventoryDestroyCount != 0 || _inventoryTransferCount != 0 || _inventoryDropCount != 0 || _inventoryMoveToPlayerCount != 0 || !Plugin.Cfg.SuppressEmptyProbeSummary.Value) { float value = Plugin.Cfg.ProbeSummaryIntervalSeconds.Value; STLogger log = Plugin.Log; log.Info($"[ProbeSummary] ── {value:F0}s snapshot ──────────────────────"); log.Info($"[ProbeSummary] GameManager events : {_gmEvents}"); log.Info($"[ProbeSummary] Players added : {_playersAdded}"); log.Info($"[ProbeSummary] Unit spawns : {_unitSpawns} Npc spawns: {_npcSpawns}"); log.Info($"[ProbeSummary] InventoryItem setup: {_inventorySetups}"); log.Info($"[ProbeSummary] Pickup spawned : {_pickupSpawns} Executed: {_pickupExecutes}"); log.Info($"[ProbeSummary] Loot spawns : {_lootSpawns}"); log.Info($"[ProbeSummary] LevelGen events : {_levelGenEvents}"); log.Info($"[ProbeSummary] Damage: unit={_unitDamageCount} npc={_npcDamageCount} breakable={_breakableDamageCount}"); log.Info($"[ProbeSummary] Deaths: unit={_unitDeaths} npc={_npcDeaths} breakable={_breakableDeaths}"); log.Info($"[ProbeSummary] Inventory: serialized={_inventoryGetSerializedCount} destroy={_inventoryDestroyCount} transfer={_inventoryTransferCount} drop={_inventoryDropCount} moveToPlayer={_inventoryMoveToPlayerCount}"); log.Info($"[ProbeSummary] Known live: players={ReverseProbeKnownObjects.KnownPlayerCount} npcs={ReverseProbeKnownObjects.KnownNpcCount} units={ReverseProbeKnownObjects.KnownUnitCount} pickups={ReverseProbeKnownObjects.KnownPickupCount}"); log.Info("[ProbeSummary] ─────────────────────────────────────"); _gmEvents = (_playersAdded = (_unitSpawns = (_npcSpawns = (_inventorySetups = (_pickupExecutes = (_pickupSpawns = (_lootSpawns = (_levelGenEvents = (_unitDamageCount = (_npcDamageCount = (_breakableDamageCount = (_unitDeaths = (_npcDeaths = (_breakableDeaths = (_inventoryGetSerializedCount = (_inventoryDestroyCount = (_inventoryTransferCount = (_inventoryDropCount = (_inventoryMoveToPlayerCount = 0))))))))))))))))))); } } } } namespace SULFURTogether.Patches { internal static class BossEncounterPatches { private static STLogger Log => Plugin.Log; public static void Apply(Harmony harmony) { if (!Plugin.Cfg.EnableBossEncounterSync.Value) { Log.Info("[BossEncounter] Disabled by config."); return; } PatchStart(harmony, FindType("BossFightHelper", "PerfectRandom.Sulfur.Core.BossFightHelper", "PerfectRandom.Sulfur.Gameplay.BossFightHelper"), "TriggerFight"); PatchStart(harmony, FindType("DesertClauseBossFightHelper", "PerfectRandom.Sulfur.Core.DesertClauseBossFightHelper", "PerfectRandom.Sulfur.Gameplay.DesertClauseBossFightHelper"), "TriggerFight", "OnStartInteractWithBoss"); PatchDesertPhaseSuppression(harmony, FindType("DesertClauseBossFightHelper", "PerfectRandom.Sulfur.Core.DesertClauseBossFightHelper", "PerfectRandom.Sulfur.Gameplay.DesertClauseBossFightHelper")); PatchDesertDismount(harmony, FindType("DesertClauseBossFightHelper", "PerfectRandom.Sulfur.Core.DesertClauseBossFightHelper", "PerfectRandom.Sulfur.Gameplay.DesertClauseBossFightHelper")); PatchDesertPikeAttach(harmony, FindType("DesertPikeCarrier", "PerfectRandom.Sulfur.Core.DesertPikeCarrier", "PerfectRandom.Sulfur.Gameplay.DesertPikeCarrier")); PatchDesertMissile(harmony, FindType("DesertMissileBase", "PerfectRandom.Sulfur.Gameplay.DesertMissileBase", "PerfectRandom.Sulfur.Core.DesertMissileBase")); PatchDesertMissileRocket(harmony, FindType("DesertMissileRocket", "PerfectRandom.Sulfur.Gameplay.DesertMissileRocket", "PerfectRandom.Sulfur.Core.DesertMissileRocket")); PatchDesertPerimeter(harmony, FindType("DesertClausePerimeter", "PerfectRandom.Sulfur.Gameplay.DesertClausePerimeter", "PerfectRandom.Sulfur.Core.DesertClausePerimeter")); PatchTerrorbaum(harmony, FindType("TerrorbaumBossFightHelper", "PerfectRandom.Sulfur.Gameplay.TerrorbaumBossFightHelper", "PerfectRandom.Sulfur.Core.TerrorbaumBossFightHelper")); PatchStart(harmony, FindType("WitchBossController", "PerfectRandom.Sulfur.Gameplay.WitchBossController", "PerfectRandom.Sulfur.Core.WitchBossController"), "EventStarted", "StartFight"); PatchStart(harmony, FindType("CousinHelper", "PerfectRandom.Sulfur.Gameplay.CousinHelper", "PerfectRandom.Sulfur.Core.CousinHelper"), "Trigger", "TriggerIntro", "Introduction", "StartFight"); PatchStart(harmony, FindType("LuciaBossFightHelper", "PerfectRandom.Sulfur.Core.LuciaBossFightHelper"), "TriggerFight"); Log.Info("[BossEncounter] Boss start entrypoint hooks registered."); if (Plugin.Cfg.EnableLuciaEyeAuthority.Value) { PatchLuciaEye(harmony, FindType("LuciaBossFightHelper", "PerfectRandom.Sulfur.Core.LuciaBossFightHelper")); } else { Log.Info("[LuciaEye] disabled by config."); } if (Plugin.Cfg.EnableLuciaDeathAuthority.Value) { PatchLuciaDeath(harmony, FindType("LuciaBossFightHelper", "PerfectRandom.Sulfur.Core.LuciaBossFightHelper")); } else { Log.Info("[LuciaDeath] disabled by config."); } Type type = FindType("CousinHelper", "PerfectRandom.Sulfur.Gameplay.CousinHelper", "PerfectRandom.Sulfur.Core.CousinHelper"); PatchDiscrete(harmony, type, "Submerge", "MoveToNewPool", "Reappear"); PatchDeath(harmony, type, "CousinDeath"); if (Plugin.Cfg.DeferBossIntroArm.Value && Plugin.Cfg.GateBossFightOnDialogClose.Value) { PatchIntroArm(harmony, type); } else { Log.Info("[BossArmDefer] intro-arm deferral disabled by config."); } PatchWitchPhaseStateHook(harmony); PatchWitchPhase2Probe(harmony); if (Plugin.Cfg.EnableWitchPhase2Manifest.Value) { PatchWitchP2HitResults(harmony); } else { Log.Info("[WitchP2] manifest disabled by config."); } if (Plugin.Cfg.EnableWitchDeathFix.Value) { PatchWitchDeath(harmony); } else { Log.Info("[WitchDeath] death fix disabled by config."); } PatchArenaDoorProbe(harmony); PatchDialogFlowProbe(harmony); ApplyCutsceneSuppressionPatches(harmony); ApplyLifecycleProbe(harmony); BossTypeDiscovery.LogEmperorTypes(); EmperorWormDiagnostics.Apply(harmony); EmperorSpiderDiagnostics.Apply(harmony); } private static void PatchWitchPhaseStateHook(Harmony harmony) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown Type type = FindType("WitchBossController", "PerfectRandom.Sulfur.Gameplay.WitchBossController"); if (type == null) { return; } HarmonyMethod val = new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("WitchChangePhase_Pre", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val2 = new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("WitchChangePhase_Post", BindingFlags.Static | BindingFlags.NonPublic)); foreach (MethodInfo item in from m in AccessTools.GetDeclaredMethods(type) where m.Name == "ChangePhase" && !m.IsStatic select m) { try { harmony.Patch((MethodBase)item, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.Info("[WitchPhase] patched WitchBossController.ChangePhase(" + string.Join(",", from p in item.GetParameters() select p.ParameterType.Name) + ")"); } catch (Exception ex) { Log.Error("[WitchPhase] patch WitchBossController.ChangePhase failed: " + ex.Message); } } } private static void PatchWitchPhase2Probe(Harmony harmony) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_00c1: 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_00e9: Expected O, but got Unknown //IL_00e9: Expected O, but got Unknown if (!Plugin.Cfg.LogWitchPhase2Probe.Value) { Log.Info("[WitchP2Probe] disabled by config."); return; } Type type = FindType("WitchPhase2", "PerfectRandom.Sulfur.Gameplay.WitchPhase2"); if (type == null) { Log.Info("[WitchP2Probe] WitchPhase2 type not found (skipped)"); return; } try { MethodInfo methodInfo = AccessTools.Method(type, "InitPhase", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("WitchP2_InitPhase_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo2 = AccessTools.Method(type, "ShowWitches", (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("WitchP2_ShowWitches_Pre", BindingFlags.Static | BindingFlags.NonPublic)), new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("WitchP2_ShowWitches_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[WitchP2Probe] patched WitchPhase2.InitPhase({methodInfo != null}) ShowWitches({methodInfo2 != null})"); } catch (Exception ex) { Log.Error("[WitchP2Probe] patch failed: " + ex.Message); } } private static void PatchDesertPhaseSuppression(Harmony harmony, Type desert) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Expected O, but got Unknown if (desert == null) { Log.Info("[BossCombat] DesertClause type not found (phase suppression skipped)"); return; } try { string[] array = new string[3] { "UpdatePhasesDeltaTime", "UpdatePhasesFixedTime", "TransitionTo" }; foreach (string text in array) { MethodInfo methodInfo = AccessTools.Method(desert, text, (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_UpdatePhases_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[BossCombat] patched DesertClause.{text}({methodInfo != null}) — client combat/transition suppression"); } MethodInfo methodInfo2 = AccessTools.Method(desert, "RepositionBossFromCamera", (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_RepositionBossFromCamera_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[BossCombat] patched DesertClause.RepositionBossFromCamera({methodInfo2 != null}) — client reposition suppression"); } catch (Exception ex) { Log.Error("[BossCombat] Desert phase suppression patch failed: " + ex.Message); } } private static bool Desert_UpdatePhases_Pre(object __instance) { try { return !NetBossEncounterManager.ShouldSuppressClientBossCombat(__instance); } catch { return true; } } private static bool Desert_RepositionBossFromCamera_Pre() { try { return !NetBossEncounterManager.ShouldSuppressClientBossReposition(); } catch { return true; } } private static void PatchDesertDismount(Harmony harmony, Type desert) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Expected O, but got Unknown if (desert == null) { Log.Info("[BossPhaseAction] DesertClause type not found (dismount sync skipped)"); return; } try { HarmonyMethod val = new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_OnBossJump_Post", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val2 = new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_OnBossLand_Post", BindingFlags.Static | BindingFlags.NonPublic)); MethodInfo methodInfo = AccessTools.Method(desert, "OnBossJump", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[BossPhaseAction] patched DesertClause.OnBossJump({methodInfo != null})"); string[] array = new string[2] { "OnBossLand", "OnBossLandTerminator" }; foreach (string text in array) { MethodInfo methodInfo2 = AccessTools.Method(desert, text, (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[BossPhaseAction] patched DesertClause.{text}({methodInfo2 != null})"); } HarmonyMethod val3 = new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_Anim_OnTriggerSandstorm_Pre", BindingFlags.Static | BindingFlags.NonPublic)); MethodInfo methodInfo3 = AccessTools.Method(desert, "Anim_OnTriggerSandstorm", (Type[])null, (Type[])null); if (methodInfo3 != null) { harmony.Patch((MethodBase)methodInfo3, val3, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[BossPhaseAction] patched DesertClause.Anim_OnTriggerSandstorm({methodInfo3 != null})"); } catch (Exception ex) { Log.Error("[BossPhaseAction] Desert dismount patch failed: " + ex.Message); } } private static void Desert_OnBossJump_Post(object __instance, bool __runOriginal) { if (__runOriginal) { NetBossEncounterManager.OnHostBossPikeDismount(__instance, jumping: true); } } private static void Desert_OnBossLand_Post(object __instance, bool __runOriginal) { if (__runOriginal) { NetBossEncounterManager.OnHostBossPikeDismount(__instance, jumping: false); } } private static bool Desert_Anim_OnTriggerSandstorm_Pre(object __instance) { return NetBossEncounterManager.OnLocalBossSandstorm(__instance); } private static void PatchTerrorbaum(Harmony harmony, Type terrorbaum) { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Expected O, but got Unknown //IL_016f: Expected O, but got Unknown if (terrorbaum == null) { Log.Info("[BossAnimSync] TerrorbaumBossFightHelper type not found (state mirror skipped)"); return; } try { Post("StartDigging", "Terror_Dig_Post"); Post("OnStartEruptAttack", "Terror_Erupt_Post"); Post("OnEruptStartAoe", "Terror_EruptAoe_Post"); Post("OnRootStart", "Terror_Root_Post"); Post("StopRoot", "Terror_RootStop_Post"); MethodInfo methodInfo = AccessTools.Method(terrorbaum, "UpdateAttackRoot", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Terror_AttackRoot_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[BossMech] patched Terrorbaum.UpdateAttackRoot({methodInfo != null}) — host lash roll"); Post("AbsorbPlayerBullet", "Terror_Absorb_Post"); Post("Anim_OnSpawnProjectiles", "Terror_Volley_Post"); MethodInfo methodInfo2 = AccessTools.Method(terrorbaum, "OnShootBulletFromSky", (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Terror_SkyShot_Pre", BindingFlags.Static | BindingFlags.NonPublic)), new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Terror_SkyShot_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[BossMech] patched Terrorbaum.OnShootBulletFromSky({methodInfo2 != null}) — sky-spike mirror"); DmgPre("Anim_OnDoSlamDamage"); DmgPre("Anim_OnDoEruptDamage"); DmgPre("Anim_OnDoEruptSlamDamage"); } catch (Exception ex) { Log.Error("[BossAnimSync] Terrorbaum patch failed: " + ex.Message); } void DmgPre(string method) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown MethodInfo methodInfo3 = AccessTools.Method(terrorbaum, method, (Type[])null, (Type[])null); if (methodInfo3 != null) { harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Terror_AnimDamage_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[BossAnimSync] patched Terrorbaum.{method}({methodInfo3 != null}) — client damage guard"); } void Post(string method, string handler) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown MethodInfo methodInfo3 = AccessTools.Method(terrorbaum, method, (Type[])null, (Type[])null); if (methodInfo3 != null) { harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod(handler, BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[BossAnimSync] patched Terrorbaum.{method}({methodInfo3 != null})"); } } private static void Terror_Dig_Post(object __instance, bool __runOriginal) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (__runOriginal) { NetBossEncounterManager.OnHostTerrorStateEvent(__instance, "TerrorDig", hasPos: false, default(Vector3)); } } private static void Terror_Erupt_Post(object __instance, Vector3 eruptPosition, string eruptTrigger, bool __runOriginal) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (__runOriginal) { NetBossEncounterManager.OnHostTerrorStateEvent(__instance, "TerrorErupt:" + eruptTrigger, hasPos: true, eruptPosition); } } private static void Terror_EruptAoe_Post(object __instance, bool __runOriginal) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (__runOriginal) { NetBossEncounterManager.OnHostTerrorStateEvent(__instance, "TerrorEruptAoe", hasPos: false, default(Vector3)); } } private static void Terror_Root_Post(object __instance, bool __runOriginal) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (__runOriginal) { NetBossEncounterManager.OnHostTerrorStateEvent(__instance, "TerrorRoot", hasPos: false, default(Vector3)); } } private static bool Terror_AnimDamage_Pre(object __instance) { return !NetBossEncounterManager.ShouldBlockClientPurePuppetBossAnimDamage(__instance); } private static void Terror_RootStop_Post(object __instance, bool __runOriginal) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (__runOriginal) { NetBossEncounterManager.OnHostTerrorStateEvent(__instance, "TerrorRootStop", hasPos: false, default(Vector3)); } } private static bool Terror_AttackRoot_Pre(object __instance) { return !TerrorbaumMechanicSync.TryHostLash(__instance); } private static void Terror_Absorb_Post(object __instance, bool __runOriginal) { if (__runOriginal) { TerrorbaumMechanicSync.HostAbsorbed(__instance); } } private static void Terror_Volley_Post(object __instance, bool __runOriginal) { if (__runOriginal) { TerrorbaumMechanicSync.HostVolleyStarted(__instance); } } private static void Terror_SkyShot_Pre(object __instance, ref object __state) { __state = TerrorbaumMechanicSync.CaptureSkyShotPre(__instance); } private static void Terror_SkyShot_Post(object __instance, object __state, bool __runOriginal) { if (__runOriginal) { TerrorbaumMechanicSync.BroadcastSkyShotPost(__instance, __state as TerrorbaumMechanicSync.SkyShotState); } } private static void PatchDesertPikeAttach(Harmony harmony, Type pikeCarrier) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Expected O, but got Unknown //IL_0105: 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_012d: Expected O, but got Unknown //IL_012d: Expected O, but got Unknown if (pikeCarrier == null) { Log.Info("[BossPhaseAction] DesertPikeCarrier type not found (pike-riding visibility skipped)"); return; } try { MethodInfo methodInfo = AccessTools.Method(pikeCarrier, "Update", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_PikeUpdate_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[BossPhaseAction] patched DesertPikeCarrier.Update({methodInfo != null}) — visibility probe"); MethodInfo methodInfo2 = AccessTools.Method(pikeCarrier, "ActivateShooting", (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_ActivateShooting_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[BossTargetRotate] patched DesertPikeCarrier.ActivateShooting({methodInfo2 != null})"); MethodInfo methodInfo3 = AccessTools.Method(pikeCarrier, "JumpTowards", (Type[])null, (Type[])null); if (methodInfo3 != null) { harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_JumpTowards_Pre", BindingFlags.Static | BindingFlags.NonPublic)), new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_JumpTowards_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[PikeJumpSync] patched DesertPikeCarrier.JumpTowards({methodInfo3 != null})"); } catch (Exception ex) { Log.Error("[BossPhaseAction] DesertPikeCarrier patch failed: " + ex.Message); } } private static void Desert_PikeUpdate_Post() { NetBossEncounterManager.UpdateBossPikeVisual(); NetBossEncounterManager.ProbeDesertVisibility(); } private static void Desert_ActivateShooting_Pre(object __instance) { NetBossEncounterManager.OnHostBossPikeActivateShooting(__instance); } private static bool Desert_JumpTowards_Pre(object __instance) { return !NetBossEncounterManager.ShouldBlockClientBossPikeJump(__instance); } private static void Desert_JumpTowards_Post(object __instance, bool __runOriginal) { if (__runOriginal) { NetBossEncounterManager.OnHostBossPikeJumpStarted(__instance); } } private static void PatchDesertMissile(Harmony harmony, Type missileBase) { //IL_0049: 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_0071: Expected O, but got Unknown //IL_0071: Expected O, but got Unknown //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Expected O, but got Unknown //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Expected O, but got Unknown //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Expected O, but got Unknown //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Expected O, but got Unknown //IL_0186: Expected O, but got Unknown if (missileBase == null) { Log.Info("[DesertMissile] DesertMissileBase type not found (missile sync skipped)"); return; } try { MethodInfo methodInfo = AccessTools.Method(missileBase, "StartMissiles", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_MissileStart_Pre", BindingFlags.Static | BindingFlags.NonPublic)), new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_MissileStart_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo2 = AccessTools.Method(missileBase, "StopRockets", (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_MissileStop_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo3 = AccessTools.Method(missileBase, "SpawnRealRocket", (Type[])null, (Type[])null); if (methodInfo3 != null) { harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_MissileRocket_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo4 = AccessTools.Method(missileBase, "InitiateFirePattern", (Type[])null, (Type[])null); if (methodInfo4 != null) { harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_MissilePattern_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo5 = AccessTools.Method(missileBase, "FireLinePattern", (Type[])null, (Type[])null); if (methodInfo5 != null) { harmony.Patch((MethodBase)methodInfo5, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_FireLine_Pre", BindingFlags.Static | BindingFlags.NonPublic)), new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_FireLine_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[DesertMissile] patched DesertMissileBase.StartMissiles({methodInfo != null}) StopRockets({methodInfo2 != null}) SpawnRealRocket({methodInfo3 != null}) InitiateFirePattern({methodInfo4 != null}) FireLinePattern({methodInfo5 != null})"); } catch (Exception ex) { Log.Error("[DesertMissile] patch failed: " + ex.Message); } } private static void PatchDesertPerimeter(Harmony harmony, Type perimeter) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown if (perimeter == null) { Log.Info("[DesertAdds] DesertClausePerimeter type not found (jump block skipped)"); return; } try { MethodInfo methodInfo = AccessTools.Method(perimeter, "UpdateJumping", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_PerimeterJumping_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[DesertAdds] patched DesertClausePerimeter.UpdateJumping({methodInfo != null})"); } catch (Exception ex) { Log.Error("[DesertAdds] perimeter patch failed: " + ex.Message); } } private static bool Desert_PerimeterJumping_Pre() { return !NetBossEncounterManager.ShouldBlockClientPerimeterJumping(); } private static void PatchDesertMissileRocket(Harmony harmony, Type rocket) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown if (rocket == null) { Log.Info("[DesertMissile] DesertMissileRocket type not found (ghost rockets skipped)"); return; } try { MethodInfo methodInfo = AccessTools.Method(rocket, "CheckAndDamageUnit", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_RocketDamage_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo2 = AccessTools.Method(rocket, "DestroyMissile", (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("Desert_RocketDestroy_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[DesertMissile] patched DesertMissileRocket.CheckAndDamageUnit({methodInfo != null}) DestroyMissile({methodInfo2 != null})"); } catch (Exception ex) { Log.Error("[DesertMissile] rocket patch failed: " + ex.Message); } } private static bool Desert_MissileStart_Pre(object __instance) { return !NetBossEncounterManager.ShouldBlockClientMissileStart(__instance); } private static void Desert_MissileStart_Post(object __instance, bool __runOriginal) { if (__runOriginal) { NetBossEncounterManager.OnHostMissileStart(__instance); } } private static void Desert_MissileStop_Post(object __instance, bool __runOriginal) { if (__runOriginal) { NetBossEncounterManager.OnHostMissileStop(__instance); } } private static void Desert_MissileRocket_Post(object __instance) { NetBossEncounterManager.OnMissileRealRocketFired(__instance); } private static void Desert_MissilePattern_Post(object __instance, bool __runOriginal) { if (__runOriginal) { NetBossEncounterManager.OnHostMissilePatternInitiated(__instance); } } private static void Desert_FireLine_Pre(object __instance) { NetBossEncounterManager.OnMissileLinePatternPre(__instance); } private static void Desert_FireLine_Post(object __instance) { NetBossEncounterManager.OnMissileLinePatternPost(); } private static bool Desert_RocketDamage_Pre(object __instance) { return !NetBossEncounterManager.IsGhostRocket(__instance); } private static void Desert_RocketDestroy_Post(object __instance) { NetBossEncounterManager.OnRocketDestroyed(__instance); } private static void WitchP2_InitPhase_Pre(object __instance) { WitchPhase2Probe.OnInitPhase(__instance); } private static bool WitchP2_ShowWitches_Pre(object __instance) { WitchPhase2Probe.OnShowWitchesEnter(__instance); try { return NetBossEncounterManager.OnLocalWitchShowWitches_Pre(__instance); } catch { return true; } } private static void WitchP2_ShowWitches_Post(object __instance, bool __runOriginal) { WitchPhase2Probe.OnShowWitchesExit(__instance); if (__runOriginal) { NetBossEncounterManager.OnHostWitchShowWitches(__instance); } } private static void PatchWitchP2HitResults(Harmony harmony) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected O, but got Unknown Type type = FindType("WitchPhase2", "PerfectRandom.Sulfur.Gameplay.WitchPhase2"); if (type == null) { Log.Info("[WitchP2] WitchPhase2 type not found (result hooks skipped)"); return; } try { MethodInfo methodInfo = AccessTools.Method(type, "RealWitchTakeDamage", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("WitchP2_RealHit_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo2 = AccessTools.Method(type, "IllusionTakeDamage", (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("WitchP2_IllusionHit_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[WitchP2] patched RealWitchTakeDamage({methodInfo != null}) IllusionTakeDamage({methodInfo2 != null})"); } catch (Exception ex) { Log.Error("[WitchP2] patch hit results failed: " + ex.Message); } } private static void WitchP2_RealHit_Post(object __instance, object unit, bool __runOriginal) { if (__runOriginal) { NetBossEncounterManager.OnHostWitchP2Hit(__instance, unit, realHit: true); } } private static void WitchP2_IllusionHit_Post(object __instance, object unit, bool __runOriginal) { if (__runOriginal) { NetBossEncounterManager.OnHostWitchP2Hit(__instance, unit, realHit: false); } } private static void PatchWitchDeath(Harmony harmony) { //IL_0072: 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_009a: Expected O, but got Unknown //IL_009a: Expected O, but got Unknown Type type = FindType("WitchBossController", "PerfectRandom.Sulfur.Gameplay.WitchBossController"); if (type == null) { Log.Info("[WitchDeath] WitchBossController type not found"); return; } MethodInfo methodInfo = AccessTools.Method(type, "WitchDeath", (Type[])null, (Type[])null); if (methodInfo == null) { Log.Info("[WitchDeath] WitchBossController.WitchDeath not found"); return; } try { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("WitchDeath_Pre", BindingFlags.Static | BindingFlags.NonPublic)), new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("WitchDeath_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.Info("[WitchDeath] patched WitchBossController.WitchDeath (prefix+postfix)"); } catch (Exception ex) { Log.Error("[WitchDeath] patch WitchDeath failed: " + ex.Message); } } private static bool WitchDeath_Pre(object __instance) { try { return NetBossEncounterManager.OnLocalWitchDeath_Pre(__instance); } catch { return true; } } private static void WitchDeath_Post(object __instance, bool __runOriginal) { if (__runOriginal) { NetBossEncounterManager.OnHostWitchDeath(__instance); } } private static bool WitchChangePhase_Pre(object __instance, object phase) { try { return NetBossEncounterManager.OnLocalWitchChangePhase_Pre(__instance, Convert.ToInt32(phase)); } catch { return true; } } private static void WitchChangePhase_Post(object __instance, object phase, bool __runOriginal) { if (__runOriginal) { int phaseInt; try { phaseInt = Convert.ToInt32(phase); } catch { phaseInt = -1; } NetBossEncounterManager.OnHostWitchChangePhase_Post(__instance, phaseInt); NetBossEncounterManager.OnHostBossPhaseChanged(__instance); } } private static void PatchDiscrete(Harmony harmony, Type? type, params string[] methodNames) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown if (type == null) { return; } HarmonyMethod val = new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("BossDiscrete_Pre", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val2 = new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("BossDiscrete_Post", BindingFlags.Static | BindingFlags.NonPublic)); foreach (string name in methodNames) { List list = (from m in AccessTools.GetDeclaredMethods(type) where m.Name == name && !m.IsStatic select m).ToList(); if (list.Count == 0) { Log.Info("[CousinPool] " + type.Name + "." + name + " not found (skipped)"); continue; } foreach (MethodInfo item in list) { try { harmony.Patch((MethodBase)item, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.Info("[CousinPool] patched " + type.Name + "." + name); } catch (Exception ex) { Log.Error("[CousinPool] patch failed " + type.Name + "." + name + ": " + ex.Message); } } } } private static bool BossDiscrete_Pre(object __instance, MethodBase __originalMethod) { try { return NetBossEncounterManager.OnLocalBossDiscreteEvent_Pre(__instance, __originalMethod?.Name ?? "?"); } catch { return true; } } private static void BossDiscrete_Post(object __instance, MethodBase __originalMethod, bool __runOriginal) { if (__runOriginal) { NetBossEncounterManager.OnLocalBossDiscreteEvent(__instance, __originalMethod?.Name ?? "?"); } } private static void PatchDeath(Harmony harmony, Type? type, params string[] methodNames) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown if (type == null) { return; } HarmonyMethod val = new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("BossDeath_Post", BindingFlags.Static | BindingFlags.NonPublic)); foreach (string name in methodNames) { foreach (MethodInfo item in from m in AccessTools.GetDeclaredMethods(type) where m.Name == name && !m.IsStatic select m) { try { harmony.Patch((MethodBase)item, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.Info("[CousinDeath] patched " + type.Name + "." + name); } catch (Exception ex) { Log.Error("[CousinDeath] patch failed " + type.Name + "." + name + ": " + ex.Message); } } } } private static void BossDeath_Post(object __instance, MethodBase __originalMethod, bool __runOriginal) { if (__runOriginal) { NetBossEncounterManager.OnHostBossDeath(__instance, __originalMethod?.Name ?? "?"); } } private static void PatchIntroArm(Harmony harmony, Type? type) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown if (type == null) { Log.Info("[BossArmDefer] CousinHelper type not found (skipped)"); return; } HarmonyMethod val = new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("CousinSpawnArm_Pre", BindingFlags.Static | BindingFlags.NonPublic)); List list = (from m in AccessTools.GetDeclaredMethods(type) where m.Name == "SpawnArm" && !m.IsStatic select m).ToList(); if (list.Count == 0) { Log.Info("[BossArmDefer] " + type.Name + ".SpawnArm not found (skipped)"); return; } foreach (MethodInfo item in list) { try { harmony.Patch((MethodBase)item, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.Info("[BossArmDefer] patched " + type.Name + ".SpawnArm(" + string.Join(",", from p in item.GetParameters() select p.ParameterType.Name) + ")"); } catch (Exception ex) { Log.Error("[BossArmDefer] patch failed " + type.Name + ".SpawnArm: " + ex.Message); } } } private static bool CousinSpawnArm_Pre(object __instance) { try { return NetBossEncounterManager.OnLocalIntroArmSpawn(__instance); } catch { return true; } } private static void PatchLuciaEye(Harmony harmony, Type? type) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown if (type == null) { Log.Info("[LuciaEye] LuciaBossFightHelper type not found (skipped)"); return; } HarmonyMethod val = new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("LuciaEyeDied_Pre", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val2 = new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("LuciaEyeDied_Post", BindingFlags.Static | BindingFlags.NonPublic)); List list = (from m in AccessTools.GetDeclaredMethods(type) where m.Name == "EyeDied" && !m.IsStatic select m).ToList(); if (list.Count == 0) { Log.Info("[LuciaEye] LuciaBossFightHelper.EyeDied not found (skipped)"); return; } foreach (MethodInfo item in list) { try { harmony.Patch((MethodBase)item, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.Info("[LuciaEye] patched " + type.Name + ".EyeDied(" + string.Join(",", from p in item.GetParameters() select p.ParameterType.Name) + ")"); } catch (Exception ex) { Log.Error("[LuciaEye] patch failed " + type.Name + ".EyeDied: " + ex.Message); } } } private static bool LuciaEyeDied_Pre(object __instance, object eyeUnit) { try { return NetBossEncounterManager.OnLocalLuciaEyeDied(__instance, eyeUnit); } catch { return true; } } private static void LuciaEyeDied_Post(object __instance, bool __runOriginal) { if (__runOriginal) { NetBossEncounterManager.OnHostLuciaEyeDied(__instance); } } private static void PatchLuciaDeath(Harmony harmony, Type? type) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown if (type == null) { Log.Info("[LuciaDeath] LuciaBossFightHelper type not found (skipped)"); return; } HarmonyMethod val = new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("LuciaOnBossDead_Pre", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val2 = new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("LuciaOnBossDead_Post", BindingFlags.Static | BindingFlags.NonPublic)); List list = (from m in AccessTools.GetDeclaredMethods(type) where m.Name == "OnBossDead" && !m.IsStatic select m).ToList(); if (list.Count == 0) { Log.Info("[LuciaDeath] LuciaBossFightHelper.OnBossDead not declared on type (skipped)"); return; } foreach (MethodInfo item in list) { try { harmony.Patch((MethodBase)item, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.Info("[LuciaDeath] patched " + type.Name + ".OnBossDead(" + string.Join(",", from p in item.GetParameters() select p.ParameterType.Name) + ")"); } catch (Exception ex) { Log.Error("[LuciaDeath] patch failed " + type.Name + ".OnBossDead: " + ex.Message); } } } private static bool LuciaOnBossDead_Pre(object __instance, object unit) { try { return NetBossEncounterManager.OnLocalLuciaBossDead_Pre(__instance, unit); } catch { return true; } } private static void LuciaOnBossDead_Post(object __instance, object unit, bool __runOriginal) { if (__runOriginal) { NetBossEncounterManager.OnHostLuciaBossDead_Post(__instance, unit); } } private static void ApplyLifecycleProbe(Harmony harmony) { if (!Plugin.Cfg.EnableBossLifecycleProbe.Value) { Log.Info("[BossLifecycle] disabled by config."); return; } Type type = FindType("BossFightHelper", "PerfectRandom.Sulfur.Core.BossFightHelper", "PerfectRandom.Sulfur.Gameplay.BossFightHelper"); PatchProbe(harmony, type, false, "TriggerFight", "TransitionTo", "StartPhase", "OnBossDead"); PatchProbe(harmony, type, true, "OnDamageReceieved"); Type type2 = FindType("BossPhase", "PerfectRandom.Sulfur.Gameplay.BossPhase"); PatchProbe(harmony, type2, false, "StartBossPhases", "StartNextPhase", "StartTransition", "SetTransitionVars"); Type type3 = FindType("WitchBossController", "PerfectRandom.Sulfur.Gameplay.WitchBossController"); PatchProbe(harmony, type3, false, "EventStarted", "StartFight", "ChangePhase", "WitchDeath", "TeleportPlayerTo"); PatchProbe(harmony, type3, true, "OnDamageMainWitch"); Type type4 = FindType("CousinHelper", "PerfectRandom.Sulfur.Gameplay.CousinHelper"); PatchProbe(harmony, type4, false, "Trigger", "TriggerIntro", "Introduction", "StartFight", "Submerge", "Reappear", "MoveToNewPool", "CousinDeath"); Type type5 = FindType("WitchAnimationControl", "PerfectRandom.Sulfur.Gameplay.WitchAnimationControl"); PatchProbe(harmony, type5, false, "EnableChurchBlockade", "LookAtWitch", "EnableOutsideTrigger"); Log.Info("[BossLifecycle] lifecycle probe hooks registered."); } private static void PatchProbe(Harmony harmony, Type? type, bool throttle, params string[] methodNames) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown if (type == null) { return; } HarmonyMethod val = new HarmonyMethod(typeof(BossEncounterPatches).GetMethod(throttle ? "BossLifecycle_Post_Throttled" : "BossLifecycle_Post", BindingFlags.Static | BindingFlags.NonPublic)); foreach (string name in methodNames) { List list = (from m in AccessTools.GetDeclaredMethods(type) where m.Name == name && !m.IsStatic && !m.IsAbstract select m).ToList(); if (list.Count == 0) { Log.Info("[BossLifecycle] " + type.Name + "." + name + " not found (skipped)"); continue; } foreach (MethodInfo item in list) { try { harmony.Patch((MethodBase)item, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { Log.Error("[BossLifecycle] patch failed " + type.Name + "." + name + ": " + ex.Message); } } } } private static void BossLifecycle_Post(object __instance, MethodBase __originalMethod, bool __runOriginal) { BossLifecycleProbe.OnLifecycle(__instance, __originalMethod?.Name ?? "?", throttle: false, __runOriginal); } private static void BossLifecycle_Post_Throttled(object __instance, MethodBase __originalMethod, bool __runOriginal) { BossLifecycleProbe.OnLifecycle(__instance, __originalMethod?.Name ?? "?", throttle: true, __runOriginal); } private static void PatchArenaDoorProbe(Harmony harmony) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Expected O, but got Unknown if (!Plugin.Cfg.LogBossPreFight.Value) { Log.Info("[ArenaDoor] probe disabled by config (LogBossPreFight=false)."); return; } try { Type type = FindType("DoorBlocker", "PerfectRandom.Sulfur.Core.DoorBlocker"); if (type != null) { MethodInfo methodInfo = AccessTools.Method(type, "CloseDoor", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("DoorClose_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[ArenaDoor] patched DoorBlocker.CloseDoor({methodInfo != null})"); } Type type2 = FindType("AllDeadTrigger", "PerfectRandom.Sulfur.Core.AllDeadTrigger"); if (!(type2 != null)) { return; } string[] array = new string[2] { "RegisterDeath", "CheckAllDead" }; foreach (string text in array) { MethodInfo methodInfo2 = AccessTools.Method(type2, text, (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("AllDead_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } Log.Info("[ArenaDoor] patched AllDeadTrigger.RegisterDeath/CheckAllDead"); } catch (Exception ex) { Log.Error("[ArenaDoor] probe patch failed: " + ex.Message); } } private static void PatchDialogFlowProbe(Harmony harmony) { //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Expected O, but got Unknown //IL_00aa: 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_00d2: Expected O, but got Unknown //IL_00d2: Expected O, but got Unknown //IL_0393: Unknown result type (might be due to invalid IL or missing references) //IL_03ae: Unknown result type (might be due to invalid IL or missing references) //IL_03bb: Expected O, but got Unknown //IL_03bb: Expected O, but got Unknown //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Expected O, but got Unknown //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Expected O, but got Unknown //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Expected O, but got Unknown //IL_0271: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Expected O, but got Unknown //IL_02b2: Unknown result type (might be due to invalid IL or missing references) //IL_02bf: Expected O, but got Unknown if (!Plugin.Cfg.LogBossPreFight.Value && !Plugin.Cfg.GateBossFightOnDialogClose.Value) { Log.Info("[DialogFlow] hooks disabled (LogBossPreFight=false, GateBossFightOnDialogClose=false)."); return; } try { Type type = FindType("Npc", "PerfectRandom.Sulfur.Core.Units.Npc"); if (type != null) { MethodInfo methodInfo = AccessTools.GetDeclaredMethods(type).FirstOrDefault((MethodInfo m) => m.Name == "Interact" && !m.IsStatic && m.GetParameters().Length == 1); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("NpcInteract_Pre", BindingFlags.Static | BindingFlags.NonPublic)), new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("NpcInteract_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[DialogFlow] patched Npc.Interact(Player)({methodInfo != null})"); } Type type2 = FindType("DialogController", "PerfectRandom.Sulfur.Core.DialogController"); if (type2 != null) { MethodInfo methodInfo2 = AccessTools.Method(type2, "SetCurrentSpeakable", (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("SetCurrentSpeakable_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[DialogFlow] patched DialogController.SetCurrentSpeakable({methodInfo2 != null})"); MethodInfo methodInfo3 = AccessTools.Method(type2, "AcceptDialogOption", (Type[])null, (Type[])null); if (methodInfo3 != null) { harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("AcceptDialogOption_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo4 = AccessTools.Method(type2, "OnMultipleChoiceRequest", (Type[])null, (Type[])null); if (methodInfo4 != null) { harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("MultipleChoiceRequest_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo5 = AccessTools.Method(type2, "Update", (Type[])null, (Type[])null); if (methodInfo5 != null) { harmony.Patch((MethodBase)methodInfo5, (HarmonyMethod)null, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("DialogControllerUpdate_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo6 = AccessTools.Method(type2, "OnDialogueStarted", (Type[])null, (Type[])null); if (methodInfo6 != null) { harmony.Patch((MethodBase)methodInfo6, (HarmonyMethod)null, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("OnDialogueStarted_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo7 = AccessTools.Method(type2, "OnDialogueFinished", (Type[])null, (Type[])null); if (methodInfo7 != null) { harmony.Patch((MethodBase)methodInfo7, (HarmonyMethod)null, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("OnDialogueFinished_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[DialogInput] patched DialogController.AcceptDialogOption({methodInfo3 != null}) OnMultipleChoiceRequest({methodInfo4 != null}) Update({methodInfo5 != null}) started({methodInfo6 != null}) finished({methodInfo7 != null})"); } Type type3 = FindType("PlayerTrigger", "PerfectRandom.Sulfur.Core.World.PlayerTrigger"); if (type3 != null) { MethodInfo methodInfo8 = AccessTools.Method(type3, "Trigger", new Type[1] { typeof(GameObject) }, (Type[])null); if (methodInfo8 != null) { harmony.Patch((MethodBase)methodInfo8, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("PlayerTrigger_Pre", BindingFlags.Static | BindingFlags.NonPublic)), new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("PlayerTrigger_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[DialogFlow] patched PlayerTrigger.Trigger({methodInfo8 != null}) (+TB-INTRO mirror)"); } } catch (Exception ex) { Log.Error("[DialogFlow] probe patch failed: " + ex.Message); } } private static bool NpcInteract_Pre(object __instance) { try { if (NetEmperorSpiderSync.TryTeleportLateP2Player(__instance)) { return false; } } catch { } try { if (NetBossEncounterManager.ShouldBlockBossDialogNpc(__instance)) { return false; } } catch { } try { if (NetBossEncounterManager.ShouldBlockStartedBossOwnDialog(__instance)) { return false; } } catch { } try { if (NetBossEncounterManager.ShouldBlockOutOfRoomBossOwnDialog(__instance)) { return false; } } catch { } return true; } private static void NpcInteract_Post(object __instance, bool __runOriginal) { BossDialogFlowProbe.OnNpcInteract(__instance, __runOriginal); if (__runOriginal) { NetBossEncounterManager.NotifyBossDialogOpened(__instance); } if (__runOriginal) { NetBossEncounterManager.OnHostBossDialogInteract(__instance); } if (__runOriginal) { NetBossEncounterManager.OnLocalBossOwnDialogInteract(__instance); } } private static void ApplyCutsceneSuppressionPatches(Harmony harmony) { //IL_0085: 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_00ad: Expected O, but got Unknown //IL_00ad: Expected O, but got Unknown //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Expected O, but got Unknown //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Expected O, but got Unknown //IL_02aa: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: Expected O, but got Unknown if (!Plugin.Cfg.GateBossDialogToInRoom.Value) { Log.Info("[BossDialogCutscene] suppression patches off (GateBossDialogToInRoom=false)"); return; } try { Type type = FindType("CousinHelper", "PerfectRandom.Sulfur.Gameplay.CousinHelper", "PerfectRandom.Sulfur.Core.CousinHelper"); MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "Introduction", Type.EmptyTypes, (Type[])null)); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("BossIntroSuppress_Pre", BindingFlags.Static | BindingFlags.NonPublic)), new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("BossIntroSuppress_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[BossDialogCutscene] patched CousinHelper.Introduction suppression ({methodInfo != null})"); MethodInfo methodInfo2 = ((type == null) ? null : AccessTools.Method(type, "DoneAppearing", Type.EmptyTypes, (Type[])null)); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("BossDoneAppearing_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[BossDialogCutscene] patched CousinHelper.DoneAppearing invuln-hold ({methodInfo2 != null})"); Type type2 = FindType("GameManager", "PerfectRandom.Sulfur.Core.GameManager"); if (type2 != null) { string[] array = new string[2] { "ModifyControllerLock", "ModifyPlayerInvulnerability" }; foreach (string name in array) { MethodInfo methodInfo3 = AccessTools.GetDeclaredMethods(type2).FirstOrDefault((MethodInfo m) => m.Name == name && m.GetParameters().Length == 2); if (methodInfo3 != null) { harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("CinematicLock_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[BossDialogCutscene] patched GameManager.{name}({methodInfo3 != null})"); } } Type type3 = FindType("Player", "PerfectRandom.Sulfur.Core.Units.Player", "PerfectRandom.Sulfur.Core.Player"); MethodInfo[] array2 = ((type3 == null) ? Array.Empty() : (from m in AccessTools.GetDeclaredMethods(type3) where m.Name == "RotateCameraTowardsPosition" select m).ToArray()); MethodInfo[] array3 = array2; foreach (MethodInfo methodInfo4 in array3) { harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("RotateCamera_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log.Info($"[BossDialogCutscene] patched Player.RotateCameraTowardsPosition x{array2.Length}"); } catch (Exception ex) { Log.Error("[BossDialogCutscene] suppression patch failed: " + ex.Message); } } private static void BossIntroSuppress_Pre(object __instance) { try { if (NetBossEncounterManager.IsLocalOutOfRoomForBoss(__instance)) { NetBossEncounterManager.SetSuppressBossCutscene(on: true); NetBossEncounterManager.MarkIntroRanOutOfRoom(__instance); } } catch { } } private static void BossIntroSuppress_Post() { try { NetBossEncounterManager.SetSuppressBossCutscene(on: false); } catch { } } private static bool CinematicLock_Pre(object[] __args) { try { if (!NetBossEncounterManager.IsSuppressingBossCutscene) { return true; } if (__args == null || __args.Length < 2) { return true; } object obj = __args[1]; bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } int num2 = num & (flag ? 1 : 0); bool flag2 = __args[0] != null && __args[0].ToString() == "Cinematic"; if (((uint)num2 & (flag2 ? 1u : 0u)) != 0) { return false; } } catch { } return true; } private static bool RotateCamera_Pre() { try { if (NetBossEncounterManager.IsSuppressingBossCutscene) { return false; } } catch { } return true; } private static void BossDoneAppearing_Post(object __instance) { try { NetBossEncounterManager.OnBossDoneAppearing(__instance); } catch { } } private static void AcceptDialogOption_Post(object __instance) { BossDialogFlowProbe.OnAcceptDialogOption(__instance); } private static void MultipleChoiceRequest_Post(object __instance) { BossDialogFlowProbe.OnMultipleChoiceRequest(__instance); } private static void DialogControllerUpdate_Post(object __instance) { BossDialogFlowProbe.OnDialogUpdate(__instance); } private static void OnDialogueStarted_Post(object __instance) { BossDialogFlowProbe.OnDialogueStarted(__instance); } private static void OnDialogueFinished_Post(object __instance) { BossDialogFlowProbe.OnDialogueFinished(__instance); } private static void SetCurrentSpeakable_Post(object speakable) { BossDialogFlowProbe.OnSetCurrentSpeakable(speakable); if (speakable == null) { NetBossEncounterManager.NotifyDialogClosed(); } if (speakable == null) { NetBossEncounterManager.OnHostBossDialogClosed(); } NetBossEncounterManager.OnLocalDialogSpeakableChanged(speakable); } private static bool PlayerTrigger_Pre(object __instance, ref bool __state) { __state = GateSyncManager.TryReadHasBeenTriggered(__instance, out var fired) && fired; try { if (NetBossEncounterManager.ShouldBlockBossAppearTrigger(__instance)) { return false; } } catch { } return true; } private static void PlayerTrigger_Post(object __instance, bool __state, bool __runOriginal) { BossDialogFlowProbe.OnPlayerTrigger(__instance); if (__runOriginal) { GateSyncManager.OnLocalPlayerTriggerFired(__instance, __state); } } private static void DoorClose_Post(object __instance, bool __runOriginal) { if (__runOriginal) { ArenaDoorProbe.OnDoorClose(__instance); } } private static void AllDead_Post(object __instance, MethodBase __originalMethod, bool __runOriginal) { if (__runOriginal) { ArenaDoorProbe.OnAllDead(__instance, __originalMethod?.Name ?? "?"); } } private static Type? FindType(string shortName, params string[] fullNames) { for (int i = 0; i < fullNames.Length; i++) { Type type = AccessTools.TypeByName(fullNames[i]); if (type != null) { return type; } } try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type[] array; try { array = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { array = ex.Types.Where((Type t) => t != null).ToArray(); } Type[] array2 = array; foreach (Type type2 in array2) { if (type2 != null && type2.Name == shortName) { return type2; } } } } catch { } Log.Info("[BossEncounter] type not found: " + shortName); return null; } private static void PatchStart(Harmony harmony, Type? type, params string[] methodNames) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown if (type == null) { return; } HarmonyMethod val = new HarmonyMethod(typeof(BossEncounterPatches).GetMethod("BossStartEntrypoint_Pre", BindingFlags.Static | BindingFlags.NonPublic)); foreach (string name in methodNames) { List list = (from m in AccessTools.GetDeclaredMethods(type) where m.Name == name && !m.IsStatic && !m.IsAbstract select m).ToList(); if (list.Count == 0) { Log.Info("[BossEncounter] " + type.Name + "." + name + " not found (skipped)"); continue; } foreach (MethodInfo item in list) { try { harmony.Patch((MethodBase)item, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.Info("[BossEncounter] patched " + type.Name + "." + name + "(" + string.Join(",", from p in item.GetParameters() select p.ParameterType.Name) + ")"); } catch (Exception ex) { Log.Error("[BossEncounter] patch failed " + type.Name + "." + name + ": " + ex.Message); } } } } private static bool BossStartEntrypoint_Pre(object __instance, MethodBase __originalMethod) { try { string source = __instance?.GetType().Name + "." + __originalMethod?.Name; return NetBossEncounterManager.OnLocalStartEntrypoint(__instance, source); } catch (Exception ex) { Log.Warn("[BossEncounter] start entrypoint prefix failed: " + ex.Message); return true; } } } internal static class BossSpawnPatches { public static void Apply(Harmony harmony) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown bool num = Plugin.Cfg.EnableBossEncounterSync.Value && Plugin.Cfg.EnableBossDynamicSpawnManifest.Value; bool value = Plugin.Cfg.EnableRuntimeSpawnSync.Value; if (!num && !value) { Plugin.Log.Info("[BossSpawn] dynamic spawn manifest + runtime spawn sync disabled by config."); return; } Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Units.UnitSO"); if (type == null) { Plugin.Log.Info("[BossSpawn] UnitSO type not found."); return; } HarmonyMethod val = new HarmonyMethod(typeof(BossSpawnPatches).GetMethod("SpawnUnitAsync_Pre", BindingFlags.Static | BindingFlags.NonPublic)); HarmonyMethod val2 = new HarmonyMethod(typeof(BossSpawnPatches).GetMethod("SpawnUnit_Post", BindingFlags.Static | BindingFlags.NonPublic)); int num2 = 0; foreach (MethodInfo item in from m in AccessTools.GetDeclaredMethods(type) where m.Name == "SpawnUnitAsync" && !m.IsStatic select m) { ParameterInfo[] parameters = item.GetParameters(); if (parameters.Length != 0 && !(parameters[0].ParameterType.Name != "MonoBehaviour")) { try { harmony.Patch((MethodBase)item, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num2++; } catch (Exception ex) { Plugin.Log.Error("[BossSpawn] patch SpawnUnitAsync failed: " + ex.Message); } } } MethodInfo methodInfo = AccessTools.GetDeclaredMethods(type).FirstOrDefault((MethodInfo m) => m.Name == "SpawnUnit" && m.IsStatic); if (methodInfo != null) { try { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num2++; } catch (Exception ex2) { Plugin.Log.Error("[BossSpawn] patch SpawnUnit failed: " + ex2.Message); } } else { Plugin.Log.Info("[BossSpawn] static UnitSO.SpawnUnit not found."); } Plugin.Log.Info($"[BossSpawn] dynamic spawn manifest hooks registered ({num2})."); } private static void SpawnUnitAsync_Pre(object __instance, MonoBehaviour mono) { BossDynamicSpawnManifest.NotePendingSpawn(__instance, mono); RuntimeSpawnManager.NotePendingSpawn(__instance, mono); } private static void SpawnUnit_Post(object unitSo, Vector3 position, object? __result) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (__result != null) { BossDynamicSpawnManifest.OnUnitSpawned(unitSo, __result, position); RuntimeSpawnManager.OnUnitSpawned(unitSo, __result, position); } } } internal static class BreakablePatches { public static void Apply(Harmony harmony) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Expected O, but got Unknown try { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Units.Breakable"); if (type == null) { Plugin.Log.Warn("[BreakableBreak] Breakable type not found — destructible sync disabled."); return; } MethodInfo methodInfo = AccessTools.DeclaredMethod(type, "Start", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(BreakablePatches).GetMethod("Breakable_Start_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { Plugin.Log.Warn("[BreakableBreak] Breakable.Start not found — spawn-position registry disabled."); } MethodInfo methodInfo2 = AccessTools.DeclaredMethod(type, "Die", (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(BreakablePatches).GetMethod("Breakable_Die_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { Plugin.Log.Warn("[BreakableBreak] Breakable.Die not found — destructible sync disabled."); } Plugin.Log.Info("[BreakableBreak] Patched Breakable.Start/Die (in-scene destructible sync)."); } catch (Exception ex) { Plugin.Log.Error("[BreakableBreak] Apply failed: " + ex.Message); } } private static void Breakable_Start_Post(Breakable __instance) { if (Plugin.Cfg.EnableBreakableSync.Value) { BreakableBreakManager.Register(__instance); } } private static void Breakable_Die_Pre(Breakable __instance) { BreakableBreakManager.CaptureLocalBreak(__instance); BreakableBreakManager.Unregister(__instance); } } internal static class CousinArmPatches { private static Type? _cousinArmType; private static FieldInfo? _npcField; private static FieldInfo? _damageField; private static FieldInfo? _barrelField; private static FieldInfo? _heightCurveField; private static FieldInfo? _forceCurveField; private static FieldInfo? _visualField; private static FieldInfo? _prefabOnHitField; private static MethodInfo? _throwMethod; private static FieldInfo? _aiAgentTargetField; private static FieldInfo? _playerUnitField; private static Type? _gmType; private static PropertyInfo? _gmInstance; private static PropertyInfo? _gmPlayers; private static bool _inThrowLoop; private static float _lastLogAt; private const BindingFlags BF = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly List _unitScratch = new List(4); public static void Apply(Harmony harmony) { //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Expected O, but got Unknown if (!Plugin.Cfg.EnableCousinArmSync.Value) { Plugin.Log.Info("[ArmThrow] CousinArm AoE/visual patch disabled by config."); return; } _cousinArmType = FindGameType("PerfectRandom.Sulfur.Gameplay.CousinArm") ?? FindGameType("PerfectRandom.Sulfur.Core.CousinArm") ?? FindGameType("CousinArm"); if (_cousinArmType == null) { Plugin.Log.Info("[ArmThrow] CousinArm type not found."); return; } _npcField = _cousinArmType.GetField("npc", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _damageField = _cousinArmType.GetField("damage", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _barrelField = _cousinArmType.GetField("barrelTransform", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _heightCurveField = _cousinArmType.GetField("throwHeightCurve", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _forceCurveField = _cousinArmType.GetField("throwForceCurve", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _visualField = _cousinArmType.GetField("visualProjectile", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _prefabOnHitField = _cousinArmType.GetField("prefabOnHit", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _throwMethod = _cousinArmType.GetMethod("ThrowProjectile", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); _gmType = FindGameType("PerfectRandom.Sulfur.Core.GameManager") ?? FindGameType("GameManager"); _gmInstance = _gmType?.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy); _gmPlayers = _gmType?.GetProperty("Players", BindingFlags.Instance | BindingFlags.Public); if (_throwMethod == null) { Plugin.Log.Info("[ArmThrow] CousinArm.ThrowProjectile not found."); return; } HarmonyMethod val = new HarmonyMethod(typeof(CousinArmPatches).GetMethod("ThrowProjectile_Pre", BindingFlags.Static | BindingFlags.NonPublic)); try { harmony.Patch((MethodBase)_throwMethod, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info($"[ArmThrow] patched CousinArm.ThrowProjectile (npc={_npcField != null} barrel={_barrelField != null} visual={_visualField != null} players={_gmPlayers != null})"); } catch (Exception ex) { Plugin.Log.Error("[ArmThrow] patch CousinArm.ThrowProjectile failed: " + ex.Message); } } private static bool ThrowProjectile_Pre(object __instance) { if (_inThrowLoop) { return true; } try { if (__instance == null || !Plugin.Cfg.EnableCousinArmSync.Value) { return true; } bool flag = NetConfig.GetMode() == NetMode.Client; if (flag) { try { _damageField?.SetValue(__instance, 0f); } catch { } } object obj2 = _npcField?.GetValue(__instance); object obj3 = ((obj2 == null) ? null : (GetMember(obj2, "AiAgent") ?? GetMember(obj2, "aiAgent"))); if (obj3 == null) { return true; } if (_aiAgentTargetField == null) { _aiAgentTargetField = obj3.GetType().GetField("target", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if (_aiAgentTargetField == null) { return true; } List list = GatherPlayerUnits(); bool flag2 = false; bool flag3 = false; HashSet members = null; try { flag2 = Plugin.Cfg.ExcludeOutOfRoomPlayersFromBossAttacks.Value; if (flag2) { flag3 = ArenaLockdownManager.TryGetActiveArenaInRoom(out members); } } catch { } int num = 0; int num2 = 0; if (list.Count > 0) { object value = _aiAgentTargetField.GetValue(obj3); _inThrowLoop = true; try { foreach (object item in list) { if (item == null) { continue; } if (flag2 && !IsTargetAttackable(item, flag3, members)) { num2++; continue; } try { _aiAgentTargetField.SetValue(obj3, item); } catch { continue; } try { _throwMethod.Invoke(__instance, null); num++; } catch (Exception ex) { Plugin.Log.Warn("[ArmThrow] per-player throw failed: " + ex.GetType().Name + ": " + ex.Message); } } } finally { _inThrowLoop = false; try { _aiAgentTargetField.SetValue(obj3, value); } catch { } } goto IL_0222; } object value2 = _aiAgentTargetField.GetValue(obj3); if (value2 != null) { Object val = (Object)((value2 is Object) ? value2 : null); if (val == null || !(val == (Object)null)) { goto IL_0222; } } return true; IL_0222: int remoteVisual = 0; int remoteSkippedOutOfRoom = 0; if (flag) { object instCapture = __instance; object npcCapture = obj2; bool fl = flag2; bool gt = flag3; HashSet mem = members; NetGameplaySyncBridge.ForEachRemotePlayerPositionWithPeer(delegate(string peerId, Vector3 pos) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) if (fl) { if (NetPlayerLifeManager.IsPeerDownOrDead(peerId)) { remoteSkippedOutOfRoom++; return; } if (gt && (mem == null || !mem.Contains(peerId))) { remoteSkippedOutOfRoom++; return; } } if (ClientVisualThrowAt(instCapture, npcCapture, pos)) { remoteVisual++; } }); } if (Plugin.Cfg.LogBossDynamicSpawn.Value) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup - _lastLogAt > 0.5f) { _lastLogAt = realtimeSinceStartup; Plugin.Log.Info(string.Format("[ArmThrow] {0} AoE throw players={1} thrown={2} skippedOutOfRoom={3} remoteVisual={4} remoteSkippedOutOfRoom={5} gated={6} members=[{7}]", flag ? "client" : "host", list.Count, num, num2, remoteVisual, remoteSkippedOutOfRoom, flag3, (members == null) ? "" : string.Join(",", members))); } } return false; } catch (Exception ex2) { Plugin.Log.Warn("[ArmThrow] ThrowProjectile_Pre failed: " + ex2.GetType().Name + ": " + ex2.Message); return true; } } private static bool ClientVisualThrowAt(object cousinArm, object? npcObj, Vector3 targetPos) { //IL_00aa: 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_00b5: 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_00c0: 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_00d7: 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_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_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_0187: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) try { object? obj = _barrelField?.GetValue(cousinArm); Transform val = (Transform)((obj is Transform) ? obj : null); object? obj2 = _heightCurveField?.GetValue(cousinArm); AnimationCurve val2 = (AnimationCurve)((obj2 is AnimationCurve) ? obj2 : null); object? obj3 = _forceCurveField?.GetValue(cousinArm); AnimationCurve val3 = (AnimationCurve)((obj3 is AnimationCurve) ? obj3 : null); object? obj4 = _visualField?.GetValue(cousinArm); ProjectileCustomVisuals val4 = (ProjectileCustomVisuals)((obj4 is ProjectileCustomVisuals) ? obj4 : null); object? obj5 = _prefabOnHitField?.GetValue(cousinArm); AutoPooledObject spawnOnDestroy = (AutoPooledObject)((obj5 is AutoPooledObject) ? obj5 : null); if ((Object)(object)val == (Object)null || val2 == null || val3 == null) { return false; } Unit val5 = (Unit)((npcObj is Unit) ? npcObj : null); if ((Object)(object)val5 == (Object)null) { return false; } Vector3 val6 = targetPos + Vector3.up * 2f; float num = Vector3.Distance(val.position, targetPos); float num2 = val2.Evaluate(num); Vector3 val7 = val6 + Vector3.up * num2 - val.position; Vector3 normalized = ((Vector3)(ref val7)).normalized; ProjectileRay val8 = default(ProjectileRay); ((ProjectileRay)(ref val8))..ctor(float3.op_Implicit(val.position), (ProjectileTypes)6); val8.gravity = ProjectileRay.GRAVITY; val8.radius = 0.05f; val8.startTime = Time.time; val8.velocity = float3.op_Implicit(val3.Evaluate(num) * normalized); object? member = GetMember(npcObj, "hitmeshCollider"); Collider val9 = (Collider)((member is Collider) ? member : null); val8.ownerInstID = (((Object)(object)val9 != (Object)null) ? ((Object)val9).GetInstanceID() : 0); val8.drawDefaultBullet = false; ProjectileData val10 = new ProjectileData { explicitDamage = 0f, spawnOnDestroy = spawnOnDestroy, damageType = (DamageTypes)7, sourceUnit = val5 }; StaticInstance.Instance.StartProjectile(val8, val10, val4); return true; } catch (Exception ex) { Plugin.Log.Warn("[ArmThrow] client visual throw-at failed: " + ex.GetType().Name + ": " + ex.Message); return false; } } internal static bool IsTargetAttackable(object playerUnit, bool gated, HashSet? members) { try { string peerId; bool num = RemotePlayerTargetProxyManager.TryGetProxyPeer(playerUnit, out peerId); if (!num) { peerId = NetGameplaySyncBridge.LocalPeerId; } if (num) { if (NetPlayerLifeManager.IsPeerDownOrDead(peerId)) { return false; } } else if (NetPlayerLifeManager.IsDownedLocalPlayerUnit(playerUnit)) { return false; } if (gated) { return members?.Contains(peerId) ?? false; } return true; } catch { return true; } } internal static List GatherPlayerUnits() { _unitScratch.Clear(); try { object obj = _gmInstance?.GetValue(null); if (obj == null) { return _unitScratch; } if (!(_gmPlayers?.GetValue(obj) is IEnumerable enumerable)) { return _unitScratch; } foreach (object item in enumerable) { if (item == null) { continue; } if (_playerUnitField == null) { _playerUnitField = item.GetType().GetField("playerUnit", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } object obj2 = _playerUnitField?.GetValue(item); if (obj2 != null) { Object val = (Object)((obj2 is Object) ? obj2 : null); if (val == null || !(val == (Object)null)) { _unitScratch.Add(obj2); } } } } catch { } return _unitScratch; } private static object? GetMember(object obj, string name) { try { Type type = obj.GetType(); PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property.GetValue(obj); } return type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj); } catch { return null; } } public static bool IsCousinArmAiAgent(object? aiAgent) { try { Component val = (Component)((aiAgent is Component) ? aiAgent : null); if (val == null || (Object)(object)val == (Object)null) { return false; } object obj = _cousinArmType; if (obj == null) { obj = FindGameType("PerfectRandom.Sulfur.Gameplay.CousinArm") ?? FindGameType("PerfectRandom.Sulfur.Core.CousinArm") ?? FindGameType("CousinArm"); _cousinArmType = (Type?)obj; } Type type = (Type)obj; if (type == null) { return false; } if ((Object)(object)val.GetComponentInParent(type) != (Object)null) { return true; } return (Object)(object)val.GetComponentInChildren(type, true) != (Object)null; } catch { return false; } } private static Type? FindGameType(string fullName) { try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { Type type = assemblies[i].GetType(fullName); if (type != null) { return type; } } } catch { } return null; } } internal static class DeathSpawnSyncPatches { private static FieldInfo? _spawnMinionsField; private static FieldInfo? _amountToSpawnField; private static PropertyInfo? _unitUnitSOProp; private static FieldInfo? _unitUnitSOField; public static void Apply(Harmony harmony) { //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Expected O, but got Unknown //IL_00b9: 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_00e1: Expected O, but got Unknown //IL_00e1: Expected O, but got Unknown if (!Plugin.Cfg.EnableRuntimeSpawnSync.Value || !Plugin.Cfg.EnableDeathSpawnSync.Value) { Plugin.Log.Info("[DeathSpawn] death-spawn sync disabled by config."); return; } Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Stats.MutationDefinition"); if (type == null) { Plugin.Log.Info("[DeathSpawn] MutationDefinition type not found."); return; } MethodInfo methodInfo = AccessTools.GetDeclaredMethods(type).FirstOrDefault((MethodInfo m) => m.Name == "OnDeathSpawnUnitsFunc"); if (methodInfo == null) { Plugin.Log.Info("[DeathSpawn] OnDeathSpawnUnitsFunc not found."); return; } try { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(DeathSpawnSyncPatches).GetMethod("OnDeathSpawnUnits_Pre", BindingFlags.Static | BindingFlags.NonPublic)), new HarmonyMethod(typeof(DeathSpawnSyncPatches).GetMethod("OnDeathSpawnUnits_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info("[DeathSpawn] Patched MutationDefinition.OnDeathSpawnUnitsFunc (death-spawn sync)."); } catch (Exception ex) { Plugin.Log.Error("[DeathSpawn] patch OnDeathSpawnUnitsFunc failed: " + ex.Message); } if (!Plugin.Cfg.EnableMinionSpawnSync.Value) { return; } MethodInfo methodInfo2 = AccessTools.GetDeclaredMethods(type).FirstOrDefault((MethodInfo m) => m.Name == "SpawnMinions"); if (methodInfo2 == null) { Plugin.Log.Info("[DeathSpawn] SpawnMinions not found."); return; } _spawnMinionsField = AccessTools.Field(type, "spawnMinionsOnDeath"); try { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(DeathSpawnSyncPatches).GetMethod("SpawnMinions_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info("[DeathSpawn] Patched MutationDefinition.SpawnMinions (minion-spawn sync)."); } catch (Exception ex2) { Plugin.Log.Error("[DeathSpawn] patch SpawnMinions failed: " + ex2.Message); } } private static bool SpawnMinions_Pre(object __instance, object owner) { try { if (RuntimeSpawnManager.ClientShouldSuppressMinions()) { return false; } object obj = ReadUnitSO(owner); int num = ReadAmountToSpawn(__instance); if (obj != null && num > 0) { RuntimeSpawnManager.BeginHostMinionContext(obj, num); } } catch { } return true; } private static object? ReadUnitSO(object? unit) { if (unit == null) { return null; } try { if (_unitUnitSOProp == null && _unitUnitSOField == null) { _unitUnitSOProp = AccessTools.Property(unit.GetType(), "unitSO"); if (_unitUnitSOProp == null) { _unitUnitSOField = AccessTools.Field(unit.GetType(), "unitSO"); } } return (_unitUnitSOProp != null) ? _unitUnitSOProp.GetValue(unit) : _unitUnitSOField?.GetValue(unit); } catch { return null; } } private static int ReadAmountToSpawn(object mutationDef) { try { object obj = _spawnMinionsField?.GetValue(mutationDef); if (obj == null) { return 0; } if (_amountToSpawnField == null) { _amountToSpawnField = AccessTools.Field(obj.GetType(), "amountToSpawn"); } return (_amountToSpawnField?.GetValue(obj) is int num) ? num : 0; } catch { return 0; } } private static bool OnDeathSpawnUnits_Pre() { try { if (RuntimeSpawnManager.ClientShouldSuppressDeathSpawn()) { return false; } RuntimeSpawnManager.BeginHostDeathSpawn(); } catch { } return true; } private static void OnDeathSpawnUnits_Post() { try { RuntimeSpawnManager.EndHostDeathSpawn(); } catch { } } } internal static class LevelGenTracePatches { private struct BlockerState { public string Field; public GameObject? Obj; public string Name; public bool ExistedActive; public bool ExistedCollider; public bool ExistedRenderer; } private struct BakedCounts { public int Npc; public int Event; public int Nav; public int DestroyedIdx; public string Room; } private static Type? _connectorType; private static bool _connectorMembersDumped; private static readonly HashSet _stateMachineFieldsDumped = new HashSet(); private static readonly HashSet _extraRoomInstancesLogged = new HashSet(); private static readonly HashSet _memberDumpDone = new HashSet(); private static readonly Dictionary _firstN = new Dictionary(); private const int FirstNLimit = 40; private static readonly string[] BlockerObjectFields = new string[6] { "disableObjectIfPlugged", "enableObjectIfPlugged", "disableObjectIfActive", "enableObjectIfActive", "connectorNavBlockerOverride", "connectorDoorBlockerOverride" }; private static bool _makerContextFieldsDumped; private static bool AllowFirstN(string category) { _firstN.TryGetValue(category, out var value); if (value >= 40) { return false; } _firstN[category] = value + 1; return true; } public static void Apply(Harmony harmony) { if (!Plugin.Cfg.EnableLevelGenTrace.Value) { Plugin.Log.Info("[LevelGenTrace] Disabled by config."); return; } PatchLevelStepTrace(harmony); PatchFinalizeConnection(harmony); PatchConnectorFinalizeSpawn(harmony); PatchNodeCoroutine(harmony, "AddExtraRoomsNode", "ExtraRoomMoveNext_Post"); PatchNodeCoroutine(harmony, "SpawnEventsNode", "GenericNodeMoveNext_Post"); PatchNodeCoroutine(harmony, "SetupLootNode", "GenericNodeMoveNext_Post"); PatchNodeCoroutine(harmony, "FinalizeAndMutateUnitsNode", "GenericNodeMoveNext_Post"); PatchNodeCoroutine(harmony, "SpawnEnemiesNode", "GenericNodeMoveNext_Post"); PatchProcessMethods(harmony, "RandomChildSubset", "RandomComponent_Process_Pre"); PatchProcessMethods(harmony, "RandomChanceSelect", "RandomComponent_Process_Pre"); PatchProcessMethods(harmony, "RandomlyRemove", "RandomComponent_Process_Pre"); PatchRoomFilterBakedLists(harmony); PatchNamedMethods(harmony, "SpawnEventsNode", "PickEventFromList", "PickEventFromList_Pre", "PickEventFromList_Post"); PatchNamedMethods(harmony, "FinalizeAndMutateUnitsNode", "GetMutation", null, "GetMutation_Post"); PatchToggleMutation(harmony); Plugin.Log.Info("[LevelGenTrace] LevelGeneration trace hooks registered."); } private static Type? FindTypeByShortName(string shortName) { string[] array = new string[3] { "LevelGeneration." + shortName, "PerfectRandom.Sulfur.Core.LevelGeneration." + shortName, shortName }; for (int i = 0; i < array.Length; i++) { Type type = AccessTools.TypeByName(array[i]); if (type != null) { return type; } } try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type[] array2; try { array2 = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { array2 = ex.Types.Where((Type t) => t != null).ToArray(); } Type[] array3 = array2; foreach (Type type2 in array3) { if (type2 != null && type2.Name == shortName) { return type2; } } } } catch { } try { List list = new List(); Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly2 in assemblies) { Type[] array4; try { array4 = assembly2.GetTypes(); } catch (ReflectionTypeLoadException ex2) { array4 = ex2.Types.Where((Type t) => t != null).ToArray(); } Type[] array3 = array4; foreach (Type type3 in array3) { if (type3 != null && type3.Name.IndexOf(shortName, StringComparison.OrdinalIgnoreCase) >= 0) { list.Add(type3.FullName ?? type3.Name); } } if (list.Count >= 10) { break; } } Plugin.Log.Info("[LevelGenTrace] type NOT FOUND: " + shortName + " — partial candidates: " + ((list.Count == 0) ? "none" : string.Join(", ", list.Take(10)))); } catch { Plugin.Log.Info("[LevelGenTrace] type NOT FOUND: " + shortName); } return null; } private static void PatchFinalizeConnection(Harmony harmony) { //IL_00aa: 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_00d2: Expected O, but got Unknown //IL_00d2: Expected O, but got Unknown Type type = FindTypeByShortName("LevelGenGraphUtilities"); if (type == null) { return; } MethodInfo methodInfo = AccessTools.GetDeclaredMethods(type).FirstOrDefault((MethodInfo m) => m.Name == "FinalizeConnection"); if (methodInfo == null) { Plugin.Log.Info("[LevelGenTrace] FinalizeConnection method not found"); return; } ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length >= 1) { _connectorType = parameters[0].ParameterType; Plugin.Log.Info("[LevelGenTrace] Connector type derived from FinalizeConnection param = " + _connectorType.FullName); } try { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(LevelGenTracePatches).GetMethod("FinalizeConnection_Pre", BindingFlags.Static | BindingFlags.NonPublic)), new HarmonyMethod(typeof(LevelGenTracePatches).GetMethod("FinalizeConnection_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info("[LevelGenTrace] patched " + type.Name + ".FinalizeConnection(" + string.Join(",", parameters.Select((ParameterInfo p) => p.ParameterType.Name)) + ")"); } catch (Exception ex) { Plugin.Log.Error("[LevelGenTrace] FinalizeConnection patch failed: " + ex.Message); } } private static void FinalizeConnection_Pre(object[] __args, out int __state) { __state = 0; try { if (__args != null && __args.Length >= 2) { __state = ChildCount(__args[0]) + ChildCount(__args[1]); } } catch { __state = 0; } } private static void FinalizeConnection_Post(object[] __args, int __state) { try { NetGameplayProbeManager.NoteLevelGenTrace("FinalizeConnection"); if (Plugin.Cfg.LogLevelGenTrace.Value && __args != null && __args.Length >= 2) { int num = ChildCount(__args[0]) + ChildCount(__args[1]); int num2 = Mathf.Max(0, __state - num); Plugin.Log.Info($"[LevelGenTrace] FinalizeConnection out={ConnectorTag(__args[0])} in={ConnectorTag(__args[1])} destroyedChildren={num2}"); } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { Plugin.Log.Debug("[LevelGenTrace] FinalizeConnection_Post: " + ex.Message); } } } private static void PatchConnectorFinalizeSpawn(Harmony harmony) { //IL_009a: 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_00c2: Expected O, but got Unknown //IL_00c2: Expected O, but got Unknown Type type = _connectorType ?? FindTypeByShortName("Connector"); if (type == null) { Plugin.Log.Info("[LevelGenTrace] Connector type unresolved — cannot patch FinalizeSpawn"); return; } MethodInfo methodInfo = AccessTools.GetDeclaredMethods(type).FirstOrDefault((MethodInfo m) => m.Name == "FinalizeSpawn" && m.GetParameters().Length == 0); if (methodInfo == null) { Plugin.Log.Info("[LevelGenTrace] " + type.FullName + ".FinalizeSpawn() not found"); return; } try { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(LevelGenTracePatches).GetMethod("ConnectorFinalizeSpawn_Pre", BindingFlags.Static | BindingFlags.NonPublic)), new HarmonyMethod(typeof(LevelGenTracePatches).GetMethod("ConnectorFinalizeSpawn_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info("[LevelGenTrace] patched " + type.FullName + ".FinalizeSpawn()"); } catch (Exception ex) { Plugin.Log.Error("[LevelGenTrace] Connector.FinalizeSpawn patch failed: " + ex.Message); } } private static void ConnectorFinalizeSpawn_Pre(object __instance, out BlockerState[] __state) { __state = Array.Empty(); try { if (!_connectorMembersDumped) { _connectorMembersDumped = true; DumpConnectorFields(__instance); } List list = new List(BlockerObjectFields.Length); string[] blockerObjectFields = BlockerObjectFields; foreach (string text in blockerObjectFields) { GameObject val = ReadGameObject(__instance, text); list.Add(new BlockerState { Field = text, Obj = val, Name = (((Object)(object)val != (Object)null) ? ((Object)val).name : ""), ExistedActive = ((Object)(object)val != (Object)null && val.activeSelf), ExistedCollider = ((Object)(object)val != (Object)null && AnyColliderEnabled(val)), ExistedRenderer = ((Object)(object)val != (Object)null && AnyRendererEnabled(val)) }); } __state = list.ToArray(); if (!Plugin.Cfg.LogLevelGenTrace.Value) { return; } bool valueOrDefault = ReadBool(__instance, "isConnected") == true; Plugin.Log.Info(string.Format("[ConnectorTrace] FinalizeSpawn PRE conn={0} isConnected={1} connectionAllowed={2} hasSpawnedDoor={3} doNotCreateNavBlocker={4} doNotCreateDoorBlocker={5}", ConnectorTag(__instance), valueOrDefault, ReadBool(__instance, "connectionAllowed"), ReadBool(__instance, "hasSpawnedDoor"), ReadBool(__instance, "doNotCreateNavBlockerHere"), ReadBool(__instance, "doNotCreateDoorBlockerHere"))); BlockerState[] array = __state; for (int i = 0; i < array.Length; i++) { BlockerState blockerState = array[i]; if (!((Object)(object)blockerState.Obj == (Object)null)) { Plugin.Log.Info($"[DoorBlockerTrace] PRE field={blockerState.Field} obj={blockerState.Name} active={blockerState.ExistedActive} collider={blockerState.ExistedCollider} renderer={blockerState.ExistedRenderer}"); } } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { Plugin.Log.Debug("[LevelGenTrace] ConnectorFinalizeSpawn_Pre: " + ex.Message); } } } private static void ConnectorFinalizeSpawn_Post(object __instance, BlockerState[] __state) { try { NetGameplayProbeManager.NoteLevelGenTrace("ConnectorFinalizeSpawn"); int num = 0; bool valueOrDefault = ReadBool(__instance, "isConnected") == true; for (int i = 0; i < __state.Length; i++) { BlockerState blockerState = __state[i]; GameObject val = ReadGameObject(__instance, blockerState.Field); bool num2 = (Object)(object)blockerState.Obj != (Object)null; bool flag = (Object)(object)val != (Object)null; if (!num2 && !flag) { continue; } num++; if (Plugin.Cfg.LogLevelGenTrace.Value) { if (!flag) { Plugin.Log.Info("[DoorBlockerTrace] POST field=" + blockerState.Field + " obj=" + blockerState.Name + " -> "); } else { Plugin.Log.Info($"[DoorBlockerTrace] POST field={blockerState.Field} obj={((Object)val).name} active={val.activeSelf} collider={AnyColliderEnabled(val)} renderer={AnyRendererEnabled(val)}"); } } } if (num > 0) { NetGameplayProbeManager.NoteLevelGenTrace("DoorBlocker", num); } if (Plugin.Cfg.LogLevelGenTrace.Value) { Plugin.Log.Info($"[ConnectorTrace] FinalizeSpawn POST conn={ConnectorTag(__instance)} isConnected={valueOrDefault} blockersRecorded={num}"); } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { Plugin.Log.Debug("[LevelGenTrace] ConnectorFinalizeSpawn_Post: " + ex.Message); } } } private static void DumpConnectorFields(object connector) { try { Type type = connector.GetType(); StringBuilder stringBuilder = new StringBuilder(); foreach (FieldInfo item in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Take(80)) { stringBuilder.Append(item.Name).Append('(').Append(item.FieldType.Name) .Append(") "); } Plugin.Log.Info($"[LevelGenTrace] {type.FullName} fields: {stringBuilder}"); } catch (Exception ex) { Plugin.Log.Warn("[LevelGenTrace] DumpConnectorFields failed: " + ex.Message); } } private static void PatchLevelStepTrace(Harmony harmony) { //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Expected O, but got Unknown Type type = FindTypeByShortName("MakerGraphContext"); if (type == null) { Plugin.Log.Info("[LevelStepTrace] MakerGraphContext type not found"); return; } MethodInfo methodInfo = AccessTools.GetDeclaredMethods(type).FirstOrDefault((MethodInfo m) => m.Name == "ResetContext"); if (methodInfo == null) { IEnumerable values = (from m in AccessTools.GetDeclaredMethods(type) where !m.IsSpecialName select m.Name + "(" + string.Join(",", from p in m.GetParameters() select p.ParameterType.Name) + ")").Take(40); Plugin.Log.Info("[LevelStepTrace] ResetContext not found on " + type.FullName + "; methods: " + string.Join(" | ", values)); return; } try { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(LevelGenTracePatches).GetMethod("ResetContext_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info("[LevelStepTrace] patched " + type.FullName + ".ResetContext(" + string.Join(",", from p in methodInfo.GetParameters() select p.ParameterType.Name) + ")"); } catch (Exception ex) { Plugin.Log.Error("[LevelStepTrace] ResetContext patch failed: " + ex.Message); } } private static void ResetContext_Post(object __instance, object[] __args) { try { NetGameplayProbeManager.NoteLevelGenTrace("LevelStep"); if (!_makerContextFieldsDumped) { _makerContextFieldsDumped = true; DumpFields(__instance, "MakerGraphContext"); } object obj = ((__args != null && __args.Length != 0) ? __args[0] : null); int num = ((__args != null && __args.Length > 1 && __args[1] is int num2) ? num2 : (-1)); string text = MemberNumber(__instance, "Seed"); object obj2 = ReadMember(__instance, "Set"); string text2 = ((obj2 != null) ? MemberNumber(obj2, "TotalExecutingNodes") : "?"); string text3 = MemberNumber(__instance, "ExecutingNodeIndex"); Object val = (Object)((obj2 is Object) ? obj2 : null); string text4 = ((val != null && val != (Object)null) ? val.name : ((obj2 != null) ? NodeName(obj2) : "?")); if (long.TryParse(text, out var result)) { NetGenerationInputCapture.FinalizeFromMakerContext(text4, result); } if (Plugin.Cfg.LogLevelGenTrace.Value) { string text5 = NodeName(obj); string text6 = obj?.GetType().FullName ?? "?"; string text7 = "?"; if (long.TryParse(text, out var result2) && num >= 0) { text7 = (result2 + num).ToString(); } string text8 = "scene=" + NetGameplayProbeManager.GetLocalRunKey(); Plugin.Log.Info($"[LevelStepTrace] START idx={num}/{text2} node={text5} type={text6} graph={text4} {text8} graphSeed={text} nodeSeed={text7} execIdx={text3}"); } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { Plugin.Log.Debug("[LevelStepTrace] ResetContext_Post: " + ex.Message); } } } private static string NodeName(object? node) { if (node == null) { return ""; } try { if ((ReadField(node, "name") ?? ReadField(node, "NodeName") ?? ReadField(node, "nodeName")) is string text && !string.IsNullOrEmpty(text)) { return text; } } catch { } return node.GetType().Name; } private static void PatchNodeCoroutine(Harmony harmony, string nodeShortName, string postfixName) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Expected O, but got Unknown Type type = FindTypeByShortName(nodeShortName); if (type == null) { return; } Type type2 = ResolveCoroutineStateMachine(type, "Execute"); if (type2 == null) { MethodInfo method = type.GetMethod("Execute", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method != null) { try { harmony.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(typeof(LevelGenTracePatches).GetMethod(postfixName, BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info("[LevelGenTrace] patched " + type.Name + ".Execute (non-iterator)"); return; } catch (Exception ex) { Plugin.Log.Error("[LevelGenTrace] " + nodeShortName + ".Execute patch failed: " + ex.Message); return; } } Plugin.Log.Info("[LevelGenTrace] " + nodeShortName + ": no Execute state machine and no Execute method"); return; } MethodInfo method2 = type2.GetMethod("MoveNext", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method2 == null) { Plugin.Log.Info("[LevelGenTrace] " + type2.FullName + ".MoveNext not found"); return; } try { harmony.Patch((MethodBase)method2, (HarmonyMethod)null, new HarmonyMethod(typeof(LevelGenTracePatches).GetMethod(postfixName, BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info("[LevelGenTrace] patched " + type2.FullName + ".MoveNext (" + nodeShortName + " coroutine)"); } catch (Exception ex2) { Plugin.Log.Error("[LevelGenTrace] " + nodeShortName + " MoveNext patch failed: " + ex2.Message); } } private static void ExtraRoomMoveNext_Post(object __instance) { try { NetGameplayProbeManager.NoteLevelGenTrace("ExtraRoom"); string item = __instance.GetType().FullName ?? "extraroom"; if (_stateMachineFieldsDumped.Add(item)) { DumpFields(__instance, "AddExtraRooms.MoveNext"); } if (!Plugin.Cfg.LogLevelGenTrace.Value) { return; } object obj = ReadFieldLike(__instance, "roomRef"); string text = ReadStrLike(__instance, "uniqueName"); if (obj == null && string.IsNullOrEmpty(text)) { return; } int hashCode = RuntimeHelpers.GetHashCode(__instance); if (_extraRoomInstancesLogged.Add(hashCode)) { string text2 = ""; string text3 = ""; string text4 = ""; if (obj != null) { text2 = ReadStrLike(obj, "prefabAsset") ?? ""; text3 = InvokeStringMethod(obj, "get_AssetGUID") ?? ReadStrLike(obj, "AssetGUID") ?? ""; text4 = ReadAnyNumber(obj, "chance"); } Plugin.Log.Info(string.Format("[ExtraRoomTrace] selected uniqueName={0} prefab={1} assetGUID={2} chance={3} sideRoomsToSpawn={4} spawnedSideRooms={5} outConnIdx={6} inConnIdx={7} roomIndex={8} flipped={9} connectors={10} uniquePerLevel={11}", text, text2, text3, text4, ReadAnyNumber(__instance, "sideRoomsToSpawn"), ReadCountLike(__instance, "spawnedSideRooms"), ReadAnyNumber(__instance, "outConnIndex"), ReadAnyNumber(__instance, "inConnIndex"), ReadAnyNumber(__instance, "roomIndex"), ReadBoolLike(__instance, "flipped"), ReadCountLike(__instance, "connectors"), ReadBoolLike(__instance, "uniquePerLevel"))); } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { Plugin.Log.Debug("[LevelGenTrace] ExtraRoomMoveNext_Post: " + ex.Message); } } } private static void GenericNodeMoveNext_Post(object __instance) { try { string text = __instance.GetType().FullName ?? "node"; NetGameplayProbeManager.NoteLevelGenTrace("NodeCoroutine"); if (_stateMachineFieldsDumped.Add(text)) { DumpFields(__instance, "NodeCoroutine " + ShortNodeLabel(text)); } else if (Plugin.Cfg.LogLevelGenTrace.Value) { int hashCode = RuntimeHelpers.GetHashCode(__instance); if (_extraRoomInstancesLogged.Add(hashCode)) { string text2 = ShortNodeLabel(text); string text3 = ReadStrLike(__instance, "selected") ?? ReadStrLike(__instance, "eventEntry") ?? ReadStrLike(__instance, "mutation") ?? ReadStrLike(__instance, "lootTable") ?? ""; string text4 = ReadStrLike(__instance, "room") ?? ""; Plugin.Log.Info(string.Format("[NodeTrace] {0} selected={1} room={2} vendor={3} usedRun={4} usedEnv={5} usedLevel={6} containers={7}", text2, text3, text4, ReadBoolLike(__instance, "vendor"), ReadCountLike(__instance, "usedUniqueEventThisRun"), ReadCountLike(__instance, "usedUniqueEventThisEnvironment"), ReadCountLike(__instance, "usedEventsThisLevel"), ReadCountLike(__instance, "containers"))); } } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { Plugin.Log.Debug("[LevelGenTrace] GenericNodeMoveNext_Post: " + ex.Message); } } } private static string ShortNodeLabel(string typeFullName) { int num = typeFullName.IndexOf('+'); string text = ((num >= 0) ? typeFullName.Substring(0, num) : typeFullName); int num2 = text.LastIndexOf('.'); if (num2 < 0) { return text; } return text.Substring(num2 + 1); } private static Type? ResolveCoroutineStateMachine(Type nodeType, string methodName) { try { MethodInfo method = nodeType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method != null) { Attribute attribute = method.GetCustomAttributes().FirstOrDefault((Attribute a) => a.GetType().Name.Contains("StateMachine")); if (attribute != null && attribute.GetType().GetProperty("StateMachineType")?.GetValue(attribute) is Type result) { return result; } } return nodeType.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((Type nt) => typeof(IEnumerator).IsAssignableFrom(nt) && nt.Name.Contains(methodName)); } catch { return null; } } private static void DumpFields(object obj, string label) { try { Type type = obj.GetType(); StringBuilder stringBuilder = new StringBuilder(); foreach (FieldInfo item in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Take(70)) { stringBuilder.Append(item.Name).Append('(').Append(item.FieldType.Name) .Append(") "); } Plugin.Log.Info($"[LevelGenTrace] {label} {type.Name} fields: {stringBuilder}"); } catch (Exception ex) { Plugin.Log.Warn("[LevelGenTrace] DumpFields failed: " + ex.Message); } } private static string? InvokeStringMethod(object obj, string method) { try { MethodInfo method2 = obj.GetType().GetMethod(method, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method2 != null) { return method2.Invoke(obj, null)?.ToString(); } } catch { } return null; } private static void PatchProcessMethods(Harmony harmony, string typeShortName, string prefixName) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Expected O, but got Unknown Type type = FindTypeByShortName(typeShortName); if (type == null) { return; } List list = (from m in AccessTools.GetDeclaredMethods(type) where m.Name == "Process" select m).ToList(); if (list.Count == 0) { Plugin.Log.Info("[LevelGenTrace] " + type.FullName + ".Process not found"); return; } foreach (MethodInfo item in list) { try { harmony.Patch((MethodBase)item, new HarmonyMethod(typeof(LevelGenTracePatches).GetMethod(prefixName, BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info("[LevelGenTrace] patched " + type.Name + ".Process(" + string.Join(",", from p in item.GetParameters() select p.ParameterType.Name) + ")"); } catch (Exception ex) { Plugin.Log.Error("[LevelGenTrace] " + typeShortName + ".Process patch failed: " + ex.Message); } } } private static void PatchNamedMethods(Harmony harmony, string typeShortName, string methodName, string? prefixName, string? postfixName) { //IL_00b3: 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) Type type = FindTypeByShortName(typeShortName); if (type == null) { return; } List list = (from m in AccessTools.GetDeclaredMethods(type) where m.Name == methodName select m).ToList(); if (list.Count == 0) { Plugin.Log.Info("[LevelGenTrace] " + type.FullName + "." + methodName + " not found"); return; } foreach (MethodInfo item in list) { try { harmony.Patch((MethodBase)item, (prefixName == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(LevelGenTracePatches).GetMethod(prefixName, BindingFlags.Static | BindingFlags.NonPublic)), (postfixName == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(LevelGenTracePatches).GetMethod(postfixName, BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info("[LevelGenTrace] patched " + type.Name + "." + methodName + "(" + string.Join(",", from p in item.GetParameters() select p.ParameterType.Name) + ")"); } catch (Exception ex) { Plugin.Log.Error("[LevelGenTrace] " + typeShortName + "." + methodName + " patch failed: " + ex.Message); } } } private static void PatchRoomFilterBakedLists(Harmony harmony) { //IL_0082: 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_00aa: Expected O, but got Unknown //IL_00aa: Expected O, but got Unknown Type type = FindTypeByShortName("Room"); if (type == null) { return; } MethodInfo methodInfo = AccessTools.GetDeclaredMethods(type).FirstOrDefault((MethodInfo m) => m.Name == "FilterAndFinalizeBakedLists"); if (methodInfo == null) { Plugin.Log.Info("[LevelGenTrace] " + type.FullName + ".FilterAndFinalizeBakedLists not found"); return; } try { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(LevelGenTracePatches).GetMethod("RoomFilterBaked_Pre", BindingFlags.Static | BindingFlags.NonPublic)), new HarmonyMethod(typeof(LevelGenTracePatches).GetMethod("RoomFilterBaked_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info("[LevelGenTrace] patched " + type.Name + ".FilterAndFinalizeBakedLists"); } catch (Exception ex) { Plugin.Log.Error("[LevelGenTrace] FilterAndFinalizeBakedLists patch failed: " + ex.Message); } } private static void PatchToggleMutation(Harmony harmony) { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected O, but got Unknown Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Units.Unit"); if (type == null) { Plugin.Log.Info("[LevelGenTrace] Unit type not found for ToggleMutation"); return; } List list = (from m in AccessTools.GetDeclaredMethods(type) where m.Name == "ToggleMutation" select m).ToList(); if (list.Count == 0) { Plugin.Log.Info("[LevelGenTrace] " + type.FullName + ".ToggleMutation not found"); return; } foreach (MethodInfo item in list) { try { harmony.Patch((MethodBase)item, new HarmonyMethod(typeof(LevelGenTracePatches).GetMethod("ToggleMutation_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info("[LevelGenTrace] patched Unit.ToggleMutation(" + string.Join(",", from p in item.GetParameters() select p.ParameterType.Name) + ")"); } catch (Exception ex) { Plugin.Log.Error("[LevelGenTrace] ToggleMutation patch failed: " + ex.Message); } } } private static void RandomComponent_Process_Pre(object __instance) { try { NetGameplayProbeManager.NoteLevelGenTrace("RoomRandomize"); string name = __instance.GetType().Name; if (_memberDumpDone.Add("proc:" + name)) { DumpFields(__instance, "RandomComponent." + name); } if (Plugin.Cfg.LogLevelGenTrace.Value && AllowFirstN("proc:" + name)) { string localRunKey = NetGameplayProbeManager.GetLocalRunKey(); int num = ChildCount(__instance); Plugin.Log.Info($"[RoomRandomizeTrace] {name} scene={localRunKey} path={GameObjectPath(__instance)} childCount={num} " + "minKeep=" + ReadAnyNumber(__instance, "minChildrenToKeep") + " maxKeep=" + ReadAnyNumber(__instance, "maxChildrenToKeep") + " chanceToRemove=" + ReadAnyNumber(__instance, "chanceToRemove") + " chanceToBeRemove=" + ReadAnyNumber(__instance, "chanceToBeRemove") + " rootIndex=" + ReadAnyNumber(__instance, "RandomizedLevelRootIndex")); } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { Plugin.Log.Debug("[LevelGenTrace] RandomComponent_Process_Pre: " + ex.Message); } } } private static void RoomFilterBaked_Pre(object __instance, object[] __args, out BakedCounts __state) { __state = default(BakedCounts); try { Component val = (Component)((__instance is Component) ? __instance : null); __state.Room = ((val != null && (Object)(object)val != (Object)null && (Object)(object)val.gameObject != (Object)null) ? ((Object)val.gameObject).name : "?"); __state.Npc = ReadCollectionCount(__instance, "bakedNPCSpawns"); __state.Event = ReadCollectionCount(__instance, "bakedEventSpawners"); __state.Nav = ReadCollectionCount(__instance, "bakedNavMeshLinks"); __state.DestroyedIdx = ((__args != null && __args.Length != 0 && __args[0] is ICollection collection) ? collection.Count : (-1)); } catch { } } private static void RoomFilterBaked_Post(object __instance, BakedCounts __state) { try { NetGameplayProbeManager.NoteLevelGenTrace("FilterBaked"); if (Plugin.Cfg.LogLevelGenTrace.Value && AllowFirstN("filterBaked")) { int num = ReadCollectionCount(__instance, "bakedNPCSpawns"); int num2 = ReadCollectionCount(__instance, "bakedEventSpawners"); int num3 = ReadCollectionCount(__instance, "bakedNavMeshLinks"); Plugin.Log.Info($"[FilterBakedTrace] scene={NetGameplayProbeManager.GetLocalRunKey()} room={__state.Room} destroyedIndexes={__state.DestroyedIdx} " + $"npcSpawns={__state.Npc}->{num} eventSpawners={__state.Event}->{num2} navLinks={__state.Nav}->{num3}"); } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { Plugin.Log.Debug("[LevelGenTrace] RoomFilterBaked_Post: " + ex.Message); } } } private static void PickEventFromList_Pre(object __instance, object[] __args) { try { if (_memberDumpDone.Add("pickEvent")) { DumpFields(__instance, "SpawnEventsNode.PickEventFromList(instance)"); } if (Plugin.Cfg.LogLevelGenTrace.Value && AllowFirstN("pickEvent")) { string text = ((__args == null) ? "" : string.Join(",", __args.Select((object a) => DescribeArg(a)))); Plugin.Log.Info("[EventPickTrace] PRE scene=" + NetGameplayProbeManager.GetLocalRunKey() + " args=[" + text + "]"); } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { Plugin.Log.Debug("[LevelGenTrace] PickEventFromList_Pre: " + ex.Message); } } } private static void PickEventFromList_Post(object __result) { try { NetGameplayProbeManager.NoteLevelGenTrace("EventPick"); if (Plugin.Cfg.LogLevelGenTrace.Value && AllowFirstN("pickEventResult")) { Plugin.Log.Info("[EventPickTrace] selected=" + DescribeArg(__result)); } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { Plugin.Log.Debug("[LevelGenTrace] PickEventFromList_Post: " + ex.Message); } } } private static void GetMutation_Post(object __instance, object[] __args, object __result) { try { NetGameplayProbeManager.NoteLevelGenTrace("Mutation"); if (_memberDumpDone.Add("getMutation") && __args != null && __args.Length != 0 && __args[0] != null) { DumpFields(__args[0], "GetMutation.arg0"); } if (Plugin.Cfg.LogLevelGenTrace.Value && AllowFirstN("getMutation")) { string text = ((__args != null && __args.Length != 0) ? DescribeArg(__args[0]) : "?"); Plugin.Log.Info("[MutationTrace] GetMutation scene=" + NetGameplayProbeManager.GetLocalRunKey() + " unit=" + text + " selected=" + DescribeArg(__result)); } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { Plugin.Log.Debug("[LevelGenTrace] GetMutation_Post: " + ex.Message); } } } private static void ToggleMutation_Pre(object __instance, object[] __args) { try { NetGameplayProbeManager.NoteLevelGenTrace("ToggleMutation"); if (Plugin.Cfg.LogLevelGenTrace.Value && AllowFirstN("toggleMutation")) { string text = ((__args != null && __args.Length != 0) ? DescribeArg(__args[0]) : "?"); string text2 = ((__args == null || __args.Length <= 1) ? "?" : (__args[1]?.ToString() ?? "?")); Component val = (Component)((__instance is Component) ? __instance : null); string text3 = ((val != null && (Object)(object)val != (Object)null && (Object)(object)val.gameObject != (Object)null) ? ((Object)val.gameObject).name : __instance.GetType().Name); Plugin.Log.Info("[MutationTrace] ToggleMutation scene=" + NetGameplayProbeManager.GetLocalRunKey() + " unit=" + text3 + " mutation=" + text + " on=" + text2); } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { Plugin.Log.Debug("[LevelGenTrace] ToggleMutation_Pre: " + ex.Message); } } } private static string DescribeArg(object? a) { if (a == null) { return ""; } if (a is string result) { return result; } Object val = (Object)((a is Object) ? a : null); if (val != null) { if (!(val != (Object)null)) { return ""; } return val.name; } if (a is ICollection collection) { return $"{a.GetType().Name}(count={collection.Count})"; } return a.GetType().Name; } private static string GameObjectPath(object obj) { try { Component val = (Component)((obj is Component) ? obj : null); object obj2; if (val == null || !((Object)(object)val != (Object)null)) { GameObject val2 = (GameObject)((obj is GameObject) ? obj : null); obj2 = ((val2 != null && (Object)(object)val2 != (Object)null) ? val2.transform : null); } else { obj2 = val.transform; } Transform val3 = (Transform)obj2; if ((Object)(object)val3 == (Object)null) { return ""; } List list = new List(); Transform val4 = val3; while ((Object)(object)val4 != (Object)null && list.Count < 8) { list.Add(((Object)val4).name); val4 = val4.parent; } list.Reverse(); return string.Join("/", list); } catch { return ""; } } private static int ReadCollectionCount(object obj, string fieldToken) { if (ReadFieldLike(obj, fieldToken) is ICollection collection) { return collection.Count; } return -1; } private static int ChildCount(object connector) { try { Component val = (Component)((connector is Component) ? connector : null); if (val != null && (Object)(object)val != (Object)null) { return val.transform.childCount; } } catch { } return 0; } private static string ConnectorTag(object connector) { try { Component val = (Component)((connector is Component) ? connector : null); if (val == null || (Object)(object)val == (Object)null) { return ""; } string text = (((Object)(object)val.gameObject != (Object)null) ? ((Object)val.gameObject).name : "?"); string text2 = ""; object obj = ReadField(connector, "room"); Component val2 = (Component)((obj is Component) ? obj : null); if (val2 != null && (Object)(object)val2 != (Object)null) { text2 = (((Object)(object)val2.gameObject != (Object)null) ? ((Object)val2.gameObject).name : ""); } else { GameObject val3 = (GameObject)((obj is GameObject) ? obj : null); if (val3 != null && (Object)(object)val3 != (Object)null) { text2 = ((Object)val3).name; } } return string.IsNullOrEmpty(text2) ? text : (text2 + "." + text); } catch { return ""; } } private static object? ReadField(object obj, string name) { try { Type type = obj.GetType(); while (type != null) { FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(obj); } type = type.BaseType; } } catch { } return null; } private static object? ReadMember(object obj, string name) { if (obj == null) { return null; } try { Type type = obj.GetType(); while (type != null) { PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanRead && property.GetIndexParameters().Length == 0) { return property.GetValue(obj, null); } type = type.BaseType; } } catch { } return ReadField(obj, name); } private static string MemberNumber(object obj, string name) { return ReadMember(obj, name)?.ToString() ?? "?"; } private static object? ReadFieldLike(object obj, string token) { try { FieldInfo[] fields = obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.Name.IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0) { return fieldInfo.GetValue(obj); } } } catch { } return null; } private static bool? ReadBool(object obj, string name) { object obj2 = ReadField(obj, name); if (!(obj2 is bool)) { return null; } return (bool)obj2; } private static bool? ReadBoolLike(object obj, string token) { object obj2 = ReadFieldLike(obj, token); if (!(obj2 is bool)) { return null; } return (bool)obj2; } private static string? ReadStrLike(object obj, string token) { object obj2 = ReadFieldLike(obj, token); if (obj2 == null) { return null; } if (obj2 is string result) { return result; } Object val = (Object)((obj2 is Object) ? obj2 : null); if (val != null) { if (!(val != (Object)null)) { return null; } return val.name; } return obj2.ToString(); } private static string ReadAnyNumber(object obj, string token) { return ReadFieldLike(obj, token)?.ToString() ?? "?"; } private static string ReadCountLike(object obj, string token) { object obj2 = ReadFieldLike(obj, token); if (obj2 is ICollection { Count: var count }) { return count.ToString(); } if (obj2 == null) { return "0"; } return "?"; } private static GameObject? ReadGameObject(object obj, string name) { object obj2 = ReadField(obj, name); GameObject val = (GameObject)((obj2 is GameObject) ? obj2 : null); if (val != null) { if (!((Object)(object)val != (Object)null)) { return null; } return val; } Component val2 = (Component)((obj2 is Component) ? obj2 : null); if (val2 != null) { if (!((Object)(object)val2 != (Object)null)) { return null; } return val2.gameObject; } return null; } private static bool AnyColliderEnabled(GameObject go) { try { Collider[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Collider val in componentsInChildren) { if ((Object)(object)val != (Object)null && val.enabled) { return true; } } } catch { } return false; } private static bool AnyRendererEnabled(GameObject go) { try { Renderer[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if ((Object)(object)val != (Object)null && val.enabled) { return true; } } } catch { } return false; } } internal static class MetalGatePatches { public static void Apply(Harmony harmony) { //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Expected O, but got Unknown //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Expected O, but got Unknown //IL_02ee: Unknown result type (might be due to invalid IL or missing references) //IL_0309: Unknown result type (might be due to invalid IL or missing references) //IL_0316: Expected O, but got Unknown //IL_0316: Expected O, but got Unknown //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Expected O, but got Unknown //IL_0373: Unknown result type (might be due to invalid IL or missing references) //IL_0380: Expected O, but got Unknown //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Expected O, but got Unknown try { if (!Plugin.Cfg.EnableGateSync.Value) { Plugin.Log.Info("[GateSync] disabled by config."); return; } Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Gameplay.Mechanisms.MetalGate.MetalGate"); if (type == null) { Plugin.Log.Warn("[GateSync] MetalGate type not found — gate sync disabled."); return; } Patch(harmony, type, "Awake", "MetalGate_Awake_Post"); Patch(harmony, type, "Close", "MetalGate_Close_Post"); Patch(harmony, type, "Open", "MetalGate_Open_Post"); MethodInfo methodInfo = AccessTools.DeclaredMethod(type, "Close", Type.EmptyTypes, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(MetalGatePatches).GetMethod("MetalGate_Close_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo2 = AccessTools.DeclaredMethod(type, "Open", Type.EmptyTypes, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(MetalGatePatches).GetMethod("MetalGate_Open_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Plugin.Log.Info("[GateSync] Patched MetalGate.Awake/Close/Open (combat-room gate sync)."); Type type2 = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.AllDeadTrigger"); MethodInfo methodInfo3 = ((type2 == null) ? null : AccessTools.DeclaredMethod(type2, "CheckAllDead", Type.EmptyTypes, (Type[])null)); if (methodInfo3 != null) { harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(MetalGatePatches).GetMethod("AllDeadTrigger_CheckAllDead_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info("[GateSync] Patched AllDeadTrigger.CheckAllDead (legit gate-reopen window)."); } Type type3 = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.BossFightHelper"); MethodInfo methodInfo4 = ((type3 == null) ? null : AccessTools.DeclaredMethod(type3, "OnBossDead", (Type[])null, (Type[])null)); if (methodInfo4 != null) { harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeof(MetalGatePatches).GetMethod("BossFightHelper_OnBossDead_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info("[GateSync] Patched BossFightHelper.OnBossDead (boss-death gate-reopen window)."); } else { Plugin.Log.Warn("[GateSync] BossFightHelper.OnBossDead not found — boss-death gate reopen not hooked."); } } catch (Exception ex) { Plugin.Log.Error("[GateSync] Apply failed: " + ex.Message); } try { if (!Plugin.Cfg.EnableTriggerDoorSync.Value && !Plugin.Cfg.EnableArenaLockdown.Value) { Plugin.Log.Info("[DoorSync] PlayerTrigger hook disabled by config."); return; } Type type4 = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.World.PlayerTrigger") ?? AccessTools.TypeByName("PerfectRandom.Sulfur.Core.PlayerTrigger"); if (type4 == null) { Plugin.Log.Warn("[DoorSync] PlayerTrigger type not found — trigger-door sync disabled."); return; } MethodInfo methodInfo5 = AccessTools.Method(type4, "Trigger", new Type[1] { typeof(GameObject) }, (Type[])null); if (methodInfo5 == null) { Plugin.Log.Warn("[DoorSync] PlayerTrigger.Trigger(GameObject) not found (skipped)."); return; } harmony.Patch((MethodBase)methodInfo5, new HarmonyMethod(typeof(MetalGatePatches).GetMethod("PlayerTrigger_Trigger_Pre", BindingFlags.Static | BindingFlags.NonPublic)), new HarmonyMethod(typeof(MetalGatePatches).GetMethod("PlayerTrigger_Trigger_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info("[DoorSync] Patched PlayerTrigger.Trigger (SetActive-door sync)."); if (Plugin.Cfg.EnableArenaLockdown.Value) { MethodInfo methodInfo6 = AccessTools.DeclaredMethod(type4, "Start", Type.EmptyTypes, (Type[])null); if (methodInfo6 != null) { harmony.Patch((MethodBase)methodInfo6, (HarmonyMethod)null, new HarmonyMethod(typeof(MetalGatePatches).GetMethod("PlayerTrigger_Start_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } catch (Exception ex2) { Plugin.Log.Error("[DoorSync] Apply failed: " + ex2.Message); } } private static void Patch(Harmony harmony, Type type, string method, string postfixName) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.DeclaredMethod(type, method, Type.EmptyTypes, (Type[])null); if (methodInfo == null) { Plugin.Log.Warn("[GateSync] MetalGate." + method + " not found (skipped)."); } else { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(MetalGatePatches).GetMethod(postfixName, BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void MetalGate_Awake_Post(object __instance, bool __runOriginal) { if (__runOriginal) { GateSyncManager.Register(__instance); } } private static void MetalGate_Close_Post(object __instance, bool __runOriginal) { if (__runOriginal) { GateSyncManager.CaptureLocalGate(__instance, closed: true); } } private static void MetalGate_Open_Post(object __instance, bool __runOriginal) { if (__runOriginal) { GateSyncManager.CaptureLocalGate(__instance, closed: false); } } private static bool MetalGate_Close_Pre(object __instance) { try { Object val = (Object)((__instance is Object) ? __instance : null); if (val != null && val != (Object)null && ArenaLockdownManager.IsGateGraced(val.GetInstanceID())) { return false; } } catch { } return true; } private static bool MetalGate_Open_Pre(object __instance) { try { if (GateSyncManager.IsApplyingMirror) { return true; } Object val = (Object)((__instance is Object) ? __instance : null); if (val != null && val != (Object)null && ArenaLockdownManager.IsGateHeld(val.GetInstanceID()) && !ArenaLockdownManager.IsLegitGateOpenWindow()) { return false; } } catch { } return true; } private static void AllDeadTrigger_CheckAllDead_Pre(object __instance) { try { if (AccessTools.Field(__instance.GetType(), "allAliveNpcs")?.GetValue(__instance) is ICollection { Count: 0 }) { ArenaLockdownManager.NotifyAllEnemiesDead(); } } catch { } } private static void BossFightHelper_OnBossDead_Post(bool __runOriginal) { if (__runOriginal) { ArenaLockdownManager.NotifyBossDead(); } } private static void PlayerTrigger_Trigger_Pre(object __instance) { ArenaLockdownManager.BeginLocalGraceIfSeal(__instance); } private static void PlayerTrigger_Start_Post(object __instance, bool __runOriginal) { if (__runOriginal) { ArenaLockdownManager.AttachDoorwaySensorIfSeal(__instance); } } private static void PlayerTrigger_Trigger_Post(object __instance, bool __runOriginal) { if (__runOriginal && !GateSyncManager.IsApplyingTriggerMirror) { TriggerDoorSyncManager.CaptureLocalTrigger(__instance); ArenaLockdownManager.OnLocalTriggerFired(__instance); } } } public static class PatchBootstrap { public static void ApplyAll(Harmony harmony) { Plugin.Log.Info("[PatchBootstrap] Applying reverse probe patches..."); ReverseProbePatches.Apply(harmony); LevelGenTracePatches.Apply(harmony); BossEncounterPatches.Apply(harmony); BossSpawnPatches.Apply(harmony); CousinArmPatches.Apply(harmony); DeathSpawnSyncPatches.Apply(harmony); WeaponFirePatches.Apply(harmony); BreakablePatches.Apply(harmony); MetalGatePatches.Apply(harmony); PickupPatches.Apply(harmony); PauseControlPatches.Apply(harmony); Plugin.Log.Info("[PatchBootstrap] Done."); } } internal static class PauseControlPatches { private static int _logCount; private static bool _wasSuppressing; private static Type _gmType; private static MethodInfo _modifyMethod; private static Type _padlockEnumType; private static Func _gmInstanceGetter; public static void Apply(Harmony harmony) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Expected O, but got Unknown try { Application.runInBackground = true; } catch { } Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); if (type != null) { MethodInfo methodInfo = AccessTools.Method(type, "ModifyGamePauseState", (Type[])null, (Type[])null); if (methodInfo != null) { try { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PauseControlPatches).GetMethod("ModifyGamePauseState_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info("[PauseControl] Patched GameManager.ModifyGamePauseState"); } catch (Exception ex) { Plugin.Log.Warn("[PauseControl] ModifyGamePauseState patch failed: " + ex.Message); } } else { Plugin.Log.Warn("[PauseControl] GameManager.ModifyGamePauseState not found"); } } else { Plugin.Log.Warn("[PauseControl] GameManager type not found"); } Type type2 = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.MenuManager"); if (!(type2 != null)) { return; } MethodInfo methodInfo2 = AccessTools.Method(type2, "OnApplicationFocus", (Type[])null, (Type[])null); if (methodInfo2 != null) { try { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(PauseControlPatches).GetMethod("OnApplicationFocus_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info("[PauseControl] Patched MenuManager.OnApplicationFocus"); } catch (Exception ex2) { Plugin.Log.Warn("[PauseControl] OnApplicationFocus patch failed: " + ex2.Message); } } } public static void Tick() { bool num = SuppressPause(); if (num && !_wasSuppressing) { ReleaseHeldGameplayPadlocks(); } _wasSuppressing = num; } private static void ReleaseHeldGameplayPadlocks() { try { if (_gmType == null) { _gmType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); _modifyMethod = ((_gmType != null) ? AccessTools.Method(_gmType, "ModifyGamePauseState", (Type[])null, (Type[])null) : null); _padlockEnumType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.LockStatePadlock"); _gmInstanceGetter = BuildInstanceGetter(_gmType); } if (_modifyMethod == null || _padlockEnumType == null || _gmInstanceGetter == null) { return; } object obj = _gmInstanceGetter(); if (obj == null) { return; } string[] array = new string[4] { "Inventory", "Paused", "DevTools", "Dialog" }; foreach (string value in array) { object obj2; try { obj2 = Enum.Parse(_padlockEnumType, value); } catch { continue; } _modifyMethod.Invoke(obj, new object[2] { obj2, false }); } if (Plugin.Cfg.LogPauseSuppression.Value) { Plugin.Log.Info("[PauseControl] session started — released held gameplay pause padlocks (menu stays open, world resumes)"); } } catch (Exception ex) { Plugin.Log.Warn("[PauseControl] retroactive padlock release failed: " + ex.Message); } } private static Func BuildInstanceGetter(Type gmType) { if (gmType == null) { return null; } PropertyInfo prop = AccessTools.Property(gmType, "Instance"); if (prop != null && prop.GetGetMethod(nonPublic: true) != null) { return () => prop.GetValue(null); } FieldInfo field = AccessTools.Field(gmType, "Instance"); if (field != null) { return () => field.GetValue(null); } return null; } private static bool SuppressPause() { try { if (!Plugin.Cfg.DisablePauseInMultiplayer.Value) { return false; } return NetConfig.GetMode() switch { NetMode.Client => NetLinkState.ClientLinked, NetMode.Host => NetLinkState.HostLinked, _ => false, }; } catch { return false; } } private static bool ModifyGamePauseState_Pre(object lockState, bool state) { if (!state) { return true; } if (!SuppressPause()) { return true; } string text = lockState?.ToString() ?? ""; switch (text) { case "Inventory": case "Paused": case "DevTools": case "Dialog": if (Plugin.Cfg.LogPauseSuppression.Value && _logCount++ < 60) { Plugin.Log.Info("[PauseControl] suppressed world pause padlock=" + text + " (multiplayer: world keeps running)"); } return false; default: return true; } } private static bool OnApplicationFocus_Pre(bool hasFocus) { if (!hasFocus && SuppressPause()) { if (Plugin.Cfg.LogPauseSuppression.Value && _logCount++ < 60) { Plugin.Log.Info("[PauseControl] ignored focus-loss pause (multiplayer)"); } return false; } return true; } } internal static class PickupPatches { public static void Apply(Harmony harmony) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Expected O, but got Unknown //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Expected O, but got Unknown try { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.InteractionManager"); if (type == null) { Plugin.Log.Warn("[WorldPickup] InteractionManager type not found — world item-drop sync disabled."); return; } MethodInfo methodInfo = AccessTools.DeclaredMethod(type, "SpawnPickup", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(PickupPatches).GetMethod("SpawnPickup_Post", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { Plugin.Log.Warn("[WorldPickup] InteractionManager.SpawnPickup not found — drop capture disabled."); } MethodInfo methodInfo2 = AccessTools.DeclaredMethod(type, "ExecutePickup", (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(PickupPatches).GetMethod("ExecutePickup_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { Plugin.Log.Warn("[WorldPickup] InteractionManager.ExecutePickup not found — take arbitration disabled."); } MethodInfo methodInfo3 = AccessTools.DeclaredMethod(type, "RemovePickup", (Type[])null, (Type[])null); if (methodInfo3 != null) { harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(PickupPatches).GetMethod("RemovePickup_Pre", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { Plugin.Log.Warn("[WorldPickup] InteractionManager.RemovePickup not found — registry cleanup degraded."); } Plugin.Log.Info("[WorldPickup] Patched InteractionManager.SpawnPickup/ExecutePickup/RemovePickup (world item-drop sync)."); } catch (Exception ex) { Plugin.Log.Error("[WorldPickup] Apply failed: " + ex.Message); } } private static void SpawnPickup_Post(Pickup __result) { if (Plugin.Cfg.EnableWorldItemDropSync.Value && !((Object)(object)__result == (Object)null)) { WorldPickupManager.CaptureLocalSpawn(__result); } } private static bool ExecutePickup_Pre(Pickup pickup, ref bool __result) { if (!Plugin.Cfg.EnableWorldItemDropSync.Value) { return true; } if (WorldPickupManager.TryBeginTake(pickup)) { __result = false; return false; } return true; } private static void RemovePickup_Pre(Pickup pickup) { WorldPickupManager.UnregisterPickup(pickup); } } internal static class ReverseProbePatches { private static int _playerTpDiagCount; private static float _playerTpDiagWindowStart; private static int _doTpDiagCount; private static float _doTpDiagWindowStart; private static bool _coopTgtResolved; private static FieldInfo? _aiOnlyTargetPlayerField; private static PropertyInfo? _aiOwnerProp; private static PropertyInfo? _aiHostilesProp; private static PropertyInfo? _unitIsAliveProp; private static PropertyInfo? _bnrGmInstance; private static PropertyInfo? _bnrGmUnits; private static PropertyInfo? _bnrGmAliveNpcs; private static bool _bnrResolved; private static int _bnrSweepLogCount; private static int _bnrLateUpdateSwallowLogCount; private static PropertyInfo? _poolDataCreateNewProp; private static int _autoPoolSafeLogCount; private static Type? _autoPooledType; private static PropertyInfo? _apActuallyPooledProp; private static FieldInfo? _apComponentTypeField; private static FieldInfo? _apPoolTypeField; private static int _poolDestroyDiagCount; private static bool _forcedDownedCrouch; private static int _aiDumpEnemyCount; private static bool _aiDumpGmDone; private static bool _aiDumpAggroDone; private static bool _ilDumped; private static Dictionary? _ilOpcodes; private static STLogger Log => Plugin.Log; private static CoopConfig Cfg => Plugin.Cfg; public static void Apply(Harmony harmony) { ApplyDamageForwardPatches(harmony); if (!Cfg.EnableReverseProbe.Value) { Log.Info("[ReverseProbe] Disabled by config."); return; } ApplyGameManagerPatches(harmony); ApplyUnitPatches(harmony); ApplyNpcPatches(harmony); ApplyAiAgentPatches(harmony); ApplyUnitManagerPatches(harmony); ApplyLootManagerPatches(harmony); ApplyInteractionManagerPatches(harmony); ApplyInventoryItemPatches(harmony); ApplyWeaponProjectilePatches(harmony); ApplyNextLevelTriggerPatches(harmony); ApplyInputReaderPatches(harmony); ApplyLevelGenPatches(harmony); ApplyTeleportDiagPatches(harmony); ApplyRemotePlayerTargetProxyPatches(harmony); ApplyDownedPlayerUntargetablePatches(harmony); ApplyMultiPlayerActivationPatches(harmony); ApplyBatchedRaycastSweepPatches(harmony); ApplyAutoPoolGetSafetyPatches(harmony); ApplyPooledDestroyDiagPatches(harmony); Log.Info("[ReverseProbe] All probes registered."); } private static string F(object? o) { return ReverseProbeFormatter.FormatInstance(o); } private static string ID(object? o) { return ReverseProbeFormatter.GetInstanceId(o); } private static string V(Vector3 v) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return ReverseProbeFormatter.FormatVector3(v); } private static string ItemName(object? o) { return ReverseProbeFormatter.FormatItemName(o); } private static HarmonyMethod Pre(string name) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown return new HarmonyMethod(typeof(ReverseProbePatches).GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic)); } private static HarmonyMethod Post(string name) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown return new HarmonyMethod(typeof(ReverseProbePatches).GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic)); } private static HarmonyMethod Fin(string name) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown return new HarmonyMethod(typeof(ReverseProbePatches).GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic)); } private static Type? FindType(string fullName) { Type type = AccessTools.TypeByName(fullName); if (type == null) { Log.Warn("[ReverseProbe] Type not found: " + fullName); } return type; } private static void TryPatch(Harmony harmony, Type type, string method, HarmonyMethod? prefix, HarmonyMethod? postfix, Type[]? paramTypes = null) { try { MethodInfo methodInfo = ((paramTypes != null) ? AccessTools.Method(type, method, paramTypes, (Type[])null) : AccessTools.Method(type, method, (Type[])null, (Type[])null)); if (methodInfo == null) { Log.Warn("[ReverseProbe] Method not found: " + type.Name + "." + method); return; } harmony.Patch((MethodBase)methodInfo, prefix, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.Debug("[ReverseProbe] Patched " + type.Name + "." + method); } catch (Exception ex) { Log.Error("[ReverseProbe] Patch failed " + type.Name + "." + method + ": " + ex.Message); } } private static void PatchAllOverloads(Harmony harmony, Type type, string methodName, HarmonyMethod? prefix = null, HarmonyMethod? postfix = null) { List list = (from m in AccessTools.GetDeclaredMethods(type) where m.Name == methodName select m).ToList(); if (list.Count == 0) { Log.Warn("[ReverseProbe] No overloads: " + type.Name + "." + methodName); return; } Log.Info($"[ReverseProbe] {type.Name}.{methodName} — {list.Count} overload(s):"); foreach (MethodInfo item in list) { string text = "(" + string.Join(", ", from p in item.GetParameters() select p.ParameterType.Name + " " + p.Name) + ")"; Log.Info($" [{item.GetParameters().Length}] {text}"); try { harmony.Patch((MethodBase)item, prefix, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { Log.Error("[ReverseProbe] Overload patch failed " + type.Name + "." + methodName + text + ": " + ex.Message); } } } private static void ApplyDamageForwardPatches(Harmony harmony) { Type type = FindType("PerfectRandom.Sulfur.Core.Units.Unit"); if (type != null) { foreach (MethodInfo item in from m in AccessTools.GetDeclaredMethods(type) where m.Name == "ReceiveDamage" select m) { try { harmony.Patch((MethodBase)item, Pre("Unit_ReceiveDamage_Pre"), Post("Unit_ReceiveDamage_Post"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { Log.Error("[DamageForward] Unit.ReceiveDamage patch failed: " + ex.Message); } } } Type type2 = FindType("PerfectRandom.Sulfur.Core.Units.Npc"); if (!(type2 != null)) { return; } foreach (MethodInfo item2 in from m in AccessTools.GetDeclaredMethods(type2) where m.Name == "ReceiveDamage" select m) { try { harmony.Patch((MethodBase)item2, Pre("Npc_ReceiveDamage_Pre"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex2) { Log.Error("[DamageForward] Npc.ReceiveDamage patch failed: " + ex2.Message); } } } private static string GetUnitCategory(object? instance) { string text = instance?.GetType().Name ?? ""; if (text.Contains("Breakable")) { return "Breakable"; } if (text == "Npc") { return "Npc"; } if (text == "Player") { return "Player"; } return "Unit"; } private static string RunStatsHostPeerId() { if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || string.IsNullOrWhiteSpace(state.PeerId)) { return "host"; } return state.PeerId; } private static void ApplyGameManagerPatches(Harmony harmony) { if (Cfg.EnablePlayerProbe.Value) { Type type = FindType("PerfectRandom.Sulfur.Core.GameManager"); if (!(type == null)) { TryPatch(harmony, type, "AddPlayer", null, Post("GM_AddPlayer_Post")); TryPatch(harmony, type, "AddNpc", null, Post("GM_AddNpc_Post")); TryPatch(harmony, type, "RemoveNpc", Pre("GM_RemoveNpc_Pre"), null); TryPatch(harmony, type, "SetState", Pre("GM_SetState_Pre"), null); TryPatch(harmony, type, "GoToLevel", Pre("GM_GoToLevel_Pre"), null); PatchAllOverloads(harmony, type, "SwitchLevelRoutine", Pre("GM_SwitchLevelRoutine_Pre")); PatchAllOverloads(harmony, type, "StartLevelRoutineGraph", Pre("GM_StartLevelRoutineGraph_Pre")); TryPatch(harmony, type, "CompleteLevel", Pre("GM_CompleteLevel_Pre"), null); TryPatch(harmony, type, "ClearLevel", Pre("GM_ClearLevel_Pre"), null); TryPatch(harmony, type, "GoToMainMenu", Pre("GM_GoToMainMenu_Pre"), null); TryPatch(harmony, type, "PlayerDied", Pre("GM_PlayerDied_Pre"), null); } } } private static void GM_AddPlayer_Post(object __instance, object player) { try { ReverseProbeSummary.IncrementPlayerAdded(); ReverseProbeSummary.IncrementGmEvent(); NetLevelSeed.ObserveGameManager(__instance, "GameManager.AddPlayer"); ReverseProbeKnownObjects.RegisterPlayer(ID(player), F(player)); NetRunStateBridge.ReportLocalPlayerObject(player); NetPlayerLifeManager.ReportLocalPlayerObject(player); Log.Info("[GM] AddPlayer >> " + F(player)); } catch (Exception ex) { Log.Error("[GM.AddPlayer] " + ex.Message); } } private static void GM_AddNpc_Post(object __instance, object npc) { try { ReverseProbeSummary.IncrementGmEvent(); ReverseProbeKnownObjects.RegisterNpc(ID(npc), F(npc)); Log.Info("[GM] AddNpc >> " + F(npc)); } catch (Exception ex) { Log.Error("[GM.AddNpc] " + ex.Message); } } private static void GM_RemoveNpc_Pre(object __instance, object npc) { try { ReverseProbeSummary.IncrementGmEvent(); Log.Info("[GM] RemoveNpc << " + F(npc)); } catch (Exception ex) { Log.Error("[GM.RemoveNpc] " + ex.Message); } } private static void GM_SetState_Pre(object __instance, object state) { try { ReverseProbeSummary.IncrementGmEvent(); NetLevelSeed.ObserveGameManager(__instance, "GameManager.SetState"); string text = state?.ToString() ?? ""; NetRunStateBridge.ReportGameState(text); Log.Info("[GM] SetState << " + text); } catch (Exception ex) { Log.Error("[GM.SetState] " + ex.Message); } } private static bool GM_GoToLevel_Pre(object __instance, object chapterSO, int levelIndex, object loadingMode, string spawnIdentifier) { try { string? chapter = chapterSO?.ToString() ?? ""; string loadingMode2 = loadingMode?.ToString() ?? ""; object gm = __instance; object chap = chapterSO; object lm = loadingMode; string sp = spawnIdentifier ?? ""; if (NetClientLoadGate.ShouldInterceptGoToLevel(localFallback: delegate { InvokeNativeGoToLevel(gm, chap, levelIndex, lm, sp); }, chapter: chapter, levelIndex: levelIndex, loadingMode: loadingMode2, spawn: sp)) { NetLoadingFade.Show(); return false; } } catch (Exception ex) { Log.Error("[ClientLoadGate] GoToLevel gate check failed: " + ex.Message); } try { ReverseProbeSummary.IncrementGmEvent(); NetLevelSeed.BeginLevelTransition(__instance, "GameManager.GoToLevel"); NetGameplayProbeManager.ClearLevelScoped("GameManager.GoToLevel"); BreakableBreakManager.Clear(); GateSyncManager.Clear(); ArenaLockdownManager.Clear(); WorldPickupManager.Clear(); string text = chapterSO?.ToString() ?? ""; string text2 = loadingMode?.ToString() ?? ""; string text3 = spawnIdentifier ?? ""; if (NetClientLoadGate.CurrentMode == NetMode.Host && text2.IndexOf("menu", StringComparison.OrdinalIgnoreCase) < 0) { NetHostTransitionGuard.Begin("host-GoToLevel"); } NetRunStateBridge.ReportGoToLevel(text, levelIndex, text2, text3); NetGenerationInputCapture.CaptureLevelTransition(text, EnumToInt(chapterSO), levelIndex, text2, text3, "GoToLevelPrefix"); Log.Info($"[GM] GoToLevel << chapter={text} level={levelIndex} mode={text2} spawn={text3}"); NetHostUsedSets netHostUsedSets = NetGameManagerUsedSets.CaptureLocal(__instance, text, levelIndex); if (netHostUsedSets != null && Cfg.LogUsedSetsTrace.Value) { Log.Info($"[UsedSetsTrace] GoToLevel chapter={text} level={levelIndex} {netHostUsedSets.ToCompactString()}"); Log.Info("[UsedSetsTrace] usedChunks=" + NetHostUsedSets.Summary(netHostUsedSets.UsedChunksThisRun)); Log.Info("[UsedSetsTrace] usedEventsRun=" + NetHostUsedSets.Summary(netHostUsedSets.UsedEventsThisRun)); Log.Info("[UsedSetsTrace] usedEventsEnv=" + NetHostUsedSets.Summary(netHostUsedSets.UsedEventsThisEnvironment)); } } catch (Exception ex2) { Log.Error("[GM.GoToLevel] " + ex2.Message); } return true; } private static void GM_SwitchLevelRoutine_Pre(object[] __args) { try { if (__args == null || __args.Length == 0) { return; } object obj = __args[0]; string text = obj?.ToString() ?? ""; int envId = EnumToInt(obj); int levelIndex = -1; for (int i = 1; i < __args.Length; i++) { if (__args[i] is int num) { levelIndex = num; break; } } string text2 = ((__args.Length > 2 && __args[2] != null) ? (__args[2].ToString() ?? "") : ""); string spawn = ""; for (int num2 = __args.Length - 1; num2 >= 1; num2--) { if (__args[num2] is string text3) { spawn = text3; break; } } NetGenerationInputCapture.CaptureLevelTransition(text, envId, levelIndex, text2, spawn, "SwitchLevelRoutinePrefix"); if (NetClientLoadGate.CurrentMode == NetMode.Host && (text2 ?? "").IndexOf("menu", StringComparison.OrdinalIgnoreCase) < 0) { NetHostTransitionGuard.Begin("host-SwitchLevelRoutine"); } if (NetClientLoadGate.CurrentMode == NetMode.Host) { bool flag = NetSceneClassify.IsHubOrSafeZoneChapter(text); if (flag && NetRunStatsManager.RunActive) { NetRunStatsList netRunStatsList = NetRunStatsManager.FinalizeAndSnapshot(); NetRunStatsClientCache.ApplyLocalFinalize(netRunStatsList.RunSeq, netRunStatsList.Players); NetGameplaySyncBridge.BroadcastRunStatsFinalized(netRunStatsList); } else if (!flag && !NetRunStatsManager.RunActive) { NetRunStatsManager.BeginRun(); } } } catch (Exception ex) { Log.Error("[GM.SwitchLevelRoutine] " + ex.Message); } } private static int EnumToInt(object? o) { try { if (o != null && o.GetType().IsEnum) { return Convert.ToInt32(o); } } catch { } return -1; } private static void GM_StartLevelRoutineGraph_Pre(object __instance, object[] __args, MethodBase __originalMethod) { try { NetGenerationInputCapture.CaptureFromStartLevelRoutineGraph(__instance, __args, __originalMethod); } catch (Exception ex) { Log.Error("[GenerationInputSnapshot] StartLevelRoutineGraph prefix failed: " + ex.Message); } } private static bool GM_CompleteLevel_Pre(object __instance) { try { ReverseProbeSummary.IncrementGmEvent(); Log.Info("[GM] CompleteLevel <<"); } catch (Exception ex) { Log.Error("[GM.CompleteLevel] " + ex.Message); } try { if (NetClientLoadGate.CurrentMode == NetMode.Host) { NetHostTransitionGuard.Begin("host-CompleteLevel"); } } catch (Exception ex2) { Log.Error("[HostTransitionGuard] CompleteLevel begin failed: " + ex2.Message); } try { if (TryComputeNextLevelTarget(__instance, out string chapter, out int level)) { object gm = __instance; if (NetClientLoadGate.TryBeginClientLevelCompleteRelay(chapter, level, delegate { InvokeNativeCompleteLevel(gm); }, out string reason)) { NetLoadingFade.Show(); Log.Info($"[ClientLoadGate] CompleteLevel intercepted -> host lead requested target={chapter}:{level} ({reason})"); return false; } Log.Info($"[ClientLoadGate] CompleteLevel allowed locally target={chapter}:{level} reason={reason}"); } } catch (Exception ex3) { Log.Error("[ClientLoadGate] CompleteLevel relay check failed: " + ex3.Message); } return true; } private static bool TryComputeNextLevelTarget(object gm, out string chapter, out int level) { chapter = ""; level = -1; if (gm == null) { return false; } Type type = gm.GetType(); object obj = type.GetProperty("currentEnvironment", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(gm) ?? type.GetField("currentEnvironment", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(gm); if (obj == null) { return false; } object obj2 = type.GetProperty("currentLevelIndex", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(gm) ?? type.GetField("currentLevelIndex", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(gm); if (obj2 == null) { return false; } int num = Convert.ToInt32(obj2); if (num < 0) { return false; } int num2 = -1; if ((obj.GetType().GetProperty("levels", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj) ?? obj.GetType().GetField("levels", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj)) is ICollection collection) { num2 = collection.Count; } int num3 = num + 1; if (num2 >= 0 && num3 >= num2) { object obj3 = type.GetMethod("GetNextEnvironment", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(gm, null); if (obj3 == null) { return false; } chapter = obj3.ToString(); level = 0; } else { object obj4 = obj.GetType().GetProperty("id", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj) ?? obj.GetType().GetField("id", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj); if (obj4 == null) { return false; } chapter = obj4.ToString(); level = num3; } return !string.IsNullOrWhiteSpace(chapter); } private static void InvokeNativeCompleteLevel(object gm) { try { MethodInfo method = gm.GetType().GetMethod("CompleteLevel", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); if (method == null) { Log.Warn("[ClientLoadGate] local CompleteLevel fallback: method not found"); return; } NetClientLoadGate.BeginHostDrivenLoad(); try { method.Invoke(gm, null); } finally { NetClientLoadGate.EndHostDrivenLoad(); } } catch (Exception ex) { Log.Error("[ClientLoadGate] local CompleteLevel fallback failed: " + ex.Message); } } private static void InvokeNativeGoToLevel(object gm, object chapterSO, int levelIndex, object loadingMode, string spawn) { try { MethodInfo methodInfo = null; MethodInfo[] methods = gm.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods) { if (!(methodInfo2.Name != "GoToLevel")) { ParameterInfo[] parameters = methodInfo2.GetParameters(); if (parameters.Length == 4 && parameters[1].ParameterType == typeof(int) && parameters[3].ParameterType == typeof(string)) { methodInfo = methodInfo2; break; } } } if (methodInfo == null) { Log.Warn("[ClientLoadGate] local GoToLevel fallback: method not found"); return; } NetClientLoadGate.BeginHostDrivenLoad(); try { methodInfo.Invoke(gm, new object[4] { chapterSO, levelIndex, loadingMode, spawn ?? "" }); } finally { NetClientLoadGate.EndHostDrivenLoad(); } } catch (Exception ex) { Log.Error("[ClientLoadGate] local GoToLevel fallback failed: " + ex.Message); } } private static void GM_GoToMainMenu_Pre(object __instance) { try { if (NetClientLoadGate.CurrentMode == NetMode.Client) { NetLinkState.ResetClientToDefault("returned-to-main-menu"); } } catch (Exception ex) { Log.Error("[LinkState] GoToMainMenu reset failed: " + ex.Message); } } private static void GM_ClearLevel_Pre(object __instance) { try { ReverseProbeSummary.IncrementGmEvent(); NetLevelSeed.ObserveGameManager(__instance, "GameManager.ClearLevel"); ReverseProbeKnownObjects.ClearLevelScopedObjects(); NetGameplayProbeManager.ClearLevelScoped("GameManager.ClearLevel"); BreakableBreakManager.Clear(); GateSyncManager.Clear(); ArenaLockdownManager.Clear(); WorldPickupManager.Clear(); NetRunStateBridge.ReportClearLevel(); Log.Info("[GM] ClearLevel <<"); } catch (Exception ex) { Log.Error("[GM.ClearLevel] " + ex.Message); } } private static void GM_PlayerDied_Pre(object __instance) { try { ReverseProbeSummary.IncrementGmEvent(); Log.Info("[GM] PlayerDied <<"); } catch (Exception ex) { Log.Error("[GM.PlayerDied] " + ex.Message); } } private static void ApplyUnitPatches(Harmony harmony) { if (Cfg.EnableUnitProbe.Value) { Type type = FindType("PerfectRandom.Sulfur.Core.Units.Unit"); if (!(type == null)) { TryPatch(harmony, type, "Spawn", null, Post("Unit_Spawn_Post")); TryPatch(harmony, type, "Die", Pre("Unit_Die_Pre"), null); TryPatch(harmony, type, "SpawnLoot", Pre("Unit_SpawnLoot_Pre"), null); TryPatch(harmony, type, "SetUnitState", Pre("Unit_SetUnitState_Pre"), null); PatchAllOverloads(harmony, type, "TeleportTo", Pre("Unit_TeleportTo_Pre")); } } } private static void Unit_Spawn_Post(object __instance) { try { ReverseProbeSummary.IncrementUnitSpawn(); string text = __instance?.GetType().Name ?? "null"; bool num = text.Contains("Breakable"); string category = (num ? "Breakable" : ((text == "Npc") ? "Npc" : ((text == "Player") ? "Player" : "Other"))); ReverseProbeKnownObjects.RegisterSpawn(ID(__instance), F(__instance), category); NetGameplayProbeManager.ReportSpawn(__instance, "Unit.Spawn", category); if (text == "Player") { NetPlayerLifeManager.ReportLocalPlayerObject(__instance); } if (num) { if (!Cfg.EnableBreakableSpawnProbe.Value) { return; } } else if (text != "Npc" && text != "Player" && !Cfg.EnableVerboseUnitSpawnProbe.Value) { return; } Log.Info("[Unit] Spawn >> " + F(__instance)); } catch (Exception ex) { Log.Error("[Unit.Spawn] " + ex.Message); } } private static bool Unit_Die_Pre(object __instance) { try { if (NetPlayerLifeManager.TryBlockLocalPlayerDeath(__instance, "Unit.Die", out string detail)) { if (Cfg.LogPlayerLifeSync.Value) { Log.Info("[Unit] Player Die intercepted by co-op downed/revive layer: " + detail + " inst=" + F(__instance)); } return false; } string unitCategory = GetUnitCategory(__instance); ReverseProbeSummary.IncrementDeath(unitCategory); ReverseProbeKnownObjects.RegisterDeath(ID(__instance)); NetGameplayProbeManager.ReportDeath(__instance, "Unit.Die", unitCategory); if (unitCategory != "Breakable" && unitCategory != "Player") { NetRunStatsManager.RecordKill(NetRunStatsManager.EntityKey(__instance), RunStatsHostPeerId()); } Log.Info("[Unit] Die << " + F(__instance)); } catch (Exception ex) { Log.Error("[Unit.Die] " + ex.Message); } return true; } private static void Unit_SpawnLoot_Pre(object __instance) { try { ReverseProbeSummary.IncrementLootSpawn(); Log.Info("[Unit] SpawnLoot << " + F(__instance)); } catch (Exception ex) { Log.Error("[Unit.SpawnLoot] " + ex.Message); } } private static void Unit_SetUnitState_Pre(object __instance, object state) { try { Log.Info($"[Unit] SetUnitState << inst={F(__instance)} state={state}"); } catch (Exception ex) { Log.Error("[Unit.SetUnitState] " + ex.Message); } } private static void Unit_TeleportTo_Pre(object __instance, object[] __args) { //IL_003f: 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_0070: Unknown result type (might be due to invalid IL or missing references) try { if (Cfg.LogTeleportDiag.Value && NetPlayerLifeManager.IsLocalPlayerUnit(__instance)) { Component val = (Component)((__instance is Component) ? __instance : null); string text = ((val != null && (Object)(object)val != (Object)null) ? V(val.transform.position) : "?"); string text2 = ((__args != null && __args.Length != 0 && __args[0] is Vector3 v) ? V(v) : "?"); float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup - _playerTpDiagWindowStart > 1f) { _playerTpDiagWindowStart = realtimeSinceStartup; _playerTpDiagCount = 0; } _playerTpDiagCount++; if (_playerTpDiagCount <= 3 || _playerTpDiagCount % 25 == 0) { Log.Info($"[TeleportDiag] PLAYER TeleportTo from={text} to={text2} rapidCount/sec={_playerTpDiagCount}\n{new StackTrace(1, fNeedFileInfo: true)}"); } } else { Log.Info("[Unit] TeleportTo << " + F(__instance)); } } catch (Exception ex) { Log.Error("[Unit.TeleportTo] " + ex.Message); } } private static void TeleportPlayer_DoTeleport_Pre(object __instance) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) try { if (!Cfg.LogTeleportDiag.Value || __instance == null) { return; } string text = "?"; try { Component val = (Component)((__instance is Component) ? __instance : null); if (val != null && (Object)(object)val != (Object)null) { text = ((Object)val.gameObject).name + "@" + V(val.transform.position); } } catch { } string text2 = "?"; string[] array = new string[5] { "destination", "teleportDestination", "target", "destinationTransform", "teleportTo" }; foreach (string text3 in array) { try { object obj2 = AccessTools.Field(__instance.GetType(), text3)?.GetValue(__instance); if (obj2 != null) { text2 = DescribeTransformish(obj2); break; } } catch { } } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup - _doTpDiagWindowStart > 1f) { _doTpDiagWindowStart = realtimeSinceStartup; _doTpDiagCount = 0; } _doTpDiagCount++; if (_doTpDiagCount <= 3 || _doTpDiagCount % 25 == 0) { Log.Info($"[TeleportDiag] TeleportPlayer.DoTeleport comp={text} dest={text2} rapidCount/sec={_doTpDiagCount}\n{new StackTrace(1, fNeedFileInfo: true)}"); } } catch (Exception ex) { Log.Error("[TeleportDiag.DoTeleport] " + ex.Message); } } private static string DescribeTransformish(object d) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) try { Transform val = (Transform)((d is Transform) ? d : null); if (val != null) { return ((Object)val).name + "@" + V(val.position); } GameObject val2 = (GameObject)((d is GameObject) ? d : null); if (val2 != null) { return ((Object)val2).name + "@" + V(val2.transform.position); } Component val3 = (Component)((d is Component) ? d : null); if (val3 != null) { return ((object)val3).GetType().Name + ":" + ((Object)val3).name + "@" + V(val3.transform.position); } return d.GetType().Name; } catch { return d?.GetType().Name ?? "null"; } } private static void ApplyTeleportDiagPatches(Harmony harmony) { if (Cfg.LogTeleportDiag.Value) { Type type = FindType("PerfectRandom.Sulfur.Core.World.TeleportPlayer"); if (!(type == null)) { TryPatch(harmony, type, "DoTeleport", Pre("TeleportPlayer_DoTeleport_Pre"), null); } } } private static void ApplyRemotePlayerTargetProxyPatches(Harmony harmony) { Type type = FindType("PerfectRandom.Sulfur.Core.Units.Unit"); if (!(type == null)) { TryPatch(harmony, type, "SetupBreakableArmor", Pre("Unit_SetupBreakableArmor_Pre"), null); } } private static bool Unit_SetupBreakableArmor_Pre(object __instance) { return !RemotePlayerTargetProxyManager.IsProxyUnit(__instance); } private static void ApplyDownedPlayerUntargetablePatches(Harmony harmony) { Type type = FindType("PerfectRandom.Sulfur.Core.Units.AI.AiAgent"); if (!(type == null)) { TryPatch(harmony, type, "GetTarget", null, Post("AiAgent_GetTarget_HideDowned_Post")); } } private static void AiAgent_GetTarget_HideDowned_Post(object __instance, ref object __result) { try { if (__result == null) { return; } if (Cfg.BalanceCoopEnemyTargeting.Value) { object obj = TryPickNearestPlayerHostile(__instance, __result); if (obj != null) { __result = obj; } } if (Cfg.HideDownedLocalPlayerFromEnemies.Value && NetPlayerLifeManager.IsDownedLocalPlayerUnit(__result) && !CousinArmPatches.IsCousinArmAiAgent(__instance)) { __result = null; } } catch { } } private static void EnsureCoopTargetingResolved(object aiAgent, object sampleUnit) { if (_coopTgtResolved) { return; } _coopTgtResolved = true; try { Type type = aiAgent.GetType(); _aiOnlyTargetPlayerField = AccessTools.Field(type, "onlyTargetPlayer"); _aiOwnerProp = AccessTools.Property(type, "Owner"); _aiHostilesProp = AccessTools.Property(type, "hostilesInLOS"); _unitIsAliveProp = AccessTools.Property(sampleUnit.GetType(), "IsAlive"); } catch (Exception ex) { Log.Warn("[CoopTarget] resolve failed: " + ex.Message); } } private static bool IsPlayerParticipantUnit(object? unit) { if (unit == null) { return false; } try { if (NetPlayerLifeManager.IsLocalPlayerUnit(unit)) { return true; } if (RemotePlayerTargetProxyManager.IsProxyUnit(unit)) { return true; } } catch { } return false; } private static bool IsUnitAlive(object unit) { try { int result; if (!(_unitIsAliveProp == null)) { object value = _unitIsAliveProp.GetValue(unit, null); bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } result = num & (flag ? 1 : 0); } else { result = 1; } return (byte)result != 0; } catch { return true; } } private static object? TryPickNearestPlayerHostile(object aiAgent, object currentResult) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_0103: 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_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) try { EnsureCoopTargetingResolved(aiAgent, currentResult); if (!IsPlayerParticipantUnit(currentResult)) { return null; } object obj = _aiOnlyTargetPlayerField?.GetValue(aiAgent); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { return null; } object? obj2 = _aiOwnerProp?.GetValue(aiAgent, null); Component val = (Component)((obj2 is Component) ? obj2 : null); if (val == null || (Object)(object)val == (Object)null) { return null; } if (!(_aiHostilesProp?.GetValue(aiAgent, null) is IEnumerable enumerable)) { return null; } Vector3 position = val.transform.position; object result = null; float num2 = float.MaxValue; foreach (object item in enumerable) { if (item == null || !IsPlayerParticipantUnit(item) || NetPlayerLifeManager.IsDownedLocalPlayerUnit(item) || !IsUnitAlive(item)) { continue; } Component val2 = (Component)((item is Component) ? item : null); if (val2 != null && !((Object)(object)val2 == (Object)null)) { Vector3 val3 = val2.transform.position - position; float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude; if (sqrMagnitude < num2) { num2 = sqrMagnitude; result = item; } } } return result; } catch { return null; } } private static void ApplyMultiPlayerActivationPatches(Harmony harmony) { Type type = FindType("PerfectRandom.Sulfur.Core.NpcUpdateManager"); if (!(type == null)) { TryPatch(harmony, type, "LateUpdate", null, Post("NpcUpdateManager_LateUpdate_Post")); } } private static void NpcUpdateManager_LateUpdate_Post() { try { RemotePlayerRegistryManager.ActivateNpcsNearRemotePlayers(); } catch (Exception ex) { Log.Warn("[PlayerRegistry] LateUpdate postfix failed: " + ex.Message); } } private static void ApplyBatchedRaycastSweepPatches(Harmony harmony) { Type type = FindType("PerfectRandom.Sulfur.Core.BatchedNPCRaycasts"); if (type == null) { return; } MethodInfo methodInfo = AccessTools.Method(type, "Update", (Type[])null, (Type[])null); if (methodInfo == null) { Log.Warn("[NpcListSweep] BatchedNPCRaycasts.Update not found"); return; } try { harmony.Patch((MethodBase)methodInfo, Pre("BatchedNPCRaycasts_Update_Pre"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { Log.Warn("[NpcListSweep] patch failed: " + ex.Message); } MethodInfo methodInfo2 = AccessTools.Method(type, "LateUpdate", (Type[])null, (Type[])null); if (!(methodInfo2 != null)) { return; } try { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, Fin("BatchedNPCRaycasts_LateUpdate_Finalizer"), (HarmonyMethod)null); } catch (Exception ex2) { Log.Warn("[NpcListSweep] LateUpdate finalizer patch failed: " + ex2.Message); } } private static Exception? BatchedNPCRaycasts_LateUpdate_Finalizer(Exception __exception) { if (__exception == null) { return null; } if (!Cfg.EnableDestroyedUnitListSweep.Value) { return __exception; } if (_bnrLateUpdateSwallowLogCount++ < 12) { Log.Warn("[NpcListSweep] swallowed " + __exception.GetType().Name + " in BatchedNPCRaycasts.LateUpdate (roster/Players count race during runtime spawn; recovers next frame): " + __exception.Message); } return null; } private static void BatchedNPCRaycasts_Update_Pre() { try { if (!Cfg.EnableDestroyedUnitListSweep.Value) { return; } if (!_bnrResolved) { _bnrResolved = true; Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); if (type != null) { _bnrGmInstance = AccessTools.Property(type, "Instance"); _bnrGmUnits = AccessTools.Property(type, "units"); _bnrGmAliveNpcs = AccessTools.Property(type, "aliveNpcs"); } } object obj = _bnrGmInstance?.GetValue(null, null); if (obj != null) { int num = 0; num += SweepDestroyed(_bnrGmUnits?.GetValue(obj, null) as IList); num += SweepDestroyed(_bnrGmAliveNpcs?.GetValue(obj, null) as IList); if (num > 0 && _bnrSweepLogCount++ < 12) { Log.Warn($"[NpcListSweep] removed {num} destroyed Unit(s) from GameManager.units/aliveNpcs before BatchedNPCRaycasts.Update (prevents Transform.get_position NRE)."); } } } catch { } } private static int SweepDestroyed(IList? list) { if (list == null) { return 0; } int num = 0; for (int num2 = list.Count - 1; num2 >= 0; num2--) { object? obj = list[num2]; if ((Object)((obj is Object) ? obj : null) == (Object)null) { list.RemoveAt(num2); num++; } } return num; } private static void ApplyAutoPoolGetSafetyPatches(Harmony harmony) { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.AutoPool+PoolData"); if (type == null) { Log.Warn("[AutoPoolSafe] AutoPool+PoolData type not found"); return; } MethodInfo methodInfo = AccessTools.Method(type, "Get", (Type[])null, (Type[])null); if (methodInfo == null) { Log.Warn("[AutoPoolSafe] PoolData.Get not found"); return; } try { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, Fin("PoolData_Get_Finalizer"), (HarmonyMethod)null); } catch (Exception ex) { Log.Warn("[AutoPoolSafe] patch failed: " + ex.Message); } } private static Exception? PoolData_Get_Finalizer(object __instance, ref object __result, Exception __exception) { if (__exception == null) { return null; } try { if (_poolDataCreateNewProp == null) { _poolDataCreateNewProp = AccessTools.Property(__instance.GetType(), "createNew"); } if (_poolDataCreateNewProp?.GetValue(__instance) is Delegate obj) { object obj2 = obj.DynamicInvoke(); Component val = (Component)((obj2 is Component) ? obj2 : null); if (val != null && (Object)(object)val != (Object)null) { val.gameObject.SetActive(true); __result = obj2; if (_autoPoolSafeLogCount++ < 12) { Log.Warn("[AutoPoolSafe] PoolData.Get hit a destroyed pooled object (AutoPool.ResetPools level-switch race); recovered with a fresh instance (projectile preserved)."); } return null; } } } catch (Exception ex) { Log.Warn("[AutoPoolSafe] recovery failed: " + ex.GetType().Name + ": " + ex.Message); } return __exception; } private static void ApplyPooledDestroyDiagPatches(Harmony harmony) { Type typeFromHandle = typeof(Object); try { MethodInfo methodInfo = AccessTools.Method(typeFromHandle, "Destroy", new Type[1] { typeof(Object) }, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, Pre("Object_Destroy_PoolDiag_Pre"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo2 = AccessTools.Method(typeFromHandle, "Destroy", new Type[2] { typeof(Object), typeof(float) }, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, Pre("Object_Destroy_PoolDiag_Pre"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } catch (Exception ex) { Log.Warn("[PoolDestroyDiag] patch failed: " + ex.Message); } } private static void Object_Destroy_PoolDiag_Pre(Object obj) { try { if (!Cfg.LogPooledObjectDestroyDiag.Value || _poolDestroyDiagCount > 40 || obj == (Object)null) { return; } object obj2 = ((obj is GameObject) ? obj : null); if (obj2 == null) { Object obj3 = ((obj is Component) ? obj : null); obj2 = ((obj3 != null) ? ((Component)obj3).gameObject : null); } GameObject val = (GameObject)obj2; if ((Object)(object)val == (Object)null) { return; } if (_autoPooledType == null) { _autoPooledType = FindType("PerfectRandom.Sulfur.Core.AutoPooledObject"); if (_autoPooledType == null) { return; } _apActuallyPooledProp = AccessTools.Property(_autoPooledType, "actuallyPooled"); _apComponentTypeField = AccessTools.Field(_autoPooledType, "componentType"); _apPoolTypeField = AccessTools.Field(_autoPooledType, "poolType"); } Component[] componentsInChildren = val.GetComponentsInChildren(_autoPooledType, true); if (componentsInChildren == null) { return; } Component[] array = componentsInChildren; foreach (Component val2 in array) { object obj4 = _apActuallyPooledProp?.GetValue(val2); if (obj4 is bool && (bool)obj4) { _poolDestroyDiagCount++; string text = ((val2 != null) ? ((Object)val2.gameObject).name : null) ?? "?"; Log.Warn($"[PoolDestroyDiag] ROGUE destroy of pooled obj: destroyedGO='{((Object)val).name}' pooledGO='{text}' poolType={_apPoolTypeField?.GetValue(val2)} compType={_apComponentTypeField?.GetValue(val2)}\nSTACK:\n{Environment.StackTrace}"); break; } } } catch { } } private static bool Unit_ReceiveDamage_Pre(object __instance, float damage, object damageType, object[] __args) { //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_0085: 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_0093: 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_014d: 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) try { if (damage > 0f && NetConfig.GetMode() == NetMode.Host && RemotePlayerTargetProxyManager.TryGetProxyPeer(__instance, out string peerId)) { object rawSource = ((__args != null && __args.Length > 2) ? __args[2] : null); bool flag = FriendlyFireClassifier.IsFromLocalPlayer(rawSource); FriendlyFireClassifier.LogClassification("host proxy hit", rawSource, damage, flag); if (flag) { if (!NetSessionSettings.FriendlyFireEnabled) { return false; } if (NetPlayerLifeManager.IsPeerDownOrDead(peerId)) { return false; } } Component val = (Component)((__instance is Component) ? __instance : null); Vector3 hitPosition = ((val != null && (Object)(object)val != (Object)null) ? val.transform.position : Vector3.zero); int damageTypeInt = 0; try { if (damageType != null) { damageTypeInt = Convert.ToInt32(damageType); } } catch { } NetPlayerLifeManager.ReportHostAuthoritativeEnemyDamage(peerId, damage, flag ? "player friendly fire" : "enemy via target proxy", hitPosition, damageTypeInt); return false; } if (NetConfig.GetMode() == NetMode.Client && RemotePlayerTargetProxyManager.TryGetProxyPeer(__instance, out string peerId2)) { if (damage > 0f && NetSessionSettings.FriendlyFireEnabled) { object rawSource2 = ((__args != null && __args.Length > 2) ? __args[2] : null); bool flag2 = FriendlyFireClassifier.IsFromLocalPlayer(rawSource2); FriendlyFireClassifier.LogClassification("client proxy hit", rawSource2, damage, flag2); if (flag2) { Component val2 = (Component)((__instance is Component) ? __instance : null); Vector3 hitPos = ((val2 != null && (Object)(object)val2 != (Object)null) ? val2.transform.position : Vector3.zero); int damageTypeInt2 = 0; try { if (damageType != null) { damageTypeInt2 = Convert.ToInt32(damageType); } } catch { } NetFriendlyFireManager.SendLocalPlayerHit(peerId2, damage, damageTypeInt2, hitPos); } } return false; } if (NetPlayerLifeManager.ShouldBlockLocalPlayerDamage(__instance)) { if (Cfg.LogPlayerLifeSync.Value) { Log.Debug($"[Unit] ReceiveDamage suppressed while local player is downed dmg={damage} inst={F(__instance)}"); } return false; } if (Cfg.EnableClientEnemyNativeDamageSuppression.Value && NetGameplayProbeManager.IsInClientEnemyNativeDamageSuppression() && NetPlayerLifeManager.IsLocalPlayerUnit(__instance) && NetPlayerLifeManager.HostDamageApplyDepth == 0) { NetGameplayProbeManager.CountSuppressedNativeEnemyDamage(); Log.Debug($"[Unit] ReceiveDamage suppressed (client native damage suppression) dmg={damage} inst={F(__instance)}"); return false; } string unitCategory = GetUnitCategory(__instance); ReverseProbeSummary.IncrementDamage(unitCategory); ReverseProbeKnownObjects.RegisterDamage(ID(__instance)); NetGameplayProbeManager.ReportDamage(__instance, "Unit.ReceiveDamage", unitCategory, damage, damageType); if (damage > 0f && NetConfig.GetMode() == NetMode.Host && NetPlayerLifeManager.IsLocalPlayerUnit(__instance) && NetGameplayProbeManager.TryReadBossUnitHealth(__instance, out var currentHp, out var _)) { NetRunStatsManager.NoteHostOwnPlayerBeforeHp(currentHp); } if (Cfg.LogUnitReceiveDamage.Value) { Log.Info($"[Unit] ReceiveDamage << {F(__instance)} dmg={damage} type={damageType}"); } } catch (Exception ex) { Log.Error("[Unit.ReceiveDamage] " + ex.Message); } return true; } private static void Unit_ReceiveDamage_Post(object __instance, float damage) { try { float maxHp; float currentHp2; if (GetUnitCategory(__instance) == "Npc") { NetGameplayProbeManager.ReportHostNpcHealthAfterDamage(__instance, damage); if (NetConfig.GetMode() == NetMode.Host && NetGameplayProbeManager.TryReadBossUnitHealth(__instance, out var currentHp, out maxHp)) { NetRunStatsManager.ResolvePendingLocalHit(NetRunStatsManager.EntityKey(__instance), currentHp); } } else if (NetConfig.GetMode() == NetMode.Host && NetPlayerLifeManager.IsLocalPlayerUnit(__instance) && NetGameplayProbeManager.TryReadBossUnitHealth(__instance, out currentHp2, out maxHp)) { NetRunStatsManager.ResolveHostOwnPlayerDamageTaken(currentHp2); } } catch (Exception ex) { Log.Error("[Unit.ReceiveDamage.Post] " + ex.Message); } } private static void ApplyNpcPatches(Harmony harmony) { if (Cfg.EnableNpcProbe.Value) { Type type = FindType("PerfectRandom.Sulfur.Core.Units.Npc"); if (!(type == null)) { TryPatch(harmony, type, "Spawn", null, Post("Npc_Spawn_Post")); TryPatch(harmony, type, "Die", Pre("Npc_Die_Pre"), null); TryPatch(harmony, type, "TriggerAttackAnimation", Pre("Npc_AttackAnim_Pre"), null); TryPatch(harmony, type, "TriggerShoot", Pre("Npc_TriggerShoot_Pre"), null); TryPatch(harmony, type, "SetShooting", Pre("Npc_SetShooting_Pre"), null); TryPatch(harmony, type, "TriggerWeaponManually", Pre("Npc_TriggerWeapon_Pre"), null); TryPatch(harmony, type, "SetAimTarget", Pre("Npc_SetAimTarget_Pre"), null); TryPatch(harmony, type, "GetAimPosition", null, Post("Npc_GetAimPosition_Post")); TryPatch(harmony, type, "HandleMeleeHit", Pre("Npc_MeleeHit_Pre"), Post("Npc_MeleeHit_Post")); TryPatch(harmony, type, "TriggerShootFromAnimation", Pre("Npc_TriggerShootFromAnimation_Pre"), null); TryPatch(harmony, type, "StartMeleeDamageState", Pre("Npc_StartMeleeDamageState_Pre"), null); TryPatch(harmony, type, "EndMeleeDamageState", Pre("Npc_EndMeleeDamageState_Pre"), null); TryPatch(harmony, type, "SetRangedAttacking", Pre("Npc_SetRangedAttacking_Pre"), null); TryPatch(harmony, type, "SetAttacking", Pre("Npc_SetAttacking_Pre"), null); TryPatch(harmony, type, "DoneAttacking", Pre("Npc_DoneAttacking_Pre"), null); TryPatch(harmony, type, "DoneShooting", Pre("Npc_DoneShooting_Pre"), null); TryPatch(harmony, type, "ToggleBehaviourTree", Pre("Npc_ToggleBehaviourTree_Pre"), null); TryPatch(harmony, type, "ActivateBehaviour", Pre("Npc_ActivateBehaviour_Pre"), null); TryPatch(harmony, type, "ActivateBehaviourTree", Pre("Npc_ActivateBehaviour_Pre"), null); TryPatch(harmony, type, "MoveToPlayer", Pre("Npc_PuppetBlockMovement_Pre"), null); TryPatch(harmony, type, "MoveToSpawnPosition", Pre("Npc_PuppetBlockMovement_Pre"), null); TryPatch(harmony, type, "SetForcedDestination", Pre("Npc_PuppetBlockMovement_Pre"), null); TryPatch(harmony, type, "PerformLunge", Pre("Npc_PuppetBlockMovement_Pre"), null); TryPatch(harmony, type, "JumpApplyForce", Pre("Npc_PuppetBlockMovement_Pre"), null); TryPatch(harmony, type, "Update", null, Post("Npc_Update_Post")); } } } private static void Npc_Spawn_Post(object __instance) { try { ReverseProbeSummary.IncrementNpcSpawn(); ReverseProbeKnownObjects.RegisterSpawn(ID(__instance), F(__instance), "Npc"); NetGameplayProbeManager.ReportSpawn(__instance, "Npc.Spawn", "Npc"); Log.Info("[Npc] Spawn >> " + F(__instance)); } catch (Exception ex) { Log.Error("[Npc.Spawn] " + ex.Message); } } private static void Npc_Die_Pre(object __instance) { try { ReverseProbeSummary.IncrementDeath("Npc"); ReverseProbeKnownObjects.RegisterDeath(ID(__instance)); NetGameplayProbeManager.ReportDeath(__instance, "Npc.Die", "Npc"); NetRunStatsManager.RecordKill(NetRunStatsManager.EntityKey(__instance), RunStatsHostPeerId()); Log.Info("[Npc] Die << " + F(__instance)); } catch (Exception ex) { Log.Error("[Npc.Die] " + ex.Message); } } private static bool Npc_AttackAnim_Pre(object __instance) { try { if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetNpcCombat(__instance, "Npc.TriggerAttackAnimation", out string _)) { return false; } NetGameplayProbeManager.ReportEnemyCombatProbe(__instance, "Npc.TriggerAttackAnimation"); Log.Info("[Npc] TriggerAttackAnimation << " + F(__instance)); } catch (Exception ex) { Log.Error("[Npc.TriggerAttackAnimation] " + ex.Message); } return true; } private static bool Npc_TriggerShoot_Pre(object __instance) { try { if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetNpcCombat(__instance, "Npc.TriggerShoot", out string _)) { return false; } NetGameplayProbeManager.ReportEnemyCombatProbe(__instance, "Npc.TriggerShoot"); if (Cfg.LogEnemyCombatProbe.Value) { Log.Info("[Npc] TriggerShoot << " + F(__instance)); } } catch (Exception ex) { Log.Error("[Npc.TriggerShoot] " + ex.Message); } return true; } private static bool Npc_SetShooting_Pre(object __instance, bool state) { try { if (state && NetGameplayProbeManager.ShouldBlockClientEnemyPuppetNpcCombat(__instance, "Npc.SetShooting", out string _)) { return false; } if (state) { NetGameplayProbeManager.ReportEnemyCombatProbe(__instance, "Npc.SetShooting", $"state={state}"); } if (Cfg.LogEnemyCombatProbe.Value) { Log.Info($"[Npc] SetShooting << {F(__instance)} state={state}"); } } catch (Exception ex) { Log.Error("[Npc.SetShooting] " + ex.Message); } return true; } private static bool Npc_TriggerWeapon_Pre(object __instance, int state) { try { if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetNpcCombat(__instance, "Npc.TriggerWeaponManually", out string _)) { return false; } NetGameplayProbeManager.ReportEnemyCombatProbe(__instance, "Npc.TriggerWeaponManually", $"state={state}"); if (Cfg.LogEnemyCombatProbe.Value) { Log.Info($"[Npc] TriggerWeaponManually << {F(__instance)} state={state}"); } } catch (Exception ex) { Log.Error("[Npc.TriggerWeaponManually] " + ex.Message); } return true; } private static bool Npc_SetAimTarget_Pre(object __instance, object target) { try { if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetNpcTargeting(__instance, target, "Npc.SetAimTarget", out string _)) { return false; } if (Cfg.LogEnemyCombatProbe.Value) { Log.Info("[Npc] SetAimTarget << " + F(__instance) + " target=" + F(target)); } } catch (Exception ex) { Log.Error("[Npc.SetAimTarget] " + ex.Message); } return true; } private static void Npc_GetAimPosition_Post(object __instance, ref Vector3 __result) { //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) try { if (NetGameplayProbeManager.TryGetClientPuppetAimOverride(__instance, out var aim)) { __result = aim; } } catch (Exception ex) { Log.Error("[Npc.GetAimPosition/AimOverride] " + ex.Message); } } private static bool Npc_ToggleBehaviourTree_Pre(object __instance, bool state) { try { if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetBehaviourTreeActivation(__instance, state)) { return false; } } catch (Exception ex) { Log.Error("[Npc.ToggleBehaviourTree/Puppet] " + ex.Message); } return true; } private static bool Npc_ActivateBehaviour_Pre(object __instance) { try { if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetNpcMovement(__instance)) { return false; } } catch (Exception ex) { Log.Error("[Npc.ActivateBehaviour/Puppet] " + ex.Message); } return true; } private static bool Npc_PuppetBlockMovement_Pre(object __instance, MethodBase __originalMethod, object[] __args) { try { string source = ((__originalMethod == null) ? "Npc.Movement" : ("Npc." + __originalMethod.Name)); NetGameplayProbeManager.ReportEnemyAiIntent(__instance, source, __args); if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetNpcMovement(__instance)) { return false; } } catch (Exception ex) { Log.Error("[Npc.PuppetBlockMovement] " + ex.Message); } return true; } private static bool Npc_MeleeHit_Pre(object __instance, ref bool __state) { __state = false; try { if (Cfg.EnableHostDrivenEnemyProxy.Value && Cfg.SuppressAllClientPuppetDamage.Value && NetGameplayProbeManager.IsClientEnemyPuppetNpc(__instance)) { NetGameplayProbeManager.EnterClientEnemyNativeDamageSuppression(); __state = true; NetGameplayProbeManager.ReportEnemyCombatProbe(__instance, "Npc.HandleMeleeHit"); return true; } if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetNpcCombat(__instance, "Npc.HandleMeleeHit", out string _)) { return false; } NetGameplayProbeManager.ReportEnemyCombatProbe(__instance, "Npc.HandleMeleeHit"); if (Cfg.EnableClientEnemyNativeDamageSuppression.Value && NetGameplayProbeManager.IsClientEnemyPuppetNpc(__instance)) { NetGameplayProbeManager.EnterClientEnemyNativeDamageSuppression(); __state = true; } if (!Cfg.EnableNpcMeleeProbe.Value) { return true; } if (!ReverseProbeState.ShouldLog("MeleeHit_" + ID(__instance), Cfg.ProbeThrottleSeconds.Value, Cfg.MaxRepeatedLogPerKey.Value)) { return true; } Log.Info("[Npc] HandleMeleeHit << " + F(__instance)); } catch (Exception ex) { Log.Error("[Npc.HandleMeleeHit] " + ex.Message); } return true; } private static void Npc_MeleeHit_Post(bool __state) { try { if (__state) { NetGameplayProbeManager.ExitClientEnemyNativeDamageSuppression(); } } catch { } } private static bool Npc_TriggerShootFromAnimation_Pre(object __instance) { try { if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetNpcCombat(__instance, "Npc.TriggerShootFromAnimation", out string _)) { return false; } NetGameplayProbeManager.ReportEnemyCombatProbe(__instance, "Npc.TriggerShootFromAnimation"); } catch (Exception ex) { Log.Error("[Npc.TriggerShootFromAnimation] " + ex.Message); } return true; } private static bool Npc_StartMeleeDamageState_Pre(object __instance) { try { if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetNpcCombat(__instance, "Npc.StartMeleeDamageState", out string _)) { return false; } NetGameplayProbeManager.ReportEnemyCombatProbe(__instance, "Npc.StartMeleeDamageState"); } catch (Exception ex) { Log.Error("[Npc.StartMeleeDamageState] " + ex.Message); } return true; } private static bool Npc_EndMeleeDamageState_Pre(object __instance) { try { if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetNpcCombat(__instance, "Npc.EndMeleeDamageState", out string _)) { return false; } NetGameplayProbeManager.ReportEnemyCombatProbe(__instance, "Npc.EndMeleeDamageState"); } catch (Exception ex) { Log.Error("[Npc.EndMeleeDamageState] " + ex.Message); } return true; } private static bool Npc_SetRangedAttacking_Pre(object __instance, bool state) { try { if (state && NetGameplayProbeManager.ShouldBlockClientEnemyPuppetNpcCombat(__instance, "Npc.SetRangedAttacking", out string _)) { return false; } if (state) { NetGameplayProbeManager.ReportEnemyCombatProbe(__instance, "Npc.SetRangedAttacking", $"state={state}"); } } catch (Exception ex) { Log.Error("[Npc.SetRangedAttacking] " + ex.Message); } return true; } private static bool Npc_SetAttacking_Pre(object __instance) { try { if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetNpcCombat(__instance, "Npc.SetAttacking", out string _)) { return false; } NetGameplayProbeManager.ReportEnemyCombatProbe(__instance, "Npc.SetAttacking"); } catch (Exception ex) { Log.Error("[Npc.SetAttacking] " + ex.Message); } return true; } private static bool Npc_DoneAttacking_Pre(object __instance) { try { if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetNpcCombat(__instance, "Npc.DoneAttacking", out string _)) { return false; } NetGameplayProbeManager.ReportEnemyCombatProbe(__instance, "Npc.DoneAttacking"); } catch (Exception ex) { Log.Error("[Npc.DoneAttacking] " + ex.Message); } return true; } private static bool Npc_DoneShooting_Pre(object __instance) { try { if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetNpcCombat(__instance, "Npc.DoneShooting", out string _)) { return false; } NetGameplayProbeManager.ReportEnemyCombatProbe(__instance, "Npc.DoneShooting"); } catch (Exception ex) { Log.Error("[Npc.DoneShooting] " + ex.Message); } return true; } private static void Npc_Update_Post(object __instance) { try { NetGameplayProbeManager.ApplyClientEnemyPuppetAnimationPostUpdate(__instance); if (Cfg.EnableNpcUpdateProbe.Value && ReverseProbeState.ShouldLog("Npc_Update_" + ID(__instance), Cfg.ProbeThrottleSeconds.Value)) { Log.Debug("[Npc] Update (throttled) >> " + F(__instance)); } } catch (Exception ex) { Log.Error("[Npc.Update] " + ex.Message); } } private static bool Npc_ReceiveDamage_Pre(object __instance, float damage, object damageType, object source) { try { int damageTypeInt = 0; try { if (damageType != null) { damageTypeInt = Convert.ToInt32(damageType); } } catch { } if (NetBossEncounterManager.TryClientBossHit(__instance, damage, damageTypeInt)) { return false; } if (NetEmperorWormSync.TryClientWormHit(__instance, damage, damageTypeInt)) { return false; } if (NetEmperorSpiderSync.TryClientSpiderHit(__instance, damage, damageTypeInt)) { return false; } if (NetGameplayProbeManager.ShouldIgnoreNonPlayerPuppetDamage(__instance, source)) { return false; } if (NetGameplayProbeManager.TrySendClientHitRequest(__instance, damage, damageType)) { return false; } ReverseProbeSummary.IncrementDamage("Npc"); ReverseProbeKnownObjects.RegisterDamage(ID(__instance)); NetGameplayProbeManager.ReportDamage(__instance, "Npc.ReceiveDamage", "Npc", damage, damageType); NetGameplayProbeManager.ReportHostNpcDamageForSync(__instance, damage); if (damage > 0f && NetConfig.GetMode() == NetMode.Host && NetGameplayProbeManager.IsConfidentPlayerDamageSource(source) && NetGameplayProbeManager.TryReadBossUnitHealth(__instance, out var currentHp, out var _)) { NetRunStatsManager.NotePendingLocalHit(NetRunStatsManager.EntityKey(__instance), RunStatsHostPeerId(), currentHp); } if (Cfg.LogUnitReceiveDamage.Value) { Log.Info($"[Npc] ReceiveDamage << {F(__instance)} dmg={damage} type={damageType}"); } } catch (Exception ex) { Log.Error("[Npc.ReceiveDamage] " + ex.Message); } return true; } private static void ApplyWeaponProjectilePatches(Harmony harmony) { Type type = FindType("PerfectRandom.Sulfur.Core.Weapons.Weapon"); if (type != null) { TryPatch(harmony, type, "DispatchProjectile", Pre("Weapon_DispatchProjectile_Pre"), null); } Type type2 = FindType("PerfectRandom.Sulfur.Core.ProjectileSystem"); if (type2 != null) { PatchAllOverloads(harmony, type2, "StartProjectile", Pre("ProjectileSystem_StartProjectile_Pre")); } } private static void Weapon_DispatchProjectile_Pre(object __instance) { try { NetGameplayProbeManager.ReportProjectileProbe(__instance, "Weapon.DispatchProjectile", "pre"); } catch (Exception ex) { Log.Error("[Weapon.DispatchProjectile/Probe] " + ex.Message); } } private static void ProjectileSystem_StartProjectile_Pre() { try { NetGameplayProbeManager.ReportProjectileProbe(null, "ProjectileSystem.StartProjectile", "pre"); } catch (Exception ex) { Log.Error("[ProjectileSystem.StartProjectile/Probe] " + ex.Message); } } private static void ApplyInputReaderPatches(Harmony harmony) { if (!Cfg.EnableCoopPlayerDownedRevive.Value) { return; } Type type = FindType("PerfectRandom.Sulfur.Core.Input.InputReader"); if (!(type == null)) { TryPatch(harmony, type, "GetMovementInput", Pre("InputReader_GetVector2ZeroWhileDowned_Pre"), null); TryPatch(harmony, type, "GetRawMovementInput", Pre("InputReader_GetVector2ZeroWhileDowned_Pre"), null); TryPatch(harmony, type, "GetHorizontalMovementInput", Pre("InputReader_GetFloatZeroWhileDowned_Pre"), null); TryPatch(harmony, type, "GetVerticalMovementInput", Pre("InputReader_GetFloatZeroWhileDowned_Pre"), null); TryPatch(harmony, type, "IsJumpKeyPressed", Pre("InputReader_GetBoolFalseWhileDowned_Pre"), null); string[] array = new string[9] { "SelectLastUsedWeapon", "SelectNextSlot", "SelectPreviousSlot", "SelectByScroll", "SelectSlot1", "SelectSlot2", "SelectSlot3", "SelectSlot4", "SelectSlot5" }; foreach (string method in array) { TryPatch(harmony, type, method, Pre("InputReader_BlockActionWhileDowned_Pre"), null); } Type type2 = FindType("PerfectRandom.Sulfur.Core.Movement.ExtendedAdvancedWalkerController"); if (type2 != null) { TryPatch(harmony, type2, "UpdateCrouching", Pre("ExtendedWalker_UpdateCrouching_Pre"), null); } } } private static bool ExtendedWalker_UpdateCrouching_Pre(object __instance) { if (__instance == null) { return true; } Type type = __instance.GetType(); if (NetPlayerLifeManager.ShouldSuppressLocalPlayerControls()) { try { FieldInfo fieldInfo = AccessTools.Field(type, "isCrouching"); bool flag = default(bool); int num; if (fieldInfo != null) { object value = fieldInfo.GetValue(__instance); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) == 0) { AccessTools.Method(type, "ToggleCrouch", (Type[])null, (Type[])null)?.Invoke(__instance, new object[1] { true }); } } catch { } _forcedDownedCrouch = true; return false; } if (_forcedDownedCrouch) { _forcedDownedCrouch = false; try { AccessTools.Method(type, "ToggleCrouch", (Type[])null, (Type[])null)?.Invoke(__instance, new object[1] { false }); } catch { } } return true; } private static bool InputReader_BlockActionWhileDowned_Pre() { return !NetPlayerLifeManager.ShouldSuppressLocalPlayerControls(); } private static bool InputReader_GetVector2ZeroWhileDowned_Pre(ref Vector2 __result) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (!NetPlayerLifeManager.ShouldSuppressLocalPlayerControls()) { return true; } __result = Vector2.zero; return false; } private static bool InputReader_GetFloatZeroWhileDowned_Pre(ref float __result) { if (!NetPlayerLifeManager.ShouldSuppressLocalPlayerControls()) { return true; } __result = 0f; return false; } private static bool InputReader_GetBoolFalseWhileDowned_Pre(ref bool __result) { if (!NetPlayerLifeManager.ShouldSuppressLocalPlayerControls()) { return true; } __result = false; return false; } private static void ApplyAiAgentPatches(Harmony harmony) { if (!Cfg.EnableNpcProbe.Value) { return; } Type type = FindType("PerfectRandom.Sulfur.Core.Units.AI.AiAgent"); if (!(type == null)) { TryPatch(harmony, type, "UpdateTarget", Pre("AI_UpdateTarget_Pre"), null); TryPatch(harmony, type, "SetNavMeshAgentState", Pre("AI_SetNavMeshState_Pre"), null); TryPatch(harmony, type, "SetCanMove", Pre("AI_SetCanMove_Pre"), null); TryPatch(harmony, type, "GetTarget", null, Post("AI_GetTarget_Post")); PatchAllOverloads(harmony, type, "SetDestination", Pre("AI_SetDestination_Pre")); Type type2 = FindType("PerfectRandom.Sulfur.Core.Units.AI.CustomRichAI"); if (type2 != null) { TryPatch(harmony, type2, "FinalMovement", Pre("CustomRichAI_FinalMovement_Pre"), null); } } } private static bool AI_UpdateTarget_Pre(object __instance) { try { DumpAiTargetingOnce(__instance); if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetAiAgentTargeting(__instance, "AiAgent.UpdateTarget", out string _)) { return false; } if (!Cfg.EnableAiUpdateTargetProbe.Value) { return true; } if (!ReverseProbeState.ShouldLog("AI_UpdateTarget_" + ID(__instance), Cfg.ProbeThrottleSeconds.Value)) { return true; } Log.Debug("[AI] UpdateTarget (throttled) << " + F(__instance)); } catch (Exception ex) { Log.Error("[AI.UpdateTarget] " + ex.Message); } return true; } private static bool AI_SetDestination_Pre(object __instance, MethodBase __originalMethod, object[] __args) { try { string source = ((__originalMethod == null) ? "AiAgent.SetDestination" : ("AiAgent." + __originalMethod.Name)); NetGameplayProbeManager.ReportEnemyAiIntent(__instance, source, __args); if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetAiAgentMovement(__instance)) { return false; } if (!Cfg.EnableAiSetDestinationProbe.Value) { return true; } if (!ReverseProbeState.ShouldLog("AI_SetDestination_" + ID(__instance), Cfg.ProbeThrottleSeconds.Value)) { return true; } Log.Debug("[AI] SetDestination (throttled) << " + F(__instance)); } catch (Exception ex) { Log.Error("[AI.SetDestination] " + ex.Message); } return true; } private static bool AI_SetNavMeshState_Pre(object __instance, bool state) { try { if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetAiAgentState(__instance, state)) { return false; } if (!Cfg.EnableAiNavMeshStateProbe.Value) { return true; } Log.Debug($"[AI] SetNavMeshAgentState << {F(__instance)} state={state}"); } catch (Exception ex) { Log.Error("[AI.SetNavMeshAgentState] " + ex.Message); } return true; } private static bool AI_SetCanMove_Pre(object __instance, bool state) { try { if (NetGameplayProbeManager.ShouldBlockClientEnemyPuppetAiAgentState(__instance, state)) { return false; } if (!Cfg.EnableAiCanMoveProbe.Value) { return true; } Log.Debug($"[AI] SetCanMove << {F(__instance)} state={state}"); } catch (Exception ex) { Log.Error("[AI.SetCanMove] " + ex.Message); } return true; } private static bool CustomRichAI_FinalMovement_Pre(object __instance) { try { if (NetGameplayProbeManager.ShouldSkipClientEnemyPuppetFinalMovement(__instance)) { return false; } } catch (Exception ex) { Log.Error("[CustomRichAI.FinalMovement/Puppet] " + ex.Message); } return true; } private static void DumpAiTargetingOnce(object aiAgent) { if (aiAgent == null || !Cfg.LogAiTargetingReverseDump.Value) { return; } try { DumpDetectionIL(aiAgent.GetType()); if (!_aiDumpGmDone) { TryDumpGameManagerPlayers(); } object obj = TryGetMember(aiAgent, "Owner"); string text = DescribeVal(obj); if (text.IndexOf("Trader", StringComparison.OrdinalIgnoreCase) < 0) { object obj2 = TryGetMember(aiAgent, "target"); object c = TryGetMember(aiAgent, "hostilesInLOS"); if (!_aiDumpAggroDone && obj2 != null) { _aiDumpAggroDone = true; Log.Info(string.Format("[AiDump] AGGRO enemy owner={0} faction={1} target={2} targetFaction={3} targetIsPlayer={4} hostilesInLOS={5}", text, ReadFaction(obj), DescribeVal(obj2), ReadFaction(obj2), TryGetMember(obj2, "isPlayer"), DescribeEnumerable(c))); } if (_aiDumpEnemyCount < 3) { _aiDumpEnemyCount++; Log.Info(string.Format("[AiDump] ENEMY agent owner={0} faction={1} onlyTargetPlayer={2} target={3} playerUnit={4}", text, ReadFaction(obj), TryGetMember(aiAgent, "onlyTargetPlayer"), DescribeVal(obj2), DescribeVal(TryGetMember(aiAgent, "playerUnit")))); } } } catch (Exception ex) { Log.Error("[AiDump] " + ex.Message); } } private static string ReadFaction(object? unit) { if (unit == null) { return "null"; } string[] array = new string[6] { "FactionId", "factionId", "Faction", "CurrentFactionId", "GetFactionId", "GetFaction" }; foreach (string text in array) { try { object obj = TryGetMember(unit, text); if (obj != null) { return text + "=" + DescribeVal(obj); } MethodInfo method = unit.GetType().GetMethod(text, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method != null) { object v = method.Invoke(unit, null); return text + "()=" + DescribeVal(v); } } catch { } } return "faction?"; } private static object? TryGetMember(object obj, string name) { try { Type type = obj.GetType(); while (type != null && type != typeof(object)) { PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { return property.GetValue(obj, null); } FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(obj); } type = type.BaseType; } } catch { } return null; } private static void TryDumpGameManagerPlayers() { try { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); object obj = ((type == null) ? null : AccessTools.Property(type, "Instance")?.GetValue(null, null)); if (obj == null) { return; } object obj2 = AccessTools.Property(type, "Players")?.GetValue(obj, null); object obj3 = AccessTools.Property(type, "PlayerUnit")?.GetValue(obj, null); object obj4 = AccessTools.Property(type, "units")?.GetValue(obj, null); if (obj3 == null && !(obj2 is ICollection { Count: >0 })) { return; } Log.Info("[AiDump] GM.Players=" + DescribeEnumerable(obj2) + " units=" + DescribeVal(obj4)); if (obj3 != null) { Log.Info(string.Format("[AiDump] GM.PlayerUnit={0} faction={1} isPlayer={2} type={3} interfaces=[{4}]", DescribeVal(obj3), ReadFaction(obj3), TryGetMember(obj3, "isPlayer"), obj3.GetType().FullName, string.Join(",", from i in obj3.GetType().GetInterfaces() select i.Name))); DumpFields("PlayerUnit", obj3, new string[3] { "faction", "team", "player" }); bool flag = false; if (obj4 is IEnumerable enumerable) { foreach (object item in enumerable) { if (item == obj3) { flag = true; break; } } } Log.Info($"[AiDump] playerInGM.units={flag}"); DumpMethods(obj3.GetType(), new string[13] { "register", "awake", "init", "setup", "spawn", "faction", "addto", "stats", "setstatus", "collider", "enable", "isplayer", "setplayer" }); STLogger log = Log; Component val = (Component)((obj3 is Component) ? obj3 : null); IEnumerable values; if (val == null || !((Object)(object)val != (Object)null)) { IEnumerable enumerable2 = new string[0]; values = enumerable2; } else { values = from c in val.GetComponents() select ((object)c).GetType().Name; } log.Info("[AiDump] Unit components=[" + string.Join(",", values) + "]"); } _aiDumpGmDone = true; } catch (Exception ex) { Log.Error("[AiDump.GM] " + ex.Message); } } private static string DescribeEnumerable(object? c) { if (c == null) { return "null"; } if (c is string result) { return result; } if (c is IEnumerable enumerable) { StringBuilder stringBuilder = new StringBuilder(); int num = 0; foreach (object item in enumerable) { if (num > 0) { stringBuilder.Append(", "); } stringBuilder.Append(DescribeVal(item)); if (++num >= 12) { stringBuilder.Append(",..."); break; } } return $"[{num}]{{{stringBuilder}}}"; } return DescribeVal(c); } private static bool NameHas(string name, params string[] kws) { string text = name.ToLowerInvariant(); foreach (string value in kws) { if (text.Contains(value)) { return true; } } return false; } private static void DumpDetectionIL(Type aiAgentType) { if (_ilDumped) { return; } _ilDumped = true; string[] array = new string[4] { "UpdateTarget", "IsPlayerInSight", "HandleDetectionDistance", "GetTarget" }; foreach (string methodName in array) { DumpMethodIL(aiAgentType, methodName); } Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Units.Npc"); if (type != null) { array = new string[3] { "HandleMeleeHit", "HitmeshHitByMelee", "StartMeleeDamageState" }; foreach (string text in array) { if (AccessTools.Method(type, text, (Type[])null, (Type[])null) != null) { DumpMethodIL(type, text); } } } try { Type type2 = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Units.Hitmesh") ?? AccessTools.TypeByName("Hitmesh"); if (type2 != null) { Log.Info("[AiDump] === Hitmesh type=" + type2.FullName + " baseType=" + type2.BaseType?.Name + " ==="); FieldInfo[] fields = type2.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { Log.Info("[AiDump] Hitmesh field " + fieldInfo.Name + " : " + fieldInfo.FieldType.Name); } MethodInfo[] methods = type2.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!methodInfo.IsSpecialName || methodInfo.Name == "Awake" || methodInfo.Name == "Start" || methodInfo.Name == "OnEnable") { Log.Info("[AiDump] Hitmesh method " + methodInfo.Name + "(" + string.Join(",", from p in methodInfo.GetParameters() select p.ParameterType.Name) + ")"); } } } object? obj = BossDamageReflect.ResolveHostPlayerUnit(); Component val = (Component)((obj is Component) ? obj : null); if (val != null && (Object)(object)val != (Object)null && type2 != null) { Component[] componentsInChildren = val.GetComponentsInChildren(type2, true); Log.Info($"[AiDump] player Hitmesh count={componentsInChildren.Length}"); Component[] array2 = componentsInChildren; foreach (Component obj2 in array2) { GameObject val2 = ((obj2 != null) ? obj2.gameObject : null); Collider val3 = (((Object)(object)val2 == (Object)null) ? null : val2.GetComponent()); object v = TryGetMember(obj2, "owner"); Log.Info(string.Format("[AiDump] Hitmesh on go={0} layer={1} collider={2} owner={3}", (val2 != null) ? ((Object)val2).name : null, (val2 != null) ? new int?(val2.layer) : ((int?)null), ((Object)(object)val3 == (Object)null) ? "none" : (((object)val3).GetType().Name + " trigger=" + val3.isTrigger), DescribeVal(v))); } } } catch (Exception ex) { Log.Error("[AiDump] Hitmesh dump failed: " + ex.Message); } try { List list = new List(); MethodInfo[] methods = aiAgentType.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods) { if (methodInfo2.IsAbstract || methodInfo2.ContainsGenericParameters) { continue; } CollectMethodRefs(methodInfo2, out HashSet calls, out HashSet fields2); if (calls.Contains("get_hostilesInLOS") || fields2.Contains("k__BackingField") || fields2.Contains("hostilesInLOS")) { bool flag = calls.Contains("Add"); bool flag2 = calls.Contains("Clear"); Log.Info(string.Format("[ILDump] hostilesInLOS touched by {0}(add={1} clear={2}) calls=[{3}]", methodInfo2.Name, flag, flag2, string.Join(",", calls.Take(24)))); if (flag) { list.Add(methodInfo2.Name); } } } foreach (string item in list) { DumpMethodIL(aiAgentType, item); } Type type3 = aiAgentType.GetField("overridetargets", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.FieldType ?? AccessTools.TypeByName("OverrideTarget"); if (type3 != null) { Log.Info("[ILDump] === OverrideTarget type=" + type3.FullName + " ==="); methods = type3.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo3 in methods) { if (!methodInfo3.IsSpecialName || methodInfo3.Name.StartsWith("get_") || methodInfo3.Name.StartsWith("set_")) { Log.Info("[ILDump] OverrideTarget." + methodInfo3.Name + "(" + string.Join(",", from p in methodInfo3.GetParameters() select p.ParameterType.Name + " " + p.Name) + ") -> " + methodInfo3.ReturnType.Name); } } FieldInfo[] fields = type3.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo2 in fields) { Log.Info("[ILDump] OverrideTarget field " + fieldInfo2.Name + " : " + fieldInfo2.FieldType.Name); } array = new string[4] { "AddUnits", "ClearUnits", "GrabHostileUnit", "get_HasTargets" }; foreach (string text2 in array) { if (type3.GetMethod(text2, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) != null) { DumpMethodIL(type3, text2); } } MethodInfo method = type3.GetMethod("AddUnits", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Type type4 = (((object)method != null && method.GetParameters().Length == 2) ? method.GetParameters()[1].ParameterType : null); if (type4 != null && type4.IsEnum) { Log.Info("[ILDump] TargetType enum " + type4.FullName + " values=[" + string.Join(",", Enum.GetNames(type4)) + "]"); } } Type[] nestedTypes = aiAgentType.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic); foreach (Type type5 in nestedTypes) { methods = type5.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo4 in methods) { if (methodInfo4.Name.Contains("GetTarget")) { DumpMethodIL(type5, methodInfo4.Name); } } } } catch (Exception ex2) { Log.Error("[ILDump] populator scan failed: " + ex2.Message); } } private static void CollectMethodRefs(MethodInfo m, out HashSet calls, out HashSet fields) { calls = new HashSet(); fields = new HashSet(); try { if (_ilOpcodes == null) { _ilOpcodes = new Dictionary(); FieldInfo[] fields2 = typeof(OpCodes).GetFields(BindingFlags.Static | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields2) { if (fieldInfo.FieldType == typeof(OpCode)) { OpCode value = (OpCode)fieldInfo.GetValue(null); _ilOpcodes[(ushort)value.Value] = value; } } } byte[] array = m.GetMethodBody()?.GetILAsByteArray(); if (array == null) { return; } Module module = m.Module; Type[] genericTypeArguments = ((m.DeclaringType != null && m.DeclaringType.IsGenericType) ? m.DeclaringType.GetGenericArguments() : null); Type[] genericMethodArguments = (m.IsGenericMethod ? m.GetGenericArguments() : null); int num = 0; while (num < array.Length) { int num2 = array[num++]; if (num2 == 254 && num < array.Length) { num2 = 0xFE00 | array[num++]; } if (!_ilOpcodes.TryGetValue(num2, out var value2)) { break; } switch (value2.OperandType) { case OperandType.ShortInlineBrTarget: case OperandType.ShortInlineI: case OperandType.ShortInlineVar: num++; break; case OperandType.InlineVar: num += 2; break; case OperandType.InlineI8: case OperandType.InlineR: num += 8; break; case OperandType.InlineSwitch: { int num3 = BitConverter.ToInt32(array, num); num += 4 + 4 * num3; break; } case OperandType.InlineMethod: { int metadataToken2 = BitConverter.ToInt32(array, num); num += 4; try { MethodBase methodBase = module.ResolveMethod(metadataToken2, genericTypeArguments, genericMethodArguments); if (methodBase != null) { calls.Add(methodBase.Name); } } catch { } break; } case OperandType.InlineField: { int metadataToken = BitConverter.ToInt32(array, num); num += 4; try { FieldInfo fieldInfo2 = module.ResolveField(metadataToken, genericTypeArguments, genericMethodArguments); if (fieldInfo2 != null) { fields.Add(fieldInfo2.Name); } } catch { } break; } case OperandType.InlineTok: num += 4; break; default: num += 4; break; case OperandType.InlineNone: break; } } } catch { } } private static void DumpMethodIL(Type t, string methodName) { try { if (_ilOpcodes == null) { _ilOpcodes = new Dictionary(); FieldInfo[] fields = typeof(OpCodes).GetFields(BindingFlags.Static | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(OpCode)) { OpCode value = (OpCode)fieldInfo.GetValue(null); _ilOpcodes[(ushort)value.Value] = value; } } } MethodInfo methodInfo = AccessTools.Method(t, methodName, (Type[])null, (Type[])null); byte[] array = methodInfo?.GetMethodBody()?.GetILAsByteArray(); if (methodInfo == null || array == null) { Log.Info("[ILDump] " + t.Name + "." + methodName + " no IL"); return; } Module module = methodInfo.Module; Type[] genericTypeArguments = (t.IsGenericType ? t.GetGenericArguments() : null); Type[] genericMethodArguments = (methodInfo.IsGenericMethod ? methodInfo.GetGenericArguments() : null); Log.Info($"[ILDump] ===== {t.Name}.{methodName} (IL {array.Length}B) ====="); int num = 0; int num2 = 0; while (num < array.Length && num2 < 200) { int num3 = array[num++]; if (num3 == 254 && num < array.Length) { num3 = 0xFE00 | array[num++]; } if (!_ilOpcodes.TryGetValue(num3, out var value2)) { break; } switch (value2.OperandType) { case OperandType.ShortInlineBrTarget: case OperandType.ShortInlineI: case OperandType.ShortInlineVar: num++; break; case OperandType.InlineVar: num += 2; break; case OperandType.InlineI8: case OperandType.InlineR: num += 8; break; case OperandType.InlineSwitch: { int num4 = BitConverter.ToInt32(array, num); num += 4 + 4 * num4; break; } case OperandType.InlineMethod: { int metadataToken3 = BitConverter.ToInt32(array, num); num += 4; try { MethodBase methodBase = module.ResolveMethod(metadataToken3, genericTypeArguments, genericMethodArguments); Log.Info("[ILDump] " + value2.Name + " " + methodBase?.DeclaringType?.Name + "." + methodBase?.Name); num2++; } catch { } break; } case OperandType.InlineField: { int metadataToken2 = BitConverter.ToInt32(array, num); num += 4; try { FieldInfo fieldInfo2 = module.ResolveField(metadataToken2, genericTypeArguments, genericMethodArguments); Log.Info("[ILDump] " + value2.Name + " " + fieldInfo2?.DeclaringType?.Name + "." + fieldInfo2?.Name); num2++; } catch { } break; } case OperandType.InlineTok: { int metadataToken = BitConverter.ToInt32(array, num); num += 4; try { MemberInfo arg = module.ResolveMember(metadataToken, genericTypeArguments, genericMethodArguments); Log.Info($"[ILDump] {value2.Name} {arg}"); num2++; } catch { } break; } default: num += 4; break; case OperandType.InlineNone: break; } } } catch (Exception ex) { Log.Error("[ILDump] " + t.Name + "." + methodName + " failed: " + ex.GetType().Name + ": " + ex.Message); } } private static string DescribeVal(object? v) { //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) if (v == null) { return "null"; } Object val = (Object)((v is Object) ? v : null); if (val != null) { if (!(val == (Object)null)) { return v.GetType().Name + ":" + val.name; } return "null(destroyed)"; } Type type = v.GetType(); if (v is Vector3 val2) { return ((Vector3)(ref val2)).ToString("F1"); } if (type.IsPrimitive || v is string || type.IsEnum) { return v.ToString(); } if (v is ICollection collection) { return $"{type.Name}[{collection.Count}]"; } return type.Name; } private static void DumpFields(string label, object obj, string[]? filter) { int num = 0; Type type = obj.GetType(); while (type != null && type != typeof(object) && type != typeof(MonoBehaviour)) { FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (filter == null || NameHas(fieldInfo.Name, filter)) { string text = ""; try { text = DescribeVal(fieldInfo.GetValue(obj)); } catch { text = ""; } Log.Info("[AiDump] " + label + "(" + type.Name + ")." + fieldInfo.Name + " : " + fieldInfo.FieldType.Name + " = " + text); if (++num > 140) { Log.Info("[AiDump] ...(field dump truncated)"); return; } } } type = type.BaseType; } } private static void DumpMethods(Type t, string[] keywords) { MethodInfo[] methods = t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.DeclaringType == typeof(object)) && !methodInfo.IsSpecialName && NameHas(methodInfo.Name, keywords)) { string text = string.Join(", ", from p in methodInfo.GetParameters() select p.ParameterType.Name + " " + p.Name); Log.Info("[AiDump] method " + methodInfo.DeclaringType?.Name + "." + methodInfo.Name + "(" + text + ") -> " + methodInfo.ReturnType.Name); } } } private static void AI_GetTarget_Post(object __instance, object __result) { try { NetGameplayProbeManager.ReportClientEnemyPuppetAiTargetResult(__instance, __result, "AiAgent.GetTarget"); if (Cfg.EnableAiTargetProbe.Value) { string agentId = ID(__instance); string newTargetId = ID(__result); if (ReverseProbeState.HasAiTargetChanged(agentId, newTargetId)) { Log.Debug("[AI] GetTarget changed >> agent=" + F(__instance) + " newTarget=" + F(__result)); } } } catch (Exception ex) { Log.Error("[AI.GetTarget] " + ex.Message); } } private static void ApplyUnitManagerPatches(Harmony harmony) { if (Cfg.EnableUnitProbe.Value) { Type type = FindType("PerfectRandom.Sulfur.Core.Units.UnitManager"); if (!(type == null)) { TryPatch(harmony, type, "AddUnit", null, Post("UM_AddUnit_Post")); TryPatch(harmony, type, "OnUnitDeath", Pre("UM_OnUnitDeath_Pre"), null); TryPatch(harmony, type, "GetAllNpcs", null, Post("UM_GetAllNpcs_Post")); } } } private static void UM_AddUnit_Post(object __instance, object unit) { try { string unitCategory = GetUnitCategory(unit); NetGameplayProbeManager.ReportSpawn(unit, "UnitManager.AddUnit", unitCategory); Log.Info("[UnitManager] AddUnit >> " + F(unit)); } catch (Exception ex) { Log.Error("[UnitManager.AddUnit] " + ex.Message); } } private static void UM_OnUnitDeath_Pre(object __instance, object unit) { try { string unitCategory = GetUnitCategory(unit); NetGameplayProbeManager.ReportDeath(unit, "UnitManager.OnUnitDeath", unitCategory); Log.Info("[UnitManager] OnUnitDeath << " + F(unit)); } catch (Exception ex) { Log.Error("[UnitManager.OnUnitDeath] " + ex.Message); } } private static void UM_GetAllNpcs_Post(object __instance, bool includeDead, object __result) { try { Log.Debug($"[UnitManager] GetAllNpcs >> includeDead={includeDead} result={__result}"); } catch (Exception ex) { Log.Error("[UnitManager.GetAllNpcs] " + ex.Message); } } private static void ApplyLootManagerPatches(Harmony harmony) { if (Cfg.EnableLootProbe.Value) { Type type = FindType("PerfectRandom.Sulfur.Core.Items.LootManager"); if (!(type == null)) { TryPatch(harmony, type, "RegisterLootDropped", Pre("LM_RegisterLootDropped_Pre"), null); TryPatch(harmony, type, "OnNewLevel", Pre("LM_OnNewLevel_Pre"), null); TryPatch(harmony, type, "ClearOnNewLevel", Pre("LM_ClearOnNewLevel_Pre"), null); PatchAllOverloads(harmony, type, "SpawnGlobalLoot", Pre("LM_SpawnGlobalLoot_Pre")); PatchAllOverloads(harmony, type, "SpawnLootFrom", Pre("LM_SpawnLootFrom_Pre")); } } } private static void LM_RegisterLootDropped_Pre(object __instance, object item) { try { if (Cfg.EnableLootRegisterProbe.Value) { string text = ItemName(item); ReverseProbeSummary.IncrementLootSpawn(); if (Cfg.CompactLootLogs.Value) { ReverseProbeSummary.AddLootRegisteredBurst(text); } if (!Cfg.CompactLootLogs.Value || Cfg.EnableVerboseLootProbe.Value) { Log.Info("[LootManager] RegisterLootDropped << item=" + text + " raw=" + F(item)); } } } catch (Exception ex) { Log.Error("[LootManager.RegisterLootDropped] " + ex.Message); } } private static void LM_OnNewLevel_Pre(object __instance) { try { Log.Info("[LootManager] OnNewLevel <<"); } catch (Exception ex) { Log.Error("[LootManager.OnNewLevel] " + ex.Message); } } private static void LM_ClearOnNewLevel_Pre(object __instance) { try { ReverseProbeKnownObjects.ClearPickups(); Log.Info("[LootManager] ClearOnNewLevel <<"); } catch (Exception ex) { Log.Error("[LootManager.ClearOnNewLevel] " + ex.Message); } } private static void LM_SpawnGlobalLoot_Pre(object __instance) { try { if (Cfg.EnableLootSpawnProbe.Value) { ReverseProbeSummary.IncrementLootSpawn(); if (Cfg.CompactLootLogs.Value) { ReverseProbeSummary.AddLootSpawnedBurst(); } if (!Cfg.CompactLootLogs.Value || Cfg.EnableVerboseLootProbe.Value) { Log.Info("[LootManager] SpawnGlobalLoot << (overload triggered)"); } } } catch (Exception ex) { Log.Error("[LootManager.SpawnGlobalLoot] " + ex.Message); } } private static void LM_SpawnLootFrom_Pre(object __instance) { try { if (Cfg.EnableLootSpawnProbe.Value) { ReverseProbeSummary.IncrementLootSpawn(); if (Cfg.CompactLootLogs.Value) { ReverseProbeSummary.AddLootSpawnedBurst(); } if (!Cfg.CompactLootLogs.Value || Cfg.EnableVerboseLootProbe.Value) { Log.Info("[LootManager] SpawnLootFrom << (overload triggered)"); } } } catch (Exception ex) { Log.Error("[LootManager.SpawnLootFrom] " + ex.Message); } } private static void ApplyInteractionManagerPatches(Harmony harmony) { if (Cfg.EnablePickupProbe.Value) { Type type = FindType("PerfectRandom.Sulfur.Core.InteractionManager"); if (!(type == null)) { TryPatch(harmony, type, "ExecutePickup", Pre("IM_ExecutePickup_Pre"), Post("IM_ExecutePickup_Post")); TryPatch(harmony, type, "RemovePickup", Pre("IM_RemovePickup_Pre"), null); TryPatch(harmony, type, "SpawnPickup", Pre("IM_SpawnPickup_Pre"), Post("IM_SpawnPickup_Post")); } } } private static void IM_ExecutePickup_Pre(object __instance, object pickup) { try { if (Cfg.EnablePickupExecuteProbe.Value) { string instanceId = ID(pickup); string pickupItemNameOrFallback = ReverseProbeKnownObjects.GetPickupItemNameOrFallback(instanceId, F(pickup)); ReverseProbeSummary.IncrementPickupExecute(); if (Cfg.CompactPickupLogs.Value) { ReverseProbeSummary.AddPickupExecutedBurst(pickupItemNameOrFallback); } ReverseProbeKnownObjects.RegisterPickupExecuted(instanceId); if (!Cfg.CompactPickupLogs.Value || Cfg.EnableVerbosePickupProbe.Value) { Log.Info("[InteractionManager] ExecutePickup << item=" + pickupItemNameOrFallback + " pickup=" + F(pickup)); } } } catch (Exception ex) { Log.Error("[InteractionManager.ExecutePickup] " + ex.Message); } } private static void IM_ExecutePickup_Post(object __instance, object pickup) { try { if (Cfg.EnableVerbosePickupProbe.Value) { Log.Info("[InteractionManager] ExecutePickup >>"); } } catch (Exception ex) { Log.Error("[InteractionManager.ExecutePickup Post] " + ex.Message); } } private static void IM_RemovePickup_Pre(object __instance, object pickup) { try { if (Cfg.EnablePickupSpawnProbe.Value) { string instanceId = ID(pickup); string pickupItemNameOrFallback = ReverseProbeKnownObjects.GetPickupItemNameOrFallback(instanceId, F(pickup)); ReverseProbeKnownObjects.RegisterPickupRemoved(instanceId); if (Cfg.CompactPickupLogs.Value) { ReverseProbeSummary.AddPickupRemovedBurst(); } if (!Cfg.CompactPickupLogs.Value || Cfg.EnableVerbosePickupProbe.Value) { Log.Info("[InteractionManager] RemovePickup << item=" + pickupItemNameOrFallback + " pickup=" + F(pickup)); } } } catch (Exception ex) { Log.Error("[InteractionManager.RemovePickup] " + ex.Message); } } private static void IM_SpawnPickup_Pre(object __instance, Vector3 position, bool motionTowardsPlayer, object item, ref string __state) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) try { __state = ItemName(item); if (Cfg.EnablePickupSpawnProbe.Value && Cfg.EnableVerbosePickupProbe.Value) { Log.Info($"[InteractionManager] SpawnPickup << pos={V(position)} motion={motionTowardsPlayer} item={__state} raw={F(item)}"); } } catch (Exception ex) { __state = ""; Log.Error("[InteractionManager.SpawnPickup] " + ex.Message); } } private static void IM_SpawnPickup_Post(object __instance, object __result, string __state) { try { if (Cfg.EnablePickupSpawnProbe.Value) { string text = (string.IsNullOrWhiteSpace(__state) ? "" : __state); ReverseProbeSummary.IncrementPickupSpawn(); ReverseProbeKnownObjects.RegisterPickupSpawned(ID(__result), F(__result), text); if (Cfg.CompactPickupLogs.Value) { ReverseProbeSummary.AddPickupSpawnedBurst(text); } if (!Cfg.CompactPickupLogs.Value || Cfg.EnableVerbosePickupProbe.Value) { Log.Info("[InteractionManager] SpawnPickup >> item=" + text + " pickup=" + F(__result)); } } } catch (Exception ex) { Log.Error("[InteractionManager.SpawnPickup Post] " + ex.Message); } } private static void ApplyInventoryItemPatches(Harmony harmony) { if (Cfg.EnableLootProbe.Value) { Type type = FindType("PerfectRandom.Sulfur.Core.Items.InventoryItem"); if (!(type == null)) { TryPatch(harmony, type, "GetSerialized", null, Post("II_GetSerialized_Post")); TryPatch(harmony, type, "DropFromPlayer", Pre("II_DropFromPlayer_Pre"), null); TryPatch(harmony, type, "DestroyFromInventory", Pre("II_DestroyFromInventory_Pre"), null); TryPatch(harmony, type, "TryMoveToPlayerInventory", Pre("II_TryMoveToPlayer_Pre"), null); TryPatch(harmony, type, "TransferOwnership", Pre("II_TransferOwnership_Pre"), null); PatchAllOverloads(harmony, type, "Setup", null, Post("II_Setup_Post")); } } } private static void II_GetSerialized_Post(object __instance, object __result) { try { ReverseProbeSummary.IncrementInventoryGetSerialized(); if (Cfg.EnableInventorySerializationProbe.Value) { Log.Debug($"[InventoryItem] GetSerialized >> {F(__instance)} result={__result}"); } } catch (Exception ex) { Log.Error("[InventoryItem.GetSerialized] " + ex.Message); } } private static void II_DropFromPlayer_Pre(object __instance) { try { ReverseProbeSummary.IncrementInventoryDrop(); if (Cfg.EnableInventoryDropProbe.Value) { Log.Info("[InventoryItem] DropFromPlayer << " + F(__instance)); } } catch (Exception ex) { Log.Error("[InventoryItem.DropFromPlayer] " + ex.Message); } } private static void II_DestroyFromInventory_Pre(object __instance) { try { ReverseProbeSummary.IncrementInventoryDestroy(); if (Cfg.EnableInventoryDestroyProbe.Value) { Log.Info("[InventoryItem] DestroyFromInventory << " + F(__instance)); } } catch (Exception ex) { Log.Error("[InventoryItem.DestroyFromInventory] " + ex.Message); } } private static void II_TryMoveToPlayer_Pre(object __instance) { try { ReverseProbeSummary.IncrementInventoryMoveToPlayer(); if (Cfg.EnableInventoryTransferProbe.Value) { Log.Info("[InventoryItem] TryMoveToPlayerInventory << " + F(__instance)); } } catch (Exception ex) { Log.Error("[InventoryItem.TryMoveToPlayerInventory] " + ex.Message); } } private static void II_TransferOwnership_Pre(object __instance, object unit, bool isTransaction) { try { ReverseProbeSummary.IncrementInventoryTransfer(); if (Cfg.EnableInventoryTransferProbe.Value) { Log.Info($"[InventoryItem] TransferOwnership << {F(__instance)} target={F(unit)} transaction={isTransaction}"); } } catch (Exception ex) { Log.Error("[InventoryItem.TransferOwnership] " + ex.Message); } } private static void II_Setup_Post(object __instance) { try { ReverseProbeSummary.IncrementInventorySetup(); if (Cfg.EnableVerboseInventoryProbe.Value) { Log.Info("[InventoryItem] Setup >> " + F(__instance)); } } catch (Exception ex) { Log.Error("[InventoryItem.Setup] " + ex.Message); } } private static void ApplyNextLevelTriggerPatches(Harmony harmony) { Type type = FindType("PerfectRandom.Sulfur.Core.LevelGeneration.NextLevelTrigger"); if (!(type == null)) { TryPatch(harmony, type, "OnTriggerEnter", Pre("NLT_OnTriggerEnter_Pre"), null); if (Cfg.EnableLevelProbe.Value) { TryPatch(harmony, type, "MakeTransition", Pre("NLT_MakeTransition_Pre"), null); } } } private static void NLT_MakeTransition_Pre(object __instance) { try { Log.Info("[NextLevelTrigger] MakeTransition <<"); } catch (Exception ex) { Log.Error("[NextLevelTrigger.MakeTransition] " + ex.Message); } } private static bool NLT_OnTriggerEnter_Pre(object __instance, Collider collider) { try { if (RemotePlayerRegistryManager.IsGhostCollider(collider)) { return false; } if (Cfg.EnableLevelProbe.Value) { Log.Info("[NextLevelTrigger] OnTriggerEnter << collider=" + ((collider != null) ? ((Object)collider).name : null)); } } catch (Exception ex) { Log.Error("[NextLevelTrigger.OnTriggerEnter] " + ex.Message); } return true; } private static void ApplyLevelGenPatches(Harmony harmony) { if (Cfg.EnableLevelProbe.Value) { Type type = FindType("LevelGeneration.SpawnPlayerNode"); if (type != null) { TryPatch(harmony, type, "Execute", Pre("SPNode_Execute_Pre"), Post("SPNode_Execute_Post")); TryPatch(harmony, type, "SpawnPlayer", Pre("SPNode_SpawnPlayer_Pre"), Post("SPNode_SpawnPlayer_Post")); } Type type2 = FindType("LevelGeneration.SpawnEnemiesNode"); if (type2 != null) { TryPatch(harmony, type2, "Execute", Pre("SENode_Execute_Pre"), Post("SENode_Execute_Post")); TryPatch(harmony, type2, "CreateAndRegisterEnemy", Pre("SENode_CreateEnemy_Pre"), Post("SENode_CreateEnemy_Post")); } Type type3 = FindType("LevelGeneration.FinalizeAndMutateUnitsNode"); if (type3 != null) { TryPatch(harmony, type3, "Execute", Pre("FMNode_Execute_Pre"), Post("FMNode_Execute_Post")); TryPatch(harmony, type3, "RegisterAndSpawnUnit", Pre("FMNode_RegSpawn_Pre"), Post("FMNode_RegSpawn_Post")); } Type type4 = FindType("LevelGeneration.SetupLootNode"); if (type4 != null) { TryPatch(harmony, type4, "Execute", Pre("SLNode_Execute_Pre"), Post("SLNode_Execute_Post")); } } } private static void SPNode_Execute_Pre(object __instance) { try { ReverseProbeSummary.IncrementLevelGen(); Log.Info("[LevelGen] SpawnPlayerNode.Execute <<"); } catch (Exception ex) { Log.Error("[LevelGen.SpawnPlayerNode.Execute] " + ex.Message); } } private static void SPNode_Execute_Post(object __instance) { try { NetLevelSeed.ReportObservedGameManagerSeed("LevelGeneration.SpawnPlayerNode.Execute"); Log.Info("[LevelGen] SpawnPlayerNode.Execute >>"); } catch (Exception ex) { Log.Error("[LevelGen.SpawnPlayerNode.Execute Post] " + ex.Message); } } private static void SPNode_SpawnPlayer_Pre(object __instance, object prefab, int playerIndex, Rect viewport) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) try { Log.Info($"[LevelGen] SpawnPlayer << playerIndex={playerIndex} viewport={viewport}"); } catch (Exception ex) { Log.Error("[LevelGen.SpawnPlayer] " + ex.Message); } } private static void SPNode_SpawnPlayer_Post(object __instance) { try { Log.Info("[LevelGen] SpawnPlayer >>"); } catch (Exception ex) { Log.Error("[LevelGen.SpawnPlayer Post] " + ex.Message); } } private static void SENode_Execute_Pre(object __instance) { try { ReverseProbeSummary.IncrementLevelGen(); Log.Info("[LevelGen] SpawnEnemiesNode.Execute <<"); } catch (Exception ex) { Log.Error("[LevelGen.SpawnEnemiesNode.Execute] " + ex.Message); } } private static void SENode_Execute_Post(object __instance) { try { NetLevelSeed.ReportObservedGameManagerSeed("LevelGeneration.SpawnEnemiesNode.Execute"); Log.Info("[LevelGen] SpawnEnemiesNode.Execute >>"); } catch (Exception ex) { Log.Error("[LevelGen.SpawnEnemiesNode.Execute Post] " + ex.Message); } } private static void SENode_CreateEnemy_Pre(object __instance, Transform unitRoot, Vector3 spawnPosition) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) try { Log.Info("[LevelGen] CreateAndRegisterEnemy << pos=" + V(spawnPosition)); } catch (Exception ex) { Log.Error("[LevelGen.CreateAndRegisterEnemy] " + ex.Message); } } private static void SENode_CreateEnemy_Post(object __instance, object __result) { try { NetGameplayProbeManager.ReportSpawn(__result, "SpawnEnemiesNode.CreateAndRegisterEnemy", GetUnitCategory(__result)); Log.Info("[LevelGen] CreateAndRegisterEnemy >> " + F(__result)); } catch (Exception ex) { Log.Error("[LevelGen.CreateAndRegisterEnemy Post] " + ex.Message); } } private static void FMNode_Execute_Pre(object __instance) { try { ReverseProbeSummary.IncrementLevelGen(); Log.Info("[LevelGen] FinalizeAndMutateUnitsNode.Execute <<"); } catch (Exception ex) { Log.Error("[LevelGen.FinalizeAndMutateUnitsNode.Execute] " + ex.Message); } } private static void FMNode_Execute_Post(object __instance) { try { NetLevelSeed.ReportObservedGameManagerSeed("LevelGeneration.FinalizeAndMutateUnitsNode.Execute"); Log.Info("[LevelGen] FinalizeAndMutateUnitsNode.Execute >>"); } catch (Exception ex) { Log.Error("[LevelGen.FinalizeAndMutateUnitsNode.Execute Post] " + ex.Message); } } private static void FMNode_RegSpawn_Pre(object __instance, object npc, object inRoom) { try { NetGameplayProbeManager.ReportSpawn(npc, "FinalizeAndMutateUnitsNode.RegisterAndSpawnUnit", GetUnitCategory(npc)); Log.Info("[LevelGen] RegisterAndSpawnUnit << npc=" + F(npc)); } catch (Exception ex) { Log.Error("[LevelGen.RegisterAndSpawnUnit] " + ex.Message); } } private static void FMNode_RegSpawn_Post(object __instance) { try { Log.Info("[LevelGen] RegisterAndSpawnUnit >>"); } catch (Exception ex) { Log.Error("[LevelGen.RegisterAndSpawnUnit Post] " + ex.Message); } } private static void SLNode_Execute_Pre(object __instance) { try { ReverseProbeSummary.IncrementLevelGen(); Log.Info("[LevelGen] SetupLootNode.Execute <<"); } catch (Exception ex) { Log.Error("[LevelGen.SetupLootNode.Execute] " + ex.Message); } } private static void SLNode_Execute_Post(object __instance) { try { NetLevelSeed.ReportObservedGameManagerSeed("LevelGeneration.SetupLootNode.Execute"); Log.Info("[LevelGen] SetupLootNode.Execute >>"); } catch (Exception ex) { Log.Error("[LevelGen.SetupLootNode.Execute Post] " + ex.Message); } } } internal static class WeaponFirePatches { public static void Apply(Harmony harmony) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown try { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Weapons.Weapon"); if (type == null) { Plugin.Log.Warn("[WeaponFire] Weapon type not found — player weapon sync disabled."); return; } MethodInfo methodInfo = AccessTools.Method(type, "Shoot", (Type[])null, (Type[])null); if (methodInfo == null) { Plugin.Log.Warn("[WeaponFire] Weapon.Shoot not found — player weapon sync disabled."); return; } HarmonyMethod val = new HarmonyMethod(typeof(WeaponFirePatches).GetMethod("Weapon_Shoot_Post", BindingFlags.Static | BindingFlags.NonPublic)); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.Info("[WeaponFire] Patched Weapon.Shoot (player weapon bullet sync)."); } catch (Exception ex) { Plugin.Log.Error("[WeaponFire] Apply failed: " + ex.Message); } } private static void Weapon_Shoot_Post(Weapon __instance) { PlayerWeaponFireManager.CaptureLocalFire(__instance); } } } namespace SULFURTogether.Networking { internal static class CoopConnection { private static NetService _service; private static string _lastSignature; private static (string Address, int Port, string Label)? _pendingSteamJoinTarget; public static bool SteamHostingEnabled { get; private set; } public static NetService Service => _service; public static bool IsRunning => _service != null; public static NetMode CurrentMode { get; private set; } public static void Initialize() { Apply(NetMode.Off, "startup"); } public static void Apply(NetMode mode, string reason) { string text = BuildSignature(mode); if (_service != null && mode == CurrentMode && text == _lastSignature) { Plugin.Log.Info($"[CoopConn] already running mode={mode} with unchanged settings — no restart (reason={reason})."); return; } if (_service != null) { try { _service.Stop(); } catch (Exception ex) { Plugin.Log.Warn("[CoopConn] stop during apply failed: " + ex.Message); } NetRunStateBridge.Attach(null); NetGameplaySyncBridge.Attach(null); _service = null; Plugin.Log.Info($"[CoopConn] stopped previous networking (was {CurrentMode})."); } if (SteamRelayBridge.IsHostingActive) { SteamRelayBridge.StopHosting(); } bool flag = mode == NetMode.Client && _pendingSteamJoinTarget.HasValue; if (SteamRelayBridge.IsClientActive && !flag) { SteamRelayBridge.StopJoining(); } if (SteamHostingEnabled) { SteamRichPresenceJoin.StopAdvertisingHosting(); } SteamHostingEnabled = false; CurrentMode = NetMode.Off; _lastSignature = null; switch (mode) { case NetMode.Off: NetConnectFeedback.Clear(); Plugin.Log.Info("[CoopConn] networking Off (reason=" + reason + ")."); return; case NetMode.Client: NetConnectFeedback.BeginAttempt(); break; default: NetConnectFeedback.Clear(); break; } try { NetService netService = new NetService(); NetRunStateBridge.Attach(netService); NetGameplaySyncBridge.Attach(netService); if (mode == NetMode.Client && _pendingSteamJoinTarget.HasValue) { (string, int, string) value = _pendingSteamJoinTarget.Value; netService.SetConnectTarget(value.Item1, value.Item2, value.Item3); } _pendingSteamJoinTarget = null; netService.Start(mode); if (mode == NetMode.Host) { NetRunStateBridge.PrimeServiceFromCache(netService); } NetRunStateBridge.PrimeLocalPlayer(netService); _service = netService; CurrentMode = mode; _lastSignature = text; Plugin.Log.Info($"[CoopConn] networking started mode={mode} (reason={reason})."); } catch (Exception ex2) { NetRunStateBridge.Attach(null); NetGameplaySyncBridge.Attach(null); _service = null; CurrentMode = NetMode.Off; _lastSignature = null; NetConnectFeedback.ReportError(CoopLoc.Format("connect.error.couldNotStart", "Could not start networking ({type}). The port may be in use or LiteNetLib is missing.", ("type", ex2.GetType().Name))); Plugin.Log.Error($"[CoopConn] failed to start mode={mode} — LiteNetLib missing or socket error. ({ex2.GetType().Name}: {ex2.Message})"); } } public static void ApplySteamClient(CSteamID hostId, string reason) { //IL_0005: 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) NetConnectFeedback.BeginAttempt(); if (!SteamRelayBridge.StartJoining(hostId, out int localPort, out string error)) { NetConnectFeedback.ReportError(error ?? CoopLoc.Get("connect.error.steamStart", "Could not start the Steam connection.")); return; } _pendingSteamJoinTarget = ("127.0.0.1", localPort, $"Steam ({hostId.m_SteamID})"); Apply(NetMode.Client, reason); if (CurrentMode != NetMode.Client) { SteamRelayBridge.StopJoining(); } } public static bool EnableSteamHosting(string reason) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) if (CurrentMode != NetMode.Host || _service == null) { return false; } if (!SteamHostingEnabled) { if (!SteamRelayBridge.StartHosting(BuildHostGamePort())) { NetConnectFeedback.ReportError(CoopLoc.Get("connect.error.steamUnavailable", "Steam is not available.")); return false; } SteamHostingEnabled = true; Plugin.Log.Info("[CoopConn] Steam hosting enabled (reason=" + reason + ")."); } if (SteamNetworkingSupport.TryGetLocalSteamId(out var id)) { SteamRichPresenceJoin.AdvertiseHosting(id); } return true; } public static void DisableSteamHosting(string reason) { if (SteamHostingEnabled) { SteamRelayBridge.StopHosting(); SteamRichPresenceJoin.StopAdvertisingHosting(); SteamHostingEnabled = false; Plugin.Log.Info("[CoopConn] Steam hosting disabled (reason=" + reason + ")."); } } private static int BuildHostGamePort() { try { return Plugin.Cfg.HostPort.Value; } catch { return 9050; } } private static string BuildSignature(NetMode mode) { if (mode == NetMode.Off) { return "off"; } try { return string.Join("|", mode.ToString(), Plugin.Cfg.HostAddress.Value, Plugin.Cfg.HostPort.Value.ToString(), Plugin.Cfg.MaxPlayers.Value.ToString(), Plugin.Cfg.ConnectionKey.Value, Plugin.Cfg.PlayerName.Value); } catch { return mode.ToString(); } } public static void Stop(string reason) { Apply(NetMode.Off, reason); } public static void Tick() { _service?.Tick(); SteamRelayBridge.Tick(); } public static void FixedTick() { _service?.FixedTick(); } } internal static class CoopMenu { private static bool _resolved; private static Type _optionsScreenType; private static Type _pauseMenuType; private static MethodInfo _optionsHide; private static PropertyInfo _optionsIsShown; private static MethodInfo _resumeGame; private static PropertyInfo _gmInstance; private static void Resolve() { if (_resolved) { return; } try { _optionsScreenType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.OptionsScreen"); if (_optionsScreenType != null) { _optionsHide = AccessTools.Method(_optionsScreenType, "Hide", (Type[])null, (Type[])null); _optionsIsShown = AccessTools.Property(_optionsScreenType, "IsShown"); } _pauseMenuType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.UI.PauseMenu"); Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); if (type != null) { _gmInstance = AccessTools.Property(type, "Instance"); _resumeGame = AccessTools.Method(type, "ResumeGame", (Type[])null, (Type[])null); } if (_resumeGame != null && _gmInstance != null) { _resolved = true; } } catch { } } public static bool IsOpen() { Resolve(); if (!IsOptionsShown()) { return IsPauseMenuActive(); } return true; } public static void CloseIfOpen(string reason) { try { Resolve(); Object optionsScreen; bool flag = IsOptionsShown(out optionsScreen); bool flag2 = IsPauseMenuActive(); if (!flag && !flag2) { return; } bool flag3 = false; if (flag && _optionsHide != null && optionsScreen != (Object)null) { _optionsHide.Invoke(optionsScreen, null); flag3 = true; } if (_resumeGame != null && _gmInstance != null) { object value = _gmInstance.GetValue(null, null); if (value != null) { _resumeGame.Invoke(value, null); flag3 = true; } } if (flag3) { Plugin.Log?.Info("[CoopUi] closed in-game menu (reason=" + reason + ")."); } } catch (Exception ex) { Plugin.Log?.Warn("[CoopUi] close menu failed: " + ex.Message); } } private static bool IsOptionsShown() { Object optionsScreen; return IsOptionsShown(out optionsScreen); } private static bool IsOptionsShown(out Object optionsScreen) { optionsScreen = null; if (_optionsScreenType == null || _optionsIsShown == null) { return false; } try { optionsScreen = Object.FindObjectOfType(_optionsScreenType); if (optionsScreen == (Object)null) { return false; } object value = _optionsIsShown.GetValue(optionsScreen, null); bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { return false; } } private static bool IsPauseMenuActive() { if (_pauseMenuType == null) { return false; } try { Object val = Object.FindObjectOfType(_pauseMenuType); if (val == (Object)null) { return false; } Behaviour val2 = (Behaviour)(object)((val is Behaviour) ? val : null); return val2 == null || val2.isActiveAndEnabled; } catch { return false; } } } internal static class NetClientJoinFlow { public enum ClientJoinMode { ManualOnly, AutoJoinFromHubOnly, AskBeforeLeavingLocalRun, ForceAutoJoin } public enum ClientLocalJoinState { HubOrMenu, CombatLevel, TransitionOrLoading, Unknown } public enum JoinDecision { Allow, SkipManualOnly, SkipPreserveCombat, SkipPreserveTransition, SkipUnknown, ForceWarn } public static int JoinFlowAutoAllowedFromHub; public static int JoinFlowSkippedManualOnly; public static int JoinFlowSkippedPreserveCombatRun; public static int JoinFlowSkippedPreserveTransition; public static int JoinFlowSkippedUnknown; public static int JoinFlowForceAutoJoinWarning; public static int JoinFlowManualAvailable; public static int JoinFlowLocalLoadPreserved; public static int JoinFlowSessionJoined; public static int JoinFlowSessionLeft; private static ClientLocalJoinState _lastLoggedState = (ClientLocalJoinState)(-1); private static string _lastLoggedSceneKey = ""; private static bool _hubReadyLogged; public static bool SessionJoinedHost { get; private set; } public static string JoinedTargetKey { get; private set; } = ""; public static string LastSkipReason { get; private set; } = ""; public static ClientJoinMode Mode { get { string value; try { value = Plugin.Cfg.ClientJoinMode.Value; } catch { return ClientJoinMode.AutoJoinFromHubOnly; } return (value ?? "").Trim() switch { "ManualOnly" => ClientJoinMode.ManualOnly, "AskBeforeLeavingLocalRun" => ClientJoinMode.AskBeforeLeavingLocalRun, "ForceAutoJoin" => ClientJoinMode.ForceAutoJoin, _ => ClientJoinMode.AutoJoinFromHubOnly, }; } } public static void Reset() { SessionJoinedHost = false; JoinedTargetKey = ""; LastSkipReason = ""; _lastLoggedState = (ClientLocalJoinState)(-1); _lastLoggedSceneKey = ""; _hubReadyLogged = false; } public static void NoteHubJoinReady() { if (!SessionJoinedHost && !_hubReadyLogged && ClassifyLocalJoinState() == ClientLocalJoinState.HubOrMenu) { _hubReadyLogged = true; Plugin.Log.Info("[JoinFlow] hub join ready; waiting host combat request"); } } public static ClientLocalJoinState ClassifyLocalJoinState() { string chapter; int level; string loadingMode; return ClassifyLocalJoinState(out chapter, out level, out loadingMode); } public static ClientLocalJoinState ClassifyLocalJoinState(out string chapter, out int level, out string loadingMode) { chapter = ""; level = -1; loadingMode = ""; if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || !state.HasLevel) { MaybeLogClassification(ClientLocalJoinState.Unknown, chapter, level, loadingMode); return ClientLocalJoinState.Unknown; } chapter = state.ChapterName; level = state.LevelIndex; loadingMode = state.LoadingMode; ClientLocalJoinState num = Classify(state.ChapterName, state.LoadingMode, state.GameState); MaybeLogClassification(num, chapter, level, loadingMode); return num; } private static ClientLocalJoinState Classify(string chapter, string loadingMode, string gameState) { string text = (chapter ?? "").ToLowerInvariant(); string text2 = (loadingMode ?? "").ToLowerInvariant(); string text3 = (gameState ?? "").ToLowerInvariant(); if (text2.Contains("menu")) { return ClientLocalJoinState.HubOrMenu; } if (text3.Contains("loading") || text3.Contains("cinematic") || text3.Contains("transition") || text2.Contains("loading") || text2.Contains("transition") || text2.Contains("nextlevel") || text2.Contains("next_level")) { return ClientLocalJoinState.TransitionOrLoading; } if (text.Contains("hub") || text.Contains("church") || text.Contains("safezone") || text.Contains("safe_zone") || text.Contains("hideout") || text.Contains("town") || text.Contains("vendor")) { return ClientLocalJoinState.HubOrMenu; } if (text.Contains("act_") || text.Contains("act0") || text.Contains("caves") || text.Contains("combat") || text.Contains("dungeon") || text.Contains("level")) { return ClientLocalJoinState.CombatLevel; } return ClientLocalJoinState.Unknown; } private static void MaybeLogClassification(ClientLocalJoinState state, string chapter, int level, string loadingMode) { string text = $"{chapter}:{level}:{loadingMode}"; if (state != _lastLoggedState || !(text == _lastLoggedSceneKey)) { _lastLoggedState = state; _lastLoggedSceneKey = text; string text2 = ((state == ClientLocalJoinState.Unknown) ? " action=manual-only" : ""); Plugin.Log.Info($"[JoinFlow] local state classified state={state} chapter={chapter} level={level} loadingMode={loadingMode}{text2}"); } } public static JoinDecision DecideAutoTakeover(ClientLocalJoinState state) { if (NetLinkState.ClientLinked) { return JoinDecision.Allow; } if (SessionJoinedHost) { return JoinDecision.Allow; } return Mode switch { ClientJoinMode.ManualOnly => JoinDecision.SkipManualOnly, ClientJoinMode.ForceAutoJoin => JoinDecision.ForceWarn, _ => state switch { ClientLocalJoinState.HubOrMenu => JoinDecision.Allow, ClientLocalJoinState.CombatLevel => JoinDecision.SkipPreserveCombat, ClientLocalJoinState.TransitionOrLoading => JoinDecision.SkipPreserveTransition, _ => JoinDecision.SkipUnknown, }, }; } public static bool TryAuthorizeAutoFollow(string targetKey, string graph) { if (!NetLinkState.ClientLinked) { LastSkipReason = "not-linked"; JoinFlowManualAvailable++; Plugin.Log.Info("[JoinFlow] auto-follow skipped reason=not-linked (联机状态 off) target=" + targetKey); return false; } ClientLocalJoinState clientLocalJoinState = ClassifyLocalJoinState(); JoinDecision joinDecision = DecideAutoTakeover(clientLocalJoinState); ClientJoinMode mode = Mode; switch (joinDecision) { case JoinDecision.Allow: LastSkipReason = ""; if (SessionJoinedHost) { Plugin.Log.Info($"[JoinFlow] auto-follow allowed (in session) mode={mode} state={clientLocalJoinState} target={targetKey} graph={Graph(graph)}"); } else { JoinFlowAutoAllowedFromHub++; Plugin.Log.Info($"[JoinFlow] auto-join allowed mode={mode} state={clientLocalJoinState} target={targetKey} graph={Graph(graph)}"); } return true; case JoinDecision.ForceWarn: LastSkipReason = ""; JoinFlowForceAutoJoinWarning++; Plugin.Log.Warn($"[JoinFlow] auto-join FORCED mode=ForceAutoJoin state={clientLocalJoinState} target={targetKey} graph={Graph(graph)} (test-only; may interrupt local run)"); return true; case JoinDecision.SkipManualOnly: JoinFlowSkippedManualOnly++; LogSkip(mode, clientLocalJoinState, "manual-only", targetKey); return false; case JoinDecision.SkipPreserveCombat: JoinFlowSkippedPreserveCombatRun++; LogSkip(mode, clientLocalJoinState, "preserve-local-run", targetKey); return false; case JoinDecision.SkipPreserveTransition: JoinFlowSkippedPreserveTransition++; LogSkip(mode, clientLocalJoinState, "preserve-transition-save", targetKey); return false; default: JoinFlowSkippedUnknown++; LogSkip(mode, clientLocalJoinState, "unknown-state-manual-only", targetKey); return false; } } private static void LogSkip(ClientJoinMode mode, ClientLocalJoinState state, string reason, string targetKey) { LastSkipReason = reason; Plugin.Log.Info($"[JoinFlow] auto-join skipped mode={mode} state={state} reason={reason}"); JoinFlowManualAvailable++; Plugin.Log.Info("[JoinFlow] manual follow available target=" + targetKey); } public static bool ShouldGateOwnCombatLoad(out string reason) { if (NetLinkState.ClientLinked) { reason = "linked"; return true; } JoinFlowLocalLoadPreserved++; reason = "not-linked-preserve-local-run"; return false; } public static void MarkJoinedHost(string targetKey, string graph, string seed) { if (!SessionJoinedHost || !(JoinedTargetKey == targetKey)) { bool sessionJoinedHost = SessionJoinedHost; SessionJoinedHost = true; JoinedTargetKey = targetKey; if (!sessionJoinedHost) { JoinFlowSessionJoined++; Plugin.Log.Info("[JoinFlow] session joined host target=" + targetKey + " graph=" + Graph(graph) + " seed=" + (string.IsNullOrEmpty(seed) ? "?" : seed)); } } } public static void LeaveSession(string reason) { if (SessionJoinedHost) { SessionJoinedHost = false; JoinedTargetKey = ""; _hubReadyLogged = false; JoinFlowSessionLeft++; Plugin.Log.Info("[JoinFlow] session left reason=" + reason); } } public static string FormatStatus(string latestHostTarget) { ClientLocalJoinState clientLocalJoinState = ClassifyLocalJoinState(); string text = (string.IsNullOrWhiteSpace(latestHostTarget) ? "none" : latestHostTarget); string text2 = (string.IsNullOrWhiteSpace(LastSkipReason) ? "none" : LastSkipReason); return $"JoinFlow mode={Mode} state={clientLocalJoinState} joined={SessionJoinedHost} latestHost={text} skipped={text2}"; } public static string FormatCounters() { return $"autoAllowedHub={JoinFlowAutoAllowedFromHub} skipManual={JoinFlowSkippedManualOnly} " + $"skipCombat={JoinFlowSkippedPreserveCombatRun} skipTransition={JoinFlowSkippedPreserveTransition} " + $"skipUnknown={JoinFlowSkippedUnknown} forceWarn={JoinFlowForceAutoJoinWarning} " + $"manualAvail={JoinFlowManualAvailable} localLoadPreserved={JoinFlowLocalLoadPreserved} " + $"joined={JoinFlowSessionJoined} left={JoinFlowSessionLeft}"; } private static string Graph(string g) { if (!string.IsNullOrEmpty(g)) { return g; } return "?"; } } internal static class NetClientLoadGate { private enum State { Idle, Waiting, HostDrivenInProgress } private enum LevelKind { Hub, Menu, Combat, Unknown } private enum PendingKind { None, Combat, DeathRespawn } private static readonly object _lock = new object(); private static NetMode _mode = NetMode.Off; private static bool _connectedToHost; private static bool _reentry; private static State _state = State.Idle; private static PendingKind _pendingKind = PendingKind.None; private static string _pendingChapter = ""; private static int _pendingLevel = -1; private static string _pendingLoadingMode = ""; private static string _pendingSpawn = ""; private static float _waitingSince; private static bool _timedOut; private static bool _fallbackLogged; private static float _nextRetryTime; private static bool _missingSeedLogged; private static float _nextRequestTime; private static int _requestAttempt; private static float _nextRelayTime; private static int _relayAttempt; private static bool _clientLedReloadInPlace; private static float _clientLedEpoch; private static bool _clientLedReloadFromHasSeed; private static int _clientLedReloadFromSeed; private static bool _clientInitiated; private static Action? _clientInitiatedFallback; private static NetHostSceneRequest? _latestHostRequest; private static float _latestHostRequestAt; private static float _deathFollowEpoch; public static int ClientLoadGateInterceptedClientLevelComplete; public static int ClientLoadGateClientLevelCompleteFollowed; public static int ClientLoadGateClientLevelCompleteLocalFallback; public static int ClientLoadGateInterceptedCombat; public static int ClientLoadGateInterceptedHubDeath; public static int ClientLoadGateHubDeathFollowed; public static int ClientLoadGateHubDeathFallback; public static int ClientLoadGateBypassedHub; public static int ClientLoadGateBypassedMenu; public static int ClientLoadGateBypassedHostMode; public static int ClientLoadGateBypassedSinglePlayer; public static int ClientLoadGateBypassedDisabled; public static int ClientLoadGateBypassedReentry; public static int ClientLoadGateBypassedUnknownLevelKind; public static int ClientLoadGateBypassedPreserveLocalRun; public static int ClientLoadGateHostInputRequestSent; public static int ClientLoadGateHostInputRequestRetry; public static int ClientLoadGateHostInputReceived; public static int ClientLoadGateStartedHostDrivenLoad; public static int ClientLoadGateApplyFailed; public static int ClientLoadGateMissingSeed; public static int ClientLoadGateTimeouts; public static int AutoFollowRequestReceived; public static int AutoFollowStarted; public static int AutoFollowSkippedDisabled; public static int AutoFollowSkippedHub; public static int AutoFollowSkippedAlreadyMatching; public static int AutoFollowSkippedReentry; public static int AutoFollowSkippedDeathRespawn; public static int AutoFollowFailed; public static int SessionReturnHubRequestReceived; public static int SessionReturnAutoFollowStarted; public static int SessionReturnSkippedNotJoined; public static int SessionReturnCompleted; public static int SessionReturnFailed; private static string _lastAutoFollowKey = ""; private static float _nextAutoFollowTime; public static bool IsHostDrivenLoadInProgress { get { lock (_lock) { return _reentry; } } } public static NetMode CurrentMode { get { lock (_lock) { return _mode; } } } public static void SetMode(NetMode mode) { lock (_lock) { if (_mode != mode) { _mode = mode; ResetLocked(); } } } public static void UpdateNetState(NetMode mode, bool connectedToHost) { lock (_lock) { _mode = mode; _connectedToHost = connectedToHost; } } public static void Reset() { lock (_lock) { ResetLocked(); } } public static void NoteLocalDeathRespawnArmed() { lock (_lock) { _deathFollowEpoch = Now(); } } private static void ResetLocked() { _state = State.Idle; _pendingKind = PendingKind.None; _reentry = false; _timedOut = false; _fallbackLogged = false; _missingSeedLogged = false; _pendingChapter = ""; _pendingLevel = -1; _pendingLoadingMode = ""; _pendingSpawn = ""; _nextRequestTime = 0f; _requestAttempt = 0; _latestHostRequest = null; _lastAutoFollowKey = ""; _nextAutoFollowTime = 0f; _clientInitiated = false; _clientInitiatedFallback = null; _clientLedReloadInPlace = false; _clientLedEpoch = 0f; _clientLedReloadFromHasSeed = false; _clientLedReloadFromSeed = 0; if (NetLoadingFade.Active) { NetLoadingFade.Hide(); } } public static void BeginHostDrivenLoad() { lock (_lock) { _reentry = true; } } public static void EndHostDrivenLoad() { lock (_lock) { _reentry = false; } } public static bool ShouldInterceptGoToLevel(string chapter, int levelIndex, string loadingMode, string spawn, Action? localFallback = null) { lock (_lock) { if (_reentry) { ClientLoadGateBypassedReentry++; LogBypass(chapter, levelIndex, loadingMode, "reentry"); return false; } if (_mode == NetMode.Off) { ClientLoadGateBypassedSinglePlayer++; return false; } if (_mode != NetMode.Client) { ClientLoadGateBypassedHostMode++; return false; } bool flag; try { flag = Plugin.Cfg.ClientWaitHostGenerationInputBeforeFirstLoad.Value; } catch { flag = false; } if (!flag) { ClientLoadGateBypassedDisabled++; LogBypass(chapter, levelIndex, loadingMode, "disabled"); return false; } if (!NetLinkState.ClientLinked) { ClientLoadGateBypassedPreserveLocalRun++; LogBypass(chapter, levelIndex, loadingMode, "not-linked-preserve-local-run"); return false; } LevelKind levelKind = ClassifyLevelKind(chapter, loadingMode, levelIndex); if (levelKind == LevelKind.Hub && IsDeathRespawn(loadingMode) && GateDeathRespawnEnabled()) { _pendingChapter = (string.IsNullOrWhiteSpace(chapter) ? "" : chapter); _pendingLevel = levelIndex; _pendingLoadingMode = loadingMode ?? ""; _pendingSpawn = spawn ?? ""; _waitingSince = Now(); _timedOut = false; _fallbackLogged = false; _missingSeedLogged = false; _nextRetryTime = 0f; _nextRequestTime = 0f; _requestAttempt = 0; _pendingKind = PendingKind.DeathRespawn; _state = State.Waiting; _clientInitiated = false; _clientInitiatedFallback = null; ClientLoadGateInterceptedHubDeath++; Plugin.Log.Info($"[ClientLoadGate] intercepted death respawn chapter={_pendingChapter} reason=waiting-host-destination hostDestReady={HasFreshHostDestinationLocked()} timeout={SafeHubDeathTimeout():F0}s"); return true; } switch (levelKind) { case LevelKind.Menu: ClientLoadGateBypassedMenu++; LogBypass(chapter, levelIndex, loadingMode, "menu"); return false; case LevelKind.Unknown: ClientLoadGateBypassedUnknownLevelKind++; break; } string kindLabel = levelKind switch { LevelKind.Unknown => "unknown", LevelKind.Hub => "hub", _ => "combat", }; BeginClientLedTransitionLocked(chapter, levelIndex, loadingMode ?? "", spawn ?? "", localFallback, kindLabel); return true; } } private static void BeginClientLedTransitionLocked(string chapter, int level, string loadingMode, string spawn, Action? localFallback, string kindLabel) { _pendingChapter = (string.IsNullOrWhiteSpace(chapter) ? "" : chapter); _pendingLevel = level; _pendingLoadingMode = loadingMode ?? ""; _pendingSpawn = spawn ?? ""; _waitingSince = Now(); _timedOut = false; _fallbackLogged = false; _missingSeedLogged = false; _nextRetryTime = 0f; _nextRequestTime = 0f; _requestAttempt = 0; _nextRelayTime = 0f; _relayAttempt = 0; _pendingKind = PendingKind.Combat; _state = State.Waiting; _clientInitiated = true; _clientInitiatedFallback = localFallback; _clientLedEpoch = Now(); _clientLedReloadFromHasSeed = false; _clientLedReloadFromSeed = 0; NetRunState state; bool flag = NetRunStateBridge.TryGetLocalRunState(out state) && state.HasLevel && NetSceneName.SameScene(state.ChapterName, state.LevelIndex, chapter, level); _clientLedReloadInPlace = ReloadInPlaceRelayEnabled() && flag; if (_clientLedReloadInPlace && state.HasLevelSeed) { _clientLedReloadFromHasSeed = true; _clientLedReloadFromSeed = state.LevelSeed; } ClientLoadGateInterceptedCombat++; Plugin.Log.Info($"[ClientLoadGate] intercepted client-led {kindLabel} transition target={_pendingChapter}:{_pendingLevel} loadingMode={_pendingLoadingMode} spawn={_pendingSpawn} reason=waiting-host-lead reloadInPlace={_clientLedReloadInPlace} connected={_connectedToHost}"); } private static void LogBypass(string chapter, int levelIndex, string loadingMode, string reason) { Plugin.Log.Info($"[ClientLoadGate] bypass GoToLevel chapter={chapter} level={levelIndex} loadingMode={loadingMode} reason={reason}"); } public static bool TryBeginClientLevelCompleteRelay(string chapter, int level, Action localFallback, out string reason) { reason = ""; lock (_lock) { if (_reentry) { reason = "reentry"; return false; } if (_mode != NetMode.Client) { reason = "not-client"; return false; } bool flag; try { flag = Plugin.Cfg.AllowClientInitiatedLevelLoad.Value; } catch { flag = false; } bool flag2; try { flag2 = Plugin.Cfg.EnableClientTransitionRelay.Value; } catch { flag2 = false; } if (!flag) { reason = "disabled"; return false; } if (!flag2) { reason = "relay-disabled"; return false; } if (!NetLinkState.ClientLinked) { reason = "not-linked"; return false; } if (_state != State.Idle) { reason = "gate-busy-" + _state; return false; } if (ClassifyLevelKind(chapter, "Normal", level) == LevelKind.Menu) { reason = "menu-target"; return false; } BeginClientLedTransitionLocked(chapter, level, "Normal", "", localFallback, "level-complete"); ClientLoadGateInterceptedClientLevelComplete++; reason = "waiting-host-lead"; Plugin.Log.Info($"[ClientLoadGate] client level-complete relay armed target={_pendingChapter}:{_pendingLevel} timeout={SafeClientInitiatedTimeout():F0}s"); return true; } } public static void OnHostGenerationInput(NetHostSceneRequest request) { if (request != null) { lock (_lock) { _latestHostRequest = request; _latestHostRequestAt = Now(); ClientLoadGateHostInputReceived++; int num = (request.HasUsedSets ? request.UsedChunksThisRun.Count : 0); int num2 = (request.HasUsedSets ? request.UsedEventsThisRun.Count : 0); int num3 = (request.HasUsedSets ? request.UsedEventsThisEnvironment.Count : 0); Plugin.Log.Info(string.Format("[ClientLoadGate] host generation input received graph={0} seed={1} level={2} usedChunks={3} usedRun={4} usedEnv={5} kind={6} state={7}", GraphName(request), request.HasLevelSeed ? request.LevelSeed.ToString() : "?", request.LevelIndex, num, num2, num3, ClassifyLevelKind(request.ChapterName, request.LoadingMode, request.LevelIndex), _state)); } Tick(); TryAutoFollow(request); } } public static bool IsCombatTarget(string chapter, string loadingMode, int levelIndex) { return ClassifyLevelKind(chapter, loadingMode, levelIndex) == LevelKind.Combat; } public static bool IsHubOrMenuTarget(string chapter, string loadingMode, int levelIndex) { LevelKind levelKind = ClassifyLevelKind(chapter, loadingMode, levelIndex); if (levelKind != LevelKind.Hub) { return levelKind == LevelKind.Menu; } return true; } public static string FormatSessionReturnCounters() { return $"hubRecv={SessionReturnHubRequestReceived} hubStarted={SessionReturnAutoFollowStarted} " + $"hubDone={SessionReturnCompleted} hubSkippedNotJoined={SessionReturnSkippedNotJoined} hubFailed={SessionReturnFailed}"; } private static void TryAutoFollow(NetHostSceneRequest req) { if (req == null) { return; } bool flag; try { flag = Plugin.Cfg.EnableAutoFollowHostSceneRequest.Value; } catch { flag = false; } lock (_lock) { if (_mode != NetMode.Client) { return; } } AutoFollowRequestReceived++; string text = req.TargetSceneKey(); string text2 = GraphName(req); string text3 = (req.HasLevelSeed ? req.LevelSeed.ToString() : "?"); if (!flag) { AutoFollowSkippedDisabled++; Plugin.Log.Info("[AutoFollow] skipped reason=disabled target=" + text + " graph=" + text2); return; } if (!req.AutoLoadAllowed || !req.HasTargetScene) { AutoFollowSkippedHub++; Plugin.Log.Info($"[AutoFollow] skipped reason=not-authorized target={text} graph={text2} autoLoad={req.AutoLoadAllowed}"); return; } bool flag2 = IsHubOrMenuTarget(req.ChapterName, req.LoadingMode, req.LevelIndex) || NetSceneClassify.IsHubOrSafeZoneGraph(req.GraphName) || NetSceneClassify.IsHubOrSafeZoneChapter(req.ChapterName); bool flag3 = !flag2 && IsCombatTarget(req.ChapterName, req.LoadingMode, req.LevelIndex); bool clientLinked = NetLinkState.ClientLinked; bool flag4 = false; if (!flag3) { if (!flag2) { AutoFollowSkippedHub++; Plugin.Log.Info("[AutoFollow] skipped reason=non-combat-non-hub target=" + text + " graph=" + text2); return; } SessionReturnHubRequestReceived++; if (!clientLinked) { SessionReturnSkippedNotJoined++; Plugin.Log.Info("[AutoFollow] skipped target=" + text + " reason=not-joined-hub-request-waiting"); return; } if ((NetSceneClassify.IsHubOrSafeZoneGraph(req.GraphName) || NetSceneClassify.IsHubOrSafeZoneChapter(req.ChapterName)) && !req.HasLevelSeed) { NetSceneFollowDiag.HubAutoFollowSkippedMissingSeed++; NetSceneFollowDiag.HubAutoFollowWaitingFinalized++; Plugin.Log.Info("[AutoFollow] skipped target=" + text + " reason=waiting-finalized-hub-seed graph=" + text2 + " seed=" + text3); Plugin.Log.Info("[JoinFlow] hub return pending finalized seed target=" + text); return; } Plugin.Log.Info("[JoinFlow] session return allowed target=" + text + " reason=host-returned-hub"); flag4 = true; } else if (!req.HasLevelSeed) { AutoFollowSkippedHub++; Plugin.Log.Info("[AutoFollow] skipped reason=no-seed target=" + text + " graph=" + text2); return; } lock (_lock) { if (_reentry) { AutoFollowSkippedReentry++; Plugin.Log.Info("[AutoFollow] skipped reason=reentry target=" + text); return; } if (_state != State.Idle) { Plugin.Log.Info($"[AutoFollow] skipped reason=gate-{_state} (gate path drives) target={text}"); return; } if (text + "|" + (req.HasLevelSeed ? req.LevelSeed.ToString() : "?") + "|" + text2 == _lastAutoFollowKey && Now() < _nextAutoFollowTime) { AutoFollowSkippedAlreadyMatching++; return; } } if (NetRunStateBridge.TryGetLocalRunState(out NetRunState state) && state.HasLevel && NetSceneName.SameScene(state.ChapterName, state.LevelIndex, req.ChapterName, req.LevelIndex)) { bool flag5; if (flag3) { flag5 = !RequireSeedMatch() || (state.HasLevelSeed && req.HasLevelSeed && state.LevelSeed == req.LevelSeed); } else if (req.HasLevelSeed) { flag5 = state.HasLevelSeed && state.LevelSeed == req.LevelSeed; if (!flag5) { NetSceneFollowDiag.HubSeedMismatchReload++; Plugin.Log.Info(string.Format("[AutoFollow] hub seed mismatch; reloading finalized hub target={0} localSeed={1} hostSeed={2}", text, state.HasLevelSeed ? state.LevelSeed.ToString() : "?", req.LevelSeed)); } } else { flag5 = true; } if (flag5 && !string.IsNullOrWhiteSpace(req.GraphName) && !string.IsNullOrWhiteSpace(state.LevelGenerator) && !string.Equals(req.GraphName, state.LevelGenerator, StringComparison.OrdinalIgnoreCase)) { flag5 = false; NetSceneFollowDiag.AutoFollowReloadGraphMismatch++; Plugin.Log.Info("[AutoFollow] graph mismatch; reloading host target requestGraph=" + req.GraphName + " localGraph=" + state.LevelGenerator); } if (flag5) { if (!flag3) { NetSceneFollowDiag.HubSeedMatched++; } AutoFollowSkippedAlreadyMatching++; Plugin.Log.Info("[AutoFollow] skipped reason=already-matching target=" + text + " graph=" + text2); return; } } if (!NetClientJoinFlow.TryAuthorizeAutoFollow(text, text2)) { return; } lock (_lock) { if (_state != State.Idle) { return; } _state = State.HostDrivenInProgress; _lastAutoFollowKey = text + "|" + (req.HasLevelSeed ? req.LevelSeed.ToString() : "?") + "|" + text2; _nextAutoFollowTime = Now() + 5f; } AutoFollowStarted++; if (flag4) { SessionReturnAutoFollowStarted++; NetSceneFollowDiag.HubAutoFollowStartedFinalized++; } string text4 = (flag4 ? "HubOrMenu" : "Combat"); string text5 = (flag4 ? "start-session-return" : "start"); Plugin.Log.Info($"[AutoFollow] received host request target={text} graph={text2} seed={text3} kind={text4} joined={clientLinked} autoLoad=True action={text5}"); Plugin.Log.Info("[AutoFollow] starting host-driven GoToLevel target=" + text + " graph=" + text2 + " seed=" + text3); bool flag6; string result; try { flag6 = NetManualSceneFollower.TryFollow(req, out result); } catch (Exception ex) { flag6 = false; result = ex.GetType().Name + ": " + ex.Message; } lock (_lock) { _state = State.Idle; } if (flag6) { if (flag4) { SessionReturnCompleted++; } Plugin.Log.Info("[AutoFollow] host-driven GoToLevel invoked: " + result); return; } AutoFollowFailed++; if (flag4) { SessionReturnFailed++; } Plugin.Log.Warn("[AutoFollow] host-driven GoToLevel failed: " + result); } private static bool RequireSeedMatch() { try { return Plugin.Cfg.EnableLevelSeedAuthority.Value && Plugin.Cfg.RequireSameLevelSeedForSceneMatch.Value; } catch { return false; } } public static void Tick() { NetHostSceneRequest netHostSceneRequest = null; bool flag = false; bool flag2 = false; Action fallback = null; lock (_lock) { if (_mode != NetMode.Client || _state != State.Waiting) { return; } if (!HasMatchingReleaseRequestLocked()) { float num = Now() - _waitingSince; if (_pendingKind == PendingKind.DeathRespawn) { if (!_timedOut && num >= SafeHubDeathTimeout()) { _timedOut = true; ClientLoadGateTimeouts++; _state = State.HostDrivenInProgress; flag = true; Plugin.Log.Warn("[ClientLoadGate] timeout waiting host destination; falling back to local hub respawn"); } if (!flag) { return; } } else { float num2 = (_clientInitiated ? SafeClientInitiatedTimeout() : SafeTimeout()); if (!_timedOut && num >= num2) { _timedOut = true; ClientLoadGateTimeouts++; if (_clientInitiated) { Plugin.Log.Warn("[ClientLoadGate] timeout waiting host lead for client level-complete; advancing locally"); fallback = _clientInitiatedFallback; _clientInitiatedFallback = null; _clientInitiated = false; _state = State.HostDrivenInProgress; flag2 = true; } else { Plugin.Log.Warn("[ClientLoadGate] timeout waiting host generation input; local combat load still blocked (still requesting)"); if (SafeFallback() && !_fallbackLogged) { _fallbackLogged = true; Plugin.Log.Warn("[ClientLoadGate] timeout fallback allowed; local combat load may diverge"); _state = State.Idle; } } } if (!flag2) { return; } } } else { if (Now() < _nextRetryTime) { return; } netHostSceneRequest = _latestHostRequest; if (_pendingKind == PendingKind.DeathRespawn && netHostSceneRequest != null) { netHostSceneRequest = BuildDeathFollowRequest(netHostSceneRequest); } } } if (flag) { DoHubDeathLocalFallback(); } else if (flag2) { DoClientInitiatedLocalFallback(fallback); } else { TryStartHostDrivenGoToLevel(netHostSceneRequest); } } private static void DoHubDeathLocalFallback() { NetHostSceneRequest request = new NetHostSceneRequest { ChapterName = _pendingChapter, LevelIndex = _pendingLevel, LoadingMode = (string.IsNullOrEmpty(_pendingLoadingMode) ? "Death" : _pendingLoadingMode), SpawnIdentifier = (string.IsNullOrEmpty(_pendingSpawn) ? "Respawn" : _pendingSpawn), HasLevelSeed = false, AutoLoadAllowed = false }; bool flag; string result; try { flag = NetManualSceneFollower.TryFollow(request, out result); } catch (Exception ex) { flag = false; result = ex.GetType().Name + ": " + ex.Message; } lock (_lock) { _state = State.Idle; _pendingKind = PendingKind.None; } ClientLoadGateHubDeathFallback++; Plugin.Log.Warn($"[ClientLoadGate] hub-death local fallback invoked ok={flag}: {result}"); } private static void DoClientInitiatedLocalFallback(Action? fallback) { ClientLoadGateClientLevelCompleteLocalFallback++; bool flag = true; string text = ""; try { fallback?.Invoke(); } catch (Exception ex) { flag = false; text = ex.GetType().Name + ": " + ex.Message; } lock (_lock) { _state = State.Idle; _pendingKind = PendingKind.None; _clientInitiated = false; } if (flag) { Plugin.Log.Warn("[ClientLoadGate] client level-complete local fallback invoked (host did not lead)"); return; } if (NetLoadingFade.Active) { NetLoadingFade.Hide(); } Plugin.Log.Warn("[ClientLoadGate] client level-complete local fallback failed: " + text); } public static bool TryConsumeHostInputRequestDue(out int attempt) { attempt = 0; lock (_lock) { if (_mode != NetMode.Client) { return false; } if (_state != State.Waiting) { return false; } if (HasMatchingReleaseRequestLocked() && Now() >= _nextRetryTime) { return false; } if (Now() < _nextRequestTime) { return false; } _nextRequestTime = Now() + SafeRequestInterval(); _requestAttempt++; attempt = _requestAttempt; if (attempt <= 1) { ClientLoadGateHostInputRequestSent++; } else { ClientLoadGateHostInputRequestRetry++; } Plugin.Log.Info($"[ClientLoadGate] requesting host generation input reason=gate-wait attempt={attempt}"); return true; } } private static NetHostSceneRequest BuildDeathFollowRequest(NetHostSceneRequest hostDest) { bool flag = ClassifyLevelKind(hostDest.ChapterName, hostDest.LoadingMode, hostDest.LevelIndex) == LevelKind.Hub || NetSceneClassify.IsHubOrSafeZoneGraph(hostDest.GraphName) || NetSceneClassify.IsHubOrSafeZoneChapter(hostDest.ChapterName); NetHostSceneRequest netHostSceneRequest = new NetHostSceneRequest { RequestId = hostDest.RequestId, HostPeerId = hostDest.HostPeerId, HostPlayerName = hostDest.HostPlayerName, ChapterName = hostDest.ChapterName, LevelIndex = hostDest.LevelIndex, LoadingMode = ((flag && !string.IsNullOrEmpty(_pendingLoadingMode)) ? _pendingLoadingMode : hostDest.LoadingMode), SpawnIdentifier = ((flag && !string.IsNullOrEmpty(_pendingSpawn)) ? _pendingSpawn : hostDest.SpawnIdentifier), HostGameState = hostDest.HostGameState, HasLevelSeed = hostDest.HasLevelSeed, LevelSeed = hostDest.LevelSeed, LevelGenerator = hostDest.LevelGenerator, GraphName = hostDest.GraphName, GenerationRunId = hostDest.GenerationRunId, HostRevision = hostDest.HostRevision, Reason = "DeathRespawnFollow", AutoLoadAllowed = hostDest.AutoLoadAllowed }; if (hostDest.HasUsedSets) { netHostSceneRequest.SetUsedSets(hostDest.ToUsedSets()); } return netHostSceneRequest; } public static bool TryConsumeTransitionRelayDue(out string chapter, out int level, out string mode, out string spawn, out int attempt) { chapter = ""; level = -1; mode = ""; spawn = ""; attempt = 0; bool flag; try { flag = Plugin.Cfg.EnableClientTransitionRelay.Value; } catch { flag = false; } if (!flag) { return false; } lock (_lock) { if (_mode != NetMode.Client) { return false; } if (_state != State.Waiting) { return false; } if (_pendingKind != PendingKind.Combat) { return false; } if (HasMatchingReleaseRequestLocked()) { return false; } if (Now() < _nextRelayTime) { return false; } _nextRelayTime = Now() + SafeRequestInterval(); _relayAttempt++; attempt = _relayAttempt; chapter = _pendingChapter; level = _pendingLevel; mode = _pendingLoadingMode; spawn = _pendingSpawn; return true; } } private static void TryStartHostDrivenGoToLevel(NetHostSceneRequest req) { lock (_lock) { if (_state != State.Waiting) { return; } if (!req.HasLevelSeed) { ClientLoadGateMissingSeed++; if (!_missingSeedLogged) { _missingSeedLogged = true; Plugin.Log.Warn("[ClientLoadGate] host generation input has no seed; cannot start host-driven load yet"); } _nextRetryTime = Now() + 3f; return; } _state = State.HostDrivenInProgress; } int num = (req.HasUsedSets ? req.UsedChunksThisRun.Count : 0); Plugin.Log.Info($"[ClientLoadGate] starting host-driven GoToLevel chapter={req.ChapterName} level={req.LevelIndex} graph={GraphName(req)} seed={req.LevelSeed} usedChunks={num}"); bool flag; string result; try { flag = NetManualSceneFollower.TryFollow(req, out result); } catch (Exception ex) { flag = false; result = ex.GetType().Name + ": " + ex.Message; } lock (_lock) { if (flag) { ClientLoadGateStartedHostDrivenLoad++; if (_pendingKind == PendingKind.DeathRespawn) { ClientLoadGateHubDeathFollowed++; } if (_clientInitiated) { ClientLoadGateClientLevelCompleteFollowed++; } Plugin.Log.Info("[ClientLoadGate] host-driven GoToLevel invoked: " + result); _state = State.Idle; _pendingKind = PendingKind.None; _clientInitiated = false; _clientInitiatedFallback = null; _lastAutoFollowKey = req.TargetSceneKey() + "|" + (req.HasLevelSeed ? req.LevelSeed.ToString() : "?") + "|" + GraphName(req); _nextAutoFollowTime = Now() + 5f; } else { ClientLoadGateApplyFailed++; Plugin.Log.Warn("[ClientLoadGate] host-driven GoToLevel failed: " + result); _state = State.Waiting; _nextRetryTime = Now() + 3f; } } } private static LevelKind ClassifyLevelKind(string chapter, string loadingMode, int levelIndex) { string text = (chapter ?? "").ToLowerInvariant(); if ((loadingMode ?? "").ToLowerInvariant().Contains("menu")) { return LevelKind.Menu; } if (text.Contains("hub") || text.Contains("safezone") || text.Contains("safe_zone") || text.Contains("hideout") || text.Contains("town") || text.Contains("vendor")) { return LevelKind.Hub; } if (text.Contains("act_") || text.Contains("act0") || text.Contains("caves") || text.Contains("combat") || text.Contains("dungeon") || text.Contains("level")) { return LevelKind.Combat; } return LevelKind.Unknown; } private static bool HasCombatRequestLocked() { if (_latestHostRequest == null || !_latestHostRequest.HasTargetScene) { return false; } return ClassifyLevelKind(_latestHostRequest.ChapterName, _latestHostRequest.LoadingMode, _latestHostRequest.LevelIndex) == LevelKind.Combat; } private static bool HasCombatReleaseRequestLocked() { NetHostSceneRequest latestHostRequest = _latestHostRequest; if (latestHostRequest == null || !latestHostRequest.HasTargetScene) { return false; } if (ClassifyLevelKind(latestHostRequest.ChapterName, latestHostRequest.LoadingMode, latestHostRequest.LevelIndex) != LevelKind.Combat) { return false; } if (NetRunStateBridge.TryGetLocalRunState(out NetRunState state) && state.HasLevel && NetSceneName.SameScene(state.ChapterName, state.LevelIndex, latestHostRequest.ChapterName, latestHostRequest.LevelIndex)) { return false; } return true; } private static bool HasMatchingReleaseRequestLocked() { if (_pendingKind == PendingKind.DeathRespawn) { return HasFreshHostDestinationLocked(); } if (_clientInitiated) { return HasClientLedReleaseRequestLocked(); } return HasCombatReleaseRequestLocked(); } private static bool HasClientLedReleaseRequestLocked() { NetHostSceneRequest latestHostRequest = _latestHostRequest; if (latestHostRequest == null || !latestHostRequest.HasTargetScene || !latestHostRequest.HasLevelSeed) { return false; } if (!NetSceneName.SameScene(latestHostRequest.ChapterName, latestHostRequest.LevelIndex, _pendingChapter, _pendingLevel)) { return false; } if (_clientLedReloadInPlace) { if (_latestHostRequestAt < _clientLedEpoch) { return false; } if (_clientLedReloadFromHasSeed && latestHostRequest.LevelSeed == _clientLedReloadFromSeed) { return false; } } return true; } private static bool HasFreshHostDestinationLocked() { NetHostSceneRequest latestHostRequest = _latestHostRequest; if (latestHostRequest == null || !latestHostRequest.HasTargetScene || !latestHostRequest.HasLevelSeed) { return false; } return _latestHostRequestAt >= _deathFollowEpoch; } private static bool IsDeathRespawn(string loadingMode) { return (loadingMode ?? "").ToLowerInvariant().Contains("death"); } private static bool GateDeathRespawnEnabled() { try { return Plugin.Cfg.ClientGateDeathRespawnUntilHostHub.Value; } catch { return false; } } private static bool ReloadInPlaceRelayEnabled() { try { return Plugin.Cfg.EnableClientReloadInPlaceRelay.Value && Plugin.Cfg.EnableClientTransitionRelay.Value; } catch { return false; } } private static float SafeHubDeathTimeout() { try { return Mathf.Max(1f, Plugin.Cfg.ClientGateDeathRespawnTimeoutSeconds.Value); } catch { return 8f; } } private static float SafeClientInitiatedTimeout() { try { return Mathf.Max(1f, Plugin.Cfg.ClientInitiatedLoadTimeoutSeconds.Value); } catch { return 15f; } } public static string FormatCounters() { return $"interceptedCombat={ClientLoadGateInterceptedCombat} clientComplete[intercepted={ClientLoadGateInterceptedClientLevelComplete},followed={ClientLoadGateClientLevelCompleteFollowed},localFallback={ClientLoadGateClientLevelCompleteLocalFallback}] interceptedDeath={ClientLoadGateInterceptedHubDeath} deathFollow[followed={ClientLoadGateHubDeathFollowed},fallback={ClientLoadGateHubDeathFallback}] reqSent={ClientLoadGateHostInputRequestSent} reqRetry={ClientLoadGateHostInputRequestRetry} " + $"hostInput={ClientLoadGateHostInputReceived} started={ClientLoadGateStartedHostDrivenLoad} applyFailed={ClientLoadGateApplyFailed} " + $"missingSeed={ClientLoadGateMissingSeed} timeouts={ClientLoadGateTimeouts} bypass[hub={ClientLoadGateBypassedHub},menu={ClientLoadGateBypassedMenu}," + $"host={ClientLoadGateBypassedHostMode},sp={ClientLoadGateBypassedSinglePlayer},disabled={ClientLoadGateBypassedDisabled},reentry={ClientLoadGateBypassedReentry},unknown={ClientLoadGateBypassedUnknownLevelKind},preserveLocal={ClientLoadGateBypassedPreserveLocalRun}] " + $"autoFollow[recv={AutoFollowRequestReceived},started={AutoFollowStarted},disabled={AutoFollowSkippedDisabled},hub={AutoFollowSkippedHub},match={AutoFollowSkippedAlreadyMatching},reentry={AutoFollowSkippedReentry},deathRespawn={AutoFollowSkippedDeathRespawn},failed={AutoFollowFailed}]"; } private static float Now() { return Time.realtimeSinceStartup; } private static float SafeTimeout() { try { return Mathf.Max(1f, Plugin.Cfg.ClientLoadGateTimeoutSeconds.Value); } catch { return 30f; } } private static float SafeRequestInterval() { try { return Mathf.Max(0.5f, Plugin.Cfg.ClientLoadGateRequestIntervalSeconds.Value); } catch { return 2f; } } private static bool SafeFallback() { try { return Plugin.Cfg.ClientLoadGateAllowFallbackAfterTimeout.Value; } catch { return false; } } private static string GraphName(NetHostSceneRequest req) { if (!string.IsNullOrEmpty(req.GraphName)) { return req.GraphName; } return "?"; } } internal static class NetConfig { public static NetMode GetMode() { return CoopConnection.CurrentMode; } } internal static class NetConnectFeedback { public static string LastError { get; private set; } = ""; public static bool Connecting { get; private set; } public static void BeginAttempt() { LastError = ""; Connecting = true; } public static void ReportConnected() { LastError = ""; Connecting = false; } public static void ReportError(string reason) { LastError = (string.IsNullOrWhiteSpace(reason) ? "Connection failed" : reason); Connecting = false; Plugin.Log?.Info("[CoopConn] join feedback: " + LastError); } public static void Clear() { LastError = ""; Connecting = false; } } public enum NetConnectionState : byte { Connecting = 1, Handshaking, Connected, Disconnected, Rejected } internal static class NetDbg { public static string Ctx(string? msg = null, int? seq = null, int? hostIdx = null, int? rev = null, float? sendAt = null) { string value = ((NetConfig.GetMode() == NetMode.Host) ? "Host" : "Client"); float realtimeSinceStartup = Time.realtimeSinceStartup; int frameCount = Time.frameCount; StringBuilder stringBuilder = new StringBuilder(64); stringBuilder.Append("[t="); stringBuilder.Append(realtimeSinceStartup.ToString("F3")); stringBuilder.Append("s f="); stringBuilder.Append(frameCount); stringBuilder.Append(" role="); stringBuilder.Append(value); if (msg != null) { stringBuilder.Append(" msg="); stringBuilder.Append(msg); } if (seq.HasValue) { stringBuilder.Append(" seq="); stringBuilder.Append(seq.Value); } if (hostIdx.HasValue) { stringBuilder.Append(" hostIdx="); stringBuilder.Append(hostIdx.Value); } if (rev.HasValue) { stringBuilder.Append(" rev="); stringBuilder.Append(rev.Value); } if (sendAt.HasValue) { float num = (realtimeSinceStartup - sendAt.Value) * 1000f; stringBuilder.Append(" ageMs="); stringBuilder.Append(num.ToString("F0")); } stringBuilder.Append(']'); return stringBuilder.ToString(); } public static string AgeMs(float sentAt) { return $"ageMs={(Time.realtimeSinceStartup - sentAt) * 1000f:F0}"; } } internal static class NetGameManagerUsedSets { public sealed class ApplyOutcome { public bool ChunksApplied; public bool EventsRunApplied; public bool EventsEnvApplied; public int ChunksBefore; public int ChunksAfter; public int EventsRunBefore; public int EventsRunAfter; public int EventsEnvBefore; public int EventsEnvAfter; public readonly List Errors = new List(); public bool AnyApplied { get { if (!ChunksApplied && !EventsRunApplied) { return EventsEnvApplied; } return true; } } public bool AnyFailed => Errors.Count > 0; } public const string ChunksField = "usedChunksThisRun"; public const string EventsRunField = "usedUniqueEventThisRun"; public const string EventsEnvField = "usedUniqueEventThisEnvironment"; private const BindingFlags FieldFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy; private static bool _elementTypesDumped; public static NetHostUsedSets? LastLocalSnapshot { get; private set; } public static int LastSnapshotLevelIndex { get; private set; } = -1; public static string LastSnapshotChapter { get; private set; } = ""; public static bool TryRead(object gameManager, out NetHostUsedSets sets, out string error) { sets = new NetHostUsedSets(); error = ""; if (gameManager == null) { error = "gameManager is null"; return false; } try { Type type = gameManager.GetType(); sets.UsedChunksThisRun = ReadKeys(gameManager, type, "usedChunksThisRun"); sets.UsedEventsThisRun = ReadKeys(gameManager, type, "usedUniqueEventThisRun"); sets.UsedEventsThisEnvironment = ReadKeys(gameManager, type, "usedUniqueEventThisEnvironment"); sets.Captured = true; return true; } catch (Exception ex) { error = ex.GetType().Name + ": " + ex.Message; return false; } } public static NetHostUsedSets? CaptureLocal(object gameManager, string chapter, int levelIndex) { if (!TryRead(gameManager, out NetHostUsedSets sets, out string error)) { if (Plugin.Cfg.LogUsedSetsTrace.Value) { Plugin.Log.Warn("[UsedSetsTrace] capture failed: " + error); } return null; } LastLocalSnapshot = sets; LastSnapshotChapter = (string.IsNullOrWhiteSpace(chapter) ? "" : chapter); LastSnapshotLevelIndex = levelIndex; return sets; } private static List ReadKeys(object owner, Type ownerType, string fieldName) { List list = new List(); FieldInfo fieldInfo = FindField(ownerType, fieldName); if (fieldInfo == null) { return list; } object value; try { value = fieldInfo.GetValue(owner); } catch { return list; } if (value is IEnumerable enumerable && !(value is string)) { foreach (object item in enumerable) { list.Add(KeyOf(item)); } } return list; } private static string KeyOf(object? item) { if (item == null) { return ""; } if (item is string result) { return result; } Object val = (Object)((item is Object) ? item : null); if (val != null) { if (!(val != (Object)null)) { return ""; } return val.name; } return item.ToString(); } public static ApplyOutcome Apply(object gameManager, NetHostUsedSets hostSets) { ApplyOutcome outcome = new ApplyOutcome(); if (gameManager == null) { outcome.Errors.Add("gameManager is null"); return outcome; } if (hostSets == null) { outcome.Errors.Add("hostSets is null"); return outcome; } Type type = gameManager.GetType(); if (!_elementTypesDumped) { _elementTypesDumped = true; DumpElementType(gameManager, type, "usedChunksThisRun"); DumpElementType(gameManager, type, "usedUniqueEventThisRun"); DumpElementType(gameManager, type, "usedUniqueEventThisEnvironment"); } ApplyOne(gameManager, type, "usedChunksThisRun", hostSets.UsedChunksThisRun, outcome, delegate(bool v) { outcome.ChunksApplied = v; }, delegate(int b, int a) { outcome.ChunksBefore = b; outcome.ChunksAfter = a; }); ApplyOne(gameManager, type, "usedUniqueEventThisRun", hostSets.UsedEventsThisRun, outcome, delegate(bool v) { outcome.EventsRunApplied = v; }, delegate(int b, int a) { outcome.EventsRunBefore = b; outcome.EventsRunAfter = a; }); ApplyOne(gameManager, type, "usedUniqueEventThisEnvironment", hostSets.UsedEventsThisEnvironment, outcome, delegate(bool v) { outcome.EventsEnvApplied = v; }, delegate(int b, int a) { outcome.EventsEnvBefore = b; outcome.EventsEnvAfter = a; }); return outcome; } private static void ApplyOne(object owner, Type ownerType, string fieldName, List hostKeys, ApplyOutcome outcome, Action setApplied, Action setCounts) { FieldInfo fieldInfo = FindField(ownerType, fieldName); if (fieldInfo == null) { outcome.Errors.Add(fieldName + ":field-not-found"); setApplied(obj: false); return; } object value; try { value = fieldInfo.GetValue(owner); } catch (Exception ex) { outcome.Errors.Add(fieldName + ":get-failed:" + ex.Message); setApplied(obj: false); return; } if (value == null) { outcome.Errors.Add(fieldName + ":collection-null"); setApplied(obj: false); return; } int num = CountOf(value); Type type = ElementType(value); if (type != typeof(string)) { outcome.Errors.Add(fieldName + ":non-string-element(" + (type?.Name ?? "?") + ")"); setApplied(obj: false); setCounts(num, num); return; } MethodInfo method = value.GetType().GetMethod("Clear", Type.EmptyTypes); MethodInfo method2 = value.GetType().GetMethod("Add", new Type[1] { typeof(string) }); if (method == null || method2 == null) { outcome.Errors.Add(fieldName + ":no-clear-or-add"); setApplied(obj: false); setCounts(num, num); return; } try { method.Invoke(value, null); if (hostKeys != null) { foreach (string hostKey in hostKeys) { method2.Invoke(value, new object[1] { hostKey ?? "" }); } } int arg = CountOf(value); setApplied(obj: true); setCounts(num, arg); } catch (Exception ex2) { outcome.Errors.Add(fieldName + ":write-failed:" + ex2.Message); setApplied(obj: false); setCounts(num, CountOf(value)); } } private static void DumpElementType(object owner, Type ownerType, string fieldName) { try { FieldInfo fieldInfo = FindField(ownerType, fieldName); if (fieldInfo == null) { Plugin.Log.Info("[UsedSetsTrace] field " + fieldName + " NOT FOUND on " + ownerType.Name); return; } object value = fieldInfo.GetValue(owner); string name = fieldInfo.FieldType.Name; string text = ((value == null) ? "?" : (ElementType(value)?.Name ?? "?")); Plugin.Log.Info("[UsedSetsTrace] field " + fieldName + " declared=" + name + " runtime=" + (value?.GetType().Name ?? "") + " element=" + text); } catch (Exception ex) { Plugin.Log.Warn("[UsedSetsTrace] DumpElementType " + fieldName + " failed: " + ex.Message); } } private static FieldInfo? FindField(Type type, string name) { Type type2 = type; while (type2 != null) { FieldInfo field = type2.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); if (field != null) { return field; } type2 = type2.BaseType; } return null; } private static int CountOf(object collection) { if (collection is ICollection collection2) { return collection2.Count; } int num = 0; if (collection is IEnumerable enumerable) { foreach (object item in enumerable) { _ = item; num++; } } return num; } private static Type? ElementType(object collection) { Type type = collection.GetType(); if (type.IsGenericType) { Type[] genericArguments = type.GetGenericArguments(); if (genericArguments.Length == 1) { return genericArguments[0]; } } Type[] interfaces = type.GetInterfaces(); foreach (Type type2 in interfaces) { if (type2.IsGenericType) { Type genericTypeDefinition = type2.GetGenericTypeDefinition(); if (genericTypeDefinition == typeof(ICollection<>) || genericTypeDefinition == typeof(IEnumerable<>)) { return type2.GetGenericArguments()[0]; } } } return null; } } internal static class NetGenerationInputCapture { private sealed class PendingTransition { public string EnvName = ""; public int EnvId = -1; public int LevelIndex = -1; public string LoadingMode = ""; public string Spawn = ""; public string Source = ""; public int Revision; public float CapturedAt; } private static readonly object _lock = new object(); private static readonly Dictionary _byRunKey = new Dictionary(); private static readonly Dictionary _bySeedLevel = new Dictionary(); private static NetGenerationInputSnapshot? _pending; private static string _lastFinalizedKey = ""; public static int Captured; public static int Finalized; public static int MatchedOnSend; public static int FallbackOnSend; public static int RunStateSnapshotAppliedHost; public static int RunStateSnapshotAppliedClient; public static int RunStateSnapshotOverrideUsed; public static int RunStateSnapshotRevisionBumped; private static PendingTransition? _pendingTransition; public static int TransitionCapturedGoToLevel; public static int TransitionCapturedSwitchLevel; public static int TransitionOverrideOldPending; public static int TransitionUsedForFinalize; public static int TransitionFinalizeWithoutTransition; private static bool _argsResolved; private static int _seedArgIndex = -1; private static int _graphArgIndex = -1; private static int _levelArgIndex = -1; private static int _loadingModeArgIndex = -1; private static int _spawnArgIndex = -1; public static NetGenerationInputSnapshot? LastSnapshot => LastFinalizedSnapshot; public static NetGenerationInputSnapshot? LastFinalizedSnapshot { get; private set; } public static string FormatTransitionCounters() { return $"capturedGoTo={TransitionCapturedGoToLevel} capturedSwitch={TransitionCapturedSwitchLevel} " + $"overrideOldPending={TransitionOverrideOldPending} usedForFinalize={TransitionUsedForFinalize} " + $"finalizeWithoutTransition={TransitionFinalizeWithoutTransition}"; } public static void CaptureLevelTransition(string envName, int envId, int levelIndex, string loadingMode, string spawn, string source) { int num2; lock (_lock) { bool num = _pendingTransition != null; num2 = (_pendingTransition?.Revision ?? 0) + 1; _pendingTransition = new PendingTransition { EnvName = (string.IsNullOrWhiteSpace(envName) ? "" : envName.Trim()), EnvId = envId, LevelIndex = levelIndex, LoadingMode = (loadingMode ?? ""), Spawn = (spawn ?? ""), Source = (source ?? ""), Revision = num2, CapturedAt = Time.realtimeSinceStartup }; if (num) { TransitionOverrideOldPending++; } if ((source ?? "").IndexOf("Switch", StringComparison.OrdinalIgnoreCase) >= 0) { TransitionCapturedSwitchLevel++; } else { TransitionCapturedGoToLevel++; } } Plugin.Log.Info(string.Format("[LevelTransition] captured source={0} target={1}:{2} envId={3} loadingMode={4} spawn={5} rev={6}", source, string.IsNullOrWhiteSpace(envName) ? "" : envName, levelIndex, envId, loadingMode, spawn, num2)); } public static string FormatRunStateSyncCounters() { return $"appliedHost={RunStateSnapshotAppliedHost} appliedClient={RunStateSnapshotAppliedClient} " + $"overrideUsed={RunStateSnapshotOverrideUsed} revisionBumped={RunStateSnapshotRevisionBumped}"; } private static void StoreFinalized(NetGenerationInputSnapshot snapshot) { if (snapshot == null) { return; } lock (_lock) { LastFinalizedSnapshot = snapshot; _byRunKey[snapshot.RunKey()] = snapshot; _bySeedLevel[snapshot.SeedLevelKey()] = snapshot; Finalized++; } } public static bool TryGet(bool hasSeed, int seed, int levelIndex, out NetGenerationInputSnapshot snapshot) { lock (_lock) { if (_bySeedLevel.TryGetValue(NetGenerationInputSnapshot.SeedLevelKey(hasSeed, seed, levelIndex), out NetGenerationInputSnapshot value)) { snapshot = value; return true; } } snapshot = null; return false; } public static void Clear() { lock (_lock) { _byRunKey.Clear(); _bySeedLevel.Clear(); _pending = null; _pendingTransition = null; _lastFinalizedKey = ""; LastFinalizedSnapshot = null; } } public static void CaptureFromStartLevelRoutineGraph(object gameManager, object[] args, MethodBase method) { try { if (!_argsResolved) { ResolveArgIndices(method); } NetGenerationInputSnapshot netGenerationInputSnapshot = new NetGenerationInputSnapshot { Finalized = false }; if (NetGameManagerUsedSets.TryRead(gameManager, out NetHostUsedSets sets, out string error)) { netGenerationInputSnapshot.UsedSets = sets; } else if (Plugin.Cfg.LogUsedSetsTrace.Value) { Plugin.Log.Warn("[GenerationInputSnapshot] used-set read failed: " + error); } int seed; string detail; if (_seedArgIndex >= 0 && args != null && _seedArgIndex < args.Length && TryToInt(args[_seedArgIndex], out var value) && value != 0) { netGenerationInputSnapshot.SeedCandidate = value; } else if (NetLevelSeed.TryReadCurrentSeed(gameManager, out seed, out detail)) { netGenerationInputSnapshot.SeedCandidate = seed; } if (_levelArgIndex >= 0 && args != null && _levelArgIndex < args.Length && TryToInt(args[_levelArgIndex], out var value2)) { netGenerationInputSnapshot.LevelIndex = value2; } if (_loadingModeArgIndex >= 0 && args != null && _loadingModeArgIndex < args.Length) { netGenerationInputSnapshot.LoadingMode = args[_loadingModeArgIndex]?.ToString() ?? ""; } if (_spawnArgIndex >= 0 && args != null && _spawnArgIndex < args.Length) { netGenerationInputSnapshot.SpawnIdentifier = args[_spawnArgIndex]?.ToString() ?? ""; } netGenerationInputSnapshot.GraphName = ResolveGraphName(args); if (NetRunStateBridge.TryGetLocalRunState(out NetRunState state)) { if (state.HasLevel) { netGenerationInputSnapshot.Chapter = state.ChapterName; if (netGenerationInputSnapshot.LevelIndex < 0) { netGenerationInputSnapshot.LevelIndex = state.LevelIndex; } } netGenerationInputSnapshot.Revision = state.Revision; } lock (_lock) { _pending = netGenerationInputSnapshot; Captured++; } if (Plugin.Cfg.LogUsedSetsTrace.Value) { Plugin.Log.Info($"[GenerationInputSnapshot] pending captured phase=StartLevelRoutineGraphPrefix chapter={netGenerationInputSnapshot.Chapter} level={netGenerationInputSnapshot.LevelIndex} seedCandidate={netGenerationInputSnapshot.SeedCandidate} usedChunks={NetHostUsedSets.Summary(netGenerationInputSnapshot.UsedSets.UsedChunksThisRun)}"); Plugin.Log.Info("[GenerationInputSnapshot] pending usedEventsRun=" + NetHostUsedSets.Summary(netGenerationInputSnapshot.UsedSets.UsedEventsThisRun) + " usedEventsEnv=" + NetHostUsedSets.Summary(netGenerationInputSnapshot.UsedSets.UsedEventsThisEnvironment)); } } catch (Exception ex) { Plugin.Log.Warn("[GenerationInputSnapshot] capture failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void FinalizeFromMakerContext(string graphName, long graphSeed) { try { string text = (graphName ?? "?") + "|" + graphSeed; bool flag; NetGenerationInputSnapshot netGenerationInputSnapshot; lock (_lock) { if (text == _lastFinalizedKey) { return; } _lastFinalizedKey = text; flag = _pending != null; netGenerationInputSnapshot = ((_pending != null) ? _pending.Clone() : new NetGenerationInputSnapshot()); netGenerationInputSnapshot.SeedCandidate = _pending?.SeedCandidate ?? 0; } if (!flag && NetRunStateBridge.TryGetLocalRunState(out NetRunState state)) { if (state.HasLevel) { netGenerationInputSnapshot.Chapter = state.ChapterName; if (netGenerationInputSnapshot.LevelIndex < 0) { netGenerationInputSnapshot.LevelIndex = state.LevelIndex; } } netGenerationInputSnapshot.Revision = state.Revision; } string text2 = graphName ?? ""; if (text2.Length > 0 && text2 != "?") { netGenerationInputSnapshot.GraphName = text2; } netGenerationInputSnapshot.Seed = (int)graphSeed; netGenerationInputSnapshot.HasSeed = true; netGenerationInputSnapshot.Finalized = true; PendingTransition pendingTransition; lock (_lock) { pendingTransition = _pendingTransition; } bool flag2 = false; if (pendingTransition != null && !string.IsNullOrWhiteSpace(pendingTransition.EnvName) && pendingTransition.EnvName != "") { string chapter = netGenerationInputSnapshot.Chapter; int levelIndex = netGenerationInputSnapshot.LevelIndex; netGenerationInputSnapshot.Chapter = pendingTransition.EnvName; if (pendingTransition.LevelIndex >= 0) { netGenerationInputSnapshot.LevelIndex = pendingTransition.LevelIndex; } if (!string.IsNullOrWhiteSpace(pendingTransition.LoadingMode)) { netGenerationInputSnapshot.LoadingMode = pendingTransition.LoadingMode; } if (pendingTransition.Spawn != null) { netGenerationInputSnapshot.SpawnIdentifier = pendingTransition.Spawn; } netGenerationInputSnapshot.FromTransition = true; netGenerationInputSnapshot.TransitionSource = pendingTransition.Source; netGenerationInputSnapshot.TargetEnvId = pendingTransition.EnvId; flag2 = true; TransitionUsedForFinalize++; Plugin.Log.Info(string.Format("[LevelTransition] finalized target={0}:{1} graph={2} seed={3} source={4} envId={5} rev={6} (was {7}:{8})", netGenerationInputSnapshot.Chapter, netGenerationInputSnapshot.LevelIndex, string.IsNullOrEmpty(netGenerationInputSnapshot.GraphName) ? "?" : netGenerationInputSnapshot.GraphName, netGenerationInputSnapshot.Seed, pendingTransition.Source, pendingTransition.EnvId, pendingTransition.Revision, chapter, levelIndex)); } else { TransitionFinalizeWithoutTransition++; Plugin.Log.Warn(string.Format("[LevelTransition] finalize without transition; fallback current GameManager state graph={0} seed={1} chapter={2} level={3}", string.IsNullOrEmpty(netGenerationInputSnapshot.GraphName) ? "?" : netGenerationInputSnapshot.GraphName, netGenerationInputSnapshot.Seed, netGenerationInputSnapshot.Chapter, netGenerationInputSnapshot.LevelIndex)); } if (!flag2 && NetSceneClassify.IsHubOrSafeZoneGraph(netGenerationInputSnapshot.GraphName)) { if (NetSceneClassify.IsCombatLikeChapter(netGenerationInputSnapshot.Chapter)) { string chapter2 = netGenerationInputSnapshot.Chapter; int levelIndex2 = netGenerationInputSnapshot.LevelIndex; bool flag3 = false; string chapter3; if (NetRunStateBridge.TryGetLocalRunState(out NetRunState state2) && state2.HasLevel && NetSceneClassify.IsHubOrSafeZoneChapter(state2.ChapterName)) { chapter3 = state2.ChapterName; flag3 = true; } else { chapter3 = netGenerationInputSnapshot.GraphName; } netGenerationInputSnapshot.Chapter = chapter3; netGenerationInputSnapshot.LevelIndex = 0; NetSceneFollowDiag.HubSnapshotIdentityCorrected++; if (!flag3) { NetSceneFollowDiag.HubSnapshotIdentityFallback++; } if (flag) { NetSceneFollowDiag.HubSnapshotPendingCombatIgnored++; } Plugin.Log.Info(string.Format("[GenerationInputSnapshot] hub identity corrected oldChapter={0} oldLevel={1} graph={2} newChapter={3} newLevel={4} seed={5} source={6}", chapter2, levelIndex2, netGenerationInputSnapshot.GraphName, netGenerationInputSnapshot.Chapter, netGenerationInputSnapshot.LevelIndex, netGenerationInputSnapshot.Seed, flag3 ? "liveLocalState" : "graphName")); } else { Plugin.Log.Info($"[GenerationInputSnapshot] safezone graph finalized graph={netGenerationInputSnapshot.GraphName} chapter={netGenerationInputSnapshot.Chapter} level={netGenerationInputSnapshot.LevelIndex} seed={netGenerationInputSnapshot.Seed}"); } } netGenerationInputSnapshot.RunId = netGenerationInputSnapshot.RunKey(); StoreFinalized(netGenerationInputSnapshot); if (Plugin.Cfg.LogUsedSetsTrace.Value) { string text3 = (flag ? "ResetContext" : "ResetContext(no-pending,current-state)"); if (!flag) { Plugin.Log.Warn("[GenerationInputSnapshot] finalize without pending; using current state"); } Plugin.Log.Info(string.Format("[GenerationInputSnapshot] finalized graph={0} seed={1} chapter={2} level={3} usedChunks={4} source={5}", string.IsNullOrEmpty(netGenerationInputSnapshot.GraphName) ? "?" : netGenerationInputSnapshot.GraphName, netGenerationInputSnapshot.Seed, netGenerationInputSnapshot.Chapter, netGenerationInputSnapshot.LevelIndex, NetHostUsedSets.Summary(netGenerationInputSnapshot.UsedSets.UsedChunksThisRun), text3)); } NetRunStateBridge.ApplyFinalizedGenerationSnapshot(netGenerationInputSnapshot); } catch (Exception ex) { Plugin.Log.Warn("[GenerationInputSnapshot] finalize failed: " + ex.GetType().Name + ": " + ex.Message); } } private static void ResolveArgIndices(MethodBase method) { _argsResolved = true; try { ParameterInfo[] array = method?.GetParameters() ?? Array.Empty(); List list = new List(); for (int i = 0; i < array.Length; i++) { ParameterInfo parameterInfo = array[i]; list.Add($"[{i}]{parameterInfo.ParameterType.Name} {parameterInfo.Name}"); string text = (parameterInfo.Name ?? "").ToLowerInvariant(); if (_seedArgIndex < 0 && text.Contains("seed")) { _seedArgIndex = i; } if (_levelArgIndex < 0 && (text == "levelindex" || (text.Contains("level") && parameterInfo.ParameterType == typeof(int)))) { _levelArgIndex = i; } if (_graphArgIndex < 0 && (text.Contains("graph") || text.Contains("set") || text.Contains("maker"))) { _graphArgIndex = i; } if (_loadingModeArgIndex < 0 && text.Contains("loading")) { _loadingModeArgIndex = i; } if (_spawnArgIndex < 0 && text.Contains("spawn")) { _spawnArgIndex = i; } } Plugin.Log.Info("[GenerationInputSnapshot] StartLevelRoutineGraph params: " + string.Join(", ", list) + " " + $"=> seedIdx={_seedArgIndex} graphIdx={_graphArgIndex} levelIdx={_levelArgIndex} loadingIdx={_loadingModeArgIndex} spawnIdx={_spawnArgIndex}"); } catch (Exception ex) { Plugin.Log.Warn("[GenerationInputSnapshot] ResolveArgIndices failed: " + ex.Message); } } private static string ResolveGraphName(object[]? args) { if (args == null) { return ""; } if (_graphArgIndex >= 0 && _graphArgIndex < args.Length) { string text = NameOf(args[_graphArgIndex]); if (!string.IsNullOrEmpty(text)) { return text; } } foreach (object obj in args) { if (obj is Object) { string text2 = NameOf(obj); if (!string.IsNullOrEmpty(text2)) { return text2; } } } return ""; } private static string NameOf(object? o) { if (o == null) { return ""; } if (o is string result) { return result; } Object val = (Object)((o is Object) ? o : null); if (val != null) { if (!(val != (Object)null)) { return ""; } return val.name; } return o.ToString() ?? ""; } private static bool TryToInt(object? o, out int value) { value = 0; if (o == null) { return false; } try { if (!(o is int num)) { if (!(o is uint num2)) { if (!(o is long num3)) { if (o is short num4) { value = num4; return true; } return false; } value = (int)num3; return true; } value = (int)num2; return true; } value = num; return true; } catch { return false; } } } public sealed class NetGenerationInputSnapshot { public string GraphName { get; set; } = ""; public string Chapter { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasSeed { get; set; } public int Seed { get; set; } public int SeedCandidate { get; set; } public string LoadingMode { get; set; } = ""; public string SpawnIdentifier { get; set; } = ""; public int Revision { get; set; } public string RunId { get; set; } = ""; public bool Finalized { get; set; } public bool FromTransition { get; set; } public string TransitionSource { get; set; } = ""; public int TargetEnvId { get; set; } = -1; public NetHostUsedSets UsedSets { get; set; } = new NetHostUsedSets(); public NetGenerationInputSnapshot Clone() { return new NetGenerationInputSnapshot { GraphName = GraphName, Chapter = Chapter, LevelIndex = LevelIndex, HasSeed = HasSeed, Seed = Seed, SeedCandidate = SeedCandidate, LoadingMode = LoadingMode, SpawnIdentifier = SpawnIdentifier, Revision = Revision, RunId = RunId, Finalized = Finalized, FromTransition = FromTransition, TransitionSource = TransitionSource, TargetEnvId = TargetEnvId, UsedSets = (UsedSets?.Clone() ?? new NetHostUsedSets()) }; } public static string SeedLevelKey(bool hasSeed, int seed, int levelIndex) { return string.Format("{0}|L{1}", hasSeed ? seed.ToString() : "?", levelIndex); } public string SeedLevelKey() { return SeedLevelKey(HasSeed, Seed, LevelIndex); } public string RunKey() { return string.Format("{0}:{1}:{2}:{3}:r{4}", Chapter, LevelIndex, HasSeed ? Seed.ToString() : "?", string.IsNullOrEmpty(GraphName) ? "?" : GraphName, Revision); } public string ToCompactString() { return string.Format("graph={0} seed={1} chapter={2} level={3} {4}", string.IsNullOrEmpty(GraphName) ? "?" : GraphName, HasSeed ? Seed.ToString() : "?", Chapter, LevelIndex, UsedSets.ToCompactString()); } } internal static class NetHandshake { public const string ProtocolMagic = "SULFUR_TOGETHER"; public const int ProtocolVersion = 2; public static void WriteRequest(NetDataWriter w, string playerName) { w.Put("SULFUR_TOGETHER"); w.Put(2); w.Put("1.0.0"); w.Put(playerName); w.Put(Plugin.Cfg.ConnectionKey.Value); } public static bool TryReadRequest(NetDataReader r, out HandshakeData data) { data = new HandshakeData(); try { data.Magic = r.GetString(); data.ProtocolVersion = r.GetInt(); data.ModVersion = r.GetString(); data.PlayerName = r.GetString(); data.ConnectionKey = r.GetString(); return true; } catch { return false; } } public static void WriteAccepted(NetDataWriter w, string assignedPeerId, int assignedSlot, string hostPeerId, string hostPlayerName, int maxPlayers) { w.Put(assignedPeerId ?? ""); w.Put(assignedSlot); w.Put(hostPeerId ?? "host"); w.Put(hostPlayerName ?? "Host"); w.Put("1.0.0"); w.Put(maxPlayers); } public static bool TryReadAccepted(NetDataReader r, out HandshakeAcceptedData data) { data = new HandshakeAcceptedData(); try { data.AssignedPeerId = r.GetString(); data.AssignedSlot = r.GetInt(); data.HostPeerId = r.GetString(); data.HostPlayerName = r.GetString(); data.HostModVersion = r.GetString(); data.MaxPlayers = r.GetInt(); return true; } catch { return false; } } } internal class HandshakeData { public string Magic = ""; public int ProtocolVersion; public string ModVersion = ""; public string PlayerName = ""; public string ConnectionKey = ""; } internal class HandshakeAcceptedData { public string AssignedPeerId = ""; public int AssignedSlot = -1; public string HostPeerId = "host"; public string HostPlayerName = "Host"; public string HostModVersion = ""; public int MaxPlayers = 1; } internal static class NetHostTransitionGuard { private static readonly object _lock = new object(); private static bool _active; private static float _startedAt; private static string _source = ""; private const float MaxSeconds = 30f; public static bool IsActive { get { lock (_lock) { if (_active && Time.realtimeSinceStartup - _startedAt > 30f) { _active = false; Plugin.Log.Warn($"[HostTransitionGuard] auto-cleared after {30f:F0}s (stuck guard safety; last source={_source})"); } return _active; } } } public static void Begin(string source) { lock (_lock) { _startedAt = Time.realtimeSinceStartup; _source = source ?? ""; if (!_active) { _active = true; Plugin.Log.Info("[HostTransitionGuard] begin source=" + _source); } } } public static void End(string source) { lock (_lock) { if (_active) { _active = false; Plugin.Log.Info("[HostTransitionGuard] end source=" + source + " (was " + _source + ")"); _source = ""; } } } public static void Reset() { lock (_lock) { _active = false; _source = ""; } } } public sealed class NetHostUsedSets { public List UsedChunksThisRun { get; set; } = new List(); public List UsedEventsThisRun { get; set; } = new List(); public List UsedEventsThisEnvironment { get; set; } = new List(); public bool Captured { get; set; } public int ChunksCount => UsedChunksThisRun?.Count ?? 0; public int EventsRunCount => UsedEventsThisRun?.Count ?? 0; public int EventsEnvCount => UsedEventsThisEnvironment?.Count ?? 0; public NetHostUsedSets Clone() { return new NetHostUsedSets { Captured = Captured, UsedChunksThisRun = new List(UsedChunksThisRun ?? new List()), UsedEventsThisRun = new List(UsedEventsThisRun ?? new List()), UsedEventsThisEnvironment = new List(UsedEventsThisEnvironment ?? new List()) }; } public string ToCompactString() { return $"captured={Captured} chunks={ChunksCount} eventsRun={EventsRunCount} eventsEnv={EventsEnvCount}"; } public static string Summary(List? items, int max = 8) { if (items == null || items.Count == 0) { return "[]"; } IEnumerable values = items.Take(max); string text = ((items.Count > max) ? $",+{items.Count - max}" : ""); return "[" + string.Join(",", values) + text + "]"; } } internal static class NetLevelSeed { private static bool _forceLevelSeedResolved; private static FieldInfo? _forceLevelSeedField; private static bool _currentSeedResolved; private static PropertyInfo? _currentSeedProperty; private static FieldInfo? _currentSeedField; private static Type? _currentSeedOwnerType; private static WeakReference? _observedGameManager; private static string _observedSource = ""; private static int _lastReportedSeed; private static int _transitionBaselineSeed; private static bool _transitionBaselineKnown; public static void ObserveGameManager(object? gameManager, string source) { if (gameManager != null) { _observedGameManager = new WeakReference(gameManager); _observedSource = (string.IsNullOrWhiteSpace(source) ? "GameManager" : source); } } public static void ResetReportLatch() { _lastReportedSeed = 0; _transitionBaselineKnown = false; } public static void BeginLevelTransition(object? gameManager, string source) { ObserveGameManager(gameManager, source); _transitionBaselineKnown = false; _transitionBaselineSeed = 0; if (gameManager != null && TryReadCurrentSeed(gameManager, out int seed, out string _) && seed != 0) { _transitionBaselineKnown = true; _transitionBaselineSeed = seed; } } public static void ReportObservedGameManagerSeed(string source) { try { if (!Plugin.Cfg.EnableLevelSeedAuthority.Value || _observedGameManager == null) { return; } object target = _observedGameManager.Target; if (target != null && TryReadCurrentSeed(target, out int seed, out string detail) && seed != _lastReportedSeed && (!_transitionBaselineKnown || seed != _transitionBaselineSeed)) { _transitionBaselineKnown = false; _lastReportedSeed = seed; string text = (string.IsNullOrWhiteSpace(source) ? _observedSource : source); if (string.IsNullOrWhiteSpace(text)) { text = "GameManager.currentSeed"; } NetRunStateBridge.ReportLevelSeed(seed, text); if (Plugin.Cfg.EnableDebugLog.Value) { Plugin.Log.Debug($"[LevelSeed] Captured currentSeed={seed} via {detail} source={text}"); } } } catch { } } public static bool TryReadCurrentSeed(object? gameManager, out int seed, out string detail) { seed = 0; detail = ""; if (gameManager == null) { detail = "GameManager instance is null."; return false; } try { ResolveCurrentSeedMembers(gameManager.GetType()); object obj = null; if (_currentSeedProperty != null) { obj = _currentSeedProperty.GetValue(gameManager, null); detail = gameManager.GetType().FullName + ".currentSeed"; } else if (_currentSeedField != null) { obj = _currentSeedField.GetValue(gameManager); detail = gameManager.GetType().FullName + "." + _currentSeedField.Name; } if (obj == null) { detail = "GameManager currentSeed member was not found."; return false; } seed = Convert.ToInt32(obj); if (seed == 0) { detail += " is 0; treating as not ready."; return false; } return true; } catch (Exception ex) { detail = "Read currentSeed failed: " + ex.GetType().Name + ": " + ex.Message; seed = 0; return false; } } public static bool TryApplyForceLevelSeed(int seed, out string result) { result = ""; try { FieldInfo fieldInfo = ResolveForceLevelSeedField(); if (fieldInfo == null) { result = "GlobalSettings.ForceLevelSeed was not found."; return false; } fieldInfo.SetValue(null, seed); result = string.Format("Set {0}.ForceLevelSeed={1}", fieldInfo.DeclaringType?.FullName ?? "GlobalSettings", seed); return true; } catch (Exception ex) { result = "ForceLevelSeed apply failed: " + ex.GetType().Name + ": " + ex.Message; return false; } } private static void ResolveCurrentSeedMembers(Type gameManagerType) { if (!_currentSeedResolved || !(_currentSeedOwnerType == gameManagerType)) { _currentSeedResolved = true; _currentSeedOwnerType = gameManagerType; _currentSeedProperty = gameManagerType.GetProperty("currentSeed", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _currentSeedField = gameManagerType.GetField("k__BackingField", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? gameManagerType.GetField("currentSeed", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } } private static FieldInfo? ResolveForceLevelSeedField() { if (_forceLevelSeedResolved) { return _forceLevelSeedField; } _forceLevelSeedResolved = true; Type type = Type.GetType("GlobalSettings, PerfectRandom.Sulfur.Core", throwOnError: false); if (type == null) { try { type = typeof(GlobalSettings); } catch { } } if (type == null) { return null; } _forceLevelSeedField = type.GetField("ForceLevelSeed", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); return _forceLevelSeedField; } } internal static class NetLinkState { private static bool _clientLinked; private static bool _hostLinked = true; public static bool ClientLinked => _clientLinked; public static bool HostLinked => _hostLinked; public static void InitFromConfig() { try { _clientLinked = Plugin.Cfg.ClientLinkedByDefault.Value; } catch { _clientLinked = false; } try { _hostLinked = Plugin.Cfg.HostLinkedByDefault.Value; } catch { _hostLinked = true; } Plugin.Log.Info($"[LinkState] init clientLinked={_clientLinked} hostLinked={_hostLinked}"); } public static void SetClientLinked(bool on, string reason) { if (_clientLinked == on) { Plugin.Log.Info("[LinkState] client already " + (on ? "LINKED" : "UNLINKED") + " (reason=" + reason + ")"); return; } _clientLinked = on; Plugin.Log.Info("[LinkState] client " + (on ? "LINKED (joining/following host)" : "UNLINKED (independent local run)") + " reason=" + reason); CoopToasts.Notify(on ? CoopLoc.Get("link.linkedToHost", "Linked to host") : CoopLoc.Get("link.playingSolo", "Playing solo")); if (!on) { NetClientJoinFlow.LeaveSession("client unlinked"); NetClientLoadGate.Reset(); } } public static void SetHostLinked(bool on, string reason) { if (_hostLinked == on) { Plugin.Log.Info("[LinkState] host already " + (on ? "ON" : "OFF") + " (reason=" + reason + ")"); } else { _hostLinked = on; Plugin.Log.Info("[LinkState] host multiplayer " + (on ? "ON (broadcasting + leading client relays)" : "OFF (single-player; ignoring relays)") + " reason=" + reason); CoopToasts.Notify(on ? CoopLoc.Get("link.hostingOn", "Hosting ON") : CoopLoc.Get("link.hostingOff", "Hosting OFF")); } } public static void ToggleHost(string reason) { SetHostLinked(!_hostLinked, reason); } public static void ResetClient(string reason) { SetClientLinked(on: false, reason); } public static void ResetClientToDefault(string reason) { bool flag; try { flag = Plugin.Cfg.ClientLinkedByDefault.Value; } catch { flag = false; } if (_clientLinked != flag) { Plugin.Log.Info("[LinkState] client 联机状态 reset to default (" + (flag ? "linked" : "unlinked") + ") reason=" + reason); SetClientLinked(flag, reason); } } public static string FormatStatus() { return "LinkState client=" + (_clientLinked ? "linked" : "off") + " host=" + (_hostLinked ? "on" : "off"); } } internal static class NetLoadBarrier { private static readonly object _lock = new object(); private static string _runKey = ""; private static readonly Dictionary _peerLoaded = new Dictionary(); private static float _startedAt; private static bool _timedOut; private static bool _allLoadedLogged; public static int LoadBarrierStarted; public static int LoadBarrierClientPending; public static int LoadBarrierClientLoaded; public static int LoadBarrierAllLoaded; public static int LoadBarrierTimeout; public static int LoadBarrierLogOnly; private static bool Enabled { get { try { return Plugin.Cfg.EnableLoadBarrier.Value; } catch { return false; } } } public static void Reset() { lock (_lock) { _runKey = ""; _peerLoaded.Clear(); _startedAt = 0f; _timedOut = false; _allLoadedLogged = false; } } public static string RunKeyFor(string chapter, int level, bool hasSeed, int seed, string graph) { return string.Format("{0}:{1}:{2}", string.IsNullOrWhiteSpace(chapter) ? "" : chapter, level, hasSeed ? seed.ToString() : "?"); } public static void MarkPending(string peerId, string runKey, string graph, string reason = "combat") { if (!Enabled || string.IsNullOrWhiteSpace(peerId) || string.IsNullOrWhiteSpace(runKey)) { return; } lock (_lock) { bool flag; try { flag = Plugin.Cfg.LoadBarrierLogOnlyMode.Value; } catch { flag = true; } if (flag) { LoadBarrierLogOnly++; } if (runKey != _runKey) { _runKey = runKey; _peerLoaded.Clear(); _startedAt = Now(); _timedOut = false; _allLoadedLogged = false; LoadBarrierStarted++; } if (!_peerLoaded.ContainsKey(peerId)) { _peerLoaded[peerId] = false; LoadBarrierClientPending++; NetLogger.Info(string.Format("[LoadBarrier] waiting clients run={0} graph={1} pending={2} reason={3}", _runKey, string.IsNullOrEmpty(graph) ? "?" : graph, PendingCountLocked(), string.IsNullOrEmpty(reason) ? "combat" : reason)); } } } public static void MarkLoaded(string peerId, string runKey, string graph) { if (!Enabled || string.IsNullOrWhiteSpace(peerId)) { return; } lock (_lock) { if (_runKey.Length <= 0 || string.IsNullOrWhiteSpace(runKey) || !(runKey != _runKey)) { bool value; bool num = _peerLoaded.TryGetValue(peerId, out value) && value; _peerLoaded[peerId] = true; if (!num) { LoadBarrierClientLoaded++; NetLogger.Info(string.Format("[LoadBarrier] client loaded peer={0} run={1} graph={2} pending={3}", peerId, (_runKey.Length > 0) ? _runKey : runKey, string.IsNullOrEmpty(graph) ? "?" : graph, PendingCountLocked())); } if (!_allLoadedLogged && _peerLoaded.Count > 0 && PendingCountLocked() == 0) { _allLoadedLogged = true; LoadBarrierAllLoaded++; NetLogger.Info($"[LoadBarrier] all clients loaded run={((_runKey.Length > 0) ? _runKey : runKey)} clients={_peerLoaded.Count}"); } } } } public static void RemovePeer(string peerId) { if (string.IsNullOrWhiteSpace(peerId)) { return; } lock (_lock) { _peerLoaded.Remove(peerId); } } public static void Tick() { if (!Enabled) { return; } lock (_lock) { if (!_timedOut && _peerLoaded.Count != 0 && PendingCountLocked() != 0) { float num; try { num = Plugin.Cfg.LoadBarrierTimeoutSeconds.Value; } catch { num = 30f; } if (!(Now() - _startedAt < num)) { _timedOut = true; LoadBarrierTimeout++; NetLogger.Warn($"[LoadBarrier] timeout waiting clients run={_runKey} pending={PendingCountLocked()} (log-only; host not blocked)"); } } } } public static string FormatStatus() { if (!Enabled) { return "loadBarrier=off"; } lock (_lock) { if (_peerLoaded.Count == 0) { return "loadBarrier=idle"; } return $"loadBarrier[run={_runKey} pending={PendingCountLocked()}/{_peerLoaded.Count} timedOut={_timedOut}]"; } } public static string FormatCounters() { return $"started={LoadBarrierStarted} pending={LoadBarrierClientPending} loaded={LoadBarrierClientLoaded} allLoaded={LoadBarrierAllLoaded} timeout={LoadBarrierTimeout} logOnly={LoadBarrierLogOnly}"; } private static int PendingCountLocked() { return _peerLoaded.Count>((KeyValuePair kv) => !kv.Value); } private static float Now() { return Time.realtimeSinceStartup; } } internal static class NetLoadingFade { private static bool _metaResolved; private static bool _metaFailed; private static Type? _uiManagerType; private static MethodInfo? _instanceGetter; private static MethodInfo? _loadingFade; private static object? _normalLoadingMode; private static FieldInfo? _loadingOverlayField; private static MethodInfo? _overlaySetState; private static MethodInfo? _overlaySetText; private static object? _uiStateShown; private static object? _uiStateHidden; public static bool Active { get; private set; } public static void Show() { try { if (!ResolveMeta()) { return; } object uiManager = GetUiManager(); if (uiManager == null) { return; } _loadingFade.Invoke(uiManager, new object[2] { true, _normalLoadingMode }); object obj = _loadingOverlayField?.GetValue(uiManager); if (obj != null) { try { _overlaySetText?.Invoke(obj, new object[2] { "", "" }); } catch { } try { if (_uiStateShown != null) { _overlaySetState?.Invoke(obj, new object[1] { _uiStateShown }); } } catch { } } Active = true; Plugin.Log.Info("[ClientLoadFade] shown (black fade + loading overlay)"); } catch (Exception ex) { Plugin.Log.Warn("[ClientLoadFade] show failed: " + ex.Message); } } public static void Hide() { try { if (!Active) { return; } Active = false; if (!ResolveMeta()) { return; } object uiManager = GetUiManager(); if (uiManager == null) { return; } _loadingFade.Invoke(uiManager, new object[2] { false, _normalLoadingMode }); object obj = _loadingOverlayField?.GetValue(uiManager); if (obj != null && _uiStateHidden != null) { try { _overlaySetState?.Invoke(obj, new object[1] { _uiStateHidden }); } catch { } } Plugin.Log.Info("[ClientLoadFade] hidden (fade back in)"); } catch (Exception ex) { Plugin.Log.Warn("[ClientLoadFade] hide failed: " + ex.Message); } } private static object? GetUiManager() { try { return _instanceGetter?.Invoke(null, null); } catch { return null; } } private static bool ResolveMeta() { if (_metaResolved) { return !_metaFailed; } _metaResolved = true; try { _uiManagerType = FindType("PerfectRandom.Sulfur.Core.UI.UIManager, PerfectRandom.Sulfur.Core", "PerfectRandom.Sulfur.Core.UI.UIManager"); if (_uiManagerType == null) { _metaFailed = true; return false; } _instanceGetter = _uiManagerType.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)?.GetGetMethod(nonPublic: true); if (_instanceGetter == null) { _metaFailed = true; return false; } MethodInfo[] methods = _uiManagerType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "LoadingFade")) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length >= 1 && parameters[0].ParameterType == typeof(bool)) { _loadingFade = methodInfo; break; } } } if (_loadingFade == null) { _metaFailed = true; return false; } ParameterInfo[] parameters2 = _loadingFade.GetParameters(); if (parameters2.Length >= 2 && parameters2[1].ParameterType.IsEnum) { _normalLoadingMode = ParseEnum(parameters2[1].ParameterType, "Normal"); } if (_normalLoadingMode == null && parameters2.Length >= 2) { _normalLoadingMode = Activator.CreateInstance(parameters2[1].ParameterType); } _loadingOverlayField = _uiManagerType.GetField("loadingOverlay", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Type type = _loadingOverlayField?.FieldType; if (type != null) { methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods) { ParameterInfo[] parameters3 = methodInfo2.GetParameters(); if (methodInfo2.Name == "SetState" && parameters3.Length == 1 && parameters3[0].ParameterType.IsEnum) { _overlaySetState = methodInfo2; _uiStateShown = ParseEnum(parameters3[0].ParameterType, "Shown"); _uiStateHidden = ParseEnum(parameters3[0].ParameterType, "Hidden"); } else if (methodInfo2.Name == "SetText" && parameters3.Length >= 1 && parameters3[0].ParameterType == typeof(string)) { _overlaySetText = methodInfo2; } } } return true; } catch (Exception ex) { _metaFailed = true; Plugin.Log.Warn("[ClientLoadFade] resolve failed: " + ex.Message); return false; } } private static object? ParseEnum(Type enumType, string name) { try { return Enum.Parse(enumType, name, ignoreCase: true); } catch { return null; } } private static Type? FindType(params string[] names) { string[] array = names; for (int i = 0; i < array.Length; i++) { Type type = Type.GetType(array[i], throwOnError: false); if (type != null) { return type; } } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { array = names; for (int j = 0; j < array.Length; j++) { string name = array[j].Split(new char[1] { ',' })[0].Trim(); Type type2 = assembly.GetType(name, throwOnError: false); if (type2 != null) { return type2; } } } return null; } } internal static class NetLocalAddress { private static string _cached; private static bool _tried; public static bool TryGetLanIPv4(out string ip) { if (!_tried) { _tried = true; _cached = Resolve(); } ip = _cached; return !string.IsNullOrEmpty(_cached); } public static void Invalidate() { _tried = false; _cached = null; } private static string Resolve() { try { using Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); socket.Connect("8.8.8.8", 65530); if (socket.LocalEndPoint is IPEndPoint iPEndPoint && !IPAddress.IsLoopback(iPEndPoint.Address)) { return iPEndPoint.Address.ToString(); } } catch { } try { IPAddress[] hostAddresses = Dns.GetHostAddresses(Dns.GetHostName()); foreach (IPAddress iPAddress in hostAddresses) { if (iPAddress.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(iPAddress)) { return iPAddress.ToString(); } } } catch { } return null; } } public sealed class NetLocalPlayerTracker { private Transform? _transform; private string _sourceDescription = ""; private object? _playerComponent; private Camera? _playerCamera; private FieldInfo? _inputReaderField; private object? _inputReader; private MethodInfo? _getMovementInputMi; private bool _playerRefsResolved; public bool HasTransform => (Object)(object)_transform != (Object)null; public Transform? LocalTransform => _transform; public void Clear() { _transform = null; _sourceDescription = ""; ResetPlayerRefs(); } public bool TrySetLocalPlayerObject(object? player, out string description) { description = ""; Transform val = ExtractTransform(player); if ((Object)(object)val == (Object)null) { return false; } _transform = val; _sourceDescription = $"{((Object)val).name}#{((Object)val).GetInstanceID()}"; description = _sourceDescription; ResetPlayerRefs(); return true; } public bool TryBuildState(string peerId, string playerName, NetRunState runState, int sequence, float now, out NetPlayerTransformState state) { //IL_00aa: 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_00c5: Unknown result type (might be due to invalid IL or missing references) state = new NetPlayerTransformState(); if ((Object)(object)_transform == (Object)null) { return false; } if (!runState.HasLevel) { return false; } try { state.PeerId = (string.IsNullOrWhiteSpace(peerId) ? runState.PeerId : peerId); state.PlayerName = (string.IsNullOrWhiteSpace(playerName) ? runState.PlayerName : playerName); state.ChapterName = runState.ChapterName; state.LevelIndex = runState.LevelIndex; state.HasLevelSeed = runState.HasLevelSeed; state.LevelSeed = runState.LevelSeed; state.Sequence = sequence; state.SentAt = now; state.Position = _transform.position; NetPlayerTransformState obj = state; Quaternion rotation = _transform.rotation; obj.RotationY = ((Quaternion)(ref rotation)).eulerAngles.y; state.LookYaw = ResolveCameraYaw(state.RotationY); state.LookPitch = ResolveCameraPitch(); state.Moving = ResolveInputMoving(); return true; } catch { _transform = null; _sourceDescription = ""; return false; } } public string FormatStatus() { if (!((Object)(object)_transform == (Object)null)) { return "localPlayer=" + _sourceDescription; } return "localPlayer="; } private void ResetPlayerRefs() { _playerRefsResolved = false; _playerComponent = null; _playerCamera = null; _inputReader = null; _getMovementInputMi = null; } private void EnsurePlayerRefs() { if (_playerRefsResolved || (Object)(object)_transform == (Object)null) { return; } _playerRefsResolved = true; try { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Units.Player"); if (type == null) { return; } _playerComponent = ((Component)_transform).GetComponent(type); if (_playerComponent != null) { ref Camera? playerCamera = ref _playerCamera; object? obj = type.GetField("playerCamera", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(_playerComponent); playerCamera = (Camera?)((obj is Camera) ? obj : null); _inputReaderField = type.GetField("inputReader", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _inputReader = _inputReaderField?.GetValue(_playerComponent); if (_inputReader != null) { _getMovementInputMi = _inputReader.GetType().GetMethod("GetMovementInput", Type.EmptyTypes); } } } catch { } } private float ResolveCameraYaw(float fallback) { //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_0059: 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) try { EnsurePlayerRefs(); Camera val = (Camera)(((Object)(object)_playerCamera != (Object)null) ? ((object)_playerCamera) : ((object)Camera.main)); if ((Object)(object)val == (Object)null) { return fallback; } Vector3 forward = ((Component)val).transform.forward; forward.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude < 1E-06f) { return fallback; } return Mathf.Atan2(forward.x, forward.z) * 57.29578f; } catch { return fallback; } } private float ResolveCameraPitch() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) try { EnsurePlayerRefs(); Camera val = (Camera)(((Object)(object)_playerCamera != (Object)null) ? ((object)_playerCamera) : ((object)Camera.main)); if ((Object)(object)val == (Object)null) { return 0f; } return (0f - Mathf.Asin(Mathf.Clamp(((Component)val).transform.forward.y, -1f, 1f))) * 57.29578f; } catch { return 0f; } } private bool ResolveInputMoving() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) try { EnsurePlayerRefs(); if (_getMovementInputMi == null || _inputReader == null) { return false; } if (_getMovementInputMi.Invoke(_inputReader, null) is Vector2 val) { return ((Vector2)(ref val)).sqrMagnitude > 0.02f; } } catch { } return false; } private static Transform? ExtractTransform(object? player) { if (player == null) { return null; } Transform val = (Transform)((player is Transform) ? player : null); if (val != null) { return val; } GameObject val2 = (GameObject)((player is GameObject) ? player : null); if (val2 != null) { return val2.transform; } Component val3 = (Component)((player is Component) ? player : null); if (val3 != null) { return val3.transform; } try { PropertyInfo property = player.GetType().GetProperty("transform", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { object? value = property.GetValue(player, null); Transform val4 = (Transform)((value is Transform) ? value : null); if (val4 != null) { return val4; } } FieldInfo field = player.GetType().GetField("transform", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { object? value2 = field.GetValue(player); Transform val5 = (Transform)((value2 is Transform) ? value2 : null); if (val5 != null) { return val5; } } PropertyInfo property2 = player.GetType().GetProperty("gameObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property2 != null) { object? value3 = property2.GetValue(player, null); GameObject val6 = (GameObject)((value3 is GameObject) ? value3 : null); if (val6 != null) { return val6.transform; } } } catch { } return null; } } internal static class NetLogger { public static void Info(string msg) { Plugin.Log.Info(msg); } public static void Warn(string msg) { Plugin.Log.Warn(msg); } public static void Error(string msg) { Plugin.Log.Error(msg); } public static void Debug(string msg) { Plugin.Log.Debug(msg); } } internal static class NetManualSceneFollower { public static bool TryFollow(NetHostSceneRequest request, out string result) { result = ""; if (request == null || !request.HasTargetScene) { result = "No valid HostSceneRequest target is available."; return false; } Type type = FindType("PerfectRandom.Sulfur.Core.GameManager, PerfectRandom.Sulfur.Core", "PerfectRandom.Sulfur.GameManager, PerfectRandom.Sulfur.Core", "GameManager, Assembly-CSharp"); if (type == null) { result = "GameManager type was not found."; return false; } object obj = FindGameManager(type); if (obj == null) { result = "GameManager instance was not found."; return false; } MethodInfo methodInfo = FindGoToLevelMethod(type); if (methodInfo == null) { result = "GameManager.GoToLevel(WorldEnvironment,int,LoadingMode,string) was not found."; return false; } ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length < 4) { result = "GameManager.GoToLevel signature is shorter than expected."; return false; } object obj2 = FindWorldEnvironment(parameters[0].ParameterType, request.ChapterName); if (obj2 == null) { result = "WorldEnvironment '" + request.ChapterName + "' was not found in loaded resources."; return false; } string text = ""; if (Plugin.Cfg.ApplyHostLevelSeedOnManualFollow.Value && request.HasLevelSeed) { text = ((!NetLevelSeed.TryApplyForceLevelSeed(request.LevelSeed, out string result2)) ? (" seed=" + request.LevelSeed + " (not applied: " + result2 + ")") : (" seed=" + request.LevelSeed + " (" + result2 + ")")); } string text2 = ApplyHostUsedSets(obj, request); object obj3 = BuildLoadingMode(parameters[2].ParameterType, request.LoadingMode); if (obj3 == null && parameters[2].ParameterType.IsValueType) { result = "Could not build LoadingMode '" + request.LoadingMode + "' for " + parameters[2].ParameterType.Name + "."; return false; } CoopMenu.CloseIfOpen("host-driven-follow"); try { object[] parameters2 = new object[4] { obj2, request.LevelIndex, obj3, request.SpawnIdentifier ?? "" }; NetClientLoadGate.BeginHostDrivenLoad(); try { methodInfo.Invoke(methodInfo.IsStatic ? null : obj, parameters2); } finally { NetClientLoadGate.EndHostDrivenLoad(); } result = "Manual GoToLevel invoked for " + request.TargetSceneKey() + " mode='" + request.LoadingMode + "' spawn='" + request.SpawnIdentifier + "'" + text + text2 + "."; return true; } catch (TargetInvocationException ex) { result = "GoToLevel threw " + (ex.InnerException?.GetType().Name ?? ex.GetType().Name) + ": " + (ex.InnerException?.Message ?? ex.Message); return false; } catch (Exception ex2) { result = "GoToLevel invoke failed: " + ex2.GetType().Name + ": " + ex2.Message; return false; } } private static string ApplyHostUsedSets(object gameManager, NetHostSceneRequest request) { try { if (!Plugin.Cfg.SyncHostUsedSetsOnManualFollow.Value) { return " usedSets=disabled"; } bool value = Plugin.Cfg.LogUsedSetsTrace.Value; if (value) { Plugin.Log.Info(string.Format("[FollowPrep] host seed={0} level={1} env={2} hasUsedSets={3}", request.HasLevelSeed ? request.LevelSeed.ToString() : "?", request.LevelIndex, request.ChapterName, request.HasUsedSets)); } if (NetGameManagerUsedSets.TryRead(gameManager, out NetHostUsedSets sets, out string error)) { if (value) { Plugin.Log.Info("[FollowPrep] local before usedChunks=" + NetHostUsedSets.Summary(sets.UsedChunksThisRun)); Plugin.Log.Info("[FollowPrep] local before usedEventsRun=" + NetHostUsedSets.Summary(sets.UsedEventsThisRun)); Plugin.Log.Info("[FollowPrep] local before usedEventsEnv=" + NetHostUsedSets.Summary(sets.UsedEventsThisEnvironment)); } } else if (value) { Plugin.Log.Warn("[FollowPrep] local before read failed: " + error); } if (!request.HasUsedSets) { if (value) { Plugin.Log.Info("[FollowPrep] host sent no used sets; local sets left unchanged."); } return " usedSets=hostNone"; } NetHostUsedSets netHostUsedSets = request.ToUsedSets(); NetGameManagerUsedSets.ApplyOutcome applyOutcome = NetGameManagerUsedSets.Apply(gameManager, netHostUsedSets); NetSceneFollowDiag.ClientFollowUsedChunksBeforeCount = applyOutcome.ChunksBefore; NetSceneFollowDiag.ClientFollowUsedChunksAfterCount = applyOutcome.ChunksAfter; NetSceneFollowDiag.ClientFollowUsedEventsRunBeforeCount = applyOutcome.EventsRunBefore; NetSceneFollowDiag.ClientFollowUsedEventsRunAfterCount = applyOutcome.EventsRunAfter; NetSceneFollowDiag.ClientFollowUsedEventsEnvBeforeCount = applyOutcome.EventsEnvBefore; NetSceneFollowDiag.ClientFollowUsedEventsEnvAfterCount = applyOutcome.EventsEnvAfter; if (applyOutcome.AnyApplied) { NetSceneFollowDiag.IncApplied(); } if (applyOutcome.AnyFailed) { NetSceneFollowDiag.IncApplyFailed(); } if (value) { Plugin.Log.Info($"[FollowPrep] applied host usedChunks={NetHostUsedSets.Summary(netHostUsedSets.UsedChunksThisRun)} (count {applyOutcome.ChunksBefore}->{applyOutcome.ChunksAfter} applied={applyOutcome.ChunksApplied})"); Plugin.Log.Info($"[FollowPrep] applied host usedEventsRun={NetHostUsedSets.Summary(netHostUsedSets.UsedEventsThisRun)} (count {applyOutcome.EventsRunBefore}->{applyOutcome.EventsRunAfter} applied={applyOutcome.EventsRunApplied})"); Plugin.Log.Info($"[FollowPrep] applied host usedEventsEnv={NetHostUsedSets.Summary(netHostUsedSets.UsedEventsThisEnvironment)} (count {applyOutcome.EventsEnvBefore}->{applyOutcome.EventsEnvAfter} applied={applyOutcome.EventsEnvApplied})"); if (applyOutcome.AnyFailed) { Plugin.Log.Warn("[FollowPrep] apply errors: " + string.Join("; ", applyOutcome.Errors)); } Plugin.Log.Info("[FollowPrep] counters " + NetSceneFollowDiag.Format()); } return string.Format(" usedSets=chunks{0}->{1},eventsRun{2}->{3},eventsEnv{4}->{5}{6}", applyOutcome.ChunksBefore, applyOutcome.ChunksAfter, applyOutcome.EventsRunBefore, applyOutcome.EventsRunAfter, applyOutcome.EventsEnvBefore, applyOutcome.EventsEnvAfter, applyOutcome.AnyFailed ? "(partial)" : ""); } catch (Exception ex) { NetSceneFollowDiag.IncApplyFailed(); return " usedSets=error:" + ex.Message; } } private static Type? FindType(params string[] names) { string[] array = names; for (int i = 0; i < array.Length; i++) { Type type = Type.GetType(array[i], throwOnError: false); if (type != null) { return type; } } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { array = names; for (int j = 0; j < array.Length; j++) { string name = array[j].Split(new char[1] { ',' })[0].Trim(); Type type2 = assembly.GetType(name, throwOnError: false); if (type2 != null) { return type2; } } } return null; } private static object? FindGameManager(Type gameManagerType) { object obj = TryGetStaticMember(gameManagerType, "Instance") ?? TryGetStaticMember(gameManagerType, "instance") ?? TryGetStaticMember(gameManagerType, "Current") ?? TryGetStaticMember(gameManagerType, "current"); if (obj != null) { return obj; } Object[] array = Resources.FindObjectsOfTypeAll(gameManagerType); if (array == null || array.Length == 0) { return null; } return array[0]; } private static object? TryGetStaticMember(Type type, string name) { try { PropertyInfo property = type.GetProperty(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { return property.GetValue(null, null); } } catch { } try { FieldInfo field = type.GetField(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(null); } } catch { } return null; } private static MethodInfo? FindGoToLevelMethod(Type gameManagerType) { MethodInfo[] methods = gameManagerType.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "GoToLevel")) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 4 && !(parameters[1].ParameterType != typeof(int)) && !(parameters[3].ParameterType != typeof(string))) { return methodInfo; } } } return null; } private static object? FindWorldEnvironment(Type expectedType, string chapterName) { string text = NetSceneName.Clean(chapterName); if (string.IsNullOrWhiteSpace(text) || text == "") { return null; } object obj = TryBuildWorldEnvironmentId(expectedType, text); if (obj != null) { return obj; } if (typeof(Object).IsAssignableFrom(expectedType)) { object obj2 = FindLoadedObjectByName(expectedType, text); if (obj2 != null) { return obj2; } } Type type = expectedType; if (type == typeof(object) || !typeof(Object).IsAssignableFrom(type)) { type = FindType("PerfectRandom.Sulfur.Core.LevelGeneration.WorldEnvironment, PerfectRandom.Sulfur.Core", "PerfectRandom.Sulfur.Core.WorldEnvironment, PerfectRandom.Sulfur.Core"); } if (type != null && typeof(Object).IsAssignableFrom(type)) { object obj3 = FindLoadedObjectByName(type, text); if (obj3 != null && expectedType.IsInstanceOfType(obj3)) { return obj3; } } object obj4 = FindWorldEnvironmentFromLists(expectedType, text); if (obj4 != null) { return obj4; } return null; } private static object? TryBuildWorldEnvironmentId(Type expectedType, string chapterName) { if (!expectedType.IsEnum) { return null; } foreach (string item in NetSceneName.LookupCandidates(chapterName)) { try { return Enum.Parse(expectedType, item, ignoreCase: true); } catch { } } string b = NetSceneName.Canonicalize(chapterName); string[] names = Enum.GetNames(expectedType); foreach (string text in names) { if (string.Equals(text, b, StringComparison.OrdinalIgnoreCase)) { try { return Enum.Parse(expectedType, text); } catch { } } } return null; } private static object? FindLoadedObjectByName(Type type, string wanted) { if (!typeof(Object).IsAssignableFrom(type)) { return null; } try { Object[] array = Resources.FindObjectsOfTypeAll(type); foreach (Object val in array) { if (MatchesObjectName(val, wanted)) { return val; } } } catch { } return null; } private static object? FindWorldEnvironmentFromLists(Type expectedType, string wanted) { Type type = FindType("PerfectRandom.Sulfur.Core.WorldEnvironmentList, PerfectRandom.Sulfur.Core", "PerfectRandom.Sulfur.Core.LevelGeneration.WorldEnvironmentList, PerfectRandom.Sulfur.Core"); if (type == null) { return null; } try { Object[] array = Resources.FindObjectsOfTypeAll(type); for (int i = 0; i < array.Length; i++) { object obj = ScanObjectGraphForWorldEnvironment(array[i], expectedType, wanted, 0); if (obj != null) { return obj; } } } catch { } return null; } private static object? ScanObjectGraphForWorldEnvironment(object? source, Type expectedType, string wanted, int depth) { if (source == null || depth > 2) { return null; } if (expectedType.IsInstanceOfType(source) && MatchesObjectName(source, wanted)) { return source; } Type type = source.GetType(); FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { object value; try { value = fieldInfo.GetValue(source); } catch { continue; } object obj2 = ScanValueForWorldEnvironment(value, expectedType, wanted, depth + 1); if (obj2 != null) { return obj2; } } PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { if (propertyInfo.GetIndexParameters().Length == 0) { object value2; try { value2 = propertyInfo.GetValue(source, null); } catch { continue; } object obj4 = ScanValueForWorldEnvironment(value2, expectedType, wanted, depth + 1); if (obj4 != null) { return obj4; } } } return null; } private static object? ScanValueForWorldEnvironment(object? value, Type expectedType, string wanted, int depth) { if (value == null || depth > 2) { return null; } if (expectedType.IsInstanceOfType(value) && MatchesObjectName(value, wanted)) { return value; } if (value is string) { return null; } if (value is IEnumerable enumerable) { foreach (object item in enumerable) { if (item != null) { if (expectedType.IsInstanceOfType(item) && MatchesObjectName(item, wanted)) { return item; } object obj = ScanObjectGraphForWorldEnvironment(item, expectedType, wanted, depth + 1); if (obj != null) { return obj; } } } } return null; } private static bool MatchesObjectName(object obj, string wanted) { if (obj == null) { return false; } HashSet hashSet = new HashSet(NetSceneName.LookupCandidates(wanted), StringComparer.OrdinalIgnoreCase); if (hashSet.Count == 0) { return false; } if (hashSet.Contains(NetSceneName.Clean(obj.ToString()))) { return true; } Object val = (Object)((obj is Object) ? obj : null); if (val != null && hashSet.Contains(NetSceneName.Clean(val.name))) { return true; } string[] obj2 = new string[9] { "Name", "name", "Id", "ID", "id", "Identifier", "identifier", "EnvironmentName", "environmentName" }; Type type = obj.GetType(); string[] array = obj2; foreach (string name in array) { try { PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0 && hashSet.Contains(NetSceneName.Clean(property.GetValue(obj, null)?.ToString()))) { return true; } } catch { } try { FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && hashSet.Contains(NetSceneName.Clean(field.GetValue(obj)?.ToString()))) { return true; } } catch { } } return false; } private static object? BuildLoadingMode(Type type, string value) { if (type == typeof(string)) { return value ?? ""; } if (type.IsEnum) { string text = Clean(value); if (!string.IsNullOrWhiteSpace(text) && text != "") { try { return Enum.Parse(type, text, ignoreCase: true); } catch { } } string[] names = Enum.GetNames(type); foreach (string text2 in names) { if (string.Equals(text2, "Normal", StringComparison.OrdinalIgnoreCase)) { return Enum.Parse(type, text2); } } Array values = Enum.GetValues(type); if (values.Length <= 0) { return null; } return values.GetValue(0); } if (type == typeof(object)) { return value ?? ""; } return null; } private static string Clean(string? value) { return NetSceneName.Clean(value); } } internal static class NetMessage { public static NetDataWriter For(NetMessageType type) { //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_000d: Expected O, but got Unknown NetDataWriter val = new NetDataWriter(); val.Put((byte)type); return val; } } public enum NetMessageType : byte { HandshakeRequest = 1, HandshakeAccepted, HandshakeRejected, Ping, Pong, Disconnect, SessionSnapshot, PeerJoined, PeerLeft, RunStateUpdate, HostSceneRequest, ClientSceneAck, ClientSceneRefused, PlayerTransformVisual, HostEnemyDeathEvent, HostEnemyStateSnapshot, ClientEnemyDeathClaim, PlayerLifeState, HostWorldRoster, HostAttackPhaseEvent, HostProjectileVisualSpawn, HostEnemyDamageEvent, HostEnemyHealthState, ClientHitRequest, HostLevelManifest, HostHitVisualEvent, ClientHostGenerationInputRequest, ClientBossStartRequest, HostBossEncounterStart, ClientBossDialogCommitRequest, HostBossDialogCommit, HostBossState, HostBossDynamicSpawn, ClientBossHitRequest, HostBossHitVisual, HostBossDiscreteEvent, ClientLuciaEyeReport, HostLuciaEyeState, HostLuciaDeath, HostWitchPhase, HostWitchP2Manifest, HostWitchP2Result, HostRuntimeSpawn, PlayerWeaponFire, PlayerHeldWeapon, ClientTransitionRequest, BreakableBreak, WorldPickupSpawn, WorldPickupTakeRequest, WorldPickupRemoved, ClientRoomEnter, HostRoomMembership, GateState, TriggerDoors, ClientArenaEnter, ArenaCommand, HostEmperorWormHead, HostEmperorWormSectionDestroy, HostEmperorWormDeath, ClientEmperorWormHit, ClientEmperorFightStart, HostEmperorFightStart, HostEmperorSpiderTransform, ClientEmperorSpiderFightStart, HostEmperorSpiderFightStart, ClientEmperorSpiderHit, HostEmperorSpiderEvent, RunStatsFinalized, PlayerFriendlyFireHit, SessionSettings } public enum NetMode { Off, Host, Client } public enum NetPeerRole : byte { Host = 1, Client } public sealed class NetPeerSession { public string PeerId { get; set; } = ""; public string PlayerName { get; set; } = ""; public string ModVersion { get; set; } = ""; public string EndPoint { get; set; } = ""; public int Slot { get; set; } = -1; public NetPeerRole Role { get; set; } public NetConnectionState State { get; set; } public float JoinedAt { get; set; } public float LastSeen { get; set; } public bool IsLocal { get; set; } public bool IsConnected => State == NetConnectionState.Connected; public string ToCompactString() { string text = (IsLocal ? ",local" : ""); return $"{PlayerName}(id={PeerId},slot={Slot},role={Role},state={State}{text})"; } } internal static class NetPlayerTransformCodec { public static void Write(NetDataWriter w, NetPlayerTransformState state) { //IL_0080: 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_00a2: Unknown result type (might be due to invalid IL or missing references) w.Put(state.PeerId ?? ""); w.Put(state.PlayerName ?? ""); w.Put(state.ChapterName ?? ""); w.Put(state.LevelIndex); w.Put(state.HasLevelSeed ? 1 : 0); w.Put(state.LevelSeed); w.Put(state.Sequence); w.Put(state.SentAt); w.Put(state.Position.x); w.Put(state.Position.y); w.Put(state.Position.z); w.Put(state.RotationY); w.Put(state.LookYaw); w.Put(state.Moving); w.Put(state.LookPitch); } public static bool TryRead(NetDataReader r, out NetPlayerTransformState state) { //IL_009e: Unknown result type (might be due to invalid IL or missing references) state = new NetPlayerTransformState(); try { state.PeerId = r.GetString(); state.PlayerName = r.GetString(); state.ChapterName = r.GetString(); state.LevelIndex = r.GetInt(); if (r.AvailableBytes >= 4) { state.HasLevelSeed = r.GetInt() != 0; } if (r.AvailableBytes >= 4) { state.LevelSeed = r.GetInt(); } state.Sequence = r.GetInt(); state.SentAt = r.GetFloat(); float num = r.GetFloat(); float num2 = r.GetFloat(); float num3 = r.GetFloat(); state.Position = new Vector3(num, num2, num3); state.RotationY = r.GetFloat(); state.LookYaw = ((r.AvailableBytes >= 4) ? r.GetFloat() : state.RotationY); state.Moving = r.AvailableBytes >= 1 && r.GetBool(); state.LookPitch = ((r.AvailableBytes >= 4) ? r.GetFloat() : 0f); return true; } catch { return false; } } } public sealed class NetPlayerTransformState { public string PeerId { get; set; } = ""; public string PlayerName { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public int Sequence { get; set; } public float SentAt { get; set; } public Vector3 Position { get; set; } public float RotationY { get; set; } public float LookYaw { get; set; } public float LookPitch { get; set; } public bool Moving { get; set; } public bool HasScene { get { if (!string.IsNullOrWhiteSpace(ChapterName) && ChapterName != "") { return LevelIndex >= 0; } return false; } } public string SceneCompareKey() { return NetSceneName.SceneCompareKey(ChapterName, LevelIndex); } public string ToCompactString() { //IL_008d: 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_00b3: Unknown result type (might be due to invalid IL or missing references) string text = (string.IsNullOrWhiteSpace(PeerId) ? "?" : PeerId); string text2 = (string.IsNullOrWhiteSpace(PlayerName) ? "?" : PlayerName); string text3 = (HasLevelSeed ? $"#seed={LevelSeed}" : "#seed=?"); return $"{text2}(id={text},scene={SceneCompareKey()}{text3},seq={Sequence},pos=({Position.x:F2},{Position.y:F2},{Position.z:F2}),rotY={RotationY:F1})"; } } internal sealed class NetRemotePlayerProxy { private readonly string _peerId; private GameObject? _root; private TextMesh? _label; private Vector3 _targetPosition; private Quaternion _targetRotation = Quaternion.identity; private bool _hasAppliedState; private static bool _loggedCollisionDiag; private GameObject? _bodyHolder; private GameObject? _bodyModel; private bool _capsuleHidden; private RemotePlayerSpriteBody? _spriteBody; private float _lookYaw; private float _lookPitch; private bool _moving; private GameObject? _labelHolder; private Transform? _labelBg; private static readonly Color LabelTextColor = new Color(0.93f, 0.74f, 0.27f, 1f); private static readonly Color LabelBgColor = new Color(0f, 0f, 0f, 0.55f); private GameObject? _weaponHolder; private GameObject? _weaponModel; private string _heldWeaponSig = "none"; private static int _cachedPlayerLayer = -1; private const float DownedWeaponWorldY = 0.15f; public string PeerId => _peerId; public string PlayerName { get; private set; } = "Remote"; public int LastSequence { get; private set; } = -1; public float LastUpdatedAt { get; private set; } public bool IsVisible { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } public Vector3 TargetPosition => _targetPosition; public Vector3 VisualPosition { get { //IL_0020: 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) if (!((Object)(object)_root != (Object)null)) { return _targetPosition; } return _root.transform.position; } } public Transform VisualTransform { get { if (!((Object)(object)_root != (Object)null)) { return null; } return _root.transform; } } public bool HasBody => (Object)(object)_bodyModel != (Object)null; public string HeldWeaponSig => _heldWeaponSig; private static int ResolveLocalPlayerLayer() { if (_cachedPlayerLayer >= 0) { return _cachedPlayerLayer; } try { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); object obj = ((type == null) ? null : AccessTools.Property(type, "Instance")?.GetValue(null, null)); object? obj2 = ((obj == null) ? null : AccessTools.Property(type, "PlayerUnit")?.GetValue(obj, null)); Component val = (Component)((obj2 is Component) ? obj2 : null); if (val != null && (Object)(object)val != (Object)null) { _cachedPlayerLayer = val.gameObject.layer; } } catch { } return _cachedPlayerLayer; } public NetRemotePlayerProxy(string peerId) { //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) _peerId = (string.IsNullOrWhiteSpace(peerId) ? "remote" : peerId); } public void Apply(NetPlayerTransformState state, float now) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) EnsureCreated(); if (!((Object)(object)_root == (Object)null)) { bool flag = !_root.activeSelf; PlayerName = (string.IsNullOrWhiteSpace(state.PlayerName) ? _peerId : state.PlayerName); LastSequence = state.Sequence; LastUpdatedAt = now; _targetPosition = state.Position; _targetRotation = Quaternion.Euler(0f, state.RotationY, 0f); _lookYaw = state.LookYaw; _lookPitch = state.LookPitch; _moving = state.Moving; _root.SetActive(true); if ((Object)(object)_label != (Object)null && _label.text != PlayerName) { _label.text = PlayerName; UpdateNameLabelLayout(); } if (!_hasAppliedState || flag) { SnapToTarget(); } _hasAppliedState = true; } } public void Tick(float deltaTime, float now, float timeoutSeconds, float interpolationSpeed, float snapDistance) { //IL_0060: 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_0070: 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_00bf: 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_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root == (Object)null) { return; } UpdateSpriteBodyState(deltaTime); if (timeoutSeconds > 0f && now - LastUpdatedAt > timeoutSeconds) { Hide(); } else { if (!_root.activeSelf || !_hasAppliedState) { return; } if (snapDistance > 0f) { float num = snapDistance * snapDistance; Vector3 val = _root.transform.position - _targetPosition; if (((Vector3)(ref val)).sqrMagnitude > num) { SnapToTarget(); UpdateLabelFacingCamera(); return; } } if (interpolationSpeed <= 0f) { SnapToTarget(); } else { float num2 = Mathf.Clamp01(deltaTime * interpolationSpeed); _root.transform.position = Vector3.Lerp(_root.transform.position, _targetPosition, num2); _root.transform.rotation = Quaternion.Slerp(_root.transform.rotation, _targetRotation, num2); } UpdateLabelFacingCamera(); } } public void Hide() { if ((Object)(object)_root != (Object)null) { _root.SetActive(false); } _hasAppliedState = false; } public void Destroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } _root = null; _label = null; _hasAppliedState = false; _weaponHolder = null; _weaponModel = null; _heldWeaponSig = "none"; _bodyHolder = null; _bodyModel = null; _capsuleHidden = false; _spriteBody = null; } public void SetBody(GameObject body) { //IL_0149: 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_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_018a: 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_01ed: 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_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0110: 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) EnsureCreated(); if ((Object)(object)_root == (Object)null || (Object)(object)body == (Object)null) { if ((Object)(object)body != (Object)null) { Object.Destroy((Object)(object)body); } return; } if ((Object)(object)_bodyHolder == (Object)null) { _bodyHolder = new GameObject("NpcBody"); Transform transform = _bodyHolder.transform; transform.SetParent(_root.transform, false); Vector3 localScale = _root.transform.localScale; float num = 1f; try { num = Plugin.Cfg.RemoteBodyScale.Value; } catch { } if (num <= 0f) { num = 1f; } transform.localScale = new Vector3((Mathf.Approximately(localScale.x, 0f) ? 1f : (1f / localScale.x)) * num, (Mathf.Approximately(localScale.y, 0f) ? 1f : (1f / localScale.y)) * num, (Mathf.Approximately(localScale.z, 0f) ? 1f : (1f / localScale.z)) * num); float num2 = 0f; try { num2 = Plugin.Cfg.RemoteBodyFeetYOffset.Value; } catch { } float num3 = (Mathf.Approximately(localScale.y, 0f) ? 1f : localScale.y); float num4 = num + num2; transform.localPosition = new Vector3(0f, num4 / num3, 0f); transform.localRotation = Quaternion.identity; } if ((Object)(object)_bodyModel != (Object)null) { Object.Destroy((Object)(object)_bodyModel); } _bodyModel = body; _spriteBody = body.GetComponent(); body.transform.SetParent(_bodyHolder.transform, false); body.transform.localPosition = Vector3.zero; body.transform.localRotation = Quaternion.identity; body.SetActive(true); HideCapsuleMesh(); } private void UpdateSpriteBodyState(float deltaTime) { bool downed = NetPlayerLifeManager.IsPeerDowned(_peerId); if ((Object)(object)_spriteBody != (Object)null) { _spriteBody.SetState(_lookYaw, _lookPitch, _moving, downed); } UpdateWeaponOrientation(downed); } private void HideCapsuleMesh() { if (!_capsuleHidden && !((Object)(object)_root == (Object)null)) { Renderer component = _root.GetComponent(); if ((Object)(object)component != (Object)null) { component.enabled = false; } _capsuleHidden = true; } } public void UpdateHeldWeapon(GameObject model, string signature) { //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) EnsureCreated(); if ((Object)(object)_root == (Object)null || (Object)(object)model == (Object)null) { if ((Object)(object)model != (Object)null) { Object.Destroy((Object)(object)model); } return; } EnsureWeaponHolder(); if ((Object)(object)_weaponHolder == (Object)null) { Object.Destroy((Object)(object)model); return; } if ((Object)(object)_weaponModel != (Object)null) { Object.Destroy((Object)(object)_weaponModel); } _weaponModel = model; Transform transform = model.transform; transform.SetParent(_weaponHolder.transform, false); float num = SafeCfg(() => Plugin.Cfg.RemoteWeaponScale.Value, 1.4f); if (num <= 0f) { num = 1f; } float num2 = SafeCfg(() => Plugin.Cfg.RemoteWeaponRight.Value, 0.15f); float num3 = SafeCfg(() => Plugin.Cfg.RemoteWeaponForward.Value, 0.3f); transform.localPosition = new Vector3(num2 / num, 0f, num3 / num); transform.localRotation = Quaternion.identity; model.SetActive(true); _heldWeaponSig = signature ?? "none"; } public void ClearHeldWeapon() { if ((Object)(object)_weaponModel != (Object)null) { Object.Destroy((Object)(object)_weaponModel); } _weaponModel = null; _heldWeaponSig = "none"; } private void EnsureWeaponHolder() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0094: 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_00bb: 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_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_weaponHolder != (Object)null) && !((Object)(object)_root == (Object)null)) { _weaponHolder = new GameObject("HeldWeapon"); Transform transform = _weaponHolder.transform; transform.SetParent(_root.transform, false); Vector3 localScale = _root.transform.localScale; float num = SafeCfg(() => Plugin.Cfg.RemoteWeaponScale.Value, 1.4f); if (num <= 0f) { num = 1f; } transform.localScale = new Vector3((Mathf.Approximately(localScale.x, 0f) ? 1f : (1f / localScale.x)) * num, (Mathf.Approximately(localScale.y, 0f) ? 1f : (1f / localScale.y)) * num, (Mathf.Approximately(localScale.z, 0f) ? 1f : (1f / localScale.z)) * num); float num2 = SafeCfg(() => Plugin.Cfg.RemoteWeaponHipHeight.Value, 1.2f); float num3 = (Mathf.Approximately(localScale.y, 0f) ? 1f : localScale.y); transform.localPosition = new Vector3(0f, num2 / num3, 0f); transform.localRotation = Quaternion.identity; } } private void UpdateWeaponOrientation(bool downed) { //IL_0028: 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_00b8: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_weaponHolder == (Object)null) && !((Object)(object)_root == (Object)null)) { float num = _root.transform.localScale.y; if (Mathf.Approximately(num, 0f)) { num = 1f; } float num2 = (downed ? 0.15f : SafeCfg(() => Plugin.Cfg.RemoteWeaponHipHeight.Value, 1.2f)); Transform transform = _weaponHolder.transform; transform.localPosition = new Vector3(0f, num2 / num, 0f); transform.rotation = Quaternion.Euler(downed ? 0f : _lookPitch, _lookYaw, 0f); } } private static float SafeCfg(Func get, float fallback) { try { return get(); } catch { return fallback; } } private void EnsureCreated() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root != (Object)null) { return; } _root = GameObject.CreatePrimitive((PrimitiveType)1); ((Object)_root).name = "SULFUR Together Remote Player Proxy - " + _peerId; _root.transform.localScale = new Vector3(0.55f, 0.95f, 0.55f); Collider component = _root.GetComponent(); if ((Object)(object)component != (Object)null) { bool flag = true; try { flag = Plugin.Cfg.EnableRemotePlayerProxyCollision.Value; } catch { } bool flag2 = true; try { flag2 = Plugin.Cfg.RemotePlayerCollisionSoft.Value; } catch { } if (flag && flag2) { Object.Destroy((Object)(object)component); } else if (flag) { component.isTrigger = false; Rigidbody val = _root.GetComponent(); if ((Object)(object)val == (Object)null) { val = _root.AddComponent(); } val.isKinematic = true; val.useGravity = false; val.collisionDetectionMode = (CollisionDetectionMode)3; int num = ResolveLocalPlayerLayer(); if (num >= 0) { _root.layer = num; } if (!_loggedCollisionDiag) { _loggedCollisionDiag = true; int layer = _root.layer; bool flag3 = num >= 0 && Physics.GetIgnoreLayerCollision(layer, num); NetLogger.Info($"[RemotePlayerCollision] proxyLayer={layer} playerLayer={num} ignoreLayerCollision={flag3} colliderEnabled={component.enabled} isTrigger={component.isTrigger} rbKinematic={val.isKinematic}"); } } else { Object.Destroy((Object)(object)component); } } Renderer component2 = _root.GetComponent(); if ((Object)(object)component2 != (Object)null) { component2.material.color = new Color(0.55f, 0.25f, 0.95f, 0.72f); } BuildNameLabel(); _root.SetActive(false); } private void BuildNameLabel() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005d: 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_0082: 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_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: 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_0147: 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_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_023b: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Expected O, but got Unknown //IL_02d7: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root == (Object)null) && !((Object)(object)_labelHolder != (Object)null)) { _labelHolder = new GameObject("NameLabel"); Transform transform = _labelHolder.transform; transform.SetParent(_root.transform, false); Vector3 localScale = _root.transform.localScale; transform.localScale = new Vector3(Mathf.Approximately(localScale.x, 0f) ? 1f : (1f / localScale.x), Mathf.Approximately(localScale.y, 0f) ? 1f : (1f / localScale.y), Mathf.Approximately(localScale.z, 0f) ? 1f : (1f / localScale.z)); float num = SafeCfg(() => Plugin.Cfg.RemoteBodyScale.Value, 1.2f); float num2 = SafeCfg(() => Plugin.Cfg.RemoteNameHeight.Value, 0.45f); float num3 = 2f * num + num2; float num4 = (Mathf.Approximately(localScale.y, 0f) ? 1f : localScale.y); transform.localPosition = new Vector3(0f, num3 / num4, 0f); GameObject val = GameObject.CreatePrimitive((PrimitiveType)5); ((Object)val).name = "Bg"; Collider component = val.GetComponent(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } MeshRenderer component2 = val.GetComponent(); Shader val2 = Shader.Find("Sprites/Default"); if ((Object)(object)val2 != (Object)null) { ((Renderer)component2).material = new Material(val2); } ((Renderer)component2).material.color = LabelBgColor; ((Renderer)component2).sortingOrder = 0; ((Renderer)component2).shadowCastingMode = (ShadowCastingMode)0; ((Renderer)component2).receiveShadows = false; val.transform.SetParent(transform, false); val.transform.localPosition = new Vector3(0f, 0f, 0.01f); _labelBg = val.transform; GameObject val3 = new GameObject("Text"); val3.transform.SetParent(transform, false); val3.transform.localPosition = Vector3.zero; _label = val3.AddComponent(); _label.text = PlayerName; _label.characterSize = SafeCfg(() => Plugin.Cfg.RemoteNameSize.Value, 0.03f); _label.fontSize = 64; _label.anchor = (TextAnchor)4; _label.alignment = (TextAlignment)1; _label.color = LabelTextColor; MeshRenderer component3 = val3.GetComponent(); if ((Object)(object)component3 != (Object)null) { ((Renderer)component3).sortingOrder = 1; } UpdateNameLabelLayout(); } } private void UpdateNameLabelLayout() { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_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_004e: 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_005b: 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) if ((Object)(object)_label == (Object)null || (Object)(object)_labelBg == (Object)null) { return; } Renderer component = ((Component)_label).GetComponent(); float num = 0.5f; float num2 = 0.2f; if ((Object)(object)component != (Object)null) { Bounds bounds = component.bounds; Vector3 size = ((Bounds)(ref bounds)).size; if (size.x > 0.001f) { num = size.x; } if (size.y > 0.001f) { num2 = size.y; } } _labelBg.localScale = new Vector3(num + 0.12f, num2 + 0.06f, 1f); } private void SnapToTarget() { //IL_001b: 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) if (!((Object)(object)_root == (Object)null)) { _root.transform.position = _targetPosition; _root.transform.rotation = _targetRotation; } } private void UpdateLabelFacingCamera() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0044: 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_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_labelHolder == (Object)null) { return; } Camera main = Camera.main; if (!((Object)(object)main == (Object)null)) { Vector3 position = _labelHolder.transform.position; Vector3 val = ((Component)main).transform.position - position; Vector3 forward = default(Vector3); ((Vector3)(ref forward))..ctor(val.x, 0f, val.z); if (((Vector3)(ref forward)).sqrMagnitude < 1E-05f) { forward = Vector3.forward; } float num = SafeCfg(() => Plugin.Cfg.RemoteBodyPitchLimit.Value, 25f); float num2 = ((Vector3)(ref forward)).magnitude * Mathf.Tan(Mathf.Clamp(num, 0f, 89f) * ((float)Math.PI / 180f)); float num3 = Mathf.Clamp(val.y, 0f - num2, num2); Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(val.x, num3, val.z); if (((Vector3)(ref val2)).sqrMagnitude < 1E-06f) { val2 = forward; } _labelHolder.transform.rotation = Quaternion.LookRotation(-((Vector3)(ref val2)).normalized, Vector3.up); } } } public sealed class NetRemotePlayerProxyManager { private readonly Dictionary _proxies = new Dictionary(); private float _nextHideDiagAt; private float _nextProxyDiagAt; private const float SoftCollisionDeadzone = 0.03f; public int ProxyCount => _proxies.Count; public int VisibleCount => _proxies.Values.Count((NetRemotePlayerProxy p) => p.IsVisible); private static bool DiagOn { get { try { return Plugin.Cfg.LogRemotePlayerBody.Value; } catch { return false; } } } public void Clear() { foreach (NetRemotePlayerProxy value in _proxies.Values) { value.Destroy(); } _proxies.Clear(); } public void HideAll() { foreach (NetRemotePlayerProxy value in _proxies.Values) { value.Hide(); } } public void Remove(string peerId) { if (!string.IsNullOrWhiteSpace(peerId)) { if (_proxies.TryGetValue(peerId, out NetRemotePlayerProxy value)) { value.Destroy(); } _proxies.Remove(peerId); } } public void Apply(NetPlayerTransformState state, NetRunState localRunState, float now, bool requireSeedMatch) { //IL_014b: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(state.PeerId)) { return; } if (!CanDisplayInLocalScene(state, localRunState, requireSeedMatch)) { if (_proxies.TryGetValue(state.PeerId, out NetRemotePlayerProxy value)) { value.Hide(); } if (DiagOn && now >= _nextHideDiagAt) { _nextHideDiagAt = now + 2f; NetLogger.Info(string.Format("[RemoteProxyDiag] REJECTED peer={0} hadProxy={1}: remote {2}:{3} seed={4} vs local {5}:{6} seed={7} requireSeedMatch={8}", state.PeerId, value != null, state.ChapterName, state.LevelIndex, state.HasLevelSeed ? state.LevelSeed.ToString() : "?", localRunState.ChapterName, localRunState.LevelIndex, localRunState.HasLevelSeed ? localRunState.LevelSeed.ToString() : "?", requireSeedMatch)); } } else { if (!_proxies.TryGetValue(state.PeerId, out NetRemotePlayerProxy value2)) { value2 = new NetRemotePlayerProxy(state.PeerId); _proxies[state.PeerId] = value2; NetLogger.Info(string.Format("[RemotePlayer] Visual proxy created for {0} at {1} scene={2}:{3} seed={4}", state.PeerId, state.Position, state.ChapterName, state.LevelIndex, state.HasLevelSeed ? state.LevelSeed.ToString() : "?")); } value2.Apply(state, now); } } public void Tick(float deltaTime, float now, float timeoutSeconds, float interpolationSpeed, float snapDistance) { //IL_00a9: Unknown result type (might be due to invalid IL or missing references) NetRemotePlayerProxy[] array = _proxies.Values.ToArray(); for (int i = 0; i < array.Length; i++) { array[i].Tick(deltaTime, now, timeoutSeconds, interpolationSpeed, snapDistance); } if (!DiagOn || _proxies.Count <= 0 || !(now >= _nextProxyDiagAt)) { return; } _nextProxyDiagAt = now + 2f; foreach (KeyValuePair proxy in _proxies) { NetRemotePlayerProxy value = proxy.Value; NetLogger.Info($"[RemoteProxyDiag] peer={proxy.Key} visible={value.IsVisible} pos={value.TargetPosition} ageSinceUpdate={now - value.LastUpdatedAt:0.0}s"); } } public string FormatStatus() { return $"remoteVisuals={VisibleCount}/{ProxyCount}"; } public void ApplySoftCollision(Transform localPlayer, float radius, float maxPushSpeed, float deltaTime) { //IL_0034: 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_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: 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_008e: 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_009e: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)localPlayer == (Object)null || radius <= 0f || deltaTime <= 0f) { return; } Rigidbody componentInParent = ((Component)localPlayer).GetComponentInParent(); Vector3 val = (((Object)(object)componentInParent != (Object)null) ? componentInParent.position : localPlayer.position); float num = maxPushSpeed * deltaTime; float num2 = 0f; float num3 = 0f; foreach (NetRemotePlayerProxy value in _proxies.Values) { if (value == null || !value.IsVisible) { continue; } Vector3 visualPosition = value.VisualPosition; float num4 = val.x - visualPosition.x; float num5 = val.z - visualPosition.z; float num6 = num4 * num4 + num5 * num5; if (num6 >= radius * radius) { continue; } float num7 = Mathf.Sqrt(num6); float num8 = radius - num7; if (!(num8 <= 0.03f)) { float num9 = Mathf.Min(num8 - 0.03f, num); if (num7 > 0.0001f) { num2 += num4 / num7 * num9; num3 += num5 / num7 * num9; } else { num2 += num9; } } } if (num2 != 0f || num3 != 0f) { Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(val.x + num2, val.y, val.z + num3); if ((Object)(object)componentInParent != (Object)null) { componentInParent.MovePosition(val2); } else { localPlayer.position = val2; } } } public void CollectInterestPositions(List into, float now, float maxAgeSeconds) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (into == null) { return; } foreach (NetRemotePlayerProxy value in _proxies.Values) { if (value.IsVisible && (!(maxAgeSeconds > 0f) || !(now - value.LastUpdatedAt > maxAgeSeconds))) { into.Add(value.TargetPosition); } } } public void ForEachInScenePlayer(Action action, float now, float maxAgeSeconds) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) if (action == null) { return; } foreach (KeyValuePair proxy in _proxies) { NetRemotePlayerProxy value = proxy.Value; if (value.IsVisible && (!(maxAgeSeconds > 0f) || !(now - value.LastUpdatedAt > maxAgeSeconds))) { action(proxy.Key, value.TargetPosition); } } } public void ForEachInScenePlayerTransform(Action action, float now, float maxAgeSeconds) { if (action == null) { return; } foreach (KeyValuePair proxy in _proxies) { NetRemotePlayerProxy value = proxy.Value; if (value.IsVisible && (!(maxAgeSeconds > 0f) || !(now - value.LastUpdatedAt > maxAgeSeconds))) { Transform visualTransform = value.VisualTransform; if ((Object)(object)visualTransform != (Object)null) { action(proxy.Key, visualTransform); } } } } internal void ForEachProxy(Action action) { if (action != null) { KeyValuePair[] array = _proxies.ToArray(); for (int i = 0; i < array.Length; i++) { KeyValuePair keyValuePair = array[i]; action(keyValuePair.Key, keyValuePair.Value); } } } private static bool CanDisplayInLocalScene(NetPlayerTransformState state, NetRunState localRunState, bool requireSeedMatch) { if (!state.HasScene || !localRunState.HasLevel) { return false; } if (!NetSceneName.SameScene(state.ChapterName, state.LevelIndex, localRunState.ChapterName, localRunState.LevelIndex)) { return false; } if (!requireSeedMatch) { return true; } if (!state.HasLevelSeed || !localRunState.HasLevelSeed) { return false; } return state.LevelSeed == localRunState.LevelSeed; } } public sealed class NetRunState { public string PeerId { get; set; } = ""; public string PlayerName { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public string LoadingMode { get; set; } = ""; public string SpawnIdentifier { get; set; } = ""; public string GameState { get; set; } = ""; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public string LevelGenerator { get; set; } = ""; public int Revision { get; set; } public float LastUpdatedAt { get; set; } public bool HasLevel { get { if (!string.IsNullOrWhiteSpace(ChapterName) && ChapterName != "") { return LevelIndex >= 0; } return false; } } public bool HasKnownGameState { get { if (!string.IsNullOrWhiteSpace(GameState)) { return GameState != ""; } return false; } } public bool IsLoadingLikeState => IsLoadingLike(GameState); public bool IsStableGameState { get { if (HasKnownGameState) { return !IsLoadingLikeState; } return false; } } public NetRunState Clone() { return new NetRunState { PeerId = PeerId, PlayerName = PlayerName, ChapterName = ChapterName, LevelIndex = LevelIndex, LoadingMode = LoadingMode, SpawnIdentifier = SpawnIdentifier, GameState = GameState, HasLevelSeed = HasLevelSeed, LevelSeed = LevelSeed, LevelGenerator = LevelGenerator, Revision = Revision, LastUpdatedAt = LastUpdatedAt }; } public bool SameSceneAs(NetRunState other) { if (other == null) { return false; } if (HasLevel && other.HasLevel) { return NetSceneName.SameScene(ChapterName, LevelIndex, other.ChapterName, other.LevelIndex); } return false; } public bool SameLevelSeedAs(NetRunState other) { if (other == null) { return false; } if (!HasLevelSeed || !other.HasLevelSeed) { return false; } return LevelSeed == other.LevelSeed; } public bool SameLevelInstanceAs(NetRunState other, bool requireSeed) { if (!SameSceneAs(other)) { return false; } if (!requireSeed) { return true; } if (!HasLevelSeed || !other.HasLevelSeed) { return false; } return LevelSeed == other.LevelSeed; } public bool HasKnownSeedMismatch(NetRunState other) { if (other == null) { return false; } if (SameSceneAs(other) && HasLevelSeed && other.HasLevelSeed) { return LevelSeed != other.LevelSeed; } return false; } public bool SameStableStateAs(NetRunState other) { if (other == null) { return false; } if (IsStableGameState && other.IsStableGameState) { return Clean(GameState) == Clean(other.GameState); } return false; } public string SceneKey() { return $"{Clean(ChapterName)}:{LevelIndex}"; } public string CompareKey() { return $"{Clean(ChapterName)}:{LevelIndex}:{Clean(GameState)}"; } public string SceneCompareKey() { return NetSceneName.SceneCompareKey(ChapterName, LevelIndex); } public string LevelInstanceKey() { string text = (HasLevelSeed ? LevelSeed.ToString() : "?"); return SceneCompareKey() + "#seed=" + text; } public string ToCompactString() { string text = (string.IsNullOrWhiteSpace(PeerId) ? "?" : PeerId); string text2 = (string.IsNullOrWhiteSpace(PlayerName) ? "?" : PlayerName); string text3 = (HasLevelSeed ? $",seed={LevelSeed}" : ",seed=?"); return $"{text2}(id={text},scene={SceneKey()}{text3},state={Clean(GameState)},rev={Revision})"; } private static bool IsLoadingLike(string value) { value = Clean(value); switch (value) { default: return value == "Cinematic"; case "": case "Uninitialized": case "Loading": return true; } } private static string Clean(string value) { if (!string.IsNullOrWhiteSpace(value)) { return value.Trim(); } return ""; } } public static class NetRunStateBridge { private static NetService? _service; private static bool _haveCachedLevel; private static string _cachedChapter = ""; private static int _cachedLevelIndex = -1; private static string _cachedLoadingMode = ""; private static string _cachedSpawn = ""; private static object _cachedPlayer; internal static void Attach(NetService? service) { _service = service; } public static void ReportGoToLevel(string chapterName, int levelIndex, string loadingMode, string spawnIdentifier) { _haveCachedLevel = true; _cachedChapter = chapterName; _cachedLevelIndex = levelIndex; _cachedLoadingMode = loadingMode; _cachedSpawn = spawnIdentifier; _service?.ReportLocalGoToLevel(chapterName, levelIndex, loadingMode, spawnIdentifier); } public static void PrimeServiceFromCache(NetService service) { if (service == null) { return; } if (_haveCachedLevel && !string.IsNullOrWhiteSpace(_cachedChapter) && _cachedChapter != "") { service.ReportLocalGoToLevel(_cachedChapter, _cachedLevelIndex, _cachedLoadingMode, _cachedSpawn); } try { NetLevelSeed.ResetReportLatch(); NetLevelSeed.ReportObservedGameManagerSeed("service-start-prime"); } catch { } } public static void ReportGameState(string gameState) { _service?.ReportLocalGameState(gameState); } public static void ReportClearLevel() { _service?.ReportLocalClearLevel(); } public static void ReportLevelSeed(int seed, string generatorName) { _service?.ReportLocalLevelSeed(seed, generatorName); } public static void ReportLocalPlayerObject(object player) { _cachedPlayer = player; _service?.ReportLocalPlayerObject(player); } public static void PrimeLocalPlayer(NetService service) { if (service == null) { return; } object cachedPlayer = _cachedPlayer; if (cachedPlayer != null) { Object val = (Object)((cachedPlayer is Object) ? cachedPlayer : null); if (val == null || !(val == (Object)null)) { service.ReportLocalPlayerObject(cachedPlayer); } } } public static void ApplyFinalizedGenerationSnapshot(NetGenerationInputSnapshot snapshot) { _service?.ApplyFinalizedGenerationSnapshot(snapshot); } public static bool TryGetLocalRunState(out NetRunState state) { state = new NetRunState(); if (_service == null) { return false; } state = _service.GetLocalRunStateSnapshot(); return state.Revision > 0; } } internal static class NetRunStateCodec { public static void Write(NetDataWriter w, NetRunState state) { w.Put(state.PeerId ?? ""); w.Put(state.PlayerName ?? ""); w.Put(state.ChapterName ?? ""); w.Put(state.LevelIndex); w.Put(state.LoadingMode ?? ""); w.Put(state.SpawnIdentifier ?? ""); w.Put(state.GameState ?? ""); w.Put(state.HasLevelSeed ? 1 : 0); w.Put(state.LevelSeed); w.Put(state.LevelGenerator ?? ""); w.Put(state.Revision); w.Put(state.LastUpdatedAt); } public static bool TryRead(NetDataReader r, out NetRunState state) { state = new NetRunState(); try { state.PeerId = r.GetString(); state.PlayerName = r.GetString(); state.ChapterName = r.GetString(); state.LevelIndex = r.GetInt(); state.LoadingMode = r.GetString(); state.SpawnIdentifier = r.GetString(); state.GameState = r.GetString(); if (r.AvailableBytes >= 4) { state.HasLevelSeed = r.GetInt() != 0; } if (r.AvailableBytes >= 4) { state.LevelSeed = r.GetInt(); } if (r.AvailableBytes > 0) { state.LevelGenerator = r.GetString(); } if (r.AvailableBytes >= 4) { state.Revision = r.GetInt(); } if (r.AvailableBytes >= 4) { state.LastUpdatedAt = r.GetFloat(); } return true; } catch { return false; } } } public sealed class NetRunStateManager { private readonly Dictionary _remoteStates = new Dictionary(); private readonly Dictionary _lastMismatchKeys = new Dictionary(); private readonly Dictionary _lastAuthorityKeys = new Dictionary(); private readonly Dictionary _lastStateNoticeKeys = new Dictionary(); public NetRunState LocalState { get; private set; } = new NetRunState(); public IReadOnlyCollection RemoteStates => _remoteStates.Values.ToList().AsReadOnly(); public void Clear() { LocalState = new NetRunState(); _remoteStates.Clear(); _lastMismatchKeys.Clear(); _lastAuthorityKeys.Clear(); _lastStateNoticeKeys.Clear(); } public void SetLocalIdentity(string peerId, string playerName) { LocalState.PeerId = (string.IsNullOrWhiteSpace(peerId) ? LocalState.PeerId : peerId); LocalState.PlayerName = (string.IsNullOrWhiteSpace(playerName) ? LocalState.PlayerName : playerName); } public NetRunState UpdateLocalGoToLevel(string chapterName, int levelIndex, string loadingMode, string spawnIdentifier, float now) { LocalState.ChapterName = Clean(chapterName, ""); LocalState.LevelIndex = levelIndex; LocalState.LoadingMode = Clean(loadingMode, ""); LocalState.SpawnIdentifier = Clean(spawnIdentifier, ""); LocalState.HasLevelSeed = false; LocalState.LevelSeed = 0; LocalState.LevelGenerator = ""; LocalState.LastUpdatedAt = now; LocalState.Revision++; return LocalState.Clone(); } public NetRunState UpdateLocalGameState(string gameState, float now) { LocalState.GameState = Clean(gameState, ""); LocalState.LastUpdatedAt = now; LocalState.Revision++; return LocalState.Clone(); } public NetRunState UpdateLocalClearLevel(float now) { LocalState.LastUpdatedAt = now; LocalState.HasLevelSeed = false; LocalState.LevelSeed = 0; LocalState.LevelGenerator = ""; LocalState.Revision++; return LocalState.Clone(); } public bool ApplyFinalizedGenerationSnapshot(string chapter, int levelIndex, bool hasSeed, int seed, string graphName, float now, out bool levelCorrected, out NetRunState updated) { bool flag = false; levelCorrected = false; if (!string.IsNullOrWhiteSpace(chapter) && chapter != "" && LocalState.ChapterName != chapter) { LocalState.ChapterName = Clean(chapter, ""); flag = true; } if (levelIndex >= 0 && LocalState.LevelIndex != levelIndex) { LocalState.LevelIndex = levelIndex; flag = true; levelCorrected = true; } if (hasSeed && (!LocalState.HasLevelSeed || LocalState.LevelSeed != seed)) { LocalState.HasLevelSeed = true; LocalState.LevelSeed = seed; flag = true; } if (!string.IsNullOrWhiteSpace(graphName) && LocalState.LevelGenerator != graphName) { LocalState.LevelGenerator = Clean(graphName, ""); flag = true; } if (flag) { LocalState.LastUpdatedAt = now; LocalState.Revision++; } updated = LocalState.Clone(); return flag; } public NetRunState UpdateLocalLevelSeed(int seed, string generatorName, float now) { LocalState.HasLevelSeed = true; LocalState.LevelSeed = seed; LocalState.LevelGenerator = Clean(generatorName, ""); LocalState.LastUpdatedAt = now; LocalState.Revision++; return LocalState.Clone(); } public void UpdateRemote(string peerId, NetRunState state, float now) { if (string.IsNullOrWhiteSpace(peerId)) { peerId = state.PeerId; } if (string.IsNullOrWhiteSpace(peerId)) { peerId = "unknown"; } state.PeerId = peerId; state.LastUpdatedAt = now; _remoteStates[peerId] = state.Clone(); } public void RemoveRemote(string peerId) { if (!string.IsNullOrWhiteSpace(peerId)) { _remoteStates.Remove(peerId); _lastMismatchKeys.Remove(peerId); _lastAuthorityKeys.Remove(peerId); _lastStateNoticeKeys.Remove(peerId); } } public bool TryGetRemote(string peerId, out NetRunState state) { state = new NetRunState(); if (string.IsNullOrWhiteSpace(peerId)) { return false; } if (!_remoteStates.TryGetValue(peerId, out NetRunState value)) { return false; } state = value.Clone(); return true; } public bool TryBuildMismatchWarning(string peerId, out string warning) { warning = ""; if (string.IsNullOrWhiteSpace(peerId)) { return false; } if (!_remoteStates.TryGetValue(peerId, out NetRunState value)) { return false; } if (!LocalState.HasLevel || !value.HasLevel) { return false; } bool requireSeed = RequireSameSeedForSceneMatch(); if (LocalState.SameLevelInstanceAs(value, requireSeed)) { return false; } if (IsTransientSceneComparison(LocalState, value, requireSeed)) { return false; } string text = "scene|" + LocalState.LevelInstanceKey() + "|" + value.LevelInstanceKey(); if (_lastMismatchKeys.TryGetValue(peerId, out string value2) && value2 == text) { return false; } _lastMismatchKeys[peerId] = text; warning = "[RunState] Scene mismatch with " + peerId + ": local=" + LocalState.ToCompactString() + " remote=" + value.ToCompactString(); return true; } public bool TryBuildStateDifferenceNotice(string peerId, out string notice) { notice = ""; if (string.IsNullOrWhiteSpace(peerId)) { return false; } if (!_remoteStates.TryGetValue(peerId, out NetRunState value)) { return false; } if (!LocalState.HasLevel || !value.HasLevel) { return false; } if (!LocalState.SameSceneAs(value)) { return false; } if (!LocalState.IsStableGameState || !value.IsStableGameState) { return false; } if (LocalState.SameStableStateAs(value)) { return false; } string text = "state|" + LocalState.CompareKey() + "|" + value.CompareKey(); if (_lastStateNoticeKeys.TryGetValue(peerId, out string value2) && value2 == text) { return false; } _lastStateNoticeKeys[peerId] = text; notice = "[RunState] Same scene but different stable GameState with " + peerId + ": local=" + LocalState.ToCompactString() + " remote=" + value.ToCompactString(); return true; } public bool TryBuildHostAuthorityWarning(out string warning) { warning = ""; if (!_remoteStates.TryGetValue("host", out NetRunState value)) { return false; } if (!LocalState.HasLevel || !value.HasLevel) { return false; } bool requireSeed = RequireSameSeedForSceneMatch(); if (LocalState.SameLevelInstanceAs(value, requireSeed)) { return false; } if (IsTransientSceneComparison(LocalState, value, requireSeed)) { return false; } string text = "client-authority|" + LocalState.LevelInstanceKey() + "|" + value.LevelInstanceKey(); if (_lastAuthorityKeys.TryGetValue("host", out string value2) && value2 == text) { return false; } _lastAuthorityKeys["host"] = text; warning = "[SceneAuthority] Client is not in host scene: local=" + LocalState.ToCompactString() + " host=" + value.ToCompactString() + " action=manual-only"; return true; } public bool TryBuildClientSceneDriftWarning(string peerId, out string warning) { warning = ""; if (string.IsNullOrWhiteSpace(peerId)) { return false; } if (!_remoteStates.TryGetValue(peerId, out NetRunState value)) { return false; } if (!LocalState.HasLevel || !value.HasLevel) { return false; } bool requireSeed = RequireSameSeedForSceneMatch(); if (LocalState.SameLevelInstanceAs(value, requireSeed)) { return false; } if (IsTransientSceneComparison(LocalState, value, requireSeed)) { return false; } string text = "host-authority|" + LocalState.LevelInstanceKey() + "|" + value.LevelInstanceKey(); if (_lastAuthorityKeys.TryGetValue(peerId, out string value2) && value2 == text) { return false; } _lastAuthorityKeys[peerId] = text; warning = "[SceneAuthority] Client " + peerId + " is not in host scene: host=" + LocalState.ToCompactString() + " client=" + value.ToCompactString() + " action=observe-only"; return true; } public string FormatStatus() { string text = ((LocalState.Revision <= 0) ? "local=" : ("local=" + LocalState.ToCompactString())); if (_remoteStates.Count == 0) { return "run:" + text + " remotes=0"; } IEnumerable values = from s in _remoteStates.Values orderby s.PeerId select s.ToCompactString(); return string.Format("run:{0} remotes={1} [{2}]", text, _remoteStates.Count, string.Join("; ", values)); } public string FormatAuthorityStatus(NetMode mode) { switch (mode) { case NetMode.Host: { int num = 0; if (LocalState.HasLevel) { bool requireSeed = RequireSameSeedForSceneMatch(); num = _remoteStates.Values.Count((NetRunState s) => s.HasLevel && !LocalState.SameLevelInstanceAs(s, requireSeed) && !IsTransientSceneComparison(LocalState, s, requireSeed)); } return $"authority=HostScene localReady={LocalState.HasLevel} clientDrift={num}"; } case NetMode.Client: { NetRunState value; bool flag = _remoteStates.TryGetValue("host", out value) && value.HasLevel; bool flag2 = flag && LocalState.HasLevel && LocalState.SameLevelInstanceAs(value, RequireSameSeedForSceneMatch()); return $"authority=HostScene hostKnown={flag} inHostScene={flag2}"; } default: return "authority=Off"; } } public string FormatBossConvergence(out bool allConverged, out int peerCount, out int convergedCount) { bool flag = RequireSameSeedForSceneMatch(); peerCount = 0; convergedCount = 0; List list = new List(); foreach (NetRunState value in _remoteStates.Values) { peerCount++; bool num = LocalState.HasLevel && value.HasLevel && LocalState.SameLevelInstanceAs(value, flag); bool flag2 = LocalState.HasKnownSeedMismatch(value); if (num) { convergedCount++; } string text = (num ? "OK" : (flag2 ? "SEED-SPLIT" : (value.HasLevel ? "DIFF-SCENE" : "NO-LEVEL"))); list.Add(value.ToCompactString() + "=>" + text); } allConverged = peerCount > 0 && convergedCount == peerCount; string text2 = ((list.Count == 0) ? "" : string.Join(" ", list)); return $"local={LocalState.ToCompactString()} requireSeed={flag} converged={convergedCount}/{peerCount} | {text2}"; } private static bool RequireSameSeedForSceneMatch() { try { return Plugin.Cfg.EnableLevelSeedAuthority.Value && Plugin.Cfg.RequireSameLevelSeedForSceneMatch.Value; } catch { return false; } } private static bool IsTransientSceneComparison(NetRunState left, NetRunState right, bool requireSeed) { if (left.IsLoadingLikeState || right.IsLoadingLikeState) { return true; } if (!requireSeed) { return false; } if (left.HasLevelSeed) { return !right.HasLevelSeed; } return true; } private static string Clean(string value, string fallback) { if (string.IsNullOrWhiteSpace(value)) { return fallback; } value = value.Trim(); if (value.Length <= 64) { return value; } return value.Substring(0, 64); } } internal static class NetRunStatsClientCache { private static int _lastAppliedRunSeq = -1; public static IReadOnlyList? LastFinalized { get; private set; } public static bool PendingRunEndDisplay { get; private set; } public static void ApplyReceivedBroadcast(NetRunStatsList list) { if (list == null || list.RunSeq <= _lastAppliedRunSeq) { return; } _lastAppliedRunSeq = list.RunSeq; LastFinalized = list.Players.AsReadOnly(); PendingRunEndDisplay = true; NetLogger.Info($"[RunStats] Client received finalized runSeq={list.RunSeq} {list.Players.Count} player(s)"); foreach (NetRunStats player in list.Players) { NetLogger.Info("[RunStats] " + player.ToCompactString()); } } public static void ApplyLocalFinalize(int runSeq, IReadOnlyList players) { if (runSeq > _lastAppliedRunSeq) { _lastAppliedRunSeq = runSeq; LastFinalized = players; PendingRunEndDisplay = true; } } public static void ConsumeAndClear() { PendingRunEndDisplay = false; LastFinalized = null; } } internal static class NetSceneClassify { public static bool IsHubOrSafeZoneGraph(string? graphName) { return MatchesHub(graphName); } public static bool IsHubOrSafeZoneChapter(string? chapter) { return MatchesHub(chapter); } private static bool MatchesHub(string? s) { string text = (s ?? string.Empty).ToLowerInvariant(); if (text.Length == 0) { return false; } if (!text.Contains("hub") && !text.Contains("safezone") && !text.Contains("safe_zone") && !text.Contains("town") && !text.Contains("hideout") && !text.Contains("vendor") && !text.Contains("xmas") && !text.Contains("sanctuary") && !text.Contains("camp")) { return text.Contains("market"); } return true; } public static bool IsCombatLikeChapter(string? chapter) { string text = (chapter ?? string.Empty).ToLowerInvariant(); if (text.Length == 0) { return false; } if (MatchesHub(text)) { return false; } if (!text.Contains("act_") && !text.Contains("act0") && !text.Contains("caves") && !text.Contains("fortress") && !text.Contains("dungeon") && !text.Contains("combat") && !text.Contains("crypt") && !text.Contains("lair")) { return text.Contains("level"); } return true; } } internal static class NetSceneFollowDiag { public static int HostSceneUsedSetsCaptured; public static int HostSceneUsedSetsSent; public static int ClientSceneUsedSetsReceived; public static int ClientFollowUsedSetsApplied; public static int ClientFollowUsedSetsApplyFailed; public static int ClientFollowUsedChunksBeforeCount; public static int ClientFollowUsedChunksAfterCount; public static int ClientFollowUsedEventsRunBeforeCount; public static int ClientFollowUsedEventsRunAfterCount; public static int ClientFollowUsedEventsEnvBeforeCount; public static int ClientFollowUsedEventsEnvAfterCount; public static int ClientLoadedAckSent; public static int HostClientLoadedAckReceived; public static int HostGenerationInputSentOnHandshake; public static int HostGenerationInputSentOnRequest; public static int HostGenerationInputNoSnapshotOnHandshake; public static int HostGenerationInputNoSnapshotOnRequest; public static int HubSnapshotIdentityCorrected; public static int HubSnapshotIdentityFallback; public static int HubSnapshotPendingCombatIgnored; public static int HubReturnDeferredMissingSeed; public static int HubReturnPreliminaryRequestSent; public static int HubReturnFinalizedRequestSent; public static int HubAutoFollowSkippedMissingSeed; public static int HubAutoFollowWaitingFinalized; public static int HubAutoFollowStartedFinalized; public static int HubSeedMismatchReload; public static int HubSeedMatched; public static int SceneAckGraphMismatch; public static int SceneAckGraphMatched; public static int AutoFollowReloadGraphMismatch; public static string FormatGraphIdentity() { return $"ackGraphMismatch={SceneAckGraphMismatch} ackGraphMatched={SceneAckGraphMatched} autoFollowReloadGraphMismatch={AutoFollowReloadGraphMismatch}"; } public static string FormatHubReturn() { return $"snapCorrected={HubSnapshotIdentityCorrected} snapFallback={HubSnapshotIdentityFallback} snapPendingCombatIgnored={HubSnapshotPendingCombatIgnored} " + $"deferredMissingSeed={HubReturnDeferredMissingSeed} preliminarySent={HubReturnPreliminaryRequestSent} finalizedSent={HubReturnFinalizedRequestSent} " + $"clientSkipMissingSeed={HubAutoFollowSkippedMissingSeed} clientWaiting={HubAutoFollowWaitingFinalized} clientStartedFinalized={HubAutoFollowStartedFinalized} " + $"seedMismatchReload={HubSeedMismatchReload} seedMatched={HubSeedMatched}"; } public static void IncCaptured() { Interlocked.Increment(ref HostSceneUsedSetsCaptured); } public static void IncSent() { Interlocked.Increment(ref HostSceneUsedSetsSent); } public static void IncReceived() { Interlocked.Increment(ref ClientSceneUsedSetsReceived); } public static void IncApplied() { Interlocked.Increment(ref ClientFollowUsedSetsApplied); } public static void IncApplyFailed() { Interlocked.Increment(ref ClientFollowUsedSetsApplyFailed); } public static string Format() { return $"captured={HostSceneUsedSetsCaptured} sent={HostSceneUsedSetsSent} received={ClientSceneUsedSetsReceived} " + $"applied={ClientFollowUsedSetsApplied} applyFailed={ClientFollowUsedSetsApplyFailed} " + $"chunks={ClientFollowUsedChunksBeforeCount}->{ClientFollowUsedChunksAfterCount} " + $"eventsRun={ClientFollowUsedEventsRunBeforeCount}->{ClientFollowUsedEventsRunAfterCount} " + $"eventsEnv={ClientFollowUsedEventsEnvBeforeCount}->{ClientFollowUsedEventsEnvAfterCount}"; } } internal static class NetSceneName { private static readonly Dictionary Aliases = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Act_01_HedgemazeFromChurch", "Act_01_Hedgemaze" }, { "HedgemazeFromChurch", "Act_01_Hedgemaze" } }; public static string Clean(string? value) { if (value == null) { return ""; } string text = value.Trim(); if (text.Length != 0) { return text; } return ""; } public static string Canonicalize(string? value) { string text = Clean(value); if (text == "") { return text; } if (Aliases.TryGetValue(text, out string value2)) { return value2; } if (text.EndsWith("FromChurch", StringComparison.OrdinalIgnoreCase)) { string text2 = text.Substring(0, text.Length - "FromChurch".Length); if (!string.IsNullOrWhiteSpace(text2)) { return text2; } } return text; } public static bool SameScene(string? leftChapter, int leftLevel, string? rightChapter, int rightLevel) { if (leftLevel >= 0 && rightLevel >= 0 && leftLevel == rightLevel) { return string.Equals(Canonicalize(leftChapter), Canonicalize(rightChapter), StringComparison.OrdinalIgnoreCase); } return false; } public static string SceneKey(string? chapterName, int levelIndex) { return $"{Clean(chapterName)}:{levelIndex}"; } public static string SceneCompareKey(string? chapterName, int levelIndex) { return $"{Canonicalize(chapterName)}:{levelIndex}"; } public static IEnumerable LookupCandidates(string? value) { string clean = Clean(value); if (clean == "") { yield break; } yield return clean; string canonical = Canonicalize(clean); if (!string.Equals(canonical, clean, StringComparison.OrdinalIgnoreCase)) { yield return canonical; } if (canonical.StartsWith("Act_", StringComparison.OrdinalIgnoreCase)) { int num = canonical.IndexOf('_', 4); if (num > 0 && num + 1 < canonical.Length) { yield return canonical.Substring(num + 1); } } } } public sealed class NetHostSceneRequest { public string RequestId { get; set; } = ""; public string HostPeerId { get; set; } = "host"; public string HostPlayerName { get; set; } = "Host"; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public string LoadingMode { get; set; } = ""; public string SpawnIdentifier { get; set; } = ""; public string HostGameState { get; set; } = ""; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public string LevelGenerator { get; set; } = ""; public int HostRevision { get; set; } public string Reason { get; set; } = "HostSceneAuthority"; public bool AutoLoadAllowed { get; set; } public bool HasUsedSets { get; set; } public List UsedChunksThisRun { get; set; } = new List(); public List UsedEventsThisRun { get; set; } = new List(); public List UsedEventsThisEnvironment { get; set; } = new List(); public string GraphName { get; set; } = ""; public string GenerationRunId { get; set; } = ""; public bool HasTargetScene { get { if (!string.IsNullOrWhiteSpace(ChapterName) && ChapterName != "") { return LevelIndex >= 0; } return false; } } public NetHostUsedSets ToUsedSets() { return new NetHostUsedSets { Captured = HasUsedSets, UsedChunksThisRun = new List(UsedChunksThisRun ?? new List()), UsedEventsThisRun = new List(UsedEventsThisRun ?? new List()), UsedEventsThisEnvironment = new List(UsedEventsThisEnvironment ?? new List()) }; } public void SetUsedSets(NetHostUsedSets sets) { if (sets == null) { HasUsedSets = false; return; } HasUsedSets = true; UsedChunksThisRun = new List(sets.UsedChunksThisRun ?? new List()); UsedEventsThisRun = new List(sets.UsedEventsThisRun ?? new List()); UsedEventsThisEnvironment = new List(sets.UsedEventsThisEnvironment ?? new List()); } public string TargetSceneKey() { string arg = (string.IsNullOrWhiteSpace(ChapterName) ? "" : ChapterName.Trim()); return $"{arg}:{LevelIndex}"; } public string ToCompactString() { string text = (string.IsNullOrWhiteSpace(RequestId) ? "?" : RequestId); string text2 = (string.IsNullOrWhiteSpace(HostPeerId) ? "host" : HostPeerId); string text3 = (HasLevelSeed ? $" seed={LevelSeed}" : " seed=?"); return $"request={text} host={text2} target={TargetSceneKey()}{text3} state={Clean(HostGameState)} rev={HostRevision} autoLoad={AutoLoadAllowed}"; } private static string Clean(string value) { if (!string.IsNullOrWhiteSpace(value)) { return value.Trim(); } return ""; } } public sealed class NetClientSceneResponse { public string RequestId { get; set; } = ""; public string ClientPeerId { get; set; } = ""; public string ClientPlayerName { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public string GameState { get; set; } = ""; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public string LevelGenerator { get; set; } = ""; public int LocalRevision { get; set; } public bool IsInTargetScene { get; set; } public string FollowPhase { get; set; } = "None"; public string Message { get; set; } = ""; public string SceneKey() { string arg = (string.IsNullOrWhiteSpace(ChapterName) ? "" : ChapterName.Trim()); return $"{arg}:{LevelIndex}"; } public string ToCompactString() { string text = (string.IsNullOrWhiteSpace(RequestId) ? "?" : RequestId); string text2 = (string.IsNullOrWhiteSpace(ClientPeerId) ? "?" : ClientPeerId); string text3 = (HasLevelSeed ? $" seed={LevelSeed}" : " seed=?"); return $"request={text} client={text2} scene={SceneKey()}{text3} state={Clean(GameState)} rev={LocalRevision} inTarget={IsInTargetScene} phase={Clean(FollowPhase)} msg='{Clean(Message)}'"; } private static string Clean(string value) { if (!string.IsNullOrWhiteSpace(value)) { return value.Trim(); } return ""; } } internal static class NetSceneRequestCodec { public static void WriteHostRequest(NetDataWriter w, NetHostSceneRequest request) { w.Put(request.RequestId ?? ""); w.Put(request.HostPeerId ?? "host"); w.Put(request.HostPlayerName ?? "Host"); w.Put(request.ChapterName ?? ""); w.Put(request.LevelIndex); w.Put(request.LoadingMode ?? ""); w.Put(request.SpawnIdentifier ?? ""); w.Put(request.HostGameState ?? ""); w.Put(request.HasLevelSeed ? 1 : 0); w.Put(request.LevelSeed); w.Put(request.LevelGenerator ?? ""); w.Put(request.HostRevision); w.Put(request.Reason ?? "HostSceneAuthority"); w.Put(request.AutoLoadAllowed ? 1 : 0); w.Put(request.HasUsedSets ? 1 : 0); WriteStringList(w, request.UsedChunksThisRun); WriteStringList(w, request.UsedEventsThisRun); WriteStringList(w, request.UsedEventsThisEnvironment); w.Put(request.GraphName ?? ""); w.Put(request.GenerationRunId ?? ""); } private static void WriteStringList(NetDataWriter w, List? list) { int num = list?.Count ?? 0; w.Put(num); for (int i = 0; i < num; i++) { w.Put(list[i] ?? ""); } } private static List ReadStringList(NetDataReader r) { List list = new List(); if (r.AvailableBytes < 4) { return list; } int num = r.GetInt(); for (int i = 0; i < num; i++) { if (r.AvailableBytes <= 0) { break; } list.Add(r.GetString()); } return list; } public static bool TryReadHostRequest(NetDataReader r, out NetHostSceneRequest request) { request = new NetHostSceneRequest(); try { request.RequestId = r.GetString(); request.HostPeerId = r.GetString(); request.HostPlayerName = r.GetString(); request.ChapterName = r.GetString(); request.LevelIndex = r.GetInt(); request.LoadingMode = r.GetString(); request.SpawnIdentifier = r.GetString(); request.HostGameState = r.GetString(); if (r.AvailableBytes >= 4) { request.HasLevelSeed = r.GetInt() != 0; } if (r.AvailableBytes >= 4) { request.LevelSeed = r.GetInt(); } if (r.AvailableBytes > 0) { request.LevelGenerator = r.GetString(); } request.HostRevision = r.GetInt(); request.Reason = r.GetString(); request.AutoLoadAllowed = r.GetInt() != 0; if (r.AvailableBytes >= 4) { request.HasUsedSets = r.GetInt() != 0; request.UsedChunksThisRun = ReadStringList(r); request.UsedEventsThisRun = ReadStringList(r); request.UsedEventsThisEnvironment = ReadStringList(r); } if (r.AvailableBytes > 0) { request.GraphName = r.GetString(); } if (r.AvailableBytes > 0) { request.GenerationRunId = r.GetString(); } return true; } catch { return false; } } public static void WriteClientResponse(NetDataWriter w, NetClientSceneResponse response) { w.Put(response.RequestId ?? ""); w.Put(response.ClientPeerId ?? ""); w.Put(response.ClientPlayerName ?? ""); w.Put(response.ChapterName ?? ""); w.Put(response.LevelIndex); w.Put(response.GameState ?? ""); w.Put(response.HasLevelSeed ? 1 : 0); w.Put(response.LevelSeed); w.Put(response.LevelGenerator ?? ""); w.Put(response.LocalRevision); w.Put(response.IsInTargetScene ? 1 : 0); w.Put(response.FollowPhase ?? "None"); w.Put(response.Message ?? ""); } public static bool TryReadClientResponse(NetDataReader r, out NetClientSceneResponse response) { response = new NetClientSceneResponse(); try { response.RequestId = r.GetString(); response.ClientPeerId = r.GetString(); response.ClientPlayerName = r.GetString(); response.ChapterName = r.GetString(); response.LevelIndex = r.GetInt(); response.GameState = r.GetString(); if (r.AvailableBytes >= 4) { response.HasLevelSeed = r.GetInt() != 0; } if (r.AvailableBytes >= 4) { response.LevelSeed = r.GetInt(); } if (r.AvailableBytes > 0) { response.LevelGenerator = r.GetString(); } response.LocalRevision = r.GetInt(); response.IsInTargetScene = r.GetInt() != 0; if (r.AvailableBytes > 0) { response.FollowPhase = r.GetString(); } else { response.FollowPhase = (response.IsInTargetScene ? "Arrived" : "Refused"); } if (r.AvailableBytes > 0) { response.Message = r.GetString(); } else { response.Message = ""; } return true; } catch { return false; } } } public sealed class NetSceneRequestManager { private readonly Dictionary _lastRequestKeys = new Dictionary(); private readonly Dictionary _lastRequestTimes = new Dictionary(); private readonly Dictionary _pendingByPeer = new Dictionary(); private readonly Dictionary _lastResponses = new Dictionary(); private readonly Dictionary _sentClientResponsePhases = new Dictionary(); private readonly Dictionary _lastFollowPhaseByPeer = new Dictionary(); private readonly List _responseHistory = new List(); public NetHostSceneRequest? LastReceivedHostRequest { get; private set; } public NetHostSceneRequest? LastManualFollowRequest { get; private set; } public NetClientSceneResponse? LastSentClientResponse { get; private set; } public void Clear() { _lastRequestKeys.Clear(); _lastRequestTimes.Clear(); _pendingByPeer.Clear(); _lastResponses.Clear(); _sentClientResponsePhases.Clear(); _lastFollowPhaseByPeer.Clear(); _responseHistory.Clear(); LastReceivedHostRequest = null; LastManualFollowRequest = null; LastSentClientResponse = null; } public void RemovePeer(string peerId) { if (!string.IsNullOrWhiteSpace(peerId)) { _lastRequestKeys.Remove(peerId); _lastRequestTimes.Remove(peerId); _pendingByPeer.Remove(peerId); _lastResponses.Remove(peerId); _lastFollowPhaseByPeer.Remove(peerId); } } public bool TryCreateHostSceneRequest(string clientPeerId, string hostPlayerName, NetRunState hostState, NetRunState clientState, float now, float minIntervalSeconds, out NetHostSceneRequest request) { request = new NetHostSceneRequest(); if (string.IsNullOrWhiteSpace(clientPeerId)) { return false; } if (!hostState.HasLevel) { return false; } bool flag = RequireSameSeedForSceneMatch(); bool flag2 = NetSceneName.SameScene(hostState.ChapterName, hostState.LevelIndex, clientState.ChapterName, clientState.LevelIndex); bool flag3 = NetClientLoadGate.IsCombatTarget(hostState.ChapterName, hostState.LoadingMode, hostState.LevelIndex); if (flag && flag3) { if (!hostState.HasLevelSeed) { return false; } if (flag2 && !clientState.HasLevelSeed) { return false; } } if (flag3 ? hostState.SameLevelInstanceAs(clientState, flag) : hostState.SameSceneAs(clientState)) { return false; } if (minIntervalSeconds < 1f) { minIntervalSeconds = 1f; } string text = $"{hostState.LevelInstanceKey()}|hostRev={hostState.Revision}|client={clientState.LevelInstanceKey()}"; if (_lastRequestKeys.TryGetValue(clientPeerId, out string value) && value == text && _lastRequestTimes.TryGetValue(clientPeerId, out var value2) && now - value2 < minIntervalSeconds) { return false; } string requestId = $"hsr-{clientPeerId}-{hostState.LevelInstanceKey().Replace(':', '-').Replace('#', '-').Replace('=', '-')}-r{hostState.Revision}-{(int)(now * 1000f)}"; request = new NetHostSceneRequest { RequestId = requestId, HostPeerId = (string.IsNullOrWhiteSpace(hostState.PeerId) ? "host" : hostState.PeerId), HostPlayerName = (string.IsNullOrWhiteSpace(hostPlayerName) ? hostState.PlayerName : hostPlayerName), ChapterName = hostState.ChapterName, LevelIndex = hostState.LevelIndex, LoadingMode = hostState.LoadingMode, SpawnIdentifier = hostState.SpawnIdentifier, HostGameState = hostState.GameState, HasLevelSeed = hostState.HasLevelSeed, LevelSeed = hostState.LevelSeed, LevelGenerator = hostState.LevelGenerator, GraphName = hostState.LevelGenerator, HostRevision = hostState.Revision, Reason = "ClientSceneDrift", AutoLoadAllowed = false }; _lastRequestKeys[clientPeerId] = text; _lastRequestTimes[clientPeerId] = now; _pendingByPeer[clientPeerId] = request; return true; } public void RecordHostRequest(NetHostSceneRequest request) { LastReceivedHostRequest = request; } public void RecordManualFollowAttempt(NetHostSceneRequest request) { LastManualFollowRequest = request; } public bool HasLastHostRequestTarget(out NetHostSceneRequest request) { request = LastReceivedHostRequest ?? new NetHostSceneRequest(); if (LastReceivedHostRequest != null) { return LastReceivedHostRequest.HasTargetScene; } return false; } public bool IsLastHostRequest(NetHostSceneRequest request) { if (LastReceivedHostRequest == null) { return false; } if (request == null) { return false; } if (!string.IsNullOrWhiteSpace(request.RequestId)) { return request.RequestId == LastReceivedHostRequest.RequestId; } return false; } public NetClientSceneResponse BuildClientResponse(NetHostSceneRequest request, NetRunState localState, string clientPeerId, string clientPlayerName, string? messageOverride = null, string? followPhaseOverride = null) { bool num = request.HasTargetScene && localState.HasLevel && NetSceneName.SameScene(localState.ChapterName, localState.LevelIndex, request.ChapterName, request.LevelIndex); bool flag = !RequireSameSeedForSceneMatch() || (request.HasLevelSeed && localState.HasLevelSeed && request.LevelSeed == localState.LevelSeed); int num2; int num3; if (!string.IsNullOrWhiteSpace(request.GraphName)) { num2 = ((!string.IsNullOrWhiteSpace(localState.LevelGenerator)) ? 1 : 0); if (num2 != 0) { num3 = (string.Equals(request.GraphName, localState.LevelGenerator, StringComparison.OrdinalIgnoreCase) ? 1 : 0); goto IL_0092; } } else { num2 = 0; } num3 = 1; goto IL_0092; IL_0092: bool flag2 = (byte)num3 != 0; if (num2 != 0) { if (flag2) { NetSceneFollowDiag.SceneAckGraphMatched++; } else { NetSceneFollowDiag.SceneAckGraphMismatch++; Plugin.Log.Info("[SceneRequest] not in target graph request=" + request.GraphName + " local=" + localState.LevelGenerator); } } bool flag3 = num && flag && flag2; string followPhase = ((!string.IsNullOrWhiteSpace(followPhaseOverride)) ? followPhaseOverride.Trim() : (flag3 ? "Arrived" : "Refused")); return new NetClientSceneResponse { RequestId = request.RequestId, ClientPeerId = (string.IsNullOrWhiteSpace(clientPeerId) ? localState.PeerId : clientPeerId), ClientPlayerName = (string.IsNullOrWhiteSpace(clientPlayerName) ? localState.PlayerName : clientPlayerName), ChapterName = localState.ChapterName, LevelIndex = localState.LevelIndex, GameState = localState.GameState, HasLevelSeed = localState.HasLevelSeed, LevelSeed = localState.LevelSeed, LevelGenerator = localState.LevelGenerator, LocalRevision = localState.Revision, IsInTargetScene = flag3, FollowPhase = followPhase, Message = ((!string.IsNullOrWhiteSpace(messageOverride)) ? messageOverride : (flag3 ? "Arrived in requested host scene." : "Automatic scene follow is not implemented; press the manual follow key to attempt it.")) }; } public bool TryRecordSentClientResponse(NetClientSceneResponse response, out string reason) { reason = ""; string text = (string.IsNullOrWhiteSpace(response.RequestId) ? "" : response.RequestId); string text2 = ((!string.IsNullOrWhiteSpace(response.FollowPhase)) ? response.FollowPhase.Trim() : (response.IsInTargetScene ? "Arrived" : "Refused")); string key = text + "|" + text2; if (_sentClientResponsePhases.ContainsKey(key)) { reason = "duplicate response phase '" + text2 + "' for request " + text; return false; } _sentClientResponsePhases[key] = text2; LastSentClientResponse = response; return true; } public void RecordClientResponse(string peerId, NetClientSceneResponse response) { if (string.IsNullOrWhiteSpace(peerId)) { peerId = response.ClientPeerId; } if (string.IsNullOrWhiteSpace(peerId)) { peerId = "unknown"; } _lastResponses[peerId] = response; _responseHistory.Add(response); string value = ((!string.IsNullOrWhiteSpace(response.FollowPhase)) ? response.FollowPhase.Trim() : (response.IsInTargetScene ? "Arrived" : "Refused")); _lastFollowPhaseByPeer[peerId] = value; _pendingByPeer.Remove(peerId); } public string FormatStatus() { int count = _pendingByPeer.Count; int count2 = _responseHistory.Count; if (LastReceivedHostRequest != null) { string text = ((LastManualFollowRequest != null) ? (" manualFollow=" + LastManualFollowRequest.TargetSceneKey()) : ""); string text2 = ((LastSentClientResponse != null) ? (" lastSent=" + CleanPhase(LastSentClientResponse.FollowPhase, LastSentClientResponse.IsInTargetScene)) : ""); return $"sceneReq:lastHost={LastReceivedHostRequest.TargetSceneKey()} autoLoad={LastReceivedHostRequest.AutoLoadAllowed}{text}{text2}"; } if (count == 0 && count2 == 0) { return "sceneReq:pending=0 responses=0"; } string text3 = ""; if (count2 > 0) { NetClientSceneResponse netClientSceneResponse = _responseHistory[_responseHistory.Count - 1]; string text4 = CleanPhase(netClientSceneResponse.FollowPhase, netClientSceneResponse.IsInTargetScene); text3 = " lastResponse=" + netClientSceneResponse.ClientPeerId + ":" + text4; } string text5 = ""; if (_lastFollowPhaseByPeer.Count > 0) { IEnumerable values = from kv in _lastFollowPhaseByPeer orderby kv.Key select kv.Key + ":" + kv.Value; text5 = " lastFollow=[" + string.Join(",", values) + "]"; } return $"sceneReq:pending={count} responses={count2}{text3}{text5}"; } private static bool RequireSameSeedForSceneMatch() { try { return Plugin.Cfg.EnableLevelSeedAuthority.Value && Plugin.Cfg.RequireSameLevelSeedForSceneMatch.Value; } catch { return false; } } private static string CleanPhase(string phase, bool inTarget) { if (!string.IsNullOrWhiteSpace(phase)) { return phase.Trim(); } if (!inTarget) { return "Refused"; } return "Arrived"; } } public class NetService { private NetManager? _net; private EventBasedNetListener? _listener; private NetMode _mode; private float _lastPingTime; private float _lastStatusTime; private float _lastRunStateSendTime; private float _nextClientReconnectTime; private int _clientReconnectAttempts; private bool _clientConnectInProgress; private NetPeer? _hostPeer; private readonly List _clients = new List(); private readonly Dictionary _peerIds = new Dictionary(); private readonly NetSessionManager _sessions = new NetSessionManager(); private readonly NetRunStateManager _runStates = new NetRunStateManager(); private readonly NetSceneRequestManager _sceneRequests = new NetSceneRequestManager(); private readonly NetLocalPlayerTracker _localPlayer = new NetLocalPlayerTracker(); private readonly NetRemotePlayerProxyManager _visualProxies = new NetRemotePlayerProxyManager(); private readonly RemotePlayerTargetProxyManager _targetProxies = new RemotePlayerTargetProxyManager(); private readonly ClientPlayerHitProxyManager _ffHitProxies = new ClientPlayerHitProxyManager(); internal static readonly RemotePlayerRegistryManager PlayerRegistry = new RemotePlayerRegistryManager(); private readonly List _remoteInterestScratch = new List(); private readonly HashSet _receivedEnemyDeathEvents = new HashSet(); private readonly HashSet _receivedClientEnemyDeathClaims = new HashSet(); private float _lastPlayerVisualSendTime; private float _lastLevelSeedPollTime; private float _lastEnemyStateSnapshotSendTime; private float _lastEnemyStateSnapshotSendErrorLogTime; private float _lastInterestFeedLogAt; private bool _enemyStateSnapshotPacketClampWarningShown; private int _playerVisualSequence; private int _enemyStateSnapshotSequence; private bool _manualFollowKeyWarningShown; private float _lastWorldRosterSendTime; private int _lastWorldRosterSentRevision = -1; private (string Address, int Port, string Label)? _connectTargetOverride; private int _sessionSettingsRevision; private string _lastClientTransitionKey = ""; private float _lastClientTransitionTime; private const int EnemySnapshotSafeByteLimit = 900; private int _snapshotChunkBytesMax; private int _snapshotChunkSplit; private int _snapshotChunkTooLargeRejected; private int _snapshotChunkSendFailed; public NetMode Mode => _mode; internal string LocalPeerId => _runStates?.LocalState.PeerId ?? ""; public void Start(NetMode mode) { //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Expected O, but got Unknown //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Expected O, but got Unknown //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Expected O, but got Unknown //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Expected O, but got Unknown //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Expected O, but got Unknown //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Expected O, but got Unknown //IL_0483: Unknown result type (might be due to invalid IL or missing references) _mode = mode; NetClientLoadGate.SetMode(mode); _sessions.Clear(); _runStates.Clear(); _sceneRequests.Clear(); _localPlayer.Clear(); _visualProxies.Clear(); PlayerHeldWeaponManager.Reset(); _targetProxies.Clear(); PlayerRegistry.Clear(); _receivedEnemyDeathEvents.Clear(); _receivedClientEnemyDeathClaims.Clear(); _peerIds.Clear(); _clients.Clear(); _hostPeer = null; _lastPingTime = 0f; _lastStatusTime = 0f; _lastRunStateSendTime = 0f; _nextClientReconnectTime = 0f; _clientReconnectAttempts = 0; _clientConnectInProgress = false; _lastPlayerVisualSendTime = 0f; _lastLevelSeedPollTime = 0f; _lastEnemyStateSnapshotSendTime = 0f; _lastEnemyStateSnapshotSendErrorLogTime = 0f; _enemyStateSnapshotPacketClampWarningShown = false; _playerVisualSequence = 0; _enemyStateSnapshotSequence = 0; _snapshotChunkBytesMax = 0; _snapshotChunkSplit = 0; _snapshotChunkTooLargeRejected = 0; _snapshotChunkSendFailed = 0; _manualFollowKeyWarningShown = false; NetLoadBarrier.Reset(); NetClientJoinFlow.Reset(); NetLinkState.InitFromConfig(); NetBossEncounterManager.Reset(); BossDynamicSpawnManifest.Reset(); _listener = new EventBasedNetListener(); _listener.ConnectionRequestEvent += new OnConnectionRequest(OnConnectionRequest); _listener.PeerConnectedEvent += new OnPeerConnected(OnPeerConnected); _listener.NetworkReceiveEvent += new OnNetworkReceive(OnNetworkReceive); _listener.PeerDisconnectedEvent += new OnPeerDisconnected(OnPeerDisconnected); _net = new NetManager((INetEventListener)(object)_listener, (PacketLayerBase)null) { AutoRecycle = true }; if (mode == NetMode.Host) { _sessions.RegisterLocalHost(Plugin.Cfg.PlayerName.Value, "1.0.0", Now()); _runStates.SetLocalIdentity("host", Plugin.Cfg.PlayerName.Value); _net.Start(Plugin.Cfg.HostPort.Value); NetLogger.Info($"[Net] Host started on port {Plugin.Cfg.HostPort.Value}"); NetLogger.Info("[Session] Host session ready: " + _sessions.FormatStatus()); if (Plugin.Cfg.EnableHostSceneAuthority.Value) { NetLogger.Info("[SceneAuthority] Host scene authority enabled (metadata-only, warning-only)."); } if (Plugin.Cfg.EnableHostSceneRequestProtocol.Value) { NetLogger.Info("[SceneRequest] HostSceneRequest protocol enabled (request/ack/refused only; no auto-load)."); } if (Plugin.Cfg.EnableLevelSeedAuthority.Value) { NetLogger.Info("[LevelSeed] Host level seed authority enabled (metadata/probe based)."); } if (Plugin.Cfg.EnableRemotePlayerVisualProxy.Value) { NetLogger.Info("[RemotePlayer] Visual proxy enabled (local-only GameObjects, no gameplay sync)."); } if (Plugin.Cfg.EnableHostEnemyDeathEventMirror.Value) { NetLogger.Info("[EnemyDeathMirror] Host enemy death event mirror enabled (host sends NPC death events)."); } if (Plugin.Cfg.EnableClientEnemyDeathClaim.Value) { NetLogger.Info($"[ClientDeathClaim] Host client enemy death claim receive enabled (apply={Plugin.Cfg.ApplyReceivedClientEnemyDeathClaimsOnHost.Value})."); } if (Plugin.Cfg.EnableHostEnemyStateSnapshotMirror.Value) { NetLogger.Info($"[EnemyStateMirror] Host enemy state snapshot mirror enabled (rate={Plugin.Cfg.EnemyStateSnapshotSendRateHz.Value}Hz, apply={Plugin.Cfg.ApplyReceivedEnemyStateSnapshots.Value})."); } if (Plugin.Cfg.EnableCoopPlayerDownedRevive.Value) { NetLogger.Info($"[PlayerLife] Host co-op downed/revive enabled (timeout={Plugin.Cfg.PlayerDownedRescueTimeoutSeconds.Value}s, reviveHold={Plugin.Cfg.PlayerReviveHoldSeconds.Value}s, reviveDistance={Plugin.Cfg.PlayerReviveDistance.Value}m)."); } return; } _runStates.SetLocalIdentity("client-local", Plugin.Cfg.PlayerName.Value); _net.Start(); if (Plugin.Cfg.EnableHostSceneAuthority.Value) { NetLogger.Info("[SceneAuthority] Client will treat host scene metadata as authoritative (warning-only)."); } if (Plugin.Cfg.EnableHostSceneRequestProtocol.Value) { NetLogger.Info("[SceneRequest] ClientSceneAck/Refused protocol enabled (reply-only; no auto-load)."); } if (Plugin.Cfg.EnableManualClientSceneFollow.Value) { NetLogger.Info($"[SceneFollow] Manual client scene follow enabled. Press {Plugin.Cfg.ManualClientSceneFollowKey.Value} after HostSceneRequest."); } if (Plugin.Cfg.EnableLevelSeedAuthority.Value) { NetLogger.Info("[LevelSeed] Client will compare host level seed when known."); } if (Plugin.Cfg.EnableRemotePlayerVisualProxy.Value) { NetLogger.Info("[RemotePlayer] Visual proxy enabled (local-only GameObjects, no gameplay sync)."); } if (Plugin.Cfg.EnableHostEnemyDeathEventMirror.Value) { NetLogger.Info($"[EnemyDeathMirror] Client enemy death event mirror receive enabled (apply={Plugin.Cfg.ApplyReceivedEnemyDeathEvents.Value})."); } if (Plugin.Cfg.EnableClientEnemyDeathClaim.Value) { NetLogger.Info("[ClientDeathClaim] Client enemy death claim send enabled (local NPC deaths are reported to Host for safe authoritative apply)."); } if (Plugin.Cfg.EnableHostEnemyStateSnapshotMirror.Value) { NetLogger.Info($"[EnemyStateMirror] Client enemy state snapshot mirror receive enabled (apply={Plugin.Cfg.ApplyReceivedEnemyStateSnapshots.Value})."); } if (Plugin.Cfg.EnableCoopPlayerDownedRevive.Value) { NetLogger.Info($"[PlayerLife] Client co-op downed/revive enabled (timeout={Plugin.Cfg.PlayerDownedRescueTimeoutSeconds.Value}s, reviveHold={Plugin.Cfg.PlayerReviveHoldSeconds.Value}s, reviveDistance={Plugin.Cfg.PlayerReviveDistance.Value}m)."); } ConnectToHost(initial: true); } public string GetConnectionSummary() { if (_mode == NetMode.Host) { string item = (CoopConnection.SteamHostingEnabled ? CoopLoc.Get("connect.summary.steamSuffix", " (Steam invites open)") : ""); return CoopLoc.Format("connect.summary.hosting", "Hosting on port {port}{steam} — {n} player(s) connected", ("port", Plugin.Cfg.HostPort.Value.ToString()), ("steam", item), ("n", _sessions.RemoteConnectedCount.ToString())); } if (_mode == NetMode.Client) { string item2 = _connectTargetOverride?.Label ?? $"{Plugin.Cfg.HostAddress.Value}:{Plugin.Cfg.HostPort.Value}"; if (_hostPeer == null) { return CoopLoc.Format("connect.summary.connecting", "Connecting to {label}…", ("label", item2)); } return CoopLoc.Format("connect.summary.connectedHost", "Connected to host {label}", ("label", item2)); } return CoopLoc.Get("connect.summary.off", "Off"); } public IReadOnlyList GetPlayerRows() { List list = new List(); if (_mode == NetMode.Off) { return list; } foreach (NetPeerSession item in _sessions.Sessions.OrderBy((NetPeerSession s) => s.Slot)) { string text = (string.IsNullOrWhiteSpace(item.PlayerName) ? item.PeerId : item.PlayerName); string text2 = (item.IsLocal ? " (you)" : ""); list.Add($"{text}{text2} — slot {item.Slot} — {item.State}"); } return list; } public void Stop() { NetManager? net = _net; if (net != null) { net.Stop(); } _net = null; _listener = null; _hostPeer = null; NetClientLoadGate.SetMode(NetMode.Off); NetGenerationInputCapture.Clear(); NetLoadBarrier.Reset(); NetClientJoinFlow.LeaveSession("networking stopped"); NetClientJoinFlow.Reset(); NetLinkState.ResetClient("networking stopped"); NetHostTransitionGuard.Reset(); NetBossEncounterManager.Reset(); BossDynamicSpawnManifest.Reset(); _clientConnectInProgress = false; _nextClientReconnectTime = 0f; _clientReconnectAttempts = 0; _lastEnemyStateSnapshotSendTime = 0f; _lastEnemyStateSnapshotSendErrorLogTime = 0f; _enemyStateSnapshotPacketClampWarningShown = false; _enemyStateSnapshotSequence = 0; _manualFollowKeyWarningShown = false; _lastWorldRosterSendTime = 0f; _lastWorldRosterSentRevision = -1; _clients.Clear(); _peerIds.Clear(); _sessions.Clear(); _runStates.Clear(); _sceneRequests.Clear(); _localPlayer.Clear(); _visualProxies.Clear(); PlayerHeldWeaponManager.Reset(); _targetProxies.Clear(); PlayerRegistry.Clear(); _receivedEnemyDeathEvents.Clear(); _receivedClientEnemyDeathClaims.Clear(); NetSessionSettings.ResetSession(); _ffHitProxies.Clear(); _sessionSettingsRevision = 0; } public void Tick() { NetManager? net = _net; if (net != null) { net.PollEvents(0); } NetClientLoadGate.UpdateNetState(_mode, _hostPeer != null); NetClientLoadGate.Tick(); HandleClientLoadGateRequestTimer(); HandleClientReconnectTimer(); HandleManualClientSceneFollowInput(); HandlePingTimer(); HandleLevelSeedPollTimer(); HandleRunStateTimer(); HandleRemotePlayerVisualProxyTimer(); HandleEnemyStateSnapshotTimer(); HandleWorldRosterTimer(); if (_mode == NetMode.Host) { NetLoadBarrier.Tick(); } _visualProxies.Tick(Time.deltaTime, Now(), Plugin.Cfg.RemotePlayerVisualTimeoutSeconds.Value, Plugin.Cfg.RemotePlayerVisualInterpolationSpeed.Value, Plugin.Cfg.RemotePlayerVisualSnapDistance.Value); if (_mode == NetMode.Host) { _targetProxies.Tick(_visualProxies, Now(), Plugin.Cfg.RemotePlayerVisualTimeoutSeconds.Value); } if (_mode == NetMode.Host) { PlayerRegistry.Tick(_visualProxies, Now(), Plugin.Cfg.RemotePlayerVisualTimeoutSeconds.Value); } else if (_mode == NetMode.Client) { RemotePlayerRegistryManager.RefreshActivationBuffer(_visualProxies, Now(), Plugin.Cfg.RemotePlayerVisualTimeoutSeconds.Value); } _ffHitProxies.Tick(_visualProxies, Now(), Plugin.Cfg.RemotePlayerVisualTimeoutSeconds.Value); if (_mode != NetMode.Off) { PlayerHeldWeaponManager.Tick(_visualProxies); } if (_mode != NetMode.Off) { WorldPickupManager.Tick(); } if (_mode != NetMode.Off) { if (Plugin.Cfg.EnableRemotePlayerSpriteBody.Value) { RemotePlayerSpriteBody.SyncProxyBodies(_visualProxies); } else if (Plugin.Cfg.EnableRemotePlayerNpcBody.Value) { RemotePlayerBodyManager.SyncProxyBodies(_visualProxies); } } HandleStatusTimer(); } public void FixedTick() { if (Plugin.Cfg.EnableRemotePlayerProxyCollision.Value && Plugin.Cfg.RemotePlayerCollisionSoft.Value && _localPlayer.HasTransform && (Object)(object)_localPlayer.LocalTransform != (Object)null) { _visualProxies.ApplySoftCollision(_localPlayer.LocalTransform, Plugin.Cfg.RemotePlayerSoftCollisionRadius.Value, Plugin.Cfg.RemotePlayerSoftCollisionPushSpeed.Value, Time.fixedDeltaTime); } } public void SetConnectTarget(string address, int port, string displayLabel) { _connectTargetOverride = (address, port, displayLabel); } private void ConnectToHost(bool initial) { if (_net != null && _mode == NetMode.Client && _hostPeer == null && !_clientConnectInProgress) { _clientConnectInProgress = true; _clientReconnectAttempts++; string text = _connectTargetOverride?.Address ?? Plugin.Cfg.HostAddress.Value; int num = _connectTargetOverride?.Port ?? Plugin.Cfg.HostPort.Value; _net.Connect(text, num, Plugin.Cfg.ConnectionKey.Value); if (initial) { NetLogger.Info($"[Net] Client connecting to {text}:{num}"); } else { NetLogger.Info($"[Net] Client reconnecting to {text}:{num} attempt={_clientReconnectAttempts}"); } } } private void HandleClientReconnectTimer() { if (_mode == NetMode.Client && _net != null && _hostPeer == null && !_clientConnectInProgress) { float num = Now(); if (!(num < _nextClientReconnectTime)) { _nextClientReconnectTime = num + 5f; ConnectToHost(initial: false); } } } private void HandlePingTimer() { if (_mode != NetMode.Off && _net != null) { float num = Now(); if (!(num - _lastPingTime < Plugin.Cfg.SendPingIntervalSeconds.Value)) { _lastPingTime = num; SendPingToAll(); } } } private void SendPingToAll() { NetDataWriter val = NetMessage.For(NetMessageType.Ping); if (_mode == NetMode.Host) { foreach (NetPeer client in _clients) { client.Send(val, (DeliveryMethod)4); } return; } if (_hostPeer != null) { _hostPeer.Send(val, (DeliveryMethod)4); } } private void HandleStatusTimer() { if (_mode == NetMode.Off || _net == null) { return; } float num = Now(); if (!(num - _lastStatusTime < 30f)) { _lastStatusTime = num; string text = (Plugin.Cfg.EnableHostSceneAuthority.Value ? (" " + _runStates.FormatAuthorityStatus(_mode)) : ""); string text2 = (Plugin.Cfg.EnableHostSceneRequestProtocol.Value ? (" " + _sceneRequests.FormatStatus()) : ""); string text3 = (Plugin.Cfg.EnableRemotePlayerVisualProxy.Value ? (" " + _localPlayer.FormatStatus() + " " + _visualProxies.FormatStatus()) : ""); string text4 = ((_mode == NetMode.Host) ? $" snapshotChunkBytesMax={_snapshotChunkBytesMax} snapshotChunkSplit={_snapshotChunkSplit} snapshotChunkTooLarge={_snapshotChunkTooLargeRejected} snapshotChunkSendFailed={_snapshotChunkSendFailed}" : ""); if (_mode == NetMode.Host) { NetLogger.Info($"[Net] Status: mode=Host peers={_clients.Count} {NetLinkState.FormatStatus()} {_sessions.FormatStatus()} {_runStates.FormatStatus()}{text}{text2}{text3}{text4} runStateSync[{NetGenerationInputCapture.FormatRunStateSyncCounters()}] {NetLoadBarrier.FormatStatus()} loadBarrier[{NetLoadBarrier.FormatCounters()}] hubReturn[{NetSceneFollowDiag.FormatHubReturn()}] transition[{NetGenerationInputCapture.FormatTransitionCounters()}] graphId[{NetSceneFollowDiag.FormatGraphIdentity()}] boss[{NetBossEncounterManager.FormatCounters()}] bossSpawn[{BossDynamicSpawnManifest.FormatCounters()}]"); } else { NetHostSceneRequest request; string latestHostTarget = (_sceneRequests.HasLastHostRequestTarget(out request) ? request.TargetSceneKey() : ""); NetLogger.Info($"[Net] Status: mode=Client connected={_hostPeer != null} {NetLinkState.FormatStatus()} {_sessions.FormatStatus()} {_runStates.FormatStatus()}{text}{text2}{text3} runStateSync[{NetGenerationInputCapture.FormatRunStateSyncCounters()}] {NetClientJoinFlow.FormatStatus(latestHostTarget)} joinFlow[{NetClientJoinFlow.FormatCounters()}] SessionReturn[{NetClientLoadGate.FormatSessionReturnCounters()}] hubReturn[{NetSceneFollowDiag.FormatHubReturn()}] transition[{NetGenerationInputCapture.FormatTransitionCounters()}] graphId[{NetSceneFollowDiag.FormatGraphIdentity()}] boss[{NetBossEncounterManager.FormatCounters()}] bossSpawn[{BossDynamicSpawnManifest.FormatCounters()}] loadGate[{NetClientLoadGate.FormatCounters()}]"); } } } public void ReportLocalGoToLevel(string chapterName, int levelIndex, string loadingMode, string spawnIdentifier) { if (Plugin.Cfg.EnableRunStateNegotiation.Value) { _localPlayer.Clear(); _visualProxies.Clear(); PlayerHeldWeaponManager.Reset(); _targetProxies.Clear(); PlayerRegistry.Clear(); _receivedEnemyDeathEvents.Clear(); _receivedClientEnemyDeathClaims.Clear(); _enemyStateSnapshotSequence = 0; _lastEnemyStateSnapshotSendTime = 0f; _lastEnemyStateSnapshotSendErrorLogTime = 0f; _playerVisualSequence = 0; NetRunState netRunState = _runStates.UpdateLocalGoToLevel(chapterName, levelIndex, loadingMode, spawnIdentifier, Now()); NetBossEncounterManager.Reset(fullSession: false); BossDynamicSpawnManifest.Reset(); NetLogger.Info("[RunState] Local GoToLevel: " + netRunState.ToCompactString() + " mode=" + loadingMode + " spawn=" + spawnIdentifier); SendLocalRunStateToConnectedPeers(); TrySendClientSceneAckForLastHostRequest("local GoToLevel"); } } public void ReportLocalGameState(string gameState) { if (Plugin.Cfg.EnableRunStateNegotiation.Value) { NetRunState netRunState = _runStates.UpdateLocalGameState(gameState, Now()); if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[RunState] Local GameState: " + netRunState.ToCompactString()); } SendLocalRunStateToConnectedPeers(); TrySendClientSceneAckForLastHostRequest("local GameState"); } } public void ReportLocalClearLevel() { if (Plugin.Cfg.EnableRunStateNegotiation.Value) { _runStates.UpdateLocalClearLevel(Now()); _localPlayer.Clear(); _visualProxies.Clear(); PlayerHeldWeaponManager.Reset(); _targetProxies.Clear(); PlayerRegistry.Clear(); _receivedEnemyDeathEvents.Clear(); _receivedClientEnemyDeathClaims.Clear(); _enemyStateSnapshotSequence = 0; _lastEnemyStateSnapshotSendTime = 0f; _lastEnemyStateSnapshotSendErrorLogTime = 0f; if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[RunState] Local ClearLevel observed; local player/proxies cleared"); } SendLocalRunStateToConnectedPeers(); } } public void ReportLocalLevelSeed(int seed, string generatorName) { if (Plugin.Cfg.EnableRunStateNegotiation.Value && Plugin.Cfg.EnableLevelSeedAuthority.Value && (!_runStates.LocalState.HasLevelSeed || _runStates.LocalState.LevelSeed != seed)) { NetRunState netRunState = _runStates.UpdateLocalLevelSeed(seed, generatorName, Now()); _visualProxies.HideAll(); NetLogger.Info($"[LevelSeed] Local level seed captured: generator={generatorName} seed={seed} state={netRunState.ToCompactString()}"); SendLocalRunStateToConnectedPeers(); SendHostSceneRequestsForDriftedClients(); TrySendClientSceneAckForLastHostRequest("local LevelSeed"); } } public void ReportLocalPlayerObject(object player) { if (Plugin.Cfg.EnableRemotePlayerVisualProxy.Value) { if (_localPlayer.TrySetLocalPlayerObject(player, out string description)) { NetLogger.Info("[RemotePlayer] Local player transform captured: " + description); } else if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[RemotePlayer] Local player transform capture failed"); } } } public NetRunState GetLocalRunStateSnapshot() { return _runStates.LocalState.Clone(); } public string FormatBossConvergence(out bool allConverged) { int peerCount; int convergedCount; return _runStates.FormatBossConvergence(out allConverged, out peerCount, out convergedCount); } internal void SendClientBossStartRequest(NetClientBossStartRequest req) { if (_mode != NetMode.Client || _net == null || _hostPeer == null || !Plugin.Cfg.EnableBossEncounterSync.Value || req == null) { return; } try { NetDataWriter val = NetMessage.For(NetMessageType.ClientBossStartRequest); NetBossEncounterCodec.WriteRequest(val, req); _hostPeer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[BossEncounter] failed to send ClientBossStartRequest: " + ex.Message); } } internal void BroadcastHostBossEncounterStart(NetBossEncounterState state) { if (_mode != NetMode.Host || _net == null || !Plugin.Cfg.EnableBossEncounterSync.Value || state == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostBossEncounterStart); NetBossEncounterCodec.WriteState(val2, state); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[BossEncounter] failed to send HostBossEncounterStart: " + ex.Message); } } } private void HandleClientBossStartRequest(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Host && Plugin.Cfg.EnableBossEncounterSync.Value) { if (!NetBossEncounterCodec.TryReadRequest(reader, out NetClientBossStartRequest result)) { NetLogger.Warn("[BossEncounter] malformed ClientBossStartRequest packet"); } else { string value; string obj = (_peerIds.TryGetValue(peer, out value) ? value : ((IPEndPoint)(object)peer).Address.ToString()); NetBossEncounterManager.HandleClientBossStartRequest(peerId: result.ClientPeerId = obj, req: result); } } } private void HandleHostBossEncounterStart(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Client && Plugin.Cfg.EnableBossEncounterSync.Value) { if (!NetBossEncounterCodec.TryReadState(reader, out NetBossEncounterState result)) { NetLogger.Warn("[BossEncounter] malformed HostBossEncounterStart packet"); } else { NetBossEncounterManager.HandleHostBossEncounterStart(result); } } } internal void SendClientBossDialogCommitRequest(NetBossDialogCommit msg) { if (_mode != NetMode.Client || _net == null || _hostPeer == null || !Plugin.Cfg.EnableBossEncounterSync.Value || msg == null) { return; } try { NetDataWriter val = NetMessage.For(NetMessageType.ClientBossDialogCommitRequest); NetBossEncounterCodec.WriteDialogCommit(val, msg); _hostPeer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[BossDialogCommit] failed to send request: " + ex.Message); } } internal void BroadcastHostBossDialogCommit(NetBossDialogCommit msg) { if (_mode != NetMode.Host || _net == null || !Plugin.Cfg.EnableBossEncounterSync.Value || msg == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostBossDialogCommit); NetBossEncounterCodec.WriteDialogCommit(val2, msg); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[BossDialogCommit] failed to broadcast: " + ex.Message); } } } internal void BroadcastHostBossState(NetBossState msg) { if (_mode != NetMode.Host || _net == null || !Plugin.Cfg.EnableBossEncounterSync.Value || msg == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostBossState); NetBossEncounterCodec.WriteBossState(val2, msg); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[BossState] failed to broadcast: " + ex.Message); } } } internal void BroadcastEmperorWormHead(float x, float y, float z, float rotY, float tailHp, int seq) { if (_mode != NetMode.Host || _net == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostEmperorWormHead); val2.Put(x); val2.Put(y); val2.Put(z); val2.Put(rotY); val2.Put(tailHp); val2.Put(seq); val.Send(val2, (DeliveryMethod)4); } catch (Exception ex) { NetLogger.Warn("[EmperorWormHead] broadcast failed: " + ex.Message); } } } private void HandleEmperorWormHead(NetPeer peer, NetDataReader reader) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (_mode != NetMode.Client) { return; } try { float num = reader.GetFloat(); float num2 = reader.GetFloat(); float num3 = reader.GetFloat(); NetEmperorWormSync.OnHeadReceived(rotY: reader.GetFloat(), tailHp: reader.GetFloat(), seq: reader.GetInt(), pos: new Vector3(num, num2, num3)); } catch (Exception ex) { NetLogger.Warn("[EmperorWormHead] malformed packet: " + ex.Message); } } internal void BroadcastEmperorWormSectionDestroy(int seq) { if (_mode != NetMode.Host || _net == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostEmperorWormSectionDestroy); val2.Put(seq); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[EmperorWorm] section-destroy broadcast failed: " + ex.Message); } } } private void HandleEmperorWormSectionDestroy(NetPeer peer, NetDataReader reader) { if (_mode != NetMode.Client) { return; } try { NetEmperorWormSync.OnSectionDestroyReceived(reader.GetInt()); } catch (Exception ex) { NetLogger.Warn("[EmperorWorm] malformed section-destroy packet: " + ex.Message); } } internal void BroadcastEmperorWormDeath(int seq) { if (_mode != NetMode.Host || _net == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostEmperorWormDeath); val2.Put(seq); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[EmperorWorm] death broadcast failed: " + ex.Message); } } } private void HandleEmperorWormDeath(NetPeer peer, NetDataReader reader) { if (_mode != NetMode.Client) { return; } try { NetEmperorWormSync.OnDeathReceived(reader.GetInt()); } catch (Exception ex) { NetLogger.Warn("[EmperorWorm] malformed death packet: " + ex.Message); } } internal void SendClientEmperorWormHit(float damage, int damageTypeInt, int seq) { if (_mode != NetMode.Client || _net == null || _hostPeer == null) { return; } try { NetDataWriter val = NetMessage.For(NetMessageType.ClientEmperorWormHit); val.Put(damage); val.Put(damageTypeInt); val.Put(seq); _hostPeer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[EmperorWorm] failed to send worm hit: " + ex.Message); } } private void HandleClientEmperorWormHit(NetPeer peer, NetDataReader reader) { if (_mode != NetMode.Host) { return; } try { float damage = reader.GetFloat(); int damageTypeInt = reader.GetInt(); int seq = reader.GetInt(); NetEmperorWormSync.HostApplyClientWormHit(damage, damageTypeInt, seq); } catch (Exception ex) { NetLogger.Warn("[EmperorWorm] malformed worm-hit packet: " + ex.Message); } } internal void SendClientEmperorFightStart() { if (_mode != NetMode.Client || _net == null || _hostPeer == null) { return; } try { NetDataWriter val = NetMessage.For(NetMessageType.ClientEmperorFightStart); _hostPeer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[EmperorWorm] failed to send fight-start request: " + ex.Message); } } private void HandleClientEmperorFightStart(NetPeer peer, NetDataReader reader) { if (_mode != NetMode.Host) { return; } try { NetEmperorWormSync.HostOnClientFightStartRequest(); } catch (Exception ex) { NetLogger.Warn("[EmperorWorm] fight-start request handling failed: " + ex.Message); } } internal void BroadcastEmperorFightStart() { if (_mode != NetMode.Host || _net == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostEmperorFightStart); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[EmperorWorm] fight-start broadcast failed: " + ex.Message); } } } private void HandleEmperorFightStart(NetPeer peer, NetDataReader reader) { if (_mode != NetMode.Client) { return; } try { NetEmperorWormSync.OnFightStartCommitReceived(); } catch (Exception ex) { NetLogger.Warn("[EmperorWorm] fight-start commit handling failed: " + ex.Message); } } internal void BroadcastEmperorSpiderTransform(float x, float y, float z, float rotY, int wp, int tgt, float hp, int seq) { if (_mode != NetMode.Host || _net == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostEmperorSpiderTransform); val2.Put(x); val2.Put(y); val2.Put(z); val2.Put(rotY); val2.Put(wp); val2.Put(tgt); val2.Put(hp); val2.Put(seq); val.Send(val2, (DeliveryMethod)4); } catch (Exception ex) { NetLogger.Warn("[EmperorSpider] transform broadcast failed: " + ex.Message); } } } private void HandleEmperorSpiderTransform(NetPeer peer, NetDataReader reader) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) if (_mode != NetMode.Client) { return; } try { float num = reader.GetFloat(); float num2 = reader.GetFloat(); float num3 = reader.GetFloat(); NetEmperorSpiderSync.OnTransformReceived(rotY: reader.GetFloat(), wp: reader.GetInt(), tgt: reader.GetInt(), hp: reader.GetFloat(), seq: reader.GetInt(), pos: new Vector3(num, num2, num3)); } catch (Exception ex) { NetLogger.Warn("[EmperorSpider] malformed transform packet: " + ex.Message); } } internal void SendClientEmperorSpiderFightStart() { if (_mode != NetMode.Client || _net == null || _hostPeer == null) { return; } try { NetDataWriter val = NetMessage.For(NetMessageType.ClientEmperorSpiderFightStart); _hostPeer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[EmperorSpider] failed to send fight-start request: " + ex.Message); } } private void HandleClientEmperorSpiderFightStart(NetPeer peer, NetDataReader reader) { if (_mode != NetMode.Host) { return; } string value; string requesterPeerId = (_peerIds.TryGetValue(peer, out value) ? value : ((IPEndPoint)(object)peer).Address.ToString()); try { NetEmperorSpiderSync.HostOnClientFightStartRequest(requesterPeerId); } catch (Exception ex) { NetLogger.Warn("[EmperorSpider] fight-start request handling failed: " + ex.Message); } } internal void BroadcastEmperorSpiderFightStart(string starterPeerId) { if (_mode != NetMode.Host || _net == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostEmperorSpiderFightStart); val2.Put(starterPeerId ?? ""); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[EmperorSpider] fight-start broadcast failed: " + ex.Message); } } } private void HandleEmperorSpiderFightStart(NetPeer peer, NetDataReader reader) { if (_mode != NetMode.Client) { return; } try { NetEmperorSpiderSync.OnFightStartCommitReceived(reader.GetString()); } catch (Exception ex) { NetLogger.Warn("[EmperorSpider] fight-start commit handling failed: " + ex.Message); } } internal void SendClientEmperorSpiderHit(float damage, int damageTypeInt, int seq) { if (_mode != NetMode.Client || _net == null || _hostPeer == null) { return; } try { NetDataWriter val = NetMessage.For(NetMessageType.ClientEmperorSpiderHit); val.Put(damage); val.Put(damageTypeInt); val.Put(seq); _hostPeer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[EmperorSpider] failed to send spider hit: " + ex.Message); } } private void HandleClientEmperorSpiderHit(NetPeer peer, NetDataReader reader) { if (_mode != NetMode.Host) { return; } try { float damage = reader.GetFloat(); int damageTypeInt = reader.GetInt(); int seq = reader.GetInt(); NetEmperorSpiderSync.HostApplyClientSpiderHit(damage, damageTypeInt, seq); } catch (Exception ex) { NetLogger.Warn("[EmperorSpider] malformed spider-hit packet: " + ex.Message); } } internal void BroadcastEmperorSpiderEvent(int eventCode, int seq) { if (_mode != NetMode.Host || _net == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostEmperorSpiderEvent); val2.Put(eventCode); val2.Put(seq); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[EmperorSpider] event broadcast failed: " + ex.Message); } } } private void HandleEmperorSpiderEvent(NetPeer peer, NetDataReader reader) { if (_mode != NetMode.Client) { return; } try { int eventCode = reader.GetInt(); int seq = reader.GetInt(); NetEmperorSpiderSync.OnEventReceived(eventCode, seq); } catch (Exception ex) { NetLogger.Warn("[EmperorSpider] malformed event packet: " + ex.Message); } } private void HandleClientBossDialogCommitRequest(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Host && Plugin.Cfg.EnableBossEncounterSync.Value) { if (!NetBossEncounterCodec.TryReadDialogCommit(reader, out NetBossDialogCommit result)) { NetLogger.Warn("[BossDialogCommit] malformed ClientBossDialogCommitRequest packet"); return; } string value; string peerId = (_peerIds.TryGetValue(peer, out value) ? value : ((IPEndPoint)(object)peer).Address.ToString()); NetBossEncounterManager.HandleClientBossDialogCommitRequest(result, peerId); } } private void HandleHostBossDialogCommit(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Client && Plugin.Cfg.EnableBossEncounterSync.Value) { if (!NetBossEncounterCodec.TryReadDialogCommit(reader, out NetBossDialogCommit result)) { NetLogger.Warn("[BossDialogCommit] malformed HostBossDialogCommit packet"); } else { NetBossEncounterManager.HandleHostBossDialogCommit(result); } } } private void HandleHostBossState(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Client && Plugin.Cfg.EnableBossEncounterSync.Value) { if (!NetBossEncounterCodec.TryReadBossState(reader, out NetBossState result)) { NetLogger.Warn("[BossState] malformed HostBossState packet"); } else { NetBossEncounterManager.HandleHostBossState(result); } } } internal void SendClientRoomEnter(NetClientRoomEnter msg) { if (_mode != NetMode.Client || _net == null || _hostPeer == null || !Plugin.Cfg.EnableBossEncounterSync.Value || msg == null) { return; } try { NetDataWriter val = NetMessage.For(NetMessageType.ClientRoomEnter); NetBossEncounterCodec.WriteRoomEnter(val, msg); _hostPeer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[RoomMembership] failed to send ClientRoomEnter: " + ex.Message); } } internal void BroadcastHostRoomMembership(NetHostRoomMembership msg) { if (_mode != NetMode.Host || _net == null || !Plugin.Cfg.EnableBossEncounterSync.Value || msg == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostRoomMembership); NetBossEncounterCodec.WriteRoomMembership(val2, msg); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[RoomMembership] failed to broadcast: " + ex.Message); } } } private void HandleClientRoomEnter(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Host && Plugin.Cfg.EnableBossEncounterSync.Value) { if (!NetBossEncounterCodec.TryReadRoomEnter(reader, out NetClientRoomEnter result)) { NetLogger.Warn("[RoomMembership] malformed ClientRoomEnter packet"); return; } string value; string peerId = (_peerIds.TryGetValue(peer, out value) ? value : ((IPEndPoint)(object)peer).Address.ToString()); NetBossEncounterManager.HandleClientRoomEnter(result, peerId); } } private void HandleHostRoomMembership(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Client && Plugin.Cfg.EnableBossEncounterSync.Value) { if (!NetBossEncounterCodec.TryReadRoomMembership(reader, out NetHostRoomMembership result)) { NetLogger.Warn("[RoomMembership] malformed HostRoomMembership packet"); } else { NetBossEncounterManager.HandleHostRoomMembership(result); } } } internal void ForEachRemotePlayerPosition(Action action) { if (action != null) { _visualProxies.ForEachInScenePlayer(delegate(string peerId, Vector3 pos) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) action(pos); }, Now(), Plugin.Cfg.RemotePlayerVisualTimeoutSeconds.Value); } } internal void ForEachRemotePlayerPositionWithPeer(Action action) { if (action != null) { _visualProxies.ForEachInScenePlayer(action, Now(), Plugin.Cfg.RemotePlayerVisualTimeoutSeconds.Value); } } internal void ForEachRemotePlayerTransform(Action action) { if (action != null) { _visualProxies.ForEachInScenePlayerTransform(action, Now(), Plugin.Cfg.RemotePlayerVisualTimeoutSeconds.Value); } } internal void BroadcastHostBossDynamicSpawn(NetBossDynamicSpawn msg) { if (_mode != NetMode.Host || _net == null || !Plugin.Cfg.EnableBossEncounterSync.Value || msg == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostBossDynamicSpawn); NetBossEncounterCodec.WriteDynamicSpawn(val2, msg); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[BossSpawn] failed to broadcast: " + ex.Message); } } } private void HandleHostBossDynamicSpawn(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Client && Plugin.Cfg.EnableBossEncounterSync.Value) { if (!NetBossEncounterCodec.TryReadDynamicSpawn(reader, out NetBossDynamicSpawn result)) { NetLogger.Warn("[BossSpawn] malformed HostBossDynamicSpawn packet"); } else { BossDynamicSpawnManifest.HandleHostBossDynamicSpawn(result); } } } internal void SendClientBossHitRequest(NetClientBossHitRequest req) { if (_mode != NetMode.Client || _net == null || _hostPeer == null || !Plugin.Cfg.EnableBossEncounterSync.Value || req == null) { return; } try { NetDataWriter val = NetMessage.For(NetMessageType.ClientBossHitRequest); NetBossEncounterCodec.WriteBossHit(val, req); _hostPeer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[BossDamage] failed to send ClientBossHitRequest: " + ex.Message); } } private void HandleClientBossHitRequest(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Host && Plugin.Cfg.EnableBossEncounterSync.Value) { if (!NetBossEncounterCodec.TryReadBossHit(reader, out NetClientBossHitRequest result)) { NetLogger.Warn("[BossDamage] malformed ClientBossHitRequest packet"); return; } string value; string peerId = (_peerIds.TryGetValue(peer, out value) ? value : ((IPEndPoint)(object)peer).Address.ToString()); NetBossEncounterManager.HandleClientBossHitRequest(result, peerId); } } internal void BroadcastHostBossHitVisual(NetHostBossHitVisual msg) { if (_mode != NetMode.Host || _net == null || !Plugin.Cfg.EnableBossEncounterSync.Value || msg == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostBossHitVisual); NetBossEncounterCodec.WriteBossHitVisual(val2, msg); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[BossDamage] failed to broadcast hit visual: " + ex.Message); } } } private void HandleHostBossHitVisual(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Client && Plugin.Cfg.EnableBossEncounterSync.Value) { if (!NetBossEncounterCodec.TryReadBossHitVisual(reader, out NetHostBossHitVisual result)) { NetLogger.Warn("[BossDamage] malformed HostBossHitVisual packet"); } else { NetBossEncounterManager.HandleHostBossHitVisual(result); } } } internal void BroadcastHostBossDiscreteEvent(NetBossDiscreteEvent msg) { if (_mode != NetMode.Host || _net == null || !Plugin.Cfg.EnableBossEncounterSync.Value || msg == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostBossDiscreteEvent); NetBossEncounterCodec.WriteDiscreteEvent(val2, msg); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[CousinPool] failed to broadcast discrete event: " + ex.Message); } } } private void HandleHostBossDiscreteEvent(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Client && Plugin.Cfg.EnableBossEncounterSync.Value) { if (!NetBossEncounterCodec.TryReadDiscreteEvent(reader, out NetBossDiscreteEvent result)) { NetLogger.Warn("[CousinPool] malformed HostBossDiscreteEvent packet"); } else { NetBossEncounterManager.HandleHostBossDiscreteEvent(result); } } } internal void SendClientLuciaEyeReport(NetLuciaEyeReport req) { if (_mode != NetMode.Client || _net == null || _hostPeer == null || !Plugin.Cfg.EnableBossEncounterSync.Value || req == null) { return; } try { NetDataWriter val = NetMessage.For(NetMessageType.ClientLuciaEyeReport); NetBossEncounterCodec.WriteLuciaEyeReport(val, req); _hostPeer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[LuciaEye] failed to send ClientLuciaEyeReport: " + ex.Message); } } private void HandleClientLuciaEyeReport(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Host && Plugin.Cfg.EnableBossEncounterSync.Value) { if (!NetBossEncounterCodec.TryReadLuciaEyeReport(reader, out NetLuciaEyeReport result)) { NetLogger.Warn("[LuciaEye] malformed ClientLuciaEyeReport packet"); return; } string value; string peerId = (_peerIds.TryGetValue(peer, out value) ? value : ((IPEndPoint)(object)peer).Address.ToString()); NetBossEncounterManager.HandleClientLuciaEyeReport(result, peerId); } } internal void BroadcastHostLuciaEyeState(NetLuciaEyeState msg) { if (_mode != NetMode.Host || _net == null || !Plugin.Cfg.EnableBossEncounterSync.Value || msg == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostLuciaEyeState); NetBossEncounterCodec.WriteLuciaEyeState(val2, msg); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[LuciaEye] failed to broadcast eye state: " + ex.Message); } } } private void HandleHostLuciaEyeState(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Client && Plugin.Cfg.EnableBossEncounterSync.Value) { if (!NetBossEncounterCodec.TryReadLuciaEyeState(reader, out NetLuciaEyeState result)) { NetLogger.Warn("[LuciaEye] malformed HostLuciaEyeState packet"); } else { NetBossEncounterManager.HandleHostLuciaEyeState(result); } } } internal void BroadcastHostLuciaDeath(NetLuciaDeath msg) { if (_mode != NetMode.Host || _net == null || !Plugin.Cfg.EnableBossEncounterSync.Value || msg == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostLuciaDeath); NetBossEncounterCodec.WriteLuciaDeath(val2, msg); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[LuciaDeath] failed to broadcast death: " + ex.Message); } } } private void HandleHostLuciaDeath(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Client && Plugin.Cfg.EnableBossEncounterSync.Value) { if (!NetBossEncounterCodec.TryReadLuciaDeath(reader, out NetLuciaDeath result)) { NetLogger.Warn("[LuciaDeath] malformed HostLuciaDeath packet"); } else { NetBossEncounterManager.HandleHostLuciaDeath(result); } } } internal void BroadcastHostWitchPhase(NetWitchPhase msg) { if (_mode != NetMode.Host || _net == null || !Plugin.Cfg.EnableBossEncounterSync.Value || msg == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostWitchPhase); NetBossEncounterCodec.WriteWitchPhase(val2, msg); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[WitchPhase] failed to broadcast phase: " + ex.Message); } } } private void HandleHostWitchPhase(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Client && Plugin.Cfg.EnableBossEncounterSync.Value) { if (!NetBossEncounterCodec.TryReadWitchPhase(reader, out NetWitchPhase result)) { NetLogger.Warn("[WitchPhase] malformed HostWitchPhase packet"); } else { NetBossEncounterManager.HandleHostWitchPhase(result); } } } internal void BroadcastHostWitchP2Manifest(NetWitchP2Manifest msg) { if (_mode != NetMode.Host || _net == null || !Plugin.Cfg.EnableBossEncounterSync.Value || msg == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostWitchP2Manifest); NetBossEncounterCodec.WriteWitchP2Manifest(val2, msg); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[WitchP2] failed to broadcast manifest: " + ex.Message); } } } private void HandleHostWitchP2Manifest(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Client && Plugin.Cfg.EnableBossEncounterSync.Value) { if (!NetBossEncounterCodec.TryReadWitchP2Manifest(reader, out NetWitchP2Manifest result)) { NetLogger.Warn("[WitchP2] malformed HostWitchP2Manifest packet"); } else { NetBossEncounterManager.HandleHostWitchP2Manifest(result); } } } internal void BroadcastHostWitchP2Result(NetWitchP2Result msg) { if (_mode != NetMode.Host || _net == null || !Plugin.Cfg.EnableBossEncounterSync.Value || msg == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostWitchP2Result); NetBossEncounterCodec.WriteWitchP2Result(val2, msg); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[WitchP2] failed to broadcast result: " + ex.Message); } } } private void HandleHostWitchP2Result(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Client && Plugin.Cfg.EnableBossEncounterSync.Value) { if (!NetBossEncounterCodec.TryReadWitchP2Result(reader, out NetWitchP2Result result)) { NetLogger.Warn("[WitchP2] malformed HostWitchP2Result packet"); } else { NetBossEncounterManager.HandleHostWitchP2Result(result); } } } internal void BroadcastHostRuntimeSpawn(NetRuntimeSpawn msg) { if (_mode != NetMode.Host || _net == null || msg == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostRuntimeSpawn); NetRuntimeSpawnCodec.Write(val2, msg); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[RuntimeSpawn] failed to broadcast: " + ex.Message); } } } private void HandleHostRuntimeSpawn(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Client) { if (!NetRuntimeSpawnCodec.TryRead(reader, out NetRuntimeSpawn result)) { NetLogger.Warn("[RuntimeSpawn] malformed HostRuntimeSpawn packet"); } else { RuntimeSpawnManager.HandleHostRuntimeSpawn(result); } } } internal void BroadcastLocalPlayerWeaponFire(NetPlayerWeaponFire msg) { if (_net == null || _mode == NetMode.Off || msg == null || !Plugin.Cfg.EnablePlayerWeaponSync.Value) { return; } NetRunState localState = _runStates.LocalState; msg.PeerId = localState.PeerId; msg.ChapterName = localState.ChapterName; msg.LevelIndex = localState.LevelIndex; msg.HasLevelSeed = localState.HasLevelSeed; msg.LevelSeed = localState.LevelSeed; msg.SentAt = Now(); NetRunStatsManager.RecordShotFired(msg.PeerId); if (_mode == NetMode.Host) { NetPeer[] array = _clients.ToArray(); foreach (NetPeer peer in array) { SendPlayerWeaponFire(peer, msg); } } else if (_hostPeer != null) { SendPlayerWeaponFire(_hostPeer, msg); } } private void SendPlayerWeaponFire(NetPeer peer, NetPlayerWeaponFire msg) { try { NetDataWriter val = NetMessage.For(NetMessageType.PlayerWeaponFire); NetPlayerWeaponFireCodec.Write(val, msg); peer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[PlayerWeaponFire] failed to send: " + ex.Message); } } } private void HandlePlayerWeaponFire(NetPeer peer, NetDataReader reader) { if (!Plugin.Cfg.EnablePlayerWeaponSync.Value) { return; } if (!NetPlayerWeaponFireCodec.TryRead(reader, out NetPlayerWeaponFire m)) { NetLogger.Warn("[PlayerWeaponFire] malformed PlayerWeaponFire packet"); } else if (_mode == NetMode.Host) { if (!_peerIds.TryGetValue(peer, out string value)) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug($"[PlayerWeaponFire] ignoring fire from unregistered peer {((IPEndPoint)(object)peer).Address}"); } return; } m.PeerId = value; NetRunStatsManager.RecordShotFired(value); if (m.MatchesScene(_runStates.LocalState)) { PlayerWeaponFireManager.Replay(m); } RelayPlayerWeaponFireToOtherClients(peer, m); } else if (_mode == NetMode.Client && !(m.PeerId == _runStates.LocalState.PeerId) && m.MatchesScene(_runStates.LocalState)) { PlayerWeaponFireManager.Replay(m); } } private void RelayPlayerWeaponFireToOtherClients(NetPeer sourcePeer, NetPlayerWeaponFire msg) { if (_mode != NetMode.Host) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { if (val != sourcePeer) { SendPlayerWeaponFire(val, msg); } } } internal void BroadcastLocalBreakableBreak(NetBreakableBreak msg) { if (_net == null || _mode == NetMode.Off || msg == null || !Plugin.Cfg.EnableBreakableSync.Value) { return; } NetRunState localState = _runStates.LocalState; msg.PeerId = localState.PeerId; msg.ChapterName = localState.ChapterName; msg.LevelIndex = localState.LevelIndex; msg.HasLevelSeed = localState.HasLevelSeed; msg.LevelSeed = localState.LevelSeed; msg.SentAt = Now(); NetRunStatsManager.RecordDestructibleDestroyed(msg.PeerId); if (_mode == NetMode.Host) { NetPeer[] array = _clients.ToArray(); foreach (NetPeer peer in array) { SendBreakableBreak(peer, msg); } } else if (_hostPeer != null) { SendBreakableBreak(_hostPeer, msg); } } private void SendBreakableBreak(NetPeer peer, NetBreakableBreak msg) { try { NetDataWriter val = NetMessage.For(NetMessageType.BreakableBreak); NetBreakableBreakCodec.Write(val, msg); peer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[BreakableBreak] failed to send: " + ex.Message); } } } private void HandleBreakableBreak(NetPeer peer, NetDataReader reader) { if (!Plugin.Cfg.EnableBreakableSync.Value) { return; } if (!NetBreakableBreakCodec.TryRead(reader, out NetBreakableBreak m)) { NetLogger.Warn("[BreakableBreak] malformed BreakableBreak packet"); } else if (_mode == NetMode.Host) { if (!_peerIds.TryGetValue(peer, out string value)) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug($"[BreakableBreak] ignoring break from unregistered peer {((IPEndPoint)(object)peer).Address}"); } return; } m.PeerId = value; NetRunStatsManager.RecordDestructibleDestroyed(value); if (m.MatchesScene(_runStates.LocalState)) { BreakableBreakManager.ApplyRemoteBreak(m); } RelayBreakableBreakToOtherClients(peer, m); } else if (_mode == NetMode.Client && !(m.PeerId == _runStates.LocalState.PeerId) && m.MatchesScene(_runStates.LocalState)) { BreakableBreakManager.ApplyRemoteBreak(m); } } private void RelayBreakableBreakToOtherClients(NetPeer sourcePeer, NetBreakableBreak msg) { if (_mode != NetMode.Host) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { if (val != sourcePeer) { SendBreakableBreak(val, msg); } } } internal void BroadcastLocalGateState(NetGateState msg) { if (_net == null || _mode == NetMode.Off || msg == null || !Plugin.Cfg.EnableGateSync.Value) { return; } NetRunState localState = _runStates.LocalState; msg.PeerId = localState.PeerId; msg.ChapterName = localState.ChapterName; msg.LevelIndex = localState.LevelIndex; msg.HasLevelSeed = localState.HasLevelSeed; msg.LevelSeed = localState.LevelSeed; msg.SentAt = Now(); if (_mode == NetMode.Host) { NetPeer[] array = _clients.ToArray(); foreach (NetPeer peer in array) { SendGateState(peer, msg); } } else if (_hostPeer != null) { SendGateState(_hostPeer, msg); } } private void SendGateState(NetPeer peer, NetGateState msg) { try { NetDataWriter val = NetMessage.For(NetMessageType.GateState); NetGateStateCodec.Write(val, msg); peer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[GateSync] failed to send: " + ex.Message); } } } private void HandleGateState(NetPeer peer, NetDataReader reader) { if (!Plugin.Cfg.EnableGateSync.Value) { return; } if (!NetGateStateCodec.TryRead(reader, out NetGateState m)) { NetLogger.Warn("[GateSync] malformed GateState packet"); } else if (_mode == NetMode.Host) { if (!_peerIds.TryGetValue(peer, out string value)) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug($"[GateSync] ignoring gate from unregistered peer {((IPEndPoint)(object)peer).Address}"); } return; } m.PeerId = value; if (m.MatchesScene(_runStates.LocalState)) { GateSyncManager.ApplyRemoteGate(m); } RelayGateStateToOtherClients(peer, m); } else if (_mode == NetMode.Client && !(m.PeerId == _runStates.LocalState.PeerId) && m.MatchesScene(_runStates.LocalState)) { GateSyncManager.ApplyRemoteGate(m); } } private void RelayGateStateToOtherClients(NetPeer sourcePeer, NetGateState msg) { if (_mode != NetMode.Host) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { if (val != sourcePeer) { SendGateState(val, msg); } } } internal void BroadcastLocalTriggerDoors(NetTriggerDoors msg) { if (_net == null || _mode == NetMode.Off || msg == null || !Plugin.Cfg.EnableTriggerDoorSync.Value) { return; } NetRunState localState = _runStates.LocalState; msg.PeerId = localState.PeerId; msg.ChapterName = localState.ChapterName; msg.LevelIndex = localState.LevelIndex; msg.HasLevelSeed = localState.HasLevelSeed; msg.LevelSeed = localState.LevelSeed; msg.SentAt = Now(); if (_mode == NetMode.Host) { NetPeer[] array = _clients.ToArray(); foreach (NetPeer peer in array) { SendTriggerDoors(peer, msg); } } else if (_hostPeer != null) { SendTriggerDoors(_hostPeer, msg); } } private void SendTriggerDoors(NetPeer peer, NetTriggerDoors msg) { try { NetDataWriter val = NetMessage.For(NetMessageType.TriggerDoors); NetTriggerDoorsCodec.Write(val, msg); peer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[DoorSync] failed to send: " + ex.Message); } } } private void HandleTriggerDoors(NetPeer peer, NetDataReader reader) { if (!Plugin.Cfg.EnableTriggerDoorSync.Value) { return; } if (!NetTriggerDoorsCodec.TryRead(reader, out NetTriggerDoors m)) { NetLogger.Warn("[DoorSync] malformed TriggerDoors packet"); } else if (_mode == NetMode.Host) { if (!_peerIds.TryGetValue(peer, out string value)) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug($"[DoorSync] ignoring doors from unregistered peer {((IPEndPoint)(object)peer).Address}"); } return; } m.PeerId = value; if (m.MatchesScene(_runStates.LocalState)) { TriggerDoorSyncManager.ApplyRemote(m); } RelayTriggerDoorsToOtherClients(peer, m); } else if (_mode == NetMode.Client && !(m.PeerId == _runStates.LocalState.PeerId) && m.MatchesScene(_runStates.LocalState)) { TriggerDoorSyncManager.ApplyRemote(m); } } private void RelayTriggerDoorsToOtherClients(NetPeer sourcePeer, NetTriggerDoors msg) { if (_mode != NetMode.Host) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { if (val != sourcePeer) { SendTriggerDoors(val, msg); } } } internal void SendClientArenaEnter(NetClientArenaEnter msg) { if (_net == null || _mode != NetMode.Client || _hostPeer == null || msg == null || !Plugin.Cfg.EnableArenaLockdown.Value) { return; } try { NetDataWriter val = NetMessage.For(NetMessageType.ClientArenaEnter); NetClientArenaEnterCodec.Write(val, msg); _hostPeer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[ArenaLockdown] failed to send ClientArenaEnter: " + ex.Message); } } private void HandleClientArenaEnter(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Host && Plugin.Cfg.EnableArenaLockdown.Value) { string value; if (!NetClientArenaEnterCodec.TryRead(reader, out NetClientArenaEnter m)) { NetLogger.Warn("[ArenaLockdown] malformed ClientArenaEnter packet"); } else if (_peerIds.TryGetValue(peer, out value)) { ArenaLockdownManager.HandleClientArenaEnter(m, value); } } } internal void BroadcastArenaCommand(NetArenaCommand msg) { if (_mode != NetMode.Host || _net == null || msg == null || !Plugin.Cfg.EnableArenaLockdown.Value || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.ArenaCommand); NetArenaCommandCodec.Write(val2, msg); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[ArenaLockdown] failed to broadcast ArenaCommand: " + ex.Message); } } } private void HandleArenaCommand(NetPeer peer, NetDataReader reader) { if (_mode == NetMode.Client && Plugin.Cfg.EnableArenaLockdown.Value) { if (!NetArenaCommandCodec.TryRead(reader, out NetArenaCommand m)) { NetLogger.Warn("[ArenaLockdown] malformed ArenaCommand packet"); } else { ArenaLockdownManager.HandleArenaCommand(m, LocalPeerId); } } } internal bool TryGetLocalScene(out string chapter, out int level, out bool hasSeed, out int seed) { NetRunState localState = _runStates.LocalState; chapter = localState.ChapterName; level = localState.LevelIndex; hasSeed = localState.HasLevelSeed; seed = localState.LevelSeed; return localState.HasLevel; } internal List GetPeerIdsInLevel(string chapter, int level, bool hasSeed, int seed) { List list = new List(); bool value = Plugin.Cfg.EnableLevelSeedAuthority.Value; if (SceneMatch(_runStates.LocalState, chapter, level, hasSeed, seed, value)) { list.Add(_runStates.LocalState.PeerId); } foreach (NetRunState remoteState in _runStates.RemoteStates) { if (SceneMatch(remoteState, chapter, level, hasSeed, seed, value)) { list.Add(remoteState.PeerId); } } return list; } private static bool SceneMatch(NetRunState s, string chapter, int level, bool hasSeed, int seed, bool seedAuth) { if (s == null || !s.HasLevel) { return false; } if (!string.Equals(s.ChapterName, chapter, StringComparison.Ordinal)) { return false; } if (s.LevelIndex != level) { return false; } if (seedAuth) { if (!hasSeed || !s.HasLevelSeed) { return false; } if (s.LevelSeed != seed) { return false; } } return true; } internal void BroadcastLocalWorldPickupSpawn(NetWorldPickupSpawn msg) { if (_net == null || _mode == NetMode.Off || msg == null || !Plugin.Cfg.EnableWorldItemDropSync.Value) { return; } NetRunState localState = _runStates.LocalState; msg.OwnerPeerId = localState.PeerId; msg.ChapterName = localState.ChapterName; msg.LevelIndex = localState.LevelIndex; msg.HasLevelSeed = localState.HasLevelSeed; msg.LevelSeed = localState.LevelSeed; msg.SentAt = Now(); if (_mode == NetMode.Host) { NetPeer[] array = _clients.ToArray(); foreach (NetPeer peer in array) { SendWorldPickupSpawn(peer, msg); } } else if (_hostPeer != null) { SendWorldPickupSpawn(_hostPeer, msg); } } private void SendWorldPickupSpawn(NetPeer peer, NetWorldPickupSpawn msg) { try { NetDataWriter val = NetMessage.For(NetMessageType.WorldPickupSpawn); NetWorldPickupCodec.WriteSpawn(val, msg); peer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[WorldPickup] spawn send failed: " + ex.Message); } } } private void HandleWorldPickupSpawn(NetPeer peer, NetDataReader reader) { if (!Plugin.Cfg.EnableWorldItemDropSync.Value) { return; } if (!NetWorldPickupCodec.TryReadSpawn(reader, out NetWorldPickupSpawn m)) { NetLogger.Warn("[WorldPickup] malformed WorldPickupSpawn packet"); } else if (_mode == NetMode.Host) { if (!_peerIds.TryGetValue(peer, out string value)) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug($"[WorldPickup] spawn from unregistered peer {((IPEndPoint)(object)peer).Address}"); } return; } m.OwnerPeerId = value; if (m.MatchesScene(_runStates.LocalState)) { WorldPickupManager.ApplyRemoteSpawn(m); } RelayWorldPickupSpawnToOtherClients(peer, m); } else if (_mode == NetMode.Client && !(m.OwnerPeerId == _runStates.LocalState.PeerId) && m.MatchesScene(_runStates.LocalState)) { WorldPickupManager.ApplyRemoteSpawn(m); } } private void RelayWorldPickupSpawnToOtherClients(NetPeer sourcePeer, NetWorldPickupSpawn msg) { if (_mode != NetMode.Host) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { if (val != sourcePeer) { SendWorldPickupSpawn(val, msg); } } } internal void SendWorldPickupTakeRequest(string ownerPeerId, ushort seq) { if (_mode != NetMode.Client || _hostPeer == null || !Plugin.Cfg.EnableWorldItemDropSync.Value) { return; } try { NetWorldPickupTake m = new NetWorldPickupTake { OwnerPeerId = ownerPeerId, Seq = seq, SentAt = Now() }; NetDataWriter val = NetMessage.For(NetMessageType.WorldPickupTakeRequest); NetWorldPickupCodec.WriteTake(val, m); _hostPeer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[WorldPickup] take request send failed: " + ex.Message); } } } private void HandleWorldPickupTakeRequest(NetPeer peer, NetDataReader reader) { if (!Plugin.Cfg.EnableWorldItemDropSync.Value || _mode != NetMode.Host) { return; } string value; if (!NetWorldPickupCodec.TryReadTake(reader, out NetWorldPickupTake m)) { NetLogger.Warn("[WorldPickup] malformed WorldPickupTakeRequest packet"); } else if (!_peerIds.TryGetValue(peer, out value)) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug($"[WorldPickup] take request from unregistered peer {((IPEndPoint)(object)peer).Address}"); } } else { WorldPickupManager.HostHandleTakeRequest(m, value); } } internal void BroadcastWorldPickupRemoved(NetWorldPickupRemoved msg) { if (_net != null && _mode == NetMode.Host && msg != null && Plugin.Cfg.EnableWorldItemDropSync.Value) { msg.SentAt = Now(); NetPeer[] array = _clients.ToArray(); foreach (NetPeer peer in array) { SendWorldPickupRemoved(peer, msg); } } } private void SendWorldPickupRemoved(NetPeer peer, NetWorldPickupRemoved msg) { try { NetDataWriter val = NetMessage.For(NetMessageType.WorldPickupRemoved); NetWorldPickupCodec.WriteRemoved(val, msg); peer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[WorldPickup] removed send failed: " + ex.Message); } } } private void HandleWorldPickupRemoved(NetPeer peer, NetDataReader reader) { if (Plugin.Cfg.EnableWorldItemDropSync.Value && _mode == NetMode.Client) { if (!NetWorldPickupCodec.TryReadRemoved(reader, out NetWorldPickupRemoved m)) { NetLogger.Warn("[WorldPickup] malformed WorldPickupRemoved packet"); } else { WorldPickupManager.ApplyRemoved(m.Key, m.TakenByPeerId); } } } internal void BroadcastLocalHeldWeapon(NetPlayerHeldWeapon msg) { if (_net == null || _mode == NetMode.Off || msg == null || !Plugin.Cfg.EnableRemoteWeaponModel.Value) { return; } msg.PeerId = _runStates.LocalState.PeerId; msg.SentAt = Now(); if (_mode == NetMode.Host) { NetPeer[] array = _clients.ToArray(); foreach (NetPeer peer in array) { SendPlayerHeldWeapon(peer, msg); } } else if (_hostPeer != null) { SendPlayerHeldWeapon(_hostPeer, msg); } } private void SendPlayerHeldWeapon(NetPeer peer, NetPlayerHeldWeapon msg) { try { NetDataWriter val = NetMessage.For(NetMessageType.PlayerHeldWeapon); NetPlayerHeldWeaponCodec.Write(val, msg); peer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[HeldWeapon] failed to send: " + ex.Message); } } } private void HandlePlayerHeldWeapon(NetPeer peer, NetDataReader reader) { if (!Plugin.Cfg.EnableRemoteWeaponModel.Value) { return; } if (!NetPlayerHeldWeaponCodec.TryRead(reader, out NetPlayerHeldWeapon m)) { NetLogger.Warn("[HeldWeapon] malformed PlayerHeldWeapon packet"); } else if (_mode == NetMode.Host) { if (!_peerIds.TryGetValue(peer, out string value)) { return; } m.PeerId = value; PlayerHeldWeaponManager.Apply(m); NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { if (val != peer) { SendPlayerHeldWeapon(val, m); } } } else if (_mode == NetMode.Client && !(m.PeerId == _runStates.LocalState.PeerId)) { PlayerHeldWeaponManager.Apply(m); } } public void ApplyFinalizedGenerationSnapshot(NetGenerationInputSnapshot snapshot) { if (snapshot == null || !snapshot.Finalized || _mode == NetMode.Off || _net == null || !Plugin.Cfg.EnableRunStateNegotiation.Value || snapshot.LevelIndex < 0) { return; } bool levelCorrected; NetRunState updated; bool num = _runStates.ApplyFinalizedGenerationSnapshot(snapshot.Chapter, snapshot.LevelIndex, snapshot.HasSeed, snapshot.Seed, snapshot.GraphName, Now(), out levelCorrected, out updated); if (_mode == NetMode.Host) { NetGenerationInputCapture.RunStateSnapshotAppliedHost++; } else { NetGenerationInputCapture.RunStateSnapshotAppliedClient++; } if (levelCorrected) { NetGenerationInputCapture.RunStateSnapshotOverrideUsed++; } if (num) { NetGenerationInputCapture.RunStateSnapshotRevisionBumped++; NetLogger.Info(string.Format("[RunStateSync] applied finalized snapshot role={0} chapter={1} level={2} graph={3} seed={4} revision={5}", _mode, updated.ChapterName, updated.LevelIndex, string.IsNullOrEmpty(snapshot.GraphName) ? "?" : snapshot.GraphName, updated.HasLevelSeed ? updated.LevelSeed.ToString() : "?", updated.Revision)); if (_mode == NetMode.Host) { NetHostTransitionGuard.End("finalized-snapshot"); } SendLocalRunStateToConnectedPeers(); SendHostSceneRequestsForDriftedClients(); } } internal void ReportLocalEnemyDeathEvent(NetGameplayDeathEvent deathEvent) { if (_mode == NetMode.Host && _net != null && Plugin.Cfg.EnableHostEnemyDeathEventMirror.Value && deathEvent != null && _clients.Count != 0) { NetPeer[] array = _clients.ToArray(); foreach (NetPeer peer in array) { SendHostEnemyDeathEvent(peer, deathEvent); } } } internal void ReportClientEnemyDeathClaim(NetGameplayDeathEvent deathEvent) { if (_mode == NetMode.Client && _net != null && Plugin.Cfg.EnableClientEnemyDeathClaim.Value && deathEvent != null && _hostPeer != null) { SendClientEnemyDeathClaim(_hostPeer, deathEvent); } } internal void ReportPlayerLifeState(NetPlayerLifeState state) { if (_mode == NetMode.Off || _net == null || !Plugin.Cfg.EnableCoopPlayerDownedRevive.Value || state == null) { return; } if (_mode == NetMode.Host) { if (string.IsNullOrWhiteSpace(state.SourcePeerId)) { state.SourcePeerId = "host"; } SendPlayerLifeStateToClients(state, null); } else if (_mode == NetMode.Client && _hostPeer != null) { SendPlayerLifeState(_hostPeer, state); } } internal void ReportHostPlayerLifeStateToAll(NetPlayerLifeState state) { if (_mode == NetMode.Host && _net != null && Plugin.Cfg.EnableCoopPlayerDownedRevive.Value && state != null) { if (string.IsNullOrWhiteSpace(state.SourcePeerId)) { state.SourcePeerId = "host"; } SendPlayerLifeStateToClients(state, null); } } internal IReadOnlyCollection GetSessionsSnapshot() { return _sessions.Sessions; } internal void BroadcastRunStatsFinalized(NetRunStatsList list) { if (_net == null || _mode != NetMode.Host || list == null) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.RunStatsFinalized); NetRunStatsListCodec.Write(val2, list); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[RunStats] failed to send finalized list: " + ex.Message); } } } } private void HandleRunStatsFinalized(NetPeer peer, NetPacketReader reader) { if (_mode == NetMode.Client) { if (!NetRunStatsListCodec.TryRead(reader, out NetRunStatsList list)) { NetLogger.Warn("[RunStats] malformed RunStatsFinalized packet"); } else { NetRunStatsClientCache.ApplyReceivedBroadcast(list); } } } private NetSessionSettingsState BuildSessionSettingsState() { return new NetSessionSettingsState { Revision = ++_sessionSettingsRevision, FriendlyFire = Plugin.Cfg.FriendlyFire.Value }; } internal void BroadcastSessionSettings(string reason) { if (_net != null && _mode == NetMode.Host) { NetSessionSettingsState netSessionSettingsState = BuildSessionSettingsState(); NetPeer[] array = _clients.ToArray(); foreach (NetPeer peer in array) { SendSessionSettings(peer, netSessionSettingsState); } if (Plugin.Cfg.LogFriendlyFire.Value) { NetLogger.Info($"[FF] session settings broadcast: friendlyFire={netSessionSettingsState.FriendlyFire} rev={netSessionSettingsState.Revision} reason={reason}"); } } } private void SendSessionSettingsToPeer(NetPeer peer) { if (_mode == NetMode.Host) { SendSessionSettings(peer, BuildSessionSettingsState()); } } private void SendSessionSettings(NetPeer peer, NetSessionSettingsState state) { try { NetDataWriter val = NetMessage.For(NetMessageType.SessionSettings); NetSessionSettingsCodec.Write(val, state); peer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[FF] failed to send session settings: " + ex.Message); } } } private void HandleSessionSettings(NetPeer peer, NetPacketReader reader) { if (_mode == NetMode.Client) { if (!NetSessionSettingsCodec.TryRead((NetDataReader)(object)reader, out NetSessionSettingsState result)) { NetLogger.Warn("[FF] malformed SessionSettings packet"); } else if (NetSessionSettings.ApplyReceived(result)) { CoopToasts.NotifySessionSetting(CoopLoc.Get("session.friendlyFire.label", "Friendly fire"), result.FriendlyFire); } } } internal void SendFriendlyFireHit(NetFriendlyFireHit msg) { if (_mode != NetMode.Client || _hostPeer == null || msg == null) { return; } try { msg.SentAt = Now(); NetDataWriter val = NetMessage.For(NetMessageType.PlayerFriendlyFireHit); NetFriendlyFireHitCodec.Write(val, msg); _hostPeer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[FF] failed to send hit request: " + ex.Message); } } } private void HandleFriendlyFireHit(NetPeer peer, NetPacketReader reader) { //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_0188: 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) //IL_0151: Unknown result type (might be due to invalid IL or missing references) if (_mode != NetMode.Host) { return; } if (!NetFriendlyFireHitCodec.TryRead((NetDataReader)(object)reader, out NetFriendlyFireHit result)) { NetLogger.Warn("[FF] malformed PlayerFriendlyFireHit packet"); return; } if (!_peerIds.TryGetValue(peer, out string value)) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug($"[FF] ignoring hit request from unregistered peer {((IPEndPoint)(object)peer).Address}"); } return; } result.SourcePeerId = value; if (!Plugin.Cfg.FriendlyFire.Value) { return; } float num = Mathf.Clamp(result.Damage, 0f, 1000f); if (!(num <= 0f) && !string.IsNullOrWhiteSpace(result.VictimPeerId) && !(result.VictimPeerId == value) && !NetPlayerLifeManager.IsPeerDownOrDead(result.VictimPeerId) && result.MatchesScene(_runStates.LocalState)) { if (Plugin.Cfg.LogFriendlyFire.Value) { NetLogger.Info($"[FF] hit request: source={value} victim={result.VictimPeerId} dmg={num:F1} type={result.DamageTypeInt} seq={result.Seq}"); } if (result.VictimPeerId == _runStates.LocalState.PeerId) { NetPlayerLifeManager.ApplyFriendlyFireDamageToLocalHost(num, result.DamageTypeInt, result.HasPosition ? result.Position : Vector3.zero, "player friendly fire (" + value + ")"); } else { NetPlayerLifeManager.ReportHostAuthoritativeEnemyDamage(result.VictimPeerId, num, "player friendly fire", result.HasPosition ? result.Position : Vector3.zero, result.DamageTypeInt); } } } internal IReadOnlyCollection GetKnownPlayerLifePeerIds() { List list = (from s in _sessions.Sessions where s.State == NetConnectionState.Connected select s.PeerId into id where !string.IsNullOrWhiteSpace(id) select id).Distinct().ToList(); if (list.Count == 0 && !string.IsNullOrWhiteSpace(_runStates.LocalState.PeerId)) { list.Add(_runStates.LocalState.PeerId); } return list.AsReadOnly(); } private void HandleEnemyStateSnapshotTimer() { //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) if (_mode != NetMode.Host || _net == null || !Plugin.Cfg.EnableHostEnemyStateSnapshotMirror.Value || _clients.Count == 0) { return; } float value = Plugin.Cfg.EnemyStateSnapshotSendRateHz.Value; if (value <= 0f) { return; } float num = 1f / value; float num2 = Now(); if (num2 - _lastEnemyStateSnapshotSendTime < num) { return; } _lastEnemyStateSnapshotSendTime = num2; _remoteInterestScratch.Clear(); _visualProxies.CollectInterestPositions(_remoteInterestScratch, num2, Plugin.Cfg.RemotePlayerVisualTimeoutSeconds.Value); NetGameplayProbeManager.SetRemotePlayerInterestPositions(_remoteInterestScratch); if (Plugin.Cfg.LogEnemyInterestDiag.Value && num2 - _lastInterestFeedLogAt > 1f) { _lastInterestFeedLogAt = num2; Transform localTransform = _localPlayer.LocalTransform; object obj; Vector3 val; if (!((Object)(object)localTransform != (Object)null)) { obj = ""; } else { val = localTransform.position; obj = ((Vector3)(ref val)).ToString("F1"); } string text = (string)obj; object obj2; if (_remoteInterestScratch.Count <= 0) { obj2 = "none"; } else { val = _remoteInterestScratch[0]; obj2 = ((Vector3)(ref val)).ToString("F1"); } string text2 = (string)obj2; NetLogger.Info($"[InterestFeed] collectedRemote={_remoteInterestScratch.Count} firstRemote={text2} hostPlayer={text} visibleProxies={_visualProxies.VisibleCount} totalProxies={_visualProxies.ProxyCount}"); } int enemyStateSnapshotMaxPerPacket = GetEnemyStateSnapshotMaxPerPacket(); List list = NetGameplayProbeManager.CollectHostEnemyStateSnapshots(_runStates.LocalState, Plugin.Cfg.OnlySendAliveEnemyStateSnapshots.Value, 256, ref _enemyStateSnapshotSequence); if (list.Count != 0) { NetPeer[] array = _clients.ToArray(); foreach (NetPeer peer in array) { SendHostEnemyStateSnapshots(peer, list, enemyStateSnapshotMaxPerPacket); } } } private void HandleWorldRosterTimer() { if (_mode != NetMode.Host || _net == null || _clients.Count == 0 || !Plugin.Cfg.EnableHostEnemyStateSnapshotMirror.Value) { return; } NetRunState localState = _runStates.LocalState; if (!localState.HasLevel) { return; } float num = Now(); bool num2 = localState.Revision != _lastWorldRosterSentRevision; bool flag = num - _lastWorldRosterSendTime >= 30f; if ((!num2 && !flag) || num - _lastWorldRosterSendTime < 3f) { return; } _lastWorldRosterSendTime = num; _lastWorldRosterSentRevision = localState.Revision; List list = NetGameplayProbeManager.BuildHostWorldRoster(); if (list.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer peer in array) { SendHostWorldRoster(peer, list, localState.Revision); } if (Plugin.Cfg.EnableHostLevelManifest.Value) { NetLevelManifest netLevelManifest = NetGameplayProbeManager.BuildLevelManifest("Host"); if (netLevelManifest != null) { SendHostLevelManifest(netLevelManifest); } } } private void SendHostWorldRoster(NetPeer peer, List records, int revision) { try { NetDataWriter val = NetMessage.For(NetMessageType.HostWorldRoster); val.Put(revision); NetWorldEntityRosterCodec.Write(val, records); peer.Send(val, (DeliveryMethod)2); NetLogger.Info($"[WorldRoster] Host sent roster peer={((IPEndPoint)(object)peer).Address} count={records.Count} revision={revision}"); } catch (Exception ex) { NetLogger.Warn("[WorldRoster] Host failed to send roster: " + ex.Message); } } private void HandleHostWorldRoster(NetPeer peer, NetDataReader reader) { try { if (_mode == NetMode.Client) { int hostRevision = reader.GetInt(); NetGameplayProbeManager.ProcessHostWorldRoster(NetWorldEntityRosterCodec.Read(reader), hostRevision); } } catch (Exception ex) { NetLogger.Warn("[WorldRoster] Client failed to process roster: " + ex.Message); } } internal void ReportHostAttackPhaseEvent(NetHostAttackPhaseEvent evt) { if (_mode == NetMode.Host && _net != null && Plugin.Cfg.EnableHostAttackPhaseEvents.Value && Plugin.Cfg.EnableHostDrivenEnemyProxy.Value && evt != null && _clients.Count != 0) { NetPeer[] array = _clients.ToArray(); foreach (NetPeer peer in array) { SendHostAttackPhaseEvent(peer, evt); } } } private void SendHostAttackPhaseEvent(NetPeer peer, NetHostAttackPhaseEvent evt) { try { NetDataWriter val = NetMessage.For(NetMessageType.HostAttackPhaseEvent); NetHostAttackPhaseEventCodec.Write(val, evt); peer.Send(val, (DeliveryMethod)0); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[AttackPhase] Failed to send: " + ex.Message); } } } private void HandleHostAttackPhaseEvent(NetPeer peer, NetDataReader reader) { try { if (_mode == NetMode.Client && Plugin.Cfg.EnableHostDrivenEnemyProxy.Value && Plugin.Cfg.EnableHostAttackPhaseEvents.Value) { if (!NetHostAttackPhaseEventCodec.TryRead(reader, out NetHostAttackPhaseEvent evt)) { NetLogger.Warn("[AttackPhase] Malformed HostAttackPhaseEvent packet"); } else { NetGameplayProbeManager.ProcessHostAttackPhaseEvent(evt); } } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[AttackPhase] Receive error: " + ex.Message); } } } internal void ReportHostProjectileVisualSpawn(NetHostProjectileVisualSpawn evt) { if (_mode == NetMode.Host && _net != null && Plugin.Cfg.EnableHostProjectileVisualSpawnEvent.Value && Plugin.Cfg.EnableHostDrivenEnemyProxy.Value && evt != null && _clients.Count != 0) { NetPeer[] array = _clients.ToArray(); foreach (NetPeer peer in array) { SendHostProjectileVisualSpawn(peer, evt); } } } private void SendHostProjectileVisualSpawn(NetPeer peer, NetHostProjectileVisualSpawn evt) { try { NetDataWriter val = NetMessage.For(NetMessageType.HostProjectileVisualSpawn); NetHostProjectileVisualSpawnCodec.Write(val, evt); peer.Send(val, (DeliveryMethod)0); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[ProjectileVisual] Failed to send: " + ex.Message); } } } private void HandleHostProjectileVisualSpawn(NetPeer peer, NetDataReader reader) { try { if (_mode == NetMode.Client && Plugin.Cfg.EnableHostProjectileVisualSpawnEvent.Value) { NetRunState state; if (!NetHostProjectileVisualSpawnCodec.TryRead(reader, out NetHostProjectileVisualSpawn evt)) { NetLogger.Warn("[ProjectileVisual] Malformed HostProjectileVisualSpawn packet"); } else if (NetRunStateBridge.TryGetLocalRunState(out state)) { evt.MatchesScene(state); } } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[ProjectileVisual] Receive error: " + ex.Message); } } } internal void ReportHostEnemyDamageEvent(NetHostEnemyDamageEvent evt) { if (_mode == NetMode.Host && _net != null && Plugin.Cfg.EnableHostEnemyDamageEventSync.Value && evt != null && _clients.Count != 0) { NetPeer[] array = _clients.ToArray(); foreach (NetPeer peer in array) { SendHostEnemyDamageEvent(peer, evt); } } } private void SendHostEnemyDamageEvent(NetPeer peer, NetHostEnemyDamageEvent evt) { try { NetDataWriter val = NetMessage.For(NetMessageType.HostEnemyDamageEvent); NetHostEnemyDamageEventCodec.Write(val, evt); peer.Send(val, (DeliveryMethod)0); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[EnemyDamage] Failed to send: " + ex.Message); } } } private void HandleHostEnemyDamageEvent(NetPeer peer, NetDataReader reader) { try { if (_mode == NetMode.Client) { if (!NetHostEnemyDamageEventCodec.TryRead(reader, out NetHostEnemyDamageEvent evt)) { NetLogger.Warn("[EnemyDamage] Malformed HostEnemyDamageEvent packet"); } else { NetGameplayProbeManager.ProcessHostEnemyDamageEvent(evt); } } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[EnemyDamage] Receive error: " + ex.Message); } } } internal void ReportHostEnemyHealthState(NetHostEnemyHealthState state) { if (_mode == NetMode.Host && _net != null && Plugin.Cfg.EnableHostEnemyHealthStateSync.Value && state != null && _clients.Count != 0) { NetPeer[] array = _clients.ToArray(); foreach (NetPeer peer in array) { SendHostEnemyHealthState(peer, state); } } } private void SendHostEnemyHealthState(NetPeer peer, NetHostEnemyHealthState state) { try { NetDataWriter val = NetMessage.For(NetMessageType.HostEnemyHealthState); NetHostEnemyHealthStateCodec.Write(val, state); peer.Send(val, (DeliveryMethod)0); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[EnemyHealth] Failed to send: " + ex.Message); } } } private void HandleHostEnemyHealthState(NetPeer peer, NetDataReader reader) { try { if (_mode == NetMode.Client) { if (!NetHostEnemyHealthStateCodec.TryRead(reader, out NetHostEnemyHealthState state)) { NetLogger.Warn("[EnemyHealth] Malformed HostEnemyHealthState packet"); } else { NetGameplayProbeManager.ProcessHostEnemyHealthState(state); } } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[EnemyHealth] Receive error: " + ex.Message); } } } internal void SendClientHitRequest(NetClientHitRequest request) { if (_mode != NetMode.Client || _net == null || !Plugin.Cfg.EnableClientHitRequest.Value || request == null || _hostPeer == null) { return; } try { NetDataWriter val = NetMessage.For(NetMessageType.ClientHitRequest); NetClientHitRequestCodec.Write(val, request); _hostPeer.Send(val, (DeliveryMethod)0); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[ClientHit] Failed to send: " + ex.Message); } } } private void HandleClientHitRequest(NetPeer peer, NetDataReader reader) { try { if (_mode == NetMode.Host && Plugin.Cfg.EnableClientHitRequest.Value) { if (!NetClientHitRequestCodec.TryRead(reader, out NetClientHitRequest result)) { NetLogger.Warn("[ClientHit] Malformed ClientHitRequest packet"); return; } if (!_peerIds.TryGetValue(peer, out string value)) { NetLogger.Warn($"[ClientHit] Ignoring hit request from unregistered peer {((IPEndPoint)(object)peer).Address}"); return; } result.ClientPeerId = value; NetGameplayProbeManager.ProcessClientHitRequest(result, value); } } catch (Exception ex) { NetLogger.Warn("[ClientHit] Receive error: " + ex.Message); } } internal void SendHostLevelManifest(NetLevelManifest manifest) { if (_mode != NetMode.Host || _net == null || !Plugin.Cfg.EnableHostLevelManifest.Value || manifest == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostLevelManifest); NetLevelManifestCodec.Write(val2, manifest); val.Send(val2, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[LevelManifest] Host failed to send manifest: " + ex.Message); } } NetLogger.Info($"[LevelManifest] Host sent manifest peers={_clients.Count} rooms={manifest.Header.RoomCount} units={manifest.Header.UnitCount} combat={manifest.Header.CombatEnemyCount} specials={manifest.Header.SpecialEventCount} genHash={manifest.Header.GenerationHash} runtimeHash={manifest.Header.RuntimeHash}"); } private void HandleHostLevelManifest(NetPeer peer, NetDataReader reader) { try { if (_mode == NetMode.Client && Plugin.Cfg.EnableHostLevelManifest.Value) { if (!NetLevelManifestCodec.TryRead(reader, out NetLevelManifest m)) { NetLogger.Warn("[LevelManifest] Malformed HostLevelManifest packet"); } else { NetGameplayProbeManager.ProcessHostLevelManifest(m); } } } catch (Exception ex) { NetLogger.Warn("[LevelManifest] Receive error: " + ex.Message); } } internal void ReportHostHitVisualEvent(NetHostHitVisualEvent evt) { if (_mode != NetMode.Host || _net == null || !Plugin.Cfg.EnableClientHitVisual.Value || evt == null || _clients.Count == 0) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { try { NetDataWriter val2 = NetMessage.For(NetMessageType.HostHitVisualEvent); NetHostHitVisualEventCodec.Write(val2, evt); val.Send(val2, (DeliveryMethod)0); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[HitVisual] Failed to send: " + ex.Message); } } } } private void HandleHostHitVisualEvent(NetPeer peer, NetDataReader reader) { try { if (_mode == NetMode.Client && Plugin.Cfg.EnableClientHitVisual.Value) { if (!NetHostHitVisualEventCodec.TryRead(reader, out NetHostHitVisualEvent e)) { NetLogger.Warn("[HitVisual] Malformed HostHitVisualEvent packet"); } else { NetGameplayProbeManager.ProcessHostHitVisualEvent(e); } } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[HitVisual] Receive error: " + ex.Message); } } } private void HandleLevelSeedPollTimer() { if (_mode != NetMode.Off && _net != null && Plugin.Cfg.EnableLevelSeedAuthority.Value) { float num = Now(); if (!(num - _lastLevelSeedPollTime < 0.5f)) { _lastLevelSeedPollTime = num; NetLevelSeed.ReportObservedGameManagerSeed("NetService.LevelSeedPoll"); } } } private void HandleRunStateTimer() { if (_mode != NetMode.Off && _net != null && Plugin.Cfg.EnableRunStateNegotiation.Value) { float num = Plugin.Cfg.RunStateBroadcastIntervalSeconds.Value; if (num < 1f) { num = 1f; } float num2 = Now(); if (!(num2 - _lastRunStateSendTime < num)) { _lastRunStateSendTime = num2; SendLocalRunStateToConnectedPeers(); SendHostSceneRequestsForDriftedClients(); } } } private void SendLocalRunStateToConnectedPeers() { if (_net == null || _mode == NetMode.Off || !Plugin.Cfg.EnableRunStateNegotiation.Value) { return; } if (_mode == NetMode.Host) { NetPeer[] array = _clients.ToArray(); foreach (NetPeer peer in array) { SendRunState(peer, _runStates.LocalState); } } else if (_hostPeer != null) { SendRunState(_hostPeer, _runStates.LocalState); } } private void SendRunState(NetPeer peer, NetRunState state) { try { if (state.Revision > 0) { NetDataWriter val = NetMessage.For(NetMessageType.RunStateUpdate); NetRunStateCodec.Write(val, state); peer.Send(val, (DeliveryMethod)2); } } catch (Exception ex) { NetLogger.Warn("[RunState] Failed to send run state: " + ex.Message); } } private void OnConnectionRequest(ConnectionRequest request) { if (_mode != NetMode.Host) { request.Reject(); } else if (_clients.Count >= Plugin.Cfg.MaxPlayers.Value - 1) { NetLogger.Info($"[Net] Rejected (server full): {_clients.Count + 1}/{Plugin.Cfg.MaxPlayers.Value}"); request.Reject(); } else { request.Accept(); } } private void OnPeerConnected(NetPeer peer) { if (_mode == NetMode.Host) { if (!_clients.Contains(peer)) { _clients.Add(peer); } NetLogger.Info($"[Net] Peer connected from {((IPEndPoint)(object)peer).Address} — awaiting HandshakeRequest"); } else { _hostPeer = peer; _clientConnectInProgress = false; _nextClientReconnectTime = 0f; NetLogger.Info($"[Net] Connected to host at {((IPEndPoint)(object)peer).Address} — sending HandshakeRequest"); NetDataWriter val = NetMessage.For(NetMessageType.HandshakeRequest); NetHandshake.WriteRequest(val, Plugin.Cfg.PlayerName.Value); peer.Send(val, (DeliveryMethod)2); } } private void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod method) { try { TouchPeer(peer); NetMessageType netMessageType = (NetMessageType)((NetDataReader)reader).GetByte(); switch (netMessageType) { case NetMessageType.HandshakeRequest: HandleHandshakeRequest(peer, reader); break; case NetMessageType.HandshakeAccepted: HandleHandshakeAccepted(peer, reader); break; case NetMessageType.HandshakeRejected: { string text2 = ((NetDataReader)reader).GetString(); NetLogger.Info("[Net] Handshake rejected: " + text2); NetConnectFeedback.ReportError(CoopLoc.Format("connect.error.hostRejected", "Host rejected: {reason}", ("reason", text2))); peer.Disconnect(); break; } case NetMessageType.Ping: HandlePing(peer); break; case NetMessageType.Pong: TouchPeer(peer); if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[Net] Pong received"); } break; case NetMessageType.RunStateUpdate: HandleRunStateUpdate(peer, reader); break; case NetMessageType.HostSceneRequest: HandleHostSceneRequest(peer, reader); break; case NetMessageType.ClientSceneAck: HandleClientSceneResponse(peer, reader, accepted: true); break; case NetMessageType.ClientSceneRefused: HandleClientSceneResponse(peer, reader, accepted: false); break; case NetMessageType.ClientHostGenerationInputRequest: HandleClientHostGenerationInputRequest(peer, reader); break; case NetMessageType.ClientTransitionRequest: HandleClientTransitionRequest(peer, reader); break; case NetMessageType.PlayerTransformVisual: HandlePlayerTransformVisual(peer, reader); break; case NetMessageType.HostEnemyDeathEvent: HandleHostEnemyDeathEvent(peer, reader); break; case NetMessageType.HostEnemyStateSnapshot: HandleHostEnemyStateSnapshot(peer, reader); break; case NetMessageType.ClientEnemyDeathClaim: HandleClientEnemyDeathClaim(peer, reader); break; case NetMessageType.PlayerLifeState: HandlePlayerLifeState(peer, reader); break; case NetMessageType.HostWorldRoster: HandleHostWorldRoster(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostAttackPhaseEvent: HandleHostAttackPhaseEvent(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostProjectileVisualSpawn: HandleHostProjectileVisualSpawn(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostEnemyDamageEvent: HandleHostEnemyDamageEvent(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostEnemyHealthState: HandleHostEnemyHealthState(peer, (NetDataReader)(object)reader); break; case NetMessageType.ClientHitRequest: HandleClientHitRequest(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostLevelManifest: HandleHostLevelManifest(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostHitVisualEvent: HandleHostHitVisualEvent(peer, (NetDataReader)(object)reader); break; case NetMessageType.ClientBossStartRequest: HandleClientBossStartRequest(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostBossEncounterStart: HandleHostBossEncounterStart(peer, (NetDataReader)(object)reader); break; case NetMessageType.ClientBossDialogCommitRequest: HandleClientBossDialogCommitRequest(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostBossDialogCommit: HandleHostBossDialogCommit(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostBossState: HandleHostBossState(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostEmperorWormHead: HandleEmperorWormHead(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostEmperorWormSectionDestroy: HandleEmperorWormSectionDestroy(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostEmperorWormDeath: HandleEmperorWormDeath(peer, (NetDataReader)(object)reader); break; case NetMessageType.ClientEmperorWormHit: HandleClientEmperorWormHit(peer, (NetDataReader)(object)reader); break; case NetMessageType.ClientEmperorFightStart: HandleClientEmperorFightStart(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostEmperorFightStart: HandleEmperorFightStart(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostEmperorSpiderTransform: HandleEmperorSpiderTransform(peer, (NetDataReader)(object)reader); break; case NetMessageType.ClientEmperorSpiderFightStart: HandleClientEmperorSpiderFightStart(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostEmperorSpiderFightStart: HandleEmperorSpiderFightStart(peer, (NetDataReader)(object)reader); break; case NetMessageType.ClientEmperorSpiderHit: HandleClientEmperorSpiderHit(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostEmperorSpiderEvent: HandleEmperorSpiderEvent(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostBossDynamicSpawn: HandleHostBossDynamicSpawn(peer, (NetDataReader)(object)reader); break; case NetMessageType.ClientBossHitRequest: HandleClientBossHitRequest(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostBossHitVisual: HandleHostBossHitVisual(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostBossDiscreteEvent: HandleHostBossDiscreteEvent(peer, (NetDataReader)(object)reader); break; case NetMessageType.ClientLuciaEyeReport: HandleClientLuciaEyeReport(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostLuciaEyeState: HandleHostLuciaEyeState(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostLuciaDeath: HandleHostLuciaDeath(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostWitchPhase: HandleHostWitchPhase(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostWitchP2Manifest: HandleHostWitchP2Manifest(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostWitchP2Result: HandleHostWitchP2Result(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostRuntimeSpawn: HandleHostRuntimeSpawn(peer, (NetDataReader)(object)reader); break; case NetMessageType.PlayerWeaponFire: HandlePlayerWeaponFire(peer, (NetDataReader)(object)reader); break; case NetMessageType.PlayerHeldWeapon: HandlePlayerHeldWeapon(peer, (NetDataReader)(object)reader); break; case NetMessageType.GateState: HandleGateState(peer, (NetDataReader)(object)reader); break; case NetMessageType.TriggerDoors: HandleTriggerDoors(peer, (NetDataReader)(object)reader); break; case NetMessageType.ClientArenaEnter: HandleClientArenaEnter(peer, (NetDataReader)(object)reader); break; case NetMessageType.ArenaCommand: HandleArenaCommand(peer, (NetDataReader)(object)reader); break; case NetMessageType.BreakableBreak: HandleBreakableBreak(peer, (NetDataReader)(object)reader); break; case NetMessageType.WorldPickupSpawn: HandleWorldPickupSpawn(peer, (NetDataReader)(object)reader); break; case NetMessageType.WorldPickupTakeRequest: HandleWorldPickupTakeRequest(peer, (NetDataReader)(object)reader); break; case NetMessageType.WorldPickupRemoved: HandleWorldPickupRemoved(peer, (NetDataReader)(object)reader); break; case NetMessageType.ClientRoomEnter: HandleClientRoomEnter(peer, (NetDataReader)(object)reader); break; case NetMessageType.HostRoomMembership: HandleHostRoomMembership(peer, (NetDataReader)(object)reader); break; case NetMessageType.RunStatsFinalized: HandleRunStatsFinalized(peer, reader); break; case NetMessageType.PlayerFriendlyFireHit: HandleFriendlyFireHit(peer, reader); break; case NetMessageType.SessionSettings: HandleSessionSettings(peer, reader); break; case NetMessageType.Disconnect: { string text = ((NetDataReader)reader).GetString(); NetLogger.Info("[Net] Disconnect: " + text); break; } case NetMessageType.SessionSnapshot: case NetMessageType.PeerJoined: case NetMessageType.PeerLeft: NetLogger.Warn($"[Net] Session message type {(byte)netMessageType} is reserved but not implemented yet"); break; default: NetLogger.Warn($"[Net] Unknown message type: {(byte)netMessageType}"); break; } } catch (Exception ex) { NetLogger.Error("[Net] Receive error: " + ex.Message); } } private void HandleHandshakeRequest(NetPeer peer, NetPacketReader reader) { if (_mode != NetMode.Host) { return; } if (!NetHandshake.TryReadRequest((NetDataReader)(object)reader, out HandshakeData data)) { RejectPeer(peer, "Malformed handshake packet"); return; } if (data.Magic != "SULFUR_TOGETHER") { RejectPeer(peer, "Wrong protocol magic: '" + data.Magic + "'"); return; } if (data.ProtocolVersion != 2) { RejectPeer(peer, $"Protocol version mismatch: client={data.ProtocolVersion} host={2}"); return; } if (data.ConnectionKey != Plugin.Cfg.ConnectionKey.Value) { RejectPeer(peer, "Wrong connection key"); return; } if (Plugin.Cfg.RequireSameModVersion.Value && data.ModVersion != "1.0.0") { RejectPeer(peer, "Mod version mismatch: client=" + data.ModVersion + " host=1.0.0"); return; } float now = Now(); NetPeerSession netPeerSession = _sessions.RegisterRemoteClient(data.PlayerName, data.ModVersion, ((IPEndPoint)(object)peer).Address.ToString(), Plugin.Cfg.MaxPlayers.Value, now); _peerIds[peer] = netPeerSession.PeerId; NetLogger.Info("[Net] Handshake OK — player='" + netPeerSession.PlayerName + "' v=" + data.ModVersion); NetLogger.Info($"[Session] Peer joined: id={netPeerSession.PeerId} slot={netPeerSession.Slot} name='{netPeerSession.PlayerName}' endpoint={netPeerSession.EndPoint}"); CoopToasts.Notify(CoopLoc.Format("toast.playerJoined", "{name} joined", ("name", netPeerSession.PlayerName))); NetDataWriter val = NetMessage.For(NetMessageType.HandshakeAccepted); NetHandshake.WriteAccepted(val, netPeerSession.PeerId, netPeerSession.Slot, "host", Plugin.Cfg.PlayerName.Value, Plugin.Cfg.MaxPlayers.Value); peer.Send(val, (DeliveryMethod)2); SendRunState(peer, _runStates.LocalState); SendSessionSettingsToPeer(peer); if (Plugin.Cfg.EnableHostSceneRequestProtocol.Value) { SendCurrentHostSceneRequestToPeer(peer, "handshake"); } } private void HandleHandshakeAccepted(NetPeer peer, NetPacketReader reader) { if (_mode == NetMode.Client) { float now = Now(); if (NetHandshake.TryReadAccepted((NetDataReader)(object)reader, out HandshakeAcceptedData data)) { NetPeerSession netPeerSession = _sessions.RegisterLocalClient(data.AssignedPeerId, data.AssignedSlot, Plugin.Cfg.PlayerName.Value, "1.0.0", now); _sessions.RegisterRemoteHost(data.HostPeerId, data.HostPlayerName, data.HostModVersion, ((IPEndPoint)(object)peer).Address.ToString(), now); _peerIds[peer] = data.HostPeerId; _runStates.SetLocalIdentity(netPeerSession.PeerId, netPeerSession.PlayerName); NetLogger.Info("[Net] Handshake accepted by host — session established"); NetLogger.Info($"[Session] Local session assigned: id={netPeerSession.PeerId} slot={netPeerSession.Slot} name='{netPeerSession.PlayerName}'"); NetLogger.Info($"[Session] Host session known: id={data.HostPeerId} name='{data.HostPlayerName}' maxPlayers={data.MaxPlayers}"); CoopToasts.Notify(CoopLoc.Format("toast.connectedTo", "Connected to {host}", ("host", data.HostPlayerName))); NetConnectFeedback.ReportConnected(); SendRunState(peer, _runStates.LocalState); } else { _sessions.RegisterLocalClient("client-local", -1, Plugin.Cfg.PlayerName.Value, "1.0.0", now); _sessions.RegisterRemoteHost("host", "Host", "", ((IPEndPoint)(object)peer).Address.ToString(), now); _peerIds[peer] = "host"; _runStates.SetLocalIdentity("client-local", Plugin.Cfg.PlayerName.Value); NetLogger.Info("[Net] Handshake accepted by host — session established (legacy payload)"); NetConnectFeedback.ReportConnected(); SendRunState(peer, _runStates.LocalState); } } } private void HandleRunStateUpdate(NetPeer peer, NetPacketReader reader) { if (!Plugin.Cfg.EnableRunStateNegotiation.Value) { return; } if (!NetRunStateCodec.TryRead((NetDataReader)(object)reader, out NetRunState state)) { NetLogger.Warn("[RunState] Malformed RunStateUpdate packet"); return; } string value; if (_mode == NetMode.Host) { if (!_peerIds.TryGetValue(peer, out value)) { NetLogger.Warn($"[RunState] Ignoring state from unregistered peer {((IPEndPoint)(object)peer).Address}"); return; } } else { value = (string.IsNullOrWhiteSpace(state.PeerId) ? "host" : state.PeerId); } _runStates.UpdateRemote(value, state, Now()); if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[RunState] Remote update: " + state.ToCompactString()); } ReportRunStateDiagnostics(value); TrySendHostSceneRequest(value); } private void ReportRunStateDiagnostics(string peerId) { if (Plugin.Cfg.EnableHostSceneAuthority.Value && Plugin.Cfg.WarnOnClientSceneDrift.Value) { if (_mode == NetMode.Client && peerId == "host") { if (_runStates.TryBuildHostAuthorityWarning(out string warning)) { NetLogger.Warn(warning); } return; } if (_mode == NetMode.Host) { if (_runStates.TryBuildClientSceneDriftWarning(peerId, out string warning2)) { NetLogger.Warn(warning2); } return; } } string notice; if (Plugin.Cfg.WarnOnRunStateMismatch.Value && _runStates.TryBuildMismatchWarning(peerId, out string warning3)) { NetLogger.Warn(warning3); } else if (Plugin.Cfg.EnableDebugLog.Value && _runStates.TryBuildStateDifferenceNotice(peerId, out notice)) { NetLogger.Debug(notice); } } private void HandleManualClientSceneFollowInput() { //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) if (_net == null || !Plugin.Cfg.EnableHostSceneRequestProtocol.Value) { return; } if (_mode == NetMode.Host) { try { KeyboardShortcut value = Plugin.Cfg.HostLinkToggleKey.Value; if (!IsUnsafeManualFollowKey(((KeyboardShortcut)(ref value)).MainKey) && ((KeyboardShortcut)(ref value)).IsDown()) { NetLinkState.ToggleHost("host key"); } return; } catch (Exception ex) { NetLogger.Warn("[LinkState] host toggle key check failed: " + ex.Message); return; } } if (_mode != NetMode.Client || _hostPeer == null || !Plugin.Cfg.EnableManualClientSceneFollow.Value) { return; } try { KeyboardShortcut value2 = Plugin.Cfg.ClientUnlinkKey.Value; if (!IsUnsafeManualFollowKey(((KeyboardShortcut)(ref value2)).MainKey) && ((KeyboardShortcut)(ref value2)).IsDown()) { NetLinkState.SetClientLinked(on: false, "user-unlink-key"); return; } } catch (Exception ex2) { NetLogger.Warn("[LinkState] unlink key check failed: " + ex2.Message); } try { KeyboardShortcut value3 = Plugin.Cfg.ManualClientSceneFollowKey.Value; if (IsUnsafeManualFollowKey(((KeyboardShortcut)(ref value3)).MainKey)) { if (!_manualFollowKeyWarningShown) { _manualFollowKeyWarningShown = true; NetLogger.Warn($"[SceneFollow] Manual follow key '{value3}' is reserved by SULFUR DevTools/F-key bindings and is ignored to avoid toggling invulnerability. Change NetworkSceneAuthority.ManualClientSceneFollowKey to PageDown or another non-F-key."); } return; } if (!((KeyboardShortcut)(ref value3)).IsDown()) { return; } } catch (Exception ex3) { NetLogger.Warn("[SceneFollow] Manual follow key check failed: " + ex3.Message); return; } NetLinkState.SetClientLinked(on: true, "user-link-key"); AttemptManualClientSceneFollow(); } private static bool IsUnsafeManualFollowKey(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 if ((int)key >= 282) { return (int)key <= 296; } return false; } private void AttemptManualClientSceneFollow() { if (_mode != NetMode.Client || _hostPeer == null) { return; } if (!_sceneRequests.HasLastHostRequestTarget(out NetHostSceneRequest request)) { if (Plugin.Cfg.ManualClientSceneFollowRequiresHostRequest.Value && !NetLinkState.ClientLinked) { NetLogger.Info("[SceneFollow] Manual follow ignored: no HostSceneRequest has been received yet."); return; } if (!_runStates.TryGetRemote("host", out NetRunState state) || !state.HasLevel) { NetLogger.Info("[SceneFollow] Manual follow ignored: no host run state target is known."); return; } NetLogger.Info(string.Format("[SceneFollow] Manual follow using host run state (no HostSceneRequest; linked={0}) target={1}:{2} seed={3}", NetLinkState.ClientLinked, state.ChapterName, state.LevelIndex, state.HasLevelSeed ? state.LevelSeed.ToString() : "?")); request = new NetHostSceneRequest { RequestId = "manual-host-state-" + (int)(Now() * 1000f), HostPeerId = (string.IsNullOrWhiteSpace(state.PeerId) ? "host" : state.PeerId), HostPlayerName = state.PlayerName, ChapterName = state.ChapterName, LevelIndex = state.LevelIndex, LoadingMode = state.LoadingMode, SpawnIdentifier = state.SpawnIdentifier, HostGameState = state.GameState, HasLevelSeed = state.HasLevelSeed, LevelSeed = state.LevelSeed, LevelGenerator = state.LevelGenerator, HostRevision = state.Revision, Reason = "ManualFollowHostRunState", AutoLoadAllowed = false }; } if (request.HasTargetScene && _runStates.TryGetRemote("host", out NetRunState state2) && state2.HasLevel && !NetSceneName.SameScene(state2.ChapterName, state2.LevelIndex, request.ChapterName, request.LevelIndex)) { NetLogger.Info($"[SceneFollow] Manual follow refused: cached host request target={request.TargetSceneKey()} is stale (host now at {state2.ChapterName}:{state2.LevelIndex}); auto-follow will reconcile."); return; } if (_runStates.LocalState.HasLevel && request.HasTargetScene && NetSceneName.SameScene(_runStates.LocalState.ChapterName, _runStates.LocalState.LevelIndex, request.ChapterName, request.LevelIndex) && (!Plugin.Cfg.RequireSameLevelSeedForSceneMatch.Value || (request.HasLevelSeed && _runStates.LocalState.HasLevelSeed && request.LevelSeed == _runStates.LocalState.LevelSeed))) { NetClientSceneResponse response = _sceneRequests.BuildClientResponse(request, _runStates.LocalState, _runStates.LocalState.PeerId, Plugin.Cfg.PlayerName.Value, "Manual follow skipped; client is already in requested host scene.", "Arrived"); SendClientSceneResponse(_hostPeer, response, ack: true); return; } _sceneRequests.RecordManualFollowAttempt(request); NetLogger.Info("[SceneFollow] Manual follow requested by user: " + request.ToCompactString()); if (NetManualSceneFollower.TryFollow(request, out string result)) { NetLogger.Info("[SceneFollow] " + result); NetLogger.Info("[JoinFlow] manual follow confirmed target=" + request.TargetSceneKey() + " graph=" + (string.IsNullOrEmpty(request.GraphName) ? "?" : request.GraphName) + " preserving-local-run=false"); NetClientSceneResponse netClientSceneResponse = _sceneRequests.BuildClientResponse(request, _runStates.LocalState, _runStates.LocalState.PeerId, Plugin.Cfg.PlayerName.Value, "Manual scene follow invoked; waiting for local GoToLevel/RunState update.", "FollowInvoked"); SendClientSceneResponse(_hostPeer, netClientSceneResponse, netClientSceneResponse.IsInTargetScene); } else { NetLogger.Warn("[SceneFollow] Manual follow failed: " + result); NetClientSceneResponse response2 = _sceneRequests.BuildClientResponse(request, _runStates.LocalState, _runStates.LocalState.PeerId, Plugin.Cfg.PlayerName.Value, "Manual scene follow failed: " + result, "Refused"); SendClientSceneResponse(_hostPeer, response2, ack: false); } } private void SendHostSceneRequestsForDriftedClients() { if (_mode != NetMode.Host || _net == null || !Plugin.Cfg.EnableHostSceneAuthority.Value || !Plugin.Cfg.EnableHostSceneRequestProtocol.Value || !Plugin.Cfg.AutoSendHostSceneRequestOnDrift.Value || !_runStates.LocalState.HasLevel) { return; } foreach (KeyValuePair peerId in _peerIds) { TrySendHostSceneRequest(peerId.Value); } } private void TrySendHostSceneRequest(string peerId) { if (_mode == NetMode.Host && _net != null && Plugin.Cfg.EnableHostSceneAuthority.Value && Plugin.Cfg.EnableHostSceneRequestProtocol.Value && Plugin.Cfg.AutoSendHostSceneRequestOnDrift.Value && !string.IsNullOrWhiteSpace(peerId) && _runStates.TryGetRemote(peerId, out NetRunState state)) { NetPeer val = FindPeerById(peerId); if (val != null && _sceneRequests.TryCreateHostSceneRequest(peerId, Plugin.Cfg.PlayerName.Value, _runStates.LocalState, state, Now(), Plugin.Cfg.HostSceneRequestIntervalSeconds.Value, out NetHostSceneRequest request)) { SendHostSceneRequest(val, request); } } } private void SendHostSceneRequest(NetPeer peer, NetHostSceneRequest request) { if (_mode == NetMode.Host && !NetLinkState.HostLinked) { return; } try { NetGenerationInputSnapshot lastFinalizedSnapshot = NetGenerationInputCapture.LastFinalizedSnapshot; bool flag = request.HasTargetScene && (NetSceneClassify.IsHubOrSafeZoneChapter(request.ChapterName) || NetSceneClassify.IsHubOrSafeZoneGraph(request.GraphName) || NetClientLoadGate.IsHubOrMenuTarget(request.ChapterName, request.LoadingMode, request.LevelIndex)); bool flag2 = request.HasTargetScene && !flag && NetClientLoadGate.IsCombatTarget(request.ChapterName, request.LoadingMode, request.LevelIndex); bool flag3 = lastFinalizedSnapshot != null && lastFinalizedSnapshot.Finalized && NetSceneClassify.IsHubOrSafeZoneGraph(lastFinalizedSnapshot.GraphName); bool flag4 = false; if (flag2 && NetPlayerLifeManager.IsLocalDeathRespawnInProgress) { NetLogger.Info("[HostSceneRequest] suppress combat advertisement during death-respawn target=" + request.TargetSceneKey() + " graph=" + (string.IsNullOrEmpty(request.GraphName) ? "?" : request.GraphName)); return; } if (flag2 && lastFinalizedSnapshot != null && lastFinalizedSnapshot.Finalized && !flag3) { if (!string.IsNullOrWhiteSpace(lastFinalizedSnapshot.Chapter) && lastFinalizedSnapshot.Chapter != "") { request.ChapterName = lastFinalizedSnapshot.Chapter; } request.LevelIndex = lastFinalizedSnapshot.LevelIndex; request.HasLevelSeed = lastFinalizedSnapshot.HasSeed; request.LevelSeed = lastFinalizedSnapshot.Seed; request.GraphName = lastFinalizedSnapshot.GraphName; request.GenerationRunId = lastFinalizedSnapshot.RunId; if (!string.IsNullOrWhiteSpace(lastFinalizedSnapshot.LoadingMode)) { request.LoadingMode = lastFinalizedSnapshot.LoadingMode; } if (!string.IsNullOrWhiteSpace(lastFinalizedSnapshot.SpawnIdentifier)) { request.SpawnIdentifier = lastFinalizedSnapshot.SpawnIdentifier; } request.SetUsedSets(lastFinalizedSnapshot.UsedSets); NetGenerationInputCapture.MatchedOnSend++; NetSceneFollowDiag.IncCaptured(); NetSceneFollowDiag.IncSent(); NetLogger.Info(string.Format("[HostSceneRequestBuild] source={0} target={1} graph={2} seed={3} usedChunks={4}", lastFinalizedSnapshot.FromTransition ? "finalizedTransition" : "finalizedSnapshot", request.TargetSceneKey(), string.IsNullOrEmpty(request.GraphName) ? "?" : request.GraphName, request.HasLevelSeed ? request.LevelSeed.ToString() : "?", request.UsedChunksThisRun.Count)); } else if (flag2) { NetHostUsedSets lastLocalSnapshot = NetGameManagerUsedSets.LastLocalSnapshot; if (lastLocalSnapshot != null) { request.SetUsedSets(lastLocalSnapshot); NetSceneFollowDiag.IncSent(); } else { request.HasUsedSets = false; } NetGenerationInputCapture.FallbackOnSend++; NetLogger.Warn("[HostSceneRequestBuild] source=LocalStateFallback WARNING may be stale target=" + request.TargetSceneKey() + " graph=" + (string.IsNullOrEmpty(request.GraphName) ? "?" : request.GraphName) + " seed=" + (request.HasLevelSeed ? request.LevelSeed.ToString() : "?")); } else if (flag && flag3 && lastFinalizedSnapshot.HasSeed) { if (!string.IsNullOrWhiteSpace(lastFinalizedSnapshot.Chapter) && lastFinalizedSnapshot.Chapter != "") { request.ChapterName = lastFinalizedSnapshot.Chapter; } request.LevelIndex = lastFinalizedSnapshot.LevelIndex; request.HasLevelSeed = lastFinalizedSnapshot.HasSeed; request.LevelSeed = lastFinalizedSnapshot.Seed; request.GraphName = lastFinalizedSnapshot.GraphName; request.GenerationRunId = lastFinalizedSnapshot.RunId; request.HasUsedSets = false; NetSceneFollowDiag.HubReturnFinalizedRequestSent++; NetLogger.Info("[HostSceneRequestBuild] source=" + (lastFinalizedSnapshot.FromTransition ? "finalizedTransition" : "finalizedSnapshot") + " target=" + request.TargetSceneKey() + " graph=" + (string.IsNullOrEmpty(request.GraphName) ? "?" : request.GraphName) + " seed=" + (request.HasLevelSeed ? request.LevelSeed.ToString() : "?") + " autoLoad=True reason=session-return-finalized"); } else if (flag) { flag4 = true; request.HasUsedSets = false; NetSceneFollowDiag.HubReturnDeferredMissingSeed++; NetSceneFollowDiag.HubReturnPreliminaryRequestSent++; NetLogger.Info("[HostSceneRequestBuild] defer hub return until finalized target=" + request.TargetSceneKey() + " graph=" + (string.IsNullOrEmpty(request.GraphName) ? "?" : request.GraphName) + " seed=" + (request.HasLevelSeed ? request.LevelSeed.ToString() : "?") + " reason=missing-finalized-hub-seed"); } else { request.HasUsedSets = false; NetLogger.Info("[HostSceneRequestBuild] source=localState target=" + request.TargetSceneKey() + " graph=" + (string.IsNullOrEmpty(request.GraphName) ? "?" : request.GraphName) + " seed=" + (request.HasLevelSeed ? request.LevelSeed.ToString() : "?") + " reason=non-combat"); } if (flag) { NetLogger.Info(string.Format("[HubReturnDiag] localStateTarget={0} finalizedGraph={1} finalizedChapter={2} snapIsHub={3} snapSeed={4} reqGraph={5} reqSeed={6} action={7}", request.TargetSceneKey(), lastFinalizedSnapshot?.GraphName ?? "?", lastFinalizedSnapshot?.Chapter ?? "?", flag3, (lastFinalizedSnapshot != null && lastFinalizedSnapshot.HasSeed) ? lastFinalizedSnapshot.Seed.ToString() : "?", string.IsNullOrEmpty(request.GraphName) ? "?" : request.GraphName, request.HasLevelSeed ? request.LevelSeed.ToString() : "?", flag4 ? "defer" : "finalized")); } bool flag5 = false; try { flag5 = Plugin.Cfg.EnableAutoFollowHostSceneRequest.Value; } catch { } request.AutoLoadAllowed = flag5 && (flag2 || (flag && !flag4)); string text = (string.IsNullOrEmpty(request.GraphName) ? "?" : request.GraphName); string text2 = (request.HasLevelSeed ? request.LevelSeed.ToString() : "noseed"); request.RequestId = "hsr-" + PeerIdOrAddress(peer) + "-" + request.TargetSceneKey().Replace(':', '-') + "-" + text + "-seed" + text2; if (request.AutoLoadAllowed) { NetLoadBarrier.MarkPending(PeerIdOrAddress(peer), NetLoadBarrier.RunKeyFor(request.ChapterName, request.LevelIndex, request.HasLevelSeed, request.LevelSeed, request.GraphName), request.GraphName, flag ? "session-return-finalized" : "combat"); } NetDataWriter val = NetMessage.For(NetMessageType.HostSceneRequest); NetSceneRequestCodec.WriteHostRequest(val, request); peer.Send(val, (DeliveryMethod)2); NetLogger.Info("[SceneRequest] HostSceneRequest sent to " + PeerIdOrAddress(peer) + ": " + request.ToCompactString() + " graph=" + (string.IsNullOrEmpty(request.GraphName) ? "?" : request.GraphName) + " usedSets=" + (request.HasUsedSets ? $"chunks={request.UsedChunksThisRun.Count},eventsRun={request.UsedEventsThisRun.Count},eventsEnv={request.UsedEventsThisEnvironment.Count}" : "none") + " action=" + (request.AutoLoadAllowed ? "auto-load" : "request-only")); } catch (Exception ex) { NetLogger.Warn("[SceneRequest] Failed to send HostSceneRequest: " + ex.Message); } } private void HandleClientLoadGateRequestTimer() { if (_mode == NetMode.Client && _net != null && _hostPeer != null && Plugin.Cfg.EnableHostSceneRequestProtocol.Value) { if (NetClientLoadGate.TryConsumeHostInputRequestDue(out var attempt)) { SendClientHostGenerationInputRequest(attempt); } if (NetClientLoadGate.TryConsumeTransitionRelayDue(out string chapter, out int level, out string mode, out string spawn, out int attempt2)) { SendClientTransitionRequest(chapter, level, mode, spawn, attempt2); } } } private void SendClientTransitionRequest(string chapter, int level, string mode, string spawn, int attempt) { if (_hostPeer == null) { return; } try { NetRunStateBridge.TryGetLocalRunState(out NetRunState state); NetDataWriter val = NetMessage.For(NetMessageType.ClientTransitionRequest); val.Put(chapter ?? ""); val.Put(level); val.Put(mode ?? ""); val.Put(spawn ?? ""); val.Put(state.ChapterName ?? ""); val.Put(state.LevelIndex); val.Put(attempt); _hostPeer.Send(val, (DeliveryMethod)2); if (attempt <= 1) { NetLogger.Info($"[TransitionRelay] client requesting host lead transition target={chapter}:{level} mode={mode} spawn={spawn} from={state.ChapterName}:{state.LevelIndex}"); } } catch (Exception ex) { NetLogger.Warn("[TransitionRelay] failed to send client transition request: " + ex.Message); } } private void SendClientHostGenerationInputRequest(int attempt) { if (_hostPeer == null) { return; } try { NetDataWriter val = NetMessage.For(NetMessageType.ClientHostGenerationInputRequest); val.Put("gate-wait"); val.Put(attempt); _hostPeer.Send(val, (DeliveryMethod)2); } catch (Exception ex) { NetLogger.Warn("[ClientLoadGate] failed to send host generation input request: " + ex.Message); } } private void HandleClientHostGenerationInputRequest(NetPeer peer, NetPacketReader reader) { if (_mode != NetMode.Host || !Plugin.Cfg.EnableHostSceneRequestProtocol.Value) { return; } string arg = "request"; int num = 0; try { if (((NetDataReader)reader).AvailableBytes > 0) { arg = ((NetDataReader)reader).GetString(); } } catch { } try { if (((NetDataReader)reader).AvailableBytes >= 4) { num = ((NetDataReader)reader).GetInt(); } } catch { } string value; string arg2 = (_peerIds.TryGetValue(peer, out value) ? value : ((IPEndPoint)(object)peer).Address.ToString()); NetLogger.Info($"[HostGenerationInput] pull request from peer={arg2} reason={arg} attempt={num}"); SendCurrentHostSceneRequestToPeer(peer, "request"); } private void HandleClientTransitionRequest(NetPeer peer, NetPacketReader reader) { string text = ""; string text2 = ""; string text3 = ""; string text4 = ""; int num = -1; int num2 = -1; int num3 = 0; try { text = ((NetDataReader)reader).GetString(); num = ((NetDataReader)reader).GetInt(); text2 = ((NetDataReader)reader).GetString(); text3 = ((NetDataReader)reader).GetString(); text4 = ((NetDataReader)reader).GetString(); num2 = ((NetDataReader)reader).GetInt(); if (((NetDataReader)reader).AvailableBytes >= 4) { num3 = ((NetDataReader)reader).GetInt(); } } catch (Exception ex) { NetLogger.Warn("[TransitionRelay] malformed client transition request: " + ex.Message); return; } if (_mode != NetMode.Host || !Plugin.Cfg.EnableClientTransitionRelay.Value || !Plugin.Cfg.EnableHostSceneRequestProtocol.Value) { return; } string value; string text5 = (_peerIds.TryGetValue(peer, out value) ? value : ((IPEndPoint)(object)peer).Address.ToString()); if (!NetLinkState.HostLinked) { NetLogger.Info("[TransitionRelay] ignore from " + text5 + ": host multiplayer (联机状态) is off"); return; } if (string.IsNullOrWhiteSpace(text) || num < 0) { NetLogger.Warn($"[TransitionRelay] reject from {text5}: bad target {text}:{num}"); return; } NetRunState localState = _runStates.LocalState; if (!localState.HasLevel) { NetLogger.Info("[TransitionRelay] reject from " + text5 + ": host has no level yet"); return; } if (!NetSceneName.SameScene(localState.ChapterName, localState.LevelIndex, text4, num2)) { NetLogger.Info($"[TransitionRelay] cross-scene lead from {text5}: client={text4}:{num2} host={localState.ChapterName}:{localState.LevelIndex} target={text}:{num}"); } bool flag = NetSceneName.SameScene(localState.ChapterName, localState.LevelIndex, text, num); bool flag2; try { flag2 = Plugin.Cfg.EnableClientReloadInPlaceRelay.Value; } catch { flag2 = false; } if (flag && !flag2) { NetLogger.Info($"[TransitionRelay] ignore from {text5}: host already at target {text}:{num} (reload-in-place off)"); return; } if (flag) { NetLogger.Info($"[TransitionRelay] reload-in-place lead from {text5}: host re-generating current level {text}:{num}"); } if (!string.IsNullOrEmpty(localState.GameState) && localState.GameState.ToLowerInvariant().Contains("load")) { NetLogger.Info("[TransitionRelay] defer from " + text5 + ": host is loading (state=" + localState.GameState + ")"); return; } if (NetHostTransitionGuard.IsActive) { NetLogger.Info($"[TransitionRelay] defer from {text5}: host mid-transition (guard active) target={text}:{num}"); return; } string text6 = text + ":" + num; float num4 = Now(); if (!(text6 == _lastClientTransitionKey) || !(num4 - _lastClientTransitionTime < 5f)) { _lastClientTransitionKey = text6; _lastClientTransitionTime = num4; NetLogger.Info($"[TransitionRelay] host LEADING client-requested transition target={text}:{num} mode={text2} spawn={text3} requestedBy={text5} attempt={num3}"); NetHostSceneRequest request = new NetHostSceneRequest { ChapterName = text, LevelIndex = num, LoadingMode = (text2 ?? ""), SpawnIdentifier = (text3 ?? ""), HasLevelSeed = false, AutoLoadAllowed = false }; bool flag3; string result; try { flag3 = NetManualSceneFollower.TryFollow(request, out result); } catch (Exception ex2) { flag3 = false; result = ex2.GetType().Name + ": " + ex2.Message; } if (flag3) { NetLogger.Info("[TransitionRelay] host transition invoked: " + result); } else { NetLogger.Warn("[TransitionRelay] host transition failed: " + result); } } } private void SendCurrentHostSceneRequestToPeer(NetPeer peer, string reason) { if (_mode != NetMode.Host) { return; } bool flag = reason == "handshake"; string value; string text = (_peerIds.TryGetValue(peer, out value) ? value : ((IPEndPoint)(object)peer).Address.ToString()); NetRunState localState = _runStates.LocalState; bool flag2 = NetGenerationInputCapture.LastFinalizedSnapshot != null; if (!localState.HasLevel && !flag2) { if (flag) { NetSceneFollowDiag.HostGenerationInputNoSnapshotOnHandshake++; } else { NetSceneFollowDiag.HostGenerationInputNoSnapshotOnRequest++; } NetLogger.Warn("[HostGenerationInput] no latest snapshot to send reason=" + reason + " peer=" + text + " (host not in a level yet)"); return; } NetHostSceneRequest netHostSceneRequest = new NetHostSceneRequest(); netHostSceneRequest.RequestId = $"hgi-{text}-{localState.LevelInstanceKey().Replace(':', '-').Replace('#', '-').Replace('=', '-')}-r{localState.Revision}-{(int)(Now() * 1000f)}"; netHostSceneRequest.HostPeerId = (string.IsNullOrWhiteSpace(localState.PeerId) ? "host" : localState.PeerId); netHostSceneRequest.HostPlayerName = Plugin.Cfg.PlayerName.Value; netHostSceneRequest.ChapterName = localState.ChapterName; netHostSceneRequest.LevelIndex = localState.LevelIndex; netHostSceneRequest.LoadingMode = localState.LoadingMode; netHostSceneRequest.SpawnIdentifier = localState.SpawnIdentifier; netHostSceneRequest.HostGameState = localState.GameState; netHostSceneRequest.HasLevelSeed = localState.HasLevelSeed; netHostSceneRequest.LevelSeed = localState.LevelSeed; netHostSceneRequest.LevelGenerator = localState.LevelGenerator; netHostSceneRequest.GraphName = localState.LevelGenerator; netHostSceneRequest.HostRevision = localState.Revision; netHostSceneRequest.Reason = (flag ? "HostGenerationInputHandshake" : "HostGenerationInputRequest"); netHostSceneRequest.AutoLoadAllowed = false; NetHostSceneRequest netHostSceneRequest2 = netHostSceneRequest; SendHostSceneRequest(peer, netHostSceneRequest2); if (flag) { NetSceneFollowDiag.HostGenerationInputSentOnHandshake++; } else { NetSceneFollowDiag.HostGenerationInputSentOnRequest++; } int num = (netHostSceneRequest2.HasUsedSets ? netHostSceneRequest2.UsedChunksThisRun.Count : 0); NetLogger.Info(string.Format("[HostGenerationInput] sending latest snapshot to peer={0} reason={1} graph={2} seed={3} usedChunks={4}", text, reason, string.IsNullOrEmpty(netHostSceneRequest2.GraphName) ? "?" : netHostSceneRequest2.GraphName, netHostSceneRequest2.HasLevelSeed ? netHostSceneRequest2.LevelSeed.ToString() : "?", num)); } private void HandleHostSceneRequest(NetPeer peer, NetPacketReader reader) { //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) if (!Plugin.Cfg.EnableHostSceneRequestProtocol.Value) { return; } if (_mode != NetMode.Client) { NetLogger.Warn("[SceneRequest] Ignoring HostSceneRequest because this instance is not a Client"); return; } if (!NetSceneRequestCodec.TryReadHostRequest((NetDataReader)(object)reader, out NetHostSceneRequest request)) { NetLogger.Warn("[SceneRequest] Malformed HostSceneRequest packet"); return; } _sceneRequests.RecordHostRequest(request); try { if (Plugin.Cfg.LogBossTransitionDiagnostics.Value) { bool isHostDrivenLoadInProgress = NetClientLoadGate.IsHostDrivenLoadInProgress; Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; string text = request.TargetSceneKey(); if (isHostDrivenLoadInProgress || !string.Equals(name, text, StringComparison.Ordinal)) { NetLogger.Info($"[TransitionDiag] HostSceneRequest while busy: current={name} target={text} loadInProgress={isHostDrivenLoadInProgress} autoLoadAllowed={request.AutoLoadAllowed} autoFollowStarted={NetClientLoadGate.AutoFollowStarted} autoFollowAlreadyMatching={NetClientLoadGate.AutoFollowSkippedAlreadyMatching} hostDrivenLoadsStarted={NetClientLoadGate.ClientLoadGateStartedHostDrivenLoad}"); } } } catch { } if (request.HasUsedSets) { NetSceneFollowDiag.IncReceived(); } NetLogger.Info(string.Format("[HostSceneRequest] received target={0} graph={1} seed={2} usedChunks={3}", request.TargetSceneKey(), string.IsNullOrEmpty(request.GraphName) ? "?" : request.GraphName, request.HasLevelSeed ? request.LevelSeed.ToString() : "?", request.HasUsedSets ? request.UsedChunksThisRun.Count : 0)); NetClientLoadGate.OnHostGenerationInput(request); NetLogger.Info("[SceneRequest] HostSceneRequest received: " + request.ToCompactString() + " usedSets=" + (request.HasUsedSets ? $"chunks={request.UsedChunksThisRun.Count},eventsRun={request.UsedEventsThisRun.Count},eventsEnv={request.UsedEventsThisEnvironment.Count}" : "none") + " action=" + (request.AutoLoadAllowed ? "auto-load" : "no-auto-load")); if (!request.AutoLoadAllowed) { NetClientJoinFlow.NoteHubJoinReady(); } NetClientSceneResponse netClientSceneResponse = _sceneRequests.BuildClientResponse(request, _runStates.LocalState, _runStates.LocalState.PeerId, Plugin.Cfg.PlayerName.Value); SendClientSceneResponse(peer, netClientSceneResponse, netClientSceneResponse.IsInTargetScene); } private void SendClientSceneResponse(NetPeer peer, NetClientSceneResponse response, bool ack) { if (!_sceneRequests.TryRecordSentClientResponse(response, out string reason)) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[SceneRequest] Client response skipped: " + reason); } return; } NetDataWriter val = NetMessage.For(ack ? NetMessageType.ClientSceneAck : NetMessageType.ClientSceneRefused); NetSceneRequestCodec.WriteClientResponse(val, response); peer.Send(val, (DeliveryMethod)2); if (ack) { NetLogger.Info("[SceneRequest] ClientSceneAck sent: " + response.ToCompactString()); if (response.IsInTargetScene) { NetSceneFollowDiag.ClientLoadedAckSent++; NetLogger.Info(string.Format("[LoadBarrier] client loaded (ack sent) graph={0} seed={1} level={2}", string.IsNullOrEmpty(response.LevelGenerator) ? "?" : response.LevelGenerator, response.HasLevelSeed ? response.LevelSeed.ToString() : "?", response.LevelIndex)); NetClientJoinFlow.MarkJoinedHost(response.SceneKey(), response.LevelGenerator, response.HasLevelSeed ? response.LevelSeed.ToString() : ""); } } else { NetLogger.Info("[SceneRequest] ClientSceneRefused sent: " + response.ToCompactString()); } } private void TrySendClientSceneAckForLastHostRequest(string source) { if (_mode == NetMode.Client && _hostPeer != null && Plugin.Cfg.EnableHostSceneRequestProtocol.Value && _sceneRequests.HasLastHostRequestTarget(out NetHostSceneRequest request)) { NetClientSceneResponse netClientSceneResponse = _sceneRequests.BuildClientResponse(request, _runStates.LocalState, _runStates.LocalState.PeerId, Plugin.Cfg.PlayerName.Value, "Client arrived in requested host scene after " + source + ".", "Arrived"); if (netClientSceneResponse.IsInTargetScene) { SendClientSceneResponse(_hostPeer, netClientSceneResponse, ack: true); } } } private void HandleClientSceneResponse(NetPeer peer, NetPacketReader reader, bool accepted) { if (!Plugin.Cfg.EnableHostSceneRequestProtocol.Value) { return; } if (_mode != NetMode.Host) { NetLogger.Warn("[SceneRequest] Ignoring ClientScene response because this instance is not a Host"); return; } if (!NetSceneRequestCodec.TryReadClientResponse((NetDataReader)(object)reader, out NetClientSceneResponse response)) { NetLogger.Warn("[SceneRequest] Malformed ClientScene response packet"); return; } string text = response.ClientPeerId; if (_peerIds.TryGetValue(peer, out string value)) { text = value; } _sceneRequests.RecordClientResponse(text, response); string text2 = (accepted ? "ClientSceneAck" : "ClientSceneRefused"); NetLogger.Info("[SceneRequest] " + text2 + " from " + text + ": " + response.ToCompactString()); if (accepted && response.IsInTargetScene) { NetSceneFollowDiag.HostClientLoadedAckReceived++; NetLoadBarrier.MarkLoaded(text, NetLoadBarrier.RunKeyFor(response.ChapterName, response.LevelIndex, response.HasLevelSeed, response.LevelSeed, response.LevelGenerator), response.LevelGenerator); } } private NetPeer? FindPeerById(string peerId) { foreach (KeyValuePair peerId2 in _peerIds) { if (peerId2.Value == peerId) { return peerId2.Key; } } return null; } private string PeerIdOrAddress(NetPeer peer) { if (_peerIds.TryGetValue(peer, out string value)) { return value; } return ((IPEndPoint)(object)peer).Address.ToString(); } private void HandleRemotePlayerVisualProxyTimer() { //IL_00f9: Unknown result type (might be due to invalid IL or missing references) if (_mode == NetMode.Off || _net == null || !Plugin.Cfg.EnableRemotePlayerVisualProxy.Value || !_runStates.LocalState.HasLevel) { return; } float num = Plugin.Cfg.RemotePlayerTransformSendRateHz.Value; if (num < 1f) { num = 1f; } if (num > 30f) { num = 30f; } float num2 = 1f / num; float num3 = Now(); if (num3 - _lastPlayerVisualSendTime < num2) { return; } _lastPlayerVisualSendTime = num3; if (_localPlayer.TryBuildState(_runStates.LocalState.PeerId, Plugin.Cfg.PlayerName.Value, _runStates.LocalState, ++_playerVisualSequence, num3, out NetPlayerTransformState state)) { if (_mode == NetMode.Host && Plugin.Cfg.EnableEnemyInterestManagement.Value) { NetGameplayProbeManager.SetLocalPlayerPositionHint(state.Position); } SendPlayerTransformToConnectedPeers(state); } } private void SendPlayerTransformToConnectedPeers(NetPlayerTransformState state) { if (_net == null || _mode == NetMode.Off) { return; } if (_mode == NetMode.Host) { NetPeer[] array = _clients.ToArray(); foreach (NetPeer peer in array) { SendPlayerTransform(peer, state); } } else if (_hostPeer != null) { SendPlayerTransform(_hostPeer, state); } } private void SendPlayerTransform(NetPeer peer, NetPlayerTransformState state) { try { NetDataWriter val = NetMessage.For(NetMessageType.PlayerTransformVisual); NetPlayerTransformCodec.Write(val, state); peer.Send(val, (DeliveryMethod)4); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[RemotePlayer] Failed to send visual transform: " + ex.Message); } } } private void HandlePlayerTransformVisual(NetPeer peer, NetPacketReader reader) { if (!Plugin.Cfg.EnableRemotePlayerVisualProxy.Value) { return; } if (!NetPlayerTransformCodec.TryRead((NetDataReader)(object)reader, out NetPlayerTransformState state)) { NetLogger.Warn("[RemotePlayer] Malformed PlayerTransformVisual packet"); } else if (_mode == NetMode.Host) { if (!_peerIds.TryGetValue(peer, out string value)) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug($"[RemotePlayer] Ignoring visual transform from unregistered peer {((IPEndPoint)(object)peer).Address}"); } return; } state.PeerId = value; if (_runStates.TryGetRemote(value, out NetRunState state2) && !string.IsNullOrWhiteSpace(state2.PlayerName)) { state.PlayerName = state2.PlayerName; } ApplyRemotePlayerVisual(state); RelayPlayerTransformToOtherClients(peer, state); } else if (_mode == NetMode.Client && !(state.PeerId == _runStates.LocalState.PeerId)) { ApplyRemotePlayerVisual(state); } } private void ApplyRemotePlayerVisual(NetPlayerTransformState state) { if (!string.IsNullOrWhiteSpace(state.PeerId) && !(state.PeerId == _runStates.LocalState.PeerId)) { NetPlayerLifeManager.ReportRemotePlayerTransform(state); _visualProxies.Apply(state, _runStates.LocalState, Now(), Plugin.Cfg.HideRemoteVisualWhenLevelSeedMismatch.Value); } } private void RelayPlayerTransformToOtherClients(NetPeer sourcePeer, NetPlayerTransformState state) { if (_mode != NetMode.Host) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { if (val != sourcePeer) { SendPlayerTransform(val, state); } } } private void SendHostEnemyDeathEvent(NetPeer peer, NetGameplayDeathEvent deathEvent) { try { NetDataWriter val = NetMessage.For(NetMessageType.HostEnemyDeathEvent); NetGameplayDeathEventCodec.Write(val, deathEvent); peer.Send(val, (DeliveryMethod)2); if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[EnemyDeathMirror] Sent to " + PeerIdOrAddress(peer) + ": " + deathEvent.ToCompactString()); } } catch (Exception ex) { NetLogger.Warn("[EnemyDeathMirror] Failed to send host enemy death event: " + ex.Message); } } private void HandleHostEnemyDeathEvent(NetPeer peer, NetPacketReader reader) { if (!NetGameplayDeathEventCodec.TryRead((NetDataReader)(object)reader, out NetGameplayDeathEvent evt)) { NetLogger.Warn("[EnemyDeathMirror] Malformed HostEnemyDeathEvent packet"); return; } if (_mode != NetMode.Client) { NetLogger.Warn("[EnemyDeathMirror] Ignoring HostEnemyDeathEvent because this instance is not a Client"); return; } if (!Plugin.Cfg.EnableHostEnemyDeathEventMirror.Value) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[EnemyDeathMirror] Ignoring HostEnemyDeathEvent because EnableHostEnemyDeathEventMirror=false"); } return; } string text = (string.IsNullOrWhiteSpace(evt.EventId) ? $"{evt.SourcePeerId}:{evt.SceneKey}:{evt.SeedText}:{evt.SpawnIndex}:{evt.Sequence}" : evt.EventId); if (!_receivedEnemyDeathEvents.Add(text)) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[EnemyDeathMirror] Duplicate HostEnemyDeathEvent ignored: " + text); } return; } if (Plugin.Cfg.LogReceivedEnemyDeathEvents.Value) { NetLogger.Info("[EnemyDeathMirror] Received host death: " + evt.ToCompactString()); } if (NetGameplayProbeManager.TryFindLocalMatch(evt, out NetGameplayEntitySnapshot snapshot, out string detail)) { if (Plugin.Cfg.LogReceivedEnemyDeathEvents.Value) { NetLogger.Info($"[EnemyDeathMirror] Matched local entity: idx={snapshot.SpawnIndex} candidate={snapshot.EntityId.CandidateKey} actor={snapshot.ActorName} pos={snapshot.PositionText} match={detail} apply={Plugin.Cfg.ApplyReceivedEnemyDeathEvents.Value}"); } if (Plugin.Cfg.ApplyReceivedEnemyDeathEvents.Value) { if (NetGameplayProbeManager.TryApplyHostDeathToLocalMatch(evt, snapshot, out string detail2)) { NetLogger.Info("[EnemyDeathMirror] Applied host death to local entity: " + detail2); } else { NetLogger.Warn("[EnemyDeathMirror] Failed to apply host death to local entity: " + detail2); } } } else if (Plugin.Cfg.LogReceivedEnemyDeathEvents.Value) { NetLogger.Info("[EnemyDeathMirror] No safe local match for host death: " + detail); } } private void SendClientEnemyDeathClaim(NetPeer peer, NetGameplayDeathEvent deathEvent) { try { NetDataWriter val = NetMessage.For(NetMessageType.ClientEnemyDeathClaim); NetGameplayDeathEventCodec.Write(val, deathEvent); peer.Send(val, (DeliveryMethod)2); if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[ClientDeathClaim] Sent to Host: " + deathEvent.ToCompactString()); } } catch (Exception ex) { NetLogger.Warn("[ClientDeathClaim] Failed to send client enemy death claim: " + ex.Message); } } private void HandleClientEnemyDeathClaim(NetPeer peer, NetPacketReader reader) { if (!NetGameplayDeathEventCodec.TryRead((NetDataReader)(object)reader, out NetGameplayDeathEvent evt)) { NetLogger.Warn("[ClientDeathClaim] Malformed ClientEnemyDeathClaim packet"); return; } if (_mode != NetMode.Host) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Warn("[ClientDeathClaim] Ignoring ClientEnemyDeathClaim because this instance is not a Host"); } return; } if (!Plugin.Cfg.EnableClientEnemyDeathClaim.Value) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[ClientDeathClaim] Ignoring ClientEnemyDeathClaim because EnableClientEnemyDeathClaim=false"); } return; } if (!_peerIds.TryGetValue(peer, out string value)) { NetLogger.Warn($"[ClientDeathClaim] Ignoring claim from unregistered peer {((IPEndPoint)(object)peer).Address}"); return; } evt.SourcePeerId = value; string text = (string.IsNullOrWhiteSpace(evt.EventId) ? $"{value}:{evt.SceneKey}:{evt.SeedText}:{evt.SpawnIndex}:{evt.Sequence}" : (value + ":" + evt.EventId)); if (!_receivedClientEnemyDeathClaims.Add(text)) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[ClientDeathClaim] Duplicate ClientEnemyDeathClaim ignored: " + text); } return; } if (Plugin.Cfg.LogReceivedClientEnemyDeathClaims.Value) { NetLogger.Info("[ClientDeathClaim] Received from " + value + ": " + evt.ToCompactString()); } if (NetGameplayProbeManager.TryFindLocalMatch(evt, out NetGameplayEntitySnapshot snapshot, out string detail)) { if (Plugin.Cfg.LogReceivedClientEnemyDeathClaims.Value) { NetLogger.Info($"[ClientDeathClaim] Matched Host local entity: idx={snapshot.SpawnIndex} candidate={snapshot.EntityId.CandidateKey} actor={snapshot.ActorName} pos={snapshot.PositionText} match={detail} apply={Plugin.Cfg.ApplyReceivedClientEnemyDeathClaimsOnHost.Value}"); } if (Plugin.Cfg.ApplyReceivedClientEnemyDeathClaimsOnHost.Value) { if (NetGameplayProbeManager.TryApplyClientDeathClaimToHostMatch(evt, snapshot, out string detail2)) { NetLogger.Info("[ClientDeathClaim] Applied client death claim on Host: " + detail2); } else { NetLogger.Warn("[ClientDeathClaim] Failed to apply client death claim on Host: " + detail2); } } } else if (Plugin.Cfg.LogReceivedClientEnemyDeathClaims.Value) { NetLogger.Info("[ClientDeathClaim] No safe Host local match for client death claim: " + detail); } } private void SendPlayerLifeState(NetPeer peer, NetPlayerLifeState state) { try { NetDataWriter val = NetMessage.For(NetMessageType.PlayerLifeState); NetPlayerLifeStateCodec.Write(val, state); peer.Send(val, (DeliveryMethod)2); if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[PlayerLife] Sent to " + PeerIdOrAddress(peer) + ": " + state.ToCompactString()); } } catch (Exception ex) { NetLogger.Warn("[PlayerLife] Failed to send player life state: " + ex.Message); } } private void SendPlayerLifeStateToClients(NetPlayerLifeState state, NetPeer? except) { if (_mode != NetMode.Host) { return; } NetPeer[] array = _clients.ToArray(); foreach (NetPeer val in array) { if (except == null || val != except) { SendPlayerLifeState(val, state); } } } private void HandlePlayerLifeState(NetPeer peer, NetPacketReader reader) { if (!NetPlayerLifeStateCodec.TryRead(reader, out NetPlayerLifeState state)) { NetLogger.Warn("[PlayerLife] Malformed PlayerLifeState packet"); } else if (!Plugin.Cfg.EnableCoopPlayerDownedRevive.Value) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[PlayerLife] Ignoring PlayerLifeState because EnableCoopPlayerDownedRevive=false"); } } else if (_mode == NetMode.Host) { if (!_peerIds.TryGetValue(peer, out string value)) { NetLogger.Warn($"[PlayerLife] Ignoring PlayerLifeState from unregistered peer {((IPEndPoint)(object)peer).Address}"); return; } if (state.Kind == NetPlayerLifeStateKind.HostDamageRequest) { NetLogger.Warn("[PlayerLife] Ignoring client-sent HostDamageRequest from " + value); return; } state.SourcePeerId = value; if (_runStates.TryGetRemote(value, out NetRunState state2) && !string.IsNullOrWhiteSpace(state2.PlayerName)) { state.PlayerName = state2.PlayerName; } if (Plugin.Cfg.LogPlayerLifeSync.Value) { NetLogger.Info("[PlayerLife] Received from " + value + ": " + state.ToCompactString()); } NetPlayerLifeManager.HandleNetworkState(state, receivedOnHost: true); if (state.Kind != NetPlayerLifeStateKind.ReviveRequest && state.Kind != NetPlayerLifeStateKind.RescueHoldStart && state.Kind != NetPlayerLifeStateKind.RescueHoldStop) { SendPlayerLifeStateToClients(state, peer); } } else if (_mode == NetMode.Client) { if (Plugin.Cfg.LogPlayerLifeSync.Value) { NetLogger.Info("[PlayerLife] Received from Host: " + state.ToCompactString()); } NetPlayerLifeManager.HandleNetworkState(state, receivedOnHost: false); } } private int GetEnemyStateSnapshotMaxPerPacket() { int num = Plugin.Cfg.EnemyStateSnapshotMaxEnemiesPerPacket.Value; if (num < 1) { num = 1; } if (num > 32) { if (!_enemyStateSnapshotPacketClampWarningShown) { _enemyStateSnapshotPacketClampWarningShown = true; NetLogger.Warn($"[EnemyStateMirror] EnemyStateSnapshotMaxEnemiesPerPacket={num} is above the current safe packet split limit; using {32} to stay below LiteNetLib packet size limits."); } return 32; } return num; } private void SendHostEnemyStateSnapshots(NetPeer peer, List snapshots, int maxPerPacket) { if (snapshots != null && snapshots.Count != 0) { if (maxPerPacket < 1) { maxPerPacket = 1; } for (int i = 0; i < snapshots.Count; i += maxPerPacket) { int count = Math.Min(maxPerPacket, snapshots.Count - i); SendHostEnemyStateSnapshotChunk(peer, snapshots, i, count, snapshots.Count); } } } private void SendHostEnemyStateSnapshotChunk(NetPeer peer, List snapshots, int offset, int count, int total) { if (count <= 0) { return; } try { NetDataWriter val = NetMessage.For(NetMessageType.HostEnemyStateSnapshot); NetGameplayEnemyStateSnapshotCodec.WriteBatch(val, snapshots, offset, count); int length = val.Length; if (length > _snapshotChunkBytesMax) { _snapshotChunkBytesMax = length; } if (length > 900 && count > 1) { _snapshotChunkSplit++; int num = count / 2; SendHostEnemyStateSnapshotChunk(peer, snapshots, offset, num, total); SendHostEnemyStateSnapshotChunk(peer, snapshots, offset + num, count - num, total); } else if (length > 900) { _snapshotChunkTooLargeRejected++; float num2 = Now(); if (num2 - _lastEnemyStateSnapshotSendErrorLogTime > 5f) { _lastEnemyStateSnapshotSendErrorLogTime = num2; NetLogger.Warn($"[EnemyStateMirror] Single-snapshot chunk too large bytes={length} limit={900} offset={offset}"); } } else { peer.Send(val, (DeliveryMethod)4); } } catch (Exception ex) { _snapshotChunkSendFailed++; float num3 = Now(); if (num3 - _lastEnemyStateSnapshotSendErrorLogTime > 2f) { _lastEnemyStateSnapshotSendErrorLogTime = num3; NetLogger.Warn($"[EnemyStateMirror] Failed to send HostEnemyStateSnapshot chunk offset={offset} count={count} total={total}: {ex.Message}"); } } } private void HandleHostEnemyStateSnapshot(NetPeer peer, NetPacketReader reader) { if (!NetGameplayEnemyStateSnapshotCodec.TryReadBatch((NetDataReader)(object)reader, out List snapshots)) { NetLogger.Warn("[EnemyStateMirror] Malformed HostEnemyStateSnapshot packet"); } else if (_mode != NetMode.Client) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Warn("[EnemyStateMirror] Ignoring HostEnemyStateSnapshot because this instance is not a Client"); } } else if (!Plugin.Cfg.EnableHostEnemyStateSnapshotMirror.Value) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[EnemyStateMirror] Ignoring HostEnemyStateSnapshot because EnableHostEnemyStateSnapshotMirror=false"); } } else { NetGameplayProbeManager.ProcessHostEnemyStateSnapshots(snapshots); } } private void HandlePing(NetPeer peer) { TouchPeer(peer); if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[Net] Ping → Pong"); } peer.Send(NetMessage.For(NetMessageType.Pong), (DeliveryMethod)4); } private void RejectPeer(NetPeer peer, string reason) { NetLogger.Info($"[Net] Rejecting peer {((IPEndPoint)(object)peer).Address}: {reason}"); NetDataWriter val = NetMessage.For(NetMessageType.HandshakeRejected); val.Put(reason); peer.Send(val, (DeliveryMethod)2); peer.Disconnect(); _clients.Remove(peer); if (_peerIds.TryGetValue(peer, out string value)) { _sessions.Remove(value); _runStates.RemoveRemote(value); _sceneRequests.RemovePeer(value); _visualProxies.Remove(value); PlayerHeldWeaponManager.RemovePeer(value); NetLoadBarrier.RemovePeer(value); _peerIds.Remove(peer); } } private void OnPeerDisconnected(NetPeer peer, DisconnectInfo info) { //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_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) NetLogger.Info($"[Net] Peer disconnected: {((IPEndPoint)(object)peer).Address} reason={info.Reason}"); _clients.Remove(peer); if (_peerIds.TryGetValue(peer, out string peerId)) { string item = _sessions.Sessions.FirstOrDefault((NetPeerSession s) => s.PeerId == peerId)?.PlayerName ?? peerId; if (_mode == NetMode.Host) { _sessions.Remove(peerId); } else { _sessions.MarkDisconnected(peerId); } _runStates.RemoveRemote(peerId); _sceneRequests.RemovePeer(peerId); _visualProxies.Remove(peerId); PlayerHeldWeaponManager.RemovePeer(peerId); NetLoadBarrier.RemovePeer(peerId); _peerIds.Remove(peer); NetLogger.Info("[Session] Peer left: id=" + peerId); CoopToasts.Notify((_mode == NetMode.Host) ? CoopLoc.Format("toast.playerLeft", "{name} left", ("name", item)) : CoopLoc.Get("toast.disconnectedFromHost", "Disconnected from host")); } if (_mode == NetMode.Client) { if (peer == _hostPeer) { _hostPeer = null; NetClientJoinFlow.LeaveSession("disconnect"); NetLogger.Info("[Session] Host connection lost"); } _clientConnectInProgress = false; _nextClientReconnectTime = Now() + 5f; if ((int)info.Reason == 0) { NetConnectFeedback.ReportError(CoopLoc.Get("connect.error.unreachable", "Connection failed — host unreachable. Check the address, port and that the host is up.")); NetLogger.Info("[Net] Client will retry connection in 5 seconds"); } else { NetLogger.Info("[Net] Client reconnect scheduled in 5 seconds"); } } } private void TouchPeer(NetPeer peer) { if (_peerIds.TryGetValue(peer, out string value)) { _sessions.Touch(value, Now()); } } private static float Now() { return Time.realtimeSinceStartup; } } public sealed class NetSessionManager { private readonly Dictionary _sessionsById = new Dictionary(); private int _nextClientPeerNumber = 1; public int ConnectedCount => _sessionsById.Values.Count((NetPeerSession s) => s.State == NetConnectionState.Connected); public int RemoteConnectedCount => _sessionsById.Values.Count((NetPeerSession s) => s.State == NetConnectionState.Connected && !s.IsLocal); public IReadOnlyCollection Sessions => _sessionsById.Values.ToList().AsReadOnly(); public void Clear() { _sessionsById.Clear(); _nextClientPeerNumber = 1; } public NetPeerSession RegisterLocalHost(string playerName, string modVersion, float now) { NetPeerSession netPeerSession = new NetPeerSession { PeerId = "host", PlayerName = SanitizeName(playerName, "Host"), ModVersion = (modVersion ?? ""), EndPoint = "local", Slot = 0, Role = NetPeerRole.Host, State = NetConnectionState.Connected, JoinedAt = now, LastSeen = now, IsLocal = true }; _sessionsById[netPeerSession.PeerId] = netPeerSession; return netPeerSession; } public NetPeerSession RegisterLocalClient(string peerId, int slot, string playerName, string modVersion, float now) { peerId = (string.IsNullOrWhiteSpace(peerId) ? "client-local" : peerId); NetPeerSession netPeerSession = new NetPeerSession { PeerId = peerId, PlayerName = SanitizeName(playerName, "Client"), ModVersion = (modVersion ?? ""), EndPoint = "local", Slot = slot, Role = NetPeerRole.Client, State = NetConnectionState.Connected, JoinedAt = now, LastSeen = now, IsLocal = true }; _sessionsById[netPeerSession.PeerId] = netPeerSession; return netPeerSession; } public NetPeerSession RegisterRemoteHost(string peerId, string playerName, string modVersion, string endPoint, float now) { peerId = (string.IsNullOrWhiteSpace(peerId) ? "host" : peerId); NetPeerSession netPeerSession = new NetPeerSession { PeerId = peerId, PlayerName = SanitizeName(playerName, "Host"), ModVersion = (modVersion ?? ""), EndPoint = (endPoint ?? ""), Slot = 0, Role = NetPeerRole.Host, State = NetConnectionState.Connected, JoinedAt = now, LastSeen = now, IsLocal = false }; _sessionsById[netPeerSession.PeerId] = netPeerSession; return netPeerSession; } public NetPeerSession RegisterRemoteClient(string playerName, string modVersion, string endPoint, int maxPlayers, float now) { int slot = AllocateClientSlot(maxPlayers); string text = $"client-{_nextClientPeerNumber++}"; NetPeerSession netPeerSession = new NetPeerSession { PeerId = text, PlayerName = SanitizeName(playerName, text), ModVersion = (modVersion ?? ""), EndPoint = (endPoint ?? ""), Slot = slot, Role = NetPeerRole.Client, State = NetConnectionState.Connected, JoinedAt = now, LastSeen = now, IsLocal = false }; _sessionsById[netPeerSession.PeerId] = netPeerSession; return netPeerSession; } public void Touch(string peerId, float now) { if (!string.IsNullOrWhiteSpace(peerId) && _sessionsById.TryGetValue(peerId, out NetPeerSession value)) { value.LastSeen = now; } } public void MarkDisconnected(string peerId) { if (!string.IsNullOrWhiteSpace(peerId) && _sessionsById.TryGetValue(peerId, out NetPeerSession value)) { value.State = NetConnectionState.Disconnected; } } public void Remove(string peerId) { if (!string.IsNullOrWhiteSpace(peerId)) { _sessionsById.Remove(peerId); } } public string FormatStatus() { if (_sessionsById.Count == 0) { return "sessions=0"; } IEnumerable values = from s in _sessionsById.Values orderby s.Slot, s.PeerId select s.ToCompactString(); return string.Format("sessions={0} [{1}]", _sessionsById.Count, string.Join("; ", values)); } private int AllocateClientSlot(int maxPlayers) { int num = ((maxPlayers < 2) ? 2 : maxPlayers); int slot; for (slot = 1; slot < num; slot++) { if (!_sessionsById.Values.Any((NetPeerSession s) => s.State == NetConnectionState.Connected && s.Slot == slot)) { return slot; } } return -1; } private static string SanitizeName(string value, string fallback) { if (string.IsNullOrWhiteSpace(value)) { return fallback; } value = value.Trim(); if (value.Length <= 32) { return value; } return value.Substring(0, 32); } } public sealed class NetSessionSettingsState { public int Revision; public bool FriendlyFire; } public static class NetSessionSettings { private static bool _receivedFriendlyFire; private static int _lastAppliedRevision = -1; public static bool FriendlyFireEnabled => NetConfig.GetMode() switch { NetMode.Host => ReadHostConfig(), NetMode.Client => _receivedFriendlyFire, _ => false, }; private static bool ReadHostConfig() { try { return Plugin.Cfg.FriendlyFire.Value; } catch { return false; } } public static bool ApplyReceived(NetSessionSettingsState state) { if (state == null) { return false; } if (state.Revision <= _lastAppliedRevision) { return false; } bool flag = _lastAppliedRevision < 0; _lastAppliedRevision = state.Revision; bool num = _receivedFriendlyFire != state.FriendlyFire; _receivedFriendlyFire = state.FriendlyFire; if (num && Plugin.Cfg.LogFriendlyFire.Value) { Plugin.Log.Info($"[FF] session settings applied: friendlyFire={state.FriendlyFire} rev={state.Revision}"); } if (num) { return !flag; } return false; } public static void ResetSession() { _receivedFriendlyFire = false; _lastAppliedRevision = -1; } } internal static class NetSessionSettingsCodec { public static void Write(NetDataWriter w, NetSessionSettingsState s) { w.Put(s.Revision); w.Put(s.FriendlyFire); } public static bool TryRead(NetDataReader r, out NetSessionSettingsState result) { result = null; try { NetSessionSettingsState netSessionSettingsState = new NetSessionSettingsState(); netSessionSettingsState.Revision = r.GetInt(); netSessionSettingsState.FriendlyFire = r.GetBool(); result = netSessionSettingsState; return true; } catch { return false; } } } internal sealed class RemotePlayerTargetProxyManager { private sealed class Proxy { public GameObject Go; public object Unit; public float LastUpdatedAt; } private readonly Dictionary _proxies = new Dictionary(); private static readonly Dictionary _proxyUnitPeers = new Dictionary(); private static bool _resolveAttempted; private static bool _resolveOk; private static Type? _unitType; private static Type? _factionType; private static object? _factionPlayer; private static PropertyInfo? _gmInstance; private static PropertyInfo? _gmUnits; private static PropertyInfo? _gmPlayerUnit; private static MethodInfo? _setStats; private static MethodInfo? _overrideFaction; private static MethodInfo? _spawn; private static FieldInfo? _unitSOField; private static FieldInfo? _isPlayerField; private static FieldInfo? _overrideTargetsField; private static MethodInfo? _addUnits; private static MethodInfo? _clearUnits; private static MethodInfo? _hasTargetsGetter; private static object? _targetTypeValue; private static Type? _unitListType; private static Type? _hitmeshType; private static FieldInfo? _hitmeshOwnerField; private static FieldInfo? _hitmeshColliderField; private const bool LogOn = false; private readonly Dictionary _overriddenOt = new Dictionary(); private readonly HashSet _aggroSeen = new HashSet(); private readonly List _staleAggro = new List(); private float _lastAggroAt; private float _lastDiagAt; private readonly HashSet _seenThisTick = new HashSet(); private readonly List _removeScratch = new List(); private static bool Enabled { get { try { return Plugin.Cfg.EnableRemotePlayerTargetProxy.Value; } catch { return false; } } } public int ProxyCount => _proxies.Count; public static bool IsProxyUnit(object? unit) { try { Object val = (Object)((unit is Object) ? unit : null); if (val != null && val != (Object)null) { lock (_proxyUnitPeers) { return _proxyUnitPeers.ContainsKey(val.GetInstanceID()); } } } catch { } return false; } public static void RegisterExternalProxyUnit(object? unit, string peerId) { try { Object val = (Object)((unit is Object) ? unit : null); if (val != null && val != (Object)null) { lock (_proxyUnitPeers) { _proxyUnitPeers[val.GetInstanceID()] = peerId; return; } } } catch { } } public static void UnregisterExternalProxyUnit(object? unit) { try { Object val = (Object)((unit is Object) ? unit : null); if (val != null && val != (Object)null) { lock (_proxyUnitPeers) { _proxyUnitPeers.Remove(val.GetInstanceID()); return; } } } catch { } } public static bool TryGetProxyPeer(object? unit, out string peerId) { peerId = ""; try { Object val = (Object)((unit is Object) ? unit : null); if (val != null && val != (Object)null) { lock (_proxyUnitPeers) { return _proxyUnitPeers.TryGetValue(val.GetInstanceID(), out peerId); } } } catch { } return false; } public void Tick(NetRemotePlayerProxyManager visualProxies, float now, float maxAgeSeconds) { if (!Enabled) { if (_proxies.Count > 0) { Clear(); } } else { if (!EnsureResolved()) { return; } bool removeWhenDowned = true; try { removeWhenDowned = Plugin.Cfg.RemoveTargetProxyWhenPeerDowned.Value; } catch { } _seenThisTick.Clear(); visualProxies.ForEachInScenePlayer(delegate(string peerId, Vector3 pos) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) if (!removeWhenDowned || !NetPlayerLifeManager.IsPeerDownOrDead(peerId)) { _seenThisTick.Add(peerId); UpdateOrCreate(peerId, pos, now); } }, now, maxAgeSeconds); if (_proxies.Count > 0) { _removeScratch.Clear(); foreach (KeyValuePair proxy in _proxies) { if (!_seenThisTick.Contains(proxy.Key)) { _removeScratch.Add(proxy.Key); } } foreach (string item in _removeScratch) { Destroy(item, "player gone"); } } DriveAggro(now); DiagProxies(now); } } private void DriveAggro(float now) { //IL_0281: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) bool flag; try { flag = Plugin.Cfg.RemotePlayerTargetProxyForceAggro.Value; } catch { flag = false; } if (!flag || _addUnits == null || _overrideTargetsField == null || _unitListType == null || _targetTypeValue == null) { if (_overriddenOt.Count > 0) { ClearAllOverrides(); } } else { if (now - _lastAggroAt < 0.5f) { return; } _lastAggroAt = now; float num; try { num = Plugin.Cfg.RemotePlayerTargetProxyAggroRange.Value; } catch { num = 30f; } try { object value = _gmInstance.GetValue(null, null); if (value == null) { return; } if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || !state.HasLevel) { if (_overriddenOt.Count > 0) { ClearAllOverrides(); } } else { if (!(_gmUnits?.GetValue(value, null) is IEnumerable enumerable)) { return; } bool flag2; try { flag2 = Plugin.Cfg.RemotePlayerTargetProxyOnlyWhenCloser.Value; } catch { flag2 = false; } object obj4 = ((!flag2) ? null : _gmPlayerUnit?.GetValue(value, null)); Component val = (Component)((obj4 is Component) ? obj4 : null); bool flag3 = val != null && (Object)(object)val != (Object)null; Vector3 val2 = (flag3 ? ((Component)obj4).transform.position : Vector3.zero); _aggroSeen.Clear(); bool flag4 = default(bool); foreach (KeyValuePair proxy in _proxies) { if ((Object)(object)proxy.Value.Go == (Object)null) { continue; } Vector3 position = proxy.Value.Go.transform.position; object unit = proxy.Value.Unit; foreach (object item in enumerable) { if (item == unit) { continue; } Component val3 = (Component)((item is Component) ? item : null); if (val3 == null || (Object)(object)val3 == (Object)null) { continue; } object obj5 = Member(item, "AiAgent"); if (obj5 == null) { continue; } Vector3 position2 = val3.transform.position; float num2 = Vector3.Distance(position2, position); float num3 = num; try { if (Member(obj5, "currentLosRange") is float num4 && num4 > 1f) { num3 = num4; } } catch { } if (num2 > num3 || (flag2 && flag3 && Vector3.Distance(position2, val2) <= num2)) { continue; } object value2 = _overrideTargetsField.GetValue(obj5); if (value2 == null) { continue; } Object val4 = (Object)((obj5 is Object) ? obj5 : null); int num5 = ((val4 != null && val4 != (Object)null) ? val4.GetInstanceID() : obj5.GetHashCode()); if (!_overriddenOt.ContainsKey(num5) && _hasTargetsGetter != null) { try { object obj7 = _hasTargetsGetter.Invoke(value2, null); int num6; if (obj7 is bool) { flag4 = (bool)obj7; num6 = 1; } else { num6 = 0; } if (((uint)num6 & (flag4 ? 1u : 0u)) != 0) { continue; } } catch { } } IList list = (IList)Activator.CreateInstance(_unitListType); list.Add(unit); try { _addUnits.Invoke(value2, new object[2] { list, _targetTypeValue }); } catch { continue; } _aggroSeen.Add(num5); _overriddenOt[num5] = value2; } } if (_overriddenOt.Count <= _aggroSeen.Count) { return; } _staleAggro.Clear(); foreach (KeyValuePair item2 in _overriddenOt) { if (!_aggroSeen.Contains(item2.Key)) { _staleAggro.Add(item2.Key); } } { foreach (int item3 in _staleAggro) { try { _clearUnits?.Invoke(_overriddenOt[item3], null); } catch { } _overriddenOt.Remove(item3); } return; } } } catch (Exception ex) { Plugin.Log.Warn("[TargetProxy] DriveAggro failed: " + ex.GetType().Name + ": " + ex.Message); } } } private void ClearAllOverrides() { foreach (object value in _overriddenOt.Values) { try { _clearUnits?.Invoke(value, null); } catch { } } _overriddenOt.Clear(); } private void DiagProxies(float now) { } private static object? Member(object? o, string name) { try { if (o == null) { return null; } Type type = o.GetType(); while (type != null && type != typeof(object)) { PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { return property.GetValue(o, null); } FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(o); } type = type.BaseType; } } catch { } return null; } private static string Describe(object? v) { if (v == null) { return "null"; } Object val = (Object)((v is Object) ? v : null); if (val != null) { if (!(val == (Object)null)) { return v.GetType().Name + ":" + val.name; } return "null(destroyed)"; } return v.ToString(); } private static string DescribeEnumerable(object? c) { if (c == null) { return "null"; } if (c is IEnumerable enumerable && !(c is string)) { StringBuilder stringBuilder = new StringBuilder(); int num = 0; foreach (object item in enumerable) { if (num > 0) { stringBuilder.Append(", "); } stringBuilder.Append(Describe(item)); if (++num >= 8) { stringBuilder.Append(",..."); break; } } return $"[{num}]{{{stringBuilder}}}"; } return Describe(c); } private void UpdateOrCreate(string peerId, Vector3 pos, float now) { //IL_0048: 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) if (_proxies.TryGetValue(peerId, out Proxy value)) { if (!((Object)(object)value.Go == (Object)null)) { value.Go.transform.position = pos; value.LastUpdatedAt = now; return; } _proxies.Remove(peerId); } TryCreate(peerId, pos, now); } private void TryCreate(string peerId, Vector3 pos, float now) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_04e2: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) try { object value = _gmInstance.GetValue(null, null); if (value == null) { return; } object obj = _gmPlayerUnit?.GetValue(value, null); object obj2 = ((obj != null && _unitSOField != null) ? _unitSOField.GetValue(obj) : null); Component val = (Component)((obj is Component) ? obj : null); int num = ((val != null && (Object)(object)val != (Object)null) ? val.gameObject.layer : 0); int num2 = num; try { num2 = Plugin.Cfg.RemotePlayerTargetProxyHitboxLayer.Value; } catch { } GameObject val2 = new GameObject("SULFURTogether RemotePlayerTargetProxy - " + peerId); val2.SetActive(false); val2.layer = num2; val2.transform.position = pos; CapsuleCollider val3 = val2.AddComponent(); val3.height = 1.9f; val3.radius = 0.4f; val3.center = new Vector3(0f, 0.95f, 0f); ((Collider)val3).isTrigger = false; Rigidbody obj4 = val2.AddComponent(); obj4.isKinematic = true; obj4.useGravity = false; Component val4 = val2.AddComponent(_unitType); if (_unitSOField != null && obj2 != null) { try { _unitSOField.SetValue(val4, obj2); } catch { } } Object val5 = (Object)(object)val4; if (val5 != null) { lock (_proxyUnitPeers) { _proxyUnitPeers[val5.GetInstanceID()] = peerId; } } if (_hitmeshType != null) { try { Component obj6 = val2.AddComponent(_hitmeshType); _hitmeshOwnerField?.SetValue(obj6, val4); _hitmeshColliderField?.SetValue(obj6, val3); } catch (Exception ex) { Plugin.Log.Warn("[TargetProxy] Hitmesh add failed: " + ex.GetType().Name + ": " + ex.Message); } } bool flag = true; try { flag = Plugin.Cfg.RemotePlayerTargetProxyBodyBlocker.Value; } catch { } if (flag && num != num2) { try { GameObject val6 = new GameObject("BodyBlocker") { layer = num }; val6.transform.SetParent(val2.transform, false); CapsuleCollider obj8 = val6.AddComponent(); obj8.height = 1.9f; obj8.radius = 0.4f; obj8.center = new Vector3(0f, 0.95f, 0f); ((Collider)obj8).isTrigger = false; } catch (Exception ex2) { Plugin.Log.Warn("[TargetProxy] body blocker add failed: " + ex2.GetType().Name + ": " + ex2.Message); } } val2.SetActive(true); if (_setStats != null && obj2 != null) { try { _setStats.Invoke(val4, new object[1] { obj2 }); } catch (Exception ex3) { Plugin.Log.Warn("[TargetProxy] SetStats failed: " + ex3.GetType().Name + ": " + ex3.Message); } } if (_spawn != null) { try { _spawn.Invoke(val4, null); } catch (Exception ex4) { Plugin.Log.Warn("[TargetProxy] Spawn failed: " + ex4.GetType().Name + ": " + ex4.Message); } } if (_overrideFaction != null && _factionPlayer != null) { try { _overrideFaction.Invoke(val4, new object[2] { _factionPlayer, 999999f }); } catch (Exception ex5) { Plugin.Log.Warn("[TargetProxy] OverrideFaction failed: " + ex5.GetType().Name + ": " + ex5.Message); } } bool flag2 = false; try { flag2 = Plugin.Cfg.RemotePlayerTargetProxySetIsPlayer.Value; } catch { } if (flag2 && _isPlayerField != null) { try { _isPlayerField.SetValue(val4, true); } catch (Exception ex6) { Plugin.Log.Warn("[TargetProxy] set isPlayer failed: " + ex6.GetType().Name + ": " + ex6.Message); } } try { if (_gmUnits?.GetValue(value, null) is IList list && !list.Contains(val4)) { list.Add(val4); } } catch (Exception ex7) { Plugin.Log.Warn("[TargetProxy] units.Add failed: " + ex7.GetType().Name + ": " + ex7.Message); } _proxies[peerId] = new Proxy { Go = val2, Unit = val4, LastUpdatedAt = now }; Plugin.Log.Info(string.Format("[TargetProxy] created proxy peer={0} pos={1:F1} layer={2} unitSO={3} faction={4}", peerId, pos, num, (obj2 == null) ? "null" : "ok", _factionPlayer?.ToString() ?? "?")); } catch (Exception ex8) { Plugin.Log.Warn("[TargetProxy] create failed peer=" + peerId + ": " + ex8.GetType().Name + ": " + ex8.Message); } } private void Destroy(string peerId, string reason) { if (!_proxies.TryGetValue(peerId, out Proxy value)) { return; } _proxies.Remove(peerId); try { object unit = value.Unit; Object val = (Object)((unit is Object) ? unit : null); if (val != null && val != (Object)null) { lock (_proxyUnitPeers) { _proxyUnitPeers.Remove(val.GetInstanceID()); } } } catch { } try { object obj2 = _gmInstance?.GetValue(null, null); if (obj2 != null && _gmUnits?.GetValue(obj2, null) is IList list) { list.Remove(value.Unit); } } catch { } try { if ((Object)(object)value.Go != (Object)null) { Object.Destroy((Object)(object)value.Go); } } catch { } } public void Clear() { ClearAllOverrides(); foreach (string item in new List(_proxies.Keys)) { Destroy(item, "clear"); } _proxies.Clear(); } private static bool EnsureResolved() { if (_resolveAttempted) { return _resolveOk; } _resolveAttempted = true; try { _unitType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Units.Unit"); Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); if (_unitType == null || type == null) { Plugin.Log.Warn("[TargetProxy] resolve failed: Unit/GameManager type missing"); return false; } _gmInstance = AccessTools.Property(type, "Instance"); _gmUnits = AccessTools.Property(type, "units"); _gmPlayerUnit = AccessTools.Property(type, "PlayerUnit"); _setStats = AccessTools.Method(_unitType, "SetStats", (Type[])null, (Type[])null); _overrideFaction = AccessTools.Method(_unitType, "OverrideFaction", (Type[])null, (Type[])null); _spawn = AccessTools.Method(_unitType, "Spawn", Type.EmptyTypes, (Type[])null); _unitSOField = AccessTools.Field(_unitType, "unitSO"); _isPlayerField = AccessTools.Field(_unitType, "isPlayer"); if (_overrideFaction != null) { _factionType = _overrideFaction.GetParameters()[0].ParameterType; try { _factionPlayer = Enum.Parse(_factionType, "Player"); } catch (Exception ex) { Plugin.Log.Warn("[TargetProxy] FactionIds.Player parse failed: " + ex.Message); } } Type type2 = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Units.AI.AiAgent"); _overrideTargetsField = ((type2 == null) ? null : AccessTools.Field(type2, "overridetargets")); Type type3 = _overrideTargetsField?.FieldType; if (type3 != null) { _addUnits = AccessTools.Method(type3, "AddUnits", (Type[])null, (Type[])null); _clearUnits = AccessTools.Method(type3, "ClearUnits", (Type[])null, (Type[])null); _hasTargetsGetter = AccessTools.PropertyGetter(type3, "HasTargets"); Type type4 = ((_addUnits != null && _addUnits.GetParameters().Length == 2) ? _addUnits.GetParameters()[1].ParameterType : null); if (type4 != null && type4.IsEnum) { try { _targetTypeValue = Enum.Parse(type4, "Closest"); } catch { try { _targetTypeValue = Enum.Parse(type4, "First"); } catch { } } } try { _unitListType = typeof(List<>).MakeGenericType(_unitType); } catch { } } _hitmeshType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Hitmesh"); if (_hitmeshType != null) { _hitmeshOwnerField = AccessTools.Field(_hitmeshType, "owner"); _hitmeshColliderField = AccessTools.Field(_hitmeshType, "hitmeshCollider"); } _resolveOk = _gmInstance != null && _gmUnits != null && _overrideFaction != null && _factionPlayer != null; Plugin.Log.Info($"[TargetProxy] resolve ok={_resolveOk} unit={_unitType != null} gmInstance={_gmInstance != null} units={_gmUnits != null} setStats={_setStats != null} overrideFaction={_overrideFaction != null} unitSOField={_unitSOField != null} factionPlayer={_factionPlayer}"); return _resolveOk; } catch (Exception ex2) { Plugin.Log.Warn("[TargetProxy] resolve exception: " + ex2.GetType().Name + ": " + ex2.Message); return false; } } } internal static class SteamIdentity { private static bool _resolved; private static PropertyInfo _initializedProp; private static MethodInfo _getPersonaName; private static string _cachedName; private static bool _cacheTried; public static bool TryGetPersonaName(out string name) { if (_cacheTried) { name = _cachedName; return !string.IsNullOrWhiteSpace(_cachedName); } _cacheTried = true; _cachedName = Resolve(); name = _cachedName; return !string.IsNullOrWhiteSpace(_cachedName); } private static string Resolve() { try { if (!_resolved) { _resolved = true; Type type = AccessTools.TypeByName("SteamManager"); _initializedProp = ((type != null) ? AccessTools.Property(type, "Initialized") : null); Type type2 = AccessTools.TypeByName("Steamworks.SteamFriends"); _getPersonaName = ((type2 != null) ? AccessTools.Method(type2, "GetPersonaName", (Type[])null, (Type[])null) : null); } if (_initializedProp == null || _getPersonaName == null) { return null; } object value = _initializedProp.GetValue(null); if (!(value is bool) || !(bool)value) { return null; } string text = _getPersonaName.Invoke(null, null) as string; if (!string.IsNullOrWhiteSpace(text)) { Plugin.Log?.Info("[SteamIdentity] persona name resolved: " + text); } return text; } catch (Exception ex) { Plugin.Log?.Info("[SteamIdentity] persona name unavailable: " + ex.Message); return null; } } } internal static class SteamNetworkingSupport { public const int RelayChannel = 0; private static bool _resolved; private static bool _available; private static Callback _sessionRequestCallback; private static Callback _sessionFailedCallback; private const int SendFlags = 41; public static bool IsAvailable { get { EnsureResolved(); return _available; } } public static event Action SessionRequested; public static event Action SessionFailed; public static bool TryGetLocalSteamId(out CSteamID id) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_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_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) if (!IsAvailable) { id = CSteamID.Nil; return false; } try { id = SteamUser.GetSteamID(); return ((CSteamID)(ref id)).IsValid(); } catch (Exception ex) { Plugin.Log?.Info("[SteamNet] GetSteamID failed: " + ex.Message); id = CSteamID.Nil; return false; } } public static bool AcceptSession(CSteamID remote) { //IL_0035: 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_0014: Unknown result type (might be due to invalid IL or missing references) if (!IsAvailable) { return false; } try { SteamNetworkingIdentity val = default(SteamNetworkingIdentity); ((SteamNetworkingIdentity)(ref val)).SetSteamID(remote); return SteamNetworkingMessages.AcceptSessionWithUser(ref val); } catch (Exception ex) { Plugin.Log?.Warn($"[SteamNet] AcceptSession({remote.m_SteamID}) failed: {ex.Message}"); return false; } } public static void CloseSession(CSteamID remote) { //IL_0034: 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_0013: Unknown result type (might be due to invalid IL or missing references) if (!IsAvailable) { return; } try { SteamNetworkingIdentity val = default(SteamNetworkingIdentity); ((SteamNetworkingIdentity)(ref val)).SetSteamID(remote); SteamNetworkingMessages.CloseSessionWithUser(ref val); } catch (Exception ex) { Plugin.Log?.Info($"[SteamNet] CloseSession({remote.m_SteamID}) failed: {ex.Message}"); } } public static bool SendRaw(CSteamID remote, byte[] data, int length) { //IL_008b: 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Invalid comparison between Unknown and I4 //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Invalid comparison between Unknown and I4 //IL_0051: 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) if (!IsAvailable || data == null || length <= 0) { return false; } IntPtr intPtr = Marshal.AllocHGlobal(length); try { Marshal.Copy(data, 0, intPtr, length); SteamNetworkingIdentity val = default(SteamNetworkingIdentity); ((SteamNetworkingIdentity)(ref val)).SetSteamID(remote); EResult val2 = SteamNetworkingMessages.SendMessageToUser(ref val, intPtr, (uint)length, 41, 0); if ((int)val2 != 1) { Plugin.Log?.Warn($"[SteamNet] SendMessageToUser({remote.m_SteamID}, {length}B) returned {val2}"); } return (int)val2 == 1; } catch (Exception ex) { Plugin.Log?.Info($"[SteamNet] SendRaw({remote.m_SteamID}, {length}B) failed: {ex.Message}"); return false; } finally { Marshal.FreeHGlobal(intPtr); } } public static int PumpReceive(int maxMessages, Action onMessage) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_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_009b: 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_0080: Unknown result type (might be due to invalid IL or missing references) if (!IsAvailable || onMessage == null || maxMessages <= 0) { return 0; } IntPtr[] array = new IntPtr[maxMessages]; int num; try { num = SteamNetworkingMessages.ReceiveMessagesOnChannel(0, array, maxMessages); } catch (Exception ex) { Plugin.Log?.Info("[SteamNet] ReceiveMessagesOnChannel failed: " + ex.Message); return 0; } for (int i = 0; i < num; i++) { try { SteamNetworkingMessage_t val = SteamNetworkingMessage_t.FromIntPtr(array[i]); byte[] array2 = new byte[val.m_cbSize]; if (val.m_cbSize > 0) { Marshal.Copy(val.m_pData, array2, 0, val.m_cbSize); } CSteamID steamID = ((SteamNetworkingIdentity)(ref val.m_identityPeer)).GetSteamID(); onMessage(steamID, array2); } catch (Exception ex2) { Plugin.Log?.Info($"[SteamNet] failed to process received message {i}: {ex2.Message}"); } finally { SteamNetworkingMessage_t.Release(array[i]); } } return num; } private static void EnsureResolved() { if (_resolved) { return; } _resolved = true; try { Type type = AccessTools.TypeByName("SteamManager"); PropertyInfo propertyInfo = ((type != null) ? AccessTools.Property(type, "Initialized") : null); bool flag = default(bool); int num; if (propertyInfo != null) { object value = propertyInfo.GetValue(null); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } _available = (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch (Exception ex) { _available = false; Plugin.Log?.Info("[SteamNet] availability check failed: " + ex.Message); } if (!_available) { Plugin.Log?.Info("[SteamNet] not available (Steam not running, or SteamManager missing) — Steam connect method disabled."); return; } try { _sessionRequestCallback = Callback.Create((DispatchDelegate)OnSessionRequest); _sessionFailedCallback = Callback.Create((DispatchDelegate)OnSessionFailed); CSteamID id; string text = (TryGetLocalSteamId(out id) ? id.m_SteamID.ToString() : "?"); Plugin.Log?.Info("[SteamNet] available; local SteamID=" + text); } catch (Exception ex2) { _available = false; Plugin.Log?.Warn("[SteamNet] callback registration failed, disabling Steam connect method: " + ex2.Message); } } private static void OnSessionRequest(SteamNetworkingMessagesSessionRequest_t evt) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: 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) CSteamID steamID = ((SteamNetworkingIdentity)(ref evt.m_identityRemote)).GetSteamID(); Plugin.Log?.Info($"[SteamNet] inbound P2P session request from {steamID.m_SteamID}"); try { SteamNetworkingSupport.SessionRequested?.Invoke(steamID); } catch (Exception ex) { Plugin.Log?.Warn("[SteamNet] SessionRequested handler threw: " + ex.Message); } } private static void OnSessionFailed(SteamNetworkingMessagesSessionFailed_t evt) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_002c: 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_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) SteamNetConnectionInfo_t info = evt.m_info; CSteamID steamID = ((SteamNetworkingIdentity)(ref info.m_identityRemote)).GetSteamID(); Plugin.Log?.Warn($"[SteamNet] P2P session FAILED with {steamID.m_SteamID} — reason={info.m_eEndReason} debug='{((SteamNetConnectionInfo_t)(ref info)).m_szEndDebug}' state={info.m_eState}"); try { SteamNetworkingSupport.SessionFailed?.Invoke(steamID); } catch (Exception ex) { Plugin.Log?.Warn("[SteamNet] SessionFailed handler threw: " + ex.Message); } } } internal static class SteamRelayBridge { private sealed class HostPeerRelay { public CSteamID SteamId; public UdpClient Socket; } private const int ReceivePumpBudgetPerTick = 64; private static readonly Dictionary _hostPeers = new Dictionary(); private static bool _hostingActive; private static int _hostGamePort; private static UdpClient _clientSocket; private static IPEndPoint _clientLiteNetLibEndpoint; private static CSteamID _clientHostId; private static bool _clientActive; private static bool _initialized; public static bool IsHostingActive => _hostingActive; public static bool IsClientActive => _clientActive; public static void Initialize() { if (!_initialized) { _initialized = true; SteamNetworkingSupport.SessionRequested += OnHostSessionRequested; SteamNetworkingSupport.SessionFailed += OnHostPeerSessionFailed; } } public static bool StartHosting(int gamePort) { if (_hostingActive) { return true; } if (!SteamNetworkingSupport.IsAvailable) { return false; } _hostGamePort = gamePort; _hostingActive = true; Plugin.Log?.Info($"[SteamRelay] hosting enabled — Steam peers bridge to loopback game port {gamePort}."); return true; } public static void StopHosting() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (!_hostingActive) { return; } foreach (HostPeerRelay value in _hostPeers.Values) { try { value.Socket.Close(); } catch { } SteamNetworkingSupport.CloseSession(value.SteamId); } _hostPeers.Clear(); _hostingActive = false; Plugin.Log?.Info("[SteamRelay] hosting disabled."); } private static void OnHostPeerSessionFailed(CSteamID remote) { //IL_0005: 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_0046: Unknown result type (might be due to invalid IL or missing references) if (_hostPeers.TryGetValue(remote.m_SteamID, out HostPeerRelay value)) { try { value.Socket.Close(); } catch { } _hostPeers.Remove(remote.m_SteamID); Plugin.Log?.Info($"[SteamRelay] dropped stale bridge for {remote.m_SteamID} after session failure — a retry will be re-accepted."); } } private static void OnHostSessionRequested(CSteamID remote) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_003f: 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_008a: 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_0097: 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_00ce: Unknown result type (might be due to invalid IL or missing references) if (!_hostingActive) { Plugin.Log?.Info($"[SteamRelay] ignored session request from {remote.m_SteamID} — Steam hosting not enabled."); } else { if (_hostPeers.ContainsKey(remote.m_SteamID)) { return; } if (SteamNetworkingSupport.AcceptSession(remote)) { try { UdpClient udpClient = new UdpClient(0, AddressFamily.InterNetwork); udpClient.Connect(IPAddress.Loopback, _hostGamePort); _hostPeers[remote.m_SteamID] = new HostPeerRelay { SteamId = remote, Socket = udpClient }; int port = ((IPEndPoint)udpClient.Client.LocalEndPoint).Port; Plugin.Log?.Info($"[SteamRelay] accepted Steam peer {remote.m_SteamID} -> loopback relay port {port}"); return; } catch (Exception ex) { Plugin.Log?.Warn($"[SteamRelay] failed to open relay socket for {remote.m_SteamID}: {ex.Message}"); return; } } Plugin.Log?.Warn($"[SteamRelay] failed to accept session from {remote.m_SteamID}"); } } public static bool StartJoining(CSteamID hostId, out int localPort, out string error) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) localPort = 0; error = null; if (!SteamNetworkingSupport.IsAvailable) { error = "Steam is not available."; return false; } if (!((CSteamID)(ref hostId)).IsValid()) { error = "That doesn't look like a valid Steam ID."; return false; } StopJoining(); try { _clientSocket = new UdpClient(0, AddressFamily.InterNetwork); localPort = ((IPEndPoint)_clientSocket.Client.LocalEndPoint).Port; _clientHostId = hostId; _clientLiteNetLibEndpoint = null; _clientActive = true; Plugin.Log?.Info($"[SteamRelay] joining {hostId.m_SteamID} — local loopback relay port {localPort}."); return true; } catch (Exception ex) { error = "Could not open a local relay socket (" + ex.GetType().Name + ")."; return false; } } public static void StopJoining() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (_clientActive) { try { _clientSocket?.Close(); } catch { } _clientSocket = null; _clientLiteNetLibEndpoint = null; if (((CSteamID)(ref _clientHostId)).IsValid()) { SteamNetworkingSupport.CloseSession(_clientHostId); } _clientHostId = CSteamID.Nil; _clientActive = false; } } public static void Tick() { if (_hostingActive) { PumpHost(); } if (_clientActive) { PumpClient(); } } private static void PumpHost() { //IL_008b: Unknown result type (might be due to invalid IL or missing references) SteamNetworkingSupport.PumpReceive(64, delegate(CSteamID sender, byte[] bytes) { //IL_0005: 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) if (!_hostPeers.TryGetValue(sender.m_SteamID, out HostPeerRelay value)) { return; } try { value.Socket.Send(bytes, bytes.Length); } catch (Exception ex2) { Plugin.Log?.Info($"[SteamRelay] host->loopback send failed for {sender.m_SteamID}: {ex2.Message}"); } }); foreach (HostPeerRelay value2 in _hostPeers.Values) { while (value2.Socket.Available > 0) { IPEndPoint remoteEP = null; byte[] array; try { array = value2.Socket.Receive(ref remoteEP); } catch (Exception ex) { Plugin.Log?.Info($"[SteamRelay] loopback receive failed for {value2.SteamId.m_SteamID}: {ex.Message}"); break; } SteamNetworkingSupport.SendRaw(value2.SteamId, array, array.Length); } } } private static void PumpClient() { //IL_0063: Unknown result type (might be due to invalid IL or missing references) SteamNetworkingSupport.PumpReceive(64, delegate(CSteamID sender, byte[] bytes) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if (sender.m_SteamID != _clientHostId.m_SteamID || _clientLiteNetLibEndpoint == null) { return; } try { _clientSocket.Send(bytes, bytes.Length, _clientLiteNetLibEndpoint); } catch (Exception ex2) { Plugin.Log?.Info("[SteamRelay] client loopback send failed: " + ex2.Message); } }); while (_clientSocket != null && _clientSocket.Available > 0) { IPEndPoint remoteEP = null; byte[] array; try { array = _clientSocket.Receive(ref remoteEP); } catch (Exception ex) { Plugin.Log?.Info("[SteamRelay] client loopback receive failed: " + ex.Message); break; } _clientLiteNetLibEndpoint = remoteEP; SteamNetworkingSupport.SendRaw(_clientHostId, array, array.Length); } } } internal static class SteamRichPresenceJoin { private const string ConnectKey = "connect"; private static Callback _joinRequestedCallback; private static bool _callbackRegistered; public static CSteamID? PendingInviteHostId { get; private set; } public static string PendingInviteFriendName { get; private set; } public static event Action JoinRequested; public static void ConsumePendingInvite() { PendingInviteHostId = null; PendingInviteFriendName = null; } public static void Initialize() { EnsureCallbackRegistered(); } public static void AdvertiseHosting(CSteamID localId) { if (!SteamNetworkingSupport.IsAvailable) { return; } EnsureCallbackRegistered(); try { string text = localId.m_SteamID.ToString(); SteamFriends.SetRichPresence("connect", text); SteamFriends.ActivateGameOverlayInviteDialogConnectString(text); Plugin.Log?.Info("[SteamRichPresence] hosting advertised — Invite Friends dialog opened."); } catch (Exception ex) { Plugin.Log?.Warn("[SteamRichPresence] AdvertiseHosting failed: " + ex.Message); } } public static void StopAdvertisingHosting() { if (!SteamNetworkingSupport.IsAvailable) { return; } try { SteamFriends.SetRichPresence("connect", ""); } catch (Exception ex) { Plugin.Log?.Info("[SteamRichPresence] StopAdvertisingHosting failed: " + ex.Message); } } private static void EnsureCallbackRegistered() { if (_callbackRegistered || !SteamNetworkingSupport.IsAvailable) { return; } _callbackRegistered = true; try { _joinRequestedCallback = Callback.Create((DispatchDelegate)OnJoinRequested); } catch (Exception ex) { _callbackRegistered = false; Plugin.Log?.Warn("[SteamRichPresence] callback registration failed: " + ex.Message); } } private static void OnJoinRequested(GameRichPresenceJoinRequested_t evt) { //IL_0044: 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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) string rgchConnect = ((GameRichPresenceJoinRequested_t)(ref evt)).m_rgchConnect; if (string.IsNullOrWhiteSpace(rgchConnect) || !ulong.TryParse(rgchConnect, out var result)) { Plugin.Log?.Info("[SteamRichPresence] ignored join request with unparseable connect string '" + rgchConnect + "'."); return; } CSteamID val = default(CSteamID); ((CSteamID)(ref val))..ctor(result); string text = null; try { text = SteamFriends.GetFriendPersonaName(evt.m_steamIDFriend); } catch { } Plugin.Log?.Info(string.Format("[SteamRichPresence] join requested — host={0}, friend={1}", result, text ?? "?")); PendingInviteHostId = val; PendingInviteFriendName = text; try { SteamRichPresenceJoin.JoinRequested?.Invoke(val, text); } catch (Exception ex) { Plugin.Log?.Warn("[SteamRichPresence] JoinRequested handler threw: " + ex.Message); } } } } namespace SULFURTogether.Networking.Gameplay { internal static class ArenaBarrierManager { private static readonly Dictionary> _barriers = new Dictionary>(); private const float MatchEpsilon = 1f; private const float Margin = 0.6f; private const float MinThickness = 1.5f; private const float FallbackSize = 6f; private static Type _triggerType; private static bool _typeResolved; private static FieldInfo _eventField; private static bool LogOn { get { try { return Plugin.Cfg.LogArenaLockdown.Value; } catch { return false; } } } public static void Seal(Vector3 arenaPos) { //IL_0000: 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_002f: 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) try { string text = Key(arenaPos); if (_barriers.ContainsKey(text)) { return; } object obj = FindMatchTrigger(arenaPos); if (obj == null) { if (LogOn) { NetLogger.Info($"[ArenaLockdown] SEAL no seal trigger near {arenaPos} — cannot place barrier"); } return; } List<(Transform, Bounds, bool, int)> list = ResolveDoorAnchors(obj); if (list.Count == 0) { if (LogOn) { NetLogger.Info($"[ArenaLockdown] SEAL trigger has no door anchor near {arenaPos}"); } return; } List list2 = new List(); foreach (var item in list) { GameObject val = BuildBarrier(item); if ((Object)(object)val != (Object)null) { list2.Add(val); } } if (list2.Count > 0) { _barriers[text] = list2; } if (LogOn) { NetLogger.Info($"[ArenaLockdown] SEAL arena={text} placed {list2.Count} barrier(s) at {list.Count} door anchor(s)"); } } catch (Exception ex) { NetLogger.Warn("[ArenaLockdown] Seal failed: " + ex.Message); } } public static void Unseal(Vector3 arenaPos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) UnsealKey(Key(arenaPos)); } private static void UnsealKey(string key) { try { if (!_barriers.TryGetValue(key, out List value)) { return; } _barriers.Remove(key); foreach (GameObject item in value) { if ((Object)(object)item != (Object)null) { Object.Destroy((Object)(object)item); } } if (LogOn) { NetLogger.Info($"[ArenaLockdown] UNSEAL arena={key} removed {value.Count} barrier(s)"); } } catch (Exception ex) { NetLogger.Warn("[ArenaLockdown] Unseal failed: " + ex.Message); } } public static bool IsSealed(Vector3 arenaPos) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) return _barriers.ContainsKey(Key(arenaPos)); } public static int CloseArenaDoorsLocal(Vector3 arenaPos) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) int num = 0; try { object obj = FindMatchTrigger(arenaPos); if (obj == null) { return 0; } if (_eventField == null) { _eventField = obj.GetType().GetField("onTriggerEvents", BindingFlags.Instance | BindingFlags.Public); } object? obj2 = _eventField?.GetValue(obj); UnityEventBase val = (UnityEventBase)((obj2 is UnityEventBase) ? obj2 : null); if (val == null) { return 0; } int persistentEventCount = val.GetPersistentEventCount(); for (int i = 0; i < persistentEventCount; i++) { string persistentMethodName = val.GetPersistentMethodName(i); Object persistentTarget = val.GetPersistentTarget(i); if (persistentTarget == (Object)null) { continue; } if (string.Equals(persistentMethodName, "Close", StringComparison.Ordinal) && ((object)persistentTarget).GetType().Name.IndexOf("MetalGate", StringComparison.Ordinal) >= 0) { if (GateSyncManager.CloseGate(persistentTarget)) { num++; } } else { if (!string.Equals(persistentMethodName, "SetActive", StringComparison.Ordinal)) { continue; } object obj3 = ((persistentTarget is GameObject) ? persistentTarget : null); if (obj3 == null) { Object obj4 = ((persistentTarget is Component) ? persistentTarget : null); obj3 = ((obj4 != null) ? ((Component)obj4).gameObject : null); } GameObject val2 = (GameObject)obj3; if ((Object)(object)val2 != (Object)null && ((Object)val2).name.IndexOf("door", StringComparison.OrdinalIgnoreCase) >= 0) { if (!val2.activeSelf) { val2.SetActive(true); } num++; } } } } catch (Exception ex) { NetLogger.Warn("[ArenaLockdown] CloseArenaDoorsLocal failed: " + ex.Message); } return num; } public static void Clear() { foreach (KeyValuePair> barrier in _barriers) { foreach (GameObject item in barrier.Value) { if ((Object)(object)item != (Object)null) { Object.Destroy((Object)(object)item); } } } _barriers.Clear(); } public static List ResolveMetalGateIds(object trigger) { List list = new List(); try { if (trigger == null) { return list; } if (_eventField == null) { _eventField = trigger.GetType().GetField("onTriggerEvents", BindingFlags.Instance | BindingFlags.Public); } object? obj = _eventField?.GetValue(trigger); UnityEventBase val = (UnityEventBase)((obj is UnityEventBase) ? obj : null); if (val == null) { return list; } int persistentEventCount = val.GetPersistentEventCount(); for (int i = 0; i < persistentEventCount; i++) { string persistentMethodName = val.GetPersistentMethodName(i); Object persistentTarget = val.GetPersistentTarget(i); if (!(persistentTarget == (Object)null) && (string.Equals(persistentMethodName, "Close", StringComparison.Ordinal) || string.Equals(persistentMethodName, "Open", StringComparison.Ordinal)) && ((object)persistentTarget).GetType().Name.IndexOf("MetalGate", StringComparison.Ordinal) >= 0) { list.Add(persistentTarget.GetInstanceID()); } } } catch { } return list; } public static List ResolveMetalGateIdsFromArena(Vector3 arenaPos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return ResolveMetalGateIds(FindMatchTrigger(arenaPos)); } private static GameObject BuildBarrier((Transform t, Bounds bounds, bool hasBounds, int layer) anchor) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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_0044: 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_0055: 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_0090: 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_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: 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_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: 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_00fb: Expected O, but got Unknown //IL_0067: Unknown result type (might be due to invalid IL or missing references) Vector3 position; Vector3 val = default(Vector3); if (anchor.hasBounds) { position = ((Bounds)(ref anchor.bounds)).center; val = ((Bounds)(ref anchor.bounds)).size + new Vector3(0.6f, 0.6f, 0.6f) * 2f; } else { position = (((Object)(object)anchor.t != (Object)null) ? anchor.t.position : Vector3.zero); ((Vector3)(ref val))..ctor(6f, 6f, 6f); } ((Vector3)(ref val))..ctor(Mathf.Max(val.x, 1.5f), Mathf.Max(val.y, 1.5f), Mathf.Max(val.z, 1.5f)); GameObject val2 = new GameObject("LD2_ArenaBarrier"); val2.transform.position = position; val2.transform.rotation = Quaternion.identity; val2.layer = anchor.layer; BoxCollider obj = val2.AddComponent(); obj.size = val; ((Collider)obj).isTrigger = false; return val2; } private static List<(Transform t, Bounds bounds, bool hasBounds, int layer)> ResolveDoorAnchors(object trigger) { List<(Transform, Bounds, bool, int)> list = new List<(Transform, Bounds, bool, int)>(); try { if (_eventField == null) { _eventField = trigger.GetType().GetField("onTriggerEvents", BindingFlags.Instance | BindingFlags.Public); } object? obj = _eventField?.GetValue(trigger); UnityEventBase val = (UnityEventBase)((obj is UnityEventBase) ? obj : null); if (val == null) { return list; } int persistentEventCount = val.GetPersistentEventCount(); for (int i = 0; i < persistentEventCount; i++) { string persistentMethodName = val.GetPersistentMethodName(i); Object persistentTarget = val.GetPersistentTarget(i); if (persistentTarget == (Object)null) { continue; } if (string.Equals(persistentMethodName, "Close", StringComparison.Ordinal) && ((object)persistentTarget).GetType().Name.IndexOf("MetalGate", StringComparison.Ordinal) >= 0) { Object obj2 = ((persistentTarget is Component) ? persistentTarget : null); GameObject val2 = ((obj2 != null) ? ((Component)obj2).gameObject : null); if ((Object)(object)val2 != (Object)null) { AddAnchor(list, val2); } } else if (string.Equals(persistentMethodName, "SetActive", StringComparison.Ordinal)) { object obj3 = ((persistentTarget is GameObject) ? persistentTarget : null); if (obj3 == null) { Object obj4 = ((persistentTarget is Component) ? persistentTarget : null); obj3 = ((obj4 != null) ? ((Component)obj4).gameObject : null); } GameObject val3 = (GameObject)obj3; if ((Object)(object)val3 != (Object)null && ((Object)val3).name.IndexOf("door", StringComparison.OrdinalIgnoreCase) >= 0) { AddAnchor(list, val3); } } } } catch { } return list; } private static void AddAnchor(List<(Transform, Bounds, bool, int)> list, GameObject door) { //IL_0002: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) Bounds item = default(Bounds); bool flag = false; try { Renderer[] componentsInChildren = door.GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { if (!flag) { item = val.bounds; flag = true; } else { ((Bounds)(ref item)).Encapsulate(val.bounds); } } } } catch { } list.Add((door.transform, item, flag, door.layer)); } private static object FindMatchTrigger(Vector3 key) { //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) //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) try { Type type = ResolveTriggerType(); if (type == null) { return null; } Object[] array = Object.FindObjectsOfType(type); Component result = null; float num = 1f; Object[] array2 = array; foreach (Object obj in array2) { Component val = (Component)(object)((obj is Component) ? obj : null); if (val != null && !((Object)(object)val == (Object)null)) { Vector3 val2 = val.transform.position - key; float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude; if (sqrMagnitude <= num) { num = sqrMagnitude; result = val; } } } return result; } catch { return null; } } private static Type ResolveTriggerType() { if (!_typeResolved) { _typeResolved = true; _triggerType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.World.PlayerTrigger") ?? AccessTools.TypeByName("PerfectRandom.Sulfur.Core.PlayerTrigger"); } return _triggerType; } private static string Key(Vector3 p) { //IL_0005: 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_0025: Unknown result type (might be due to invalid IL or missing references) return $"{Mathf.RoundToInt(p.x)}_{Mathf.RoundToInt(p.y)}_{Mathf.RoundToInt(p.z)}"; } } internal sealed class ArenaDoorwaySensor : MonoBehaviour { public string ArenaKey; public Vector3 ArenaPos; private int _overlap; private float _lastToggleTime; private const float DebounceSeconds = 0.5f; private void OnTriggerEnter(Collider other) { if (IsLocalPlayer(other)) { _overlap++; } } private void OnTriggerExit(Collider other) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) if (!IsLocalPlayer(other)) { return; } _overlap--; if (_overlap <= 0) { _overlap = 0; float unscaledTime = Time.unscaledTime; if (!(unscaledTime - _lastToggleTime < 0.5f)) { _lastToggleTime = unscaledTime; ArenaLockdownManager.OnLocalDoorwayTraversed(ArenaKey, ArenaPos); } } } private static bool IsLocalPlayer(Collider other) { if ((Object)(object)other == (Object)null) { return false; } Transform val = ArenaLockdownManager.LocalPlayerRoot(); if ((Object)(object)val == (Object)null) { return false; } Transform transform = ((Component)other).transform; if (!((Object)(object)transform.root == (Object)(object)val) && !((Object)(object)transform == (Object)(object)val)) { return transform.IsChildOf(val); } return true; } } internal static class ArenaLockdownManager { private sealed class Lockdown { public Vector3 Pos; public float T0; public string Chapter = ""; public int Level = -1; public bool HasSeed; public int Seed; public readonly HashSet InRoom = new HashSet(); public bool SealFired; public bool TeleportFired; public bool Released; } private sealed class SandstormArena { public Vector3 Pos; public float T0; public string Chapter = ""; public int Level = -1; public bool HasSeed; public int Seed; public bool PullFired; } private static readonly Dictionary _locks = new Dictionary(); private static readonly Dictionary _sandstormArenas = new Dictionary(); private static readonly Dictionary> _gracedGates = new Dictionary>(); private static readonly Dictionary> _heldGates = new Dictionary>(); private static float _legitGateOpenUntil = -999f; private const float LegitGateOpenWindowSeconds = 2f; private static readonly HashSet _localCrossed = new HashSet(); private static readonly Dictionary _doorwayCrossings = new Dictionary(); private static string _activeArenaKey = ""; private static readonly HashSet _clientArenaMembers = new HashSet(); private static string _clientArenaKey = ""; private const float SealDelaySeconds = 5f; private const float TeleportDelaySeconds = 10f; private const float SandstormPullDelaySeconds = 3f; private const float SandstormArenaRadius = 20f; private const float SandstormPullBesideOffset = 3f; private const float GateEntryAnchorRadius = 25f; private const float GateReleaseRadius = 10f; private const float GraceGateRadius = 12f; private static FieldInfo _eventField; private static bool _eventFieldResolved; private static bool _armed; private static Vector3 _armedArena; public static Action ShowPrompt; public static Action HidePrompt; public static Action ShowToast; private const float DownedRescueSeconds = 5f; private static float _downedOutsideSince; private static bool Enabled { get { try { return Plugin.Cfg.EnableArenaLockdown.Value; } catch { return false; } } } private static bool LogOn { get { try { return Plugin.Cfg.LogArenaLockdown.Value; } catch { return false; } } } private static bool GraceEnabled { get { try { return Plugin.Cfg.EnableArenaGracePeriod.Value; } catch { return false; } } } public static void BeginLocalGraceIfSeal(object trigger) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) try { if (!Enabled || !GraceEnabled || !NetGameplaySyncBridge.IsSessionActive || !IsSealTrigger(trigger, out var pos)) { return; } string text = Key(pos); if (!_gracedGates.ContainsKey(text)) { List list = ArenaBarrierManager.ResolveMetalGateIds(trigger); _gracedGates[text] = new HashSet(list); if (LogOn) { NetLogger.Info(string.Format("[ArenaLockdown] grace begin arena={0} gates=[{1}] (held open ~{2:0}s)", text, string.Join(",", list), 5f)); } } } catch (Exception ex) { NetLogger.Warn("[ArenaLockdown] BeginLocalGrace failed: " + ex.Message); } } public static bool IsGateGraced(int gateId) { foreach (KeyValuePair> gracedGate in _gracedGates) { if (gracedGate.Value.Contains(gateId)) { return true; } } return false; } private static void EndLocalGrace(Vector3 arenaPos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) string text = Key(arenaPos); if (_gracedGates.Remove(text) && LogOn) { NetLogger.Info("[ArenaLockdown] grace end arena=" + text); } } public static bool IsGateHeld(int gateId) { foreach (KeyValuePair> heldGate in _heldGates) { if (heldGate.Value.Contains(gateId)) { return true; } } return false; } public static bool IsLegitGateOpenWindow() { return Time.unscaledTime <= _legitGateOpenUntil; } public static void NotifyAllEnemiesDead() { _legitGateOpenUntil = Time.unscaledTime + 2f; if (LogOn) { NetLogger.Info($"[ArenaLockdown] all-enemies-dead → legit gate-open window ({2f:0}s)"); } } public static void NotifyBossDead() { _legitGateOpenUntil = Time.unscaledTime + 2f; if (LogOn) { NetLogger.Info($"[ArenaLockdown] boss dead → legit gate-open window ({2f:0}s)"); } } public static void OnLocalTriggerFired(object trigger) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) try { if (Enabled && NetGameplaySyncBridge.IsSessionActive && IsSealTrigger(trigger, out var pos)) { if (LogOn) { NetLogger.Info($"[ArenaLockdown] local crossed seal trigger arena=({pos.x:0.0},{pos.y:0.0},{pos.z:0.0})"); } _localCrossed.Add(Key(pos)); ReportLocalInRoom(pos); } } catch (Exception ex) { NetLogger.Warn("[ArenaLockdown] OnLocalTriggerFired failed: " + ex.Message); } } private static void ReportLocalInRoom(Vector3 pos) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (NetGameplaySyncBridge.TryGetLocalScene(out string chapter, out int level, out bool hasSeed, out int seed)) { if (NetGameplaySyncBridge.IsHost) { ReportEntry(NetGameplaySyncBridge.LocalPeerId, pos, chapter, level, hasSeed, seed); return; } NetGameplaySyncBridge.SendClientArenaEnter(new NetClientArenaEnter { ArenaPos = pos, ChapterName = chapter, LevelIndex = level, HasLevelSeed = hasSeed, LevelSeed = seed }); } } public static void HandleClientArenaEnter(NetClientArenaEnter m, string peerId) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (Enabled && NetGameplaySyncBridge.IsHost && m != null) { ReportEntry(peerId, m.ArenaPos, m.ChapterName, m.LevelIndex, m.HasLevelSeed, m.LevelSeed); } } private static void ReportEntry(string peerId, Vector3 pos, string chap, int lvl, bool hasSeed, int seed) { //IL_0008: 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_002a: Unknown result type (might be due to invalid IL or missing references) if (NetGameplaySyncBridge.IsHost) { string text = Key(pos); bool flag = false; if (!_locks.TryGetValue(text, out Lockdown value)) { value = new Lockdown { Pos = pos, T0 = Time.unscaledTime, Chapter = chap, Level = lvl, HasSeed = hasSeed, Seed = seed }; _locks[text] = value; flag = true; NetLogger.Info(string.Format("[ArenaLockdown] START arena={0} level={1}:{2} seed={3} t0 by {4}", text, chap, lvl, hasSeed ? seed.ToString() : "?", peerId)); } if (value.InRoom.Add(peerId)) { NetLogger.Info("[ArenaLockdown] in-room += " + peerId + " arena=" + text + " members=[" + string.Join(",", value.InRoom) + "]"); _activeArenaKey = text; BroadcastArenaMembership(value); } if (flag) { IssueCommand(value, ArenaCommandKind.NotifyEntered); IssueCommand(value, ArenaCommandKind.Notify); } } } public static void Tick() { if (Enabled && NetGameplaySyncBridge.IsSessionActive) { if (NetGameplaySyncBridge.IsHost) { HostTick(); } LocalTick(); TickSandstormDownedRescue(); } } private static void TickSandstormDownedRescue() { //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) //IL_00dd: 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) try { if (!NetBossEncounterManager.TryGetActiveSandstormArenaSphere(out var center, out var radius) || radius <= 0f) { _downedOutsideSince = 0f; return; } if (!NetPlayerLifeManager.ShouldSuppressLocalPlayerControls()) { _downedOutsideSince = 0f; return; } object obj = ResolveLocalPlayerUnit(); Component val = (Component)((obj is Component) ? obj : null); if (val == null || (Object)(object)val == (Object)null) { _downedOutsideSince = 0f; return; } float num = Vector3.Distance(val.transform.position, center); if (num <= radius) { _downedOutsideSince = 0f; return; } float unscaledTime = Time.unscaledTime; if (_downedOutsideSince <= 0f) { _downedOutsideSince = unscaledTime; } else if (!(unscaledTime - _downedOutsideSince < 5f)) { _downedOutsideSince = 0f; NetLogger.Info($"[ArenaLockdown] downed rescue: local player downed OUTSIDE the sandstorm for {5f:0}s (dist={num:0.0}m r={radius:0.0}) — pulling in"); ApplyLocalCommand(ArenaCommandKind.PullIn, ResolveNearFirstPlayerTarget(center)); } } catch { _downedOutsideSince = 0f; } } public static void BeginSandstormArena(Vector3 center, string chapter, int level, bool hasSeed, int seed) { //IL_001a: 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_0040: Unknown result type (might be due to invalid IL or missing references) try { if (Enabled && NetGameplaySyncBridge.IsHost && NetGameplaySyncBridge.IsSessionActive) { string text = Key(center); if (!_sandstormArenas.ContainsKey(text)) { _sandstormArenas[text] = new SandstormArena { Pos = center, T0 = Time.unscaledTime, Chapter = chapter, Level = level, HasSeed = hasSeed, Seed = seed }; NetLogger.Info($"[ArenaLockdown] sandstorm arena START arena={text} level={chapter}:{level} pull in {3f:0}s"); } } } catch (Exception ex) { NetLogger.Warn("[ArenaLockdown] BeginSandstormArena failed: " + ex.Message); } } private static void HostTickSandstorm() { if (_sandstormArenas.Count == 0) { return; } float unscaledTime = Time.unscaledTime; foreach (KeyValuePair sandstormArena in _sandstormArenas) { SandstormArena value = sandstormArena.Value; if (!value.PullFired && unscaledTime - value.T0 >= 3f) { value.PullFired = true; IssueSandstormPull(value); } } } private static void IssueSandstormPull(SandstormArena sa) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) List peerIdsInLevel = NetGameplaySyncBridge.GetPeerIdsInLevel(sa.Chapter, sa.Level, sa.HasSeed, sa.Seed); if (peerIdsInLevel.Count == 0) { if (LogOn) { NetLogger.Info("[ArenaLockdown] PullIn arena=" + Key(sa.Pos) + " — no targets, skip"); } return; } Vector3 val = ResolveNearFirstPlayerTarget(sa.Pos); NetLogger.Info(string.Format("[ArenaLockdown] PullIn arena={0} target={1:F0} targets=[{2}] (out-of-arena players teleport in)", Key(sa.Pos), val, string.Join(",", peerIdsInLevel))); if (peerIdsInLevel.Contains(NetGameplaySyncBridge.LocalPeerId)) { ApplyLocalCommand(ArenaCommandKind.PullIn, val); } NetGameplaySyncBridge.BroadcastArenaCommand(new NetArenaCommand { Kind = ArenaCommandKind.PullIn, ArenaPos = val, TargetPeerIds = peerIdsInLevel }); } private static Vector3 ResolveNearFirstPlayerTarget(Vector3 fallbackCenter) { //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_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_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) Vector3 center = fallbackCenter; float radius = 20f; if (NetBossEncounterManager.TryGetSandstormArenaSphere(out var center2, out var radius2) && radius2 > 0f) { center = center2; radius = radius2; } bool foundAnchor; return ResolveArenaEntryTarget(center, radius, includeLocal: true, out foundAnchor); } private static Vector3 ResolveArenaEntryTarget(Vector3 center, float radius, bool includeLocal, out bool foundAnchor) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_008b: 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_00c4: 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_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_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) Vector3 anchor = Vector3.zero; bool found = false; float bestSqr = float.MaxValue; float r2 = radius * radius; if (includeLocal) { try { object obj = ResolveLocalPlayerUnit(); Component val = (Component)((obj is Component) ? obj : null); if (val != null && (Object)(object)val != (Object)null) { Consider(val.transform.position); } } catch { } } try { NetGameplaySyncBridge.ForEachRemotePlayerPositionWithPeer(delegate(string peer, Vector3 pos) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) Consider(pos); }); } catch { } foundAnchor = found; if (!found) { return center; } Vector3 val2 = anchor - center; val2.y = 0f; if (((Vector3)(ref val2)).sqrMagnitude < 0.01f) { val2 = Vector3.forward; } return anchor + ((Vector3)(ref val2)).normalized * 3f; void Consider(Vector3 p) { //IL_0000: 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_000c: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) Vector3 val3 = p - center; float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude; if (sqrMagnitude <= r2 && sqrMagnitude < bestSqr) { bestSqr = sqrMagnitude; anchor = p; found = true; } } } private static Vector3 ResolveGateTeleportDest(Vector3 arenaPos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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_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_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_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_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_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_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_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) bool foundAnchor; Vector3 val = ResolveArenaEntryTarget(arenaPos, 25f, includeLocal: false, out foundAnchor); if (foundAnchor) { return val + Vector3.up * 0.5f; } RaycastHit val2 = default(RaycastHit); if (Physics.Raycast(arenaPos + Vector3.up * 30f, Vector3.down, ref val2, 60f, -5, (QueryTriggerInteraction)1)) { return ((RaycastHit)(ref val2)).point + Vector3.up * 0.1f; } return arenaPos + Vector3.up * 0.5f; } private static void HostTick() { HostTickSandstorm(); if (_locks.Count == 0) { return; } float unscaledTime = Time.unscaledTime; foreach (KeyValuePair @lock in _locks) { Lockdown value = @lock.Value; if (value.Released) { continue; } float num = unscaledTime - value.T0; if (!value.SealFired && num >= 5f) { value.SealFired = true; if (GraceEnabled) { IssueCommand(value, ArenaCommandKind.CloseDoor); } IssueCommand(value, ArenaCommandKind.Seal); } if (!value.TeleportFired && num >= 10f) { value.TeleportFired = true; IssueCommand(value, ArenaCommandKind.Popup); } } } private static void IssueCommand(Lockdown ld, ArenaCommandKind kind) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) List list; switch (kind) { case ArenaCommandKind.Seal: case ArenaCommandKind.Popup: case ArenaCommandKind.Release: case ArenaCommandKind.CloseDoor: list = ComputeAllInLevel(ld); break; case ArenaCommandKind.NotifyEntered: list = ld.InRoom.ToList(); break; default: list = ComputeNonInRoom(ld); break; } if (list.Count == 0) { if (LogOn) { NetLogger.Info($"[ArenaLockdown] {kind} arena={Key(ld.Pos)} — no targets, skip"); } return; } NetLogger.Info(string.Format("[ArenaLockdown] {0} arena={1} inRoom=[{2}] targets=[{3}]", kind, Key(ld.Pos), string.Join(",", ld.InRoom), string.Join(",", list))); if (list.Contains(NetGameplaySyncBridge.LocalPeerId)) { ApplyLocalCommand(kind, ld.Pos); } NetGameplaySyncBridge.BroadcastArenaCommand(new NetArenaCommand { Kind = kind, ArenaPos = ld.Pos, TargetPeerIds = list }); } public static void HandleArenaCommand(NetArenaCommand m, string localPeerId) { //IL_0018: 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_00ed: Unknown result type (might be due to invalid IL or missing references) if (!Enabled || m == null) { return; } if (m.Kind == ArenaCommandKind.Membership) { _clientArenaKey = Key(m.ArenaPos); _clientArenaMembers.Clear(); if (m.TargetPeerIds != null) { foreach (string targetPeerId in m.TargetPeerIds) { _clientArenaMembers.Add(targetPeerId); } } if (LogOn) { NetLogger.Info("[ArenaLockdown] client received membership arena=" + _clientArenaKey + " members=[" + string.Join(",", _clientArenaMembers) + "]"); } } else { if (m.Kind == ArenaCommandKind.PullIn) { bool flag = m.TargetPeerIds != null && m.TargetPeerIds.Contains(localPeerId); NetLogger.Info(string.Format("[ArenaLockdown] client received PullIn arena={0} me={1} targets=[{2}] targeted={3}", Key(m.ArenaPos), localPeerId, (m.TargetPeerIds == null) ? "" : string.Join(",", m.TargetPeerIds), flag)); } if (m.TargetPeerIds != null && m.TargetPeerIds.Contains(localPeerId)) { ApplyLocalCommand(m.Kind, m.ArenaPos); } } } private static void BroadcastArenaMembership(Lockdown ld) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) try { NetGameplaySyncBridge.BroadcastArenaCommand(new NetArenaCommand { Kind = ArenaCommandKind.Membership, ArenaPos = ld.Pos, TargetPeerIds = ld.InRoom.ToList() }); } catch (Exception ex) { NetLogger.Warn("[ArenaLockdown] BroadcastArenaMembership failed: " + ex.Message); } } public static bool TryGetActiveArenaInRoom(out HashSet members) { members = null; try { if (!Enabled) { return false; } if (NetGameplaySyncBridge.IsHost) { if (!string.IsNullOrEmpty(_activeArenaKey) && _locks.TryGetValue(_activeArenaKey, out Lockdown value) && !value.Released && value.InRoom.Count > 0) { members = value.InRoom; return true; } Lockdown lockdown = null; foreach (KeyValuePair @lock in _locks) { Lockdown value2 = @lock.Value; if (!value2.Released && value2.InRoom.Count != 0 && (lockdown == null || value2.T0 > lockdown.T0)) { lockdown = value2; } } if (lockdown != null) { members = lockdown.InRoom; return true; } return false; } if (_clientArenaMembers.Count > 0) { members = _clientArenaMembers; return true; } return false; } catch { return false; } } public static bool IsLocalPlayerInActiveArena() { try { if (!TryGetActiveArenaInRoom(out HashSet members) || members == null) { return true; } return members.Contains(NetGameplaySyncBridge.LocalPeerId); } catch { return true; } } private static void ApplyLocalCommand(ArenaCommandKind kind, Vector3 arenaPos) { //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_0144: 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_0240: Unknown result type (might be due to invalid IL or missing references) //IL_024c: 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_0083: 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_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_02dd: Unknown result type (might be due to invalid IL or missing references) //IL_02d8: Unknown result type (might be due to invalid IL or missing references) //IL_02f3: Unknown result type (might be due to invalid IL or missing references) //IL_02f4: Unknown result type (might be due to invalid IL or missing references) //IL_02fd: Unknown result type (might be due to invalid IL or missing references) //IL_02ff: Unknown result type (might be due to invalid IL or missing references) //IL_0337: Unknown result type (might be due to invalid IL or missing references) //IL_0338: Unknown result type (might be due to invalid IL or missing references) //IL_0342: Unknown result type (might be due to invalid IL or missing references) //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_034c: Unknown result type (might be due to invalid IL or missing references) //IL_036f: Unknown result type (might be due to invalid IL or missing references) //IL_0379: Unknown result type (might be due to invalid IL or missing references) //IL_038d: Unknown result type (might be due to invalid IL or missing references) //IL_0321: Unknown result type (might be due to invalid IL or missing references) try { switch (kind) { case ArenaCommandKind.Notify: Toast(CoopLoc.Get("arena.toast.title", "Arena Lockdown"), CoopLoc.Get("arena.toast.teammateEntered", "A teammate entered the arena — head in now to join them!")); break; case ArenaCommandKind.NotifyEntered: Toast(CoopLoc.Get("arena.toast.title", "Arena Lockdown"), CoopLoc.Get("arena.toast.youEntered", "You entered the arena — the gate seals in a few seconds; teammates can still run in.")); break; case ArenaCommandKind.CloseDoor: { EndLocalGrace(arenaPos); int num2 = ArenaBarrierManager.CloseArenaDoorsLocal(arenaPos); List list = ArenaBarrierManager.ResolveMetalGateIdsFromArena(arenaPos); if (list.Count > 0) { _heldGates[Key(arenaPos)] = new HashSet(list); } if (LogOn) { NetLogger.Info(string.Format("[ArenaLockdown] CloseDoor arena={0} closedDoors={1} heldGates=[{2}] (door now held closed)", Key(arenaPos), num2, string.Join(",", list))); } break; } case ArenaCommandKind.Seal: if (IsEffectivelyInArena(arenaPos)) { if (LogOn) { NetLogger.Info("[ArenaLockdown] Seal arena=" + Key(arenaPos) + " local player inside — no barrier"); } } else { ArenaBarrierManager.Seal(arenaPos); Toast(CoopLoc.Get("arena.toast.title", "Arena Lockdown"), CoopLoc.Get("arena.toast.sealedOut", "You've been sealed out — you'll be brought in shortly.")); } break; case ArenaCommandKind.Popup: { if (IsEffectivelyInArena(arenaPos)) { if (LogOn) { NetLogger.Info("[ArenaLockdown] Popup arena=" + Key(arenaPos) + " local player inside — no popup"); } break; } if (!ArenaBarrierManager.IsSealed(arenaPos)) { ArenaBarrierManager.Seal(arenaPos); } _armed = true; _armedArena = arenaPos; string item = "?"; try { item = ((object)Plugin.Cfg.ArenaEnterConfirmKey.Value/*cast due to .constrained prefix*/).ToString(); } catch { } string text = CoopLoc.Format("arena.enterPrompt", "Press [{key}] to enter the arena", ("key", item)); if (ShowPrompt != null) { try { ShowPrompt(text); break; } catch (Exception ex) { NetLogger.Warn("[ArenaLockdown] ShowPrompt failed: " + ex.Message); break; } } NetLogger.Info("[ArenaLockdown] POPUP armed teleport (UI deferred to Native UI Lib) — " + text); break; } case ArenaCommandKind.Release: _heldGates.Remove(Key(arenaPos)); _gracedGates.Remove(Key(arenaPos)); if (!IsEffectivelyInArena(arenaPos)) { if (LogOn) { NetLogger.Info($"[ArenaLockdown] RELEASE arena=({arenaPos.x:0.0},{arenaPos.y:0.0},{arenaPos.z:0.0}) — fight over, entering"); } TeleportIntoArena(arenaPos); } break; case ArenaCommandKind.PullIn: { object obj = ResolveLocalPlayerUnit(); if (obj == null) { NetLogger.Info("[ArenaLockdown] PullIn — local player unit MISSING, cannot teleport"); break; } Component val = (Component)((obj is Component) ? obj : null); Vector3 val2 = ((val != null && (Object)(object)val != (Object)null) ? val.transform.position : Vector3.positiveInfinity); if (!NetBossEncounterManager.TryGetSandstormArenaSphere(out var center, out var radius) || radius <= 0f) { center = arenaPos; radius = 20f; } float num = Vector3.Distance(val2, center); if (num <= radius) { NetLogger.Info($"[ArenaLockdown] PullIn local player INSIDE sphere (dist={num:0.0}m r={radius:0.0} ppos={val2:F0}) — no teleport"); break; } Vector3 val3 = arenaPos + Vector3.up * 0.5f; NetLogger.Info($"[ArenaLockdown] PullIn local player OUTSIDE sphere (dist={num:0.0}m r={radius:0.0} ppos={val2:F0}) — teleporting to {val3:F0} (near first player)"); MoveLocalPlayerTo(obj, val3); Toast(CoopLoc.Get("arena.sandstorm.title", "Sandstorm Arena"), CoopLoc.Get("arena.sandstorm.pulledIn", "Pulled into the arena — the sandstorm outside would grind you down.")); try { NetBossEncounterManager.OnLocalTeleportedIntoArena(); break; } catch { break; } } case ArenaCommandKind.Membership: break; } } catch (Exception ex2) { NetLogger.Warn($"[ArenaLockdown] ApplyLocalCommand({kind}) failed: {ex2.Message}"); } } private static void Toast(string title, string message) { if (ShowToast != null) { try { ShowToast(title, message); return; } catch (Exception ex) { NetLogger.Warn("[ArenaLockdown] ShowToast failed: " + ex.Message); } } if (LogOn) { NetLogger.Info("[ArenaLockdown] toast (UI deferred): " + title + " — " + message); } } private static void LocalTick() { //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_0022: Unknown result type (might be due to invalid IL or missing references) if (!_armed) { return; } try { KeyboardShortcut value = Plugin.Cfg.ArenaEnterConfirmKey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { TeleportIntoArena(_armedArena); } } catch { } } private static void TeleportIntoArena(Vector3 arenaPos) { //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_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: 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) try { object obj = ResolveLocalPlayerUnit(); if (obj != null) { Vector3 val = ResolveGateTeleportDest(arenaPos); MethodInfo methodInfo = AccessTools.Method(obj.GetType(), "TeleportTo", new Type[1] { typeof(Vector3) }, (Type[])null) ?? AccessTools.Method(obj.GetType(), "TeleportTo", (Type[])null, (Type[])null); if (methodInfo != null) { methodInfo.Invoke(obj, new object[1] { val }); } else { Component val2 = (Component)((obj is Component) ? obj : null); if (val2 != null && (Object)(object)val2 != (Object)null) { val2.transform.position = val; } } NetLogger.Info($"[ArenaLockdown] teleported local player into arena arena=({arenaPos.x:0.0},{arenaPos.y:0.0},{arenaPos.z:0.0}) dest={val:F1}"); Toast(CoopLoc.Get("arena.entering.title", "Arena"), CoopLoc.Get("arena.entering.msg", "Entering the arena.")); } else { NetLogger.Warn("[ArenaLockdown] teleport: local player unit missing"); } } catch (Exception ex) { NetLogger.Warn("[ArenaLockdown] teleport failed: " + ex.Message); } ArenaBarrierManager.Unseal(arenaPos); if (_armed && Key(_armedArena) == Key(arenaPos)) { _armed = false; if (HidePrompt != null) { try { HidePrompt(); } catch { } } } string text = Key(arenaPos); _localCrossed.Add(text); _doorwayCrossings.TryGetValue(text, out var value); if (value % 2 == 0) { _doorwayCrossings[text] = value + 1; } ReportLocalInRoom(arenaPos); try { NetBossEncounterManager.OnLocalTeleportedIntoArena(); } catch { } } public static void OnGateOpened(Vector3 gatePos) { //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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) if (!Enabled || !NetGameplaySyncBridge.IsHost || _locks.Count == 0) { return; } try { Lockdown lockdown = null; float num = 100f; foreach (KeyValuePair @lock in _locks) { if (!@lock.Value.Released) { Vector3 val = @lock.Value.Pos - gatePos; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude <= num) { num = sqrMagnitude; lockdown = @lock.Value; } } } if (lockdown != null) { lockdown.Released = true; NetLogger.Info("[ArenaLockdown] gate-open near arena=" + Key(lockdown.Pos) + " → RELEASE"); IssueCommand(lockdown, ArenaCommandKind.Release); } } catch (Exception ex) { NetLogger.Warn("[ArenaLockdown] OnGateOpened failed: " + ex.Message); } } private static List ComputeNonInRoom(Lockdown ld) { return (from id in NetGameplaySyncBridge.GetPeerIdsInLevel(ld.Chapter, ld.Level, ld.HasSeed, ld.Seed) where !ld.InRoom.Contains(id) select id).ToList(); } private static List ComputeAllInLevel(Lockdown ld) { return NetGameplaySyncBridge.GetPeerIdsInLevel(ld.Chapter, ld.Level, ld.HasSeed, ld.Seed); } private static bool IsEffectivelyInArena(Vector3 arenaPos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) string text = Key(arenaPos); if (_doorwayCrossings.TryGetValue(text, out var value)) { return value % 2 == 1; } return _localCrossed.Contains(text); } public static void OnLocalDoorwayTraversed(string arenaKey, Vector3 arenaPos) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) _doorwayCrossings.TryGetValue(arenaKey, out var value); value++; _doorwayCrossings[arenaKey] = value; if (LogOn) { NetLogger.Info($"[ArenaLockdown] doorway traversal arena={arenaKey} count={value} inside={value % 2 == 1}"); } if (value % 2 == 1) { _localCrossed.Add(arenaKey); ReportLocalInRoom(arenaPos); } } public static Transform LocalPlayerRoot() { try { object obj = ResolveLocalPlayerUnit(); Component val = (Component)((obj is Component) ? obj : null); return (val != null && (Object)(object)val != (Object)null) ? val.transform.root : null; } catch { return null; } } public static void AttachDoorwaySensorIfSeal(object trigger) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005c: 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) try { if (!Enabled || !NetGameplaySyncBridge.IsSessionActive) { return; } Component val = (Component)((trigger is Component) ? trigger : null); if (val != null && !((Object)(object)val == (Object)null) && IsSealTrigger(trigger, out var pos) && !((Object)(object)val.GetComponent() != (Object)null)) { ArenaDoorwaySensor arenaDoorwaySensor = val.gameObject.AddComponent(); arenaDoorwaySensor.ArenaKey = Key(pos); arenaDoorwaySensor.ArenaPos = pos; if (LogOn) { NetLogger.Info("[ArenaLockdown] doorway sensor attached arena=" + Key(pos)); } } } catch (Exception ex) { NetLogger.Warn("[ArenaLockdown] AttachDoorwaySensor failed: " + ex.Message); } } private static bool MoveLocalPlayerTo(object unit, Vector3 dest) { //IL_0062: 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) try { if (unit == null) { NetLogger.Warn("[ArenaLockdown] teleport: local player unit missing"); return false; } MethodInfo methodInfo = AccessTools.Method(unit.GetType(), "TeleportTo", new Type[1] { typeof(Vector3) }, (Type[])null) ?? AccessTools.Method(unit.GetType(), "TeleportTo", (Type[])null, (Type[])null); if (methodInfo != null) { methodInfo.Invoke(unit, new object[1] { dest }); } else { Component val = (Component)((unit is Component) ? unit : null); if (val == null || !((Object)(object)val != (Object)null)) { return false; } val.transform.position = dest; } return true; } catch (Exception ex) { NetLogger.Warn("[ArenaLockdown] teleport failed: " + ex.Message); return false; } } private static object ResolveLocalPlayerUnit() { try { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); object obj = ((type == null) ? null : AccessTools.Property(type, "Instance")?.GetValue(null, null)); if (obj == null) { return null; } object obj2 = AccessTools.Property(type, "PlayerUnit")?.GetValue(obj, null); Object val = (Object)((obj2 is Object) ? obj2 : null); if (val != null && val == (Object)null) { return null; } return obj2; } catch { return null; } } private static bool IsSealTrigger(object trigger, out Vector3 pos) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_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) pos = Vector3.zero; try { Component val = (Component)((trigger is Component) ? trigger : null); if (val == null || (Object)(object)val == (Object)null) { return false; } pos = val.transform.position; if (!_eventFieldResolved) { _eventFieldResolved = true; _eventField = trigger.GetType().GetField("onTriggerEvents", BindingFlags.Instance | BindingFlags.Public); } object? obj = _eventField?.GetValue(trigger); UnityEventBase val2 = (UnityEventBase)((obj is UnityEventBase) ? obj : null); if (val2 == null) { return false; } int persistentEventCount = val2.GetPersistentEventCount(); for (int i = 0; i < persistentEventCount; i++) { string persistentMethodName = val2.GetPersistentMethodName(i); Object persistentTarget = val2.GetPersistentTarget(i); if (persistentTarget == (Object)null) { continue; } if (string.Equals(persistentMethodName, "Close", StringComparison.Ordinal) && ((object)persistentTarget).GetType().Name.IndexOf("MetalGate", StringComparison.Ordinal) >= 0) { return true; } if (string.Equals(persistentMethodName, "SetActive", StringComparison.Ordinal)) { object obj2 = ((persistentTarget is GameObject) ? persistentTarget : null); if (obj2 == null) { Object obj3 = ((persistentTarget is Component) ? persistentTarget : null); obj2 = ((obj3 != null) ? ((Component)obj3).gameObject : null); } GameObject val3 = (GameObject)obj2; if ((Object)(object)val3 != (Object)null && ((Object)val3).name.IndexOf("door", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } } } catch { } return false; } private static string Key(Vector3 p) { //IL_0005: 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_0025: Unknown result type (might be due to invalid IL or missing references) return $"{Mathf.RoundToInt(p.x)}_{Mathf.RoundToInt(p.y)}_{Mathf.RoundToInt(p.z)}"; } public static void Clear() { _locks.Clear(); _sandstormArenas.Clear(); _gracedGates.Clear(); _heldGates.Clear(); _legitGateOpenUntil = -999f; _localCrossed.Clear(); _doorwayCrossings.Clear(); _activeArenaKey = ""; _clientArenaMembers.Clear(); _clientArenaKey = ""; ArenaBarrierManager.Clear(); _armed = false; if (HidePrompt != null) { try { HidePrompt(); } catch { } } } } internal static class BreakableBreakManager { private static readonly Dictionary _registry = new Dictionary(); private static bool _applyingMirror; private static readonly HashSet _broadcasted = new HashSet(); private static int _captureSeq; private const float MatchEpsilon = 0.75f; public static bool IsApplyingMirror => _applyingMirror; public static void Register(Breakable b) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)b == (Object)null) { return; } try { _registry[b] = ((Component)b).transform.position; } catch { } } public static void Unregister(Breakable b) { if ((Object)(object)b == (Object)null) { return; } try { _registry.Remove(b); } catch { } } public static void CaptureLocalBreak(Breakable b) { //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_006e: 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) try { if (!_applyingMirror && Plugin.Cfg.EnableBreakableSync.Value && NetGameplaySyncBridge.IsSessionActive && !((Object)(object)b == (Object)null) && _broadcasted.Add(b)) { Vector3 val = ResolveSpawnPos(b); NetGameplaySyncBridge.ReportLocalBreakableBreak(new NetBreakableBreak { Sequence = ++_captureSeq, Position = val }); if (Plugin.Cfg.LogBreakableSync.Value) { NetLogger.Info($"[BreakableBreak] capture name={((Object)b).name} pos={val}"); } } } catch (Exception ex) { NetLogger.Warn("[BreakableBreak] capture failed: " + ex.Message); } } public static void ApplyRemoteBreak(NetBreakableBreak m) { //IL_0022: 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_00a1: Unknown result type (might be due to invalid IL or missing references) try { if (!Plugin.Cfg.EnableBreakableSync.Value || m == null) { return; } Breakable val = FindMatch(m.Position); if ((Object)(object)val == (Object)null) { if (Plugin.Cfg.LogBreakableSync.Value) { NetLogger.Info($"[BreakableBreak] mirror peer={m.PeerId} no alive match near {m.Position}"); } return; } _applyingMirror = true; try { val.Break(); } finally { _applyingMirror = false; } if (Plugin.Cfg.LogBreakableSync.Value) { NetLogger.Info($"[BreakableBreak] mirror peer={m.PeerId} broke name={((Object)val).name} near {m.Position}"); } } catch (Exception ex) { NetLogger.Warn("[BreakableBreak] mirror failed: " + ex.Message); } } private static Vector3 ResolveSpawnPos(Breakable b) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_000f: 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_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (_registry.TryGetValue(b, out var value)) { return value; } try { return ((Component)b).transform.position; } catch { return Vector3.zero; } } private static Breakable FindMatch(Vector3 key) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) Breakable result = null; float num = 0.5625f; List list = null; foreach (KeyValuePair item in _registry) { Breakable key2 = item.Key; if ((Object)(object)key2 == (Object)null) { (list ?? (list = new List())).Add(key2); } else if ((int)((Unit)key2).UnitState != 0) { Vector3 val = item.Value - key; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude <= num) { num = sqrMagnitude; result = key2; } } } if (list != null) { foreach (Breakable item2 in list) { _registry.Remove(item2); } } return result; } public static void Clear() { _registry.Clear(); _broadcasted.Clear(); } } internal sealed class ClientPlayerHitProxyManager { private sealed class HitProxy { public GameObject Go; public object Unit; public float LastUpdatedAt; } private readonly Dictionary _proxies = new Dictionary(); private readonly HashSet _seenThisTick = new HashSet(); private readonly List _removeScratch = new List(); private static bool _resolveAttempted; private static bool _resolveOk; private static Type? _unitType; private static PropertyInfo? _gmInstance; private static PropertyInfo? _gmPlayerUnit; private static PropertyInfo? _gmUnits; private static MethodInfo? _setStats; private static MethodInfo? _spawn; private static FieldInfo? _unitSOField; private static Type? _hitmeshType; private static FieldInfo? _hitmeshOwnerField; private static FieldInfo? _hitmeshColliderField; private static FieldInfo? _hitmeshHitShapesField; private static bool LogOn { get { try { return Plugin.Cfg.LogFriendlyFire.Value; } catch { return false; } } } public void Tick(NetRemotePlayerProxyManager visualProxies, float now, float maxAgeSeconds) { if (NetConfig.GetMode() != NetMode.Client || !NetSessionSettings.FriendlyFireEnabled || visualProxies == null) { if (_proxies.Count > 0) { Clear(); } } else { if (!EnsureResolved()) { return; } _seenThisTick.Clear(); visualProxies.ForEachInScenePlayer(delegate(string peerId, Vector3 pos) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!NetPlayerLifeManager.IsPeerDownOrDead(peerId)) { _seenThisTick.Add(peerId); UpdateOrCreate(peerId, pos, now); } }, now, maxAgeSeconds); if (_proxies.Count <= 0) { return; } _removeScratch.Clear(); foreach (KeyValuePair proxy in _proxies) { if (!_seenThisTick.Contains(proxy.Key)) { _removeScratch.Add(proxy.Key); } } foreach (string item in _removeScratch) { Destroy(item, "player gone/downed"); } } } public void Clear() { foreach (string item in new List(_proxies.Keys)) { Destroy(item, "clear"); } _proxies.Clear(); } private void UpdateOrCreate(string peerId, Vector3 pos, float now) { //IL_0048: 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) if (_proxies.TryGetValue(peerId, out HitProxy value)) { if (!((Object)(object)value.Go == (Object)null)) { value.Go.transform.position = pos; value.LastUpdatedAt = now; return; } _proxies.Remove(peerId); } TryCreate(peerId, pos, now); } private void TryCreate(string peerId, Vector3 pos, float now) { //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_0066: Unknown result type (might be due to invalid IL or missing references) try { object value = _gmInstance.GetValue(null, null); if (value == null) { return; } object obj = _gmPlayerUnit?.GetValue(value, null); object obj2 = ((obj != null && _unitSOField != null) ? _unitSOField.GetValue(obj) : null); GameObject val = new GameObject("SULFURTogether FFHitProxy - " + peerId); val.SetActive(false); val.transform.position = pos; int num = 6; try { num = Plugin.Cfg.RemotePlayerTargetProxyHitboxLayer.Value; } catch { } val.layer = num; CapsuleCollider val2 = val.AddComponent(); val2.height = 1.9f; val2.radius = 0.4f; val2.center = new Vector3(0f, 0.95f, 0f); ((Collider)val2).isTrigger = false; Rigidbody obj4 = val.AddComponent(); obj4.isKinematic = true; obj4.useGravity = false; Component val3 = val.AddComponent(_unitType); if (_unitSOField != null && obj2 != null) { try { _unitSOField.SetValue(val3, obj2); } catch { } } RemotePlayerTargetProxyManager.RegisterExternalProxyUnit(val3, peerId); if (_hitmeshType != null) { try { Component obj6 = val.AddComponent(_hitmeshType); _hitmeshOwnerField?.SetValue(obj6, val3); _hitmeshColliderField?.SetValue(obj6, val2); try { Type type = _hitmeshHitShapesField?.FieldType.GetElementType(); if ((object)type != null) { _hitmeshHitShapesField.SetValue(obj6, Array.CreateInstance(type, 0)); } } catch { } } catch (Exception ex) { Plugin.Log.Warn("[FF] hit-proxy Hitmesh add failed: " + ex.Message); } } val.SetActive(true); if (_setStats != null && obj2 != null) { try { _setStats.Invoke(val3, new object[1] { obj2 }); } catch (Exception ex2) { Plugin.Log.Warn("[FF] hit-proxy SetStats failed: " + ex2.Message); } } if (_spawn != null) { try { _spawn.Invoke(val3, null); } catch (Exception ex3) { Plugin.Log.Warn("[FF] hit-proxy Spawn failed: " + ex3.Message); } } _proxies[peerId] = new HitProxy { Go = val, Unit = val3, LastUpdatedAt = now }; if (LogOn) { Plugin.Log.Info(string.Format("[FF] hit proxy created peer={0} pos={1:F1} layer={2} unitSO={3}", peerId, pos, num, (obj2 == null) ? "null" : "ok")); } } catch (Exception ex4) { Plugin.Log.Warn("[FF] hit-proxy create failed peer=" + peerId + ": " + ex4.GetType().Name + ": " + ex4.Message); } } private void Destroy(string peerId, string reason) { if (!_proxies.TryGetValue(peerId, out HitProxy value)) { return; } _proxies.Remove(peerId); try { RemotePlayerTargetProxyManager.UnregisterExternalProxyUnit(value.Unit); } catch { } try { object obj2 = _gmInstance?.GetValue(null, null); if (obj2 != null && _gmUnits?.GetValue(obj2, null) is IList list) { list.Remove(value.Unit); } } catch { } try { if ((Object)(object)value.Go != (Object)null) { Object.Destroy((Object)(object)value.Go); } } catch { } if (LogOn) { Plugin.Log.Info("[FF] hit proxy destroyed peer=" + peerId + " reason=" + reason); } } private static bool EnsureResolved() { if (_resolveAttempted) { return _resolveOk; } _resolveAttempted = true; try { _unitType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Units.Unit"); Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); if (_unitType == null || type == null) { Plugin.Log.Warn("[FF] hit-proxy resolve failed: Unit/GameManager type missing"); return false; } _gmInstance = AccessTools.Property(type, "Instance"); _gmPlayerUnit = AccessTools.Property(type, "PlayerUnit"); _gmUnits = AccessTools.Property(type, "units"); _setStats = AccessTools.Method(_unitType, "SetStats", (Type[])null, (Type[])null); _spawn = AccessTools.Method(_unitType, "Spawn", Type.EmptyTypes, (Type[])null); _unitSOField = AccessTools.Field(_unitType, "unitSO"); _hitmeshType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Hitmesh"); if (_hitmeshType != null) { _hitmeshOwnerField = AccessTools.Field(_hitmeshType, "owner"); _hitmeshColliderField = AccessTools.Field(_hitmeshType, "hitmeshCollider"); _hitmeshHitShapesField = AccessTools.Field(_hitmeshType, "hitShapes"); } _resolveOk = _gmInstance != null && _gmPlayerUnit != null; if (LogOn) { Plugin.Log.Info($"[FF] hit-proxy resolve ok={_resolveOk} hitmesh={_hitmeshType != null}"); } return _resolveOk; } catch (Exception ex) { Plugin.Log.Warn("[FF] hit-proxy resolve exception: " + ex.GetType().Name + ": " + ex.Message); return false; } } } internal static class FriendlyFireClassifier { private static Type? _sourceDataType; private static FieldInfo? _sourceUnitField; private static FieldInfo? _isPlayerField; private const int MaxSampleLines = 20; private static int _sampleLines; public static bool IsFromLocalPlayer(object? rawSource) { if (rawSource == null) { return false; } try { object obj = BossDamageReflect.ResolveHostPlayerUnit(); if (obj != null && rawSource == obj) { return true; } ResolveSourceDataFields(rawSource); if (_sourceDataType == null || !_sourceDataType.IsInstanceOfType(rawSource)) { return false; } if (_sourceUnitField != null && obj != null && _sourceUnitField.GetValue(rawSource) == obj) { return true; } bool flag = default(bool); int num; if (_isPlayerField != null) { object value = _isPlayerField.GetValue(rawSource); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { return true; } return false; } catch { return false; } } private static void ResolveSourceDataFields(object rawSource) { if (!(_sourceDataType != null)) { Type type = rawSource.GetType(); if (!(type.Name != "DamageSourceData")) { _sourceDataType = type; _sourceUnitField = AccessTools.Field(type, "sourceUnit"); _isPlayerField = AccessTools.Field(type, "isPlayer"); } } } public static void LogClassification(string context, object? rawSource, float damage, bool fromLocalPlayer) { try { if (Plugin.Cfg.LogFriendlyFire.Value && _sampleLines < 20) { _sampleLines++; string text = Describe(rawSource); Plugin.Log.Info($"[FF] {context}: fromLocalPlayer={fromLocalPlayer} dmg={damage:F1} source={text} ({_sampleLines}/{20})"); } } catch { } } private static string Describe(object? rawSource) { if (rawSource == null) { return "null"; } try { Type type = rawSource.GetType(); if (_sourceDataType != null && _sourceDataType.IsInstanceOfType(rawSource)) { string text = _isPlayerField?.GetValue(rawSource)?.ToString() ?? "?"; object? obj = _sourceUnitField?.GetValue(rawSource); Object val = (Object)((obj is Object) ? obj : null); string text2 = ((val != null && val != (Object)null) ? val.name : "null"); string text3 = AccessTools.Field(type, "melee")?.GetValue(rawSource)?.ToString() ?? "?"; string text4 = ((AccessTools.Field(type, "projectile")?.GetValue(rawSource) == null) ? "null" : "set"); return type.Name + "(isPlayer=" + text + ",sourceUnit=" + text2 + ",melee=" + text3 + ",projectile=" + text4 + ")"; } return type.Name; } catch { return rawSource.GetType().Name; } } } internal static class GateSyncManager { private static readonly Dictionary _registry = new Dictionary(); private static readonly Dictionary _lastState = new Dictionary(); private static bool _applyingMirror; private static int _captureSeq; private const float MatchEpsilon = 1f; private static MethodInfo? _closeMethod; private static MethodInfo? _openMethod; private static bool _methodsResolved; private static bool _applyingTriggerMirror; private static Type _playerTriggerType; private static MethodInfo _triggerMethod; private static FieldInfo _hasBeenTriggeredField; private static FieldInfo _onTriggerEventsField; public static bool IsApplyingMirror => _applyingMirror; private static bool Enabled { get { try { return Plugin.Cfg.EnableGateSync.Value; } catch { return false; } } } private static bool LogOn { get { try { return Plugin.Cfg.LogGateSync.Value; } catch { return false; } } } public static bool IsApplyingTriggerMirror => _applyingTriggerMirror; public static void Register(object gate) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) Component val = (Component)((gate is Component) ? gate : null); if (val == null || (Object)(object)val == (Object)null) { return; } try { _registry[val] = val.transform.position; } catch { } } public static void CaptureLocalGate(object gate, bool closed) { //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_0080: 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_00b3: Unknown result type (might be due to invalid IL or missing references) try { if (_applyingMirror || !Enabled || !NetGameplaySyncBridge.IsSessionActive) { return; } Component val = (Component)((gate is Component) ? gate : null); if (val != null && !((Object)(object)val == (Object)null) && (!_lastState.TryGetValue(val, out var value) || value != closed)) { _lastState[val] = closed; Vector3 val2 = ResolvePos(val); NetGameplaySyncBridge.ReportLocalGateState(new NetGateState { Sequence = ++_captureSeq, Position = val2, Closed = closed }); if (LogOn) { NetLogger.Info(string.Format("[GateSync] capture name={0} {1} pos={2}", ((Object)val).name, closed ? "CLOSE" : "OPEN", val2)); } if (!closed) { ArenaLockdownManager.OnGateOpened(val2); } } } catch (Exception ex) { NetLogger.Warn("[GateSync] capture failed: " + ex.Message); } } public static void ApplyRemoteGate(NetGateState m) { //IL_0041: 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_0129: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) try { if (!Enabled || m == null) { return; } if (m.Kind == 1) { ApplyRemoteRoomTrigger(m); return; } if (!ResolveMethods()) { if (LogOn) { NetLogger.Info("[GateSync] MetalGate methods unresolved"); } return; } Component val = FindMatch(m.Position); if ((Object)(object)val == (Object)null) { if (LogOn) { NetLogger.Info($"[GateSync] mirror peer={m.PeerId} no gate near {m.Position}"); } return; } MethodInfo methodInfo = (m.Closed ? _closeMethod : _openMethod); if (!(methodInfo == null)) { _applyingMirror = true; try { methodInfo.Invoke(val, null); } finally { _applyingMirror = false; } _lastState[val] = m.Closed; if (LogOn) { NetLogger.Info(string.Format("[GateSync] mirror peer={0} {1} name={2} near {3}", m.PeerId, m.Closed ? "CLOSE" : "OPEN", ((Object)val).name, m.Position)); } if (!m.Closed) { ArenaLockdownManager.OnGateOpened(m.Position); } } } catch (Exception ex) { NetLogger.Warn("[GateSync] mirror failed: " + ex.Message); } } public static bool CloseGate(object gate) { try { if (gate == null || !ResolveMethods()) { return false; } _closeMethod.Invoke(gate, null); return true; } catch (Exception ex) { NetLogger.Warn("[GateSync] CloseGate failed: " + ex.Message); return false; } } public static void OnLocalPlayerTriggerFired(object playerTrigger, bool wasTriggeredBefore) { //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_00b0: 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) try { if (_applyingTriggerMirror || !Enabled || !NetGameplaySyncBridge.IsSessionActive) { return; } Component val = (Component)((playerTrigger is Component) ? playerTrigger : null); if (val == null || (Object)(object)val == (Object)null || !EnsureTriggerReflect(val)) { return; } bool flag = default(bool); int num; if (_hasBeenTriggeredField != null) { object value = _hasBeenTriggeredField.GetValue(playerTrigger); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } bool flag2 = (byte)((uint)num & (flag ? 1u : 0u)) != 0; if (!wasTriggeredBefore && flag2 && HasAppearAnimatorPersistent(playerTrigger)) { Vector3 position = val.transform.position; NetGameplaySyncBridge.ReportLocalGateState(new NetGateState { Sequence = ++_captureSeq, Position = position, Kind = 1 }); NetLogger.Info($"[BossIntroSync] capture room-event trigger name={((Object)val).name} pos={position:F1}"); } } catch (Exception ex) { NetLogger.Warn("[BossIntroSync] capture failed: " + ex.Message); } } private static void ApplyRemoteRoomTrigger(NetGateState m) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) try { string detail; Component val = FindLocalPlayerTrigger(m.Position, out detail); if ((Object)(object)val == (Object)null) { NetLogger.Warn($"[BossIntroSync] mirror peer={m.PeerId} no PlayerTrigger near {m.Position:F1} ({detail})"); } else { if (!EnsureTriggerReflect(val) || _triggerMethod == null) { return; } object obj = _hasBeenTriggeredField?.GetValue(val); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { if (LogOn) { NetLogger.Info("[BossIntroSync] mirror peer=" + m.PeerId + " already fired locally name=" + ((Object)val).name); } return; } GameObject val2 = ResolveLocalPlayerGo() ?? val.gameObject; _applyingTriggerMirror = true; try { _triggerMethod.Invoke(val, new object[1] { val2 }); } finally { _applyingTriggerMirror = false; } NetLogger.Info($"[BossIntroSync] mirror peer={m.PeerId} fired local room-event trigger name={((Object)val).name} pos={m.Position:F1}"); } } catch (Exception ex) { NetLogger.Warn("[BossIntroSync] mirror failed: " + ex.Message); } } internal static bool HasAppearAnimatorPersistent(object playerTrigger) { try { if (playerTrigger == null || !EnsureTriggerReflect((Component)((playerTrigger is Component) ? playerTrigger : null))) { return false; } object? obj = _onTriggerEventsField?.GetValue(playerTrigger); UnityEventBase val = (UnityEventBase)((obj is UnityEventBase) ? obj : null); if (val == null) { return false; } int persistentEventCount = val.GetPersistentEventCount(); for (int i = 0; i < persistentEventCount; i++) { if (val.GetPersistentTarget(i) is Animator && val.GetPersistentMethodName(i) == "SetTrigger") { return true; } } return false; } catch { return false; } } internal static bool TryReadHasBeenTriggered(object playerTrigger, out bool fired) { fired = false; try { if (!EnsureTriggerReflect((Component)((playerTrigger is Component) ? playerTrigger : null)) || _hasBeenTriggeredField == null) { return false; } object value = _hasBeenTriggeredField.GetValue(playerTrigger); bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } fired = (byte)((uint)num & (flag ? 1u : 0u)) != 0; return true; } catch { return false; } } private static bool EnsureTriggerReflect(Component sample) { try { if (_playerTriggerType == null) { _playerTriggerType = (((Object)(object)sample != (Object)null && ((object)sample).GetType().Name == "PlayerTrigger") ? ((object)sample).GetType() : (AccessTools.TypeByName("PerfectRandom.Sulfur.Core.World.PlayerTrigger") ?? AccessTools.TypeByName("PlayerTrigger"))); if (_playerTriggerType != null) { _triggerMethod = _playerTriggerType.GetMethod("Trigger", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(GameObject) }, null); _hasBeenTriggeredField = _playerTriggerType.GetField("hasBeenTriggered", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _onTriggerEventsField = _playerTriggerType.GetField("onTriggerEvents", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } } return _playerTriggerType != null; } catch { return false; } } private static Component FindLocalPlayerTrigger(Vector3 key, out string detail) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005b: 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) detail = ""; try { if (!EnsureTriggerReflect(null)) { detail = "PlayerTrigger type unresolved"; return null; } Component val = null; float num = 1f; Object[] array = Object.FindObjectsOfType(_playerTriggerType); foreach (Object obj in array) { Component val2 = (Component)(object)((obj is Component) ? obj : null); if (val2 != null && !((Object)(object)val2 == (Object)null)) { Vector3 val3 = val2.transform.position - key; float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude; if (sqrMagnitude <= num) { num = sqrMagnitude; val = val2; } } } if ((Object)(object)val == (Object)null) { detail = "no match within epsilon"; } return val; } catch (Exception ex) { detail = ex.GetType().Name; return null; } } private static GameObject ResolveLocalPlayerGo() { try { object? obj = BossDamageReflect.ResolveHostPlayerUnit(); object? obj2 = ((obj is Component) ? obj : null); return (obj2 != null) ? ((Component)obj2).gameObject : null; } catch { return null; } } private static Vector3 ResolvePos(Component c) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_000f: 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_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) if (_registry.TryGetValue(c, out var value)) { return value; } try { Vector3 position = c.transform.position; _registry[c] = position; return position; } catch { return Vector3.zero; } } private static Component FindMatch(Vector3 key) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) Component result = null; float num = 1f; List list = null; foreach (KeyValuePair item in _registry) { Component key2 = item.Key; if ((Object)(object)key2 == (Object)null) { (list ?? (list = new List())).Add(key2); continue; } Vector3 val = item.Value - key; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude <= num) { num = sqrMagnitude; result = key2; } } if (list != null) { foreach (Component item2 in list) { _registry.Remove(item2); _lastState.Remove(item2); } } return result; } private static bool ResolveMethods() { if (_methodsResolved) { if (_closeMethod != null) { return _openMethod != null; } return false; } _methodsResolved = true; try { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Gameplay.Mechanisms.MetalGate.MetalGate"); if (type != null) { _closeMethod = AccessTools.Method(type, "Close", Type.EmptyTypes, (Type[])null); _openMethod = AccessTools.Method(type, "Open", Type.EmptyTypes, (Type[])null); } } catch { } if (_closeMethod != null) { return _openMethod != null; } return false; } public static void Clear() { _registry.Clear(); _lastState.Clear(); } } internal enum ArenaCommandKind : byte { Seal = 1, Popup, Release, Notify, CloseDoor, NotifyEntered, Membership, PullIn } internal sealed class NetArenaCommand { public ArenaCommandKind Kind { get; set; } public Vector3 ArenaPos { get; set; } public List TargetPeerIds { get; set; } = new List(); } internal static class NetArenaCommandCodec { private const byte Version = 1; public static void Write(NetDataWriter w, NetArenaCommand m) { //IL_0015: 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_0037: Unknown result type (might be due to invalid IL or missing references) w.Put((byte)1); w.Put((byte)m.Kind); w.Put(m.ArenaPos.x); w.Put(m.ArenaPos.y); w.Put(m.ArenaPos.z); List list = m.TargetPeerIds ?? new List(); w.Put(list.Count); foreach (string item in list) { w.Put(item ?? ""); } } public static bool TryRead(NetDataReader r, out NetArenaCommand m) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) m = new NetArenaCommand(); try { if (r.GetByte() != 1) { return false; } m.Kind = (ArenaCommandKind)r.GetByte(); m.ArenaPos = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); int num = r.GetInt(); for (int i = 0; i < num; i++) { m.TargetPeerIds.Add(r.GetString()); } return true; } catch { return false; } } } internal sealed class NetBreakableBreak { public string PeerId { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public int Sequence { get; set; } public float SentAt { get; set; } public Vector3 Position { get; set; } public bool MatchesScene(NetRunState localState) { if (!localState.HasLevel) { return false; } if (!string.Equals(localState.ChapterName, ChapterName, StringComparison.Ordinal)) { return false; } if (localState.LevelIndex != LevelIndex) { return false; } if (Plugin.Cfg.EnableLevelSeedAuthority.Value) { if (!HasLevelSeed || !localState.HasLevelSeed) { return false; } if (localState.LevelSeed != LevelSeed) { return false; } } return true; } } internal static class NetBreakableBreakCodec { private const byte Version = 1; public static void Write(NetDataWriter w, NetBreakableBreak m) { //IL_0077: 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_0099: Unknown result type (might be due to invalid IL or missing references) w.Put((byte)1); w.Put(m.PeerId ?? ""); w.Put(m.ChapterName ?? ""); w.Put(m.LevelIndex); w.Put(m.HasLevelSeed); if (m.HasLevelSeed) { w.Put(m.LevelSeed); } w.Put(m.Sequence); w.Put(m.SentAt); w.Put(m.Position.x); w.Put(m.Position.y); w.Put(m.Position.z); } public static bool TryRead(NetDataReader r, out NetBreakableBreak m) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) m = new NetBreakableBreak(); try { if (r.GetByte() != 1) { return false; } m.PeerId = r.GetString(); m.ChapterName = r.GetString(); m.LevelIndex = r.GetInt(); m.HasLevelSeed = r.GetBool(); if (m.HasLevelSeed) { m.LevelSeed = r.GetInt(); } m.Sequence = r.GetInt(); m.SentAt = r.GetFloat(); m.Position = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); return true; } catch { return false; } } } internal sealed class NetClientArenaEnter { public string PeerId { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public Vector3 ArenaPos { get; set; } } internal static class NetClientArenaEnterCodec { private const byte Version = 1; public static void Write(NetDataWriter w, NetClientArenaEnter m) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) w.Put((byte)1); w.Put(m.PeerId ?? ""); w.Put(m.ChapterName ?? ""); w.Put(m.LevelIndex); w.Put(m.HasLevelSeed); if (m.HasLevelSeed) { w.Put(m.LevelSeed); } w.Put(m.ArenaPos.x); w.Put(m.ArenaPos.y); w.Put(m.ArenaPos.z); } public static bool TryRead(NetDataReader r, out NetClientArenaEnter m) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) m = new NetClientArenaEnter(); try { if (r.GetByte() != 1) { return false; } m.PeerId = r.GetString(); m.ChapterName = r.GetString(); m.LevelIndex = r.GetInt(); m.HasLevelSeed = r.GetBool(); if (m.HasLevelSeed) { m.LevelSeed = r.GetInt(); } m.ArenaPos = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); return true; } catch { return false; } } } internal sealed class NetClientHitRequest { public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public int RequestSeq { get; set; } public string ClientPeerId { get; set; } = ""; public int TargetHostSpawnIndex { get; set; } = -1; public string TargetUnitIdentifier { get; set; } = ""; public float DamageCandidate { get; set; } public bool HasAttackerPosition { get; set; } public Vector3 AttackerPosition { get; set; } public float SentAt { get; set; } public string SceneKey { get { if (!string.IsNullOrWhiteSpace(ChapterName)) { return $"{ChapterName}:{LevelIndex}"; } return ":-1"; } } public bool MatchesScene(NetRunState localState) { if (!localState.HasLevel) { return false; } if (!string.Equals(localState.ChapterName, ChapterName, StringComparison.Ordinal)) { return false; } if (localState.LevelIndex != LevelIndex) { return false; } if (Plugin.Cfg.EnableLevelSeedAuthority.Value) { if (!HasLevelSeed || !localState.HasLevelSeed) { return false; } if (localState.LevelSeed != LevelSeed) { return false; } } return true; } } internal static class NetClientHitRequestCodec { public static void Write(NetDataWriter w, NetClientHitRequest r) { //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) w.Put(r.ChapterName ?? ""); w.Put(r.LevelIndex); w.Put(r.HasLevelSeed); if (r.HasLevelSeed) { w.Put(r.LevelSeed); } w.Put(r.RequestSeq); w.Put(r.ClientPeerId ?? ""); w.Put(r.TargetHostSpawnIndex); w.Put(r.TargetUnitIdentifier ?? ""); w.Put(r.DamageCandidate); w.Put(r.HasAttackerPosition); if (r.HasAttackerPosition) { w.Put(r.AttackerPosition.x); w.Put(r.AttackerPosition.y); w.Put(r.AttackerPosition.z); } w.Put(r.SentAt); } public static bool TryRead(NetDataReader r, out NetClientHitRequest result) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) result = null; try { NetClientHitRequest netClientHitRequest = new NetClientHitRequest(); netClientHitRequest.ChapterName = r.GetString(); netClientHitRequest.LevelIndex = r.GetInt(); netClientHitRequest.HasLevelSeed = r.GetBool(); if (netClientHitRequest.HasLevelSeed) { netClientHitRequest.LevelSeed = r.GetInt(); } netClientHitRequest.RequestSeq = r.GetInt(); netClientHitRequest.ClientPeerId = r.GetString(); netClientHitRequest.TargetHostSpawnIndex = r.GetInt(); netClientHitRequest.TargetUnitIdentifier = r.GetString(); netClientHitRequest.DamageCandidate = r.GetFloat(); netClientHitRequest.HasAttackerPosition = r.GetBool(); if (netClientHitRequest.HasAttackerPosition) { netClientHitRequest.AttackerPosition = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } netClientHitRequest.SentAt = r.GetFloat(); result = netClientHitRequest; return true; } catch { return false; } } } internal sealed class NetFriendlyFireHit { public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public int Seq { get; set; } public string SourcePeerId { get; set; } = ""; public string VictimPeerId { get; set; } = ""; public float Damage { get; set; } public int DamageTypeInt { get; set; } public bool HasPosition { get; set; } public Vector3 Position { get; set; } public float SentAt { get; set; } public bool MatchesScene(NetRunState localState) { if (!localState.HasLevel) { return false; } if (!string.Equals(localState.ChapterName, ChapterName, StringComparison.Ordinal)) { return false; } if (localState.LevelIndex != LevelIndex) { return false; } if (Plugin.Cfg.EnableLevelSeedAuthority.Value) { if (!HasLevelSeed || !localState.HasLevelSeed) { return false; } if (localState.LevelSeed != LevelSeed) { return false; } } return true; } } internal static class NetFriendlyFireHitCodec { public static void Write(NetDataWriter w, NetFriendlyFireHit h) { //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) w.Put(h.ChapterName ?? ""); w.Put(h.LevelIndex); w.Put(h.HasLevelSeed); if (h.HasLevelSeed) { w.Put(h.LevelSeed); } w.Put(h.Seq); w.Put(h.SourcePeerId ?? ""); w.Put(h.VictimPeerId ?? ""); w.Put(h.Damage); w.Put(h.DamageTypeInt); w.Put(h.HasPosition); if (h.HasPosition) { w.Put(h.Position.x); w.Put(h.Position.y); w.Put(h.Position.z); } w.Put(h.SentAt); } public static bool TryRead(NetDataReader r, out NetFriendlyFireHit result) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) result = null; try { NetFriendlyFireHit netFriendlyFireHit = new NetFriendlyFireHit(); netFriendlyFireHit.ChapterName = r.GetString(); netFriendlyFireHit.LevelIndex = r.GetInt(); netFriendlyFireHit.HasLevelSeed = r.GetBool(); if (netFriendlyFireHit.HasLevelSeed) { netFriendlyFireHit.LevelSeed = r.GetInt(); } netFriendlyFireHit.Seq = r.GetInt(); netFriendlyFireHit.SourcePeerId = r.GetString(); netFriendlyFireHit.VictimPeerId = r.GetString(); netFriendlyFireHit.Damage = r.GetFloat(); netFriendlyFireHit.DamageTypeInt = r.GetInt(); netFriendlyFireHit.HasPosition = r.GetBool(); if (netFriendlyFireHit.HasPosition) { netFriendlyFireHit.Position = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } netFriendlyFireHit.SentAt = r.GetFloat(); result = netFriendlyFireHit; return true; } catch { return false; } } } internal static class NetFriendlyFireManager { private static int _seq; public static void SendLocalPlayerHit(string victimPeerId, float damage, int damageTypeInt, Vector3 hitPos) { //IL_002c: 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_003d: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrWhiteSpace(victimPeerId) && !(damage <= 0f)) { NetFriendlyFireHit netFriendlyFireHit = new NetFriendlyFireHit { VictimPeerId = victimPeerId, Damage = damage, DamageTypeInt = damageTypeInt, HasPosition = (hitPos != Vector3.zero), Position = hitPos, Seq = ++_seq }; if (NetRunStateBridge.TryGetLocalRunState(out NetRunState state) && state != null) { netFriendlyFireHit.SourcePeerId = state.PeerId ?? ""; netFriendlyFireHit.ChapterName = state.ChapterName ?? ""; netFriendlyFireHit.LevelIndex = state.LevelIndex; netFriendlyFireHit.HasLevelSeed = state.HasLevelSeed; netFriendlyFireHit.LevelSeed = state.LevelSeed; } NetGameplaySyncBridge.SendFriendlyFireHit(netFriendlyFireHit); if (Plugin.Cfg.LogFriendlyFire.Value) { Plugin.Log.Info($"[FF] hit request sent: victim={victimPeerId} dmg={damage:F1} type={damageTypeInt} seq={netFriendlyFireHit.Seq}"); } } } } internal sealed class NetGameplayDeathEvent { public string EventId { get; set; } = ""; public string SourcePeerId { get; set; } = "host"; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public int SourceRevision { get; set; } public int Sequence { get; set; } public int SpawnIndex { get; set; } public string CandidateKey { get; set; } = ""; public string LocalInstanceId { get; set; } = ""; public int UnityInstanceId { get; set; } public string TypeName { get; set; } = ""; public string UnitIdentifier { get; set; } = ""; public string UnitGlobalId { get; set; } = ""; public string Category { get; set; } = "Npc"; public string ActorName { get; set; } = ""; public bool HasPosition { get; set; } public Vector3 Position { get; set; } public int DamageCount { get; set; } public string Source { get; set; } = ""; public float SentAt { get; set; } public string SceneKey => $"{ChapterName}:{LevelIndex}"; public string SeedText { get { if (!HasLevelSeed) { return "?"; } return LevelSeed.ToString(); } } public string PositionText { get { //IL_0014: 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_0034: Unknown result type (might be due to invalid IL or missing references) if (!HasPosition) { return "(?)"; } return $"({Position.x:F2},{Position.y:F2},{Position.z:F2})"; } } public bool MatchesScene(NetRunState localState) { if (!localState.HasLevel) { return false; } if (!NetSceneName.SameScene(localState.ChapterName, localState.LevelIndex, ChapterName, LevelIndex)) { return false; } if (Plugin.Cfg.EnableLevelSeedAuthority.Value) { if (HasLevelSeed && localState.HasLevelSeed) { return LevelSeed == localState.LevelSeed; } return false; } return true; } public string ToCompactString() { return $"event={EventId} src={SourcePeerId} seq={Sequence} idx={SpawnIndex} candidate={CandidateKey} category={Category} actor={ActorName} pos={PositionText} scene={SceneKey} seed={SeedText} damageCount={DamageCount} source={Source}"; } } internal static class NetGameplayDeathEventCodec { public static void Write(NetDataWriter w, NetGameplayDeathEvent evt) { //IL_013a: 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_015c: Unknown result type (might be due to invalid IL or missing references) w.Put(evt.EventId ?? ""); w.Put(evt.SourcePeerId ?? "host"); w.Put(evt.ChapterName ?? ""); w.Put(evt.LevelIndex); w.Put(evt.HasLevelSeed ? 1 : 0); w.Put(evt.LevelSeed); w.Put(evt.SourceRevision); w.Put(evt.Sequence); w.Put(evt.SpawnIndex); w.Put(evt.CandidateKey ?? ""); w.Put(evt.LocalInstanceId ?? ""); w.Put(evt.UnityInstanceId); w.Put(evt.TypeName ?? ""); w.Put(evt.UnitIdentifier ?? ""); w.Put(evt.UnitGlobalId ?? ""); w.Put(evt.Category ?? "Npc"); w.Put(evt.ActorName ?? ""); w.Put(evt.HasPosition ? 1 : 0); w.Put(evt.Position.x); w.Put(evt.Position.y); w.Put(evt.Position.z); w.Put(evt.DamageCount); w.Put(evt.Source ?? ""); w.Put(evt.SentAt); } public static bool TryRead(NetDataReader r, out NetGameplayDeathEvent evt) { //IL_0111: Unknown result type (might be due to invalid IL or missing references) evt = new NetGameplayDeathEvent(); try { evt.EventId = r.GetString(); evt.SourcePeerId = r.GetString(); evt.ChapterName = r.GetString(); evt.LevelIndex = r.GetInt(); evt.HasLevelSeed = r.GetInt() != 0; evt.LevelSeed = r.GetInt(); evt.SourceRevision = r.GetInt(); evt.Sequence = r.GetInt(); evt.SpawnIndex = r.GetInt(); evt.CandidateKey = r.GetString(); evt.LocalInstanceId = r.GetString(); evt.UnityInstanceId = r.GetInt(); evt.TypeName = r.GetString(); evt.UnitIdentifier = r.GetString(); evt.UnitGlobalId = r.GetString(); evt.Category = r.GetString(); evt.ActorName = r.GetString(); evt.HasPosition = r.GetInt() != 0; float num = r.GetFloat(); float num2 = r.GetFloat(); float num3 = r.GetFloat(); evt.Position = new Vector3(num, num2, num3); evt.DamageCount = r.GetInt(); evt.Source = r.GetString(); evt.SentAt = r.GetFloat(); return true; } catch { return false; } } } internal sealed class NetGameplayEnemyStateSnapshot { public int Sequence { get; set; } public string SourcePeerId { get; set; } = "host"; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public int SourceRevision { get; set; } public float SentAt { get; set; } public int SpawnIndex { get; set; } public string CandidateKey { get; set; } = ""; public string LocalInstanceId { get; set; } = ""; public int UnityInstanceId { get; set; } public string TypeName { get; set; } = ""; public string UnitIdentifier { get; set; } = ""; public string UnitGlobalId { get; set; } = ""; public string Category { get; set; } = "Npc"; public string ActorName { get; set; } = ""; public bool HasPosition { get; set; } public Vector3 Position { get; set; } public bool HasRotationY { get; set; } public float RotationY { get; set; } public bool IsDead { get; set; } public bool HasAnimatorState { get; set; } public int AnimatorLayer { get; set; } public int AnimatorFullPathHash { get; set; } public int AnimatorShortNameHash { get; set; } public float AnimatorNormalizedTime { get; set; } public float AnimatorSpeed { get; set; } public bool HasAnimatorMovingBool { get; set; } public bool AnimatorMovingBool { get; set; } public bool HasAnimatorAttackBool { get; set; } public bool AnimatorAttackBool { get; set; } public bool HasAnimatorCoweringBool { get; set; } public bool AnimatorCoweringBool { get; set; } public bool HasHostCombatAction { get; set; } public int HostCombatActionKind { get; set; } public int HostCombatActionState { get; set; } public int HostCombatActionSequence { get; set; } public bool HasHostCombatAim { get; set; } public Vector3 HostCombatOriginPosition { get; set; } public Vector3 HostCombatAimPosition { get; set; } public bool HasAiIntent { get; set; } public int AiIntentSequence { get; set; } public int AiIntentKind { get; set; } public Vector3 AiIntentDestination { get; set; } public bool HasAiIntentLookAt { get; set; } public Vector3 AiIntentLookAt { get; set; } public int HostCombatAnimatorStateCount { get; set; } public int[] HostCombatAnimatorPathHashes { get; } = new int[4]; public int[] HostCombatAnimatorLayers { get; } = new int[4]; public int[] HostCombatAnimatorFullPathHashes { get; } = new int[4]; public float[] HostCombatAnimatorNormalizedTimes { get; } = new float[4]; public float[] HostCombatAnimatorSpeeds { get; } = new float[4]; public bool HasEnemyIntent { get; set; } public int EnemyIntentKind { get; set; } public int EnemyIntentSequence { get; set; } public float EnemyIntentDuration { get; set; } public bool EnemyIntentHasTargetPosition { get; set; } public Vector3 EnemyIntentTargetPosition { get; set; } public bool EnemyIntentHasAimPosition { get; set; } public Vector3 EnemyIntentAimPosition { get; set; } public bool EnemyIntentHasOriginPosition { get; set; } public Vector3 EnemyIntentOriginPosition { get; set; } public int EnemyIntentWeaponActionState { get; set; } public bool HasHostTarget { get; set; } public byte HostTargetKind { get; set; } public Vector3 HostTargetPosition { get; set; } public string SceneKey { get { if (!string.IsNullOrWhiteSpace(ChapterName)) { return $"{ChapterName}:{LevelIndex}"; } return ":-1"; } } public string SeedText { get { if (!HasLevelSeed) { return "?"; } return LevelSeed.ToString(); } } public string PositionText { get { //IL_0014: 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_0034: Unknown result type (might be due to invalid IL or missing references) if (!HasPosition) { return "(?)"; } return $"({Position.x:F2},{Position.y:F2},{Position.z:F2})"; } } public bool MatchesScene(NetRunState localState) { if (!localState.HasLevel) { return false; } if (!string.Equals(localState.ChapterName, ChapterName, StringComparison.Ordinal)) { return false; } if (localState.LevelIndex != LevelIndex) { return false; } if (Plugin.Cfg.EnableLevelSeedAuthority.Value) { if (!HasLevelSeed || !localState.HasLevelSeed) { return false; } if (localState.LevelSeed != LevelSeed) { return false; } } return true; } } internal static class NetGameplayEnemyStateSnapshotCodec { private const int MaxSnapshotsPerPacket = 128; private const byte WireVersion = 8; private const byte FlagHasPosition = 1; private const byte FlagHasRotationY = 2; private const byte FlagIsDead = 4; private const byte FlagHasAnimator = 8; private const byte FlagHasMoving = 16; private const byte FlagHasAttack = 32; private const byte FlagHasCowering = 64; private const byte FlagHasHostCombatAction = 128; private const byte BoolMovingTrue = 1; private const byte BoolAttackTrue = 2; private const byte BoolCoweringTrue = 4; private const byte Flag2HasHostCombatAim = 1; private const byte Flag2HasHostCombatAnimatorStates = 2; private const byte Flag2HasAiIntent = 4; private const byte Flag2HasAiIntentLookAt = 8; private const byte Flag3HasEnemyIntent = 1; private const byte Flag3HasHostTarget = 2; public static void WriteBatch(NetDataWriter w, IReadOnlyList snapshots) { WriteBatch(w, snapshots, 0, snapshots?.Count ?? 0); } public static void WriteBatch(NetDataWriter w, IReadOnlyList snapshots, int offset, int requestedCount) { if (snapshots == null) { w.Put((byte)8); w.Put(0); return; } if (offset < 0) { offset = 0; } if (offset > snapshots.Count) { offset = snapshots.Count; } int val = snapshots.Count - offset; int num = Math.Min(Math.Min(requestedCount, val), 128); if (num < 0) { num = 0; } w.Put((byte)8); w.Put(num); if (num > 0) { NetGameplayEnemyStateSnapshot netGameplayEnemyStateSnapshot = snapshots[offset]; w.Put(netGameplayEnemyStateSnapshot.SourcePeerId ?? ""); w.Put(netGameplayEnemyStateSnapshot.ChapterName ?? ""); w.Put(netGameplayEnemyStateSnapshot.LevelIndex); w.Put(netGameplayEnemyStateSnapshot.HasLevelSeed); w.Put(netGameplayEnemyStateSnapshot.LevelSeed); w.Put(netGameplayEnemyStateSnapshot.SourceRevision); w.Put(netGameplayEnemyStateSnapshot.SentAt); for (int i = 0; i < num; i++) { WriteOneCompact(w, snapshots[offset + i]); } } } public static bool TryReadBatch(NetDataReader r, out List snapshots) { snapshots = new List(); try { if (r.GetByte() != 8) { return false; } int num = r.GetInt(); if (num < 0 || num > 128) { return false; } if (num == 0) { return true; } string sourcePeerId = r.GetString(); string chapterName = r.GetString(); int levelIndex = r.GetInt(); bool hasLevelSeed = r.GetBool(); int levelSeed = r.GetInt(); int sourceRevision = r.GetInt(); float sentAt = r.GetFloat(); for (int i = 0; i < num; i++) { if (!TryReadOneCompact(r, sourcePeerId, chapterName, levelIndex, hasLevelSeed, levelSeed, sourceRevision, sentAt, out NetGameplayEnemyStateSnapshot s)) { return false; } snapshots.Add(s); } return true; } catch { snapshots.Clear(); return false; } } private static void WriteOneCompact(NetDataWriter w, NetGameplayEnemyStateSnapshot s) { //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_0313: Unknown result type (might be due to invalid IL or missing references) //IL_0324: Unknown result type (might be due to invalid IL or missing references) //IL_0335: Unknown result type (might be due to invalid IL or missing references) //IL_02d8: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Unknown result type (might be due to invalid IL or missing references) //IL_02fa: Unknown result type (might be due to invalid IL or missing references) //IL_0541: Unknown result type (might be due to invalid IL or missing references) //IL_0552: Unknown result type (might be due to invalid IL or missing references) //IL_0563: Unknown result type (might be due to invalid IL or missing references) //IL_0484: Unknown result type (might be due to invalid IL or missing references) //IL_0495: Unknown result type (might be due to invalid IL or missing references) //IL_04a6: Unknown result type (might be due to invalid IL or missing references) //IL_04bf: Unknown result type (might be due to invalid IL or missing references) //IL_04d0: Unknown result type (might be due to invalid IL or missing references) //IL_04e1: Unknown result type (might be due to invalid IL or missing references) //IL_04fa: Unknown result type (might be due to invalid IL or missing references) //IL_050b: Unknown result type (might be due to invalid IL or missing references) //IL_051c: Unknown result type (might be due to invalid IL or missing references) w.Put(s.Sequence); w.Put(s.SpawnIndex); byte b = 0; if (s.HasPosition) { b |= 1; } if (s.HasRotationY) { b |= 2; } if (s.IsDead) { b |= 4; } if (s.HasAnimatorState) { b |= 8; } if (s.HasAnimatorMovingBool) { b |= 0x10; } if (s.HasAnimatorAttackBool) { b |= 0x20; } if (s.HasAnimatorCoweringBool) { b |= 0x40; } if (s.HasHostCombatAction) { b |= 0x80; } byte b2 = 0; if (s.HasHostCombatAim) { b2 |= 1; } if (s.HostCombatAnimatorStateCount > 0) { b2 |= 2; } if (s.HasAiIntent) { b2 |= 4; } if (s.HasAiIntentLookAt) { b2 |= 8; } byte b3 = 0; if (s.HasEnemyIntent) { b3 |= 1; } if (s.HasHostTarget) { b3 |= 2; } w.Put(b); w.Put(b2); w.Put(b3); if (s.HasPosition) { w.Put(s.Position.x); w.Put(s.Position.y); w.Put(s.Position.z); } if (s.HasRotationY) { w.Put(s.RotationY); } if (s.HasAnimatorState) { int num = s.AnimatorLayer; if (num < 0) { num = 0; } if (num > 255) { num = 255; } w.Put((byte)num); w.Put(s.AnimatorFullPathHash); w.Put(s.AnimatorNormalizedTime); w.Put(s.AnimatorSpeed); byte b4 = 0; if (s.HasAnimatorMovingBool && s.AnimatorMovingBool) { b4 |= 1; } if (s.HasAnimatorAttackBool && s.AnimatorAttackBool) { b4 |= 2; } if (s.HasAnimatorCoweringBool && s.AnimatorCoweringBool) { b4 |= 4; } w.Put(b4); } if (s.HasHostCombatAction) { int num2 = s.HostCombatActionKind; if (num2 < 0) { num2 = 0; } if (num2 > 255) { num2 = 255; } w.Put((byte)num2); w.Put(s.HostCombatActionState); w.Put(s.HostCombatActionSequence); } if (s.HasHostCombatAim) { w.Put(s.HostCombatOriginPosition.x); w.Put(s.HostCombatOriginPosition.y); w.Put(s.HostCombatOriginPosition.z); w.Put(s.HostCombatAimPosition.x); w.Put(s.HostCombatAimPosition.y); w.Put(s.HostCombatAimPosition.z); } if (s.HasAiIntent) { int num3 = s.AiIntentKind; if (num3 < 0) { num3 = 0; } if (num3 > 255) { num3 = 255; } w.Put(s.AiIntentSequence); w.Put((byte)num3); w.Put(s.AiIntentDestination.x); w.Put(s.AiIntentDestination.y); w.Put(s.AiIntentDestination.z); } if (s.HasAiIntentLookAt) { w.Put(s.AiIntentLookAt.x); w.Put(s.AiIntentLookAt.y); w.Put(s.AiIntentLookAt.z); } if (s.HostCombatAnimatorStateCount > 0) { int num4 = s.HostCombatAnimatorStateCount; if (num4 < 0) { num4 = 0; } if (num4 > 4) { num4 = 4; } w.Put((byte)num4); for (int i = 0; i < num4; i++) { int num5 = s.HostCombatAnimatorLayers[i]; if (num5 < 0) { num5 = 0; } if (num5 > 255) { num5 = 255; } w.Put(s.HostCombatAnimatorPathHashes[i]); w.Put((byte)num5); w.Put(s.HostCombatAnimatorFullPathHashes[i]); w.Put(s.HostCombatAnimatorNormalizedTimes[i]); w.Put(s.HostCombatAnimatorSpeeds[i]); } } if (s.HasEnemyIntent) { int num6 = s.EnemyIntentKind; if (num6 < 0) { num6 = 0; } if (num6 > 255) { num6 = 255; } w.Put((byte)num6); w.Put(s.EnemyIntentSequence); w.Put(s.EnemyIntentDuration); w.Put(s.EnemyIntentWeaponActionState); byte b5 = 0; if (s.EnemyIntentHasTargetPosition) { b5 |= 1; } if (s.EnemyIntentHasAimPosition) { b5 |= 2; } if (s.EnemyIntentHasOriginPosition) { b5 |= 4; } w.Put(b5); if (s.EnemyIntentHasTargetPosition) { w.Put(s.EnemyIntentTargetPosition.x); w.Put(s.EnemyIntentTargetPosition.y); w.Put(s.EnemyIntentTargetPosition.z); } if (s.EnemyIntentHasAimPosition) { w.Put(s.EnemyIntentAimPosition.x); w.Put(s.EnemyIntentAimPosition.y); w.Put(s.EnemyIntentAimPosition.z); } if (s.EnemyIntentHasOriginPosition) { w.Put(s.EnemyIntentOriginPosition.x); w.Put(s.EnemyIntentOriginPosition.y); w.Put(s.EnemyIntentOriginPosition.z); } } if (s.HasHostTarget) { w.Put(s.HostTargetKind); w.Put(s.HostTargetPosition.x); w.Put(s.HostTargetPosition.y); w.Put(s.HostTargetPosition.z); } } private static bool TryReadOneCompact(NetDataReader r, string sourcePeerId, string chapterName, int levelIndex, bool hasLevelSeed, int levelSeed, int sourceRevision, float sentAt, out NetGameplayEnemyStateSnapshot s) { //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: 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_04e2: Unknown result type (might be due to invalid IL or missing references) //IL_042d: Unknown result type (might be due to invalid IL or missing references) //IL_0465: Unknown result type (might be due to invalid IL or missing references) //IL_049d: Unknown result type (might be due to invalid IL or missing references) s = new NetGameplayEnemyStateSnapshot(); try { s.Sequence = r.GetInt(); s.SourcePeerId = sourcePeerId; s.ChapterName = chapterName; s.LevelIndex = levelIndex; s.HasLevelSeed = hasLevelSeed; s.LevelSeed = levelSeed; s.SourceRevision = sourceRevision; s.SentAt = sentAt; s.SpawnIndex = r.GetInt(); s.Category = "Npc"; s.UnitIdentifier = ""; s.ActorName = ((s.SpawnIndex > 0) ? $"Npc#{s.SpawnIndex}" : "Npc"); byte b = r.GetByte(); byte b2 = r.GetByte(); byte b3 = r.GetByte(); s.HasPosition = (b & 1) != 0; if (s.HasPosition) { s.Position = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } s.HasRotationY = (b & 2) != 0; if (s.HasRotationY) { s.RotationY = r.GetFloat(); } s.IsDead = (b & 4) != 0; s.HasAnimatorState = (b & 8) != 0; if (s.HasAnimatorState) { s.AnimatorLayer = r.GetByte(); s.AnimatorFullPathHash = r.GetInt(); s.AnimatorShortNameHash = 0; s.AnimatorNormalizedTime = r.GetFloat(); s.AnimatorSpeed = r.GetFloat(); byte b4 = r.GetByte(); s.HasAnimatorMovingBool = (b & 0x10) != 0; s.AnimatorMovingBool = (b4 & 1) != 0; s.HasAnimatorAttackBool = (b & 0x20) != 0; s.AnimatorAttackBool = (b4 & 2) != 0; s.HasAnimatorCoweringBool = (b & 0x40) != 0; s.AnimatorCoweringBool = (b4 & 4) != 0; } s.HasHostCombatAction = (b & 0x80) != 0; if (s.HasHostCombatAction) { s.HostCombatActionKind = r.GetByte(); s.HostCombatActionState = r.GetInt(); s.HostCombatActionSequence = r.GetInt(); } s.HasHostCombatAim = (b2 & 1) != 0; if (s.HasHostCombatAim) { s.HostCombatOriginPosition = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); s.HostCombatAimPosition = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } s.HasAiIntent = (b2 & 4) != 0; if (s.HasAiIntent) { s.AiIntentSequence = r.GetInt(); s.AiIntentKind = r.GetByte(); s.AiIntentDestination = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } s.HasAiIntentLookAt = (b2 & 8) != 0; if (s.HasAiIntentLookAt) { s.AiIntentLookAt = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } if ((b2 & 2) != 0) { int num = r.GetByte(); if (num < 0 || num > 4) { return false; } s.HostCombatAnimatorStateCount = num; for (int i = 0; i < num; i++) { s.HostCombatAnimatorPathHashes[i] = r.GetInt(); s.HostCombatAnimatorLayers[i] = r.GetByte(); s.HostCombatAnimatorFullPathHashes[i] = r.GetInt(); s.HostCombatAnimatorNormalizedTimes[i] = r.GetFloat(); s.HostCombatAnimatorSpeeds[i] = r.GetFloat(); } } s.HasEnemyIntent = (b3 & 1) != 0; if (s.HasEnemyIntent) { s.EnemyIntentKind = r.GetByte(); s.EnemyIntentSequence = r.GetInt(); s.EnemyIntentDuration = r.GetFloat(); s.EnemyIntentWeaponActionState = r.GetInt(); byte b5 = r.GetByte(); s.EnemyIntentHasTargetPosition = (b5 & 1) != 0; if (s.EnemyIntentHasTargetPosition) { s.EnemyIntentTargetPosition = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } s.EnemyIntentHasAimPosition = (b5 & 2) != 0; if (s.EnemyIntentHasAimPosition) { s.EnemyIntentAimPosition = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } s.EnemyIntentHasOriginPosition = (b5 & 4) != 0; if (s.EnemyIntentHasOriginPosition) { s.EnemyIntentOriginPosition = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } } s.HasHostTarget = (b3 & 2) != 0; if (s.HasHostTarget) { s.HostTargetKind = r.GetByte(); s.HostTargetPosition = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } return true; } catch { s = new NetGameplayEnemyStateSnapshot(); return false; } } } internal sealed class NetGameplayEntityId { public string LocalInstanceId { get; private set; } = "null"; public int UnityInstanceId { get; private set; } public string TypeName { get; private set; } = ""; public string FullTypeName { get; private set; } = ""; public string UnitIdentifier { get; private set; } = ""; public string UnitGlobalId { get; private set; } = ""; public string CandidateKey { get; private set; } = ""; public static NetGameplayEntityId FromObject(object? obj) { NetGameplayEntityId netGameplayEntityId = new NetGameplayEntityId(); if (obj == null) { netGameplayEntityId.CandidateKey = "null"; return netGameplayEntityId; } Type type = obj.GetType(); netGameplayEntityId.TypeName = type.Name; netGameplayEntityId.FullTypeName = type.FullName ?? type.Name; netGameplayEntityId.LocalInstanceId = ReverseProbeFormatter.GetInstanceId(obj); Object val = (Object)((obj is Object) ? obj : null); int result; if (val != null) { netGameplayEntityId.UnityInstanceId = val.GetInstanceID(); } else if (int.TryParse(netGameplayEntityId.LocalInstanceId, out result)) { netGameplayEntityId.UnityInstanceId = result; } object obj2 = TryGetMemberValue(obj, "UnitIdentifier") ?? TryGetMemberValue(obj, "unitIdentifier") ?? TryGetMemberValue(obj, "UnitId") ?? TryGetMemberValue(obj, "unitId") ?? TryGetMemberValue(obj, "Id") ?? TryGetMemberValue(obj, "id"); netGameplayEntityId.UnitIdentifier = CleanValue(obj2?.ToString()); netGameplayEntityId.UnitGlobalId = CleanValue(TryGetGlobalId(obj2)); string text = ((!string.IsNullOrWhiteSpace(netGameplayEntityId.UnitGlobalId)) ? ("global=" + netGameplayEntityId.UnitGlobalId) : ((!string.IsNullOrWhiteSpace(netGameplayEntityId.UnitIdentifier)) ? ("unit=" + netGameplayEntityId.UnitIdentifier) : ("local=" + netGameplayEntityId.LocalInstanceId))); netGameplayEntityId.CandidateKey = netGameplayEntityId.TypeName + "|" + text; return netGameplayEntityId; } public string FormatCompact() { string text = (string.IsNullOrWhiteSpace(UnitIdentifier) ? "-" : UnitIdentifier); string text2 = (string.IsNullOrWhiteSpace(UnitGlobalId) ? "-" : UnitGlobalId); return $"type={TypeName} local={LocalInstanceId} unity={UnityInstanceId} unitId={text} globalId={text2}"; } private static object? TryGetMemberValue(object obj, string memberName) { try { Type type = obj.GetType(); PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { return property.GetValue(obj, null); } FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(obj); } } catch { } return null; } private static string TryGetGlobalId(object? unitIdentifier) { if (unitIdentifier == null) { return ""; } try { string text = CleanValue((unitIdentifier.GetType().GetMethod("AsGlobalId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null)?.Invoke(unitIdentifier, null))?.ToString()); if (!string.IsNullOrWhiteSpace(text)) { return text; } } catch { } return CleanValue((TryGetMemberValue(unitIdentifier, "GlobalId") ?? TryGetMemberValue(unitIdentifier, "globalId"))?.ToString()); } private static string CleanValue(string? value) { if (string.IsNullOrWhiteSpace(value)) { return ""; } string text = value.Trim(); switch (text) { case "null": case "": case "?": return ""; default: if (text.Length > 96) { text = text.Substring(0, 96); } return text; } } } internal sealed class NetGameplayEntitySnapshot { public NetGameplayEntityId EntityId { get; set; } = NetGameplayEntityId.FromObject(null); public string Category { get; set; } = "Unit"; public string ActorName { get; set; } = ""; public int SyncCategory { get; set; } public string Source { get; set; } = ""; public int SpawnIndex { get; set; } public bool HasPosition { get; set; } public Vector3 Position { get; set; } public bool HasInitialPosition { get; set; } public Vector3 InitialPosition { get; set; } public string SceneKey { get; set; } = ":-1"; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public string GameState { get; set; } = ""; public float FirstSeenAt { get; set; } public float LastSeenAt { get; set; } public int DamageCount { get; set; } public bool IsDead { get; set; } public bool PendingStableContextLog { get; set; } private WeakReference? RuntimeObjectRef { get; set; } public string PositionText { get { //IL_0014: 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_0034: Unknown result type (might be due to invalid IL or missing references) if (!HasPosition) { return "(?)"; } return $"({Position.x:F2},{Position.y:F2},{Position.z:F2})"; } } public string SeedText { get { if (!HasLevelSeed) { return "?"; } return LevelSeed.ToString(); } } public void SetRuntimeObject(object? runtimeObject) { if (runtimeObject != null) { RuntimeObjectRef = new WeakReference(runtimeObject); } } public bool TryGetRuntimeObject(out object? runtimeObject) { runtimeObject = null; if (RuntimeObjectRef == null) { return false; } if (!RuntimeObjectRef.TryGetTarget(out object target) || target == null) { return false; } runtimeObject = target; return true; } public string FormatSpawnLine(string phase) { return $"[GameplayProbe] Spawn{phase} idx={SpawnIndex} candidate={EntityId.CandidateKey} {EntityId.FormatCompact()} category={Category} actor={ActorName} pos={PositionText} scene={SceneKey} seed={SeedText} state={GameState} source={Source}"; } public string FormatDeathLine(string source, float lifetime) { return $"[GameplayProbe] Death idx={SpawnIndex} candidate={EntityId.CandidateKey} category={Category} actor={ActorName} pos={PositionText} scene={SceneKey} seed={SeedText} damageCount={DamageCount} lifetime={lifetime:F1}s source={source}"; } public string FormatDamageLine(string source, float damage, object? damageType) { return $"[GameplayProbe] Damage idx={SpawnIndex} candidate={EntityId.CandidateKey} category={Category} actor={ActorName} dmg={damage:F2} type={damageType} count={DamageCount} scene={SceneKey} seed={SeedText} source={source}"; } } internal static class NetGameplayProbeManager { private sealed class EnemyStateTarget { public NetGameplayEntitySnapshot Snapshot { get; set; } public NetGameplayEnemyStateSnapshot HostSnapshot { get; set; } public Vector3 StartPosition { get; set; } public Vector3 Position { get; set; } public bool HasPosition { get; set; } public bool HasRotationY { get; set; } public float RotationY { get; set; } public int Sequence { get; set; } public float PlaybackStartedAt { get; set; } public float PlaybackDuration { get; set; } public float LastUpdatedAt { get; set; } public object? RuntimeObject { get; set; } public Transform? Transform { get; set; } public bool AnimationAppliedForSequence { get; set; } public bool LoggedSnap { get; set; } } private sealed class EnemyPuppetRecord { public string Key { get; set; } = ""; public NetGameplayEntitySnapshot Snapshot { get; set; } public object? Npc { get; set; } public object? AiAgent { get; set; } public object? MovementDriver { get; set; } public int NpcId { get; set; } public int AiAgentId { get; set; } public int MovementDriverId { get; set; } public bool Applied { get; set; } public float LastSeenAt { get; set; } public float LastAppliedAt { get; set; } public bool? OriginalDisableVerifyPosition { get; set; } public bool? OriginalPreventNavMeshActivation { get; set; } public bool? OriginalRigidbodyIsKinematic { get; set; } public NetGameplayEnemyStateSnapshot? LastHostSnapshot { get; set; } public int LastAppliedAnimatorFullPathHash { get; set; } public float LastAnimatorApplyAt { get; set; } public bool LastLoggedAnimatorState { get; set; } public Animator? CachedAnimator { get; set; } public int CachedAnimatorId { get; set; } public Renderer[]? CachedRenderers { get; set; } public bool RenderersCached { get; set; } public bool AnimatorParamCacheInitialized { get; set; } public int MovingParamHash { get; set; } public int AttackParamHash { get; set; } public int CoweringParamHash { get; set; } public bool HasMovingParam { get; set; } public bool HasAttackParam { get; set; } public bool HasCoweringParam { get; set; } public List CombatTriggerParamHashes { get; } = new List(); public int LastAppliedCombatTriggerSequence { get; set; } public float LastAppliedCombatTriggerAt { get; set; } public int LastAppliedCombatVisualActionSequence { get; set; } public float LastAppliedCombatVisualActionAt { get; set; } public int LastAppliedCombatAnimatorFallbackSequence { get; set; } public float LastAppliedCombatAnimatorFallbackAt { get; set; } public int LastAppliedGenericCombatAnimatorSignature { get; set; } public float LastAppliedGenericCombatAnimatorAt { get; set; } public int LastVisualProjectileSequence { get; set; } public float LastVisualProjectileAt { get; set; } public bool HasMotionDerivedMoving { get; set; } public bool MotionDerivedMoving { get; set; } public float LastMotionDerivedMovingAt { get; set; } public bool HasLastActionState { get; set; } public bool LastActionState { get; set; } public float LastActionStateFlipAt { get; set; } public float LastRawActionTrueAt { get; set; } public int LastReplayHash { get; set; } public int ReplayCount { get; set; } public float LastTargetAuthorityApplyAt { get; set; } public float LastTargetAuthorityLogAt { get; set; } public int LastTargetAuthorityClearedCount { get; set; } public string LastTargetAuthoritySummary { get; set; } = ""; public int TargetAuthorityBarrenStreak { get; set; } public bool TargetAuthoritySectionScanDisabled { get; set; } public float LastCombatProbeLogAt { get; set; } public int LastAppliedAiIntentSequence { get; set; } public float LastAppliedAiIntentAt { get; set; } public float LastIntentCorrectionAt { get; set; } public ClientEnemyControlMode ControlMode { get; set; } public Vector3 LastAppliedMoveTargetPosition { get; set; } } private sealed class HostEnemyAiIntent { public float ExpiresAt { get; set; } public string Source { get; set; } = ""; public int Sequence { get; set; } public int Kind { get; set; } public bool HasDestination { get; set; } public Vector3 Destination { get; set; } public bool HasLookAt { get; set; } public Vector3 LookAt { get; set; } } private sealed class HostEnemyCombatAction { public float ExpiresAt { get; set; } public string Source { get; set; } = ""; public int Sequence { get; set; } public int Kind { get; set; } public int State { get; set; } public bool HasAim { get; set; } public Vector3 OriginPosition { get; set; } public Vector3 AimPosition { get; set; } } private sealed class ClientVisualProjectile { public GameObject? GameObject { get; set; } public Vector3 Position { get; set; } public Vector3 Target { get; set; } public float Speed { get; set; } public float ExpiresAt { get; set; } } private sealed class AnimatorBoolReset { public Animator? Animator { get; set; } public int Hash { get; set; } public float ResetAt { get; set; } } private enum ClientEnemyControlMode { PassiveSnapshot, IntentLocomotion, AuthorizedCombat, Lunge, Dead } private sealed class ClientEnemyAuthorizedIntentWindow { public int NpcId { get; set; } public int Sequence { get; set; } public int Kind { get; set; } public float ExpiresAt { get; set; } public int WeaponActionState { get; set; } public bool HasAimPosition { get; set; } public Vector3 AimPosition { get; set; } public bool HasOriginPosition { get; set; } public Vector3 OriginPosition { get; set; } public bool RootReplayed { get; set; } } private sealed class HostEnemyStateSendCache { public float LastSentAt { get; set; } public bool HasPosition { get; set; } public Vector3 Position { get; set; } public bool HasRotationY { get; set; } public float RotationY { get; set; } public bool IsDead { get; set; } public bool HasAnimatorState { get; set; } public int AnimatorFullPathHash { get; set; } public float AnimatorNormalizedTime { get; set; } public bool HasMovingBool { get; set; } public bool MovingBool { get; set; } public bool HasAttackBool { get; set; } public bool AttackBool { get; set; } public bool HasCoweringBool { get; set; } public bool CoweringBool { get; set; } public bool HasHostCombatAction { get; set; } public int HostCombatActionKind { get; set; } public int HostCombatActionState { get; set; } public int HostCombatActionSequence { get; set; } public bool HasHostCombatAim { get; set; } public Vector3 HostCombatOriginPosition { get; set; } public Vector3 HostCombatAimPosition { get; set; } public int HostCombatAnimatorStateCount { get; set; } public bool HasAiIntent { get; set; } public int AiIntentSequence { get; set; } public int AiIntentKind { get; set; } public Vector3 AiIntentDestination { get; set; } public bool HasAiIntentLookAt { get; set; } public Vector3 AiIntentLookAt { get; set; } public int[] HostCombatAnimatorPathHashes { get; } = new int[4]; public int[] HostCombatAnimatorFullPathHashes { get; } = new int[4]; public float[] HostCombatAnimatorNormalizedTimes { get; } = new float[4]; } private struct PendingHostBind { public int HostSpawnIndex; public string UnitIdentifier; public string Category; public bool HasPosition; public Vector3 Position; public float RecordedAt; } private sealed class PendingDeadEntry { public float MarkedAt; public string Reason = ""; public bool VisualFallbackAttempted; } private sealed class DriftStat { public string Unit = ""; public int Count; public float SumHostError; public float MaxHostError; public float LastHostError; public int Snaps; public int Repeated; } private sealed class PendingHitFlash { public Renderer[] Renderers = Array.Empty(); public float ResetAt; } private struct BindingTombstone { public int HostSpawnIndex; public string LocalKey; public string UnitIdentifier; public float ReleasedAt; public string ReleaseReason; } private sealed class PendingHealthEntry { public NetHostEnemyHealthState State { get; set; } public float ReceivedAt { get; set; } } private const int CombatActionNone = 0; private const int CombatActionAttackAnimation = 1; private const int CombatActionShoot = 2; private const int CombatActionSetShooting = 3; private const int CombatActionTriggerWeapon = 4; private const int CombatActionTriggerShootFromAnimation = 5; private const int CombatActionStartMeleeDamage = 6; private const int CombatActionEndMeleeDamage = 7; private const int CombatActionSetRangedAttacking = 8; private const int CombatActionSetAttacking = 9; private const int CombatActionDoneAttacking = 10; private const int CombatActionDoneShooting = 11; private const bool EaSelfPlayAttackAnimation = false; private const int AiIntentKindNone = 0; private const int AiIntentKindDestination = 1; private const int AiIntentKindLookAt = 2; private const int AiIntentKindCombat = 3; private const int EnemyIntentKindNone = 0; private const int EnemyIntentKindAttackMelee = 4; private const int EnemyIntentKindAttackRanged = 5; private const int EnemyIntentKindWeaponAction = 6; private const int SyncCatUnknown = 0; private const int SyncCatCombatEnemy = 1; private const int SyncCatTrader = 2; private const int SyncCatInteractNpc = 3; private const int SyncCatGhost = 4; private const int SyncCatEventNpc = 5; private const int SyncCatAmbient = 6; private const int SyncCatHazard = 7; private static readonly Dictionary EntitiesByLocalId = new Dictionary(); private static readonly Dictionary PendingEnemyStateTargets = new Dictionary(); private static readonly Dictionary ActiveEnemyPuppets = new Dictionary(); private static readonly Dictionary ActiveEnemyPuppetsByNpcId = new Dictionary(); private static readonly Dictionary HostEnemyStateSendCacheBySpawnIndex = new Dictionary(); private static readonly Dictionary HostEnemyCombatActionsByNpcId = new Dictionary(); private static readonly Dictionary HostEnemyAiIntentsByNpcId = new Dictionary(); private static readonly Dictionary EnemyCombatProbeLastLogAtById = new Dictionary(); private static readonly Dictionary ProjectileProbeLastLogAtByKey = new Dictionary(); private static readonly Dictionary HostEnemyDamageLastAtByKey = new Dictionary(); private static readonly List ClientVisualProjectiles = new List(32); private static readonly List PendingAnimatorBoolResets = new List(64); private static readonly List ScratchEnemyStateTargetKeys = new List(64); private static readonly HashSet ClientPuppetNpcIds = new HashSet(); private static readonly HashSet ClientPuppetAiAgentIds = new HashSet(); private static readonly HashSet BarrenTargetAuthorityAiIds = new HashSet(); private static readonly HashSet ClientPuppetMovementDriverIds = new HashSet(); private static readonly string[] ClientPuppetExtraLocomotionControllers = new string[1] { "CrawWatcher" }; private static readonly HashSet _visualOnlyFlyerCollidersDisabled = new HashSet(); private static readonly HashSet PendingStableSpawnLogs = new HashSet(); private static readonly Dictionary _clientAuthorizedIntentByNpcId = new Dictionary(); private static readonly Dictionary ClientHostToLocalKeyByHostSpawnIndex = new Dictionary(); private static int _clientRosterRevision = -1; private static readonly Dictionary ClientLocalKeyToHostSpawnIndex = new Dictionary(); private static readonly Dictionary _runtimeSpawnBindingsByHostIdx = new Dictionary(); private static readonly Dictionary _clientLastSnapshotRecvByHostIdx = new Dictionary(); private static readonly Dictionary _pendingHostBindLedger = new Dictionary(); private const float RetroBindPosTolerance = 5f; private const float RetroBindLedgerTtlSeconds = 30f; private static int _retroBindLedgerAdds; private static int _retroBindSuccess; private static int _retroBindAmbiguousDeferred; private static int _retroBindLedgerExpired; private static readonly HashSet ClientQuarantinedEntities = new HashSet(); private static bool _hostPlayerPositionHintValid; private static Vector3 _hostPlayerPositionHint; private static readonly List _remoteInterestPositions = new List(); private static readonly Dictionary FarEnemyLastSentAtBySpawnIndex = new Dictionary(); private static readonly Dictionary ClientAttackPhaseLastSeqBySpawnIndex = new Dictionary(); private static int _clientPuppetDamageSuppressed; private static int _hostAttackPhaseEventsSent; private static int _clientAttackPhaseEventsReceived; private static int _clientAttackPhaseAnimatorApplies; private static int _interestManagementFarSkipped; private static int _interestEngagedExempt; private static readonly Dictionary ClientPuppetHealthBySpawnIndex = new Dictionary(); private static readonly Dictionary ClientPuppetMaxHealthBySpawnIndex = new Dictionary(); private static readonly Dictionary HostDamageEventSeqBySpawnIndex = new Dictionary(); private static int _hostEnemyDamageEventsSent; private static int _hostEnemyHealthStatesSent; private static int _clientEnemyDamageEventsReceived; private static int _clientEnemyHealthStatesReceived; private static int _clientEnemyHealthStatesApplied; private static int _clientEnemyHealthApplySkippedNoBinding; private static int _hostDeathAppliedByRosterBinding; private static int _hostDeathAppliedAfterSnap; private static int _hostDeathRejectedNoBinding; private static int _hostDeathLateBindAttempts; private static int _hostDeathLateBindSuccess; private static int _clientDeathClaimsSuppressedByHostAuthority; private static int _hostDeathTombstoneHit; private static int _hostDeathNeverBound; private static int _clientHealthApplyDisabled; private static int _clientHealthNoCurrentHp; private static int _clientHealthNoEntity; private static int _clientHealthNoRuntimeObj; private static int _clientHealthUnityDestroyed; private static int _clientHealthNoStats; private static int _clientHealthSetStatusMissing; private static int _clientHealthEnumArgFailed; private static int _clientHealthSetStatusFailed; private static int _clientHealthWriteFailed; private static int _clientHealthWriteReadBackUnchanged; private static int _puppetReleaseSuppressedAwaitingHost; private static int _puppetStaleReleaseSuppressed; private static int _clientHitRequestSeq; private static int _clientHitRequestsSent; private static int _clientHitRequestsSkippedNoPuppet; private static int _clientHitRequestsSkippedNoBinding; private static int _clientPuppetNonPlayerDamageIgnored; private static int _puppetDamageSourceSamplesLogged; private static int _suppressedSourceSamplesLogged; private static int _hostHitRequestsRecv; private static int _hostHitRequestsRejectedScene; private static int _hostHitRequestsRejectedNoTarget; private static int _hostHitRequestsRejectedTypeMismatch; private static int _hostHitRequestsRejectedDead; private static int _hostHitRequestsRejectedRateLimit; private static int _hostHitRequestsAccepted; private static int _hostHitRequestsDamageApplied; private static int _hostHitRequestsDamageFailed; private static readonly Dictionary _hostHitRequestLastAtByHostIdx = new Dictionary(); private static readonly Dictionary _hostHitPendingDamageByHostIdx = new Dictionary(); private static readonly Dictionary _hostHitPendingDamagePeerByHostIdx = new Dictionary(); private static int _hostHitRequestsCoalesced; private static int _hostHitResultHealthStateSent; private static int _clientLocalHitPredicted; private static int _clientLocalHitConfirmed; private static readonly Dictionary _clientPendingHitByHostIdx = new Dictionary(); private const float ClientHitConfirmWindowSeconds = 2f; private static readonly HashSet _clientTerminalDeadHostIdx = new HashSet(); private static readonly Dictionary _clientPendingDeadHostIdx = new Dictionary(); private static int _pendingDeadMarked; private static int _pendingDeadHostDeathApplied; private static int _pendingDeadVisualFallbackAttempted; private static int _pendingDeadVisualFallbackSucceeded; private static int _pendingDeadVisualFallbackFailed; private static int _pendingDeadBlockedHitFlash; private static int _pendingDeadBlockedClientHit; private static int _terminalDeadMarkedAfterDie; private static int _terminalDeadMarkedAfterVisualFallback; private static int _terminalDeadMarkedFromHealthOnly; private static int _terminalDeadBlockedAttackPhase; private static int _terminalDeadBlockedGenericReplay; private static int _terminalDeadBlockedMovement; private static int _terminalDeadBlockedHitReaction; private static int _terminalDeadHealthUpdatesIgnored; private static int _terminalDeadDeathReapplySkipped; private static int _clientHitSkipPendingDead; private static int _clientHitSkipTerminalDead; private static int _hostManifestBuilt; private static int _clientManifestBuilt; private static int _hostManifestSent; private static int _clientManifestReceived; private static int _manifestHashMatch; private static int _manifestHashMismatch; private static int _generationHashMatch; private static int _generationHashMismatch; private static int _runtimeHashMatch; private static int _runtimeHashMismatch; private static string _lastGenRunKey = ""; private static List? _lastGenSignature; private static int _generationHashStableSameRevision; private static int _generationHashChangedSameRevision; private static int _manifestRoomMismatch; private static int _manifestUnitMismatch; private static int _manifestSpecialMismatch; private static int _manifestHostOnlyUnits; private static int _manifestClientOnlyUnits; private static int _manifestClientOnlyCombatQuarantined; private static int _manifestHostEnemyBoundExisting; private static int _manifestHostEnemyBindFailedNoCandidate; private static int _manifestHostEnemyBindFailedAmbiguous; private static int _manifestHostEnemyModifierMismatch; private static int _manifestSeedMismatch; private static float _clientFirstLevelEntitySeenAt; private static bool _clientManifestProvisionalLogged; private static int _manifestDeferredSceneMismatch; private static int _manifestDeferredLevelMismatch; private static int _manifestDeferredSeedMismatch; private static int _manifestProcessedMatchingRun; private static int _manifestDroppedStaleRun; private static int _manifestAcceptedAfterRunStateFix; private static int _manifestDeferredLevelMismatchAfterFix; private static NetLevelManifest? _deferredHostManifest; private static int _manifestPlayerExcluded; private static int _quarantinePlayerSkipped; private static int _manifestClientOnlyQuarantineNoLocalKey; private static int _manifestClientOnlyQuarantineNoRuntime; private static int _manifestClientOnlyAlreadyQuarantined; private static int _manifestClientOnlyNonCombatSkipped; private static int _manifestClientOnlyQuarantineApplied; private static int _manifestClientOnlyQuarantineFailed; private static int _currentClientOnlyUnits; private static int _currentClientOnlyCombatUnits; private static int _currentClientOnlyNonCombatUnits; private static int _currentClientOnlyQuarantineApplied; private static int _currentClientOnlyAlreadyQuarantined; private static int _currentClientOnlyNoRuntime; private static int _totalClientOnlyQuarantineApplied; private static int _hostHitVisualPlayed; private static int _hostHitVisualFailedNoNpc; private static int _hostHitVisualEventSent; private static int _hostHitVisualSeq; private static int _clientHitVisualEventRecv; private static int _clientHitVisualPlayed; private static int _clientHitVisualSkipNoBinding; private static int _clientHitVisualSkipPendingDead; private static int _clientHitVisualSkipTerminalDead; private static int _clientHitVisualDuplicateSeq; private static int _hostHitFatalVisualPlayed; private static int _clientHitFatalVisualPlayed; private static int _clientHitVisualSkippedFatalTerminalDead; private static readonly Dictionary _clientHitVisualLastSeqByHostIdx = new Dictionary(); private static readonly Dictionary _driftByHostIdx = new Dictionary(); private static int _puppetTransformCorrectionLarge; private static int _puppetTransformCorrectionRepeated; private static int _localNavMeshStillEnabledOnHostBound; private static int _localAiCanMoveOnHostBound; private static int _rvoStillEnabledOnHostBound; private static int _hostBoundPuppetsAudited; private static int _levelGenFinalizeConnectionTraced; private static int _levelGenConnectorFinalizeSpawnTraced; private static int _levelGenExtraRoomTraced; private static int _levelGenDoorBlockerRecorded; private static int _levelGenStepTraced; private static int _levelGenNodeCoroutineTraced; private static readonly List _pendingHitFlashes = new List(32); private static readonly Dictionary _clientDamageVisualLastSeqByHostIdx = new Dictionary(); private static readonly int _hitFlashColorPropId = Shader.PropertyToID("_Color"); private static readonly int _hitFlashEmissionPropId = Shader.PropertyToID("_EmissionColor"); private static readonly int _hitFlashHitTimePropId = Shader.PropertyToID("_HitTime"); private static readonly int _hitFlashHitTypePropId = Shader.PropertyToID("_HitType"); private static readonly Dictionary _npcDoWhiteFlashCache = new Dictionary(); private static readonly Dictionary _npcSetHitEffectCache = new Dictionary(); private static readonly HashSet _flashApiLoggedTypes = new HashSet(); private static int _damageVisualReactionsPlayed; private static int _dmgVisualNativeDoWhiteFlash; private static int _dmgVisualNativeSetHitEffect; private static int _dmgVisualMaterialHitTime; private static int _dmgVisualFallbackColor; private static int _dmgVisualFailedNoNpc; private static int _dmgVisualFailedNoMethod; private static int _dmgVisualFailedNoMaterial; private static int _damageVisualReactionSkippedTerminalDead; private static int _damageVisualReactionSkippedPendingDead; private static int _damageVisualReactionSkippedNoBinding; private static int _damageVisualReactionSkippedDuplicateSeq; private static readonly Dictionary _bindingTombstones = new Dictionary(); private const float TombstoneMaxAge = 60f; private static readonly Dictionary _pendingHealthByHostIdx = new Dictionary(); private const float PendingHealthMaxAge = 8f; private static int _clientHealthStatesPendingQueued; private static int _clientHealthStatesPendingApplied; private static int _clientHealthStatesPendingExpired; private static int _rosterOneToOneBound; private static int _rosterPreservedBound; private static int _deathAppliedByBinding; private static int _deathAppliedBoundDespiteDrift; private static int _deathBoundDriftWarning; private static int _deathRejectedUnboundNetEntity; private static int _clientOnlyCombatQuarantined; private static int _quarantinedCombatSuppressed; private static int _nextSpawnIndex; private static int _spawnEvents; private static int _newSpawns; private static int _duplicateSpawnEvents; private static int _damageEvents; private static int _deathEvents; private static int _suppressedContextEvents; private static int _deathMirrorSequence; private static int _hostDeathApplyDepth; private static int _enemyStateTargetsQueued; private static int _enemyStateTargetsApplied; private static int _enemyStateTargetsSnapped; private static int _clientEnemyPuppetsActivated; private static int _clientEnemyPuppetsReleased; private static int _clientEnemyPuppetTargetClears; private static int _clientEnemyPuppetTargetBlocks; private static int _clientEnemyPuppetCombatBlocks; private static int _enemyCombatProbeEvents; private static int _enemyTargetLocalCleared; private static int _enemyTargetLocalOverwrite; private static int _enemyTargetNoKnownMember; private static int _enemyTargetBossMembersDiscovered; private static int _enemyTargetBossSuppressionApplied; private static int _enemyTargetSuppressionFailed; private static int _enemyTargetHostTargetApplied; private static readonly HashSet _targetProbeDumpedTypes = new HashSet(); private static int _hostEnemyCombatActionSequence; private static int _hostEnemyCombatActionMarks; private static int _clientCombatAnimatorTriggerApplies; private static int _clientCombatAnimatorStateApplies; private static int _clientCombatVisualActionReplays; private static int _clientCombatAnimatorFallbacks; private static int _clientVisualProjectileMirrors; private static int _projectileProbeEvents; private static int _hostEnemyDamageChecks; private static int _hostEnemyDamageHits; private static int _clientGenericCombatAnimatorStateApplies; private static int _hostEnemyAiIntentMarks; private static int _clientEnemyAiIntentApplies; private static int _clientEnemyAiIntentCorrections; private static int _clientPuppetIntentReplayDepth; private static int _clientPuppetInternalTargetClearDepth; private static int _clientPuppetCombatVisualReplayDepth; private static int _clientIntentWindows; private static int _clientAuthorizedAttackPasses; private static int _clientUnauthorizedAttackBlocks; private static int _clientAuthorizedMeleeEvents; private static int _clientSuppressedNativeEnemyDamage; private static int _clientEnemyNativeDamageSuppressDepth; private static int _clientCombatRootReplays; private static int _clientCombatRootReplaySkippedDuplicate; private static int _clientAuthorizedChildPasses; private static int _clientBlockedSpontaneousCombatCalls; private static int _driftSkippedIntentLocomotion; private static int _driftSkippedAuthorizedCombat; private static int _hardDriftCorrections; private static int _softDriftCorrections; private static int _genericCombatStateSkippedDuringAuthorizedIntent; private static int _clientAuthorizedCombatRootReplayDepth; private static int _traderExcludedFromEnemySync; private static int _nonCombatExcludedFromEnemySync; private static int _deathClaimRejectedNonCombat; private static int _combatProbeRejectedNonCombat; private static int _clientRootReplayAttempts; private static int _clientRootReplaySkippedDuplicate; private static int _clientRootReplayUnsupported; private static int _clientRootReplayFailed; private static int _clientAuthorizedChildAfterRoot; private static int _clientChildBlockedBeforeRootReplay; private static int _entityTypeMismatchRejected; private static int _deathMirrorRejectedTypeMismatch; private static int _stateApplyRejectedTypeMismatch; private static int _hostRosterRecordsSent; private static int _clientRosterRecordsReceived; private static int _clientRosterBound; private static int _clientRosterHostOnlyMissing; private static int _clientRosterClientOnlyQuarantined; private static int _clientRosterTypeMismatch; private static int _clientRosterFingerprintMismatch; private static float _lastSummaryAt; private static readonly Dictionary _enemyPosDiagLastAtByIdx = new Dictionary(); private static float _lastSnapCollLogAt; private static int _snapCollExclLogged; private static readonly string[] TargetAuthorityMemberNames = new string[22] { "OverrideTarget", "overrideTarget", "targetUnit", "TargetUnit", "target", "Target", "currentTarget", "CurrentTarget", "aimTarget", "AimTarget", "targetTransform", "TargetTransform", "targetObject", "TargetObject", "playerTarget", "PlayerTarget", "lastTarget", "LastTarget", "attackTarget", "AttackTarget", "shootTarget", "ShootTarget" }; private static float _lastHitchLogTime = -999f; private static int _lastHitchGc0 = -1; private static int _releasedPuppetsOnHostDeath; private static float _rtSpawnProbeNext; private static int _evictedStaleHostBindings; private static int _retroBindDiagLogged; private static readonly Dictionary _unitGetHpMiCache = new Dictionary(); private static readonly Dictionary _unitGetNormHpMiCache = new Dictionary(); private static readonly Dictionary _unitStatsPropCache = new Dictionary(); private static readonly Dictionary _unitStatsFieldCache = new Dictionary(); private static readonly Dictionary _statsGetStatusCache = new Dictionary(); private static readonly Dictionary _statsGetAttrCache = new Dictionary(); private static readonly Dictionary _statsSetStatusCache = new Dictionary(); private static readonly HashSet _nativeApiLoggedTypes = new HashSet(); private const int EntityStatusCurrentHealth = 92; private const int EntityStatMaxHealth = 60; private static readonly string[] NpcHealthFieldNamesExact = new string[16] { "health", "Health", "currentHealth", "CurrentHealth", "_health", "hp", "HP", "Hp", "_hp", "hitPoints", "HitPoints", "_hitPoints", "lifePoints", "LifePoints", "vitality", "Vitality" }; private static readonly string[] NpcMaxHpFieldNamesExact = new string[11] { "maxHealth", "MaxHealth", "maxHp", "MaxHp", "_maxHealth", "maxHP", "MaxHP", "maxHitPoints", "MaxHitPoints", "maxVitality", "MaxVitality" }; private static readonly string[] HealthKeywords = new string[6] { "health", "hp", "hitpoint", "lifepoint", "vitality", "leben" }; private static readonly Dictionary DiscoveredCurrentHpMember = new Dictionary(); private static readonly Dictionary DiscoveredMaxHpMember = new Dictionary(); private static readonly Dictionary _attackPhaseLastSentBySpawnIdx = new Dictionary(); private static readonly Dictionary _enemyDmgEventLastSentBySpawnIdx = new Dictionary(); private static readonly Dictionary _enemyHealthLastSentBySpawnIdx = new Dictionary(); private static int _attackPhaseThrottled; private static int _enemyDamageEventThrottled; private static int _interestDiagLogged; private static void NoteDrift(string key, float hostError, bool snapped, string unit) { if (!string.IsNullOrEmpty(key) && ClientLocalKeyToHostSpawnIndex.TryGetValue(key, out var value)) { if (!_driftByHostIdx.TryGetValue(value, out DriftStat value2)) { value2 = new DriftStat { Unit = (unit ?? "") }; _driftByHostIdx[value] = value2; } value2.Count++; value2.SumHostError += hostError; value2.LastHostError = hostError; if (hostError > value2.MaxHostError) { value2.MaxHostError = hostError; } if (snapped) { value2.Snaps++; } if (hostError > 4f) { value2.Repeated++; _puppetTransformCorrectionRepeated++; } float value3 = Plugin.Cfg.EnemyStateSnapshotSnapDistance.Value; if (hostError > Mathf.Max(value3, 0.1f)) { _puppetTransformCorrectionLarge++; } } } private static void AuditHostBoundPuppetLocalAi() { _localNavMeshStillEnabledOnHostBound = 0; _localAiCanMoveOnHostBound = 0; _rvoStillEnabledOnHostBound = 0; _hostBoundPuppetsAudited = 0; foreach (KeyValuePair activeEnemyPuppet in ActiveEnemyPuppets) { if (!ClientLocalKeyToHostSpawnIndex.ContainsKey(activeEnemyPuppet.Key)) { continue; } EnemyPuppetRecord value = activeEnemyPuppet.Value; _hostBoundPuppetsAudited++; try { object? obj = TryFindComponentByTypeName(value.Npc, "NavMeshAgent") ?? ((value.AiAgent != null) ? TryFindComponentByTypeName(value.AiAgent, "NavMeshAgent") : null); Behaviour val = (Behaviour)((obj is Behaviour) ? obj : null); if (val != null && (Object)(object)val != (Object)null && val.enabled) { _localNavMeshStillEnabledOnHostBound++; } if (value.AiAgent != null) { if ((ReadBoolMember(value.AiAgent, "canMove") ?? ReadBoolMember(value.AiAgent, "CanMove")) == true) { _localAiCanMoveOnHostBound++; } if ((ReadBoolMember(value.AiAgent, "rvoEnabled") ?? ReadBoolMember(value.AiAgent, "useRVO")) == true) { _rvoStillEnabledOnHostBound++; } } } catch { } } } private static bool? ReadBoolMember(object obj, string name) { try { Type type = obj.GetType(); FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(bool)) { return (bool)field.GetValue(obj); } PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.PropertyType == typeof(bool) && property.CanRead) { return (bool)property.GetValue(obj); } } catch { } return null; } private static string FormatTopDrift(int topN) { if (_driftByHostIdx.Count == 0) { return "none"; } IEnumerable> enumerable = _driftByHostIdx.OrderByDescending, float>((KeyValuePair kv) => kv.Value.MaxHostError).Take(topN); StringBuilder stringBuilder = new StringBuilder(); foreach (KeyValuePair item in enumerable) { DriftStat value = item.Value; float num = ((value.Count > 0) ? (value.SumHostError / (float)value.Count) : 0f); stringBuilder.Append($" [idx={item.Key} unit={value.Unit} avgErr={num:F2} maxErr={value.MaxHostError:F2} snaps={value.Snaps} repeated={value.Repeated} n={value.Count}]"); } return stringBuilder.ToString(); } public static void NoteLevelGenTrace(string kind, int doorBlockerDelta = 0) { switch (kind) { case "FinalizeConnection": _levelGenFinalizeConnectionTraced++; break; case "ConnectorFinalizeSpawn": _levelGenConnectorFinalizeSpawnTraced++; break; case "ExtraRoom": _levelGenExtraRoomTraced++; break; case "LevelStep": _levelGenStepTraced++; break; case "NodeCoroutine": _levelGenNodeCoroutineTraced++; break; } if (doorBlockerDelta != 0) { _levelGenDoorBlockerRecorded += doorBlockerDelta; } } public static string GetLocalRunKey() { if (NetRunStateBridge.TryGetLocalRunState(out NetRunState state) && state.HasLevel) { return string.Format("{0}:{1}:{2}:r{3}", state.ChapterName, state.LevelIndex, state.HasLevelSeed ? state.LevelSeed.ToString() : "?", state.Revision); } return ""; } public static void Tick() { if (IsEnabled()) { TryFlushPendingStableSpawnLogs(); ApplyPendingEnemyStateTargets(); UpdateCombatAnimatorBoolResets(); UpdateClientVisualProjectiles(); UpdateClientHitFlashes(); UpdateClientPendingDead(); FlushPendingClientHitDamage(); TryProcessDeferredManifest(); ReleaseStaleEnemyPuppets(); PruneExpiredHostEnemyCombatActions(); PruneExpiredHostEnemyAiIntents(); PruneExpiredAuthorizedIntentWindows(Time.realtimeSinceStartup); ExpireOldPendingHealthStates(); ExpireStalePendingHostBinds(); MaybeLogSummary(); } } public static void ClearLevelScoped(string source) { if (IsEnabled()) { int count = EntitiesByLocalId.Count; ReleaseAllEnemyPuppets("level scoped clear: " + source); EntitiesByLocalId.Clear(); PendingEnemyStateTargets.Clear(); HostEnemyStateSendCacheBySpawnIndex.Clear(); HostEnemyCombatActionsByNpcId.Clear(); HostEnemyAiIntentsByNpcId.Clear(); EnemyCombatProbeLastLogAtById.Clear(); ProjectileProbeLastLogAtByKey.Clear(); HostEnemyDamageLastAtByKey.Clear(); DestroyAllClientVisualProjectiles(); PendingAnimatorBoolResets.Clear(); PendingStableSpawnLogs.Clear(); NetPlayerLifeManager.ClearLevelScoped(source); _nextSpawnIndex = 0; _enemyStateTargetsQueued = 0; _enemyStateTargetsApplied = 0; _enemyStateTargetsSnapped = 0; _clientEnemyPuppetsActivated = 0; _clientEnemyPuppetsReleased = 0; _clientEnemyPuppetTargetClears = 0; _clientEnemyPuppetTargetBlocks = 0; _clientEnemyPuppetCombatBlocks = 0; _enemyCombatProbeEvents = 0; _enemyTargetLocalCleared = 0; _enemyTargetLocalOverwrite = 0; _enemyTargetNoKnownMember = 0; _enemyTargetBossMembersDiscovered = 0; _enemyTargetBossSuppressionApplied = 0; _enemyTargetSuppressionFailed = 0; _enemyTargetHostTargetApplied = 0; _targetProbeDumpedTypes.Clear(); _hostEnemyCombatActionSequence = 0; _hostEnemyCombatActionMarks = 0; _clientCombatAnimatorTriggerApplies = 0; _clientCombatAnimatorStateApplies = 0; _clientCombatVisualActionReplays = 0; _clientCombatAnimatorFallbacks = 0; _clientVisualProjectileMirrors = 0; _projectileProbeEvents = 0; _hostEnemyDamageChecks = 0; _hostEnemyDamageHits = 0; _clientGenericCombatAnimatorStateApplies = 0; _hostEnemyAiIntentMarks = 0; _clientEnemyAiIntentApplies = 0; _clientEnemyAiIntentCorrections = 0; _clientPuppetIntentReplayDepth = 0; _clientAuthorizedIntentByNpcId.Clear(); _clientIntentWindows = 0; _clientAuthorizedAttackPasses = 0; _clientUnauthorizedAttackBlocks = 0; _clientAuthorizedMeleeEvents = 0; _clientSuppressedNativeEnemyDamage = 0; _clientEnemyNativeDamageSuppressDepth = 0; _clientCombatRootReplays = 0; _clientCombatRootReplaySkippedDuplicate = 0; _clientAuthorizedChildPasses = 0; _clientBlockedSpontaneousCombatCalls = 0; _driftSkippedIntentLocomotion = 0; _driftSkippedAuthorizedCombat = 0; _hardDriftCorrections = 0; _softDriftCorrections = 0; _genericCombatStateSkippedDuringAuthorizedIntent = 0; _clientAuthorizedCombatRootReplayDepth = 0; _traderExcludedFromEnemySync = 0; _nonCombatExcludedFromEnemySync = 0; _deathClaimRejectedNonCombat = 0; _combatProbeRejectedNonCombat = 0; _clientRootReplayAttempts = 0; _clientRootReplaySkippedDuplicate = 0; _clientRootReplayUnsupported = 0; _clientRootReplayFailed = 0; _clientAuthorizedChildAfterRoot = 0; _clientChildBlockedBeforeRootReplay = 0; _entityTypeMismatchRejected = 0; _deathMirrorRejectedTypeMismatch = 0; _stateApplyRejectedTypeMismatch = 0; _hostRosterRecordsSent = 0; _clientRosterRecordsReceived = 0; _clientRosterBound = 0; _clientRosterHostOnlyMissing = 0; _clientRosterClientOnlyQuarantined = 0; _clientRosterTypeMismatch = 0; _clientRosterFingerprintMismatch = 0; ClientHostToLocalKeyByHostSpawnIndex.Clear(); ClientLocalKeyToHostSpawnIndex.Clear(); _interestEngagedExempt = 0; _interestDiagLogged = 0; _snapCollExclLogged = 0; _lastSnapCollLogAt = 0f; _clientLastSnapshotRecvByHostIdx.Clear(); _retroBindDiagLogged = 0; _runtimeSpawnBindingsByHostIdx.Clear(); _pendingHostBindLedger.Clear(); _retroBindLedgerAdds = 0; _retroBindSuccess = 0; _retroBindAmbiguousDeferred = 0; _retroBindLedgerExpired = 0; ClientQuarantinedEntities.Clear(); _clientRosterRevision = -1; FarEnemyLastSentAtBySpawnIndex.Clear(); ClientAttackPhaseLastSeqBySpawnIndex.Clear(); _hostPlayerPositionHintValid = false; _rosterOneToOneBound = 0; _deathAppliedByBinding = 0; _deathAppliedBoundDespiteDrift = 0; _deathBoundDriftWarning = 0; _deathRejectedUnboundNetEntity = 0; _clientOnlyCombatQuarantined = 0; _quarantinedCombatSuppressed = 0; ClientPuppetHealthBySpawnIndex.Clear(); ClientPuppetMaxHealthBySpawnIndex.Clear(); HostDamageEventSeqBySpawnIndex.Clear(); _hostEnemyDamageEventsSent = 0; _hostEnemyHealthStatesSent = 0; _clientEnemyDamageEventsReceived = 0; _clientEnemyHealthStatesReceived = 0; _clientEnemyHealthStatesApplied = 0; _clientEnemyHealthApplySkippedNoBinding = 0; _hostDeathAppliedByRosterBinding = 0; _hostDeathAppliedAfterSnap = 0; _hostDeathRejectedNoBinding = 0; _hostDeathLateBindAttempts = 0; _hostDeathLateBindSuccess = 0; _clientDeathClaimsSuppressedByHostAuthority = 0; _hostDeathTombstoneHit = 0; _hostDeathNeverBound = 0; _bindingTombstones.Clear(); _pendingHealthByHostIdx.Clear(); _clientHealthStatesPendingQueued = 0; _clientHealthStatesPendingApplied = 0; _clientHealthStatesPendingExpired = 0; _clientHealthApplyDisabled = 0; _clientHealthNoCurrentHp = 0; _clientHealthNoEntity = 0; _clientHealthNoRuntimeObj = 0; _clientHealthUnityDestroyed = 0; _clientHealthNoStats = 0; _clientHealthSetStatusMissing = 0; _clientHealthEnumArgFailed = 0; _clientHealthSetStatusFailed = 0; _clientHealthWriteFailed = 0; _clientHealthWriteReadBackUnchanged = 0; _puppetReleaseSuppressedAwaitingHost = 0; _puppetStaleReleaseSuppressed = 0; _clientHitRequestSeq = 0; _clientHitRequestsSent = 0; _clientHitRequestsSkippedNoPuppet = 0; _clientHitRequestsSkippedNoBinding = 0; _clientPuppetNonPlayerDamageIgnored = 0; _hostHitRequestsRecv = 0; _hostHitRequestsRejectedScene = 0; _hostHitRequestsRejectedNoTarget = 0; _hostHitRequestsRejectedTypeMismatch = 0; _hostHitRequestsRejectedDead = 0; _hostHitRequestsRejectedRateLimit = 0; _hostHitRequestsCoalesced = 0; _hostHitPendingDamageByHostIdx.Clear(); _hostHitPendingDamagePeerByHostIdx.Clear(); _hostHitRequestsAccepted = 0; _hostHitRequestsDamageApplied = 0; _hostHitRequestsDamageFailed = 0; _hostHitRequestLastAtByHostIdx.Clear(); _hostHitResultHealthStateSent = 0; _clientLocalHitPredicted = 0; _clientLocalHitConfirmed = 0; _clientPendingHitByHostIdx.Clear(); _clientTerminalDeadHostIdx.Clear(); _clientPendingDeadHostIdx.Clear(); _pendingDeadMarked = 0; _pendingDeadHostDeathApplied = 0; _pendingDeadVisualFallbackAttempted = 0; _pendingDeadVisualFallbackSucceeded = 0; _pendingDeadVisualFallbackFailed = 0; _pendingDeadBlockedHitFlash = 0; _pendingDeadBlockedClientHit = 0; _terminalDeadMarkedAfterDie = 0; _terminalDeadMarkedAfterVisualFallback = 0; _terminalDeadMarkedFromHealthOnly = 0; _terminalDeadBlockedAttackPhase = 0; _terminalDeadBlockedGenericReplay = 0; _terminalDeadBlockedMovement = 0; _terminalDeadBlockedHitReaction = 0; _terminalDeadHealthUpdatesIgnored = 0; _terminalDeadDeathReapplySkipped = 0; _clientHitSkipPendingDead = 0; _clientHitSkipTerminalDead = 0; _pendingHitFlashes.Clear(); _clientDamageVisualLastSeqByHostIdx.Clear(); _damageVisualReactionsPlayed = 0; _dmgVisualNativeDoWhiteFlash = 0; _dmgVisualNativeSetHitEffect = 0; _dmgVisualMaterialHitTime = 0; _dmgVisualFallbackColor = 0; _dmgVisualFailedNoNpc = 0; _dmgVisualFailedNoMethod = 0; _dmgVisualFailedNoMaterial = 0; _damageVisualReactionSkippedTerminalDead = 0; _damageVisualReactionSkippedPendingDead = 0; _damageVisualReactionSkippedNoBinding = 0; _damageVisualReactionSkippedDuplicateSeq = 0; _hostManifestBuilt = 0; _clientManifestBuilt = 0; _hostManifestSent = 0; _clientManifestReceived = 0; _manifestHashMatch = 0; _manifestHashMismatch = 0; _manifestRoomMismatch = 0; _manifestUnitMismatch = 0; _manifestSpecialMismatch = 0; _manifestHostOnlyUnits = 0; _manifestClientOnlyUnits = 0; _manifestClientOnlyCombatQuarantined = 0; _manifestHostEnemyBoundExisting = 0; _manifestHostEnemyBindFailedNoCandidate = 0; _manifestHostEnemyBindFailedAmbiguous = 0; _manifestHostEnemyModifierMismatch = 0; _manifestSeedMismatch = 0; _clientFirstLevelEntitySeenAt = 0f; _clientManifestProvisionalLogged = false; _manifestDeferredSceneMismatch = 0; _manifestDeferredLevelMismatch = 0; _manifestDeferredSeedMismatch = 0; _manifestProcessedMatchingRun = 0; _manifestDroppedStaleRun = 0; _manifestAcceptedAfterRunStateFix = 0; _manifestDeferredLevelMismatchAfterFix = 0; _deferredHostManifest = null; _manifestPlayerExcluded = 0; _quarantinePlayerSkipped = 0; _generationHashMatch = 0; _generationHashMismatch = 0; _runtimeHashMatch = 0; _runtimeHashMismatch = 0; _generationHashStableSameRevision = 0; _generationHashChangedSameRevision = 0; _lastGenRunKey = ""; _lastGenSignature = null; _manifestClientOnlyQuarantineNoLocalKey = 0; _manifestClientOnlyQuarantineNoRuntime = 0; _manifestClientOnlyAlreadyQuarantined = 0; _manifestClientOnlyNonCombatSkipped = 0; _manifestClientOnlyQuarantineApplied = 0; _manifestClientOnlyQuarantineFailed = 0; _hostHitVisualPlayed = 0; _hostHitVisualFailedNoNpc = 0; _hostHitVisualEventSent = 0; _hostHitVisualSeq = 0; _clientHitVisualEventRecv = 0; _clientHitVisualPlayed = 0; _clientHitVisualSkipNoBinding = 0; _clientHitVisualSkipPendingDead = 0; _clientHitVisualSkipTerminalDead = 0; _clientHitVisualDuplicateSeq = 0; _hostHitFatalVisualPlayed = 0; _clientHitFatalVisualPlayed = 0; _clientHitVisualSkippedFatalTerminalDead = 0; _clientHitVisualLastSeqByHostIdx.Clear(); _levelGenFinalizeConnectionTraced = 0; _levelGenConnectorFinalizeSpawnTraced = 0; _levelGenExtraRoomTraced = 0; _levelGenDoorBlockerRecorded = 0; _levelGenStepTraced = 0; _levelGenNodeCoroutineTraced = 0; _driftByHostIdx.Clear(); _puppetTransformCorrectionLarge = 0; _puppetTransformCorrectionRepeated = 0; _localNavMeshStillEnabledOnHostBound = 0; _localAiCanMoveOnHostBound = 0; _rvoStillEnabledOnHostBound = 0; _hostBoundPuppetsAudited = 0; _currentClientOnlyUnits = 0; _currentClientOnlyCombatUnits = 0; _currentClientOnlyNonCombatUnits = 0; _currentClientOnlyQuarantineApplied = 0; _currentClientOnlyAlreadyQuarantined = 0; _currentClientOnlyNoRuntime = 0; _totalClientOnlyQuarantineApplied = 0; if (count > 0 || Plugin.Cfg.EnableDebugLog.Value) { Plugin.Log.Info($"[GameplayProbe] Clear level scoped entities count={count} source={Clean(source)}"); } } } public static void ReportSpawn(object? entity, string source, string category) { //IL_015c: Unknown result type (might be due to invalid IL or missing references) if (!IsEnabled() || entity == null || RemotePlayerTargetProxyManager.IsProxyUnit(entity)) { return; } category = CleanCategory(category, entity); if (!ShouldTrackCategory(category)) { return; } _spawnEvents++; NetGameplayEntityId netGameplayEntityId = NetGameplayEntityId.FromObject(entity); string text = netGameplayEntityId.LocalInstanceId; if (string.IsNullOrWhiteSpace(text) || text == "null") { text = netGameplayEntityId.CandidateKey; } NetGameplayEntitySnapshot value; bool flag = !EntitiesByLocalId.TryGetValue(text, out value); if (flag) { value = BuildSnapshot(entity, netGameplayEntityId, source, category); value.SpawnIndex = ++_nextSpawnIndex; EntitiesByLocalId[text] = value; _newSpawns++; if (NetConfig.GetMode() == NetMode.Client) { TryRetroactiveBindNewLocalEntity(value); } if (NetConfig.GetMode() == NetMode.Client && _clientFirstLevelEntitySeenAt <= 0f) { _clientFirstLevelEntitySeenAt = Time.realtimeSinceStartup; if (Plugin.Cfg.EnableHostLevelManifest.Value && _clientManifestReceived == 0 && !_clientManifestProvisionalLogged) { _clientManifestProvisionalLogged = true; NetLogger.Info("[LevelManifest] Client generated provisional level before host manifest"); } } } else { _duplicateSpawnEvents++; value.LastSeenAt = Time.realtimeSinceStartup; value.SetRuntimeObject(entity); if (!string.IsNullOrWhiteSpace(source)) { value.Source = Clean(source); } if (!value.HasPosition && TryGetPosition(entity, out var position)) { value.HasPosition = true; value.Position = position; } RefreshSceneContext(value); } if (CanLogWithCurrentContext(out string _)) { if (value.PendingStableContextLog) { value.PendingStableContextLog = false; PendingStableSpawnLogs.Remove(text); } if (flag && Plugin.Cfg.LogGameplayEntitySpawn.Value) { Plugin.Log.Info(value.FormatSpawnLine("")); } } else { value.PendingStableContextLog = true; PendingStableSpawnLogs.Add(text); _suppressedContextEvents++; } } public static void ReportDeath(object? entity, string source, string category) { if (!IsEnabled() || entity == null) { return; } category = CleanCategory(category, entity); if (!ShouldTrackCategory(category)) { return; } _deathEvents++; NetGameplayEntitySnapshot orCreateSnapshot = GetOrCreateSnapshot(entity, source, category); if (orCreateSnapshot.IsDead) { return; } orCreateSnapshot.IsDead = true; orCreateSnapshot.LastSeenAt = Time.realtimeSinceStartup; RefreshSceneContext(orCreateSnapshot); if (!CanLogWithCurrentContext(out string _)) { _suppressedContextEvents++; return; } float lifetime = Math.Max(0f, orCreateSnapshot.LastSeenAt - orCreateSnapshot.FirstSeenAt); if (Plugin.Cfg.LogGameplayEntityDeath.Value) { Plugin.Log.Info(orCreateSnapshot.FormatDeathLine(Clean(source), lifetime)); } TryReportHostEnemyDeathMirror(orCreateSnapshot, Clean(source)); TryReportClientEnemyDeathClaim(orCreateSnapshot, Clean(source)); } public static void ReportDamage(object? entity, string source, string category, float damage, object? damageType) { if (!IsEnabled() || entity == null) { return; } category = CleanCategory(category, entity); if (!ShouldTrackCategory(category)) { return; } _damageEvents++; NetGameplayEntitySnapshot orCreateSnapshot = GetOrCreateSnapshot(entity, source, category); orCreateSnapshot.DamageCount++; orCreateSnapshot.LastSeenAt = Time.realtimeSinceStartup; RefreshSceneContext(orCreateSnapshot); if (Plugin.Cfg.LogGameplayEntityDamage.Value) { if (!CanLogWithCurrentContext(out string _)) { _suppressedContextEvents++; } else { Plugin.Log.Info(orCreateSnapshot.FormatDamageLine(Clean(source), damage, damageType)); } } } public static bool IsEnemyStateMirrorControlling(object? npcOrAgentOrMovement, out NetGameplayEntitySnapshot? snapshot, out string detail) { snapshot = null; detail = ""; if (!IsEnabled()) { detail = "gameplay entity probe disabled"; return false; } if (NetConfig.GetMode() != NetMode.Client) { detail = "local peer is not Client"; return false; } if (!Plugin.Cfg.EnableHostEnemyStateSnapshotMirror.Value) { detail = "EnableHostEnemyStateSnapshotMirror=false"; return false; } if (!Plugin.Cfg.ApplyReceivedEnemyStateSnapshots.Value) { detail = "ApplyReceivedEnemyStateSnapshots=false"; return false; } if (PendingEnemyStateTargets.Count == 0) { detail = "no active Host enemy state targets"; return false; } if (!CanLogWithCurrentContext(out string reason)) { detail = "unstable local scene/seed context: " + reason; return false; } string detail2; object obj = ResolveMirroredEnemyObject(npcOrAgentOrMovement, out detail2); if (obj == null) { detail = "could not resolve mirrored NPC from " + detail2; return false; } snapshot = FindSnapshotForRuntimeObject(obj); if (snapshot == null) { detail = "no tracked gameplay snapshot for resolved object: " + detail2; return false; } if (snapshot.IsDead) { detail = $"tracked NPC already dead idx={snapshot.SpawnIndex}"; return false; } if (!string.Equals(snapshot.Category, "Npc", StringComparison.OrdinalIgnoreCase)) { detail = "tracked category is not Npc: " + snapshot.Category; return false; } string snapshotTargetKey = GetSnapshotTargetKey(snapshot); if (string.IsNullOrWhiteSpace(snapshotTargetKey) || !PendingEnemyStateTargets.TryGetValue(snapshotTargetKey, out EnemyStateTarget value)) { detail = $"no active Host state target for idx={snapshot.SpawnIndex}"; return false; } float num = Time.realtimeSinceStartup - value.LastUpdatedAt; if (!value.HasPosition || num > 2f) { detail = $"Host state target stale idx={snapshot.SpawnIndex} age={num:F2}s"; return false; } detail = $"idx={snapshot.SpawnIndex} actor={snapshot.ActorName} targetSeq={value.Sequence} targetAge={num:F2}s resolved={detail2}"; return true; } private static NetGameplayEntitySnapshot? FindSnapshotForRuntimeObject(object entity) { foreach (NetGameplayEntitySnapshot value2 in EntitiesByLocalId.Values) { if (value2.TryGetRuntimeObject(out object runtimeObject) && runtimeObject == entity) { return value2; } } NetGameplayEntityId netGameplayEntityId = NetGameplayEntityId.FromObject(entity); string text = ((string.IsNullOrWhiteSpace(netGameplayEntityId.LocalInstanceId) || netGameplayEntityId.LocalInstanceId == "null") ? netGameplayEntityId.CandidateKey : netGameplayEntityId.LocalInstanceId); if (!string.IsNullOrWhiteSpace(text) && EntitiesByLocalId.TryGetValue(text, out NetGameplayEntitySnapshot value)) { return value; } if (!string.IsNullOrWhiteSpace(netGameplayEntityId.CandidateKey)) { foreach (NetGameplayEntitySnapshot value3 in EntitiesByLocalId.Values) { if (string.Equals(value3.EntityId.CandidateKey, netGameplayEntityId.CandidateKey, StringComparison.Ordinal)) { return value3; } } } return null; } private static object? ResolveMirroredEnemyObject(object? value, out string detail) { detail = ((value == null) ? "null" : (value.GetType().FullName ?? value.GetType().Name)); if (value == null) { return null; } if (LooksLikeTrackedNpc(value)) { return value; } object obj = TryGetMemberValue(value, "Owner") ?? TryGetMemberValue(value, "owner") ?? TryGetMemberValue(value, "Unit") ?? TryGetMemberValue(value, "unit") ?? TryGetMemberValue(value, "Npc") ?? TryGetMemberValue(value, "npc"); if (obj != null && LooksLikeTrackedNpc(obj)) { detail += " -> Owner"; return obj; } object obj2 = TryGetMemberValue(value, "aiAgent") ?? TryGetMemberValue(value, "AiAgent") ?? TryGetMemberValue(value, "AI") ?? TryGetMemberValue(value, "ai"); if (obj2 != null && obj2 != value) { object obj3 = TryGetMemberValue(obj2, "Owner") ?? TryGetMemberValue(obj2, "owner"); if (obj3 != null && LooksLikeTrackedNpc(obj3)) { detail += " -> aiAgent.Owner"; return obj3; } } return null; } private static bool LooksLikeTrackedNpc(object value) { try { Type type = value.GetType(); if (type.Name == "Npc") { return true; } Type baseType = type.BaseType; while (baseType != null) { if (baseType.Name == "Npc") { return true; } baseType = baseType.BaseType; } } catch { } return false; } private static bool IsDeathPositionWithinTolerance(Vector3 localPosition, Vector3 hostPosition, float tolerance, out string detail) { //IL_0000: 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_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_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_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_0077: 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) if (!IsFinite(localPosition) || !IsFinite(hostPosition)) { detail = "non-finite position"; return false; } float num = Vector3.Distance(localPosition, hostPosition); if (!Plugin.Cfg.EnemyDeathMirrorUseHorizontalPositionTolerance.Value) { detail = $"distance={num:F2}m"; return num <= tolerance; } float num2 = localPosition.x - hostPosition.x; float num3 = localPosition.z - hostPosition.z; float num4 = Mathf.Sqrt(num2 * num2 + num3 * num3); float num5 = Mathf.Abs(localPosition.y - hostPosition.y); detail = $"horizontal={num4:F2}m vertical={num5:F2}m full={num:F2}m"; return num4 <= tolerance; } public static bool TryFindLocalMatch(NetGameplayDeathEvent deathEvent, out NetGameplayEntitySnapshot? snapshot, out string detail) { //IL_050d: Unknown result type (might be due to invalid IL or missing references) //IL_0518: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) snapshot = null; detail = ""; if (!IsEnabled()) { detail = "gameplay entity probe disabled"; return false; } if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state)) { detail = "local run state unavailable"; return false; } if (!deathEvent.MatchesScene(state)) { detail = "scene/seed mismatch local=" + state.ToCompactString() + " remote=" + deathEvent.SceneKey + " seed=" + deathEvent.SeedText; return false; } if (ClientHostToLocalKeyByHostSpawnIndex.TryGetValue(deathEvent.SpawnIndex, out string value) && EntitiesByLocalId.TryGetValue(value, out NetGameplayEntitySnapshot value2) && !value2.IsDead) { if (!string.IsNullOrWhiteSpace(deathEvent.UnitIdentifier) && !string.IsNullOrWhiteSpace(value2.EntityId.UnitIdentifier) && !string.Equals(deathEvent.UnitIdentifier, value2.EntityId.UnitIdentifier, StringComparison.Ordinal)) { _deathMirrorRejectedTypeMismatch++; _entityTypeMismatchRejected++; detail = "roster-bound but unitId mismatch host=" + deathEvent.UnitIdentifier + " client=" + value2.EntityId.UnitIdentifier; Plugin.Log.Info($"[DeathMatch] Reject unitId mismatch via binding hostIdx={deathEvent.SpawnIndex}"); return false; } snapshot = value2; if (deathEvent.HasPosition && value2.HasPosition) { float num = Plugin.Cfg.EnemyDeathMirrorPositionTolerance.Value; if (num < 0f) { num = 0f; } float num2 = Vector3.Distance(deathEvent.Position, value2.Position); if (num2 > num) { _deathBoundDriftWarning++; Plugin.Log.Info($"[DeathMatch] Bound drift={num2:F1}m > tol={num:F1}m; applying anyway hostIdx={deathEvent.SpawnIndex} unit={deathEvent.UnitIdentifier}"); if (num2 > num * 2f) { _deathAppliedBoundDespiteDrift++; } } } _deathAppliedByBinding++; detail = "roster-bound death match"; return true; } if (ClientHostToLocalKeyByHostSpawnIndex.Count > 0) { _hostDeathLateBindAttempts++; if (TryLateBindForDeathEvent(deathEvent, out NetGameplayEntitySnapshot snapshot2) && snapshot2 != null) { snapshot = snapshot2; _hostDeathLateBindSuccess++; detail = $"late-bind death match hostIdx={deathEvent.SpawnIndex} unit={deathEvent.UnitIdentifier}"; return true; } _deathRejectedUnboundNetEntity++; _hostDeathRejectedNoBinding++; float realtimeSinceStartup = Time.realtimeSinceStartup; if (_bindingTombstones.TryGetValue(deathEvent.SpawnIndex, out var value3) && realtimeSinceStartup - value3.ReleasedAt <= 60f) { _hostDeathTombstoneHit++; detail = $"roster-active no-binding, tombstone: puppet released {realtimeSinceStartup - value3.ReleasedAt:F1}s ago reason={value3.ReleaseReason} key={value3.LocalKey} unit={value3.UnitIdentifier}"; } else { _hostDeathNeverBound++; detail = $"roster active (rev={_clientRosterRevision} bindings={ClientHostToLocalKeyByHostSpawnIndex.Count}) hostIdx={deathEvent.SpawnIndex} never bound, late-bind failed"; } return false; } float num3 = Plugin.Cfg.EnemyDeathMirrorPositionTolerance.Value; if (num3 < 0f) { num3 = 0f; } List list = EntitiesByLocalId.Values.Where((NetGameplayEntitySnapshot s) => !s.IsDead && string.Equals(s.Category, deathEvent.Category, StringComparison.OrdinalIgnoreCase)).ToList(); NetGameplayEntitySnapshot netGameplayEntitySnapshot = list.FirstOrDefault((NetGameplayEntitySnapshot s) => s.SpawnIndex == deathEvent.SpawnIndex); if (netGameplayEntitySnapshot != null) { if (!string.IsNullOrWhiteSpace(deathEvent.UnitIdentifier) && !string.IsNullOrWhiteSpace(netGameplayEntitySnapshot.EntityId.UnitIdentifier) && !string.Equals(deathEvent.UnitIdentifier, netGameplayEntitySnapshot.EntityId.UnitIdentifier, StringComparison.Ordinal)) { _deathMirrorRejectedTypeMismatch++; _entityTypeMismatchRejected++; detail = "spawnIndex matched but unitId mismatch host=" + deathEvent.UnitIdentifier + " client=" + netGameplayEntitySnapshot.EntityId.UnitIdentifier; Plugin.Log.Info($"[EntityBind] Reject type mismatch hostUnit={deathEvent.UnitIdentifier} clientUnit={netGameplayEntitySnapshot.EntityId.UnitIdentifier} hostIdx={deathEvent.SpawnIndex} clientIdx={netGameplayEntitySnapshot.SpawnIndex}"); return false; } snapshot = netGameplayEntitySnapshot; if (deathEvent.HasPosition && netGameplayEntitySnapshot.HasPosition) { if (IsDeathPositionWithinTolerance(netGameplayEntitySnapshot.Position, deathEvent.Position, num3, out string detail2)) { detail = "spawnIndex+position " + detail2; return true; } detail = $"spawnIndex matched but position {detail2} > tolerance={num3:F2}m"; return false; } detail = "spawnIndex matched; position unavailable on one side"; return true; } if (!string.IsNullOrWhiteSpace(deathEvent.UnitGlobalId)) { List list2 = list.Where((NetGameplayEntitySnapshot s) => string.Equals(s.EntityId.UnitGlobalId, deathEvent.UnitGlobalId, StringComparison.Ordinal)).ToList(); if (list2.Count == 1) { snapshot = list2[0]; detail = "unique UnitGlobalId fallback match"; return true; } } if (!string.IsNullOrWhiteSpace(deathEvent.CandidateKey)) { List list3 = list.Where((NetGameplayEntitySnapshot s) => string.Equals(s.EntityId.CandidateKey, deathEvent.CandidateKey, StringComparison.Ordinal)).ToList(); if (list3.Count == 1) { snapshot = list3[0]; detail = "unique candidateKey fallback match"; return true; } } detail = $"no local alive match candidates={list.Count}"; return false; } public static bool TryApplyHostDeathToLocalMatch(NetGameplayDeathEvent deathEvent, NetGameplayEntitySnapshot? snapshot, out string detail) { if (!Plugin.Cfg.ApplyReceivedEnemyDeathEvents.Value) { detail = "ApplyReceivedEnemyDeathEvents=false"; return false; } return TryApplyMatchedNpcDeath(deathEvent, snapshot, "HostEnemyDeathEvent", out detail); } public static bool TryApplyClientDeathClaimToHostMatch(NetGameplayDeathEvent deathEvent, NetGameplayEntitySnapshot? snapshot, out string detail) { if (!Plugin.Cfg.ApplyReceivedClientEnemyDeathClaimsOnHost.Value) { detail = "ApplyReceivedClientEnemyDeathClaimsOnHost=false"; return false; } if (snapshot != null && IsNonCombatForSync(snapshot)) { detail = "[DeathClaim] Rejected category=" + SyncCatName(snapshot.SyncCategory) + " actor=" + snapshot.ActorName; _deathClaimRejectedNonCombat++; Plugin.Log.Info(detail); return false; } return TryApplyMatchedNpcDeath(deathEvent, snapshot, "ClientEnemyDeathClaim", out detail); } private static bool TryApplyMatchedNpcDeath(NetGameplayDeathEvent deathEvent, NetGameplayEntitySnapshot? snapshot, string reason, out string detail) { //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) detail = ""; if (!IsEnabled()) { detail = "gameplay entity probe disabled"; return false; } if (snapshot == null) { detail = "matched snapshot is null"; return false; } if (snapshot.IsDead) { detail = $"local entity already dead idx={snapshot.SpawnIndex}"; return false; } if (deathEvent.SpawnIndex >= 0 && IsClientTerminalDead(deathEvent.SpawnIndex)) { _terminalDeadDeathReapplySkipped++; detail = $"terminal-dead latch already set hostIdx={deathEvent.SpawnIndex}"; return false; } if (!string.Equals(snapshot.Category, "Npc", StringComparison.OrdinalIgnoreCase)) { detail = "category '" + snapshot.Category + "' is not eligible"; return false; } if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || !deathEvent.MatchesScene(state)) { detail = "local scene/seed no longer matches death event"; return false; } if (!snapshot.TryGetRuntimeObject(out object runtimeObject) || runtimeObject == null) { detail = $"runtime object is no longer available idx={snapshot.SpawnIndex}"; return false; } Object val = (Object)((runtimeObject is Object) ? runtimeObject : null); if (val != null && val == (Object)null) { detail = $"runtime Unity object was destroyed idx={snapshot.SpawnIndex}"; return false; } string value; NetGameplayEntitySnapshot value2; bool flag = Plugin.Cfg.AllowRosterBoundDeathDespitePositionDrift.Value && deathEvent.SpawnIndex >= 0 && ClientHostToLocalKeyByHostSpawnIndex.TryGetValue(deathEvent.SpawnIndex, out value) && EntitiesByLocalId.TryGetValue(value, out value2) && value2 == snapshot; if (TryGetPosition(runtimeObject, out var position)) { snapshot.HasPosition = true; snapshot.Position = position; if (deathEvent.HasPosition) { if (flag) { float num = Vector3.Distance(position, deathEvent.Position); if (num > 2f) { Plugin.Log.Info($"[DeathApply] Roster-bound death drift={num:F1}m hostIdx={deathEvent.SpawnIndex} — applying anyway"); } } else { float num2 = Plugin.Cfg.EnemyDeathMirrorPositionTolerance.Value; if (num2 < 0f) { num2 = 0f; } if (!IsDeathPositionWithinTolerance(position, deathEvent.Position, num2, out string detail2)) { detail = $"position drift before apply {detail2} > tolerance={num2:F2}m"; return false; } } } } if (flag && Plugin.Cfg.HostDeathSnapBeforeApply.Value && deathEvent.HasPosition) { TrySnapEntityToPosition(runtimeObject, deathEvent.Position); _hostDeathAppliedAfterSnap++; } if (flag) { _hostDeathAppliedByRosterBinding++; } MethodInfo methodInfo = FindNoArgInstanceMethod(runtimeObject.GetType(), "Die"); if (methodInfo == null) { detail = "no no-arg Die method on " + runtimeObject.GetType().FullName; return false; } try { _hostDeathApplyDepth++; bool flag2 = deathEvent.SpawnIndex >= 0 && IsClientPendingDead(deathEvent.SpawnIndex); methodInfo.Invoke(runtimeObject, null); snapshot.IsDead = true; snapshot.LastSeenAt = Time.realtimeSinceStartup; if (deathEvent.SpawnIndex >= 0) { if (flag2) { _pendingDeadHostDeathApplied++; } _terminalDeadMarkedAfterDie++; MarkClientTerminalDead(deathEvent.SpawnIndex, "Npc.Die applied via " + reason); } if (Plugin.Cfg.ReleasePuppetOnHostDeath.Value) { ReleaseClientEnemyPuppetOnHostDeath(snapshot, deathEvent.SpawnIndex, reason); } detail = $"invoked {methodInfo.DeclaringType?.Name ?? runtimeObject.GetType().Name}.Die() idx={snapshot.SpawnIndex} reason={reason} rosterBound={flag} wasPendingDead={flag2}"; return true; } catch (TargetInvocationException ex) { detail = "Die() threw " + (ex.InnerException?.GetType().Name ?? ex.GetType().Name) + ": " + (ex.InnerException?.Message ?? ex.Message); return false; } catch (Exception ex2) { detail = "Die() invoke failed: " + ex2.Message; return false; } finally { if (_hostDeathApplyDepth > 0) { _hostDeathApplyDepth--; } } } private static void TryReportHostEnemyDeathMirror(NetGameplayEntitySnapshot snapshot, string source) { //IL_01c7: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Cfg.EnableHostEnemyDeathEventMirror.Value && string.Equals(snapshot.Category, "Npc", StringComparison.OrdinalIgnoreCase)) { NetRunState state; if (IsNonCombatForSync(snapshot)) { _nonCombatExcludedFromEnemySync++; } else if (NetRunStateBridge.TryGetLocalRunState(out state) && state.HasLevel && (!Plugin.Cfg.EnableLevelSeedAuthority.Value || state.HasLevelSeed)) { _deathMirrorSequence++; NetGameplayDeathEvent netGameplayDeathEvent = new NetGameplayDeathEvent(); netGameplayDeathEvent.EventId = $"{state.PeerId}:{state.Revision}:{snapshot.SpawnIndex}:{_deathMirrorSequence}"; netGameplayDeathEvent.SourcePeerId = (string.IsNullOrWhiteSpace(state.PeerId) ? "host" : state.PeerId); netGameplayDeathEvent.ChapterName = state.ChapterName; netGameplayDeathEvent.LevelIndex = state.LevelIndex; netGameplayDeathEvent.HasLevelSeed = state.HasLevelSeed; netGameplayDeathEvent.LevelSeed = state.LevelSeed; netGameplayDeathEvent.SourceRevision = state.Revision; netGameplayDeathEvent.Sequence = _deathMirrorSequence; netGameplayDeathEvent.SpawnIndex = snapshot.SpawnIndex; netGameplayDeathEvent.CandidateKey = snapshot.EntityId.CandidateKey; netGameplayDeathEvent.LocalInstanceId = snapshot.EntityId.LocalInstanceId; netGameplayDeathEvent.UnityInstanceId = snapshot.EntityId.UnityInstanceId; netGameplayDeathEvent.TypeName = snapshot.EntityId.TypeName; netGameplayDeathEvent.UnitIdentifier = snapshot.EntityId.UnitIdentifier; netGameplayDeathEvent.UnitGlobalId = snapshot.EntityId.UnitGlobalId; netGameplayDeathEvent.Category = snapshot.Category; netGameplayDeathEvent.ActorName = snapshot.ActorName; netGameplayDeathEvent.HasPosition = snapshot.HasPosition; netGameplayDeathEvent.Position = snapshot.Position; netGameplayDeathEvent.DamageCount = snapshot.DamageCount; netGameplayDeathEvent.Source = source; netGameplayDeathEvent.SentAt = Time.realtimeSinceStartup; NetGameplaySyncBridge.ReportLocalEnemyDeath(netGameplayDeathEvent); } } } private static void TryReportClientEnemyDeathClaim(NetGameplayEntitySnapshot snapshot, string source) { //IL_0237: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.Cfg.EnableClientEnemyDeathClaim.Value || _hostDeathApplyDepth > 0 || NetConfig.GetMode() != NetMode.Client || !string.Equals(snapshot.Category, "Npc", StringComparison.OrdinalIgnoreCase)) { return; } if (Plugin.Cfg.EnableHostEnemyDamageEventSync.Value && Plugin.Cfg.DisableClientEnemyDeathClaimWhenHostDamageSyncEnabled.Value) { _clientDeathClaimsSuppressedByHostAuthority++; return; } if (IsNonCombatForSync(snapshot)) { _deathClaimRejectedNonCombat++; return; } string snapshotTargetKey = GetSnapshotTargetKey(snapshot); NetRunState state; if (!string.IsNullOrWhiteSpace(snapshotTargetKey) && ClientQuarantinedEntities.Contains(snapshotTargetKey)) { _quarantinedCombatSuppressed++; } else if (NetRunStateBridge.TryGetLocalRunState(out state) && state.HasLevel && (!Plugin.Cfg.EnableLevelSeedAuthority.Value || state.HasLevelSeed)) { _deathMirrorSequence++; NetGameplayDeathEvent netGameplayDeathEvent = new NetGameplayDeathEvent(); netGameplayDeathEvent.EventId = $"{state.PeerId}:{state.Revision}:{snapshot.SpawnIndex}:claim:{_deathMirrorSequence}"; netGameplayDeathEvent.SourcePeerId = (string.IsNullOrWhiteSpace(state.PeerId) ? "client" : state.PeerId); netGameplayDeathEvent.ChapterName = state.ChapterName; netGameplayDeathEvent.LevelIndex = state.LevelIndex; netGameplayDeathEvent.HasLevelSeed = state.HasLevelSeed; netGameplayDeathEvent.LevelSeed = state.LevelSeed; netGameplayDeathEvent.SourceRevision = state.Revision; netGameplayDeathEvent.Sequence = _deathMirrorSequence; netGameplayDeathEvent.SpawnIndex = snapshot.SpawnIndex; netGameplayDeathEvent.CandidateKey = snapshot.EntityId.CandidateKey; netGameplayDeathEvent.LocalInstanceId = snapshot.EntityId.LocalInstanceId; netGameplayDeathEvent.UnityInstanceId = snapshot.EntityId.UnityInstanceId; netGameplayDeathEvent.TypeName = snapshot.EntityId.TypeName; netGameplayDeathEvent.UnitIdentifier = snapshot.EntityId.UnitIdentifier; netGameplayDeathEvent.UnitGlobalId = snapshot.EntityId.UnitGlobalId; netGameplayDeathEvent.Category = snapshot.Category; netGameplayDeathEvent.ActorName = snapshot.ActorName; netGameplayDeathEvent.HasPosition = snapshot.HasPosition; netGameplayDeathEvent.Position = snapshot.Position; netGameplayDeathEvent.DamageCount = snapshot.DamageCount; netGameplayDeathEvent.Source = source + "/ClientDeathClaim"; netGameplayDeathEvent.SentAt = Time.realtimeSinceStartup; NetGameplaySyncBridge.ReportClientEnemyDeathClaim(netGameplayDeathEvent); } } private static NetGameplayEntitySnapshot GetOrCreateSnapshot(object entity, string source, string category) { NetGameplayEntityId netGameplayEntityId = NetGameplayEntityId.FromObject(entity); string key = ((string.IsNullOrWhiteSpace(netGameplayEntityId.LocalInstanceId) || netGameplayEntityId.LocalInstanceId == "null") ? netGameplayEntityId.CandidateKey : netGameplayEntityId.LocalInstanceId); if (EntitiesByLocalId.TryGetValue(key, out NetGameplayEntitySnapshot value)) { value.SetRuntimeObject(entity); return value; } value = BuildSnapshot(entity, netGameplayEntityId, source, category); value.SpawnIndex = ++_nextSpawnIndex; EntitiesByLocalId[key] = value; _newSpawns++; return value; } private static NetGameplayEntitySnapshot BuildSnapshot(object entity, NetGameplayEntityId id, string source, string category) { //IL_0072: 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) NetGameplayEntitySnapshot netGameplayEntitySnapshot = new NetGameplayEntitySnapshot { EntityId = id, Category = Clean(category), ActorName = GetActorName(entity), Source = Clean(source), FirstSeenAt = Time.realtimeSinceStartup, LastSeenAt = Time.realtimeSinceStartup }; netGameplayEntitySnapshot.SetRuntimeObject(entity); netGameplayEntitySnapshot.SyncCategory = ClassifyEntitySyncCategory(entity, netGameplayEntitySnapshot.ActorName); if (TryGetPosition(entity, out var position)) { netGameplayEntitySnapshot.HasPosition = true; netGameplayEntitySnapshot.Position = position; netGameplayEntitySnapshot.HasInitialPosition = true; netGameplayEntitySnapshot.InitialPosition = position; } RefreshSceneContext(netGameplayEntitySnapshot); return netGameplayEntitySnapshot; } private static void RefreshSceneContext(NetGameplayEntitySnapshot snapshot) { if (NetRunStateBridge.TryGetLocalRunState(out NetRunState state) && state.HasLevel) { snapshot.SceneKey = state.SceneKey(); snapshot.HasLevelSeed = state.HasLevelSeed; snapshot.LevelSeed = state.LevelSeed; snapshot.GameState = (string.IsNullOrWhiteSpace(state.GameState) ? "" : state.GameState); } } private static void TryFlushPendingStableSpawnLogs() { if (PendingStableSpawnLogs.Count == 0 || !Plugin.Cfg.LogGameplayEntitySpawn.Value || !CanLogWithCurrentContext(out string _)) { return; } string[] array = PendingStableSpawnLogs.ToArray(); foreach (string text in array) { if (!EntitiesByLocalId.TryGetValue(text, out NetGameplayEntitySnapshot value)) { PendingStableSpawnLogs.Remove(text); continue; } if (value.IsDead) { PendingStableSpawnLogs.Remove(text); continue; } RefreshSceneContext(value); value.PendingStableContextLog = false; PendingStableSpawnLogs.Remove(text); Plugin.Log.Info(value.FormatSpawnLine("[late-context]")); } } private static void MaybeLogSummary() { float num = Plugin.Cfg.GameplayEntityProbeSummaryIntervalSeconds.Value; if (num < 1f) { num = 1f; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup - _lastSummaryAt < num)) { _lastSummaryAt = realtimeSinceStartup; int num2 = EntitiesByLocalId.Values.Count((NetGameplayEntitySnapshot s) => !s.IsDead); int num3 = EntitiesByLocalId.Values.Count((NetGameplayEntitySnapshot s) => s.IsDead); int num4 = EntitiesByLocalId.Values.Count((NetGameplayEntitySnapshot s) => s.Category == "Npc" && !s.IsDead); NetRunState state; string text = (NetRunStateBridge.TryGetLocalRunState(out state) ? state.ToCompactString() : "local="); if (_spawnEvents != 0 || _damageEvents != 0 || _deathEvents != 0 || num2 != 0 || num3 != 0) { Plugin.Log.Info($"[GameplayProbe] Summary traderExcluded={_traderExcludedFromEnemySync} nonCombatExcluded={_nonCombatExcludedFromEnemySync} deathClaimRejectedNonCombat={_deathClaimRejectedNonCombat} combatProbeRejectedNonCombat={_combatProbeRejectedNonCombat}"); Plugin.Log.Info($"[GameplayProbe] Summary rootReplayAttempts={_clientRootReplayAttempts} rootReplays={_clientCombatRootReplays} rootReplaySkippedDup={_clientRootReplaySkippedDuplicate} rootReplayUnsupported={_clientRootReplayUnsupported} rootReplayFailed={_clientRootReplayFailed} childAfterRoot={_clientAuthorizedChildAfterRoot} childBlockedBeforeRoot={_clientChildBlockedBeforeRootReplay}"); Plugin.Log.Info($"[GameplayProbe] Summary typeMismatch={_entityTypeMismatchRejected} deathTypeMismatch={_deathMirrorRejectedTypeMismatch} stateTypeMismatch={_stateApplyRejectedTypeMismatch}"); Plugin.Log.Info($"[GameplayProbe] Summary rosterSent={_hostRosterRecordsSent} rosterReceived={_clientRosterRecordsReceived} rosterBound={_clientRosterBound} rosterOneToOne={_rosterOneToOneBound} rosterHostOnly={_clientRosterHostOnlyMissing} rosterClientOnly={_clientRosterClientOnlyQuarantined} quarantined={_clientOnlyCombatQuarantined} quarantineSuppressed={_quarantinedCombatSuppressed} rosterTypeMismatch={_clientRosterTypeMismatch} rosterFingerprintMismatch={_clientRosterFingerprintMismatch} rosterBindings={ClientHostToLocalKeyByHostSpawnIndex.Count} deathBound={_deathAppliedByBinding} deathUnbound={_deathRejectedUnboundNetEntity} deathBoundDrift={_deathBoundDriftWarning} deathBoundBigDrift={_deathAppliedBoundDespiteDrift}"); Plugin.Log.Info($"[GameplayProbe] Summary alive={num2} npcAlive={num4} dead={num3} totalSeen={EntitiesByLocalId.Count} newSpawns={_newSpawns} spawnEvents={_spawnEvents} duplicateSpawnEvents={_duplicateSpawnEvents} damageEvents={_damageEvents} deathEvents={_deathEvents} enemyStateTargets={PendingEnemyStateTargets.Count} enemyStateQueued={_enemyStateTargetsQueued} enemyStateApplied={_enemyStateTargetsApplied} enemyStateSnapped={_enemyStateTargetsSnapped} enemyPuppets={ActiveEnemyPuppets.Count} enemyPuppetsActivated={_clientEnemyPuppetsActivated} enemyPuppetsReleased={_clientEnemyPuppetsReleased} puppetTargetClears={_clientEnemyPuppetTargetClears} puppetTargetBlocks={_clientEnemyPuppetTargetBlocks} puppetCombatBlocks={_clientEnemyPuppetCombatBlocks} enemyCombatProbeEvents={_enemyCombatProbeEvents} hostCombatActions={_hostEnemyCombatActionMarks} clientCombatTriggers={_clientCombatAnimatorTriggerApplies} clientCombatStates={_clientCombatAnimatorStateApplies} clientCombatVisualReplays={_clientCombatVisualActionReplays} clientCombatFallbacks={_clientCombatAnimatorFallbacks} genericCombatStates={_clientGenericCombatAnimatorStateApplies} genericCombatSkipped={_genericCombatStateSkippedDuringAuthorizedIntent} visualProjectiles={_clientVisualProjectileMirrors} activeVisualProjectiles={ClientVisualProjectiles.Count} projectileProbeEvents={_projectileProbeEvents} hostDamageChecks={_hostEnemyDamageChecks} hostDamageHits={_hostEnemyDamageHits} hostAiIntents={_hostEnemyAiIntentMarks} clientAiIntents={_clientEnemyAiIntentApplies} clientAiCorrections={_clientEnemyAiIntentCorrections} driftSkipLoco={_driftSkippedIntentLocomotion} driftSkipCombat={_driftSkippedAuthorizedCombat} hardDrift={_hardDriftCorrections} softDrift={_softDriftCorrections} clientIntentWindows={_clientIntentWindows} activeIntentWindows={_clientAuthorizedIntentByNpcId.Count} clientRootReplays={_clientCombatRootReplays} clientRootSkipped={_clientCombatRootReplaySkippedDuplicate} clientAuthorizedAttacks={_clientAuthorizedAttackPasses} clientAuthorizedChild={_clientAuthorizedChildPasses} clientBlockedSpontaneous={_clientBlockedSpontaneousCombatCalls} clientUnauthorizedBlocks={_clientUnauthorizedAttackBlocks} clientAuthorizedMelee={_clientAuthorizedMeleeEvents} clientSuppressedDamage={_clientSuppressedNativeEnemyDamage} pendingContext={PendingStableSpawnLogs.Count} suppressedContextEvents={_suppressedContextEvents} context={text}"); Plugin.Log.Info($"[GameplayProbe] Phase5.0 hostAttackPhaseEventsSent={_hostAttackPhaseEventsSent} clientAttackPhaseEventsReceived={_clientAttackPhaseEventsReceived} clientAttackPhaseAnimatorApplies={_clientAttackPhaseAnimatorApplies} clientPuppetDamageSuppressed={_clientPuppetDamageSuppressed} interestManagementFarSkipped={_interestManagementFarSkipped} interestEngagedExempt={_interestEngagedExempt} attackPhaseThrottled={_attackPhaseThrottled} enemyDamageEventThrottled={_enemyDamageEventThrottled}"); Plugin.Log.Info($"[GameplayProbe] Phase5.2 hostDmgEventsSent={_hostEnemyDamageEventsSent} hostHealthStatesSent={_hostEnemyHealthStatesSent} clientDmgEventsRecv={_clientEnemyDamageEventsReceived} clientHealthStatesRecv={_clientEnemyHealthStatesReceived} clientHealthApplied={_clientEnemyHealthStatesApplied} clientHealthNoBinding={_clientEnemyHealthApplySkippedNoBinding} healthPendingQueued={_clientHealthStatesPendingQueued} healthPendingApplied={_clientHealthStatesPendingApplied} healthPendingExpired={_clientHealthStatesPendingExpired} deathByRosterBind={_hostDeathAppliedByRosterBinding} deathSnapped={_hostDeathAppliedAfterSnap} deathRejectedNoBinding={_hostDeathRejectedNoBinding} deathTombstone={_hostDeathTombstoneHit} deathNeverBound={_hostDeathNeverBound} lateBindAttempts={_hostDeathLateBindAttempts} lateBindSuccess={_hostDeathLateBindSuccess} claimsSuppressed={_clientDeathClaimsSuppressedByHostAuthority}"); Plugin.Log.Info($"[GameplayProbe] Phase5.2-diag healthFail_disabled={_clientHealthApplyDisabled} healthFail_noHp={_clientHealthNoCurrentHp} healthFail_noEntity={_clientHealthNoEntity} healthFail_noRuntimeObj={_clientHealthNoRuntimeObj} healthFail_unityDestroyed={_clientHealthUnityDestroyed} healthFail_noStats={_clientHealthNoStats} healthFail_noSetStatus={_clientHealthSetStatusMissing} healthFail_enumArgBuild={_clientHealthEnumArgFailed} healthFail_invokeFail={_clientHealthSetStatusFailed} healthFail_write={_clientHealthWriteFailed} healthFail_readBackUnchanged={_clientHealthWriteReadBackUnchanged} puppetSuppressedAwaitingHost={_puppetReleaseSuppressedAwaitingHost} puppetStaleSuppressed={_puppetStaleReleaseSuppressed}"); Plugin.Log.Info($"[GameplayProbe] Phase5.3-B clientHitSent={_clientHitRequestsSent} clientHitSkipNoPuppet={_clientHitRequestsSkippedNoPuppet} clientHitSkipNoBinding={_clientHitRequestsSkippedNoBinding} puppetNonPlayerDmgIgnored={_clientPuppetNonPlayerDamageIgnored} clientHitSkipPendingDead={_clientHitSkipPendingDead} clientHitSkipTerminalDead={_clientHitSkipTerminalDead} clientHitPredicted={_clientLocalHitPredicted} clientHitConfirmed={_clientLocalHitConfirmed} hostHitRecv={_hostHitRequestsRecv} hostHitRejectScene={_hostHitRequestsRejectedScene} hostHitRejectNoTarget={_hostHitRequestsRejectedNoTarget} hostHitRejectType={_hostHitRequestsRejectedTypeMismatch} hostHitRejectDead={_hostHitRequestsRejectedDead} hostHitRejectRateLimit={_hostHitRequestsRejectedRateLimit} hostHitCoalesced={_hostHitRequestsCoalesced} hostHitAccepted={_hostHitRequestsAccepted} hostHitDmgApplied={_hostHitRequestsDamageApplied} hostHitDmgFailed={_hostHitRequestsDamageFailed} hostHitResultHealthSent={_hostHitResultHealthStateSent}"); Plugin.Log.Info($"[GameplayProbe] Phase5.3-D-death pendingDeadMarked={_pendingDeadMarked} pendingDeadHostDeathApplied={_pendingDeadHostDeathApplied} pendingDeadAwaitingHostDeath={_clientPendingDeadHostIdx.Count} pdVisualFallbackAttempted={_pendingDeadVisualFallbackAttempted} pdVisualFallbackSucceeded={_pendingDeadVisualFallbackSucceeded} pdVisualFallbackFailed={_pendingDeadVisualFallbackFailed} pdBlockedHitFlash={_pendingDeadBlockedHitFlash} pdBlockedClientHit={_pendingDeadBlockedClientHit} terminalDeadMarkedAfterDie={_terminalDeadMarkedAfterDie} terminalDeadMarkedAfterVisualFallback={_terminalDeadMarkedAfterVisualFallback} terminalDeadMarkedFromHealthOnly={_terminalDeadMarkedFromHealthOnly} activeTerminalDead={_clientTerminalDeadHostIdx.Count} tdBlockedAttackPhase={_terminalDeadBlockedAttackPhase} tdBlockedGenericReplay={_terminalDeadBlockedGenericReplay} tdBlockedMovement={_terminalDeadBlockedMovement} tdBlockedHitReaction={_terminalDeadBlockedHitReaction} tdHealthIgnored={_terminalDeadHealthUpdatesIgnored} tdDeathReapplySkipped={_terminalDeadDeathReapplySkipped}"); Plugin.Log.Info($"[GameplayProbe] Phase5.3-D-flash dmgVisualPlayed={_damageVisualReactionsPlayed} dmgVisualDoWhiteFlash={_dmgVisualNativeDoWhiteFlash} dmgVisualSetHitEffect={_dmgVisualNativeSetHitEffect} dmgVisualMaterialHitTime={_dmgVisualMaterialHitTime} dmgVisualFallbackColor={_dmgVisualFallbackColor} dmgVisualFailNoNpc={_dmgVisualFailedNoNpc} dmgVisualFailNoMethod={_dmgVisualFailedNoMethod} dmgVisualFailNoMaterial={_dmgVisualFailedNoMaterial} dmgVisualSkipTerminalDead={_damageVisualReactionSkippedTerminalDead} dmgVisualSkipPendingDead={_damageVisualReactionSkippedPendingDead} dmgVisualSkipNoBinding={_damageVisualReactionSkippedNoBinding} dmgVisualSkipDupSeq={_damageVisualReactionSkippedDuplicateSeq} activeFlashes={_pendingHitFlashes.Count}"); Plugin.Log.Info($"[GameplayProbe] Phase5.3-E-manifest hostManifestBuilt={_hostManifestBuilt} clientManifestBuilt={_clientManifestBuilt} hostManifestSent={_hostManifestSent} clientManifestReceived={_clientManifestReceived} seedMismatch={_manifestSeedMismatch} roomMismatch={_manifestRoomMismatch} unitMismatch={_manifestUnitMismatch} specialMismatch={_manifestSpecialMismatch} modifierMismatch={_manifestHostEnemyModifierMismatch} hostOnlyUnits={_manifestHostOnlyUnits} clientOnlyUnits={_manifestClientOnlyUnits} hostEnemyBoundExisting={_manifestHostEnemyBoundExisting} hostEnemyBindFailedNoCandidate={_manifestHostEnemyBindFailedNoCandidate} hostEnemyBindFailedAmbiguous={_manifestHostEnemyBindFailedAmbiguous}"); Plugin.Log.Info($"[GameplayProbe] Phase5.3-F-generation-diff generationHashMatch={_generationHashMatch} generationHashMismatch={_generationHashMismatch} runtimeHashMatch={_runtimeHashMatch} runtimeHashMismatch={_runtimeHashMismatch} genHashStableSameRev={_generationHashStableSameRevision} genHashChangedSameRev={_generationHashChangedSameRevision}"); Plugin.Log.Info($"[GameplayProbe] Phase5.3-F-quarantine clientOnlyQuarantined={_manifestClientOnlyCombatQuarantined} qApplied={_manifestClientOnlyQuarantineApplied} qFailed={_manifestClientOnlyQuarantineFailed} qNoLocalKey={_manifestClientOnlyQuarantineNoLocalKey} qNoRuntime={_manifestClientOnlyQuarantineNoRuntime} qNonCombatSkipped={_manifestClientOnlyNonCombatSkipped} qAlready={_manifestClientOnlyAlreadyQuarantined}"); Plugin.Log.Info($"[GameplayProbe] Phase5.3-G-client-hit-visual hostHitVisualPlayed={_hostHitVisualPlayed} hostHitFatalVisualPlayed={_hostHitFatalVisualPlayed} hostHitVisualFailedNoNpc={_hostHitVisualFailedNoNpc} hostHitVisualEventSent={_hostHitVisualEventSent} clientHitVisualEventRecv={_clientHitVisualEventRecv} clientHitVisualPlayed={_clientHitVisualPlayed} clientHitFatalVisualPlayed={_clientHitFatalVisualPlayed} clientHitVisualSkipNoBinding={_clientHitVisualSkipNoBinding} clientHitVisualSkipPendingDead={_clientHitVisualSkipPendingDead} clientHitVisualSkipTerminalDead={_clientHitVisualSkipTerminalDead} clientHitVisualSkipFatalTerminalDead={_clientHitVisualSkippedFatalTerminalDead} clientHitVisualDuplicateSeq={_clientHitVisualDuplicateSeq}"); Plugin.Log.Info($"[GameplayProbe] Phase5.3-H-levelstep-trace levelStepResetContext={_levelGenStepTraced} nodeCoroutineMoveNext={_levelGenNodeCoroutineTraced}"); Plugin.Log.Info($"[GameplayProbe] Phase5.3-G-connector-trace finalizeConnection={_levelGenFinalizeConnectionTraced} connectorFinalizeSpawn={_levelGenConnectorFinalizeSpawnTraced} doorBlockerRecorded={_levelGenDoorBlockerRecorded}"); Plugin.Log.Info($"[GameplayProbe] Phase5.3-G-extra-room-trace extraRoomMoveNext={_levelGenExtraRoomTraced}"); Plugin.Log.Info($"[GameplayProbe] Phase5.3-H-manifest-gate processedMatchingRun={_manifestProcessedMatchingRun} deferredSceneMismatch={_manifestDeferredSceneMismatch} deferredLevelMismatch={_manifestDeferredLevelMismatch} deferredSeedMismatch={_manifestDeferredSeedMismatch} droppedStaleRun={_manifestDroppedStaleRun} acceptedAfterRunStateFix={_manifestAcceptedAfterRunStateFix} deferredLevelMismatchAfterFix={_manifestDeferredLevelMismatchAfterFix} hasDeferred={_deferredHostManifest != null} playerExcluded={_manifestPlayerExcluded} quarantinePlayerSkipped={_quarantinePlayerSkipped}"); Plugin.Log.Info($"[GameplayProbe] Phase5.3-G-manifest-quarantine currentClientOnly={_currentClientOnlyUnits} currentCombat={_currentClientOnlyCombatUnits} currentNonCombat={_currentClientOnlyNonCombatUnits} currentQuarantineApplied={_currentClientOnlyQuarantineApplied} currentAlready={_currentClientOnlyAlreadyQuarantined} currentNoRuntime={_currentClientOnlyNoRuntime} totalQuarantineApplied={_totalClientOnlyQuarantineApplied} totalClientOnlyUnitsSeen={_manifestClientOnlyUnits}"); AuditHostBoundPuppetLocalAi(); Plugin.Log.Info($"[GameplayProbe] Phase5.3-G-drift-diagnosis puppetsAudited={_hostBoundPuppetsAudited} navMeshStillEnabled={_localNavMeshStillEnabledOnHostBound} aiCanMove={_localAiCanMoveOnHostBound} rvoEnabled={_rvoStillEnabledOnHostBound} correctionLarge={_puppetTransformCorrectionLarge} correctionRepeated={_puppetTransformCorrectionRepeated} topDrift={FormatTopDrift(5)}"); Plugin.Log.Info($"[GameplayProbe] Phase5.4-C-target-authority localCleared={_enemyTargetLocalCleared} localOverwrite={_enemyTargetLocalOverwrite} noKnownMember={_enemyTargetNoKnownMember} bossMembersDiscovered={_enemyTargetBossMembersDiscovered} bossSuppressed={_enemyTargetBossSuppressionApplied} suppressionFailed={_enemyTargetSuppressionFailed} hostTargetApplied={_enemyTargetHostTargetApplied}"); } } } private static void LogEnemyPositionSourcesDiag(object npc, NetGameplayEntitySnapshot snapshot, Vector3 npcPos, float now) { //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_009c: 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_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) try { if (!Plugin.Cfg.LogTeleportDiag.Value) { return; } int spawnIndex = snapshot.SpawnIndex; _enemyPosDiagLastAtByIdx.TryGetValue(spawnIndex, out var value); if (now - value < 1f) { return; } _enemyPosDiagLastAtByIdx[spawnIndex] = now; object obj = TryFindComponentByTypeName(npc, "AiAgent"); object obj2 = TryFindComponentByTypeName(npc, "CustomRichAI") ?? TryFindComponentByTypeName(npc, "RichAI"); object comp = TryFindComponentByTypeName(npc, "Animator"); string text = "-"; if (obj2 != null) { text = ((TryGetMemberValue(obj2, "position") is Vector3 val) ? ((Vector3)(ref val)).ToString("F1") : P(obj2)); } string text2 = "-"; string text3 = "-"; string text4 = "-"; string text5 = "-"; string text6 = "-"; Component val2 = (Component)((npc is Component) ? npc : null); if (val2 != null && (Object)(object)val2 != (Object)null) { Bounds bounds; Vector3 center; try { Renderer componentInChildren = val2.GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null) { bounds = componentInChildren.bounds; center = ((Bounds)(ref bounds)).center; text2 = ((Vector3)(ref center)).ToString("F1"); } } catch { } try { Collider componentInChildren2 = val2.GetComponentInChildren(); if ((Object)(object)componentInChildren2 != (Object)null) { bounds = componentInChildren2.bounds; center = ((Bounds)(ref bounds)).center; text3 = ((Vector3)(ref center)).ToString("F1"); } } catch { } } if (obj != null) { if ((TryGetMemberValue(obj, "destination") ?? ((obj2 != null) ? TryGetMemberValue(obj2, "destination") : null)) is Vector3 val3) { text4 = ((Vector3)(ref val3)).ToString("F1"); } if (TryGetBoolMember(obj, "canMove", out var value2)) { text5 = value2.ToString(); } if ((TryGetMemberValue(obj, "velocity") ?? ((obj2 != null) ? TryGetMemberValue(obj2, "velocity") : null)) is Vector3 val4) { text6 = ((Vector3)(ref val4)).magnitude.ToString("F1"); } } Plugin.Log.Info(string.Format("[PosDiag] HOST idx={0} unit={1} npc={2} aiAgent={3} richAI={4} animator={5} renderer={6} collider={7} dest={8} canMove={9} vel={10}", spawnIndex, snapshot.EntityId.UnitIdentifier, ((Vector3)(ref npcPos)).ToString("F1"), P(obj), text, P(comp), text2, text3, text4, text5, text6)); } catch { } static string P(object? obj6) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) try { Component val5 = (Component)((obj6 is Component) ? obj6 : null); if (val5 != null && (Object)(object)val5 != (Object)null) { Vector3 position = val5.transform.position; return ((Vector3)(ref position)).ToString("F1"); } } catch { } return "-"; } } public static List CollectHostEnemyStateSnapshots(NetRunState state, bool aliveOnly, int maxCount, ref int sequence) { //IL_0307: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if (!IsEnabled()) { return list; } if (!Plugin.Cfg.EnableHostEnemyStateSnapshotMirror.Value) { return list; } if (!state.HasLevel) { return list; } if (Plugin.Cfg.EnableLevelSeedAuthority.Value && !state.HasLevelSeed) { return list; } if (maxCount <= 0) { maxCount = 64; } if (maxCount > 256) { maxCount = 256; } float realtimeSinceStartup = Time.realtimeSinceStartup; int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; foreach (NetGameplayEntitySnapshot item in from s in EntitiesByLocalId.Values where string.Equals(s.Category, "Npc", StringComparison.OrdinalIgnoreCase) orderby s.SpawnIndex select s) { num++; if (aliveOnly && item.IsDead) { num2++; MaybeLogSnapCollExclusion(item, "dead"); continue; } if (item.SyncCategory == 2 || item.SyncCategory == 3 || item.SyncCategory == 6) { _traderExcludedFromEnemySync++; num3++; MaybeLogSnapCollExclusion(item, "special:" + SyncCatName(item.SyncCategory)); continue; } if (IsEmperorWormSectionSnapshot(item)) { num3++; MaybeLogSnapCollExclusion(item, "emperor-worm-section"); continue; } if (list.Count >= maxCount) { break; } if (item.TryGetRuntimeObject(out object runtimeObject) && runtimeObject != null) { Object val = (Object)((runtimeObject is Object) ? runtimeObject : null); if (val != null && val == (Object)null) { num4++; MaybeLogSnapCollExclusion(item, "destroyed-runtime"); continue; } if (TryGetPosition(runtimeObject, out var position)) { item.HasPosition = true; item.Position = position; LogEnemyPositionSourcesDiag(runtimeObject, item, position, realtimeSinceStartup); } } sequence++; NetGameplayEnemyStateSnapshot netGameplayEnemyStateSnapshot = new NetGameplayEnemyStateSnapshot { Sequence = sequence, SourcePeerId = (string.IsNullOrWhiteSpace(state.PeerId) ? "host" : state.PeerId), ChapterName = state.ChapterName, LevelIndex = state.LevelIndex, HasLevelSeed = state.HasLevelSeed, LevelSeed = state.LevelSeed, SourceRevision = state.Revision, SentAt = Time.realtimeSinceStartup, SpawnIndex = item.SpawnIndex, CandidateKey = item.EntityId.CandidateKey, LocalInstanceId = item.EntityId.LocalInstanceId, UnityInstanceId = item.EntityId.UnityInstanceId, TypeName = item.EntityId.TypeName, UnitIdentifier = item.EntityId.UnitIdentifier, UnitGlobalId = item.EntityId.UnitGlobalId, Category = item.Category, ActorName = item.ActorName, HasPosition = item.HasPosition, Position = item.Position, IsDead = item.IsDead }; if (item.TryGetRuntimeObject(out object runtimeObject2) && runtimeObject2 != null && TryGetRotationY(runtimeObject2, out var rotationY)) { netGameplayEnemyStateSnapshot.HasRotationY = true; netGameplayEnemyStateSnapshot.RotationY = rotationY; } if (Plugin.Cfg.EnableHostEnemyAnimationMirror.Value && item.TryGetRuntimeObject(out object runtimeObject3) && runtimeObject3 != null) { TryPopulateEnemyAnimatorSnapshot(runtimeObject3, netGameplayEnemyStateSnapshot); } if (item.TryGetRuntimeObject(out object runtimeObject4) && runtimeObject4 != null) { TryPopulateEnemyAiIntentSnapshot(runtimeObject4, netGameplayEnemyStateSnapshot); TryPopulateHostTargetIdentity(runtimeObject4, netGameplayEnemyStateSnapshot); } if (ShouldSendHostEnemyStateSnapshot(netGameplayEnemyStateSnapshot, realtimeSinceStartup)) { bool hasActiveCombatAction = (netGameplayEnemyStateSnapshot.HasHostCombatAction && netGameplayEnemyStateSnapshot.HostCombatActionKind != 0) || (Plugin.Cfg.FullRateForEngagedEnemies.Value && netGameplayEnemyStateSnapshot.HasHostTarget); if (ShouldSendByInterestManagement(item, hasActiveCombatAction, realtimeSinceStartup)) { RememberHostEnemyStateSnapshotSent(netGameplayEnemyStateSnapshot, realtimeSinceStartup); list.Add(netGameplayEnemyStateSnapshot); num5++; } } } if (Plugin.Cfg.LogEnemyInterestDiag.Value && realtimeSinceStartup - _lastSnapCollLogAt > 1f) { _lastSnapCollLogAt = realtimeSinceStartup; NetLogger.Info($"[SnapColl] npcTracked={num} collected={num5} sent={list.Count} excludedDead={num2} excludedSpecial={num3} excludedDestroyed={num4}"); } return list; } private static void MaybeLogSnapCollExclusion(NetGameplayEntitySnapshot snap, string reason) { if (Plugin.Cfg.LogEnemyInterestDiag.Value && _snapCollExclLogged < 80) { _snapCollExclLogged++; NetLogger.Info($"[SnapColl] excluded idx={snap.SpawnIndex} unit={snap.EntityId.UnitIdentifier} actor={snap.ActorName} reason={reason}"); } } private static bool ShouldSendHostEnemyStateSnapshot(NetGameplayEnemyStateSnapshot snapshot, float now) { //IL_00bf: 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_0283: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_0331: Unknown result type (might be due to invalid IL or missing references) //IL_0337: Unknown result type (might be due to invalid IL or missing references) //IL_02f7: Unknown result type (might be due to invalid IL or missing references) //IL_02fd: Unknown result type (might be due to invalid IL or missing references) if (snapshot == null) { return false; } if (!Plugin.Cfg.EnableEnemyStateSnapshotDeltaCompression.Value) { return true; } int spawnIndex = snapshot.SpawnIndex; if (spawnIndex <= 0) { return true; } if (!HostEnemyStateSendCacheBySpawnIndex.TryGetValue(spawnIndex, out HostEnemyStateSendCache value)) { return true; } float num = Plugin.Cfg.EnemyStateSnapshotHeartbeatSeconds.Value; if (num < 0.1f) { num = 0.1f; } if (now - value.LastSentAt >= num) { return true; } if (snapshot.IsDead != value.IsDead) { return true; } if (snapshot.HasPosition != value.HasPosition) { return true; } if (snapshot.HasPosition && value.HasPosition) { float num2 = Plugin.Cfg.EnemyStateSnapshotPositionDeltaThreshold.Value; if (num2 < 0f) { num2 = 0f; } if (Vector3.Distance(snapshot.Position, value.Position) >= num2) { return true; } } if (snapshot.HasRotationY != value.HasRotationY) { return true; } if (snapshot.HasRotationY && value.HasRotationY) { float num3 = Plugin.Cfg.EnemyStateSnapshotRotationDeltaThresholdDegrees.Value; if (num3 < 0f) { num3 = 0f; } if (Mathf.Abs(Mathf.DeltaAngle(snapshot.RotationY, value.RotationY)) >= num3) { return true; } } if (snapshot.HasAnimatorState != value.HasAnimatorState) { return true; } if (snapshot.HasAnimatorState && value.HasAnimatorState) { if (snapshot.AnimatorFullPathHash != value.AnimatorFullPathHash) { return true; } float num4 = Plugin.Cfg.EnemyStateSnapshotAnimationTimeDeltaThreshold.Value; if (num4 < 0f) { num4 = 0f; } if (num4 > 1f) { num4 = 1f; } if (FractionDistance(snapshot.AnimatorNormalizedTime, value.AnimatorNormalizedTime) >= num4) { return true; } if (snapshot.HasAnimatorMovingBool != value.HasMovingBool || snapshot.AnimatorMovingBool != value.MovingBool) { return true; } if (snapshot.HasAnimatorAttackBool != value.HasAttackBool || snapshot.AnimatorAttackBool != value.AttackBool) { return true; } if (snapshot.HasAnimatorCoweringBool != value.HasCoweringBool || snapshot.AnimatorCoweringBool != value.CoweringBool) { return true; } } if (snapshot.HasHostCombatAction != value.HasHostCombatAction) { return true; } if (snapshot.HasHostCombatAction && value.HasHostCombatAction) { if (snapshot.HostCombatActionSequence != value.HostCombatActionSequence) { return true; } if (snapshot.HostCombatActionKind != value.HostCombatActionKind) { return true; } if (snapshot.HostCombatActionState != value.HostCombatActionState) { return true; } } if (snapshot.HasHostCombatAim != value.HasHostCombatAim) { return true; } if (snapshot.HasHostCombatAim && value.HasHostCombatAim) { if (Vector3.Distance(snapshot.HostCombatOriginPosition, value.HostCombatOriginPosition) > 0.05f) { return true; } if (Vector3.Distance(snapshot.HostCombatAimPosition, value.HostCombatAimPosition) > 0.05f) { return true; } } if (snapshot.HasAiIntent != value.HasAiIntent) { return true; } if (snapshot.HasAiIntent && value.HasAiIntent) { if (snapshot.AiIntentSequence != value.AiIntentSequence) { return true; } if (snapshot.AiIntentKind != value.AiIntentKind) { return true; } if (Vector3.Distance(snapshot.AiIntentDestination, value.AiIntentDestination) > 0.1f) { return true; } } if (snapshot.HasAiIntentLookAt != value.HasAiIntentLookAt) { return true; } if (snapshot.HasAiIntentLookAt && value.HasAiIntentLookAt && Vector3.Distance(snapshot.AiIntentLookAt, value.AiIntentLookAt) > 0.1f) { return true; } if (snapshot.HostCombatAnimatorStateCount != value.HostCombatAnimatorStateCount) { return true; } int num5 = snapshot.HostCombatAnimatorStateCount; if (num5 > 4) { num5 = 4; } for (int i = 0; i < num5; i++) { if (snapshot.HostCombatAnimatorPathHashes[i] != value.HostCombatAnimatorPathHashes[i]) { return true; } if (snapshot.HostCombatAnimatorFullPathHashes[i] != value.HostCombatAnimatorFullPathHashes[i]) { return true; } if (FractionDistance(snapshot.HostCombatAnimatorNormalizedTimes[i], value.HostCombatAnimatorNormalizedTimes[i]) > 0.2f) { return true; } } return false; } private static void RememberHostEnemyStateSnapshotSent(NetGameplayEnemyStateSnapshot snapshot, float now) { //IL_0032: 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_0116: 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_015e: Unknown result type (might be due to invalid IL or missing references) if (snapshot != null && snapshot.SpawnIndex > 0) { HostEnemyStateSendCacheBySpawnIndex[snapshot.SpawnIndex] = new HostEnemyStateSendCache { LastSentAt = now, HasPosition = snapshot.HasPosition, Position = snapshot.Position, HasRotationY = snapshot.HasRotationY, RotationY = snapshot.RotationY, IsDead = snapshot.IsDead, HasAnimatorState = snapshot.HasAnimatorState, AnimatorFullPathHash = snapshot.AnimatorFullPathHash, AnimatorNormalizedTime = snapshot.AnimatorNormalizedTime, HasMovingBool = snapshot.HasAnimatorMovingBool, MovingBool = snapshot.AnimatorMovingBool, HasAttackBool = snapshot.HasAnimatorAttackBool, AttackBool = snapshot.AnimatorAttackBool, HasCoweringBool = snapshot.HasAnimatorCoweringBool, CoweringBool = snapshot.AnimatorCoweringBool, HasHostCombatAction = snapshot.HasHostCombatAction, HostCombatActionKind = snapshot.HostCombatActionKind, HostCombatActionState = snapshot.HostCombatActionState, HostCombatActionSequence = snapshot.HostCombatActionSequence, HasHostCombatAim = snapshot.HasHostCombatAim, HostCombatOriginPosition = snapshot.HostCombatOriginPosition, HostCombatAimPosition = snapshot.HostCombatAimPosition, HasAiIntent = snapshot.HasAiIntent, AiIntentSequence = snapshot.AiIntentSequence, AiIntentKind = snapshot.AiIntentKind, AiIntentDestination = snapshot.AiIntentDestination, HasAiIntentLookAt = snapshot.HasAiIntentLookAt, AiIntentLookAt = snapshot.AiIntentLookAt, HostCombatAnimatorStateCount = snapshot.HostCombatAnimatorStateCount }; int num = snapshot.HostCombatAnimatorStateCount; if (num > 4) { num = 4; } for (int i = 0; i < num; i++) { HostEnemyStateSendCacheBySpawnIndex[snapshot.SpawnIndex].HostCombatAnimatorPathHashes[i] = snapshot.HostCombatAnimatorPathHashes[i]; HostEnemyStateSendCacheBySpawnIndex[snapshot.SpawnIndex].HostCombatAnimatorFullPathHashes[i] = snapshot.HostCombatAnimatorFullPathHashes[i]; HostEnemyStateSendCacheBySpawnIndex[snapshot.SpawnIndex].HostCombatAnimatorNormalizedTimes[i] = snapshot.HostCombatAnimatorNormalizedTimes[i]; } } } public static void ProcessHostEnemyStateSnapshots(List snapshots) { //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) if (!IsEnabled() || snapshots == null || snapshots.Count == 0) { return; } if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state)) { if (Plugin.Cfg.LogReceivedEnemyStateSnapshots.Value) { NetLogger.Warn($"[EnemyStateMirror] Ignoring HostEnemyStateSnapshot batch because local run state is unavailable count={snapshots.Count}"); } return; } int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; float num7 = 0f; float num8 = 0f; string text = ""; float num9 = Plugin.Cfg.EnemyStateSnapshotPositionTolerance.Value; if (num9 < 0f) { num9 = 0f; } foreach (NetGameplayEnemyStateSnapshot snapshot2 in snapshots) { if (!snapshot2.MatchesScene(state)) { num++; continue; } if (snapshot2.SpawnIndex > 0) { _clientLastSnapshotRecvByHostIdx[snapshot2.SpawnIndex] = Time.realtimeSinceStartup; } if (!TryFindLocalEnemyStateMatch(snapshot2, out NetGameplayEntitySnapshot snapshot, out string detail)) { num3++; if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug($"[EnemyStateMirror] No local match for host snapshot idx={snapshot2.SpawnIndex} actor={snapshot2.ActorName} detail={detail}"); } continue; } num2++; if (snapshot != null && snapshot.IsDead != snapshot2.IsDead) { num4++; } if (snapshot != null && snapshot2.HasPosition && snapshot.HasPosition) { float num10 = Vector3.Distance(snapshot.Position, snapshot2.Position); num7 += num10; if (num10 > num9) { num5++; } if (num10 > num8) { num8 = num10; text = $"idx={snapshot2.SpawnIndex} actor={snapshot2.ActorName} local={snapshot.PositionText} host={snapshot2.PositionText}"; } } string detail2 = "apply disabled"; if (Plugin.Cfg.ApplyReceivedEnemyStateSnapshots.Value && TryQueueEnemyStateTarget(snapshot, snapshot2, out detail2)) { num6++; } else if (Plugin.Cfg.ApplyReceivedEnemyStateSnapshots.Value && Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug($"[EnemyStateMirror] Snapshot apply not queued idx={snapshot2.SpawnIndex} actor={snapshot2.ActorName} detail={detail2}"); } } if (Plugin.Cfg.LogReceivedEnemyStateSnapshots.Value) { float num11 = ((num2 > 0) ? (num7 / (float)num2) : 0f); bool value = Plugin.Cfg.ApplyReceivedEnemyStateSnapshots.Value; NetGameplayEnemyStateSnapshot netGameplayEnemyStateSnapshot = snapshots[0]; NetLogger.Info($"[EnemyStateMirror] Batch seq={netGameplayEnemyStateSnapshot.Sequence}-{snapshots[snapshots.Count - 1].Sequence} count={snapshots.Count} matched={num2} unmatched={num3} sceneSkipped={num} deadMismatch={num4} drift>{num9:F1}m={num5} avgDelta={num11:F2}m maxDelta={num8:F2}m queued={num6} activeTargets={PendingEnemyStateTargets.Count} scene={netGameplayEnemyStateSnapshot.SceneKey} seed={netGameplayEnemyStateSnapshot.SeedText} apply={value}"); if (Plugin.Cfg.EnableDebugLog.Value && num8 > num9 && !string.IsNullOrWhiteSpace(text)) { NetLogger.Debug("[EnemyStateMirror] Worst drift: " + text); } } } private static bool TryQueueEnemyStateTarget(NetGameplayEntitySnapshot? localSnapshot, NetGameplayEnemyStateSnapshot hostSnapshot, out string detail) { //IL_0067: 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_00a8: 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_0261: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_0276: Unknown result type (might be due to invalid IL or missing references) //IL_029f: Unknown result type (might be due to invalid IL or missing references) //IL_02af: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) detail = ""; if (localSnapshot == null) { detail = "local snapshot is null"; return false; } if (localSnapshot.IsDead) { detail = $"local entity already dead idx={localSnapshot.SpawnIndex}"; return false; } if (hostSnapshot.IsDead) { detail = $"host entity is dead idx={hostSnapshot.SpawnIndex}"; return false; } if (!hostSnapshot.HasPosition) { detail = "host snapshot has no position"; return false; } if (!IsFinite(hostSnapshot.Position)) { detail = $"host snapshot position is not finite idx={hostSnapshot.SpawnIndex}"; return false; } if (localSnapshot.HasPosition && IsFinite(localSnapshot.Position)) { float num = Vector3.Distance(localSnapshot.Position, hostSnapshot.Position); float num2 = Math.Max(100f, Plugin.Cfg.EnemyStateSnapshotSnapDistance.Value * 10f); if (num > num2) { if (!_runtimeSpawnBindingsByHostIdx.ContainsKey(hostSnapshot.SpawnIndex)) { detail = $"unsafe apply distance={num:F2}m > max={num2:F2}m idx={hostSnapshot.SpawnIndex}"; return false; } if (Plugin.Cfg.LogBossDynamicSpawn.Value) { NetLogger.Info($"[DesertAdds] far-apply exemption (RT3 mirror) idx={hostSnapshot.SpawnIndex} dist={num:F1}m"); } } } if (!localSnapshot.TryGetRuntimeObject(out object runtimeObject) || runtimeObject == null) { detail = $"runtime object unavailable idx={localSnapshot.SpawnIndex}"; return false; } Object val = (Object)((runtimeObject is Object) ? runtimeObject : null); if (val != null && val == (Object)null) { detail = $"runtime Unity object destroyed idx={localSnapshot.SpawnIndex}"; return false; } if (!TryGetTransform(runtimeObject, out Transform transform) || (Object)(object)transform == (Object)null) { detail = $"runtime object has no transform idx={localSnapshot.SpawnIndex}"; return false; } string snapshotTargetKey = GetSnapshotTargetKey(localSnapshot); if (string.IsNullOrWhiteSpace(snapshotTargetKey)) { detail = $"no stable local key idx={localSnapshot.SpawnIndex}"; return false; } if (PendingEnemyStateTargets.TryGetValue(snapshotTargetKey, out EnemyStateTarget value) && hostSnapshot.Sequence <= value.Sequence) { detail = $"stale transform target idx={localSnapshot.SpawnIndex} incomingSeq={hostSnapshot.Sequence} currentSeq={value.Sequence}"; return false; } float realtimeSinceStartup = Time.realtimeSinceStartup; Vector3 startPosition = ((localSnapshot.HasPosition && IsFinite(localSnapshot.Position)) ? localSnapshot.Position : hostSnapshot.Position); string detail2; float enemyStateSnapshotPlaybackDuration = GetEnemyStateSnapshotPlaybackDuration(snapshotTargetKey, realtimeSinceStartup, startPosition, hostSnapshot.Position, hostSnapshot, out detail2); PendingEnemyStateTargets[snapshotTargetKey] = new EnemyStateTarget { Snapshot = localSnapshot, HostSnapshot = hostSnapshot, StartPosition = startPosition, HasPosition = true, Position = hostSnapshot.Position, HasRotationY = hostSnapshot.HasRotationY, RotationY = hostSnapshot.RotationY, Sequence = hostSnapshot.Sequence, PlaybackStartedAt = realtimeSinceStartup, PlaybackDuration = enemyStateSnapshotPlaybackDuration, LastUpdatedAt = realtimeSinceStartup, RuntimeObject = runtimeObject, Transform = transform }; _enemyStateTargetsQueued++; detail = $"queued transform target idx={localSnapshot.SpawnIndex} seq={hostSnapshot.Sequence} playback={detail2}"; return true; } private static float GetEnemyStateSnapshotPlaybackDuration(string key, float now, Vector3 startPosition, Vector3 hostPosition, NetGameplayEnemyStateSnapshot? hostSnapshot, out string detail) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) if (hostSnapshot != null && hostSnapshot.HasHostCombatAction && hostSnapshot.HostCombatActionKind != 0) { detail = "combat-snap playback=0"; return 0f; } float num = Plugin.Cfg.EnemyStateSnapshotSnapDistance.Value; if (num < 0.1f) { num = 0.1f; } float num2 = Vector3.Distance(startPosition, hostPosition); if (num2 >= num) { detail = $"snap distance={num2:F2}m duration=0"; return 0f; } float num3 = Plugin.Cfg.EnemyStateSnapshotSendRateHz.Value; if (num3 < 0.1f) { num3 = 0.1f; } float num4 = 1f / num3; float num5 = num4; if (!string.IsNullOrWhiteSpace(key) && PendingEnemyStateTargets.TryGetValue(key, out EnemyStateTarget value)) { float num6 = now - value.LastUpdatedAt; if (num6 >= 0.02f && num6 <= 2f) { num5 = num6; } } float num7 = Plugin.Cfg.EnemyStateSnapshotPlaybackDurationMultiplier.Value; if (num7 < 0.5f) { num7 = 0.5f; } if (num7 > 2.5f) { num7 = 2.5f; } float num8 = num5 * num7; float num9 = Mathf.Max(0.03f, num4 * 0.5f); float num10 = Mathf.Min(1.25f, num4 * 2.5f); num8 = Mathf.Clamp(num8, num9, num10); detail = $"duration={num8:F3}s observed={num5:F3}s expected={num4:F3}s distance={num2:F2}m"; return num8; } private static void ApplyPendingEnemyStateTargets() { //IL_0454: Unknown result type (might be due to invalid IL or missing references) //IL_0459: Unknown result type (might be due to invalid IL or missing references) //IL_045b: Unknown result type (might be due to invalid IL or missing references) //IL_0466: Unknown result type (might be due to invalid IL or missing references) //IL_04b3: Unknown result type (might be due to invalid IL or missing references) //IL_04ba: Unknown result type (might be due to invalid IL or missing references) //IL_04c1: Unknown result type (might be due to invalid IL or missing references) //IL_04c6: Unknown result type (might be due to invalid IL or missing references) //IL_0507: Unknown result type (might be due to invalid IL or missing references) //IL_050b: Unknown result type (might be due to invalid IL or missing references) //IL_0517: Unknown result type (might be due to invalid IL or missing references) //IL_0519: Unknown result type (might be due to invalid IL or missing references) //IL_06bd: Unknown result type (might be due to invalid IL or missing references) //IL_06c2: Unknown result type (might be due to invalid IL or missing references) //IL_06f3: Unknown result type (might be due to invalid IL or missing references) //IL_06f5: Unknown result type (might be due to invalid IL or missing references) //IL_06f9: Unknown result type (might be due to invalid IL or missing references) //IL_06fe: Unknown result type (might be due to invalid IL or missing references) //IL_06ce: Unknown result type (might be due to invalid IL or missing references) //IL_06d0: Unknown result type (might be due to invalid IL or missing references) //IL_0700: Unknown result type (might be due to invalid IL or missing references) //IL_0702: Unknown result type (might be due to invalid IL or missing references) //IL_0723: Unknown result type (might be due to invalid IL or missing references) //IL_0754: Unknown result type (might be due to invalid IL or missing references) //IL_0756: Unknown result type (might be due to invalid IL or missing references) //IL_075a: Unknown result type (might be due to invalid IL or missing references) //IL_0712: Unknown result type (might be due to invalid IL or missing references) //IL_057c: Unknown result type (might be due to invalid IL or missing references) //IL_0581: Unknown result type (might be due to invalid IL or missing references) //IL_05ca: Unknown result type (might be due to invalid IL or missing references) //IL_05ce: Unknown result type (might be due to invalid IL or missing references) //IL_05d5: Unknown result type (might be due to invalid IL or missing references) //IL_05da: Unknown result type (might be due to invalid IL or missing references) //IL_06b0: Unknown result type (might be due to invalid IL or missing references) //IL_06b2: Unknown result type (might be due to invalid IL or missing references) //IL_0689: Unknown result type (might be due to invalid IL or missing references) //IL_068b: Unknown result type (might be due to invalid IL or missing references) //IL_068f: Unknown result type (might be due to invalid IL or missing references) //IL_0694: Unknown result type (might be due to invalid IL or missing references) //IL_064c: Unknown result type (might be due to invalid IL or missing references) //IL_064e: Unknown result type (might be due to invalid IL or missing references) //IL_062e: Unknown result type (might be due to invalid IL or missing references) //IL_0630: Unknown result type (might be due to invalid IL or missing references) //IL_0634: Unknown result type (might be due to invalid IL or missing references) //IL_0639: Unknown result type (might be due to invalid IL or missing references) //IL_08d3: Unknown result type (might be due to invalid IL or missing references) //IL_08d7: Unknown result type (might be due to invalid IL or missing references) //IL_080b: Unknown result type (might be due to invalid IL or missing references) //IL_081f: Unknown result type (might be due to invalid IL or missing references) //IL_0833: Unknown result type (might be due to invalid IL or missing references) //IL_0869: Unknown result type (might be due to invalid IL or missing references) //IL_086e: Unknown result type (might be due to invalid IL or missing references) //IL_0870: Unknown result type (might be due to invalid IL or missing references) //IL_089e: Unknown result type (might be due to invalid IL or missing references) //IL_08b8: Unknown result type (might be due to invalid IL or missing references) //IL_08c3: Unknown result type (might be due to invalid IL or missing references) if (PendingEnemyStateTargets.Count == 0) { return; } if (!Plugin.Cfg.ApplyReceivedEnemyStateSnapshots.Value) { PendingEnemyStateTargets.Clear(); return; } float realtimeSinceStartup = Time.realtimeSinceStartup; float num = Plugin.Cfg.EnemyStateSnapshotInterpolationSpeed.Value; if (num < 1f) { num = 1f; } float num2 = Plugin.Cfg.EnemyStateSnapshotSnapDistance.Value; if (num2 < 0.1f) { num2 = 0.1f; } bool value = Plugin.Cfg.EnemyStateSnapshotApplyRotationY.Value; ScratchEnemyStateTargetKeys.Clear(); ScratchEnemyStateTargetKeys.AddRange(PendingEnemyStateTargets.Keys); foreach (string scratchEnemyStateTargetKey in ScratchEnemyStateTargetKeys) { if (!PendingEnemyStateTargets.TryGetValue(scratchEnemyStateTargetKey, out EnemyStateTarget value2)) { continue; } NetGameplayEntitySnapshot snapshot = value2.Snapshot; if (snapshot == null) { ReleaseEnemyPuppet(scratchEnemyStateTargetKey, "snapshot null"); PendingEnemyStateTargets.Remove(scratchEnemyStateTargetKey); continue; } if (IsClientTerminalDeadByKey(scratchEnemyStateTargetKey)) { _terminalDeadBlockedMovement++; PendingEnemyStateTargets.Remove(scratchEnemyStateTargetKey); continue; } if (snapshot.IsDead || !value2.HasPosition) { if (ClientLocalKeyToHostSpawnIndex.ContainsKey(scratchEnemyStateTargetKey)) { _puppetReleaseSuppressedAwaitingHost++; PendingEnemyStateTargets.Remove(scratchEnemyStateTargetKey); } else { ReleaseEnemyPuppet(scratchEnemyStateTargetKey, snapshot.IsDead ? "local-snapshot dead (unbound)" : "no position (unbound)"); PendingEnemyStateTargets.Remove(scratchEnemyStateTargetKey); } continue; } if (realtimeSinceStartup - value2.LastUpdatedAt > 2f) { PendingEnemyStateTargets.Remove(scratchEnemyStateTargetKey); continue; } object runtimeObject = value2.RuntimeObject; Transform transform = value2.Transform; if (runtimeObject != null) { Object val = (Object)((runtimeObject is Object) ? runtimeObject : null); if ((val == null || !(val == (Object)null)) && !((Object)(object)transform == (Object)null)) { Object val2 = (Object)(object)transform; if (val2 == null || !(val2 == (Object)null)) { goto IL_02de; } } } bool flag = ClientLocalKeyToHostSpawnIndex.ContainsKey(scratchEnemyStateTargetKey); if (!snapshot.TryGetRuntimeObject(out runtimeObject) || runtimeObject == null) { ReleaseEnemyPuppet(scratchEnemyStateTargetKey, flag ? "runtime object unavailable (host-bound, likely scene reload)" : "runtime object unavailable"); PendingEnemyStateTargets.Remove(scratchEnemyStateTargetKey); continue; } Object val3 = (Object)((runtimeObject is Object) ? runtimeObject : null); if (val3 != null && val3 == (Object)null) { ReleaseEnemyPuppet(scratchEnemyStateTargetKey, flag ? "runtime Unity object destroyed (host-bound)" : "runtime Unity object destroyed"); PendingEnemyStateTargets.Remove(scratchEnemyStateTargetKey); continue; } if (!TryGetTransform(runtimeObject, out transform) || (Object)(object)transform == (Object)null) { ReleaseEnemyPuppet(scratchEnemyStateTargetKey, flag ? "runtime object has no transform (host-bound)" : "runtime object has no transform"); PendingEnemyStateTargets.Remove(scratchEnemyStateTargetKey); continue; } value2.RuntimeObject = runtimeObject; value2.Transform = transform; goto IL_02de; IL_02de: if (NetBossEncounterManager.IsWitchPhase2Suppressed(runtimeObject) || IsSelfAnimatingClientBossAdd(snapshot) || IsEmperorWormSectionSnapshot(snapshot) || (IsDesertBossSnapshot(snapshot) && NetBossEncounterManager.IsLocalIntroPresentationActive())) { continue; } bool num3 = (IsDesertBossPikeSnapshot(snapshot) && NetBossEncounterManager.IsDesertPikePuppetExempt()) || (IsDesertBossSnapshot(snapshot) && !IsDesertBossPikeSnapshot(snapshot) && NetBossEncounterManager.IsDesertBossBodyPuppetExempt()) || IsDesertEnemyPikeSnapshot(snapshot); EnsureClientEnemyPuppetMode(scratchEnemyStateTargetKey, snapshot, value2.HostSnapshot, runtimeObject, realtimeSinceStartup); int num4 = ObjectIdentity(runtimeObject); ClientEnemyControlMode clientEnemyControlMode = ClientEnemyControlMode.PassiveSnapshot; if (value2.HostSnapshot != null && NetConfig.GetMode() == NetMode.Client) { if (num4 != 0 && TryCreateOrRefreshClientIntentWindow(num4, value2.HostSnapshot, realtimeSinceStartup) && _clientAuthorizedIntentByNpcId.TryGetValue(num4, out ClientEnemyAuthorizedIntentWindow value3) && ActiveEnemyPuppetsByNpcId.TryGetValue(num4, out EnemyPuppetRecord value4) && value4.Npc != null) { TryExecuteClientEnemyIntentRootReplay(value4, value3, value4.Npc, value2.HostSnapshot); } if (num4 != 0 && _clientAuthorizedIntentByNpcId.TryGetValue(num4, out ClientEnemyAuthorizedIntentWindow value5) && realtimeSinceStartup <= value5.ExpiresAt) { clientEnemyControlMode = ((value5.Kind != 4 && value5.Kind != 5 && value5.Kind != 6) ? ClientEnemyControlMode.IntentLocomotion : ClientEnemyControlMode.AuthorizedCombat); } else if (IsClientEnemyIntentDrivenMotionEnabled() && value2.HostSnapshot.HasAiIntent) { clientEnemyControlMode = ClientEnemyControlMode.IntentLocomotion; } if (ActiveEnemyPuppets.TryGetValue(scratchEnemyStateTargetKey, out EnemyPuppetRecord value6)) { value6.ControlMode = clientEnemyControlMode; } } if (num3) { continue; } Vector3 position = transform.position; if (!IsFinite(position) || !IsFinite(value2.Position)) { PendingEnemyStateTargets.Remove(scratchEnemyStateTargetKey); continue; } float num5 = ((value2.PlaybackDuration <= 0.001f) ? 1f : Mathf.Clamp01((realtimeSinceStartup - value2.PlaybackStartedAt) / value2.PlaybackDuration)); Vector3 val4 = Vector3.Lerp(value2.StartPosition, value2.Position, num5); int num6; if (IsClientEnemyIntentDrivenMotionEnabled()) { num6 = (value2.HostSnapshot.HasAiIntent ? 1 : 0); if (num6 != 0 && clientEnemyControlMode != ClientEnemyControlMode.AuthorizedCombat && ActiveEnemyPuppets.TryGetValue(scratchEnemyStateTargetKey, out EnemyPuppetRecord value7)) { TryApplyClientEnemyAiIntent(value7, runtimeObject, value2.HostSnapshot, realtimeSinceStartup); } } else { num6 = 0; } float num7 = Vector3.Distance(position, value2.Position); float num8 = Vector3.Distance(position, val4); bool flag2 = false; Vector3 val5; if (num6 != 0) { float num9 = Plugin.Cfg.EnemyIntentCorrectionDistance.Value; if (num9 < 0.25f) { num9 = 0.25f; } float num10 = Plugin.Cfg.EnemyIntentHardSnapDistance.Value; if (num10 < num9) { num10 = num9 + 0.5f; } if (num7 >= num10) { val5 = value2.Position; flag2 = true; _hardDriftCorrections++; _clientEnemyAiIntentCorrections++; } else { switch (clientEnemyControlMode) { case ClientEnemyControlMode.AuthorizedCombat: { float num16 = Mathf.Max(Time.deltaTime, 1f / 60f); float num17 = Mathf.Clamp01(num * num16 * 0.1f); val5 = Vector3.Lerp(position, value2.Position, num17); _softDriftCorrections++; break; } case ClientEnemyControlMode.IntentLocomotion: { float num13 = Mathf.Max(0.4f, num9 * 0.5f); if (num7 >= num13) { float num14 = Mathf.Max(Time.deltaTime, 1f / 60f); float num15 = Mathf.Clamp01(num * num14 * 0.15f); val5 = Vector3.Lerp(position, val4, num15); _softDriftCorrections++; } else { val5 = position; _driftSkippedIntentLocomotion++; } break; } default: if (num7 >= num9) { float num11 = Mathf.Max(Time.deltaTime, 1f / 60f); float num12 = Mathf.Clamp01(num * num11 * 0.35f); val5 = Vector3.Lerp(position, val4, num12); _softDriftCorrections++; _clientEnemyAiIntentCorrections++; } else { val5 = position; } break; } } } else if (num7 >= num2) { val5 = value2.Position; flag2 = true; } else if (num8 >= num2) { val5 = val4; flag2 = true; } else { float num18 = Mathf.Max(Time.deltaTime, 1f / 60f); float num19 = Mathf.Clamp01(num * num18); val5 = Vector3.Lerp(position, val4, num19); } if (Vector3.Distance(position, val5) > 0.001f) { transform.position = val5; } snapshot.HasPosition = true; snapshot.Position = val5; snapshot.LastSeenAt = realtimeSinceStartup; NoteDrift(scratchEnemyStateTargetKey, num7, flag2, snapshot.EntityId.UnitIdentifier); ApplyClientEnemyPuppetMotionAnimation(scratchEnemyStateTargetKey, value2.HostSnapshot, runtimeObject, position, val5, value2.Position, realtimeSinceStartup); if (!value2.AnimationAppliedForSequence) { ApplyClientEnemyAnimationMirror(scratchEnemyStateTargetKey, value2.HostSnapshot, runtimeObject, realtimeSinceStartup); value2.AnimationAppliedForSequence = true; } _enemyStateTargetsApplied++; if (flag2) { _enemyStateTargetsSnapped++; if (Plugin.Cfg.EnableDebugLog.Value && !value2.LoggedSnap) { value2.LoggedSnap = true; NetLogger.Debug($"[EnemyStateMirror] Snapped local enemy to Host playback idx={snapshot.SpawnIndex} actor={snapshot.ActorName} desiredDistance={num8:F2}m hostDistance={num7:F2}m target=({value2.Position.x:F2},{value2.Position.y:F2},{value2.Position.z:F2})"); } } if (value && value2.HasRotationY && IsFinite(value2.RotationY)) { Vector3 eulerAngles = transform.eulerAngles; if (IsFinite(eulerAngles)) { float num20 = (flag2 ? 1f : Mathf.Clamp01(num * Mathf.Max(Time.deltaTime, 1f / 60f))); eulerAngles.y = Mathf.LerpAngle(eulerAngles.y, value2.RotationY, num20); if (IsFinite(eulerAngles)) { transform.eulerAngles = eulerAngles; } } } if (num5 >= 1f && Vector3.Distance(val5, value2.Position) < 0.05f && realtimeSinceStartup - value2.LastUpdatedAt > 0.75f) { PendingEnemyStateTargets.Remove(scratchEnemyStateTargetKey); } } } public static bool ShouldSkipClientEnemyPuppetFinalMovement(object? movementDriver) { if (!IsClientEnemyPuppetModeEnabled()) { return false; } if (movementDriver == null) { return false; } if (IsClientEnemyIntentDrivenMotionEnabled()) { return false; } return ClientPuppetMovementDriverIds.Contains(ObjectIdentity(movementDriver)); } public static bool ShouldBlockClientEnemyPuppetAiAgentState(object? aiAgent, bool requestedState) { if (!IsClientEnemyPuppetModeEnabled()) { return false; } if (aiAgent == null) { return false; } if (!requestedState) { return false; } if (_clientPuppetIntentReplayDepth > 0) { return false; } return ClientPuppetAiAgentIds.Contains(ObjectIdentity(aiAgent)); } public static bool ShouldBlockClientEnemyPuppetAiAgentMovement(object? aiAgent) { if (!IsClientEnemyPuppetModeEnabled()) { return false; } if (aiAgent == null) { return false; } if (_clientPuppetIntentReplayDepth > 0) { return false; } return ClientPuppetAiAgentIds.Contains(ObjectIdentity(aiAgent)); } public static bool ShouldBlockClientEnemyPuppetNpcMovement(object? npc) { if (!IsClientEnemyPuppetModeEnabled()) { return false; } if (npc == null) { return false; } if (_clientPuppetIntentReplayDepth > 0) { return false; } return ClientPuppetNpcIds.Contains(ObjectIdentity(npc)); } public static bool ShouldBlockClientEnemyPuppetBehaviourTreeActivation(object? npc, bool requestedState) { if (!IsClientEnemyPuppetModeEnabled()) { return false; } if (npc == null) { return false; } if (requestedState) { return ClientPuppetNpcIds.Contains(ObjectIdentity(npc)); } return false; } public static bool ShouldBlockClientEnemyPuppetNpcTargeting(object? npc, object? target, string source, out string detail) { detail = ""; if (!IsHostOnlyEnemyTargetAuthorityEnabled()) { return false; } if (npc == null) { return false; } if (_clientPuppetInternalTargetClearDepth > 0) { return false; } int num = ObjectIdentity(npc); if (num == 0 || !ActiveEnemyPuppetsByNpcId.TryGetValue(num, out EnemyPuppetRecord value)) { return false; } float realtimeSinceStartup = Time.realtimeSinceStartup; _clientEnemyPuppetTargetBlocks++; ApplyClientEnemyTargetAuthority(value, realtimeSinceStartup, "blocked " + Clean(source)); detail = $"idx={value.Snapshot.SpawnIndex} actor={value.Snapshot.ActorName} source={Clean(source)} target={DescribeTarget(target)}"; MaybeLogClientEnemyTargetAuthority(value, realtimeSinceStartup, "block-local-npc-target", detail, force: false); return true; } public static bool ShouldBlockClientEnemyPuppetAiAgentTargeting(object? aiAgent, string source, out string detail) { detail = ""; if (!IsHostOnlyEnemyTargetAuthorityEnabled()) { return false; } if (aiAgent == null) { return false; } int num = ObjectIdentity(aiAgent); if (num == 0 || !ClientPuppetAiAgentIds.Contains(num)) { return false; } _clientEnemyPuppetTargetBlocks++; if (BarrenTargetAuthorityAiIds.Contains(num)) { return true; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (TryGetPuppetRecordForAiAgent(aiAgent, out EnemyPuppetRecord record)) { ApplyClientEnemyTargetAuthority(record, realtimeSinceStartup, "blocked " + Clean(source)); detail = $"idx={record.Snapshot.SpawnIndex} actor={record.Snapshot.ActorName} source={Clean(source)} ai={aiAgent.GetType().Name}"; MaybeLogClientEnemyTargetAuthority(record, realtimeSinceStartup, "block-local-ai-target", detail, force: false); } else { detail = "source=" + Clean(source) + " ai=" + aiAgent.GetType().Name; } return true; } public static void ReportClientEnemyPuppetAiTargetResult(object? aiAgent, object? result, string source) { if (!IsHostOnlyEnemyTargetAuthorityEnabled() || aiAgent == null || result == null) { return; } int num = ObjectIdentity(aiAgent); if (num != 0 && ClientPuppetAiAgentIds.Contains(num)) { float realtimeSinceStartup = Time.realtimeSinceStartup; _clientEnemyPuppetTargetBlocks++; if (TryGetPuppetRecordForAiAgent(aiAgent, out EnemyPuppetRecord record)) { ApplyClientEnemyTargetAuthority(record, realtimeSinceStartup, "observed " + Clean(source)); string detail = $"idx={record.Snapshot.SpawnIndex} actor={record.Snapshot.ActorName} source={Clean(source)} returnedTarget={DescribeTarget(result)}"; MaybeLogClientEnemyTargetAuthority(record, realtimeSinceStartup, "observed-local-ai-target", detail, force: false); } } } public static bool ShouldBlockClientEnemyPuppetNpcCombat(object? npc, string source, out string detail) { detail = ""; if (!IsEnemyCombatProbeEnabled()) { return false; } if (!IsClientEnemyPuppetModeEnabled()) { return false; } if (npc == null) { return false; } if (TryGetSnapshotForEntity(npc, out NetGameplayEntitySnapshot snapshot) && snapshot != null && IsNonCombatForSync(snapshot)) { _combatProbeRejectedNonCombat++; return false; } if (snapshot != null) { string snapshotTargetKey = GetSnapshotTargetKey(snapshot); if (!string.IsNullOrWhiteSpace(snapshotTargetKey) && ClientQuarantinedEntities.Contains(snapshotTargetKey)) { _quarantinedCombatSuppressed++; detail = $"quarantined client-only unit={snapshot.EntityId.UnitIdentifier} localIdx={snapshot.SpawnIndex}"; return true; } } int num = ObjectIdentity(npc); if (num == 0 || !ActiveEnemyPuppetsByNpcId.TryGetValue(num, out EnemyPuppetRecord value)) { return false; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (Plugin.Cfg.EnableHostAuthorizedIntentExecution.Value && _clientAuthorizedIntentByNpcId.TryGetValue(num, out ClientEnemyAuthorizedIntentWindow value2) && realtimeSinceStartup <= value2.ExpiresAt) { bool num2 = IsMeleeHitCombatSource(source); bool flag = IsRootCombatSource(source); bool flag2 = IsChildCombatSource(source); if (num2) { _clientAuthorizedMeleeEvents++; detail = $"authorized-melee idx={value.Snapshot.SpawnIndex} actor={value.Snapshot.ActorName} seq={value2.Sequence}"; if (Plugin.Cfg.LogHostAuthorizedIntentExecution.Value) { MaybeLogEnemyCombatProbe(npc, source, "authorized-melee", detail, force: false); } return false; } if (flag) { if (_clientAuthorizedCombatRootReplayDepth > 0) { _clientAuthorizedAttackPasses++; detail = $"authorized-root idx={value.Snapshot.SpawnIndex} actor={value.Snapshot.ActorName} source={Clean(source)} seq={value2.Sequence}"; if (Plugin.Cfg.LogHostAuthorizedIntentExecution.Value) { MaybeLogEnemyCombatProbe(npc, source, "authorized-root", detail, force: false); } return false; } _clientBlockedSpontaneousCombatCalls++; _clientEnemyPuppetCombatBlocks++; detail = $"blocked-spontaneous idx={value.Snapshot.SpawnIndex} actor={value.Snapshot.ActorName} source={Clean(source)} seq={value2.Sequence}"; if (Plugin.Cfg.LogHostAuthorizedIntentExecution.Value) { MaybeLogEnemyCombatProbe(npc, source, "blocked-spontaneous", detail, force: false); } return true; } if (flag2) { bool flag3 = source.IndexOf("DoneAttacking", StringComparison.OrdinalIgnoreCase) >= 0 || source.IndexOf("DoneShooting", StringComparison.OrdinalIgnoreCase) >= 0; if (value2.RootReplayed || flag3) { _clientAuthorizedChildAfterRoot++; _clientAuthorizedChildPasses++; detail = $"authorized-child-after-root idx={value.Snapshot.SpawnIndex} actor={value.Snapshot.ActorName} source={Clean(source)} seq={value2.Sequence} rootReplayed={value2.RootReplayed}"; if (Plugin.Cfg.LogHostAuthorizedIntentExecution.Value) { MaybeLogEnemyCombatProbe(npc, source, "authorized-child-after-root", detail, force: false); } return false; } _clientChildBlockedBeforeRootReplay++; _clientEnemyPuppetCombatBlocks++; detail = $"blocked-child-before-root idx={value.Snapshot.SpawnIndex} actor={value.Snapshot.ActorName} source={Clean(source)} seq={value2.Sequence}"; if (Plugin.Cfg.LogHostAuthorizedIntentExecution.Value) { MaybeLogEnemyCombatProbe(npc, source, "blocked-child-before-root", detail, force: false); } return true; } } if ((_clientPuppetCombatVisualReplayDepth > 0 || _clientAuthorizedCombatRootReplayDepth > 0) && !IsMeleeHitCombatSource(source)) { return false; } _clientUnauthorizedAttackBlocks++; _clientEnemyPuppetCombatBlocks++; ApplyClientEnemyTargetAuthority(value, realtimeSinceStartup, "blocked combat " + Clean(source)); detail = $"idx={value.Snapshot.SpawnIndex} actor={value.Snapshot.ActorName} source={Clean(source)}"; if (!IsLowValueCombatProbeSource(source) || Plugin.Cfg.EnableDebugLog.Value) { MaybeLogEnemyCombatProbe(npc, source, "blocked-client-puppet", detail, force: false); } return true; } public static bool IsClientEnemyPuppetNpc(object? npc) { if (npc == null) { return false; } int num = ObjectIdentity(npc); if (num != 0) { return ActiveEnemyPuppetsByNpcId.ContainsKey(num); } return false; } public static bool TryGetClientPuppetAimOverride(object? npc, out Vector3 aim) { //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_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) aim = Vector3.zero; if (npc == null) { return false; } if (!Plugin.Cfg.EnableClientPuppetAimOverride.Value) { return false; } if (!IsClientEnemyPuppetModeEnabled()) { return false; } int num = ObjectIdentity(npc); if (num == 0) { return false; } if (!ActiveEnemyPuppetsByNpcId.ContainsKey(num)) { return false; } if (!_clientAuthorizedIntentByNpcId.TryGetValue(num, out ClientEnemyAuthorizedIntentWindow value) || value == null) { return false; } if (Time.realtimeSinceStartup > value.ExpiresAt) { return false; } if (!value.HasAimPosition || !IsFinite(value.AimPosition)) { return false; } aim = value.AimPosition; return true; } public static bool IsInClientEnemyNativeDamageSuppression() { return _clientEnemyNativeDamageSuppressDepth > 0; } public static void EnterClientEnemyNativeDamageSuppression() { _clientEnemyNativeDamageSuppressDepth++; } public static void ExitClientEnemyNativeDamageSuppression() { if (_clientEnemyNativeDamageSuppressDepth > 0) { _clientEnemyNativeDamageSuppressDepth--; } } public static void CountSuppressedNativeEnemyDamage() { _clientSuppressedNativeEnemyDamage++; } private static bool TryCreateOrRefreshClientIntentWindow(int npcId, NetGameplayEnemyStateSnapshot hostSnapshot, float now) { //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_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) //IL_00df: 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_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: 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_017e: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: 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_0230: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_0271: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.Cfg.EnableHostAuthorizedIntentExecution.Value) { return false; } if (hostSnapshot == null) { return false; } if (!hostSnapshot.HasEnemyIntent && (!hostSnapshot.HasHostCombatAction || hostSnapshot.HostCombatActionKind == 0)) { return false; } float num = Plugin.Cfg.HostAuthorizedIntentWindowSeconds.Value; if (num < 0.1f) { num = 0.1f; } if (num > 5f) { num = 5f; } int num2; int weaponActionState; bool flag; Vector3 val; Vector3 originPosition; if (hostSnapshot.HasEnemyIntent) { num2 = hostSnapshot.EnemyIntentKind; weaponActionState = hostSnapshot.EnemyIntentWeaponActionState; flag = hostSnapshot.EnemyIntentHasAimPosition; val = hostSnapshot.EnemyIntentAimPosition; originPosition = (hostSnapshot.EnemyIntentHasOriginPosition ? hostSnapshot.EnemyIntentOriginPosition : Vector3.zero); } else { num2 = HostCombatActionKindToEnemyIntentKind(hostSnapshot.HostCombatActionKind); weaponActionState = hostSnapshot.HostCombatActionState; flag = hostSnapshot.HasHostCombatAim; val = hostSnapshot.HostCombatAimPosition; originPosition = (hostSnapshot.HasHostCombatAim ? hostSnapshot.HostCombatOriginPosition : Vector3.zero); } int num3 = (hostSnapshot.HasHostCombatAction ? hostSnapshot.HostCombatActionSequence : hostSnapshot.Sequence); ClientEnemyAuthorizedIntentWindow value; bool flag2 = !_clientAuthorizedIntentByNpcId.TryGetValue(npcId, out value) || value.Sequence != num3; if (!_clientAuthorizedIntentByNpcId.TryGetValue(npcId, out ClientEnemyAuthorizedIntentWindow value2)) { value2 = new ClientEnemyAuthorizedIntentWindow { NpcId = npcId }; _clientAuthorizedIntentByNpcId[npcId] = value2; } value2.Sequence = num3; value2.Kind = num2; if (flag2) { value2.ExpiresAt = now + num; } value2.WeaponActionState = weaponActionState; value2.HasAimPosition = flag; value2.AimPosition = val; value2.HasOriginPosition = flag; value2.OriginPosition = originPosition; if (flag2) { value2.RootReplayed = false; } if (flag2) { _clientIntentWindows++; if (Plugin.Cfg.LogHostAuthorizedIntentExecution.Value && (num2 == 4 || num2 == 5 || num2 == 6)) { string text = num2 switch { 5 => "AttackRanged", 4 => "AttackMelee", _ => "WeaponAction", }; Vector3 val2 = Vector3.zero; EnemyPuppetRecord value3; bool flag3 = ActiveEnemyPuppetsByNpcId.TryGetValue(npcId, out value3) && value3.Snapshot != null && value3.Snapshot.HasPosition; if (flag3) { val2 = value3.Snapshot.Position; } Plugin.Log.Info(string.Format("[EnemyIntent] Client AuthCombat npcId={0} seq={1} kind={2} controlMode=AuthorizedCombat setDestination=false reportLastSeen=false hostPos={3} aimPos={4}", npcId, num3, text, flag3 ? $"({val2.x:F2},{val2.y:F2},{val2.z:F2})" : "?", flag ? $"({val.x:F2},{val.y:F2},{val.z:F2})" : "?")); } } return flag2; } private static void TryExecuteClientEnemyIntentRootReplay(EnemyPuppetRecord record, ClientEnemyAuthorizedIntentWindow window, object runtimeNpc, NetGameplayEnemyStateSnapshot? hostSnapshot) { _clientRootReplayAttempts++; if (record == null || window == null || runtimeNpc == null) { _clientRootReplayFailed++; if (Plugin.Cfg.LogHostAuthorizedIntentExecution.Value) { Plugin.Log.Info("[EnemyIntent] Client root-replay skipped npcId=0 seq=? reason=no npc/window/runtime"); } return; } if (Time.realtimeSinceStartup > window.ExpiresAt) { _clientRootReplayFailed++; if (Plugin.Cfg.LogHostAuthorizedIntentExecution.Value) { Plugin.Log.Info($"[EnemyIntent] Client root-replay skipped npcId={record.NpcId} seq={window.Sequence} reason=stale window"); } return; } if (window.RootReplayed) { _clientRootReplaySkippedDuplicate++; return; } int kind = window.Kind; bool num = kind == 5; bool flag = kind == 4; bool flag2 = kind == 6; if (!num && !flag && !flag2) { _clientRootReplayUnsupported++; if (Plugin.Cfg.LogHostAuthorizedIntentExecution.Value) { Plugin.Log.Info($"[EnemyIntent] Client root-replay skipped npcId={record.NpcId} seq={window.Sequence} reason=unsupported kind={kind}"); } return; } bool flag3 = false; string text = "?"; try { _clientAuthorizedCombatRootReplayDepth++; _clientPuppetCombatVisualReplayDepth++; switch ((hostSnapshot != null && hostSnapshot.HasHostCombatAction) ? hostSnapshot.HostCombatActionKind : 0) { case 4: text = "TriggerWeaponManually"; flag3 = TryInvokeInstanceMethod(runtimeNpc, text, window.WeaponActionState); break; case 2: text = "TriggerShoot"; flag3 = TryInvokeInstanceMethod(runtimeNpc, text); break; case 1: text = "TriggerAttackAnimation"; flag3 = TryInvokeInstanceMethod(runtimeNpc, text); break; case 3: text = "TriggerShoot"; flag3 = TryInvokeInstanceMethod(runtimeNpc, text); if (!flag3) { text = "SetShooting"; flag3 = TryInvokeInstanceMethod(runtimeNpc, text, true); } break; default: if (flag2 || flag) { text = "TriggerWeaponManually"; flag3 = TryInvokeInstanceMethod(runtimeNpc, text, window.WeaponActionState); if (!flag3) { text = "TriggerAttackAnimation"; flag3 = TryInvokeInstanceMethod(runtimeNpc, text); } break; } text = "SetRangedAttacking"; flag3 = TryInvokeInstanceMethod(runtimeNpc, text, true); if (!flag3) { text = "SetShooting"; flag3 = TryInvokeInstanceMethod(runtimeNpc, text, true); } if (!flag3) { text = "TriggerShoot"; flag3 = TryInvokeInstanceMethod(runtimeNpc, text); } break; } } catch (Exception ex) { flag3 = false; text = "?exception:" + ex.Message; } finally { if (_clientAuthorizedCombatRootReplayDepth > 0) { _clientAuthorizedCombatRootReplayDepth--; } if (_clientPuppetCombatVisualReplayDepth > 0) { _clientPuppetCombatVisualReplayDepth--; } } if (!flag3) { _clientRootReplayFailed++; if (Plugin.Cfg.LogHostAuthorizedIntentExecution.Value) { Plugin.Log.Info($"[EnemyIntent] Client root-replay skipped npcId={record.NpcId} seq={window.Sequence} kind={kind} method={text} reason=missing method"); } return; } window.RootReplayed = true; _clientCombatRootReplays++; if (Plugin.Cfg.LogHostAuthorizedIntentExecution.Value) { Plugin.Log.Info(string.Format("[EnemyIntent] Client root-replay npcId={0} seq={1} kind={2} method={3} actor={4}", record.NpcId, window.Sequence, kind, text, record.Snapshot?.ActorName ?? "?")); } } private static void PruneExpiredAuthorizedIntentWindows(float now) { if (_clientAuthorizedIntentByNpcId.Count == 0) { return; } int[] array = _clientAuthorizedIntentByNpcId.Keys.ToArray(); foreach (int key in array) { if (_clientAuthorizedIntentByNpcId.TryGetValue(key, out ClientEnemyAuthorizedIntentWindow value) && now > value.ExpiresAt) { _clientAuthorizedIntentByNpcId.Remove(key); } } } public static void ReportProjectileProbe(object? sourceObject, string source, string stage) { if (!IsEnabled()) { return; } try { string text = NetConfig.GetMode().ToString(); string key = $"{text}:{Clean(source)}:{Clean(stage)}:{ObjectIdentity(sourceObject)}"; float realtimeSinceStartup = Time.realtimeSinceStartup; float num = (Plugin.Cfg.EnableDebugLog.Value ? 0.15f : 1f); if (!ProjectileProbeLastLogAtByKey.TryGetValue(key, out var value) || !(realtimeSinceStartup - value < num)) { ProjectileProbeLastLogAtByKey[key] = realtimeSinceStartup; _projectileProbeEvents++; NetLogger.Info("[ProjectileProbe] role=" + text + " source=" + Clean(source) + " stage=" + Clean(stage) + " obj=" + ((sourceObject == null) ? "" : DescribeTarget(sourceObject))); } } catch { } } public static void ReportEnemyAiIntent(object? sourceObject, string source, object[]? args) { //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_011c: 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_018f: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) if (!IsEnabled() || sourceObject == null) { return; } try { if (NetConfig.GetMode() != NetMode.Host || !Plugin.Cfg.EnableClientEnemyIntentDrivenMotion.Value) { return; } string detail; object obj = ResolveMirroredEnemyObject(sourceObject, out detail); if (obj == null) { return; } Object val = (Object)((obj is Object) ? obj : null); if (val != null && val == (Object)null) { return; } NetGameplayEntitySnapshot netGameplayEntitySnapshot = FindSnapshotForRuntimeObject(obj); if (netGameplayEntitySnapshot == null || netGameplayEntitySnapshot.IsDead || !string.Equals(netGameplayEntitySnapshot.Category, "Npc", StringComparison.OrdinalIgnoreCase)) { return; } Vector3 destination; bool flag = TryExtractIntentDestination(args, out destination); if (!flag) { flag = TryResolveCombatTargetPosition(obj, out destination); } if (!flag || !IsFinite(destination)) { return; } int num = ObjectIdentity(obj); if (num != 0) { float realtimeSinceStartup = Time.realtimeSinceStartup; HostEnemyAiIntent hostEnemyAiIntent = new HostEnemyAiIntent { ExpiresAt = realtimeSinceStartup + 1.5f, Source = Clean(source), Sequence = ++_hostEnemyAiIntentMarks, Kind = 1, HasDestination = true, Destination = destination, HasLookAt = true, LookAt = destination }; HostEnemyAiIntentsByNpcId[num] = hostEnemyAiIntent; if (Plugin.Cfg.LogEnemyAiIntentMirror.Value && Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug($"[EnemyAiIntent] Host captured idx={netGameplayEntitySnapshot.SpawnIndex} actor={netGameplayEntitySnapshot.ActorName} source={Clean(source)} dest=({destination.x:F2},{destination.y:F2},{destination.z:F2}) seq={hostEnemyAiIntent.Sequence}"); } } } catch { } } private static bool TryExtractIntentDestination(object[]? args, out Vector3 destination) { //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_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_0031: 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_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_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0097: 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_007e: 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_00a3: Unknown result type (might be due to invalid IL or missing references) destination = Vector3.zero; if (args == null) { return false; } try { foreach (object obj in args) { if (obj != null) { if (obj is Vector3 val && IsFinite(val)) { destination = val; return true; } if (TryGetPosition(obj, out var position) && IsFinite(position)) { destination = position; return true; } if (TryGetVector3Member(obj, "position", out var value) && IsFinite(value)) { destination = value; return true; } if (TryGetVector3Member(obj, "Position", out value) && IsFinite(value)) { destination = value; return true; } } } } catch { } return false; } public static void ReportEnemyCombatProbe(object? npc, string source, string detail = "") { MarkHostEnemyCombatActionIfUseful(npc, source, detail); if (IsEnemyCombatProbeEnabled() && (!IsLowValueCombatProbeSource(source) || Plugin.Cfg.EnableDebugLog.Value)) { MaybeLogEnemyCombatProbe(npc, source, "observe", detail, force: false); } } private static bool IsLowValueCombatProbeSource(string source) { return IsMeleeHitCombatSource(source); } private static bool IsMeleeHitCombatSource(string source) { if (string.IsNullOrWhiteSpace(source)) { return false; } return source.IndexOf("HandleMeleeHit", StringComparison.OrdinalIgnoreCase) >= 0; } private static bool IsHostCombatActionSource(string source) { if (string.IsNullOrWhiteSpace(source)) { return false; } if (source.IndexOf("TriggerAttackAnimation", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("TriggerShoot", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("SetShooting", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("TriggerWeaponManually", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("TriggerShootFromAnimation", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("StartMeleeDamageState", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("EndMeleeDamageState", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("SetRangedAttacking", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("SetAttacking", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("DoneAttacking", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("DoneShooting", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } return false; } private static bool IsAuthorizedCombatSource(string source) { return IsHostCombatActionSource(source); } private static bool IsRootCombatSource(string source) { if (string.IsNullOrWhiteSpace(source)) { return false; } if (source.IndexOf("TriggerWeaponManually", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("TriggerShoot", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("SetShooting", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("TriggerAttackAnimation", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } return false; } private static bool IsChildCombatSource(string source) { if (string.IsNullOrWhiteSpace(source)) { return false; } if (source.IndexOf("StartMeleeDamageState", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("EndMeleeDamageState", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("TriggerShootFromAnimation", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("DoneAttacking", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("DoneShooting", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("SetRangedAttacking", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (source.IndexOf("SetAttacking", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } return false; } private static bool HasActiveAuthorizedCombatIntentWindow(int npcId, float now) { if (!_clientAuthorizedIntentByNpcId.TryGetValue(npcId, out ClientEnemyAuthorizedIntentWindow value)) { return false; } if (now > value.ExpiresAt) { return false; } if (value.Kind != 4 && value.Kind != 5) { return value.Kind == 6; } return true; } private static void MarkHostEnemyCombatActionIfUseful(object? npc, string source, string detail) { //IL_010e: 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) if (npc == null || !IsHostCombatActionSource(source)) { return; } try { if (NetConfig.GetMode() != NetMode.Host || !Plugin.Cfg.EnableHostEnemyAnimationMirror.Value) { return; } int num = ObjectIdentity(npc); if (num != 0) { float realtimeSinceStartup = Time.realtimeSinceStartup; float num2 = Plugin.Cfg.EnemyAnimationMirrorHostCombatActionHoldSeconds.Value; if (num2 < 0.1f) { num2 = 0.1f; } if (num2 > 2f) { num2 = 2f; } int hostCombatActionKind = GetHostCombatActionKind(source); int state = ((hostCombatActionKind == 4) ? ParseStateDetail(detail, 0) : 0); if (hostCombatActionKind != 3 || !HostEnemyCombatActionsByNpcId.TryGetValue(num, out HostEnemyCombatAction value) || value.Kind != 4 || !(realtimeSinceStartup < value.ExpiresAt)) { Vector3 origin; Vector3 aim; bool hasAim = TryBuildHostCombatAim(npc, out origin, out aim); HostEnemyCombatAction hostEnemyCombatAction = new HostEnemyCombatAction { ExpiresAt = realtimeSinceStartup + num2, Source = Clean(source), Sequence = ++_hostEnemyCombatActionSequence, Kind = hostCombatActionKind, State = state, HasAim = hasAim, OriginPosition = origin, AimPosition = aim }; HostEnemyCombatActionsByNpcId[num] = hostEnemyCombatAction; _hostEnemyCombatActionMarks++; TryApplyHostAuthoritativeEnemyDamage(npc, hostEnemyCombatAction, realtimeSinceStartup); TryBroadcastHostAttackPhaseEvent(npc, hostEnemyCombatAction); } } } catch { } } private static void TryBroadcastHostAttackPhaseEvent(object npc, HostEnemyCombatAction action) { //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) try { if (!Plugin.Cfg.EnableHostAttackPhaseEvents.Value || !Plugin.Cfg.EnableHostDrivenEnemyProxy.Value) { return; } NetGameplayEntitySnapshot netGameplayEntitySnapshot = FindSnapshotForRuntimeObject(npc); if (netGameplayEntitySnapshot == null || netGameplayEntitySnapshot.SyncCategory != 1) { return; } NetRunState state; if (Plugin.Cfg.EnableCombatEventCoalescing.Value && ThrottlePerEntity(_attackPhaseLastSentBySpawnIdx, netGameplayEntitySnapshot.SpawnIndex, Plugin.Cfg.AttackPhaseEventMinIntervalSeconds.Value, Time.realtimeSinceStartup)) { _attackPhaseThrottled++; } else if (NetRunStateBridge.TryGetLocalRunState(out state) && state.HasLevel) { int num = CombatActionKindToAttackPhase(action.Kind); int num2 = CombatActionKindToAttackKind(action.Kind); NetHostAttackPhaseEvent netHostAttackPhaseEvent = new NetHostAttackPhaseEvent { ChapterName = state.ChapterName, LevelIndex = state.LevelIndex, HasLevelSeed = state.HasLevelSeed, LevelSeed = state.LevelSeed, HostSpawnIndex = netGameplayEntitySnapshot.SpawnIndex, UnitIdentifier = (netGameplayEntitySnapshot.EntityId.UnitIdentifier ?? ""), AttackPhase = num, AttackKind = num2, ActionKind = action.Kind, ActionState = action.State, Sequence = action.Sequence, HasAimData = action.HasAim, OriginPosition = action.OriginPosition, AimPosition = action.AimPosition, SentAt = Time.realtimeSinceStartup }; if (netGameplayEntitySnapshot.TryGetRuntimeObject(out object runtimeObject) && runtimeObject != null) { TryPopulateAttackPhaseAnimatorHint(runtimeObject, netHostAttackPhaseEvent); } NetGameplaySyncBridge.ReportHostAttackPhaseEvent(netHostAttackPhaseEvent); _hostAttackPhaseEventsSent++; if (Plugin.Cfg.LogHostAttackPhaseEvents.Value) { Plugin.Log.Info($"[AttackPhase] Host broadcast idx={netGameplayEntitySnapshot.SpawnIndex} actor={netGameplayEntitySnapshot.ActorName} phase={num} kind={num2} actionKind={action.Kind} seq={action.Sequence} hasAnim={netHostAttackPhaseEvent.HasAnimatorHint}"); } } } catch { } } private static int CombatActionKindToAttackPhase(int kind) { switch (kind) { case 1: case 2: case 3: case 4: case 5: case 8: case 9: return 1; case 6: return 2; case 7: return 3; case 10: case 11: return 0; default: return 0; } } private static int CombatActionKindToAttackKind(int kind) { switch (kind) { case 1: case 6: case 7: case 9: case 10: return 1; case 2: case 3: case 5: case 8: case 11: return 2; case 4: return 3; default: return 0; } } private static void TryPopulateAttackPhaseAnimatorHint(object runtimeObj, NetHostAttackPhaseEvent evt) { //IL_0045: 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) try { Animator val = null; Component val2 = (Component)((runtimeObj is Component) ? runtimeObj : null); if (val2 != null) { val = val2.GetComponentInChildren(); } else { GameObject val3 = (GameObject)((runtimeObj is GameObject) ? runtimeObj : null); if (val3 != null) { val = val3.GetComponentInChildren(); } } if (!((Object)(object)val == (Object)null) && ((Behaviour)val).isActiveAndEnabled && val.layerCount != 0) { AnimatorStateInfo currentAnimatorStateInfo = val.GetCurrentAnimatorStateInfo(0); if (((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash != 0) { evt.HasAnimatorHint = true; evt.AnimatorFullPathHash = ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash; evt.AnimatorNormalizedTime = ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).normalizedTime; } } } catch { } } private static int GetHostCombatActionKind(string source) { if (string.IsNullOrWhiteSpace(source)) { return 0; } if (source.IndexOf("TriggerWeaponManually", StringComparison.OrdinalIgnoreCase) >= 0) { return 4; } if (source.IndexOf("TriggerShootFromAnimation", StringComparison.OrdinalIgnoreCase) >= 0) { return 5; } if (source.IndexOf("TriggerShoot", StringComparison.OrdinalIgnoreCase) >= 0) { return 2; } if (source.IndexOf("SetShooting", StringComparison.OrdinalIgnoreCase) >= 0) { return 3; } if (source.IndexOf("TriggerAttackAnimation", StringComparison.OrdinalIgnoreCase) >= 0) { return 1; } if (source.IndexOf("StartMeleeDamageState", StringComparison.OrdinalIgnoreCase) >= 0) { return 6; } if (source.IndexOf("EndMeleeDamageState", StringComparison.OrdinalIgnoreCase) >= 0) { return 7; } if (source.IndexOf("SetRangedAttacking", StringComparison.OrdinalIgnoreCase) >= 0) { return 8; } if (source.IndexOf("SetAttacking", StringComparison.OrdinalIgnoreCase) >= 0) { return 9; } if (source.IndexOf("DoneAttacking", StringComparison.OrdinalIgnoreCase) >= 0) { return 10; } if (source.IndexOf("DoneShooting", StringComparison.OrdinalIgnoreCase) >= 0) { return 11; } return 0; } private static int HostCombatActionKindToEnemyIntentKind(int combatActionKind) { switch (combatActionKind) { case 1: case 4: case 6: case 7: case 9: case 10: return 4; case 2: case 3: case 5: case 8: case 11: return 5; default: return 0; } } private static int ParseStateDetail(string detail, int fallback) { if (string.IsNullOrWhiteSpace(detail)) { return fallback; } try { int num = detail.IndexOf("state=", StringComparison.OrdinalIgnoreCase); if (num < 0) { return fallback; } num += "state=".Length; int i; for (i = num; i < detail.Length && (char.IsDigit(detail[i]) || detail[i] == '-' || detail[i] == '+'); i++) { } if (i <= num) { return fallback; } if (int.TryParse(detail.Substring(num, i - num), out var result)) { return result; } } catch { } return fallback; } private static bool TryBuildHostCombatAim(object npc, out Vector3 origin, out Vector3 aim) { //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_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_009b: 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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) origin = Vector3.zero; aim = Vector3.zero; try { if (!TryResolveHostCombatBarrelOrigin(npc, out origin) || !IsFinite(origin)) { if (!TryExtractWorldPosition(npc, out origin)) { return false; } origin += Vector3.up * 0.85f; } if ((!TryInvokeVector3Method(npc, "GetAimPosition", out aim) || !IsFinite(aim)) && !TryResolveCombatTargetPosition(npc, out aim)) { if (!TryGetTransform(npc, out Transform transform) || !((Object)(object)transform != (Object)null)) { return false; } aim = origin + transform.forward * 8f; } if (!IsFinite(origin) || !IsFinite(aim)) { return false; } Vector3 val = aim - origin; if (((Vector3)(ref val)).sqrMagnitude < 0.05f) { return false; } return true; } catch { origin = Vector3.zero; aim = Vector3.zero; return false; } } private static bool TryResolveHostCombatBarrelOrigin(object npc, out Vector3 origin) { //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_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_0052: Unknown result type (might be due to invalid IL or missing references) origin = Vector3.zero; try { object obj = TryFindLikelyWeaponObject(npc); if (obj == null) { return false; } object? obj2 = TryGetMemberValue(obj, "BarrelTransform") ?? TryGetMemberValue(obj, "barrelTransform"); Transform val = (Transform)((obj2 is Transform) ? obj2 : null); if (val != null && (Object)(object)val != (Object)null) { origin = val.position; return IsFinite(origin); } } catch { } return false; } private static void TryPopulateHostTargetIdentity(object npc, NetGameplayEnemyStateSnapshot snapshot) { //IL_0014: 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) try { if (npc != null && snapshot != null && TryResolveCombatTargetPosition(npc, out var position) && IsFinite(position)) { snapshot.HasHostTarget = true; snapshot.HostTargetKind = 1; snapshot.HostTargetPosition = position; } } catch { } } private static bool TryResolveCombatTargetPosition(object npc, out Vector3 position) { //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_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) position = Vector3.zero; try { if (TryResolveCombatTargetPositionFromObject(npc, out position)) { return true; } object obj = TryGetMemberValue(npc, "AiAgent") ?? TryGetMemberValue(npc, "aiAgent"); if (TryResolveCombatTargetPositionFromObject(obj, out position)) { return true; } if (TryResolveCombatTargetPositionFromObject((obj == null) ? null : TryGetMemberValue(obj, "ai"), out position)) { return true; } if (TryResolveCombatTargetPositionFromObject(TryFindLikelyWeaponObject(npc), out position)) { return true; } object obj2 = TryGetMemberValue(npc, "behaviourTree"); if (TryResolveCombatTargetPositionFromObject(obj2, out position)) { return true; } if (TryResolveCombatTargetPositionFromObject(TryGetMemberValue(npc, "blackboard") ?? TryGetMemberValue(npc, "Blackboard") ?? TryGetMemberValue(obj, "blackboard") ?? TryGetMemberValue(obj, "Blackboard") ?? TryGetMemberValue(obj2, "blackboard") ?? TryGetMemberValue(obj2, "Blackboard"), out position)) { return true; } } catch { } position = Vector3.zero; return false; } private static bool TryResolveCombatTargetPositionFromObject(object? container, out Vector3 position) { //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_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) position = Vector3.zero; if (container == null) { return false; } try { string[] targetAuthorityMemberNames = TargetAuthorityMemberNames; foreach (string memberName in targetAuthorityMemberNames) { object obj = TryGetMemberValue(container, memberName); if (obj != null && TryExtractWorldPosition(obj, out position)) { return true; } } string[] array = new string[16] { "aimTarget", "AimTarget", "attackTarget", "AttackTarget", "targetUnit", "TargetUnit", "targetTransform", "TargetTransform", "lastTarget", "LastTarget", "targetPosition", "TargetPosition", "targetPos", "TargetPos", "attackingPosition", "AttackingPosition" }; for (int j = 0; j < array.Length; j++) { object obj2 = TryGetMemberValue(container, array[j]); if (obj2 != null && TryExtractWorldPosition(obj2, out position)) { return true; } } } catch { } position = Vector3.zero; return false; } private static bool TryExtractWorldPosition(object? value, out Vector3 position) { //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_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_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_0020: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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_0095: 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_00c5: 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_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: 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_0144: 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) position = Vector3.zero; if (value == null) { return false; } try { if (value is Vector3 val) { if (!IsFinite(val)) { return false; } position = val; return true; } Transform val2 = (Transform)((value is Transform) ? value : null); if (val2 != null && (Object)(object)val2 != (Object)null) { position = val2.position; return IsFinite(position); } Component val3 = (Component)((value is Component) ? value : null); if (val3 != null && (Object)(object)val3 != (Object)null) { position = val3.transform.position; return IsFinite(position); } GameObject val4 = (GameObject)((value is GameObject) ? value : null); if (val4 != null && (Object)(object)val4 != (Object)null) { position = val4.transform.position; return IsFinite(position); } if (TryGetTransform(value, out Transform transform) && (Object)(object)transform != (Object)null) { position = transform.position; return IsFinite(position); } if ((TryGetMemberValue(value, "position") ?? TryGetMemberValue(value, "Position")) is Vector3 val5 && IsFinite(val5)) { position = val5; return true; } } catch { } position = Vector3.zero; return false; } private static bool TryGetActiveHostEnemyCombatAction(object? npc, float now, out HostEnemyCombatAction action) { action = null; if (npc == null) { return false; } int num = ObjectIdentity(npc); if (num == 0) { return false; } if (!HostEnemyCombatActionsByNpcId.TryGetValue(num, out action)) { return false; } if (now <= action.ExpiresAt) { return true; } HostEnemyCombatActionsByNpcId.Remove(num); action = null; return false; } private static void PruneExpiredHostEnemyCombatActions() { if (HostEnemyCombatActionsByNpcId.Count == 0) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; List list = new List(); foreach (KeyValuePair item in HostEnemyCombatActionsByNpcId) { if (realtimeSinceStartup > item.Value.ExpiresAt) { list.Add(item.Key); } } for (int i = 0; i < list.Count; i++) { HostEnemyCombatActionsByNpcId.Remove(list[i]); } } private static bool TryGetActiveHostEnemyAiIntent(object? npc, float now, out HostEnemyAiIntent intent) { intent = null; if (npc == null) { return false; } int num = ObjectIdentity(npc); if (num == 0) { return false; } if (!HostEnemyAiIntentsByNpcId.TryGetValue(num, out intent)) { return false; } if (now <= intent.ExpiresAt) { return true; } HostEnemyAiIntentsByNpcId.Remove(num); intent = null; return false; } private static void PruneExpiredHostEnemyAiIntents() { if (HostEnemyAiIntentsByNpcId.Count == 0) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; List list = new List(); foreach (KeyValuePair item in HostEnemyAiIntentsByNpcId) { if (realtimeSinceStartup > item.Value.ExpiresAt) { list.Add(item.Key); } } for (int i = 0; i < list.Count; i++) { HostEnemyAiIntentsByNpcId.Remove(list[i]); } } private static bool IsClientEnemyIntentDrivenMotionEnabled() { try { return Plugin.Cfg.EnableClientEnemyIntentDrivenMotion.Value && IsClientEnemyPuppetModeEnabled(); } catch { return false; } } private static bool IsHostOnlyEnemyTargetAuthorityEnabled() { try { return Plugin.Cfg.EnableHostOnlyEnemyTargetAuthority.Value && IsClientEnemyPuppetModeEnabled(); } catch { return false; } } private static bool IsEnemyCombatProbeEnabled() { try { return Plugin.Cfg.EnableEnemyCombatProbe.Value; } catch { return false; } } private static bool TryGetPuppetRecordForAiAgent(object? aiAgent, out EnemyPuppetRecord record) { record = null; if (aiAgent == null) { return false; } int num = ObjectIdentity(aiAgent); if (num == 0) { return false; } foreach (EnemyPuppetRecord value in ActiveEnemyPuppets.Values) { if (value.AiAgentId == num || value.AiAgent == aiAgent) { record = value; return true; } } return false; } private static void ApplyClientEnemyTargetAuthority(EnemyPuppetRecord record, float now, string reason) { //IL_0371: Unknown result type (might be due to invalid IL or missing references) //IL_0385: Unknown result type (might be due to invalid IL or missing references) //IL_0399: Unknown result type (might be due to invalid IL or missing references) if (!IsHostOnlyEnemyTargetAuthorityEnabled() || record == null || (record.Npc == null && record.AiAgent == null && record.MovementDriver == null) || (record.LastTargetAuthorityApplyAt > 0f && now - record.LastTargetAuthorityApplyAt < 0.5f)) { return; } bool flag = LooksLikeBoss(record.Snapshot?.ActorName) || LooksLikeBoss(record.Snapshot?.EntityId.TypeName); int num = 0; List list = new List(4); _clientPuppetInternalTargetClearDepth++; try { num += TryClearKnownTargetMembers(record.Npc, "npc", list); num += TryClearKnownTargetMembers(record.AiAgent, "aiAgent", list); num += TryClearKnownTargetMembers(record.MovementDriver, "movement", list); object obj = TryGetMemberValue(record.Npc, "behaviourTree"); num += TryClearKnownTargetMembers(obj, "behaviourTree", list); object target = TryGetMemberValue(record.Npc, "blackboard") ?? TryGetMemberValue(record.Npc, "Blackboard") ?? TryGetMemberValue(record.AiAgent, "blackboard") ?? TryGetMemberValue(record.AiAgent, "Blackboard") ?? TryGetMemberValue(obj, "blackboard") ?? TryGetMemberValue(obj, "Blackboard"); num += TryClearKnownTargetMembers(target, "blackboard", list); if ((flag || num == 0) && !record.TargetAuthoritySectionScanDisabled) { num += TryClearBossSectionTargets(record, list); } } finally { if (_clientPuppetInternalTargetClearDepth > 0) { _clientPuppetInternalTargetClearDepth--; } } record.LastTargetAuthorityApplyAt = now; if (num > 0) { _clientEnemyPuppetTargetClears += num; _enemyTargetLocalCleared += num; } if (flag) { if (num > 0) { _enemyTargetBossSuppressionApplied++; } else { _enemyTargetSuppressionFailed++; } } if (num == 0) { _enemyTargetNoKnownMember++; if (!record.TargetAuthoritySectionScanDisabled && ++record.TargetAuthorityBarrenStreak >= 6) { record.TargetAuthoritySectionScanDisabled = true; if (record.AiAgentId != 0) { BarrenTargetAuthorityAiIds.Add(record.AiAgentId); } MaybeLogClientEnemyTargetAuthority(record, now, "section-scan-disabled", $"boss={flag} reason=barren-6x (no clearable target members; skipping per-frame child scan + block-path apply/log)", force: true); } } else { record.TargetAuthorityBarrenStreak = 0; } MaybeDumpEnemyTargetMembers(record, num == 0, flag); string text = ((list.Count == 0) ? "no-known-target-members" : string.Join(",", list.Take(8).ToArray())); record.LastTargetAuthorityClearedCount = num; record.LastTargetAuthoritySummary = text; if (!record.TargetAuthoritySectionScanDisabled) { MaybeLogClientEnemyTargetAuthority(record, now, "apply-host-only-target", $"reason={Clean(reason)} cleared={num} boss={flag} members={text}", force: false); if (num == 0) { MaybeLogClientEnemyTargetAuthority(record, now, "suppression-failed", $"reason=no-target-member boss={flag}", force: false); } } NetGameplayEnemyStateSnapshot lastHostSnapshot = record.LastHostSnapshot; if (lastHostSnapshot != null && lastHostSnapshot.HasHostTarget) { _enemyTargetHostTargetApplied++; string text2 = ((lastHostSnapshot.HostTargetKind == 1) ? "HostPlayer" : ((lastHostSnapshot.HostTargetKind == 2) ? "RemotePlayer" : ((lastHostSnapshot.HostTargetKind == 0) ? "None" : "Unknown"))); MaybeLogClientEnemyTargetAuthority(record, now, "client applied host target", $"targetKind={text2} targetPos=({lastHostSnapshot.HostTargetPosition.x:F1},{lastHostSnapshot.HostTargetPosition.y:F1},{lastHostSnapshot.HostTargetPosition.z:F1})", force: false); } } private static int TryClearBossSectionTargets(EnemyPuppetRecord record, List clearedOn) { GameObject val = AsGameObject(record.Npc) ?? AsGameObject(record.AiAgent) ?? AsGameObject(record.MovementDriver); if ((Object)(object)val == (Object)null) { return 0; } int num = 0; int num2 = 0; try { Component[] componentsInChildren = val.GetComponentsInChildren(true); foreach (Component val2 in componentsInChildren) { if ((Object)(object)val2 == (Object)null) { continue; } string name = ((object)val2).GetType().Name; if (LooksLikeAiOrTargetHolder(name)) { if (++num2 > 48) { break; } num += TryClearKnownTargetMembers(val2, "section." + name, clearedOn); } } } catch { } return num; } private static bool LooksLikeAiOrTargetHolder(string typeName) { if (string.IsNullOrEmpty(typeName)) { return false; } string text = typeName.ToLowerInvariant(); if (!text.Contains("ai") && !text.Contains("brain") && !text.Contains("section") && !text.Contains("worm") && !text.Contains("combat") && !text.Contains("enemy") && !text.Contains("boss") && !text.Contains("target") && !text.Contains("aggro") && !text.Contains("attack")) { return text.Contains("npc"); } return true; } private static bool LooksLikeBoss(string? name) { string text = (name ?? string.Empty).ToLowerInvariant(); if (text.Length == 0) { return false; } if (!text.Contains("boss") && !text.Contains("emperor") && !text.Contains("worm") && !text.Contains("shavwa") && !text.Contains("section") && !text.Contains("colossus") && !text.Contains("giant")) { return text.Contains("titan"); } return true; } private static GameObject? AsGameObject(object? obj) { try { GameObject val = (GameObject)((obj is GameObject) ? obj : null); if (val != null) { return ((Object)(object)val != (Object)null) ? val : null; } Component val2 = (Component)((obj is Component) ? obj : null); if (val2 != null) { return ((Object)(object)val2 != (Object)null) ? val2.gameObject : null; } } catch { } return null; } private static void MaybeDumpEnemyTargetMembers(EnemyPuppetRecord record, bool noKnownMembers, bool boss) { if (!Plugin.Cfg.LogEnemyTargetAuthority.Value || (!boss && !noKnownMembers)) { return; } object obj = record.Npc ?? record.AiAgent ?? record.MovementDriver; if (obj == null) { return; } string text = obj.GetType().FullName ?? obj.GetType().Name; if (!_targetProbeDumpedTypes.Add(text)) { return; } _enemyTargetBossMembersDiscovered++; string text2 = record.Snapshot?.ActorName ?? ""; NetLogger.Info($"[EnemyTargetAuthorityProbe] type={text} actor={text2} boss={boss} noKnownMembers={noKnownMembers}"); DumpCandidateTargetMembers(obj, "primary"); GameObject val = AsGameObject(obj); if (!((Object)(object)val != (Object)null)) { return; } try { HashSet hashSet = new HashSet(); int num = 0; Component[] componentsInChildren = val.GetComponentsInChildren(true); foreach (Component val2 in componentsInChildren) { if ((Object)(object)val2 == (Object)null) { continue; } string name = ((object)val2).GetType().Name; if (LooksLikeAiOrTargetHolder(name) && hashSet.Add(((object)val2).GetType().FullName ?? name)) { if (++num > 16) { break; } NetLogger.Info("[EnemyTargetAuthorityProbe] boss section root=" + ((Object)val).name + " component=" + ((object)val2).GetType().FullName + " obj=" + ((Object)val2.gameObject).name); DumpCandidateTargetMembers(val2, "section." + name); } } } catch { } } private static void DumpCandidateTargetMembers(object obj, string label) { try { Type type = obj.GetType(); List list = new List(); Type type2 = type; while (type2 != null && type2 != typeof(object) && type2 != typeof(Object) && list.Count < 24) { FieldInfo[] fields = type2.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (LooksLikeTargetMemberName(fieldInfo.Name) && list.Count < 24) { list.Add("field " + fieldInfo.Name + ":" + fieldInfo.FieldType.Name); } } PropertyInfo[] properties = type2.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { if (propertyInfo.GetIndexParameters().Length == 0 && LooksLikeTargetMemberName(propertyInfo.Name) && list.Count < 24) { list.Add("prop " + propertyInfo.Name + ":" + propertyInfo.PropertyType.Name); } } type2 = type2.BaseType; } NetLogger.Info("[EnemyTargetAuthorityProbe] " + label + " type=" + type.Name + " candidateTargetMembers=" + ((list.Count == 0) ? "none" : string.Join(", ", list))); } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { Plugin.Log.Debug("[EnemyTargetAuthorityProbe] dump failed: " + ex.Message); } } } private static bool LooksLikeTargetMemberName(string name) { if (string.IsNullOrEmpty(name)) { return false; } string text = name.ToLowerInvariant(); if (!text.Contains("target") && !text.Contains("aim") && !text.Contains("focus") && !text.Contains("aggro") && !text.Contains("player") && !text.Contains("brain") && !text.Contains("blackboard") && !text.Contains("threat") && !text.Contains("victim") && !text.Contains("prey") && !text.Contains("enemy")) { return text.Contains("hostile"); } return true; } private static int TryClearKnownTargetMembers(object? target, string label, List clearedOn) { if (target == null) { return 0; } try { Object val = (Object)((target is Object) ? target : null); if (val != null && val == (Object)null) { return 0; } } catch { return 0; } int num = 0; string[] targetAuthorityMemberNames = TargetAuthorityMemberNames; foreach (string text in targetAuthorityMemberNames) { if (TryGetMemberValue(target, text) != null && TrySetObjectMember(target, text, null)) { num++; clearedOn.Add(label + "." + text); } } return num; } private static void MaybeLogClientEnemyTargetAuthority(EnemyPuppetRecord record, float now, string action, string detail, bool force) { if (Plugin.Cfg.LogEnemyTargetAuthority.Value || force) { float targetAuthorityProbeInterval = GetTargetAuthorityProbeInterval(); if (force || !(record.LastTargetAuthorityLogAt > 0f) || !(now - record.LastTargetAuthorityLogAt < targetAuthorityProbeInterval)) { record.LastTargetAuthorityLogAt = now; NetLogger.Info($"[EnemyTargetAuthority] mode=HostOnly action={Clean(action)} idx={record.Snapshot.SpawnIndex} actor={record.Snapshot.ActorName} {Clean(detail)}"); } } } private static void MaybeLogEnemyCombatProbe(object? npc, string source, string action, string detail, bool force) { if (!Plugin.Cfg.LogEnemyCombatProbe.Value && !force) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; int num = ObjectIdentity(npc); if (num == 0) { num = Clean(source).GetHashCode(); } float targetAuthorityProbeInterval = GetTargetAuthorityProbeInterval(); if (!force && EnemyCombatProbeLastLogAtById.TryGetValue(num, out var value) && realtimeSinceStartup - value < targetAuthorityProbeInterval) { return; } EnemyCombatProbeLastLogAtById[num] = realtimeSinceStartup; _enemyCombatProbeEvents++; string text = SafeNetModeText(); string text2 = ""; int num2 = 0; if (npc != null) { NetGameplayEntitySnapshot netGameplayEntitySnapshot = FindSnapshotForRuntimeObject(npc); if (netGameplayEntitySnapshot != null) { text2 = netGameplayEntitySnapshot.ActorName; num2 = netGameplayEntitySnapshot.SpawnIndex; } else { text2 = Clean(GetActorName(npc)); } } NetLogger.Info($"[EnemyCombatProbe] role={text} action={Clean(action)} source={Clean(source)} idx={num2} actor={text2} {Clean(detail)}"); } private static float GetTargetAuthorityProbeInterval() { try { float num = Plugin.Cfg.EnemyTargetAuthorityProbeIntervalSeconds.Value; if (num < 0.25f) { num = 0.25f; } if (num > 30f) { num = 30f; } return num; } catch { return 2f; } } private static string DescribeTarget(object? target) { if (target == null) { return "null"; } try { Object val = (Object)((target is Object) ? target : null); if (val != null && val == (Object)null) { return "destroyed"; } string name = target.GetType().Name; Object val2 = (Object)((target is Object) ? target : null); string arg = ((val2 != null && !string.IsNullOrWhiteSpace(val2.name)) ? Clean(val2.name) : Clean(TryGetMemberValue(target, "name")?.ToString() ?? TryGetMemberValue(target, "Name")?.ToString())); return $"{name}/{arg}/id={ObjectIdentity(target)}"; } catch { return ""; } } private static string SafeNetModeText() { try { return NetConfig.GetMode().ToString(); } catch { return "Unknown"; } } private static bool IsClientEnemyPuppetModeEnabled() { try { return Plugin.Cfg.EnableClientEnemyPuppetMode.Value && Plugin.Cfg.EnableHostEnemyStateSnapshotMirror.Value && Plugin.Cfg.ApplyReceivedEnemyStateSnapshots.Value && NetConfig.GetMode() == NetMode.Client; } catch { return false; } } private static bool IsSelfAnimatingClientBossAdd(NetGameplayEntitySnapshot? snapshot) { if (snapshot == null) { return false; } try { return string.Equals(snapshot.EntityId?.UnitIdentifier, "GoblinCousinArm", StringComparison.Ordinal); } catch { return false; } } private static bool IsEmperorWormSectionSnapshot(NetGameplayEntitySnapshot? snapshot) { if (snapshot == null) { return false; } try { return (snapshot.EntityId?.UnitIdentifier ?? "").IndexOf("EmperorWormSection", StringComparison.OrdinalIgnoreCase) >= 0; } catch { return false; } } private static bool IsDesertBossSnapshot(NetGameplayEntitySnapshot? snapshot) { if (snapshot == null) { return false; } try { return (snapshot.EntityId?.UnitIdentifier ?? "").IndexOf("DesertClause", StringComparison.OrdinalIgnoreCase) >= 0; } catch { return false; } } internal static string DescribeEnemyFireMirrorState(object npc) { try { int num = ObjectIdentity(npc); if (num == 0) { return "id0"; } if (NetConfig.GetMode() == NetMode.Host) { if (HostEnemyCombatActionsByNpcId.TryGetValue(num, out HostEnemyCombatAction value)) { return $"mark[kind={value.Kind} state={value.State} seq={value.Sequence} expIn={value.ExpiresAt - Time.realtimeSinceStartup:F2}]"; } return "mark[none]"; } string text = "hostSnap[none]"; if (ActiveEnemyPuppetsByNpcId.TryGetValue(num, out EnemyPuppetRecord value2) && value2.LastHostSnapshot != null) { NetGameplayEnemyStateSnapshot lastHostSnapshot = value2.LastHostSnapshot; text = $"hostSnap[combat={lastHostSnapshot.HasHostCombatAction}:{lastHostSnapshot.HostCombatActionKind}/{lastHostSnapshot.HostCombatActionState} intent={lastHostSnapshot.HasEnemyIntent}:{lastHostSnapshot.EnemyIntentKind} seq={lastHostSnapshot.HostCombatActionSequence}]"; } else if (!ActiveEnemyPuppetsByNpcId.ContainsKey(num)) { text = "hostSnap[no-puppet-record]"; } ClientEnemyAuthorizedIntentWindow value3; string text2 = (_clientAuthorizedIntentByNpcId.TryGetValue(num, out value3) ? $"window[kind={value3.Kind} seq={value3.Sequence} expIn={value3.ExpiresAt - Time.realtimeSinceStartup:F2} rootReplayed={value3.RootReplayed}]" : "window[none]"); return text + " " + text2; } catch (Exception ex) { return "err:" + ex.GetType().Name + ":" + ex.Message; } } private static bool IsDesertBossPikeSnapshot(NetGameplayEntitySnapshot? snapshot) { if (snapshot == null) { return false; } try { return (snapshot.EntityId?.UnitIdentifier ?? "").IndexOf("DesertClausePike", StringComparison.OrdinalIgnoreCase) >= 0; } catch { return false; } } private static bool IsDesertEnemyPikeSnapshot(NetGameplayEntitySnapshot? snapshot) { if (snapshot == null) { return false; } try { return (snapshot.EntityId?.UnitIdentifier ?? "").StartsWith("HellshrewPike", StringComparison.OrdinalIgnoreCase); } catch { return false; } } private static void EnsureClientEnemyPuppetMode(string key, NetGameplayEntitySnapshot snapshot, NetGameplayEnemyStateSnapshot hostSnapshot, object runtimeObject, float now) { if (!IsClientEnemyPuppetModeEnabled() || snapshot == null || snapshot.IsDead || !string.Equals(snapshot.Category, "Npc", StringComparison.OrdinalIgnoreCase) || IsNonCombatForSync(snapshot) || string.IsNullOrWhiteSpace(key)) { return; } string detail; object obj = ResolveMirroredEnemyObject(runtimeObject, out detail); if (obj == null) { return; } Object val = (Object)((obj is Object) ? obj : null); if (val == null || !(val == (Object)null)) { if (!ActiveEnemyPuppets.TryGetValue(key, out EnemyPuppetRecord value)) { value = new EnemyPuppetRecord { Key = key, Snapshot = snapshot, Npc = obj, NpcId = ObjectIdentity(obj), LastSeenAt = now }; ActiveEnemyPuppets[key] = value; } value.Snapshot = snapshot; value.LastHostSnapshot = hostSnapshot; value.Npc = obj; int num = ObjectIdentity(obj); if (value.NpcId != 0 && value.NpcId != num) { ActiveEnemyPuppetsByNpcId.Remove(value.NpcId); } value.NpcId = num; if (value.NpcId != 0) { ActiveEnemyPuppetsByNpcId[value.NpcId] = value; } value.LastSeenAt = now; if (!value.Applied || !(now - value.LastAppliedAt < 0.5f)) { ApplyClientEnemyPuppetMode(value, now); } } } private static void ApplyClientEnemyPuppetMode(EnemyPuppetRecord record, float now) { object npc = record.Npc; if (npc == null) { return; } Object val = (Object)((npc is Object) ? npc : null); if (val != null && val == (Object)null) { return; } object obj = TryGetMemberValue(npc, "AiAgent") ?? TryGetMemberValue(npc, "aiAgent"); object obj2 = ((obj == null) ? null : TryGetMemberValue(obj, "ai")); if (obj2 == null) { obj2 = TryFindComponentByTypeName(npc, "CustomRichAI"); } record.AiAgent = obj; record.MovementDriver = obj2; record.AiAgentId = ((obj != null) ? ObjectIdentity(obj) : 0); record.MovementDriverId = ((obj2 != null) ? ObjectIdentity(obj2) : 0); ClientPuppetNpcIds.Add(record.NpcId); if (record.AiAgentId != 0) { ClientPuppetAiAgentIds.Add(record.AiAgentId); } if (record.MovementDriverId != 0) { ClientPuppetMovementDriverIds.Add(record.MovementDriverId); } if (!record.OriginalDisableVerifyPosition.HasValue && TryGetBoolMember(npc, "disableVerifyPosition", out var value)) { record.OriginalDisableVerifyPosition = value; } if (!record.OriginalPreventNavMeshActivation.HasValue && TryGetBoolMember(npc, "preventNavMeshActivation", out var value2)) { record.OriginalPreventNavMeshActivation = value2; } MakePuppetRigidbodyKinematic(record, npc); bool flag = IsClientEnemyIntentDrivenMotionEnabled(); if (flag) { _clientPuppetIntentReplayDepth++; try { TryInvokeInstanceMethod(obj, "SetCanMove", true); TryInvokeInstanceMethod(obj, "SetNavMeshAgentState", true); TryInvokeInstanceMethod(obj, "ToggleRVO", true); } finally { if (_clientPuppetIntentReplayDepth > 0) { _clientPuppetIntentReplayDepth--; } } TryInvokeInstanceMethod(npc, "ToggleBehaviourTree", false); TrySetBoolMember(npc, "disableVerifyPosition", value: true); TrySetBoolMember(npc, "preventNavMeshActivation", value: false); TrySetBoolMember(npc, "NavMeshEnabledTarget", value: true); TryDisableBehaviourComponent(TryGetMemberValue(npc, "behaviourTree")); } else { TryInvokeInstanceMethod(obj, "StopOnCurrentPosition"); TryInvokeInstanceMethod(obj, "SetCanMove", false); TryInvokeInstanceMethod(obj, "SetNavMeshAgentState", false); TryInvokeInstanceMethod(obj, "ToggleRVO", false); TryInvokeInstanceMethod(npc, "ToggleBehaviourTree", false); TrySetBoolMember(npc, "disableVerifyPosition", value: true); TrySetBoolMember(npc, "preventNavMeshActivation", value: true); TrySetBoolMember(npc, "NavMeshEnabledTarget", value: false); TryDisableBehaviourComponent(TryGetMemberValue(npc, "behaviourTree")); TryDisableNavMeshObject(TryGetMemberValue(npc, "navMeshAgent")); TryDisableRvoController(TryGetMemberValue(obj, "rvoController")); TryZeroRigidbodyVelocity(TryGetMemberValue(npc, "Rigidbody") ?? TryGetMemberValue(npc, "rigidbody")); } bool flag2 = false; string[] clientPuppetExtraLocomotionControllers = ClientPuppetExtraLocomotionControllers; foreach (string typeName in clientPuppetExtraLocomotionControllers) { object obj3 = TryFindComponentByTypeName(npc, typeName); if (obj3 != null) { TryDisableBehaviourComponent(obj3); flag2 = true; } } if (flag2) { DisableFlyerPuppetPhysicsCollider(npc, record); } ApplyClientEnemyTargetAuthority(record, now, flag ? "intent puppet apply" : "puppet apply"); bool num = !record.Applied; record.Applied = true; record.LastAppliedAt = now; if (num) { _clientEnemyPuppetsActivated++; if (Plugin.Cfg.LogClientEnemyPuppetMode.Value) { NetLogger.Info(string.Format("[EnemyPuppet] Activated Client puppet mode idx={0} actor={1} aiAgent={2} movement={3}", record.Snapshot.SpawnIndex, record.Snapshot.ActorName, (obj == null) ? "none" : obj.GetType().Name, (obj2 == null) ? "none" : obj2.GetType().Name)); } } } private static void TryApplyClientEnemyAiIntent(EnemyPuppetRecord record, object runtimeObject, NetGameplayEnemyStateSnapshot hostSnapshot, float now) { //IL_0317: 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_0389: Unknown result type (might be due to invalid IL or missing references) //IL_0397: Unknown result type (might be due to invalid IL or missing references) //IL_03a5: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_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_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0271: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) if (record == null || runtimeObject == null || hostSnapshot == null || !IsClientEnemyIntentDrivenMotionEnabled() || !hostSnapshot.HasAiIntent || !IsFinite(hostSnapshot.AiIntentDestination)) { return; } Vector3 aiIntentDestination = hostSnapshot.AiIntentDestination; float num = Plugin.Cfg.EnemyIntentReplayMinIntervalSeconds.Value; if (num < 0.02f) { num = 0.02f; } if (num > 2f) { num = 2f; } if ((record.LastAppliedAiIntentSequence == hostSnapshot.AiIntentSequence && Vector3.Distance(record.LastAppliedMoveTargetPosition, aiIntentDestination) < 0.75f) || now - record.LastAppliedAiIntentAt < num) { return; } string detail; object obj = record.Npc ?? ResolveMirroredEnemyObject(runtimeObject, out detail); object obj2 = record.AiAgent ?? ((obj == null) ? null : (TryGetMemberValue(obj, "AiAgent") ?? TryGetMemberValue(obj, "aiAgent"))); object obj3 = record.MovementDriver ?? ((obj2 == null) ? null : TryGetMemberValue(obj2, "ai")); bool flag = false; try { _clientPuppetIntentReplayDepth++; if (obj2 != null) { TryInvokeInstanceMethod(obj2, "SetCanMove", true); TryInvokeInstanceMethod(obj2, "SetNavMeshAgentState", true); TryInvokeInstanceMethod(obj2, "ToggleRVO", true); flag |= TryInvokeInstanceMethod(obj2, "SetDestination", aiIntentDestination); flag |= TryInvokeInstanceMethod(obj2, "SetDestination", aiIntentDestination, false); flag |= TryInvokeInstanceMethod(obj2, "SetDestination", aiIntentDestination, true); flag |= TrySetVector3Member(obj2, "destination", aiIntentDestination); flag |= TrySetVector3Member(obj2, "Destination", aiIntentDestination); flag |= TrySetVector3Member(obj2, "targetPosition", aiIntentDestination); flag |= TrySetVector3Member(obj2, "TargetPosition", aiIntentDestination); } if (obj3 != null) { flag |= TryInvokeInstanceMethod(obj3, "SetDestination", aiIntentDestination); flag |= TryInvokeInstanceMethod(obj3, "SetDestination", aiIntentDestination, false); flag |= TrySetVector3Member(obj3, "destination", aiIntentDestination); flag |= TrySetVector3Member(obj3, "targetPosition", aiIntentDestination); } if (obj != null) { flag |= TryInvokeInstanceMethod(obj, "SetForcedDestination", aiIntentDestination); flag |= TryInvokeInstanceMethod(obj, "SetDestination", aiIntentDestination); TrySetBoolMember(obj, "NavMeshEnabledTarget", value: true); TrySetBoolMember(obj, "preventNavMeshActivation", value: false); } } catch { } finally { if (_clientPuppetIntentReplayDepth > 0) { _clientPuppetIntentReplayDepth--; } } if (hostSnapshot.HasAiIntentLookAt) { TryFaceHostAiIntent(runtimeObject, hostSnapshot); } if (flag) { record.LastAppliedAiIntentSequence = hostSnapshot.AiIntentSequence; record.LastAppliedAiIntentAt = now; record.LastAppliedMoveTargetPosition = aiIntentDestination; _clientEnemyAiIntentApplies++; if (Plugin.Cfg.LogEnemyAiIntentMirror.Value && (Plugin.Cfg.EnableDebugLog.Value || _clientEnemyAiIntentApplies <= 12)) { NetLogger.Info($"[EnemyAiIntent] Client replayed Host intent idx={hostSnapshot.SpawnIndex} actor={hostSnapshot.ActorName} seq={hostSnapshot.AiIntentSequence} dest=({aiIntentDestination.x:F2},{aiIntentDestination.y:F2},{aiIntentDestination.z:F2}) applied={flag}"); } } } private static void TryFaceHostAiIntent(object runtimeObject, NetGameplayEnemyStateSnapshot hostSnapshot) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) if (runtimeObject == null || hostSnapshot == null || !hostSnapshot.HasAiIntentLookAt || !TryGetTransform(runtimeObject, out Transform transform) || (Object)(object)transform == (Object)null) { return; } Vector3 val = hostSnapshot.AiIntentLookAt - transform.position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.001f) { return; } try { transform.rotation = Quaternion.LookRotation(((Vector3)(ref val)).normalized, Vector3.up); } catch { } } private static void TryApplyHostAuthoritativeEnemyDamage(object npc, HostEnemyCombatAction action, float now) { //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) //IL_0319: 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_0367: Unknown result type (might be due to invalid IL or missing references) //IL_0376: Unknown result type (might be due to invalid IL or missing references) //IL_0385: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_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_009b: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) if (npc == null || action == null) { return; } try { if (!Plugin.Cfg.EnableSyntheticRangedDamageFallback.Value || !Plugin.Cfg.EnableHostAuthoritativeEnemyRangedDamage.Value || NetConfig.GetMode() != NetMode.Host || (action.Kind != 2 && action.Kind != 3) || !action.HasAim || !IsFinite(action.OriginPosition) || !IsFinite(action.AimPosition)) { return; } Vector3 originPosition = action.OriginPosition; Vector3 val = action.AimPosition; Vector3 val2 = val - originPosition; float num = Plugin.Cfg.EnemyHostProjectileMaxDistance.Value; if (num < 2f) { num = 2f; } if (num > 80f) { num = 80f; } if (((Vector3)(ref val2)).sqrMagnitude < 0.05f) { return; } if (((Vector3)(ref val2)).magnitude > num) { val = originPosition + ((Vector3)(ref val2)).normalized * num; } float num2 = Plugin.Cfg.EnemyHostProjectileHitRadius.Value; if (num2 < 0.1f) { num2 = 0.1f; } if (num2 > 2.5f) { num2 = 2.5f; } float num3 = Plugin.Cfg.EnemyHostProjectileVerticalTolerance.Value; if (num3 < 0.1f) { num3 = 0.1f; } if (num3 > 4f) { num3 = 4f; } float value = Plugin.Cfg.EnemyHostProjectileDamage.Value; if (value <= 0f) { return; } float num4 = Plugin.Cfg.EnemyHostProjectileDamageCooldownSeconds.Value; if (num4 < 0.05f) { num4 = 0.05f; } if (num4 > 5f) { num4 = 5f; } IReadOnlyList hostKnownAliveRemotePeerPositions = NetPlayerLifeManager.GetHostKnownAliveRemotePeerPositions(); if (hostKnownAliveRemotePeerPositions == null || hostKnownAliveRemotePeerPositions.Count == 0) { return; } int damageTypeInt = 0; try { object obj = TryGetMemberValue(npc, "damageTypeOverride"); if (obj != null) { int num5 = Convert.ToInt32(obj); if (num5 != 0) { damageTypeInt = num5; } } } catch { } for (int i = 0; i < hostKnownAliveRemotePeerPositions.Count; i++) { NetPlayerLifeManager.PeerCombatPosition peerCombatPosition = hostKnownAliveRemotePeerPositions[i]; if (peerCombatPosition == null || string.IsNullOrWhiteSpace(peerCombatPosition.PeerId)) { continue; } _hostEnemyDamageChecks++; Vector3 point = peerCombatPosition.Position + Vector3.up * 0.85f; if (!TrySegmentCapsuleHit(originPosition, val, point, num2, num3, out var closest, out var horizontalDistance)) { continue; } string key = $"{ObjectIdentity(npc)}:{peerCombatPosition.PeerId}"; if (!HostEnemyDamageLastAtByKey.TryGetValue(key, out var value2) || !(now - value2 < num4)) { HostEnemyDamageLastAtByKey[key] = now; _hostEnemyDamageHits++; string text = Clean(GetActorName(npc)); string reason = $"enemy={text};kind={action.Kind};seq={action.Sequence};dist={horizontalDistance:F2}"; NetPlayerLifeManager.ReportHostAuthoritativeEnemyDamage(peerCombatPosition.PeerId, value, reason, closest, damageTypeInt); if (Plugin.Cfg.LogEnemyHostDamageAuthority.Value) { NetLogger.Info($"[EnemyDamageAuthority] Host projectile hit peer={peerCombatPosition.PeerId} actor={text} seq={action.Sequence} damage={value:F1} closest=({closest.x:F2},{closest.y:F2},{closest.z:F2}) dist={horizontalDistance:F2}"); } } } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[EnemyDamageAuthority] Host damage check failed: " + ex.Message); } } } private static bool TrySegmentCapsuleHit(Vector3 start, Vector3 end, Vector3 point, float radius, float verticalTolerance, out Vector3 closest, out float horizontalDistance) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: 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_004b: 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_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_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_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_008c: 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_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: 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_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) closest = start; horizontalDistance = float.MaxValue; try { Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(start.x, 0f, start.z); Vector3 val2 = new Vector3(end.x, 0f, end.z); Vector3 val3 = default(Vector3); ((Vector3)(ref val3))..ctor(point.x, 0f, point.z); Vector3 val4 = val2 - val; float sqrMagnitude = ((Vector3)(ref val4)).sqrMagnitude; if (sqrMagnitude < 0.0001f) { return false; } float num = Mathf.Clamp01(Vector3.Dot(val3 - val, val4) / sqrMagnitude); Vector3 val5 = val + val4 * num; horizontalDistance = Vector3.Distance(val3, val5); if (horizontalDistance > radius) { return false; } closest = Vector3.Lerp(start, end, num); if (Mathf.Abs(point.y - closest.y) > verticalTolerance) { return false; } return true; } catch { return false; } } private static void TryPopulateHostCombatAnimatorStates(object runtimeObject, NetGameplayEnemyStateSnapshot stateSnapshot) { //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) if (runtimeObject == null || stateSnapshot == null || !Plugin.Cfg.EnableGenericHostCombatAnimatorStateMirror.Value) { return; } try { if (!TryGetTransform(runtimeObject, out Transform transform) || (Object)(object)transform == (Object)null) { return; } Component val = (Component)((runtimeObject is Component) ? runtimeObject : null); Animator[] array; if (val != null && (Object)(object)val != (Object)null) { array = val.GetComponentsInChildren(true); } else { GameObject val2 = (GameObject)((runtimeObject is GameObject) ? runtimeObject : null); array = ((val2 == null || !((Object)(object)val2 != (Object)null)) ? ((Component)transform).GetComponentsInChildren(true) : val2.GetComponentsInChildren(true)); } int num = 0; for (int i = 0; i < array.Length; i++) { if (num >= 4) { break; } Animator val3 = array[i]; if ((Object)(object)val3 == (Object)null) { continue; } Object val4 = (Object)(object)val3; if ((val4 == null || !(val4 == (Object)null)) && val3.layerCount > 0) { int num2 = 0; AnimatorStateInfo currentAnimatorStateInfo = val3.GetCurrentAnimatorStateInfo(num2); if (((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash != 0 || ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).shortNameHash != 0) { int relativeAnimatorPathHash = GetRelativeAnimatorPathHash(transform, ((Component)val3).transform); stateSnapshot.HostCombatAnimatorPathHashes[num] = relativeAnimatorPathHash; stateSnapshot.HostCombatAnimatorLayers[num] = num2; stateSnapshot.HostCombatAnimatorFullPathHashes[num] = ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash; stateSnapshot.HostCombatAnimatorNormalizedTimes[num] = (IsFinite(((AnimatorStateInfo)(ref currentAnimatorStateInfo)).normalizedTime) ? ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).normalizedTime : 0f); stateSnapshot.HostCombatAnimatorSpeeds[num] = (IsFinite(val3.speed) ? val3.speed : 1f); num++; } } } stateSnapshot.HostCombatAnimatorStateCount = num; } catch { } } private static int GetRelativeAnimatorPathHash(Transform root, Transform target) { try { if ((Object)(object)root == (Object)null || (Object)(object)target == (Object)null) { return 0; } if (root == target) { return Animator.StringToHash(""); } List list = new List(8); Transform val = target; while ((Object)(object)val != (Object)null && val != root) { list.Add(Clean(((Object)val).name)); val = val.parent; } list.Reverse(); return Animator.StringToHash((list.Count == 0) ? "" : string.Join("/", list.ToArray())); } catch { return 0; } } private static void TryApplyGenericHostCombatAnimatorStates(EnemyPuppetRecord record, object runtimeObject, NetGameplayEnemyStateSnapshot hostSnapshot, int actionSequence, float now) { //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) if (record == null || runtimeObject == null || hostSnapshot == null || !Plugin.Cfg.EnableGenericHostCombatAnimatorStateMirror.Value || hostSnapshot.HostCombatAnimatorStateCount <= 0) { return; } if (record.NpcId != 0 && HasActiveAuthorizedCombatIntentWindow(record.NpcId, now)) { _genericCombatStateSkippedDuringAuthorizedIntent++; return; } int num = ComputeGenericCombatAnimatorSignature(hostSnapshot, actionSequence); if ((num == record.LastAppliedGenericCombatAnimatorSignature && now - record.LastAppliedGenericCombatAnimatorAt < 0.1f) || now - record.LastAppliedGenericCombatAnimatorAt < 0.055f) { return; } int num2 = 0; try { if (!TryGetTransform(runtimeObject, out Transform transform) || (Object)(object)transform == (Object)null) { return; } for (int i = 0; i < hostSnapshot.HostCombatAnimatorStateCount && i < 4; i++) { int num3 = hostSnapshot.HostCombatAnimatorFullPathHashes[i]; if (num3 == 0) { continue; } int pathHash = hostSnapshot.HostCombatAnimatorPathHashes[i]; if (!TryFindAnimatorByRelativePathHash(transform, pathHash, out Animator animator) || (Object)(object)animator == (Object)null) { continue; } int num4 = hostSnapshot.HostCombatAnimatorLayers[i]; if (num4 < 0 || num4 >= animator.layerCount) { num4 = 0; } float num5 = Fraction01(hostSnapshot.HostCombatAnimatorNormalizedTimes[i]); float value = Plugin.Cfg.EnemyAnimationMirrorCrossFadeSeconds.Value; try { AnimatorStateInfo currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo(num4); bool flag = ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash != num3; float b = Fraction01(((AnimatorStateInfo)(ref currentAnimatorStateInfo)).normalizedTime); float num6 = FractionDistance(num5, b); if (flag || !(num6 < 0.12f) || num != record.LastAppliedGenericCombatAnimatorSignature) { if (value <= 0f || !flag) { animator.Play(num3, num4, num5); } else { animator.CrossFade(num3, Mathf.Clamp(value, 0f, 0.5f), num4, num5); } float num7 = hostSnapshot.HostCombatAnimatorSpeeds[i]; if (IsFinite(num7) && num7 >= 0f && num7 <= 5f) { animator.speed = ((num7 < 0.01f) ? 1f : num7); } num2++; } } catch { } } } catch { } if (num2 > 0) { record.LastAppliedGenericCombatAnimatorSignature = num; record.LastAppliedGenericCombatAnimatorAt = now; _clientGenericCombatAnimatorStateApplies += num2; if (Plugin.Cfg.LogEnemyAnimationMirror.Value) { NetLogger.Info($"[EnemyCombatAnim] Client applied continuous Host combat Animator states idx={hostSnapshot.SpawnIndex} actor={hostSnapshot.ActorName} seq={actionSequence} states={hostSnapshot.HostCombatAnimatorStateCount} applied={num2} sig={num}"); } } } private static int ComputeGenericCombatAnimatorSignature(NetGameplayEnemyStateSnapshot hostSnapshot, int actionSequence) { int num = 17; num = num * 31 + actionSequence; int num2 = hostSnapshot.HostCombatAnimatorStateCount; if (num2 > 4) { num2 = 4; } for (int i = 0; i < num2; i++) { num = num * 31 + hostSnapshot.HostCombatAnimatorPathHashes[i]; num = num * 31 + hostSnapshot.HostCombatAnimatorLayers[i]; num = num * 31 + hostSnapshot.HostCombatAnimatorFullPathHashes[i]; num = num * 31 + Mathf.RoundToInt(Fraction01(hostSnapshot.HostCombatAnimatorNormalizedTimes[i]) * 20f); } return num; } private static bool TryFindAnimatorByRelativePathHash(Transform root, int pathHash, out Animator? animator) { animator = null; try { if ((Object)(object)root == (Object)null) { return false; } Animator[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); foreach (Animator val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { Object val2 = (Object)(object)val; if ((val2 == null || !(val2 == (Object)null)) && GetRelativeAnimatorPathHash(root, ((Component)val).transform) == pathHash) { animator = val; return true; } } } } catch { } return false; } private static void TryPopulateEnemyAiIntentSnapshot(object runtimeObject, NetGameplayEnemyStateSnapshot stateSnapshot) { //IL_0036: 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_0079: 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) if (runtimeObject == null || stateSnapshot == null) { return; } try { if (Plugin.Cfg.EnableClientEnemyIntentDrivenMotion.Value && TryGetActiveHostEnemyAiIntent(runtimeObject, Time.realtimeSinceStartup, out HostEnemyAiIntent intent) && intent.HasDestination && IsFinite(intent.Destination)) { stateSnapshot.HasAiIntent = true; stateSnapshot.AiIntentSequence = intent.Sequence; stateSnapshot.AiIntentKind = intent.Kind; stateSnapshot.AiIntentDestination = intent.Destination; stateSnapshot.HasAiIntentLookAt = intent.HasLookAt && IsFinite(intent.LookAt); stateSnapshot.AiIntentLookAt = intent.LookAt; } } catch { } } private static void TryPopulateEnemyAnimatorSnapshot(object runtimeObject, NetGameplayEnemyStateSnapshot stateSnapshot) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0170: 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_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) if (runtimeObject == null || stateSnapshot == null) { return; } try { if (!TryGetAnimator(runtimeObject, out Animator animator) || (Object)(object)animator == (Object)null || animator.layerCount <= 0) { return; } int num = 0; AnimatorStateInfo currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo(num); if (((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash == 0 && ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).shortNameHash == 0) { return; } stateSnapshot.HasAnimatorState = true; stateSnapshot.AnimatorLayer = num; stateSnapshot.AnimatorFullPathHash = ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash; stateSnapshot.AnimatorShortNameHash = ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).shortNameHash; stateSnapshot.AnimatorNormalizedTime = (IsFinite(((AnimatorStateInfo)(ref currentAnimatorStateInfo)).normalizedTime) ? ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).normalizedTime : 0f); stateSnapshot.AnimatorSpeed = (IsFinite(animator.speed) ? animator.speed : 1f); if (TryGetAnimatorBool(animator, "Moving", out var value)) { stateSnapshot.HasAnimatorMovingBool = true; stateSnapshot.AnimatorMovingBool = value; } if (TryGetAnimatorBool(animator, "Attack", out var value2)) { stateSnapshot.HasAnimatorAttackBool = true; stateSnapshot.AnimatorAttackBool = value2; } if (TryGetAnimatorBool(animator, "Cowering", out var value3)) { stateSnapshot.HasAnimatorCoweringBool = true; stateSnapshot.AnimatorCoweringBool = value3; } if (TryGetActiveHostEnemyCombatAction(runtimeObject, Time.realtimeSinceStartup, out HostEnemyCombatAction action)) { stateSnapshot.HasAnimatorAttackBool = true; stateSnapshot.AnimatorAttackBool = true; stateSnapshot.HasHostCombatAction = true; stateSnapshot.HostCombatActionKind = action.Kind; stateSnapshot.HostCombatActionState = action.State; stateSnapshot.HostCombatActionSequence = action.Sequence; stateSnapshot.HasHostCombatAim = action.HasAim; stateSnapshot.HostCombatOriginPosition = action.OriginPosition; stateSnapshot.HostCombatAimPosition = action.AimPosition; TryPopulateHostCombatAnimatorStates(runtimeObject, stateSnapshot); stateSnapshot.HasEnemyIntent = true; stateSnapshot.EnemyIntentKind = HostCombatActionKindToEnemyIntentKind(action.Kind); stateSnapshot.EnemyIntentSequence = action.Sequence; stateSnapshot.EnemyIntentWeaponActionState = action.State; stateSnapshot.EnemyIntentDuration = Plugin.Cfg.EnemyAnimationMirrorHostCombatActionHoldSeconds.Value; stateSnapshot.EnemyIntentHasAimPosition = action.HasAim; stateSnapshot.EnemyIntentAimPosition = action.AimPosition; stateSnapshot.EnemyIntentHasOriginPosition = action.HasAim; stateSnapshot.EnemyIntentOriginPosition = action.OriginPosition; if (Plugin.Cfg.LogEnemyAnimationMirror.Value && Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug($"[EnemyCombatAnim] Host marked combat action idx={stateSnapshot.SpawnIndex} actor={stateSnapshot.ActorName} kind={action.Kind} state={action.State} source={action.Source} seq={action.Sequence}"); } } if (Plugin.Cfg.LogEnemyAnimationMirror.Value && Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug($"[EnemyAnimMirror] Host snapshot idx={stateSnapshot.SpawnIndex} actor={stateSnapshot.ActorName} state={stateSnapshot.AnimatorFullPathHash} short={stateSnapshot.AnimatorShortNameHash} t={Fraction01(stateSnapshot.AnimatorNormalizedTime):F2} moving={BoolText(stateSnapshot.HasAnimatorMovingBool, stateSnapshot.AnimatorMovingBool)} attack={BoolText(stateSnapshot.HasAnimatorAttackBool, stateSnapshot.AnimatorAttackBool)} cowering={BoolText(stateSnapshot.HasAnimatorCoweringBool, stateSnapshot.AnimatorCoweringBool)} combat={stateSnapshot.HasHostCombatAction}:{stateSnapshot.HostCombatActionKind}/{stateSnapshot.HostCombatActionState}#{stateSnapshot.HostCombatActionSequence}"); } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[EnemyAnimMirror] Host animator snapshot failed: " + ex.Message); } } } public static void ApplyClientEnemyPuppetAnimationPostUpdate(object? npc) { if (!IsEnemyAnimationMirrorApplyEnabled() || npc == null) { return; } try { int num = ObjectIdentity(npc); if (num != 0 && ActiveEnemyPuppetsByNpcId.TryGetValue(num, out EnemyPuppetRecord value) && value.LastHostSnapshot != null) { float realtimeSinceStartup = Time.realtimeSinceStartup; ApplyClientEnemyAnimationMirror(value.Key, value.LastHostSnapshot, npc, realtimeSinceStartup, parametersOnly: true); } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[EnemyAnimMirror] Puppet post-update apply failed: " + ex.Message); } } } private static void ApplyClientEnemyPuppetMotionAnimation(string key, NetGameplayEnemyStateSnapshot? hostSnapshot, object runtimeObject, Vector3 currentPosition, Vector3 nextPosition, Vector3 hostTargetPosition, float now) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0062: 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_0074: Unknown result type (might be due to invalid IL or missing references) if (!IsEnemyAnimationMirrorApplyEnabled() || string.IsNullOrWhiteSpace(key) || !ActiveEnemyPuppets.TryGetValue(key, out EnemyPuppetRecord value) || runtimeObject == null) { return; } Object val = (Object)((runtimeObject is Object) ? runtimeObject : null); if (val == null || !(val == (Object)null)) { float num = ((IsFinite(currentPosition) && IsFinite(nextPosition)) ? Vector3.Distance(currentPosition, nextPosition) : 0f); float num2 = ((IsFinite(nextPosition) && IsFinite(hostTargetPosition)) ? Vector3.Distance(nextPosition, hostTargetPosition) : 0f); bool motionDerivedMoving = num > 0.0025f || num2 > 0.035f; if (hostSnapshot != null && hostSnapshot.HasAnimatorMovingBool && hostSnapshot.AnimatorMovingBool) { motionDerivedMoving = true; } value.HasMotionDerivedMoving = true; value.MotionDerivedMoving = motionDerivedMoving; value.LastMotionDerivedMovingAt = now; ApplyClientEnemyAnimationMirror(key, hostSnapshot, runtimeObject, now, parametersOnly: true); } } private static void ApplyClientEnemyAnimationMirror(string key, NetGameplayEnemyStateSnapshot? hostSnapshot, object runtimeObject, float now, bool parametersOnly = false) { //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) if (!IsEnemyAnimationMirrorApplyEnabled() || hostSnapshot == null || !hostSnapshot.HasAnimatorState || runtimeObject == null) { return; } Object val = (Object)((runtimeObject is Object) ? runtimeObject : null); if (val != null && val == (Object)null) { return; } if (IsClientTerminalDeadByKey(key)) { _terminalDeadBlockedGenericReplay++; return; } try { ActiveEnemyPuppets.TryGetValue(key, out EnemyPuppetRecord value); if (value == null || !TryGetCachedAnimator(value, runtimeObject, out Animator animator) || (Object)(object)animator == (Object)null || animator.layerCount <= 0) { return; } int num = hostSnapshot.AnimatorLayer; if (num < 0 || num >= animator.layerCount) { num = 0; } EnsureAnimatorParameterCache(value, animator); bool flag = IsClientEnemyActionPlaybackSnapshot(hostSnapshot); if (flag) { value.LastRawActionTrueAt = now; } else if (value.LastRawActionTrueAt > 0f && now - value.LastRawActionTrueAt <= 0.35f) { return; } if (value.HasMovingParam) { bool flag2 = hostSnapshot.HasAnimatorMovingBool; bool flag3 = flag2 && hostSnapshot.AnimatorMovingBool; if (value.HasMotionDerivedMoving && now - value.LastMotionDerivedMovingAt <= 0.25f) { flag2 = true; if (value.MotionDerivedMoving) { flag3 = true; } else if (!hostSnapshot.AnimatorMovingBool) { flag3 = false; } } if (flag2) { animator.SetBool(value.MovingParamHash, flag3); } } if (hostSnapshot.HasAnimatorAttackBool && value.HasAttackParam) { animator.SetBool(value.AttackParamHash, hostSnapshot.AnimatorAttackBool); } if (hostSnapshot.HasAnimatorCoweringBool && value.HasCoweringParam) { animator.SetBool(value.CoweringParamHash, hostSnapshot.AnimatorCoweringBool); } bool flag4 = flag; LogClientActionStateFlipIfChanged(value, hostSnapshot, flag4, now); if (flag4 && Plugin.Cfg.EnemyAnimationMirrorApplyHostCombatStatePlayback.Value) { TryApplyClientCombatAnimatorTriggers(value, animator, hostSnapshot, now); TryReplayClientHostCombatVisualAction(value, runtimeObject, hostSnapshot, now); } if (IsFinite(hostSnapshot.AnimatorSpeed) && hostSnapshot.AnimatorSpeed >= 0f && hostSnapshot.AnimatorSpeed <= 5f) { animator.speed = ((animator.speed < 0.01f && flag4) ? 1f : hostSnapshot.AnimatorSpeed); } bool value2 = Plugin.Cfg.EnemyAnimationMirrorApplyAnimatorStatePlayback.Value; bool flag5 = Plugin.Cfg.EnemyAnimationMirrorApplyHostCombatStatePlayback.Value && flag4; if (parametersOnly || (!value2 && !flag5)) { return; } bool flag6 = value.HasMotionDerivedMoving && value.MotionDerivedMoving && now - value.LastMotionDerivedMovingAt <= 0.25f; if (flag6 && !flag4) { return; } int animatorFullPathHash = hostSnapshot.AnimatorFullPathHash; if (animatorFullPathHash == 0) { return; } AnimatorStateInfo currentAnimatorStateInfo = animator.GetCurrentAnimatorStateInfo(num); bool flag7 = ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash != animatorFullPathHash; float num2 = Fraction01(hostSnapshot.AnimatorNormalizedTime); float b = Fraction01(((AnimatorStateInfo)(ref currentAnimatorStateInfo)).normalizedTime); float num3 = FractionDistance(num2, b); float num4 = Plugin.Cfg.EnemyAnimationMirrorNormalizedTimeTolerance.Value; if (num4 < 0.02f) { num4 = 0.02f; } if (num4 > 1f) { num4 = 1f; } bool flag8 = !flag7 && flag4 && num3 > num4 && now - value.LastAnimatorApplyAt > 0.1f; if (false) { if (Plugin.Cfg.LogEnemyAnimationMirror.Value && (flag7 || flag8) && now - value.LastAnimatorApplyAt > 0.25f) { value.LastAnimatorApplyAt = now; NetLogger.Info($"[EnemyAnimMirror] idx={hostSnapshot.SpawnIndex} unit={hostSnapshot.UnitIdentifier} SELF-PLAY skip-hash hostState={animatorFullPathHash} changed={flag7} drift={num3:F2}"); } } else if (flag7 || flag8) { float value3 = Plugin.Cfg.EnemyAnimationMirrorCrossFadeSeconds.Value; if (value3 <= 0f || flag8) { animator.Play(animatorFullPathHash, num, num2); } else { animator.CrossFade(animatorFullPathHash, Mathf.Clamp(value3, 0f, 0.5f), num, num2); } if (animatorFullPathHash == value.LastReplayHash) { value.ReplayCount++; } else { value.LastReplayHash = animatorFullPathHash; value.ReplayCount = 1; } bool num5 = Plugin.Cfg.LogEnemyAnimationMirror.Value && (value.LastAppliedAnimatorFullPathHash != animatorFullPathHash || flag8 || !value.LastLoggedAnimatorState); value.LastAppliedAnimatorFullPathHash = animatorFullPathHash; value.LastAnimatorApplyAt = now; value.LastLoggedAnimatorState = true; if (flag4 && !Plugin.Cfg.EnemyAnimationMirrorApplyAnimatorStatePlayback.Value) { _clientCombatAnimatorStateApplies++; } if (num5) { NetLogger.Info($"[EnemyAnimMirror] idx={hostSnapshot.SpawnIndex} unit={hostSnapshot.UnitIdentifier} state={animatorFullPathHash} t={num2:F2} changed={flag7} resync={flag8} replay={value.ReplayCount} drift={num3:F2} action={flag4} moving={flag6}"); } } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug($"[EnemyAnimMirror] Apply failed idx={hostSnapshot.SpawnIndex} actor={hostSnapshot.ActorName}: {ex.Message}"); } } } private static bool IsClientEnemyActionPlaybackSnapshot(NetGameplayEnemyStateSnapshot hostSnapshot) { if (hostSnapshot == null) { return false; } if (hostSnapshot.IsDead) { return true; } if (hostSnapshot.HasHostCombatAction && hostSnapshot.HostCombatActionKind != 0) { return true; } if (hostSnapshot.HasAnimatorAttackBool && hostSnapshot.AnimatorAttackBool) { return true; } if (hostSnapshot.HasAnimatorCoweringBool && hostSnapshot.AnimatorCoweringBool) { return true; } return false; } private static void LogClientActionStateFlipIfChanged(EnemyPuppetRecord record, NetGameplayEnemyStateSnapshot hostSnapshot, bool actionState, float now) { if (record != null && hostSnapshot != null && Plugin.Cfg.LogEnemyAnimationMirror.Value && (!record.HasLastActionState || record.LastActionState != actionState)) { float num = ((record.HasLastActionState && record.LastActionStateFlipAt > 0f) ? (now - record.LastActionStateFlipAt) : (-1f)); string text = ((!actionState) ? "none" : (hostSnapshot.IsDead ? "dead" : ((hostSnapshot.HasHostCombatAction && hostSnapshot.HostCombatActionKind != 0) ? $"combatAction(kind={hostSnapshot.HostCombatActionKind},seq={hostSnapshot.HostCombatActionSequence})" : ((hostSnapshot.HasAnimatorAttackBool && hostSnapshot.AnimatorAttackBool) ? "attackBool" : ((!hostSnapshot.HasAnimatorCoweringBool || !hostSnapshot.AnimatorCoweringBool) ? "?" : "cowering"))))); bool flag = record.HasMotionDerivedMoving && record.MotionDerivedMoving && now - record.LastMotionDerivedMovingAt <= 0.25f; string text2 = (record.HasLastActionState ? record.LastActionState.ToString() : "init"); NetLogger.Info(string.Format("[ActionStateFlip] idx={0} unit={1} {2}->{3} reason={4} sinceLastFlip={5:F2}s moving={6} attackBool={7}", hostSnapshot.SpawnIndex, hostSnapshot.UnitIdentifier, text2, actionState, text, num, flag, hostSnapshot.HasAnimatorAttackBool ? hostSnapshot.AnimatorAttackBool.ToString() : "-")); record.HasLastActionState = true; record.LastActionState = actionState; record.LastActionStateFlipAt = now; } } private static void TryReplayClientHostCombatVisualAction(EnemyPuppetRecord record, object runtimeObject, NetGameplayEnemyStateSnapshot hostSnapshot, float now) { if (record == null || runtimeObject == null || hostSnapshot == null || !hostSnapshot.HasHostCombatAction || hostSnapshot.HostCombatActionKind == 0) { return; } int num = hostSnapshot.HostCombatActionSequence; if (num <= 0) { num = hostSnapshot.Sequence; } TryFaceHostCombatAim(runtimeObject, hostSnapshot); TryApplyGenericHostCombatAnimatorStates(record, runtimeObject, hostSnapshot, num, now); TrySpawnClientVisualProjectileForHostCombat(record, hostSnapshot, num, now); TryApplyClientCombatAnimatorFallback(record, runtimeObject, hostSnapshot, num, now); if (!Plugin.Cfg.EnemyAnimationMirrorReplayHostCombatMethods.Value || now - record.LastAppliedCombatVisualActionAt < 0.08f) { return; } int npcId = record.NpcId; ClientEnemyAuthorizedIntentWindow value = null; bool flag = npcId != 0 && _clientAuthorizedIntentByNpcId.TryGetValue(npcId, out value) && now <= value.ExpiresAt; if (flag && value.RootReplayed && value.Sequence == num) { _clientCombatRootReplaySkippedDuplicate++; } else { if (!flag && record.LastAppliedCombatVisualActionSequence == num && now - record.LastAppliedCombatVisualActionAt < 0.75f) { return; } bool flag2 = false; string text = ""; try { _clientAuthorizedCombatRootReplayDepth++; _clientPuppetCombatVisualReplayDepth++; switch (hostSnapshot.HostCombatActionKind) { case 4: text = "TriggerWeaponManually"; flag2 = TryInvokeInstanceMethod(runtimeObject, text, hostSnapshot.HostCombatActionState); break; case 2: if (Plugin.Cfg.EnemyProjectileVisualMirrorUseNativeShootReplay.Value) { text = "TriggerShoot"; flag2 = TryInvokeInstanceMethod(runtimeObject, text); } break; case 1: text = "TriggerAttackAnimation"; flag2 = TryInvokeInstanceMethod(runtimeObject, text); break; case 3: if (Plugin.Cfg.EnemyProjectileVisualMirrorUseNativeShootReplay.Value) { text = "TriggerShoot"; flag2 = TryInvokeInstanceMethod(runtimeObject, text); if (!flag2) { text = "SetShooting"; flag2 = TryInvokeInstanceMethod(runtimeObject, text, true); } } break; } } catch { flag2 = false; } finally { if (_clientAuthorizedCombatRootReplayDepth > 0) { _clientAuthorizedCombatRootReplayDepth--; } if (_clientPuppetCombatVisualReplayDepth > 0) { _clientPuppetCombatVisualReplayDepth--; } } if (flag2) { if (flag && value != null && value.Sequence == num) { value.RootReplayed = true; } record.LastAppliedCombatVisualActionSequence = num; record.LastAppliedCombatVisualActionAt = now; _clientCombatVisualActionReplays++; _clientCombatRootReplays++; if (Plugin.Cfg.LogEnemyAnimationMirror.Value) { NetLogger.Info($"[EnemyCombatAnim] Client replayed Host combat visual idx={hostSnapshot.SpawnIndex} actor={hostSnapshot.ActorName} kind={hostSnapshot.HostCombatActionKind} state={hostSnapshot.HostCombatActionState} seq={num} method={text}"); } } } } private static void TrySpawnClientVisualProjectileForHostCombat(EnemyPuppetRecord record, NetGameplayEnemyStateSnapshot hostSnapshot, int actionSequence, float now) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007e: 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) //IL_008a: 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_008c: 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_0092: 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_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0144: 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_015f: 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_0184: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_0264: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) if (record == null || hostSnapshot == null || !Plugin.Cfg.EnemyProjectileVisualMirrorEnabled.Value || (hostSnapshot.HostCombatActionKind != 2 && hostSnapshot.HostCombatActionKind != 3) || !hostSnapshot.HasHostCombatAim || (record.LastVisualProjectileSequence == actionSequence && now - record.LastVisualProjectileAt < 0.75f) || now - record.LastVisualProjectileAt < 0.3f || !IsFinite(hostSnapshot.HostCombatOriginPosition) || !IsFinite(hostSnapshot.HostCombatAimPosition)) { return; } Vector3 hostCombatOriginPosition = hostSnapshot.HostCombatOriginPosition; Vector3 hostCombatAimPosition = hostSnapshot.HostCombatAimPosition; Vector3 val = hostCombatAimPosition - hostCombatOriginPosition; if (((Vector3)(ref val)).sqrMagnitude < 0.05f) { return; } float num = Plugin.Cfg.EnemyProjectileVisualMirrorSpeed.Value; if (num < 1f) { num = 1f; } if (num > 80f) { num = 80f; } float num2 = Plugin.Cfg.EnemyProjectileVisualMirrorLifetime.Value; if (num2 < 0.1f) { num2 = 0.1f; } if (num2 > 5f) { num2 = 5f; } try { GameObject val2 = GameObject.CreatePrimitive((PrimitiveType)2); ((Object)val2).name = $"SULFUR Together Enemy Projectile Visual #{actionSequence}"; val2.transform.position = hostCombatOriginPosition; Vector3 normalized = ((Vector3)(ref val)).normalized; val2.transform.rotation = Quaternion.LookRotation(normalized, Vector3.up) * Quaternion.Euler(90f, 0f, 0f); val2.transform.localScale = new Vector3(0.045f, 0.55f, 0.045f); Collider component = val2.GetComponent(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } ClientVisualProjectiles.Add(new ClientVisualProjectile { GameObject = val2, Position = hostCombatOriginPosition, Target = hostCombatAimPosition, Speed = num, ExpiresAt = now + num2 }); record.LastVisualProjectileSequence = actionSequence; record.LastVisualProjectileAt = now; _clientVisualProjectileMirrors++; if (Plugin.Cfg.LogEnemyAnimationMirror.Value) { NetLogger.Info($"[EnemyProjectileVisual] Spawned visual projectile idx={hostSnapshot.SpawnIndex} actor={hostSnapshot.ActorName} seq={actionSequence} origin=({hostCombatOriginPosition.x:F2},{hostCombatOriginPosition.y:F2},{hostCombatOriginPosition.z:F2}) target=({hostCombatAimPosition.x:F2},{hostCombatAimPosition.y:F2},{hostCombatAimPosition.z:F2})"); } } catch { } } private static void UpdateClientVisualProjectiles() { //IL_007d: 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_008d: 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_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: 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_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) if (ClientVisualProjectiles.Count == 0) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; float num = Mathf.Max(Time.deltaTime, 1f / 60f); for (int num2 = ClientVisualProjectiles.Count - 1; num2 >= 0; num2--) { ClientVisualProjectile clientVisualProjectile = ClientVisualProjectiles[num2]; if (clientVisualProjectile != null && !((Object)(object)clientVisualProjectile.GameObject == (Object)null)) { Object gameObject = (Object)(object)clientVisualProjectile.GameObject; if ((gameObject == null || !(gameObject == (Object)null)) && !(realtimeSinceStartup >= clientVisualProjectile.ExpiresAt)) { Vector3 val = clientVisualProjectile.Target - clientVisualProjectile.Position; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude <= 0.05f) { DestroyClientVisualProjectileAt(num2); continue; } Vector3 val2 = ((Vector3)(ref val)).normalized * clientVisualProjectile.Speed * num; if (((Vector3)(ref val2)).magnitude >= magnitude) { clientVisualProjectile.Position = clientVisualProjectile.Target; } else { clientVisualProjectile.Position += val2; } clientVisualProjectile.GameObject.transform.position = clientVisualProjectile.Position; Vector3 val3 = clientVisualProjectile.Target - clientVisualProjectile.Position; if (((Vector3)(ref val3)).sqrMagnitude > 0.0001f) { Transform transform = clientVisualProjectile.GameObject.transform; val3 = clientVisualProjectile.Target - clientVisualProjectile.Position; transform.rotation = Quaternion.LookRotation(((Vector3)(ref val3)).normalized, Vector3.up) * Quaternion.Euler(90f, 0f, 0f); } continue; } } DestroyClientVisualProjectileAt(num2); } } private static void DestroyAllClientVisualProjectiles() { for (int num = ClientVisualProjectiles.Count - 1; num >= 0; num--) { DestroyClientVisualProjectileAt(num); } ClientVisualProjectiles.Clear(); } private static void DestroyClientVisualProjectileAt(int index) { if (index < 0 || index >= ClientVisualProjectiles.Count) { return; } try { ClientVisualProjectile clientVisualProjectile = ClientVisualProjectiles[index]; if (clientVisualProjectile != null && (Object)(object)clientVisualProjectile.GameObject != (Object)null) { Object.Destroy((Object)(object)clientVisualProjectile.GameObject); } } catch { } ClientVisualProjectiles.RemoveAt(index); } private static void TryApplyClientCombatAnimatorFallback(EnemyPuppetRecord record, object runtimeObject, NetGameplayEnemyStateSnapshot hostSnapshot, int actionSequence, float now) { if (record == null || runtimeObject == null || hostSnapshot == null || !Plugin.Cfg.EnemyAnimationMirrorApplyCombatAnimatorFallback.Value || (record.LastAppliedCombatAnimatorFallbackSequence == actionSequence && now - record.LastAppliedCombatAnimatorFallbackAt < 0.75f) || now - record.LastAppliedCombatAnimatorFallbackAt < 0.08f) { return; } int num = 0; try { Component val = (Component)((runtimeObject is Component) ? runtimeObject : null); if (val != null && (Object)(object)val != (Object)null) { Animator[] componentsInChildren = val.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { num += PulseCombatAnimator(componentsInChildren[i], hostSnapshot, now); } } else { GameObject val2 = (GameObject)((runtimeObject is GameObject) ? runtimeObject : null); if (val2 != null && (Object)(object)val2 != (Object)null) { Animator[] componentsInChildren2 = val2.GetComponentsInChildren(true); for (int j = 0; j < componentsInChildren2.Length; j++) { num += PulseCombatAnimator(componentsInChildren2[j], hostSnapshot, now); } } } object obj = TryFindLikelyWeaponObject(runtimeObject); num += TryPulseWeaponVisualMethods(obj, hostSnapshot); Component val3 = (Component)((obj is Component) ? obj : null); if (val3 != null && (Object)(object)val3 != (Object)null) { Animator[] componentsInChildren3 = val3.GetComponentsInChildren(true); for (int k = 0; k < componentsInChildren3.Length; k++) { num += PulseCombatAnimator(componentsInChildren3[k], hostSnapshot, now); } } } catch { } if (num > 0) { record.LastAppliedCombatAnimatorFallbackSequence = actionSequence; record.LastAppliedCombatAnimatorFallbackAt = now; _clientCombatAnimatorFallbacks++; if (Plugin.Cfg.LogEnemyAnimationMirror.Value) { NetLogger.Info($"[EnemyCombatAnim] Client combat animator fallback idx={hostSnapshot.SpawnIndex} actor={hostSnapshot.ActorName} kind={hostSnapshot.HostCombatActionKind} seq={actionSequence} applied={num}"); } } } private static int PulseCombatAnimator(Animator? animator, NetGameplayEnemyStateSnapshot hostSnapshot, float now) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Invalid comparison between Unknown and I4 //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Invalid comparison between Unknown and I4 //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Invalid comparison between Unknown and I4 if ((Object)(object)animator == (Object)null) { return 0; } try { if (animator != null && (Object)(object)animator == (Object)null) { return 0; } int num = 0; AnimatorControllerParameter[] parameters = animator.parameters; foreach (AnimatorControllerParameter val in parameters) { if (val == null || string.IsNullOrWhiteSpace(val.name) || !LooksLikeCombatAnimatorParameter(val.name, hostSnapshot.HostCombatActionKind)) { continue; } if ((int)val.type == 9) { try { animator.SetTrigger(val.nameHash); num++; } catch { } } else if ((int)val.type == 4) { try { animator.SetBool(val.nameHash, true); PendingAnimatorBoolResets.Add(new AnimatorBoolReset { Animator = animator, Hash = val.nameHash, ResetAt = now + 0.35f }); num++; } catch { } } else if ((int)val.type == 3) { try { animator.SetInteger(val.nameHash, hostSnapshot.HostCombatActionState); num++; } catch { } } } return num; } catch { return 0; } } private static void UpdateCombatAnimatorBoolResets() { if (PendingAnimatorBoolResets.Count == 0) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; for (int num = PendingAnimatorBoolResets.Count - 1; num >= 0; num--) { AnimatorBoolReset animatorBoolReset = PendingAnimatorBoolResets[num]; if (animatorBoolReset != null && !(realtimeSinceStartup < animatorBoolReset.ResetAt)) { try { if ((Object)(object)animatorBoolReset.Animator != (Object)null) { animatorBoolReset.Animator.SetBool(animatorBoolReset.Hash, false); } } catch { } PendingAnimatorBoolResets.RemoveAt(num); } } } private static bool LooksLikeCombatAnimatorParameter(string name, int kind) { if (string.IsNullOrWhiteSpace(name)) { return false; } string text = name.ToLowerInvariant(); if (text.Contains("move") || text.Contains("moving") || text.Contains("idle") || text.Contains("cower")) { return false; } if (kind == 2 || kind == 3) { if (!text.Contains("shoot") && !text.Contains("fire") && !text.Contains("attack")) { return text.Contains("ranged"); } return true; } if (!text.Contains("attack") && !text.Contains("melee") && !text.Contains("weapon") && !text.Contains("swing") && !text.Contains("stab")) { return text.Contains("lunge"); } return true; } private static int TryPulseWeaponVisualMethods(object? weapon, NetGameplayEnemyStateSnapshot hostSnapshot) { if (weapon == null) { return 0; } int num = 0; try { _clientPuppetCombatVisualReplayDepth++; if (hostSnapshot.HostCombatActionKind == 4 || hostSnapshot.HostCombatActionKind == 1) { if (TryInvokeInstanceMethod(weapon, "TriggerAttackAnimation")) { num++; } if (TryInvokeInstanceMethod(weapon, "SetAttacking", true)) { num++; } if (TryInvokeInstanceMethod(weapon, "MeleeWeaponAttack")) { num++; } if (TryInvokeInstanceMethod(weapon, "MeleeAttack")) { num++; } } else if (hostSnapshot.HostCombatActionKind == 2 || hostSnapshot.HostCombatActionKind == 3) { if (TryInvokeInstanceMethod(weapon, "PlayWeaponShootAnimation")) { num++; } if (TryInvokeInstanceMethod(weapon, "SetRangedAttacking", true)) { num++; } } } catch { } finally { if (_clientPuppetCombatVisualReplayDepth > 0) { _clientPuppetCombatVisualReplayDepth--; } } return num; } private static object? TryFindLikelyWeaponObject(object runtimeObject) { if (runtimeObject == null) { return null; } string[] array = new string[12] { "weapon", "Weapon", "currentWeapon", "CurrentWeapon", "equippedWeapon", "EquippedWeapon", "heldWeapon", "HeldWeapon", "activeWeapon", "ActiveWeapon", "inventoryItem", "InventoryItem" }; for (int i = 0; i < array.Length; i++) { object obj = TryGetMemberValue(runtimeObject, array[i]); if (obj != null) { return obj; } } return TryFindComponentByTypeName(runtimeObject, "Weapon"); } private static void TryFaceHostCombatAim(object runtimeObject, NetGameplayEnemyStateSnapshot hostSnapshot) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) if (runtimeObject == null || hostSnapshot == null || !hostSnapshot.HasHostCombatAim || !TryGetTransform(runtimeObject, out Transform transform) || (Object)(object)transform == (Object)null) { return; } Vector3 val = hostSnapshot.HostCombatAimPosition - transform.position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.001f) { return; } try { transform.rotation = Quaternion.LookRotation(((Vector3)(ref val)).normalized, Vector3.up); } catch { } } private static void TryApplyClientCombatAnimatorTriggers(EnemyPuppetRecord record, Animator animator, NetGameplayEnemyStateSnapshot hostSnapshot, float now) { if (record == null || (Object)(object)animator == (Object)null || hostSnapshot == null || record.CombatTriggerParamHashes.Count == 0 || (record.LastAppliedCombatTriggerSequence == hostSnapshot.Sequence && now - record.LastAppliedCombatTriggerAt < 0.5f) || now - record.LastAppliedCombatTriggerAt < 0.08f) { return; } for (int i = 0; i < record.CombatTriggerParamHashes.Count; i++) { try { animator.SetTrigger(record.CombatTriggerParamHashes[i]); } catch { } } record.LastAppliedCombatTriggerSequence = hostSnapshot.Sequence; record.LastAppliedCombatTriggerAt = now; _clientCombatAnimatorTriggerApplies++; } private static bool IsKnownCombatTriggerParameterName(string name) { if (string.IsNullOrWhiteSpace(name)) { return false; } if (!string.Equals(name, "Attack", StringComparison.OrdinalIgnoreCase) && !string.Equals(name, "Shoot", StringComparison.OrdinalIgnoreCase) && !string.Equals(name, "Shooting", StringComparison.OrdinalIgnoreCase) && !string.Equals(name, "Fire", StringComparison.OrdinalIgnoreCase) && !string.Equals(name, "Melee", StringComparison.OrdinalIgnoreCase) && !string.Equals(name, "Attacking", StringComparison.OrdinalIgnoreCase) && !string.Equals(name, "MeleeAttack", StringComparison.OrdinalIgnoreCase) && !string.Equals(name, "WeaponAttack", StringComparison.OrdinalIgnoreCase) && !string.Equals(name, "RangedAttack", StringComparison.OrdinalIgnoreCase) && !string.Equals(name, "TriggerAttack", StringComparison.OrdinalIgnoreCase)) { return string.Equals(name, "TriggerShoot", StringComparison.OrdinalIgnoreCase); } return true; } private static bool IsEnemyAnimationMirrorApplyEnabled() { try { return Plugin.Cfg.EnableHostEnemyAnimationMirror.Value && Plugin.Cfg.ApplyReceivedEnemyAnimationMirror.Value && IsClientEnemyPuppetModeEnabled(); } catch { return false; } } private static bool TryGetCachedAnimator(EnemyPuppetRecord record, object runtimeObject, out Animator? animator) { animator = null; try { if ((Object)(object)record.CachedAnimator != (Object)null) { Object cachedAnimator = (Object)(object)record.CachedAnimator; if (cachedAnimator != null && cachedAnimator != (Object)null) { animator = record.CachedAnimator; return true; } record.CachedAnimator = null; record.CachedAnimatorId = 0; record.AnimatorParamCacheInitialized = false; } if (!TryGetAnimator(runtimeObject, out animator) || (Object)(object)animator == (Object)null) { return false; } record.CachedAnimator = animator; record.CachedAnimatorId = ObjectIdentity(animator); record.AnimatorParamCacheInitialized = false; return true; } catch { animator = null; return false; } } private static void EnsureAnimatorParameterCache(EnemyPuppetRecord record, Animator animator) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Invalid comparison between Unknown and I4 //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Invalid comparison between Unknown and I4 if (record.AnimatorParamCacheInitialized) { return; } record.MovingParamHash = Animator.StringToHash("Moving"); record.AttackParamHash = Animator.StringToHash("Attack"); record.CoweringParamHash = Animator.StringToHash("Cowering"); record.HasMovingParam = false; record.HasAttackParam = false; record.HasCoweringParam = false; try { record.CombatTriggerParamHashes.Clear(); AnimatorControllerParameter[] parameters = animator.parameters; foreach (AnimatorControllerParameter val in parameters) { if (val == null) { continue; } if ((int)val.type == 4) { if (val.nameHash == record.MovingParamHash) { record.HasMovingParam = true; } else if (val.nameHash == record.AttackParamHash) { record.HasAttackParam = true; } else if (val.nameHash == record.CoweringParamHash) { record.HasCoweringParam = true; } } else if ((int)val.type == 9 && IsKnownCombatTriggerParameterName(val.name) && !record.CombatTriggerParamHashes.Contains(val.nameHash)) { record.CombatTriggerParamHashes.Add(val.nameHash); } } } catch { } record.AnimatorParamCacheInitialized = true; } private static bool TryGetAnimator(object target, out Animator? animator) { animator = null; if (target == null) { return false; } try { Object val = (Object)((target is Object) ? target : null); if (val != null && val == (Object)null) { return false; } object? obj = TryGetMemberValue(target, "animator") ?? TryGetMemberValue(target, "Animator"); Animator val2 = (Animator)((obj is Animator) ? obj : null); if (val2 != null && (Object)(object)val2 != (Object)null) { animator = val2; return true; } Component val3 = (Component)((target is Component) ? target : null); if (val3 != null && (Object)(object)val3 != (Object)null) { animator = val3.GetComponent(); if ((Object)(object)animator != (Object)null) { return true; } animator = val3.GetComponentInChildren(true); return (Object)(object)animator != (Object)null; } GameObject val4 = (GameObject)((target is GameObject) ? target : null); if (val4 != null && (Object)(object)val4 != (Object)null) { animator = val4.GetComponent(); if ((Object)(object)animator != (Object)null) { return true; } animator = val4.GetComponentInChildren(true); return (Object)(object)animator != (Object)null; } } catch { } return false; } private static bool TryGetAnimatorBool(Animator animator, string parameterName, out bool value) { value = false; try { if (!AnimatorHasParameter(animator, parameterName, (AnimatorControllerParameterType)4)) { return false; } value = animator.GetBool(parameterName); return true; } catch { return false; } } private static bool TrySetAnimatorBool(Animator animator, string parameterName, bool value) { try { if (!AnimatorHasParameter(animator, parameterName, (AnimatorControllerParameterType)4)) { return false; } animator.SetBool(parameterName, value); return true; } catch { return false; } } private static bool AnimatorHasParameter(Animator animator, string parameterName, AnimatorControllerParameterType type) { //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) if ((Object)(object)animator == (Object)null) { return false; } try { AnimatorControllerParameter[] parameters = animator.parameters; if (parameters == null) { return false; } foreach (AnimatorControllerParameter val in parameters) { if (val != null && val.type == type && string.Equals(val.name, parameterName, StringComparison.Ordinal)) { return true; } } } catch { } return false; } private static float Fraction01(float value) { if (!IsFinite(value)) { return 0f; } value -= Mathf.Floor(value); if (value < 0f) { value += 1f; } return Mathf.Clamp01(value); } private static float FractionDistance(float a, float b) { float num = Mathf.Abs(Fraction01(a) - Fraction01(b)); return Mathf.Min(num, 1f - num); } private static string BoolText(bool has, bool value) { if (!has) { return "n/a"; } if (!value) { return "false"; } return "true"; } private static void ReleaseStaleEnemyPuppets() { if (ActiveEnemyPuppets.Count == 0) { return; } if (!IsClientEnemyPuppetModeEnabled()) { ReleaseAllEnemyPuppets("puppet mode disabled"); return; } float value = Plugin.Cfg.ClientEnemyPuppetStaleReleaseSeconds.Value; if (value <= 0f) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; KeyValuePair[] array = ActiveEnemyPuppets.ToArray(); for (int i = 0; i < array.Length; i++) { KeyValuePair keyValuePair = array[i]; if (realtimeSinceStartup - keyValuePair.Value.LastSeenAt <= value) { continue; } int value2; bool flag = ClientLocalKeyToHostSpawnIndex.TryGetValue(keyValuePair.Key, out value2); string value3; bool num = flag && Plugin.Cfg.EvictStaleHostBindings.Value && (!ClientHostToLocalKeyByHostSpawnIndex.TryGetValue(value2, out value3) || !string.Equals(value3, keyValuePair.Key, StringComparison.Ordinal)); bool flag2 = flag && IsClientKnownDeadHostIdx(value2); if (num || flag2) { ClientLocalKeyToHostSpawnIndex.Remove(keyValuePair.Key); if (ClientHostToLocalKeyByHostSpawnIndex.TryGetValue(value2, out string value4) && string.Equals(value4, keyValuePair.Key, StringComparison.Ordinal)) { ClientHostToLocalKeyByHostSpawnIndex.Remove(value2); } _evictedStaleHostBindings++; string arg = (flag2 ? "bound to buried hostIdx" : "forward map disowned"); ReleaseEnemyPuppet(keyValuePair.Key, $"stale orphan binding hostIdx={value2} ({arg}, dbEvicted={_evictedStaleHostBindings})"); } else if (flag) { _puppetStaleReleaseSuppressed++; float num2 = realtimeSinceStartup - keyValuePair.Value.LastSeenAt; keyValuePair.Value.LastSeenAt = realtimeSinceStartup; if (Plugin.Cfg.LogClientEnemyPuppetMode.Value) { string text = "recv=never"; int value7; if (ClientLocalKeyToHostSpawnIndex.TryGetValue(keyValuePair.Key, out var value5) && _clientLastSnapshotRecvByHostIdx.TryGetValue(value5, out var value6)) { text = $"hostIdx={value5} lastRecv={realtimeSinceStartup - value6:F2}s"; } else if (ClientLocalKeyToHostSpawnIndex.TryGetValue(keyValuePair.Key, out value7)) { text = $"hostIdx={value7} recv=never"; } NetLogger.Info($"[EnemyPuppet] Stale-release suppressed (host-bound) idx={keyValuePair.Value.Snapshot.SpawnIndex} actor={keyValuePair.Value.Snapshot.ActorName} stale={num2:F2}s {text} — timer reset"); } } else { ReleaseEnemyPuppet(keyValuePair.Key, $"stale for {realtimeSinceStartup - keyValuePair.Value.LastSeenAt:F2}s (unbound)"); } } } public static void ClientFrameHitchTick(long updateBodyMs, long gameplayMs) { try { if (!Plugin.Cfg.LogClientFrameHitch.Value || NetGameplaySyncBridge.BossMode != NetMode.Client) { return; } float unscaledDeltaTime = Time.unscaledDeltaTime; if (unscaledDeltaTime < 0.05f) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup - _lastHitchLogTime < 0.1f) { return; } _lastHitchLogTime = realtimeSinceStartup; int count = ActiveEnemyPuppets.Count; int num = 0; foreach (KeyValuePair activeEnemyPuppet in ActiveEnemyPuppets) { string text = activeEnemyPuppet.Value?.Snapshot?.ActorName; if (text != null && text.IndexOf("Craw", StringComparison.OrdinalIgnoreCase) >= 0) { num++; } } int num2 = GC.CollectionCount(0); int num3 = ((_lastHitchGc0 >= 0) ? (num2 - _lastHitchGc0) : 0); _lastHitchGc0 = num2; NetLogger.Info($"[FrameHitch] dt={unscaledDeltaTime * 1000f:F0}ms (~{((unscaledDeltaTime > 0f) ? (1f / unscaledDeltaTime) : 0f):F0}fps) updateBody={updateBodyMs}ms (gameplay={gameplayMs}ms) puppets={count} craws={num} gc0Δ={num3} — hitch>>updateBody ⇒ cost is native (physics/render), not mod code"); } catch { } } private static void ReleaseAllEnemyPuppets(string reason) { if (ActiveEnemyPuppets.Count == 0) { ClientPuppetNpcIds.Clear(); ClientPuppetAiAgentIds.Clear(); BarrenTargetAuthorityAiIds.Clear(); ClientPuppetMovementDriverIds.Clear(); ActiveEnemyPuppetsByNpcId.Clear(); return; } string[] array = ActiveEnemyPuppets.Keys.ToArray(); for (int i = 0; i < array.Length; i++) { ReleaseEnemyPuppet(array[i], reason); } ClientPuppetNpcIds.Clear(); ClientPuppetAiAgentIds.Clear(); BarrenTargetAuthorityAiIds.Clear(); ClientPuppetMovementDriverIds.Clear(); _visualOnlyFlyerCollidersDisabled.Clear(); ActiveEnemyPuppetsByNpcId.Clear(); } private static void ReleaseClientEnemyPuppetOnHostDeath(NetGameplayEntitySnapshot snapshot, int hostIdx, string reason) { try { string snapshotTargetKey = GetSnapshotTargetKey(snapshot); if (!string.IsNullOrWhiteSpace(snapshotTargetKey)) { if (ActiveEnemyPuppets.ContainsKey(snapshotTargetKey)) { ReleaseEnemyPuppet(snapshotTargetKey, "host death (" + reason + ")"); _releasedPuppetsOnHostDeath++; } if (ClientLocalKeyToHostSpawnIndex.TryGetValue(snapshotTargetKey, out var value)) { ClientLocalKeyToHostSpawnIndex.Remove(snapshotTargetKey); ClientHostToLocalKeyByHostSpawnIndex.Remove(value); _runtimeSpawnBindingsByHostIdx.Remove(value); } } if (hostIdx > 0) { if (ClientHostToLocalKeyByHostSpawnIndex.TryGetValue(hostIdx, out string value2)) { ClientLocalKeyToHostSpawnIndex.Remove(value2); } ClientHostToLocalKeyByHostSpawnIndex.Remove(hostIdx); _runtimeSpawnBindingsByHostIdx.Remove(hostIdx); _clientLastSnapshotRecvByHostIdx.Remove(hostIdx); } } catch { } } private static void ReleaseEnemyPuppet(string key, string reason) { if (string.IsNullOrWhiteSpace(key) || !ActiveEnemyPuppets.TryGetValue(key, out EnemyPuppetRecord value)) { return; } ActiveEnemyPuppets.Remove(key); if (value.NpcId != 0) { ClientPuppetNpcIds.Remove(value.NpcId); ActiveEnemyPuppetsByNpcId.Remove(value.NpcId); } if (value.AiAgentId != 0) { ClientPuppetAiAgentIds.Remove(value.AiAgentId); BarrenTargetAuthorityAiIds.Remove(value.AiAgentId); } if (value.MovementDriverId != 0) { ClientPuppetMovementDriverIds.Remove(value.MovementDriverId); } object obj = value.Npc; object obj2 = value.AiAgent; Object val = (Object)((obj is Object) ? obj : null); if (val != null && val == (Object)null) { obj = null; } Object val2 = (Object)((obj2 is Object) ? obj2 : null); if (val2 != null && val2 == (Object)null) { obj2 = null; } if (obj != null) { if (value.OriginalDisableVerifyPosition.HasValue) { TrySetBoolMember(obj, "disableVerifyPosition", value.OriginalDisableVerifyPosition.Value); } if (value.OriginalPreventNavMeshActivation.HasValue) { TrySetBoolMember(obj, "preventNavMeshActivation", value.OriginalPreventNavMeshActivation.Value); } } if (obj2 != null) { TryInvokeInstanceMethod(obj2, "SetCanMove", true); TryInvokeInstanceMethod(obj2, "SetNavMeshAgentState", true); TryInvokeInstanceMethod(obj2, "ToggleRVO", true); } if (obj != null) { TryInvokeInstanceMethod(obj, "ToggleBehaviourTree", true); } RestorePuppetRigidbody(value); int spawnIndex = value.Snapshot.SpawnIndex; if (spawnIndex > 0 && ClientHostToLocalKeyByHostSpawnIndex.ContainsKey(spawnIndex)) { _bindingTombstones[spawnIndex] = new BindingTombstone { HostSpawnIndex = spawnIndex, LocalKey = key, UnitIdentifier = (value.Snapshot.EntityId.UnitIdentifier ?? ""), ReleasedAt = Time.realtimeSinceStartup, ReleaseReason = reason }; } _clientEnemyPuppetsReleased++; bool flag = ClientLocalKeyToHostSpawnIndex.ContainsKey(key); NetLogger.Info(string.Format("[EnemyPuppet] {0} idx={1} actor={2} hostBound={3} reason={4}", NetDbg.Ctx("Release"), value.Snapshot.SpawnIndex, value.Snapshot.ActorName, flag, Clean(reason))); } private static int ObjectIdentity(object? obj) { if (obj == null) { return 0; } try { Object val = (Object)((obj is Object) ? obj : null); if (val != null) { return (!(val == (Object)null)) ? val.GetInstanceID() : 0; } } catch { } return RuntimeHelpers.GetHashCode(obj); } private static object? TryFindComponentByTypeName(object owner, string typeName) { try { Component val = null; Component val2 = (Component)((owner is Component) ? owner : null); if (val2 != null) { val = val2; } else { GameObject val3 = (GameObject)((owner is GameObject) ? owner : null); if (val3 != null) { val = val3.GetComponent(); } } if ((Object)(object)val == (Object)null) { return null; } Component[] componentsInChildren = val.GetComponentsInChildren(true); foreach (Component val4 in componentsInChildren) { if (!((Object)(object)val4 == (Object)null)) { Type type = ((object)val4).GetType(); if (type.Name == typeName || (type.FullName != null && type.FullName.EndsWith("." + typeName, StringComparison.Ordinal))) { return val4; } } } } catch { } return null; } private static bool TryInvokeInstanceMethod(object? target, string methodName, params object[] args) { if (target == null) { return false; } try { Object val = (Object)((target is Object) ? target : null); if (val != null && val == (Object)null) { return false; } Type[] types = args.Select((object a) => (a != null) ? a.GetType() : typeof(object)).ToArray(); Type type = target.GetType(); while (type != null) { MethodInfo methodInfo = null; try { methodInfo = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, types, null); } catch { } if (methodInfo == null) { foreach (MethodInfo item in from m in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == methodName select m) { ParameterInfo[] parameters = item.GetParameters(); if (parameters.Length != args.Length) { continue; } bool flag = true; for (int num = 0; num < parameters.Length; num++) { if (args[num] != null && !parameters[num].ParameterType.IsInstanceOfType(args[num])) { flag = false; break; } } if (flag) { methodInfo = item; break; } } } if (!(methodInfo == null)) { methodInfo.Invoke(target, args); return true; } type = type.BaseType; } } catch { } return false; } private static bool TryInvokeVector3Method(object? target, string methodName, out Vector3 result) { //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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0089: 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) result = Vector3.zero; if (target == null) { return false; } try { Object val = (Object)((target is Object) ? target : null); if (val != null && val == (Object)null) { return false; } Type type = target.GetType(); while (type != null) { MethodInfo methodInfo = null; try { methodInfo = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); } catch { } if (!(methodInfo == null) && !(methodInfo.ReturnType != typeof(Vector3))) { if (methodInfo.Invoke(target, null) is Vector3 val2) { result = val2; return true; } return false; } type = type.BaseType; } } catch { } return false; } private static bool TryGetBoolMember(object? target, string memberName, out bool value) { value = false; if (target == null) { return false; } try { Object val = (Object)((target is Object) ? target : null); if (val != null && val == (Object)null) { return false; } Type type = target.GetType(); while (type != null) { FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(bool)) { value = (bool)field.GetValue(target); return true; } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.PropertyType == typeof(bool) && property.GetIndexParameters().Length == 0) { value = (bool)property.GetValue(target, null); return true; } type = type.BaseType; } } catch { } return false; } private static bool TrySetBoolMember(object? target, string memberName, bool value) { if (target == null) { return false; } try { Object val = (Object)((target is Object) ? target : null); if (val != null && val == (Object)null) { return false; } Type type = target.GetType(); while (type != null) { FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(bool)) { field.SetValue(target, value); return true; } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.PropertyType == typeof(bool) && property.CanWrite && property.GetIndexParameters().Length == 0) { property.SetValue(target, value, null); return true; } type = type.BaseType; } } catch { } return false; } private static bool TrySetObjectMember(object? target, string memberName, object? value) { if (target == null) { return false; } try { Object val = (Object)((target is Object) ? target : null); if (val != null && val == (Object)null) { return false; } Type type = target.GetType(); while (type != null) { FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { if (value == null && field.FieldType.IsValueType && Nullable.GetUnderlyingType(field.FieldType) == null) { return false; } if (value != null && !field.FieldType.IsInstanceOfType(value)) { return false; } field.SetValue(target, value); return true; } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite && property.GetIndexParameters().Length == 0) { if (value == null && property.PropertyType.IsValueType && Nullable.GetUnderlyingType(property.PropertyType) == null) { return false; } if (value != null && !property.PropertyType.IsInstanceOfType(value)) { return false; } property.SetValue(target, value, null); return true; } type = type.BaseType; } } catch { } return false; } private static void TryDisableBehaviourComponent(object? maybeBehaviour) { try { Behaviour val = (Behaviour)((maybeBehaviour is Behaviour) ? maybeBehaviour : null); if (val != null && (Object)(object)val != (Object)null) { val.enabled = false; } } catch { } } private static void TryDisableNavMeshObject(object? navMeshAgent) { if (navMeshAgent == null) { return; } try { Object val = (Object)((navMeshAgent is Object) ? navMeshAgent : null); if (val == null || !(val == (Object)null)) { TrySetBoolMember(navMeshAgent, "canMove", value: false); TrySetBoolMember(navMeshAgent, "enabled", value: false); TrySetBoolMember(navMeshAgent, "updatePosition", value: false); TrySetBoolMember(navMeshAgent, "updateRotation", value: false); } } catch { } } private static void TryDisableRvoController(object? rvoController) { if (rvoController != null) { TrySetBoolMember(rvoController, "enabled", value: false); } } private static void TryZeroRigidbodyVelocity(object? rigidbody) { //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_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) if (rigidbody == null) { return; } try { Object val = (Object)((rigidbody is Object) ? rigidbody : null); if ((val == null || !(val == (Object)null)) && !(TryGetBoolMember(rigidbody, "isKinematic", out var value) && value)) { Vector3 zero = Vector3.zero; TrySetVector3Member(rigidbody, "velocity", zero); TrySetVector3Member(rigidbody, "linearVelocity", zero); TrySetVector3Member(rigidbody, "angularVelocity", zero); } } catch { } } private static void DisableFlyerPuppetPhysicsCollider(object npc, EnemyPuppetRecord record) { try { Component val = (Component)((npc is Component) ? npc : null); if (val == null || (Object)(object)val == (Object)null || !_visualOnlyFlyerCollidersDisabled.Add(((Object)val).GetInstanceID())) { return; } Collider[] componentsInChildren = val.GetComponentsInChildren(true); int num = 0; Collider[] array = componentsInChildren; foreach (Collider val2 in array) { if (!((Object)(object)val2 == (Object)null) && val2.enabled && !val2.isTrigger && !((Object)(object)((Component)val2).GetComponent("Hitmesh") != (Object)null)) { val2.enabled = false; num++; } } if (Plugin.Cfg.LogClientEnemyPuppetMode.Value) { NetLogger.Info($"[EnemyPuppet] flyer disabled {num} physics collider(s) (hitboxes kept) idx={record?.Snapshot?.SpawnIndex} actor={record?.Snapshot?.ActorName}"); } } catch { } } private static void MakePuppetRigidbodyKinematic(EnemyPuppetRecord record, object npc) { try { if (!Plugin.Cfg.MakeClientPuppetsKinematic.Value) { return; } object obj = TryGetMemberValue(npc, "Rigidbody") ?? TryGetMemberValue(npc, "rigidbody"); if (obj == null) { return; } Object val = (Object)((obj is Object) ? obj : null); if (val == null || !(val == (Object)null)) { if (!record.OriginalRigidbodyIsKinematic.HasValue && TryGetBoolMember(obj, "isKinematic", out var value)) { record.OriginalRigidbodyIsKinematic = value; } TrySetBoolMember(obj, "isKinematic", value: true); } } catch { } } private static void RestorePuppetRigidbody(EnemyPuppetRecord record) { try { if (!record.OriginalRigidbodyIsKinematic.HasValue) { return; } object npc = record.Npc; if (npc == null) { return; } Object val = (Object)((npc is Object) ? npc : null); if (val != null && val == (Object)null) { return; } object obj = TryGetMemberValue(npc, "Rigidbody") ?? TryGetMemberValue(npc, "rigidbody"); if (obj != null) { Object val2 = (Object)((obj is Object) ? obj : null); if (val2 == null || !(val2 == (Object)null)) { TrySetBoolMember(obj, "isKinematic", record.OriginalRigidbodyIsKinematic.Value); } } } catch { } } private static bool TryGetVector3Member(object? target, string memberName, out Vector3 value) { //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_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: 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_00c7: 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) value = Vector3.zero; if (target == null) { return false; } try { Object val = (Object)((target is Object) ? target : null); if (val != null && val == (Object)null) { return false; } Type type = target.GetType(); while (type != null) { FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(Vector3)) { value = (Vector3)field.GetValue(target); return IsFinite(value); } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.PropertyType == typeof(Vector3) && property.GetIndexParameters().Length == 0) { value = (Vector3)property.GetValue(target, null); return IsFinite(value); } type = type.BaseType; } } catch { } value = Vector3.zero; return false; } private static bool TrySetVector3Member(object? target, string memberName, Vector3 value) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) if (target == null) { return false; } try { Type type = target.GetType(); while (type != null) { FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(Vector3)) { field.SetValue(target, value); return true; } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.PropertyType == typeof(Vector3) && property.CanWrite && property.GetIndexParameters().Length == 0) { property.SetValue(target, value, null); return true; } type = type.BaseType; } } catch { } return false; } private static string GetSnapshotTargetKey(NetGameplayEntitySnapshot snapshot) { if (!string.IsNullOrWhiteSpace(snapshot.EntityId.LocalInstanceId) && snapshot.EntityId.LocalInstanceId != "null") { return snapshot.EntityId.LocalInstanceId; } if (!string.IsNullOrWhiteSpace(snapshot.EntityId.CandidateKey)) { return snapshot.EntityId.CandidateKey; } if (snapshot.SpawnIndex <= 0) { return ""; } return "spawn:" + snapshot.SpawnIndex; } private static bool TryFindLocalEnemyStateMatch(NetGameplayEnemyStateSnapshot hostSnapshot, out NetGameplayEntitySnapshot? snapshot, out string detail) { snapshot = null; detail = ""; if (ClientHostToLocalKeyByHostSpawnIndex.TryGetValue(hostSnapshot.SpawnIndex, out string value) && EntitiesByLocalId.TryGetValue(value, out NetGameplayEntitySnapshot value2)) { if (!string.IsNullOrWhiteSpace(hostSnapshot.UnitIdentifier) && !string.IsNullOrWhiteSpace(value2.EntityId.UnitIdentifier) && !string.Equals(hostSnapshot.UnitIdentifier, value2.EntityId.UnitIdentifier, StringComparison.Ordinal)) { _stateApplyRejectedTypeMismatch++; _entityTypeMismatchRejected++; detail = "roster-bound but unitId mismatch host=" + hostSnapshot.UnitIdentifier + " client=" + value2.EntityId.UnitIdentifier; Plugin.Log.Info($"[EnemyState] Rejected type mismatch hostUnit={hostSnapshot.UnitIdentifier} clientUnit={value2.EntityId.UnitIdentifier} hostIdx={hostSnapshot.SpawnIndex}"); return false; } snapshot = value2; RefreshRuntimePositionIfAvailable(snapshot); detail = "roster-bound match"; return true; } int num = 0; NetGameplayEntitySnapshot netGameplayEntitySnapshot = null; int num2 = 0; NetGameplayEntitySnapshot netGameplayEntitySnapshot2 = null; int num3 = 0; bool flag = !string.IsNullOrWhiteSpace(hostSnapshot.UnitGlobalId); bool flag2 = !string.IsNullOrWhiteSpace(hostSnapshot.CandidateKey); foreach (NetGameplayEntitySnapshot value3 in EntitiesByLocalId.Values) { if (!string.Equals(value3.Category, hostSnapshot.Category, StringComparison.OrdinalIgnoreCase)) { continue; } num++; if (value3.SpawnIndex == hostSnapshot.SpawnIndex) { if (string.IsNullOrWhiteSpace(hostSnapshot.UnitIdentifier) || string.IsNullOrWhiteSpace(value3.EntityId.UnitIdentifier) || string.Equals(hostSnapshot.UnitIdentifier, value3.EntityId.UnitIdentifier, StringComparison.Ordinal)) { snapshot = value3; RefreshRuntimePositionIfAvailable(snapshot); detail = "spawnIndex match"; return true; } _stateApplyRejectedTypeMismatch++; _entityTypeMismatchRejected++; if (Plugin.Cfg.EnableDebugLog.Value) { Plugin.Log.Info($"[EnemyState] Rejected type mismatch hostUnit={hostSnapshot.UnitIdentifier} clientUnit={value3.EntityId.UnitIdentifier} hostIdx={hostSnapshot.SpawnIndex}"); } } else { if (flag && string.Equals(value3.EntityId.UnitGlobalId, hostSnapshot.UnitGlobalId, StringComparison.Ordinal)) { netGameplayEntitySnapshot = value3; num2++; } if (flag2 && string.Equals(value3.EntityId.CandidateKey, hostSnapshot.CandidateKey, StringComparison.Ordinal)) { netGameplayEntitySnapshot2 = value3; num3++; } } } if (num2 == 1 && netGameplayEntitySnapshot != null) { snapshot = netGameplayEntitySnapshot; RefreshRuntimePositionIfAvailable(snapshot); detail = "unique UnitGlobalId fallback match"; return true; } if (num3 == 1 && netGameplayEntitySnapshot2 != null) { snapshot = netGameplayEntitySnapshot2; RefreshRuntimePositionIfAvailable(snapshot); detail = "unique candidateKey fallback match"; return true; } detail = $"no local entity match candidates={num}"; return false; } private static void RefreshRuntimePositionIfAvailable(NetGameplayEntitySnapshot snapshot) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) try { if (snapshot != null && snapshot.TryGetRuntimeObject(out object runtimeObject) && runtimeObject != null) { Object val = (Object)((runtimeObject is Object) ? runtimeObject : null); if ((val == null || !(val == (Object)null)) && TryGetPosition(runtimeObject, out var position)) { snapshot.HasPosition = true; snapshot.Position = position; } } } catch { } } private static bool CanLogWithCurrentContext(out string reason) { reason = ""; if (!Plugin.Cfg.RequireStableSceneAndSeedForGameplayProbe.Value) { return true; } if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state)) { reason = "no local run state"; return false; } if (!state.HasLevel) { reason = "no chapter/level"; return false; } if (Plugin.Cfg.EnableLevelSeedAuthority.Value && !state.HasLevelSeed) { reason = "level seed unknown"; return false; } return true; } private static MethodInfo? FindNoArgInstanceMethod(Type type, string methodName) { Type type2 = type; while (type2 != null) { try { MethodInfo method = type2.GetMethod(methodName, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method != null) { return method; } } catch { } type2 = type2.BaseType; } return null; } private static bool IsEnabled() { try { return Plugin.Cfg.EnableGameplayEntityProbe.Value; } catch { return false; } } private static bool ShouldTrackCategory(string category) { if (category == "Player") { return false; } if (category == "Breakable") { return false; } return true; } public static List BuildHostWorldRoster() { //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if (!IsEnabled()) { return list; } if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || !state.HasLevel) { return list; } foreach (NetGameplayEntitySnapshot item2 in from s in EntitiesByLocalId.Values where !s.IsDead orderby s.SpawnIndex select s) { if (item2.TryGetRuntimeObject(out object runtimeObject) && runtimeObject != null) { Object val = (Object)((runtimeObject is Object) ? runtimeObject : null); if ((val == null || !(val == (Object)null)) && TryGetPosition(runtimeObject, out var position)) { item2.HasPosition = true; item2.Position = position; } } NetWorldEntityRecord item = new NetWorldEntityRecord { NetEntityId = ComputeNetEntityId(item2, state), SyncCategory = item2.SyncCategory, Category = item2.Category, UnitIdentifier = (item2.EntityId.UnitIdentifier ?? ""), ActorName = (item2.ActorName ?? ""), SpawnIndex = item2.SpawnIndex, HasPosition = item2.HasPosition, Position = item2.Position, SceneRevision = state.Revision, ChapterName = (state.ChapterName ?? ""), LevelIndex = state.LevelIndex, HasLevelSeed = state.HasLevelSeed, LevelSeed = state.LevelSeed }; list.Add(item); } _hostRosterRecordsSent = list.Count; return list; } private static string LocalKeyForObject(object entity) { NetGameplayEntityId netGameplayEntityId = NetGameplayEntityId.FromObject(entity); string text = netGameplayEntityId.LocalInstanceId; if (string.IsNullOrWhiteSpace(text) || text == "null") { text = netGameplayEntityId.CandidateKey; } return text; } public static int GetSpawnIndexForObject(object entity) { try { if (entity == null) { return 0; } if (EntitiesByLocalId.TryGetValue(LocalKeyForObject(entity), out NetGameplayEntitySnapshot value)) { return value.SpawnIndex; } } catch { } return 0; } public static void ProbeRuntimeSpawnBindings() { //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) try { if (!Plugin.Cfg.LogBossDynamicSpawn.Value) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _rtSpawnProbeNext) { return; } if (NetConfig.GetMode() == NetMode.Host) { _rtSpawnProbeNext = realtimeSinceStartup + 1f; { foreach (KeyValuePair item in EntitiesByLocalId) { NetGameplayEntitySnapshot value = item.Value; if (value != null && !value.IsDead && value.SpawnIndex > 0 && (value.ActorName ?? "").IndexOf("Hellshrew", StringComparison.OrdinalIgnoreCase) >= 0 && ((value.ActorName ?? "").IndexOf("DesertClause", StringComparison.OrdinalIgnoreCase) < 0 || (value.ActorName ?? "").IndexOf("Pike", StringComparison.OrdinalIgnoreCase) >= 0) && value.TryGetRuntimeObject(out object runtimeObject)) { Component val = (Component)((runtimeObject is Component) ? runtimeObject : null); if (val != null && !((Object)(object)val == (Object)null)) { string text = (((Object)(object)val.transform.parent != (Object)null) ? ((Object)val.transform.parent).name : ""); NetLogger.Info($"[DesertAdds] HOST idx={value.SpawnIndex} name={value.ActorName} pos={val.transform.position:F1} parent={text}"); } } } return; } } if (NetConfig.GetMode() != NetMode.Client || _runtimeSpawnBindingsByHostIdx.Count == 0) { return; } _rtSpawnProbeNext = realtimeSinceStartup + 1f; foreach (KeyValuePair item2 in _runtimeSpawnBindingsByHostIdx) { int key = item2.Key; string value2 = item2.Value; string text2 = "?"; string text3 = "?"; string text4 = "?"; string text5 = "recv=never"; if (EntitiesByLocalId.TryGetValue(value2, out NetGameplayEntitySnapshot value3) && value3 != null) { text2 = value3.ActorName; if (value3.TryGetRuntimeObject(out object runtimeObject2)) { Component val2 = (Component)((runtimeObject2 is Component) ? runtimeObject2 : null); if (val2 != null && (Object)(object)val2 != (Object)null) { Vector3 position = val2.transform.position; text3 = ((Vector3)(ref position)).ToString("F1"); bool flag = false; bool activeInHierarchy = val2.gameObject.activeInHierarchy; Renderer[] componentsInChildren = val2.GetComponentsInChildren(true); foreach (Renderer val3 in componentsInChildren) { if ((Object)(object)val3 != (Object)null && val3.enabled && ((Component)val3).gameObject.activeInHierarchy) { flag = true; break; } } text4 = $"go={activeInHierarchy} rend={flag}"; goto IL_02f0; } } text3 = "no-runtime-obj"; } else { text3 = "no-entity"; } goto IL_02f0; IL_02f0: if (_clientLastSnapshotRecvByHostIdx.TryGetValue(key, out var value4)) { text5 = $"lastRecv={realtimeSinceStartup - value4:F1}s"; } string value5; string text6 = (ClientHostToLocalKeyByHostSpawnIndex.TryGetValue(key, out value5) ? (string.Equals(value5, value2, StringComparison.Ordinal) ? "bind=live" : "bind=stolen") : "bind=lost"); NetLogger.Info($"[DesertAdds] hostIdx={key} name={text2} pos={text3} {text4} {text5} {text6}"); } } catch { } } public static bool IsRuntimeMirroredEntity(object entity) { try { if (entity == null || _runtimeSpawnBindingsByHostIdx.Count == 0) { return false; } string text = LocalKeyForObject(entity); if (string.IsNullOrWhiteSpace(text)) { return false; } foreach (KeyValuePair item in _runtimeSpawnBindingsByHostIdx) { if (string.Equals(item.Value, text, StringComparison.Ordinal)) { return true; } } } catch { } return false; } public static bool TryGetMirroredRuntimeObject(int hostSpawnIndex, out object? runtimeObject) { runtimeObject = null; try { if (hostSpawnIndex <= 0 || !_runtimeSpawnBindingsByHostIdx.TryGetValue(hostSpawnIndex, out string value)) { return false; } if (!EntitiesByLocalId.TryGetValue(value, out NetGameplayEntitySnapshot value2) || value2 == null) { return false; } if (!value2.TryGetRuntimeObject(out runtimeObject) || runtimeObject == null) { return false; } object? obj = runtimeObject; Object val = (Object)((obj is Object) ? obj : null); if (val != null && val == (Object)null) { runtimeObject = null; return false; } return true; } catch { return false; } } public static bool RegisterMirroredRuntimeSpawn(object entity, int hostSpawnIndex) { try { if (entity == null || hostSpawnIndex <= 0) { return false; } if (NetConfig.GetMode() != NetMode.Client) { return false; } string text = LocalKeyForObject(entity); if (string.IsNullOrWhiteSpace(text)) { return false; } SetClientHostBinding(hostSpawnIndex, text); _runtimeSpawnBindingsByHostIdx[hostSpawnIndex] = text; return true; } catch { return false; } } private static bool IsClientKnownDeadHostIdx(int hostIdx) { if (hostIdx > 0 && Plugin.Cfg.SkipDeadHostIdxRebind.Value) { return _clientTerminalDeadHostIdx.Contains(hostIdx); } return false; } private static void SetClientHostBinding(int hostIdx, string localKey) { if (hostIdx <= 0 || string.IsNullOrWhiteSpace(localKey) || IsClientKnownDeadHostIdx(hostIdx)) { return; } if (Plugin.Cfg.EvictStaleHostBindings.Value) { if (ClientHostToLocalKeyByHostSpawnIndex.TryGetValue(hostIdx, out string value) && !string.IsNullOrWhiteSpace(value) && !string.Equals(value, localKey, StringComparison.Ordinal)) { ClientLocalKeyToHostSpawnIndex.Remove(value); _evictedStaleHostBindings++; } if (ClientLocalKeyToHostSpawnIndex.TryGetValue(localKey, out var value2) && value2 != hostIdx && ClientHostToLocalKeyByHostSpawnIndex.TryGetValue(value2, out string value3) && string.Equals(value3, localKey, StringComparison.Ordinal)) { ClientHostToLocalKeyByHostSpawnIndex.Remove(value2); _evictedStaleHostBindings++; } } ClientHostToLocalKeyByHostSpawnIndex[hostIdx] = localKey; ClientLocalKeyToHostSpawnIndex[localKey] = hostIdx; } private static string ComputeNetEntityId(NetGameplayEntitySnapshot snapshot, NetRunState state) { //IL_001e: 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_004c: Unknown result type (might be due to invalid IL or missing references) try { int num = (state.HasLevelSeed ? state.LevelSeed : 0); if (snapshot.HasPosition) { int num2 = Mathf.RoundToInt(snapshot.Position.x * 4f); int num3 = Mathf.RoundToInt(snapshot.Position.y * 4f); int num4 = Mathf.RoundToInt(snapshot.Position.z * 4f); return $"{state.ChapterName}|{state.LevelIndex}|{num}|{snapshot.EntityId.UnitIdentifier}|{snapshot.SpawnIndex}|{num2}|{num3}|{num4}"; } return $"{state.ChapterName}|{state.LevelIndex}|{num}|{snapshot.EntityId.UnitIdentifier}|{snapshot.SpawnIndex}"; } catch { return $"idx:{snapshot.SpawnIndex}"; } } private static void RecordPendingHostBind(int hostIdx, string? unitId, string? category, bool hasPos, Vector3 pos) { //IL_0065: 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) if (Plugin.Cfg.EnableRetroactiveEnemyBinding.Value && !string.IsNullOrWhiteSpace(unitId) && !ClientHostToLocalKeyByHostSpawnIndex.ContainsKey(hostIdx)) { _pendingHostBindLedger[hostIdx] = new PendingHostBind { HostSpawnIndex = hostIdx, UnitIdentifier = unitId, Category = (category ?? ""), HasPosition = hasPos, Position = pos, RecordedAt = Time.realtimeSinceStartup }; _retroBindLedgerAdds++; } } private static void PruneBoundPendingHostBinds() { if (_pendingHostBindLedger.Count == 0) { return; } List list = null; foreach (KeyValuePair item in _pendingHostBindLedger) { if (ClientHostToLocalKeyByHostSpawnIndex.ContainsKey(item.Key)) { (list ?? (list = new List())).Add(item.Key); } } if (list == null) { return; } foreach (int item2 in list) { _pendingHostBindLedger.Remove(item2); } } private static void ExpireStalePendingHostBinds() { if (_pendingHostBindLedger.Count == 0) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; List list = null; foreach (KeyValuePair item in _pendingHostBindLedger) { if (realtimeSinceStartup - item.Value.RecordedAt > 30f) { (list ?? (list = new List())).Add(item.Key); } } if (list == null) { return; } foreach (int item2 in list) { _pendingHostBindLedger.Remove(item2); } _retroBindLedgerExpired += list.Count; } private static void TryRetroactiveBindNewLocalEntity(NetGameplayEntitySnapshot snap) { //IL_00fc: 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) if (!Plugin.Cfg.EnableRetroactiveEnemyBinding.Value || _pendingHostBindLedger.Count == 0 || snap == null || snap.IsDead || IsPlayerEntitySnapshot(snap)) { return; } string text = snap.EntityId.UnitIdentifier ?? ""; if (string.IsNullOrWhiteSpace(text)) { return; } string snapshotTargetKey = GetSnapshotTargetKey(snap); if (string.IsNullOrWhiteSpace(snapshotTargetKey) || ClientLocalKeyToHostSpawnIndex.ContainsKey(snapshotTargetKey)) { return; } int num = -1; float num2 = float.MaxValue; int num3 = 0; int num4 = 0; foreach (KeyValuePair item in _pendingHostBindLedger) { PendingHostBind value = item.Value; if (!string.Equals(value.UnitIdentifier, text, StringComparison.Ordinal) || IsClientKnownDeadHostIdx(value.HostSpawnIndex)) { continue; } num4++; if (!ClientHostToLocalKeyByHostSpawnIndex.ContainsKey(value.HostSpawnIndex)) { num3++; float num5 = ((value.HasPosition && snap.HasPosition) ? Vector3.Distance(value.Position, snap.Position) : 0f); if (num5 < num2) { num2 = num5; num = value.HostSpawnIndex; } } } if (num < 0) { if (num4 > 0) { MaybeLogRetroBindDiag(snap, text, "no-unbound-slot", num4, num3, float.NaN); } return; } if (num3 > 1 && snap.HasPosition && num2 > 5f) { _retroBindAmbiguousDeferred++; MaybeLogRetroBindDiag(snap, text, "ambiguous-pos-defer", num4, num3, num2); return; } SetClientHostBinding(num, snapshotTargetKey); ClientQuarantinedEntities.Remove(snapshotTargetKey); _pendingHostBindLedger.Remove(num); _retroBindSuccess++; TryApplyPendingHealthState(num, snapshotTargetKey); if (Plugin.Cfg.LogLevelManifestDiff.Value) { Plugin.Log.Info($"[RetroBind] bound late-spawned localUnit={text} localIdx={snap.SpawnIndex} → hostIdx={num} dist={num2:F1}m (ledger now {_pendingHostBindLedger.Count}, retroBound={_retroBindSuccess})"); } } private static void MaybeLogRetroBindDiag(NetGameplayEntitySnapshot snap, string unitId, string reason, int sameUnitInLedger, int unboundMatches, float bestDist) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.Cfg.LogEnemyInterestDiag.Value || _retroBindDiagLogged >= 60) { return; } _retroBindDiagLogged++; string text = ""; int num = 0; foreach (KeyValuePair item in _pendingHostBindLedger) { PendingHostBind value = item.Value; if (string.Equals(value.UnitIdentifier, unitId, StringComparison.Ordinal)) { bool flag = ClientHostToLocalKeyByHostSpawnIndex.ContainsKey(value.HostSpawnIndex); float num2 = ((value.HasPosition && snap.HasPosition) ? Vector3.Distance(value.Position, snap.Position) : (-1f)); text += string.Format(" [hostIdx={0} pos={1} dist={2:F1} bound={3}]", value.HostSpawnIndex, ((Vector3)(ref value.Position)).ToString("F0"), num2, flag); if (++num >= 6) { break; } } } string text2 = (float.IsNaN(bestDist) ? "n/a" : bestDist.ToString("F1")); object[] obj = new object[8] { reason, unitId, snap.SpawnIndex, null, null, null, null, null }; object obj2; if (!snap.HasPosition) { obj2 = "?"; } else { Vector3 position = snap.Position; obj2 = ((Vector3)(ref position)).ToString("F0"); } obj[3] = obj2; obj[4] = sameUnitInLedger; obj[5] = unboundMatches; obj[6] = text2; obj[7] = text; NetLogger.Info(string.Format("[RetroBindDiag] reason={0} localUnit={1} localIdx={2} localPos={3} sameUnitParked={4} unboundMatches={5} bestDist={6}m parked:{7}", obj)); } public static void ProcessHostWorldRoster(List records, int hostRevision) { //IL_0571: Unknown result type (might be due to invalid IL or missing references) //IL_03ef: Unknown result type (might be due to invalid IL or missing references) //IL_03f6: Unknown result type (might be due to invalid IL or missing references) //IL_037e: Unknown result type (might be due to invalid IL or missing references) //IL_0385: Unknown result type (might be due to invalid IL or missing references) if (records == null || records.Count == 0 || NetConfig.GetMode() != NetMode.Client) { return; } _clientRosterRecordsReceived += records.Count; if (hostRevision == _clientRosterRevision) { if (Plugin.Cfg.EnableDebugLog.Value) { Plugin.Log.Info($"[WorldRoster] Client skipping duplicate roster revision={hostRevision}"); } return; } _clientRosterRevision = hostRevision; Dictionary dictionary = new Dictionary(ClientHostToLocalKeyByHostSpawnIndex); ClientHostToLocalKeyByHostSpawnIndex.Clear(); ClientLocalKeyToHostSpawnIndex.Clear(); ClientQuarantinedEntities.Clear(); _clientRosterBound = 0; _clientRosterHostOnlyMissing = 0; _clientRosterClientOnlyQuarantined = 0; _clientRosterTypeMismatch = 0; _clientRosterFingerprintMismatch = 0; _rosterOneToOneBound = 0; _rosterPreservedBound = 0; _clientOnlyCombatQuarantined = 0; Plugin.Log.Info($"[WorldRoster] Client received Host roster count={records.Count} revision={hostRevision}"); HashSet hashSet = new HashSet(); int num = 0; foreach (KeyValuePair item in _runtimeSpawnBindingsByHostIdx) { ClientHostToLocalKeyByHostSpawnIndex[item.Key] = item.Value; ClientLocalKeyToHostSpawnIndex[item.Value] = item.Key; hashSet.Add(item.Value); num++; } if (num > 0) { Plugin.Log.Info($"[WorldRoster] re-asserted {num} RT3 runtime bindings across reconcile"); } foreach (NetWorldEntityRecord record in records) { if (IsClientKnownDeadHostIdx(record.SpawnIndex)) { continue; } if (Plugin.Cfg.StableWorldRosterBinding.Value && dictionary.TryGetValue(record.SpawnIndex, out var value) && !string.IsNullOrWhiteSpace(value) && !hashSet.Contains(value) && EntitiesByLocalId.TryGetValue(value, out NetGameplayEntitySnapshot value2) && !value2.IsDead && (string.IsNullOrWhiteSpace(record.UnitIdentifier) || string.IsNullOrWhiteSpace(value2.EntityId.UnitIdentifier) || string.Equals(record.UnitIdentifier, value2.EntityId.UnitIdentifier, StringComparison.Ordinal))) { ClientHostToLocalKeyByHostSpawnIndex[record.SpawnIndex] = value; ClientLocalKeyToHostSpawnIndex[value] = record.SpawnIndex; hashSet.Add(value); _clientRosterBound++; _rosterOneToOneBound++; _rosterPreservedBound++; TryApplyPendingHealthState(record.SpawnIndex, value); continue; } NetGameplayEntitySnapshot netGameplayEntitySnapshot = null; float num2 = float.MaxValue; string text = ""; bool flag = !string.IsNullOrWhiteSpace(record.UnitIdentifier); foreach (NetGameplayEntitySnapshot value3 in EntitiesByLocalId.Values) { if (value3.IsDead || !string.Equals(value3.Category, record.Category, StringComparison.OrdinalIgnoreCase)) { continue; } string snapshotTargetKey = GetSnapshotTargetKey(value3); if (hashSet.Contains(snapshotTargetKey)) { continue; } bool flag2 = !string.IsNullOrWhiteSpace(value3.EntityId.UnitIdentifier); if (flag && flag2) { if (!string.Equals(record.UnitIdentifier, value3.EntityId.UnitIdentifier, StringComparison.Ordinal)) { _clientRosterTypeMismatch++; } else if (record.HasPosition && value3.HasPosition) { float num3 = Vector3.Distance(record.Position, value3.Position); if (num3 <= 5f && num3 < num2) { netGameplayEntitySnapshot = value3; num2 = num3; text = $"unitId+position dist={num3:F1}m"; } } else if (netGameplayEntitySnapshot == null) { netGameplayEntitySnapshot = value3; num2 = 0f; text = "unitId (no position)"; } } else if (record.HasPosition && value3.HasPosition) { float num4 = Vector3.Distance(record.Position, value3.Position); if (num4 < 2f && num4 < num2) { netGameplayEntitySnapshot = value3; num2 = num4; text = $"position-only dist={num4:F1}m"; _clientRosterFingerprintMismatch++; } } } if (netGameplayEntitySnapshot != null) { string snapshotTargetKey2 = GetSnapshotTargetKey(netGameplayEntitySnapshot); if (!string.IsNullOrWhiteSpace(snapshotTargetKey2)) { ClientHostToLocalKeyByHostSpawnIndex[record.SpawnIndex] = snapshotTargetKey2; ClientLocalKeyToHostSpawnIndex[snapshotTargetKey2] = record.SpawnIndex; hashSet.Add(snapshotTargetKey2); _clientRosterBound++; _rosterOneToOneBound++; if (netGameplayEntitySnapshot.SyncCategory == 0 && record.SyncCategory != 0) { netGameplayEntitySnapshot.SyncCategory = record.SyncCategory; } Plugin.Log.Info($"[WorldRoster] Bound hostIdx={record.SpawnIndex} hostUnit={record.UnitIdentifier} hostActor={record.ActorName} → localIdx={netGameplayEntitySnapshot.SpawnIndex} localUnit={netGameplayEntitySnapshot.EntityId.UnitIdentifier} reason={text}"); TryApplyPendingHealthState(record.SpawnIndex, snapshotTargetKey2); } } else { _clientRosterHostOnlyMissing++; RecordPendingHostBind(record.SpawnIndex, record.UnitIdentifier, record.Category, record.HasPosition, record.Position); Plugin.Log.Info($"[WorldRoster] No local match for hostIdx={record.SpawnIndex} hostUnit={record.UnitIdentifier} hostActor={record.ActorName} hostCat={record.Category}"); } } foreach (NetGameplayEntitySnapshot value4 in EntitiesByLocalId.Values) { if (value4.IsDead) { continue; } if (IsPlayerEntitySnapshot(value4)) { _quarantinePlayerSkipped++; continue; } string snapshotTargetKey3 = GetSnapshotTargetKey(value4); if (!string.IsNullOrWhiteSpace(snapshotTargetKey3) && !hashSet.Contains(snapshotTargetKey3)) { _clientRosterClientOnlyQuarantined++; if (IsCombatEnemyForSync(value4)) { ClientQuarantinedEntities.Add(snapshotTargetKey3); _clientOnlyCombatQuarantined++; Plugin.Log.Info($"[WorldRoster] Quarantine client-only CombatEnemy localIdx={value4.SpawnIndex} unit={value4.EntityId.UnitIdentifier} actor={value4.ActorName}"); continue; } Plugin.Log.Info($"[WorldRoster] Client-only non-combat localIdx={value4.SpawnIndex} unit={value4.EntityId.UnitIdentifier} actor={value4.ActorName} cat={value4.Category} syncCat={SyncCatName(value4.SyncCategory)}"); } } PruneBoundPendingHostBinds(); Plugin.Log.Info($"[WorldRoster] Client reconciliation complete bound={_clientRosterBound} (1:1={_rosterOneToOneBound} preserved={_rosterPreservedBound}) hostOnly={_clientRosterHostOnlyMissing} clientOnly={_clientRosterClientOnlyQuarantined} quarantined={_clientOnlyCombatQuarantined} typeMismatch={_clientRosterTypeMismatch} retroLedger={_pendingHostBindLedger.Count} retroBound={_retroBindSuccess}"); } public static NetLevelManifest? BuildLevelManifest(string role) { //IL_0314: Unknown result type (might be due to invalid IL or missing references) //IL_0319: 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_0218: 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_02ea: Unknown result type (might be due to invalid IL or missing references) //IL_03b1: Unknown result type (might be due to invalid IL or missing references) if (!IsEnabled()) { return null; } if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || !state.HasLevel) { return null; } NetLevelManifest netLevelManifest = new NetLevelManifest(); NetLevelManifestHeader header = netLevelManifest.Header; header.ManifestVersion = 1; header.Role = role; header.SceneName = state.ChapterName ?? ""; header.LevelIndex = state.LevelIndex; header.HasLevelSeed = state.HasLevelSeed; header.LevelSeed = (state.HasLevelSeed ? state.LevelSeed : 0); header.GenerationRevision = state.Revision; header.BuiltAt = Time.realtimeSinceStartup; int num = 0; foreach (NetGameplayEntitySnapshot item2 in EntitiesByLocalId.Values.OrderBy((NetGameplayEntitySnapshot s) => s.SpawnIndex)) { if (IsPlayerEntitySnapshot(item2)) { _manifestPlayerExcluded++; continue; } object runtimeObject = null; if (item2.TryGetRuntimeObject(out runtimeObject) && runtimeObject != null) { Object val = (Object)((runtimeObject is Object) ? runtimeObject : null); if ((val == null || !(val == (Object)null)) && TryGetPosition(runtimeObject, out var position)) { item2.HasPosition = true; item2.Position = position; } } string text = TryGetGameObjectName(runtimeObject); string modifierFlags = ExtractModifierFlags(text, item2.ActorName, item2.EntityId.UnitIdentifier); uint componentFingerprint = ComputeComponentFingerprint(runtimeObject); bool isCombatEnemy = IsCombatEnemyForSync(item2); NetLevelManifestUnit item = new NetLevelManifestUnit { ManifestIndex = num++, SpawnIndex = item2.SpawnIndex, UnitIdentifier = (item2.EntityId.UnitIdentifier ?? ""), ActorName = (item2.ActorName ?? ""), GameObjectName = text, SyncCategory = item2.SyncCategory, Category = (item2.Category ?? ""), IsCombatEnemy = isCombatEnemy, HasPosition = item2.HasPosition, Position = item2.Position, HasInitialPosition = item2.HasInitialPosition, InitialPosition = item2.InitialPosition, ModifierFlags = modifierFlags, ComponentFingerprint = componentFingerprint, IsDead = item2.IsDead }; netLevelManifest.Units.Add(item); if (item2.SyncCategory == 2 || item2.SyncCategory == 4 || item2.SyncCategory == 5 || item2.SyncCategory == 3) { netLevelManifest.Specials.Add(new NetLevelManifestSpecial { Type = SyncCatName(item2.SyncCategory), Name = ((!string.IsNullOrWhiteSpace(item2.EntityId.UnitIdentifier)) ? item2.EntityId.UnitIdentifier : (item2.ActorName ?? "")), SyncCategory = item2.SyncCategory, HasPosition = item2.HasPosition, Position = item2.Position }); } } try { Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).IsValid() && ((Scene)(ref activeScene)).isLoaded) { int num2 = 0; GameObject[] rootGameObjects = ((Scene)(ref activeScene)).GetRootGameObjects(); foreach (GameObject val2 in rootGameObjects) { if ((Object)(object)val2 == (Object)null) { continue; } string text2 = ((Object)val2).name ?? ""; if (text2.IndexOf("room", StringComparison.OrdinalIgnoreCase) >= 0) { netLevelManifest.Rooms.Add(new NetLevelManifestRoom { RoomIndex = num2++, RoomName = Clean(text2), HasPosition = true, Position = val2.transform.position, ChildCount = val2.transform.childCount }); if (netLevelManifest.Rooms.Count >= 128) { break; } } } netLevelManifest.Rooms = netLevelManifest.Rooms.OrderBy((NetLevelManifestRoom rm) => rm.RoomName, StringComparer.Ordinal).ToList(); for (int num4 = 0; num4 < netLevelManifest.Rooms.Count; num4++) { netLevelManifest.Rooms[num4].RoomIndex = num4; } } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[LevelManifest] room scan failed: " + ex.Message); } } header.RoomCount = netLevelManifest.Rooms.Count; header.UnitCount = netLevelManifest.Units.Count; header.CombatEnemyCount = netLevelManifest.Units.Count((NetLevelManifestUnit x) => x.IsCombatEnemy); header.SpecialEventCount = netLevelManifest.Specials.Count; header.GenerationHash = ComputeGenerationHash(netLevelManifest); header.RuntimeHash = ComputeRuntimeHash(netLevelManifest); if (role == "Host") { DiagnoseGenerationHashStability(netLevelManifest); } if (role == "Host") { _hostManifestBuilt++; _hostManifestSent++; } else { _clientManifestBuilt++; } if (Plugin.Cfg.LogLevelManifest.Value) { NetLogger.Info(string.Format("[LevelManifest] {0} built manifest scene={1} level={2} seed={3} rooms={4} units={5} combat={6} specials={7} genHash={8} runtimeHash={9}", role, header.SceneName, header.LevelIndex, header.HasLevelSeed ? header.LevelSeed.ToString() : "?", header.RoomCount, header.UnitCount, header.CombatEnemyCount, header.SpecialEventCount, header.GenerationHash, header.RuntimeHash)); } return netLevelManifest; } public static void ProcessHostLevelManifest(NetLevelManifest hostManifest) { if (hostManifest != null && NetConfig.GetMode() == NetMode.Client) { _clientManifestReceived++; if (TryGateHostManifest(hostManifest, "recv")) { RunManifestDiffAndReconcile(hostManifest); } } } private static bool TryGateHostManifest(NetLevelManifest m, string source) { NetLevelManifestHeader header = m.Header; if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || !state.HasLevel) { DeferManifest(m, "no-local-run-state", source); return false; } if (!string.Equals(state.ChapterName ?? "", header.SceneName ?? "", StringComparison.Ordinal)) { _manifestDeferredSceneMismatch++; DeferManifest(m, "scene host=" + header.SceneName + " client=" + state.ChapterName, source); return false; } if (state.LevelIndex != header.LevelIndex) { _manifestDeferredLevelMismatch++; _manifestDeferredLevelMismatchAfterFix++; DeferManifest(m, $"level host={header.LevelIndex} client={state.LevelIndex}", source); return false; } if (header.HasLevelSeed != state.HasLevelSeed || (header.HasLevelSeed && state.HasLevelSeed && state.LevelSeed != header.LevelSeed)) { _manifestDeferredSeedMismatch++; DeferManifest(m, "seed host=" + (header.HasLevelSeed ? header.LevelSeed.ToString() : "?") + " client=" + (state.HasLevelSeed ? state.LevelSeed.ToString() : "?"), source); return false; } _manifestProcessedMatchingRun++; if (source == "deferred") { _manifestAcceptedAfterRunStateFix++; } NetLogger.Info(string.Format("[ManifestGate] accepted matching run chapter={0} level={1} graph={2} seed={3} source={4}", state.ChapterName, header.LevelIndex, string.IsNullOrEmpty(state.LevelGenerator) ? "?" : state.LevelGenerator, header.HasLevelSeed ? header.LevelSeed.ToString() : "?", source)); return true; } private static void DeferManifest(NetLevelManifest m, string reason, string source) { if (_deferredHostManifest != null && _deferredHostManifest != m) { _manifestDroppedStaleRun++; } _deferredHostManifest = m; if (Plugin.Cfg.LogLevelManifest.Value) { NetLogger.Info("[LevelManifest] DEFER manifest (run mismatch) source=" + source + " reason=" + Clean(reason) + " — not reconciled"); } } private static void TryProcessDeferredManifest() { if (_deferredHostManifest == null) { return; } NetLevelManifest deferredHostManifest = _deferredHostManifest; if (TryGateHostManifest(deferredHostManifest, "deferred")) { _deferredHostManifest = null; if (Plugin.Cfg.LogLevelManifest.Value) { NetLogger.Info("[LevelManifest] Deferred manifest now matches local run — processing"); } RunManifestDiffAndReconcile(deferredHostManifest); } } private static void RunManifestDiffAndReconcile(NetLevelManifest hostManifest) { float num = ((_clientFirstLevelEntitySeenAt > 0f) ? (Time.realtimeSinceStartup - _clientFirstLevelEntitySeenAt) : (-1f)); NetLogger.Info("[LevelManifest] Host manifest accepted (run match) elapsed=" + ((num >= 0f) ? (num.ToString("F2") + "s") : "n/a")); NetLevelManifest netLevelManifest = BuildLevelManifest("Client"); if (netLevelManifest == null) { NetLogger.Warn("[LevelManifest] Client could not build local manifest (no level state) — cannot reconcile yet"); return; } DiffManifests(hostManifest, netLevelManifest); ReconcileHostManifest(hostManifest, netLevelManifest); } private static void DiffManifests(NetLevelManifest host, NetLevelManifest client) { NetLevelManifestHeader header = host.Header; NetLevelManifestHeader header2 = client.Header; bool flag = header.GenerationHash == header2.GenerationHash; bool flag2 = header.RuntimeHash == header2.RuntimeHash; if (flag) { _generationHashMatch++; } else { _generationHashMismatch++; } if (flag2) { _runtimeHashMatch++; } else { _runtimeHashMismatch++; } if (flag) { _manifestHashMatch++; } else { _manifestHashMismatch++; } NetLogger.Info($"[LevelManifestDiff] generationHash host={header.GenerationHash} client={header2.GenerationHash} match={flag}"); NetLogger.Info($"[LevelManifestDiff] runtimeHash host={header.RuntimeHash} client={header2.RuntimeHash} match={flag2} (runtime divergence is expected)"); bool flag3 = header.HasLevelSeed && header2.HasLevelSeed && header.LevelSeed == header2.LevelSeed; if (!flag3) { _manifestSeedMismatch++; NetLogger.Info(string.Format("[LevelManifestDiff] seed mismatch host={0} client={1} scene={2} level={3}", header.HasLevelSeed ? header.LevelSeed.ToString() : "?", header2.HasLevelSeed ? header2.LevelSeed.ToString() : "?", header.SceneName, header.LevelIndex)); } else if (Plugin.Cfg.LogLevelManifestDiff.Value) { NetLogger.Info($"[LevelManifestDiff] seed host={header.LevelSeed} client={header2.LevelSeed} (match)"); } int num = 0; int num2 = Math.Max(host.Rooms.Count, client.Rooms.Count); for (int i = 0; i < num2; i++) { NetLevelManifestRoom netLevelManifestRoom = ((i < host.Rooms.Count) ? host.Rooms[i] : null); NetLevelManifestRoom netLevelManifestRoom2 = ((i < client.Rooms.Count) ? client.Rooms[i] : null); if (netLevelManifestRoom == null || netLevelManifestRoom2 == null || !string.Equals(netLevelManifestRoom.RoomName, netLevelManifestRoom2.RoomName, StringComparison.Ordinal)) { num++; if (Plugin.Cfg.LogLevelManifestDiff.Value && num <= 12) { NetLogger.Info(string.Format("[LevelManifestDiff] room mismatch idx={0} host={1} client={2}", i, netLevelManifestRoom?.RoomName ?? "", netLevelManifestRoom2?.RoomName ?? "")); } } } if (num > 0) { _manifestRoomMismatch += num; } NetLogger.Info($"[LevelManifestDiff] rooms host={host.Rooms.Count} client={client.Rooms.Count} mismatch={num}"); int num3 = Math.Max(host.Units.Count, client.Units.Count); int num4 = 0; for (int j = 0; j < num3; j++) { NetLevelManifestUnit netLevelManifestUnit = ((j < host.Units.Count) ? host.Units[j] : null); NetLevelManifestUnit netLevelManifestUnit2 = ((j < client.Units.Count) ? client.Units[j] : null); if (netLevelManifestUnit == null || netLevelManifestUnit2 == null || !string.Equals(netLevelManifestUnit.UnitIdentifier, netLevelManifestUnit2.UnitIdentifier, StringComparison.Ordinal)) { num4++; if (Plugin.Cfg.LogLevelManifestDiff.Value && num4 <= 16) { NetLogger.Info(string.Format("[LevelManifestDiff] unit mismatch idx={0} host={1}{2} client={3}{4}", j, netLevelManifestUnit?.UnitIdentifier ?? "", PosTag(netLevelManifestUnit), netLevelManifestUnit2?.UnitIdentifier ?? "", PosTag(netLevelManifestUnit2))); } } else if (netLevelManifestUnit != null && netLevelManifestUnit2 != null && !string.Equals(netLevelManifestUnit.ModifierFlags ?? "", netLevelManifestUnit2.ModifierFlags ?? "", StringComparison.Ordinal)) { _manifestHostEnemyModifierMismatch++; if (Plugin.Cfg.LogLevelManifestDiff.Value) { NetLogger.Info(string.Format("[LevelManifestDiff] modifier mismatch idx={0} unit={1} host={2} client={3}", j, netLevelManifestUnit.UnitIdentifier, string.IsNullOrEmpty(netLevelManifestUnit.ModifierFlags) ? "None" : netLevelManifestUnit.ModifierFlags, string.IsNullOrEmpty(netLevelManifestUnit2.ModifierFlags) ? "None" : netLevelManifestUnit2.ModifierFlags)); } } } if (num4 > 0) { _manifestUnitMismatch += num4; } Dictionary d = new Dictionary(); foreach (NetLevelManifestUnit unit in client.Units) { Increment(d, UnitSignature(unit)); } Dictionary d2 = new Dictionary(); foreach (NetLevelManifestUnit unit2 in host.Units) { Increment(d2, UnitSignature(unit2)); } int num5 = 0; int num6 = 0; foreach (NetLevelManifestUnit unit3 in host.Units) { string k = UnitSignature(unit3); if (GetCount(d, k) > 0) { Decrement(d, k); continue; } num5++; if (Plugin.Cfg.LogLevelManifestDiff.Value && num5 <= 16) { NetLogger.Info($"[LevelManifestDiff] hostOnly unit idx={unit3.ManifestIndex} unit={unit3.UnitIdentifier}{ModTag(unit3)}{PosTag(unit3)}"); } } foreach (NetLevelManifestUnit unit4 in client.Units) { string k2 = UnitSignature(unit4); if (GetCount(d2, k2) > 0) { Decrement(d2, k2); continue; } num6++; if (Plugin.Cfg.LogLevelManifestDiff.Value && num6 <= 16) { NetLogger.Info($"[LevelManifestDiff] clientOnly unit idx={unit4.ManifestIndex} unit={unit4.UnitIdentifier}{ModTag(unit4)}{PosTag(unit4)}"); } } _manifestHostOnlyUnits += num5; _manifestClientOnlyUnits += num6; NetLogger.Info($"[LevelManifestDiff] units host={host.Units.Count} client={client.Units.Count} mismatch={num4} hostOnly={num5} clientOnly={num6}"); int num7 = 0; int num8 = Math.Max(host.Specials.Count, client.Specials.Count); for (int l = 0; l < num8; l++) { NetLevelManifestSpecial netLevelManifestSpecial = ((l < host.Specials.Count) ? host.Specials[l] : null); NetLevelManifestSpecial netLevelManifestSpecial2 = ((l < client.Specials.Count) ? client.Specials[l] : null); if (netLevelManifestSpecial == null || netLevelManifestSpecial2 == null || !string.Equals(netLevelManifestSpecial.Name, netLevelManifestSpecial2.Name, StringComparison.Ordinal)) { num7++; if (Plugin.Cfg.LogLevelManifestDiff.Value && num7 <= 12) { NetLogger.Info(string.Format("[LevelManifestDiff] special mismatch idx={0} host={1} client={2}", l, netLevelManifestSpecial?.Name ?? "", netLevelManifestSpecial2?.Name ?? "")); } } } if (num7 > 0) { _manifestSpecialMismatch += num7; } NetLogger.Info($"[LevelManifestDiff] specials host={host.Specials.Count} client={client.Specials.Count} mismatch={num7}"); string arg = "none"; string arg2 = ""; int num9 = -1; if (!flag3) { arg = "Seed"; arg2 = "host=" + (header.HasLevelSeed ? header.LevelSeed.ToString() : "?") + " client=" + (header2.HasLevelSeed ? header2.LevelSeed.ToString() : "?"); } else if (num > 0) { arg = "Room"; for (int m = 0; m < num2; m++) { NetLevelManifestRoom netLevelManifestRoom3 = ((m < host.Rooms.Count) ? host.Rooms[m] : null); NetLevelManifestRoom netLevelManifestRoom4 = ((m < client.Rooms.Count) ? client.Rooms[m] : null); if (netLevelManifestRoom3 == null || netLevelManifestRoom4 == null || !string.Equals(netLevelManifestRoom3.RoomName, netLevelManifestRoom4.RoomName, StringComparison.Ordinal)) { num9 = m; arg2 = "host=" + (netLevelManifestRoom3?.RoomName ?? "") + " client=" + (netLevelManifestRoom4?.RoomName ?? ""); break; } } } else if (num4 > 0) { arg = "Unit"; for (int n = 0; n < num3; n++) { NetLevelManifestUnit netLevelManifestUnit3 = ((n < host.Units.Count) ? host.Units[n] : null); NetLevelManifestUnit netLevelManifestUnit4 = ((n < client.Units.Count) ? client.Units[n] : null); if (netLevelManifestUnit3 == null || netLevelManifestUnit4 == null || !string.Equals(netLevelManifestUnit3.UnitIdentifier, netLevelManifestUnit4.UnitIdentifier, StringComparison.Ordinal)) { num9 = n; arg2 = "host=" + (netLevelManifestUnit3?.UnitIdentifier ?? "") + " client=" + (netLevelManifestUnit4?.UnitIdentifier ?? ""); break; } } } else if (num7 > 0) { arg = "Special"; arg2 = "see special mismatch lines above"; } else if (_manifestHostEnemyModifierMismatch > 0) { arg = "Modifier"; arg2 = "see modifier mismatch lines above"; } NetLogger.Info($"[LevelGenDiff] firstDivergence category={arg} idx={num9} reason={arg2}"); } private static void ReconcileHostManifest(NetLevelManifest host, NetLevelManifest client) { //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Unknown result type (might be due to invalid IL or missing references) int num = 0; int num2 = 0; int num3 = 0; Dictionary> dictionary = new Dictionary>(); foreach (NetGameplayEntitySnapshot value3 in EntitiesByLocalId.Values) { if (value3.IsDead) { continue; } string text = value3.EntityId.UnitIdentifier ?? ""; if (!string.IsNullOrWhiteSpace(text)) { if (!dictionary.TryGetValue(text, out var value)) { value = (dictionary[text] = new List()); } value.Add(value3); } } HashSet hashSet = new HashSet(); foreach (NetLevelManifestUnit unit in host.Units) { if (!unit.IsCombatEnemy || string.IsNullOrWhiteSpace(unit.UnitIdentifier) || IsClientKnownDeadHostIdx(unit.SpawnIndex)) { continue; } if (!dictionary.TryGetValue(unit.UnitIdentifier, out var value2) || value2.Count == 0) { _manifestHostEnemyBindFailedNoCandidate++; num3++; RecordPendingHostBind(unit.SpawnIndex, unit.UnitIdentifier, unit.Category, unit.HasPosition, unit.Position); if (Plugin.Cfg.LogLevelManifestDiff.Value) { NetLogger.Info("[LevelManifest] hostOnly enemy unit=" + unit.UnitIdentifier + ModTag(unit) + PosTag(unit) + " — no local candidate (parked for retro-bind)"); } continue; } NetGameplayEntitySnapshot netGameplayEntitySnapshot = null; float num4 = float.MaxValue; int num5 = 0; foreach (NetGameplayEntitySnapshot item in value2) { string snapshotTargetKey = GetSnapshotTargetKey(item); if (!string.IsNullOrWhiteSpace(snapshotTargetKey) && !hashSet.Contains(snapshotTargetKey)) { num5++; float num6 = ((unit.HasPosition && item.HasPosition) ? Vector3.Distance(unit.Position, item.Position) : 0f); if (num6 < num4) { num4 = num6; netGameplayEntitySnapshot = item; } } } if (netGameplayEntitySnapshot == null) { _manifestHostEnemyBindFailedNoCandidate++; } else if (num5 > 1 && unit.HasPosition && netGameplayEntitySnapshot.HasPosition && num4 > 8f) { _manifestHostEnemyBindFailedAmbiguous++; if (Plugin.Cfg.LogLevelManifestDiff.Value) { NetLogger.Info($"[LevelManifest] bind ambiguous hostUnit={unit.UnitIdentifier} candidates={num5} closest={num4:F1}m"); } } else { string snapshotTargetKey2 = GetSnapshotTargetKey(netGameplayEntitySnapshot); SetClientHostBinding(unit.SpawnIndex, snapshotTargetKey2); hashSet.Add(snapshotTargetKey2); ClientQuarantinedEntities.Remove(snapshotTargetKey2); _manifestHostEnemyBoundExisting++; num++; TryApplyPendingHealthState(unit.SpawnIndex, snapshotTargetKey2); } } _currentClientOnlyUnits = 0; _currentClientOnlyCombatUnits = 0; _currentClientOnlyNonCombatUnits = 0; _currentClientOnlyQuarantineApplied = 0; _currentClientOnlyAlreadyQuarantined = 0; _currentClientOnlyNoRuntime = 0; foreach (NetGameplayEntitySnapshot value4 in EntitiesByLocalId.Values) { if (value4.IsDead) { continue; } if (IsPlayerEntitySnapshot(value4)) { _quarantinePlayerSkipped++; continue; } string snapshotTargetKey3 = GetSnapshotTargetKey(value4); if (string.IsNullOrWhiteSpace(snapshotTargetKey3)) { _manifestClientOnlyQuarantineNoLocalKey++; } else { if (hashSet.Contains(snapshotTargetKey3) || ClientLocalKeyToHostSpawnIndex.ContainsKey(snapshotTargetKey3)) { continue; } _currentClientOnlyUnits++; if (!IsCombatEnemyForSync(value4)) { _manifestClientOnlyNonCombatSkipped++; _currentClientOnlyNonCombatUnits++; continue; } _currentClientOnlyCombatUnits++; if (ClientQuarantinedEntities.Contains(snapshotTargetKey3)) { _manifestClientOnlyAlreadyQuarantined++; _currentClientOnlyAlreadyQuarantined++; continue; } if (value4.TryGetRuntimeObject(out object runtimeObject) && runtimeObject != null) { Object val = (Object)((runtimeObject is Object) ? runtimeObject : null); if (val == null || !(val == (Object)null)) { ClientQuarantinedEntities.Add(snapshotTargetKey3); _manifestClientOnlyCombatQuarantined++; num2++; if (TryQuarantineClientOnlyEnemy(value4, snapshotTargetKey3)) { _manifestClientOnlyQuarantineApplied++; _totalClientOnlyQuarantineApplied++; _currentClientOnlyQuarantineApplied++; } else { _manifestClientOnlyQuarantineFailed++; } continue; } } _manifestClientOnlyQuarantineNoRuntime++; _currentClientOnlyNoRuntime++; if (Plugin.Cfg.LogLevelManifestDiff.Value) { NetLogger.Info($"[LevelManifest] client-only enemy idx={value4.SpawnIndex} unit={value4.EntityId.UnitIdentifier} — runtime object gone, cannot quarantine"); } } } PruneBoundPendingHostBinds(); NetLogger.Info($"[LevelManifest] Reconcile complete bound={num} hostOnly={num3} currentClientOnly={_currentClientOnlyUnits} (combat={_currentClientOnlyCombatUnits} nonCombat={_currentClientOnlyNonCombatUnits}) quarantineAppliedThisPass={_currentClientOnlyQuarantineApplied} alreadyQuarantined={_currentClientOnlyAlreadyQuarantined} noRuntime={_currentClientOnlyNoRuntime} retroLedger={_pendingHostBindLedger.Count} retroBound={_retroBindSuccess}"); } private static bool TryQuarantineClientOnlyEnemy(NetGameplayEntitySnapshot snap, string localKey) { if (Plugin.Cfg.LogLevelManifestDiff.Value) { NetLogger.Info($"[LevelManifest] quarantine client-only enemy localKey={Clean(localKey)} idx={snap.SpawnIndex} unit={snap.EntityId.UnitIdentifier} actor={snap.ActorName} reason=not-in-host-manifest"); } if (!Plugin.Cfg.QuarantineClientOnlyManifestEnemies.Value) { return true; } if (!snap.TryGetRuntimeObject(out object runtimeObject) || runtimeObject == null) { return false; } Object val = (Object)((runtimeObject is Object) ? runtimeObject : null); if (val != null && val == (Object)null) { return false; } try { TryInvokeNoArgOrBool(runtimeObject, "ToggleBehaviourTree", boolArg: false); object obj = TryFindComponentByTypeName(runtimeObject, "AiAgent"); if (obj != null) { TryInvokeNoArgOrBool(obj, "SetNavMeshAgentState", boolArg: false); TryInvokeNoArgOrBool(obj, "SetCanMove", boolArg: false); TryInvokeNoArgOrBool(obj, "ToggleRVO", boolArg: false); } return true; } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[LevelManifest] quarantine AI disable failed: " + ex.Message); } return false; } } private static string TryGetGameObjectName(object? runtimeObj) { try { Object val = (Object)((runtimeObj is Object) ? runtimeObj : null); if (val != null && val != (Object)null && !string.IsNullOrWhiteSpace(val.name)) { return Clean(val.name); } } catch { } return ""; } private static string ExtractModifierFlags(string goName, string actorName, string unitId) { List list = new List(); string text = (goName + " " + actorName + " " + unitId).ToLowerInvariant(); if (text.Contains("(offensive)") || text.Contains("offensive")) { list.Add("Offensive"); } if (text.Contains("(defensive)") || text.Contains("defensive")) { list.Add("Defensive"); } if (text.Contains("elite")) { list.Add("Elite"); } if (text.Contains("champion")) { list.Add("Champion"); } if (text.Contains("buffed")) { list.Add("Buffed"); } if (list.Count != 0) { return string.Join("|", list); } return ""; } private static uint ComputeComponentFingerprint(object? runtimeObj) { try { Component val = (Component)((runtimeObj is Component) ? runtimeObj : null); if (val == null || (Object)(object)val == (Object)null) { return 0u; } Component[] components = val.GetComponents(); if (components == null || components.Length == 0) { return 0u; } List list = new List(components.Length); Component[] array = components; foreach (Component val2 in array) { if ((Object)(object)val2 != (Object)null) { list.Add(((object)val2).GetType().Name); } } list.Sort(StringComparer.Ordinal); return Fnv1a(string.Join(",", list)); } catch { return 0u; } } private static uint ComputeGenerationHash(NetLevelManifest m) { try { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(m.Header.HasLevelSeed ? m.Header.LevelSeed : 0).Append(';'); foreach (string item in m.Units.Select(UnitSignature).OrderBy((string s) => s, StringComparer.Ordinal)) { stringBuilder.Append(item).Append('|'); } stringBuilder.Append("#rooms#"); foreach (string item2 in m.Rooms.Select((NetLevelManifestRoom r) => r.RoomName).OrderBy((string s) => s, StringComparer.Ordinal)) { stringBuilder.Append(item2).Append('|'); } stringBuilder.Append("#specials#"); foreach (string item3 in m.Specials.Select((NetLevelManifestSpecial s) => s.Type + ":" + s.Name).OrderBy((string s) => s, StringComparer.Ordinal)) { stringBuilder.Append(item3).Append('|'); } return Fnv1a(stringBuilder.ToString()); } catch { return 0u; } } private static void DiagnoseGenerationHashStability(NetLevelManifest m) { try { string text = string.Format("{0}:{1}:{2}:r{3}", m.Header.SceneName, m.Header.LevelIndex, m.Header.HasLevelSeed ? m.Header.LevelSeed.ToString() : "?", m.Header.GenerationRevision); List list = m.Units.Select(UnitSignature).OrderBy((string s) => s, StringComparer.Ordinal).ToList(); if (text == _lastGenRunKey && _lastGenSignature != null) { HashSet prev = new HashSet(_lastGenSignature); HashSet cur = new HashSet(list); List list2 = list.Where((string s) => !prev.Contains(s)).Take(20).ToList(); List list3 = _lastGenSignature.Where((string s) => !cur.Contains(s)).Take(20).ToList(); if (list2.Count == 0 && list3.Count == 0) { _generationHashStableSameRevision++; } else { _generationHashChangedSameRevision++; NetLogger.Info($"[GenHashDiag] generation signature CHANGED same runKey={text} added={list2.Count} removed={list3.Count}"); foreach (string item in list2) { NetLogger.Info("[GenHashDiag] + " + item); } foreach (string item2 in list3) { NetLogger.Info("[GenHashDiag] - " + item2); } } } _lastGenRunKey = text; _lastGenSignature = list; } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[GenHashDiag] failed: " + ex.Message); } } } private static uint ComputeRuntimeHash(NetLevelManifest m) { try { StringBuilder stringBuilder = new StringBuilder(); foreach (string item in m.Units.Select(delegate(NetLevelManifestUnit u) { //IL_000c: 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) int num = (u.HasPosition ? Mathf.RoundToInt(u.Position.x) : 0); int num2 = (u.HasPosition ? Mathf.RoundToInt(u.Position.z) : 0); return $"{u.UnitIdentifier}:{(u.IsDead ? 1 : 0)}:{num},{num2}"; }).OrderBy((string s) => s, StringComparer.Ordinal)) { stringBuilder.Append(item).Append('|'); } return Fnv1a(stringBuilder.ToString()); } catch { return 0u; } } private static string UnitSignature(NetLevelManifestUnit u) { //IL_0009: 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) if (u.HasInitialPosition) { int num = Mathf.RoundToInt(u.InitialPosition.x / 2f); int num2 = Mathf.RoundToInt(u.InitialPosition.z / 2f); return $"{u.UnitIdentifier}#{num},{num2}"; } return u.UnitIdentifier + "#?"; } private static string PosTag(NetLevelManifestUnit? u) { //IL_0017: 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_0037: Unknown result type (might be due to invalid IL or missing references) if (u == null || !u.HasPosition) { return ""; } return $" pos=({u.Position.x:F1},{u.Position.y:F1},{u.Position.z:F1})"; } private static string ModTag(NetLevelManifestUnit? u) { if (u == null || string.IsNullOrEmpty(u.ModifierFlags)) { return ""; } return " mod=" + u.ModifierFlags; } private static uint Fnv1a(string s) { uint num = 2166136261u; foreach (char c in s) { num ^= c; num *= 16777619; } return num; } private static void Increment(Dictionary d, string k) { d.TryGetValue(k, out var value); d[k] = value + 1; } private static void Decrement(Dictionary d, string k) { if (d.TryGetValue(k, out var value)) { if (value <= 1) { d.Remove(k); } else { d[k] = value - 1; } } } private static int GetCount(Dictionary d, string k) { if (!d.TryGetValue(k, out var value)) { return 0; } return value; } private static int ClassifyEntitySyncCategory(object entity, string actorName) { if (entity == null) { return 0; } try { if (IsBossUnit(entity)) { return 1; } bool num = TryFindComponentByTypeName(entity, "ShopKeeper") != null || TryGetMemberValue(entity, "ShopKeeper") != null; bool flag = TryFindComponentByTypeName(entity, "Shop") != null; if (num || flag) { return 2; } bool num2 = TryFindComponentByTypeName(entity, "DialogSpeaker") != null; bool flag2 = TryFindComponentByTypeName(entity, "Speakable") != null; if (num2 || flag2) { return 3; } string text = (actorName ?? "").ToLowerInvariant(); if (text.Contains("trader") || text.Contains("shopkeeper") || text.Contains("skrip") || text.Contains("fex") || text.Contains("grocer") || text.Contains("stiffleg") || text.Contains("qiosk") || text.Contains("unit_trader_")) { return 2; } if (text.Contains("ghost") || text.Contains("spirit") || text.Contains("haunt")) { return 4; } if (text.Contains("chicken") || text.Contains("ambient") || text.Contains("shanty") || text.Contains("critter")) { return 6; } string name = entity.GetType().Name; if (name.Contains("Trader") || name.Contains("ShopKeeper")) { return 2; } if (name.Contains("Ghost")) { return 4; } if (name == "Npc") { return 1; } } catch { } return 0; } private static bool IsBossUnit(object entity) { try { object obj = TryGetMemberValue(entity, "unitSO"); if (obj == null) { return false; } object obj2 = TryGetMemberValue(obj, "unitType"); if (obj2 == null) { return false; } int num = Convert.ToInt32(obj2); int num2 = Convert.ToInt32(Enum.Parse(obj2.GetType(), "Boss")); return (num & num2) != 0; } catch { return false; } } private static bool IsCombatEnemyForSync(NetGameplayEntitySnapshot snapshot) { if (snapshot == null) { return false; } if (IsPlayerEntitySnapshot(snapshot)) { return false; } int syncCategory = snapshot.SyncCategory; if (syncCategory != 1) { return syncCategory == 0; } return true; } private static bool IsPlayerEntitySnapshot(NetGameplayEntitySnapshot snapshot) { if (snapshot == null) { return false; } if (string.Equals(snapshot.Category, "Player", StringComparison.OrdinalIgnoreCase)) { return true; } if ((snapshot.ActorName ?? "").IndexOf("Unit_Player", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if ((snapshot.EntityId?.UnitIdentifier ?? "").IndexOf("Unit_Player", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } string text = snapshot.Source ?? ""; if (text.IndexOf("Player", StringComparison.OrdinalIgnoreCase) >= 0 && text.IndexOf("Players", StringComparison.OrdinalIgnoreCase) < 0) { return true; } return false; } private static bool IsNonCombatForSync(NetGameplayEntitySnapshot snapshot) { if (snapshot == null) { return false; } int syncCategory = snapshot.SyncCategory; if (syncCategory != 2) { return syncCategory == 3; } return true; } private static string SyncCatName(int cat) { return cat switch { 1 => "CombatEnemy", 2 => "Trader", 3 => "InteractiveNpc", 4 => "Ghost", 5 => "EventNpc", 6 => "Ambient", 7 => "Hazard", _ => "Unknown", }; } private static string CleanCategory(string category, object entity) { category = Clean(category); if (string.IsNullOrWhiteSpace(category) || category == "") { string name = entity.GetType().Name; if (name.Contains("Breakable")) { return "Breakable"; } if (name == "Npc") { return "Npc"; } if (name == "Player") { return "Player"; } return "Unit"; } return category; } private static string GetActorName(object entity) { try { Object val = (Object)((entity is Object) ? entity : null); if (val != null && !string.IsNullOrWhiteSpace(val.name)) { return Clean(val.name); } string text = Clean((TryGetMemberValue(entity, "DisplayName") ?? TryGetMemberValue(entity, "displayName") ?? TryGetMemberValue(entity, "Name") ?? TryGetMemberValue(entity, "name") ?? TryGetMemberValue(entity, "unitName") ?? TryGetMemberValue(entity, "UnitName"))?.ToString()); if (!string.IsNullOrWhiteSpace(text) && text != "") { return text; } } catch { } return entity.GetType().Name; } private static bool TryGetTransform(object entity, out Transform? transform) { transform = null; try { Component val = (Component)((entity is Component) ? entity : null); if (val != null && (Object)(object)val.transform != (Object)null) { transform = val.transform; return true; } GameObject val2 = (GameObject)((entity is GameObject) ? entity : null); if (val2 != null && (Object)(object)val2.transform != (Object)null) { transform = val2.transform; return true; } object? obj = TryGetMemberValue(entity, "transform") ?? TryGetMemberValue(entity, "Transform"); Transform val3 = (Transform)((obj is Transform) ? obj : null); if (val3 != null) { transform = val3; return true; } } catch { } return false; } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private static bool IsFinite(Vector3 value) { //IL_0000: 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_001a: Unknown result type (might be due to invalid IL or missing references) if (IsFinite(value.x) && IsFinite(value.y)) { return IsFinite(value.z); } return false; } private static bool TryGetRotationY(object entity, out float rotationY) { //IL_0026: 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_0094: Unknown result type (might be due to invalid IL or missing references) rotationY = 0f; try { Component val = (Component)((entity is Component) ? entity : null); if (val != null && (Object)(object)val.transform != (Object)null) { rotationY = val.transform.eulerAngles.y; return IsFinite(rotationY); } GameObject val2 = (GameObject)((entity is GameObject) ? entity : null); if (val2 != null && (Object)(object)val2.transform != (Object)null) { rotationY = val2.transform.eulerAngles.y; return IsFinite(rotationY); } object? obj = TryGetMemberValue(entity, "transform") ?? TryGetMemberValue(entity, "Transform"); Transform val3 = (Transform)((obj is Transform) ? obj : null); if (val3 != null) { rotationY = val3.eulerAngles.y; return IsFinite(rotationY); } } catch { } return false; } private static bool TryGetPosition(object entity, out Vector3 position) { //IL_0001: 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_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_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) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) position = default(Vector3); try { Component val = (Component)((entity is Component) ? entity : null); if (val != null && (Object)(object)val.transform != (Object)null) { position = val.transform.position; return true; } GameObject val2 = (GameObject)((entity is GameObject) ? entity : null); if (val2 != null && (Object)(object)val2.transform != (Object)null) { position = val2.transform.position; return true; } object? obj = TryGetMemberValue(entity, "transform") ?? TryGetMemberValue(entity, "Transform"); Transform val3 = (Transform)((obj is Transform) ? obj : null); if (val3 != null) { position = val3.position; return true; } if ((TryGetMemberValue(entity, "position") ?? TryGetMemberValue(entity, "Position")) is Vector3 val4) { position = val4; return true; } } catch { } return false; } private static bool TryGetSnapshotForEntity(object entity, out NetGameplayEntitySnapshot? snapshot) { snapshot = null; if (entity == null) { return false; } try { NetGameplayEntityId netGameplayEntityId = NetGameplayEntityId.FromObject(entity); string text = ((string.IsNullOrWhiteSpace(netGameplayEntityId.LocalInstanceId) || netGameplayEntityId.LocalInstanceId == "null") ? netGameplayEntityId.CandidateKey : netGameplayEntityId.LocalInstanceId); if (string.IsNullOrWhiteSpace(text)) { return false; } return EntitiesByLocalId.TryGetValue(text, out snapshot); } catch { return false; } } private static object? TryGetMemberValue(object obj, string memberName) { try { Type type = obj.GetType(); PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { return property.GetValue(obj, null); } FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(obj); } } catch { } return null; } private static string Clean(string? value) { if (string.IsNullOrWhiteSpace(value)) { return ""; } string text = value.Trim(); if (text.Length > 96) { text = text.Substring(0, 96); } return text; } public static void SetLocalPlayerPositionHint(Vector3 position) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) _hostPlayerPositionHintValid = true; _hostPlayerPositionHint = position; } public static void SetRemotePlayerInterestPositions(List positions) { _remoteInterestPositions.Clear(); if (positions != null) { _remoteInterestPositions.AddRange(positions); } } public static bool ShouldSuppressAllClientPuppetDamage(object? npcSource) { if (!Plugin.Cfg.EnableHostDrivenEnemyProxy.Value) { return false; } if (!Plugin.Cfg.SuppressAllClientPuppetDamage.Value) { return false; } if (NetConfig.GetMode() != NetMode.Client) { return false; } if (npcSource == null) { return false; } int num = ObjectIdentity(npcSource); if (num == 0) { return false; } bool flag = ActiveEnemyPuppetsByNpcId.ContainsKey(num); if (!flag) { NetGameplayEntitySnapshot netGameplayEntitySnapshot = FindSnapshotForRuntimeObject(npcSource); if (netGameplayEntitySnapshot != null) { string snapshotTargetKey = GetSnapshotTargetKey(netGameplayEntitySnapshot); if (!string.IsNullOrWhiteSpace(snapshotTargetKey) && ClientQuarantinedEntities.Contains(snapshotTargetKey)) { flag = true; } } } if (!flag) { return false; } _clientPuppetDamageSuppressed++; if (Plugin.Cfg.LogClientPuppetDamageSuppression.Value) { int key = num; if (ActiveEnemyPuppetsByNpcId.TryGetValue(key, out EnemyPuppetRecord value)) { Plugin.Log.Info($"[Phase5] Suppressed puppet damage src={value.Snapshot.ActorName} idx={value.Snapshot.SpawnIndex}"); } } return true; } public static void ProcessHostAttackPhaseEvent(NetHostAttackPhaseEvent evt) { if (evt == null || !IsEnabled() || !Plugin.Cfg.EnableHostDrivenEnemyProxy.Value || !Plugin.Cfg.EnableClientAttackPhaseAnimatorDrive.Value || NetConfig.GetMode() != NetMode.Client) { return; } _clientAttackPhaseEventsReceived++; if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || !evt.MatchesScene(state)) { return; } EnemyPuppetRecord value2; int value3; if (!ClientHostToLocalKeyByHostSpawnIndex.TryGetValue(evt.HostSpawnIndex, out string value)) { if (Plugin.Cfg.LogHostAttackPhaseEvents.Value) { NetLogger.Info($"[AttackPhase] Client no roster binding for hostIdx={evt.HostSpawnIndex} unit={evt.UnitIdentifier} phase={evt.AttackPhase}"); } } else if (!ActiveEnemyPuppets.TryGetValue(value, out value2)) { if (Plugin.Cfg.LogHostAttackPhaseEvents.Value) { NetLogger.Info($"[AttackPhase] Client no active puppet for key={value} hostIdx={evt.HostSpawnIndex} phase={evt.AttackPhase}"); } } else if (IsClientTerminalDead(evt.HostSpawnIndex)) { _terminalDeadBlockedAttackPhase++; if (Plugin.Cfg.LogClientTerminalDead.Value) { NetLogger.Info($"[TerminalDead] blocked AttackPhase hostIdx={evt.HostSpawnIndex} phase={evt.AttackPhase} reason=terminal-dead"); } } else if (!ClientAttackPhaseLastSeqBySpawnIndex.TryGetValue(evt.HostSpawnIndex, out value3) || evt.Sequence > value3 || evt.AttackPhase == 0) { ClientAttackPhaseLastSeqBySpawnIndex[evt.HostSpawnIndex] = evt.Sequence; if (Plugin.Cfg.LogHostAttackPhaseEvents.Value) { NetLogger.Info($"[AttackPhase] Client received hostIdx={evt.HostSpawnIndex} actor={value2.Snapshot.ActorName} phase={evt.AttackPhase} kind={evt.AttackKind} seq={evt.Sequence} hasAnimHint={evt.HasAnimatorHint}"); } TryApplyAttackPhaseToClientPuppet(value2, evt); } } private static void TryApplyAttackPhaseToClientPuppet(EnemyPuppetRecord puppet, NetHostAttackPhaseEvent evt) { if (evt.AttackPhase != 1 && evt.AttackPhase != 2) { return; } try { Animator val = puppet.CachedAnimator; if ((Object)(object)val == (Object)null && puppet.Npc != null) { object npc = puppet.Npc; Component val2 = (Component)((npc is Component) ? npc : null); if (val2 != null) { val = val2.GetComponentInChildren(); } else { GameObject val3 = (GameObject)((npc is GameObject) ? npc : null); if (val3 != null) { val = val3.GetComponentInChildren(); } } if ((Object)(object)val != (Object)null) { puppet.CachedAnimator = val; puppet.CachedAnimatorId = ((Object)val).GetInstanceID(); } } if ((Object)(object)val == (Object)null || !((Behaviour)val).isActiveAndEnabled) { return; } if (evt.HasAnimatorHint && evt.AnimatorFullPathHash != 0) { float value = Plugin.Cfg.ClientAttackPhaseCrossFadeSeconds.Value; float num = Mathf.Clamp01(evt.AnimatorNormalizedTime); if (value <= 0.001f) { val.Play(evt.AnimatorFullPathHash, -1, num); } else { val.CrossFadeInFixedTime(evt.AnimatorFullPathHash, value, -1, num); } _clientAttackPhaseAnimatorApplies++; if (Plugin.Cfg.LogHostAttackPhaseEvents.Value) { NetLogger.Info($"[AttackPhase] Client applied Animator state={evt.AnimatorFullPathHash} t={num:F2} crossFade={value:F3} idx={evt.HostSpawnIndex} actor={puppet.Snapshot.ActorName}"); } } else if (puppet.HasAttackParam) { val.SetBool(puppet.AttackParamHash, true); _clientAttackPhaseAnimatorApplies++; PendingAnimatorBoolResets.Add(new AnimatorBoolReset { Animator = val, Hash = puppet.AttackParamHash, ResetAt = Time.realtimeSinceStartup + 0.5f }); if (Plugin.Cfg.LogHostAttackPhaseEvents.Value) { NetLogger.Info($"[AttackPhase] Client pulsed attack bool idx={evt.HostSpawnIndex} actor={puppet.Snapshot.ActorName} phase={evt.AttackPhase}"); } } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug($"[AttackPhase] Apply failed idx={evt.HostSpawnIndex}: {ex.Message}"); } } } public static void ReportHostNpcDamageForSync(object npc, float damage) { if (npc == null || damage <= 0f) { return; } try { if (NetConfig.GetMode() != NetMode.Host || !Plugin.Cfg.EnableHostEnemyDamageEventSync.Value || !IsEnabled()) { return; } NetGameplayEntitySnapshot netGameplayEntitySnapshot = TryFindSnapshotForNpc(npc); if (netGameplayEntitySnapshot == null || IsNonCombatForSync(netGameplayEntitySnapshot) || !NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || !state.HasLevel) { return; } int spawnIndex = netGameplayEntitySnapshot.SpawnIndex; if (Plugin.Cfg.EnableCombatEventCoalescing.Value && ThrottlePerEntity(_enemyDmgEventLastSentBySpawnIdx, spawnIndex, Plugin.Cfg.EnemyDamageEventMinIntervalSeconds.Value, Time.realtimeSinceStartup)) { _enemyDamageEventThrottled++; return; } HostDamageEventSeqBySpawnIndex.TryGetValue(spawnIndex, out var value); int num = value + 1; HostDamageEventSeqBySpawnIndex[spawnIndex] = num; NetHostEnemyDamageEvent netHostEnemyDamageEvent = new NetHostEnemyDamageEvent { ChapterName = state.ChapterName, LevelIndex = state.LevelIndex, HasLevelSeed = state.HasLevelSeed, LevelSeed = state.LevelSeed, HostSpawnIndex = spawnIndex, UnitIdentifier = (netGameplayEntitySnapshot.EntityId.UnitIdentifier ?? ""), Sequence = num, DamageAmount = damage, IsDead = false, SentAt = Time.realtimeSinceStartup }; NetGameplaySyncBridge.ReportHostEnemyDamageEvent(netHostEnemyDamageEvent); _hostEnemyDamageEventsSent++; if (Plugin.Cfg.LogHostEnemyDamageEvents.Value) { NetLogger.Info($"[EnemyDamage] Host sent idx={spawnIndex} unit={netHostEnemyDamageEvent.UnitIdentifier} dmg={damage:F1} (health state follows in postfix)"); } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[EnemyDamage] ReportHostNpcDamageForSync failed: " + ex.Message); } } } private static NetGameplayEntitySnapshot? TryFindSnapshotForNpc(object npc) { try { return GetOrCreateSnapshot(npc, "damage-sync", "Npc"); } catch { return null; } } public static void ReportHostNpcHealthAfterDamage(object npc, float damageAmount) { //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) if (npc == null) { return; } try { if (NetConfig.GetMode() != NetMode.Host || !Plugin.Cfg.EnableHostEnemyHealthStateSync.Value || !IsEnabled()) { return; } NetGameplayEntitySnapshot netGameplayEntitySnapshot = TryFindSnapshotForNpc(npc); if (netGameplayEntitySnapshot == null || IsNonCombatForSync(netGameplayEntitySnapshot) || !NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || !state.HasLevel || !TryReadUnitHealthNative(npc, out var currentHp, out var maxHp)) { return; } int spawnIndex = netGameplayEntitySnapshot.SpawnIndex; HostDamageEventSeqBySpawnIndex.TryGetValue(spawnIndex, out var value); bool flag = currentHp <= 0f; if (flag || !Plugin.Cfg.EnableCombatEventCoalescing.Value || !ThrottlePerEntity(_enemyHealthLastSentBySpawnIdx, spawnIndex, Plugin.Cfg.EnemyDamageEventMinIntervalSeconds.Value, Time.realtimeSinceStartup)) { Vector3 position; bool flag2 = TryGetPosition(npc, out position); NetHostEnemyHealthState netHostEnemyHealthState = new NetHostEnemyHealthState { ChapterName = state.ChapterName, LevelIndex = state.LevelIndex, HasLevelSeed = state.HasLevelSeed, LevelSeed = state.LevelSeed, HostSpawnIndex = spawnIndex, UnitIdentifier = (netGameplayEntitySnapshot.EntityId.UnitIdentifier ?? ""), Sequence = value, HasCurrentHealth = true, CurrentHealth = currentHp, HasMaxHealth = (maxHp > 0f), MaxHealth = maxHp, HasNormalizedHealth = (maxHp > 0f), NormalizedHealth = ((maxHp > 0f) ? Mathf.Clamp01(currentHp / maxHp) : 0f), IsDead = flag, HasPosition = flag2, Position = (flag2 ? position : Vector3.zero), SentAt = Time.realtimeSinceStartup }; NetGameplaySyncBridge.ReportHostEnemyHealthState(netHostEnemyHealthState); _hostEnemyHealthStatesSent++; if (Plugin.Cfg.LogHostEnemyHealthState.Value) { float num = ((maxHp > 0f) ? Mathf.Clamp01(currentHp / maxHp) : 0f); NetLogger.Info(NetDbg.Ctx("HealthState", value, spawnIndex, _clientRosterRevision) + " " + $"sent hp={currentHp:F1}/{maxHp:F1} norm={num:P0} dead={flag} unit={netHostEnemyHealthState.UnitIdentifier}"); } } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[EnemyHealth] ReportHostNpcHealthAfterDamage failed: " + ex.Message); } } } public static bool ShouldIgnoreNonPlayerPuppetDamage(object? npc, object? source) { try { if (npc == null) { return false; } if (NetConfig.GetMode() != NetMode.Client) { return false; } if (!Plugin.Cfg.FilterNonPlayerPuppetDamage.Value) { return false; } if (!IsClientEnemyPuppetNpc(npc)) { return false; } bool flag = DamageSourceConfidentlyNonPlayer(source); if (Plugin.Cfg.LogClientHitRequests.Value && _puppetDamageSourceSamplesLogged < 12) { _puppetDamageSourceSamplesLogged++; NetLogger.Info($"[ClientHit] puppet damage source eval npc={ObjectIdentity(npc)} nonPlayer={flag} source={DescribeDamageSource(source)}"); } if (!flag) { return false; } _clientPuppetNonPlayerDamageIgnored++; if (Plugin.Cfg.LogTeleportDiag.Value && _suppressedSourceSamplesLogged < 24) { _suppressedSourceSamplesLogged++; NetLogger.Info($"[ClientHit] SUPPRESSED non-player puppet dmg npc={ObjectIdentity(npc)} source={DescribeDamageSource(source)}"); } return true; } catch { return false; } } private static bool DamageSourceConfidentlyNonPlayer(object? source) { if (source == null) { return false; } if (TryGetBoolMember(source, "isPlayer", out var value)) { return !value; } return false; } public static bool IsConfidentPlayerDamageSource(object? source) { bool value = default(bool); return source != null && TryGetBoolMember(source, "isPlayer", out value) && value; } private static string DescribeDamageSource(object? source) { if (source == null) { return "null"; } try { bool value; string text = (TryGetBoolMember(source, "isPlayer", out value) ? value.ToString() : "?"); bool value2; string text2 = (TryGetBoolMember(source, "melee", out value2) ? value2.ToString() : "?"); return source.GetType().Name + "(isPlayer=" + text + ",melee=" + text2 + ",name=" + DescribeField(source, "name") + ",damageType=" + DescribeField(source, "damageType") + ",sourceUnit=" + DescribeField(source, "sourceUnit") + ",sourceWeapon=" + DescribeField(source, "sourceWeapon") + ",projectile=" + DescribeField(source, "projectile") + ",sourceEffect=" + DescribeField(source, "sourceEffect") + ",instanceId=" + DescribeField(source, "instanceId") + ")"; } catch { return source.GetType().Name; } } private static string DescribeField(object source, string field) { try { object obj = TryGetMemberValue(source, field); if (obj == null) { return "null"; } if (obj is string || obj.GetType().IsValueType) { return obj.ToString(); } Object val = (Object)((obj is Object) ? obj : null); if (val != null) { return obj.GetType().Name + ":" + val.name; } return obj.GetType().Name; } catch { return "?"; } } public static bool TrySendClientHitRequest(object? npc, float damage, object? damageType) { if (npc == null || damage <= 0f) { return false; } try { if (NetConfig.GetMode() != NetMode.Client) { return false; } if (!Plugin.Cfg.EnableClientHitRequest.Value) { return false; } if (!Plugin.Cfg.EnableHostDrivenEnemyProxy.Value) { return false; } if (BossDynamicSpawnManifest.IsHitGated(npc)) { return true; } if (!IsClientEnemyPuppetNpc(npc)) { _clientHitRequestsSkippedNoPuppet++; return false; } EnemyPuppetRecord enemyPuppetRecord = null; string text = null; int num = ObjectIdentity(npc); if (num != 0 && ActiveEnemyPuppetsByNpcId.TryGetValue(num, out EnemyPuppetRecord value)) { enemyPuppetRecord = value; text = enemyPuppetRecord.Key; } if (string.IsNullOrEmpty(text)) { _clientHitRequestsSkippedNoBinding++; return false; } if (!ClientLocalKeyToHostSpawnIndex.TryGetValue(text, out var value2)) { _clientHitRequestsSkippedNoBinding++; return false; } if (IsClientTerminalDead(value2)) { _clientHitSkipTerminalDead++; return true; } if (IsClientPendingDead(value2)) { _clientHitSkipPendingDead++; _pendingDeadBlockedClientHit++; return true; } if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || !state.HasLevel) { return false; } string text2 = enemyPuppetRecord?.Snapshot?.EntityId?.UnitIdentifier ?? ""; NetClientHitRequest netClientHitRequest = new NetClientHitRequest { ChapterName = state.ChapterName, LevelIndex = state.LevelIndex, HasLevelSeed = state.HasLevelSeed, LevelSeed = state.LevelSeed, RequestSeq = ++_clientHitRequestSeq, ClientPeerId = (state.PeerId ?? ""), TargetHostSpawnIndex = value2, TargetUnitIdentifier = text2, DamageCandidate = damage, HasAttackerPosition = false, SentAt = Time.realtimeSinceStartup }; NetGameplaySyncBridge.SendClientHitRequest(netClientHitRequest); _clientHitRequestsSent++; _clientLocalHitPredicted++; _clientPendingHitByHostIdx[value2] = Time.realtimeSinceStartup; if (Plugin.Cfg.LogClientHitRequests.Value) { NetLogger.Info($"[ClientHit] Sent seq={netClientHitRequest.RequestSeq} hostIdx={value2} unit={text2} dmg={damage:F1} type={damageType} (predicted)"); } return true; } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[ClientHit] TrySendClientHitRequest failed: " + ex.Message); } return false; } } private static void FlushPendingClientHitDamage() { if (NetConfig.GetMode() != NetMode.Host || _hostHitPendingDamageByHostIdx.Count == 0) { return; } float value = Plugin.Cfg.ClientHitRequestRateLimitSeconds.Value; if (value <= 0f) { _hostHitPendingDamageByHostIdx.Clear(); return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state)) { return; } List list = null; foreach (KeyValuePair item in _hostHitPendingDamageByHostIdx) { _hostHitRequestLastAtByHostIdx.TryGetValue(item.Key, out var value2); if (realtimeSinceStartup - value2 >= value) { (list ?? (list = new List())).Add(item.Key); } } if (list == null) { return; } foreach (int item2 in list) { float num = _hostHitPendingDamageByHostIdx[item2]; _hostHitPendingDamageByHostIdx.Remove(item2); _hostHitPendingDamagePeerByHostIdx.TryGetValue(item2, out string value3); _hostHitPendingDamagePeerByHostIdx.Remove(item2); if (num <= 0f) { continue; } NetGameplayEntitySnapshot netGameplayEntitySnapshot = null; foreach (NetGameplayEntitySnapshot value4 in EntitiesByLocalId.Values) { if (value4.SpawnIndex == item2) { netGameplayEntitySnapshot = value4; break; } } if (netGameplayEntitySnapshot == null || netGameplayEntitySnapshot.IsDead) { continue; } _hostHitRequestLastAtByHostIdx[item2] = realtimeSinceStartup; if (TryApplyHostHitDamage(netGameplayEntitySnapshot, num, out string _, out bool fatal, out float actualApplied)) { _hostHitRequestsDamageApplied++; if (!string.IsNullOrWhiteSpace(value3)) { NetRunStatsManager.RecordDamageDealtDelta(value3, RunStatsEntityKey(netGameplayEntitySnapshot), actualApplied); } BroadcastHostHitVisual(netGameplayEntitySnapshot, state, fatal); if (Plugin.Cfg.LogClientHitRequests.Value) { NetLogger.Info($"[ClientHit] FLUSH coalesced hostIdx={item2} dmg={num:F1} fatal={fatal}"); } } } } public static void ProcessClientHitRequest(NetClientHitRequest request, string peerId) { //IL_0356: Unknown result type (might be due to invalid IL or missing references) //IL_035b: Unknown result type (might be due to invalid IL or missing references) if (request == null) { return; } try { if (NetConfig.GetMode() != NetMode.Host || !Plugin.Cfg.EnableClientHitRequest.Value || !IsEnabled()) { return; } _hostHitRequestsRecv++; if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || !request.MatchesScene(state)) { _hostHitRequestsRejectedScene++; if (Plugin.Cfg.LogClientHitRequests.Value) { NetLogger.Warn($"[ClientHit] REJECT scene-mismatch peer={peerId} seq={request.RequestSeq} reqScene={request.SceneKey} hostScene={state?.ChapterName}:{state?.LevelIndex}"); } return; } if (request.DamageCandidate <= 0f) { _hostHitRequestsRejectedNoTarget++; return; } NetGameplayEntitySnapshot netGameplayEntitySnapshot = null; foreach (NetGameplayEntitySnapshot value6 in EntitiesByLocalId.Values) { if (value6.SpawnIndex == request.TargetHostSpawnIndex) { netGameplayEntitySnapshot = value6; break; } } if (netGameplayEntitySnapshot == null) { _hostHitRequestsRejectedNoTarget++; if (Plugin.Cfg.LogClientHitRequests.Value) { NetLogger.Warn($"[ClientHit] REJECT no-target peer={peerId} seq={request.RequestSeq} hostIdx={request.TargetHostSpawnIndex}"); } return; } string text = netGameplayEntitySnapshot.EntityId?.UnitIdentifier ?? ""; if (!string.IsNullOrEmpty(request.TargetUnitIdentifier) && !string.Equals(request.TargetUnitIdentifier, text, StringComparison.Ordinal)) { _hostHitRequestsRejectedTypeMismatch++; if (Plugin.Cfg.LogClientHitRequests.Value) { NetLogger.Warn($"[ClientHit] REJECT type-mismatch peer={peerId} seq={request.RequestSeq} clientUnit={request.TargetUnitIdentifier} hostUnit={text}"); } return; } if (netGameplayEntitySnapshot.IsDead) { _hostHitRequestsRejectedDead++; if (Plugin.Cfg.LogClientHitRequests.Value) { NetLogger.Info($"[ClientHit] REJECT dead peer={peerId} seq={request.RequestSeq} hostIdx={request.TargetHostSpawnIndex} unit={text}"); } return; } float value = Plugin.Cfg.ClientHitRequestRateLimitSeconds.Value; if (value > 0f) { float realtimeSinceStartup = Time.realtimeSinceStartup; _hostHitRequestLastAtByHostIdx.TryGetValue(request.TargetHostSpawnIndex, out var value2); if (realtimeSinceStartup - value2 < value) { _hostHitPendingDamageByHostIdx.TryGetValue(request.TargetHostSpawnIndex, out var value3); _hostHitPendingDamageByHostIdx[request.TargetHostSpawnIndex] = value3 + request.DamageCandidate; _hostHitPendingDamagePeerByHostIdx[request.TargetHostSpawnIndex] = peerId; _hostHitRequestsCoalesced++; return; } _hostHitRequestLastAtByHostIdx[request.TargetHostSpawnIndex] = realtimeSinceStartup; } float value4 = Plugin.Cfg.ClientHitRequestMaxRangeMeters.Value; if (value4 > 0f && request.HasAttackerPosition && TryGetPosition(netGameplayEntitySnapshot, out var pos)) { float num = Vector3.Distance(request.AttackerPosition, pos); if (num > value4) { _hostHitRequestsRejectedRateLimit++; if (Plugin.Cfg.LogClientHitRequests.Value) { NetLogger.Warn($"[ClientHit] REJECT range peer={peerId} seq={request.RequestSeq} dist={num:F1} max={value4:F1}"); } return; } } _hostHitRequestsAccepted++; float num2 = request.DamageCandidate; if (_hostHitPendingDamageByHostIdx.TryGetValue(request.TargetHostSpawnIndex, out var value5) && value5 > 0f) { num2 += value5; _hostHitPendingDamageByHostIdx.Remove(request.TargetHostSpawnIndex); _hostHitPendingDamagePeerByHostIdx.Remove(request.TargetHostSpawnIndex); } if (Plugin.Cfg.LogClientHitRequests.Value) { NetLogger.Info(string.Format("[ClientHit] ACCEPT peer={0} seq={1} hostIdx={2} unit={3} dmg={4:F1}{5}", peerId, request.RequestSeq, request.TargetHostSpawnIndex, text, num2, (value5 > 0f) ? $" (+{value5:F1} coalesced)" : "")); } if (TryApplyHostHitDamage(netGameplayEntitySnapshot, num2, out string result, out bool fatal, out float actualApplied)) { _hostHitRequestsDamageApplied++; NetRunStatsManager.RecordDamageDealtDelta(peerId, RunStatsEntityKey(netGameplayEntitySnapshot), actualApplied); BroadcastHostHitVisual(netGameplayEntitySnapshot, state, fatal); return; } _hostHitRequestsDamageFailed++; NetLogger.Warn($"[ClientHit] Damage apply failed peer={peerId} seq={request.RequestSeq} hostIdx={request.TargetHostSpawnIndex} result={result}"); } catch (Exception ex) { NetLogger.Warn("[ClientHit] ProcessClientHitRequest failed: " + ex.Message); } } private static bool TryApplyHostHitDamage(NetGameplayEntitySnapshot snapshot, float damage, out string result, out bool fatal) { float actualApplied; return TryApplyHostHitDamage(snapshot, damage, out result, out fatal, out actualApplied); } private static int RunStatsEntityKey(NetGameplayEntitySnapshot snapshot) { if (snapshot.TryGetRuntimeObject(out object runtimeObject) && runtimeObject != null) { return NetRunStatsManager.EntityKey(runtimeObject); } return snapshot.SpawnIndex; } private static bool TryApplyHostHitDamage(NetGameplayEntitySnapshot snapshot, float damage, out string result, out bool fatal, out float actualApplied) { result = ""; fatal = false; actualApplied = 0f; if (!snapshot.TryGetRuntimeObject(out object runtimeObject) || runtimeObject == null) { result = "noRuntimeObj"; return false; } Object val = (Object)((runtimeObject is Object) ? runtimeObject : null); if (val != null && val == (Object)null) { result = "unityDestroyed"; return false; } if (!TryReadUnitHealthNative(runtimeObject, out var currentHp, out var _)) { result = "cantReadHp"; return false; } if (currentHp <= 0f) { result = "alreadyDead"; return false; } float num = Mathf.Max(0f, currentHp - damage); fatal = num <= 0f; actualApplied = currentHp - num; if (!TryWriteUnitHealthNative(runtimeObject, num)) { result = $"writeHpFailed hp={currentHp:F1}→{num:F1}"; return false; } result = $"hp={currentHp:F1}→{num:F1} dmg={damage:F1}"; ReportHostNpcHealthAfterDamage(runtimeObject, damage); _hostHitResultHealthStateSent++; if (Plugin.Cfg.EnableClientHitVisual.Value) { Renderer[] renderers = null; try { Component val2 = (Component)((runtimeObject is Component) ? runtimeObject : null); if (val2 != null) { renderers = val2.GetComponentsInChildren(true); } else { GameObject val3 = (GameObject)((runtimeObject is GameObject) ? runtimeObject : null); if (val3 != null) { renderers = val3.GetComponentsInChildren(true); } } } catch { } string text = InvokeNpcWhiteFlash(runtimeObject, renderers, snapshot.SpawnIndex); if (text != "none" && text != "none-noMaterial") { _hostHitVisualPlayed++; if (fatal) { _hostHitFatalVisualPlayed++; } } else { _hostHitVisualFailedNoNpc++; } if (Plugin.Cfg.LogClientHitFlash.Value) { NetLogger.Info($"[HitVisual] Host local flash hostIdx={snapshot.SpawnIndex} unit={snapshot.EntityId.UnitIdentifier} fatal={fatal} path={text}"); } } if (num <= 0f && !snapshot.IsDead) { try { MethodInfo method = runtimeObject.GetType().GetMethod("Die", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method != null) { method.Invoke(runtimeObject, null); result += " triggered-die"; } else { result += " die-notfound"; } } catch (Exception ex) { result = result + " die-failed(" + ex.GetType().Name + ")"; } } return true; } private static bool TryGetPosition(NetGameplayEntitySnapshot snapshot, out Vector3 pos) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) pos = Vector3.zero; if (!snapshot.TryGetRuntimeObject(out object runtimeObject) || runtimeObject == null) { return false; } return TryGetPosition(runtimeObject, out pos); } private static void BroadcastHostHitVisual(NetGameplayEntitySnapshot snapshot, NetRunState state, bool fatal) { if (!Plugin.Cfg.EnableClientHitVisual.Value) { return; } try { NetHostHitVisualEvent netHostHitVisualEvent = new NetHostHitVisualEvent { ChapterName = (state.ChapterName ?? ""), LevelIndex = state.LevelIndex, HasLevelSeed = state.HasLevelSeed, LevelSeed = (state.HasLevelSeed ? state.LevelSeed : 0), HostSpawnIndex = snapshot.SpawnIndex, UnitIdentifier = (snapshot.EntityId.UnitIdentifier ?? ""), Sequence = ++_hostHitVisualSeq, IsFatal = fatal, SentAt = Time.realtimeSinceStartup }; NetGameplaySyncBridge.ReportHostHitVisualEvent(netHostHitVisualEvent); _hostHitVisualEventSent++; if (Plugin.Cfg.LogClientHitFlash.Value) { NetLogger.Info($"[HitVisual] Host sent hit-visual hostIdx={netHostHitVisualEvent.HostSpawnIndex} unit={netHostHitVisualEvent.UnitIdentifier} seq={netHostHitVisualEvent.Sequence} fatal={fatal}"); } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[HitVisual] BroadcastHostHitVisual failed: " + ex.Message); } } } public static void ProcessHostHitVisualEvent(NetHostHitVisualEvent evt) { if (evt == null || NetConfig.GetMode() != NetMode.Client) { return; } try { _clientHitVisualEventRecv++; if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || !evt.MatchesScene(state)) { return; } if (IsClientTerminalDead(evt.HostSpawnIndex)) { if (evt.IsFatal) { _clientHitVisualSkippedFatalTerminalDead++; } else { _clientHitVisualSkipTerminalDead++; } return; } if (!evt.IsFatal && IsClientPendingDead(evt.HostSpawnIndex)) { _clientHitVisualSkipPendingDead++; return; } if (!ClientHostToLocalKeyByHostSpawnIndex.TryGetValue(evt.HostSpawnIndex, out string value) || !ActiveEnemyPuppets.TryGetValue(value, out EnemyPuppetRecord value2)) { _clientHitVisualSkipNoBinding++; return; } if (evt.Sequence > 0 && _clientHitVisualLastSeqByHostIdx.TryGetValue(evt.HostSpawnIndex, out var value3) && evt.Sequence <= value3) { _clientHitVisualDuplicateSeq++; return; } if (evt.Sequence > 0) { _clientHitVisualLastSeqByHostIdx[evt.HostSpawnIndex] = evt.Sequence; } object npc = value2.Npc; if (npc != null) { Object val = (Object)((npc is Object) ? npc : null); if (val == null || !(val == (Object)null)) { Renderer[] puppetRenderers = GetPuppetRenderers(value2); string text = InvokeNpcWhiteFlash(npc, puppetRenderers, evt.HostSpawnIndex); if (text != "none" && text != "none-noMaterial") { _clientHitVisualPlayed++; if (evt.IsFatal) { _clientHitFatalVisualPlayed++; } if (Plugin.Cfg.LogClientHitFlash.Value) { NetLogger.Info($"[HitVisual] Client played flash hostIdx={evt.HostSpawnIndex} unit={evt.UnitIdentifier} seq={evt.Sequence} fatal={evt.IsFatal} path={text}"); } } return; } } _clientHitVisualSkipNoBinding++; } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[HitVisual] ProcessHostHitVisualEvent failed: " + ex.Message); } } } private static void MarkClientPendingDead(int hostIdx, string reason) { if (hostIdx >= 0 && Plugin.Cfg.EnableClientPendingDeadState.Value && NetConfig.GetMode() == NetMode.Client && !_clientTerminalDeadHostIdx.Contains(hostIdx) && !_clientPendingDeadHostIdx.ContainsKey(hostIdx)) { _clientPendingDeadHostIdx[hostIdx] = new PendingDeadEntry { MarkedAt = Time.realtimeSinceStartup, Reason = reason }; _pendingDeadMarked++; if (Plugin.Cfg.LogClientPendingDead.Value) { NetLogger.Info($"[PendingDead] mark hostIdx={hostIdx} unit={UnitForHostIdx(hostIdx)} reason={Clean(reason)}"); } } } private static void MarkClientTerminalDead(int hostIdx, string reason) { if (hostIdx >= 0 && Plugin.Cfg.EnableClientTerminalDeadLatch.Value && NetConfig.GetMode() == NetMode.Client) { if (reason != null && reason.IndexOf("HealthState", StringComparison.OrdinalIgnoreCase) >= 0) { _terminalDeadMarkedFromHealthOnly++; } _clientPendingDeadHostIdx.Remove(hostIdx); if (_clientTerminalDeadHostIdx.Add(hostIdx) && Plugin.Cfg.LogClientTerminalDead.Value) { NetLogger.Info($"[TerminalDead] mark hostIdx={hostIdx} unit={UnitForHostIdx(hostIdx)} reason={Clean(reason)}"); } } } private static string UnitForHostIdx(int hostIdx) { if (ClientHostToLocalKeyByHostSpawnIndex.TryGetValue(hostIdx, out string value) && EntitiesByLocalId.TryGetValue(value, out NetGameplayEntitySnapshot value2) && value2 != null) { return value2.EntityId?.UnitIdentifier ?? ""; } return ""; } private static bool IsClientTerminalDead(int hostIdx) { if (Plugin.Cfg.EnableClientTerminalDeadLatch.Value && hostIdx >= 0) { return _clientTerminalDeadHostIdx.Contains(hostIdx); } return false; } private static bool IsClientTerminalDeadByKey(string? key) { if (Plugin.Cfg.EnableClientTerminalDeadLatch.Value && !string.IsNullOrEmpty(key) && ClientLocalKeyToHostSpawnIndex.TryGetValue(key, out var value)) { return _clientTerminalDeadHostIdx.Contains(value); } return false; } private static bool IsClientPendingDead(int hostIdx) { if (hostIdx >= 0) { return _clientPendingDeadHostIdx.ContainsKey(hostIdx); } return false; } private static bool IsClientPendingDeadByKey(string? key) { if (!string.IsNullOrEmpty(key) && ClientLocalKeyToHostSpawnIndex.TryGetValue(key, out var value)) { return _clientPendingDeadHostIdx.ContainsKey(value); } return false; } private static void UpdateClientPendingDead() { if (_clientPendingDeadHostIdx.Count == 0 || !Plugin.Cfg.EnableClientDeathVisualFallback.Value || NetConfig.GetMode() != NetMode.Client) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; float num = Plugin.Cfg.ClientDeathVisualFallbackDelaySeconds.Value; if (num < 0f) { num = 0f; } List list = null; foreach (KeyValuePair item in _clientPendingDeadHostIdx) { if (!item.Value.VisualFallbackAttempted && !(realtimeSinceStartup - item.Value.MarkedAt < num)) { (list ?? (list = new List())).Add(item.Key); } } if (list == null) { return; } foreach (int item2 in list) { if (!_clientPendingDeadHostIdx.TryGetValue(item2, out PendingDeadEntry value)) { continue; } value.VisualFallbackAttempted = true; _pendingDeadVisualFallbackAttempted++; if (TryApplyVisualOnlyDeathShim(item2, out string detail)) { _pendingDeadVisualFallbackSucceeded++; _terminalDeadMarkedAfterVisualFallback++; MarkClientTerminalDead(item2, "VisualFallback death shim"); if (Plugin.Cfg.LogClientPendingDead.Value) { NetLogger.Info($"[PendingDead] visual-only death shim applied hostIdx={item2} {detail} -> terminal"); } } else { _pendingDeadVisualFallbackFailed++; if (Plugin.Cfg.LogClientPendingDead.Value) { NetLogger.Info($"[PendingDead] visual-only death shim FAILED hostIdx={item2} {detail}"); } } } } private static bool TryApplyVisualOnlyDeathShim(int hostIdx, out string detail) { detail = ""; if (!ClientHostToLocalKeyByHostSpawnIndex.TryGetValue(hostIdx, out string value) || !ActiveEnemyPuppets.TryGetValue(value, out EnemyPuppetRecord value2)) { detail = "no puppet"; return false; } object npc = value2.Npc; if (npc != null) { Object val = (Object)((npc is Object) ? npc : null); if (val == null || !(val == (Object)null)) { try { Animator val2 = value2.CachedAnimator; if ((Object)(object)val2 == (Object)null) { Component val3 = (Component)((npc is Component) ? npc : null); if (val3 != null) { val2 = val3.GetComponentInChildren(true); } else { GameObject val4 = (GameObject)((npc is GameObject) ? npc : null); if (val4 != null) { val2 = val4.GetComponentInChildren(true); } } if ((Object)(object)val2 != (Object)null) { value2.CachedAnimator = val2; value2.CachedAnimatorId = ((Object)val2).GetInstanceID(); } } if ((Object)(object)val2 == (Object)null) { detail = "no animator"; return false; } TryResetAnimatorTrigger(val2, "Attack"); TryResetAnimatorTrigger(val2, "Jump"); TryResetAnimatorTrigger(val2, "Land"); TryInvokeNoArgOrBool(value2.AiAgent, "SetNavMeshAgentState", boolArg: false); TryInvokeNoArgOrBool(value2.AiAgent, "ToggleRVO", boolArg: false); bool flag = Random.Range(0, 2) == 0; val2.SetBool("Dead", true); val2.SetBool("DeathRight", flag); TryInvokeNoArgOrBool(npc, "ToggleBehaviourTree", boolArg: false); detail = $"animator Dead=true DeathRight={flag}"; return true; } catch (Exception ex) { detail = "exception " + ex.GetType().Name + ": " + ex.Message; return false; } } } detail = "npc null/destroyed"; return false; } private static void TryResetAnimatorTrigger(Animator animator, string trigger) { try { animator.ResetTrigger(trigger); } catch { } } private static void TryInvokeNoArgOrBool(object? target, string methodName, bool boolArg) { if (target == null) { return; } try { Type type = target.GetType(); MethodInfo method = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(bool) }, null); if (method != null) { method.Invoke(target, new object[1] { boolArg }); return; } MethodInfo method2 = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method2 != null) { method2.Invoke(target, null); } } catch { } } private static void TryPlayDamageVisualReaction(int hostIdx, int seq, string unit, string source) { try { if (!Plugin.Cfg.EnableClientHitFlash.Value || NetConfig.GetMode() != NetMode.Client) { return; } if (IsClientTerminalDead(hostIdx)) { _damageVisualReactionSkippedTerminalDead++; if (Plugin.Cfg.LogClientHitFlash.Value) { NetLogger.Info($"[DamageVisual] skip hostIdx={hostIdx} seq={seq} reason=terminal-dead"); } return; } if (IsClientPendingDead(hostIdx)) { _damageVisualReactionSkippedPendingDead++; _pendingDeadBlockedHitFlash++; if (Plugin.Cfg.LogClientHitFlash.Value) { NetLogger.Info($"[DamageVisual] skip hostIdx={hostIdx} seq={seq} reason=pending-dead"); } return; } if (!ClientHostToLocalKeyByHostSpawnIndex.TryGetValue(hostIdx, out string value) || !ActiveEnemyPuppets.TryGetValue(value, out EnemyPuppetRecord value2)) { _damageVisualReactionSkippedNoBinding++; return; } if (seq > 0 && _clientDamageVisualLastSeqByHostIdx.TryGetValue(hostIdx, out var value3) && seq <= value3) { _damageVisualReactionSkippedDuplicateSeq++; return; } if (seq > 0) { _clientDamageVisualLastSeqByHostIdx[hostIdx] = seq; } object npc = value2.Npc; if (npc != null) { Object val = (Object)((npc is Object) ? npc : null); if (val == null || !(val == (Object)null)) { string text = TryPlayNativeWhiteFlash(npc, value2, hostIdx); if (text != "none") { _damageVisualReactionsPlayed++; if (Plugin.Cfg.LogClientHitFlash.Value) { NetLogger.Info($"[DamageVisual] {text} hostIdx={hostIdx} seq={seq} unit={unit} source={source}"); } } return; } } _dmgVisualFailedNoNpc++; if (Plugin.Cfg.LogClientHitFlash.Value) { NetLogger.Info($"[DamageVisual] fail hostIdx={hostIdx} seq={seq} reason=no-npc"); } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[DamageVisual] TryPlayDamageVisualReaction failed: " + ex.Message); } } } private static string TryPlayNativeWhiteFlash(object npc, EnemyPuppetRecord puppet, int hostIdx) { Renderer[] puppetRenderers = GetPuppetRenderers(puppet); string text = InvokeNpcWhiteFlash(npc, puppetRenderers, hostIdx); switch (text) { case "DoWhiteFlash": _dmgVisualNativeDoWhiteFlash++; break; case "SetHitEffect(1)": _dmgVisualNativeSetHitEffect++; break; case "material _HitTime/_HitType": _dmgVisualMaterialHitTime++; break; case "fallback MPB _Color tint": _dmgVisualFallbackColor++; break; case "none-noMaterial": _dmgVisualFailedNoMaterial++; break; case "none": _dmgVisualFailedNoMethod++; break; } return text; } public static bool TryPlayBossHitVisual(object unit) { if (unit == null) { return false; } try { Renderer[] renderers = null; Component val = (Component)((unit is Component) ? unit : null); if (val != null) { renderers = val.GetComponentsInChildren(true); } else { GameObject val2 = (GameObject)((unit is GameObject) ? unit : null); if (val2 != null) { renderers = val2.GetComponentsInChildren(true); } } string text = InvokeNpcWhiteFlash(unit, renderers, -1); return text != "none" && text != "none-noMaterial"; } catch { return false; } } private static string InvokeNpcWhiteFlash(object npc, Renderer[]? renderers, int hostIdx) { //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Expected O, but got Unknown //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0264: Unknown result type (might be due to invalid IL or missing references) //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_02a0: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Unknown result type (might be due to invalid IL or missing references) Type type = npc.GetType(); string text = type.FullName ?? type.Name; if (!_npcDoWhiteFlashCache.TryGetValue(text, out MethodInfo value)) { value = null; Type type2 = type; while (type2 != null && value == null) { value = type2.GetMethod("DoWhiteFlash", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); type2 = type2.BaseType; } _npcDoWhiteFlashCache[text] = value; LogFlashApiOnce(text, "DoWhiteFlash", value); } if (value != null) { try { value.Invoke(npc, null); return "DoWhiteFlash"; } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[DamageVisual] DoWhiteFlash threw: " + ex.Message); } } } if (!_npcSetHitEffectCache.TryGetValue(text, out MethodInfo value2)) { value2 = null; Type type3 = type; while (type3 != null && value2 == null) { value2 = type3.GetMethod("SetHitEffect", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(int) }, null); type3 = type3.BaseType; } _npcSetHitEffectCache[text] = value2; LogFlashApiOnce(text, "SetHitEffect(int)", value2); } if (value2 != null) { try { value2.Invoke(npc, new object[1] { 1 }); return "SetHitEffect(1)"; } catch (Exception ex2) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[DamageVisual] SetHitEffect threw: " + ex2.Message); } } } if (renderers != null && renderers.Length != 0) { int num = 0; float time = Time.time; Renderer[] array = renderers; foreach (Renderer val in array) { if ((Object)(object)val == (Object)null) { continue; } try { Material material = val.material; if (!((Object)(object)material == (Object)null)) { if (material.HasProperty(_hitFlashHitTimePropId)) { material.SetFloat(_hitFlashHitTimePropId, time); } if (material.HasProperty(_hitFlashHitTypePropId)) { material.SetFloat(_hitFlashHitTypePropId, 1f); } num++; } } catch { } } if (num > 0) { if (Plugin.Cfg.LogClientHitFlash.Value) { NetLogger.Info($"[DamageVisual] fallback material _HitTime/_HitType hostIdx={hostIdx} renderers={num}"); } return "material _HitTime/_HitType"; } MaterialPropertyBlock val2 = new MaterialPropertyBlock(); Color white = Color.white; int num2 = 0; array = renderers; foreach (Renderer val3 in array) { if (!((Object)(object)val3 == (Object)null)) { val3.GetPropertyBlock(val2); val2.SetColor(_hitFlashColorPropId, white); val2.SetColor(_hitFlashEmissionPropId, white * 0.6f); val3.SetPropertyBlock(val2); num2++; } } if (num2 > 0) { float num3 = Plugin.Cfg.ClientHitFlashDurationSeconds.Value; if (num3 < 0.02f) { num3 = 0.02f; } _pendingHitFlashes.Add(new PendingHitFlash { Renderers = renderers, ResetAt = Time.realtimeSinceStartup + num3 }); return "fallback MPB _Color tint"; } return "none-noMaterial"; } return "none"; } private static void LogFlashApiOnce(string typeName, string label, MethodInfo? mi) { string item = label + ":" + typeName; if (!_flashApiLoggedTypes.Add(item)) { return; } if (mi != null) { ParameterInfo[] parameters = mi.GetParameters(); string text = string.Join(", ", Array.ConvertAll(parameters, (ParameterInfo p) => p.ParameterType.Name + " " + p.Name)); NetLogger.Info("[DamageVisual] resolved " + (mi.DeclaringType?.Name ?? typeName) + "." + mi.Name + "(" + text + ") returns " + mi.ReturnType.Name); } else { NetLogger.Info("[DamageVisual] " + label + " not found on " + typeName + " — will try next fallback"); } } private static Renderer[]? GetPuppetRenderers(EnemyPuppetRecord puppet) { if (puppet.RenderersCached) { return puppet.CachedRenderers; } puppet.RenderersCached = true; try { object npc = puppet.Npc; Component val = (Component)((npc is Component) ? npc : null); if (val != null) { puppet.CachedRenderers = val.GetComponentsInChildren(true); } else { GameObject val2 = (GameObject)((npc is GameObject) ? npc : null); if (val2 != null) { puppet.CachedRenderers = val2.GetComponentsInChildren(true); } else { puppet.CachedRenderers = null; } } } catch { puppet.CachedRenderers = null; } return puppet.CachedRenderers; } private static void UpdateClientHitFlashes() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown if (_pendingHitFlashes.Count == 0) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; for (int num = _pendingHitFlashes.Count - 1; num >= 0; num--) { PendingHitFlash pendingHitFlash = _pendingHitFlashes[num]; if (!(realtimeSinceStartup < pendingHitFlash.ResetAt)) { try { MaterialPropertyBlock propertyBlock = new MaterialPropertyBlock(); Renderer[] renderers = pendingHitFlash.Renderers; foreach (Renderer val in renderers) { if ((Object)(object)val != (Object)null) { val.SetPropertyBlock(propertyBlock); } } } catch { } _pendingHitFlashes.RemoveAt(num); } } } private static object? GetUnitStatsObject(object unit, Type t, string typeName) { if (!_unitStatsPropCache.TryGetValue(typeName, out PropertyInfo value)) { value = null; Type type = t; while (type != null && value == null) { value = type.GetProperty("Stats", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); type = type.BaseType; } _unitStatsPropCache[typeName] = value; } if (value != null) { try { return value.GetValue(unit, null); } catch { } } if (!_unitStatsFieldCache.TryGetValue(typeName, out FieldInfo value2)) { value2 = null; Type type2 = t; while (type2 != null && value2 == null) { value2 = type2.GetField("Stats", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type2.GetField("stats", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? type2.GetField("_stats", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); type2 = type2.BaseType; } _unitStatsFieldCache[typeName] = value2; } if (value2 != null) { try { return value2.GetValue(unit); } catch { } } return null; } private static float InvokeStatsMethod(object stats, string methodName, int enumId, Dictionary miCache) { string key = stats.GetType().FullName ?? stats.GetType().Name; if (!miCache.TryGetValue(key, out MethodInfo value)) { value = null; MethodInfo[] methods = stats.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != methodName)) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length >= 1 && (parameters[0].ParameterType == typeof(int) || parameters[0].ParameterType.IsEnum)) { value = methodInfo; break; } } } miCache[key] = value; } if (value == null) { return -1f; } try { ParameterInfo[] parameters2 = value.GetParameters(); object obj = ((parameters2[0].ParameterType == typeof(int)) ? ((object)enumId) : BuildEnumArgument(parameters2[0].ParameterType, enumId)); float value2; return BoxedToFloat(value.Invoke(stats, new object[1] { obj }), out value2) ? value2 : (-1f); } catch { return -1f; } } public static bool TryReadBossUnitHealth(object unit, out float currentHp, out float maxHp) { return TryReadUnitHealthNative(unit, out currentHp, out maxHp); } public static bool TryWriteBossUnitHealth(object unit, float hp) { return TryWriteUnitHealthNative(unit, hp); } private static bool TryReadUnitHealthNative(object unit, out float currentHp, out float maxHp) { currentHp = 0f; maxHp = 0f; if (unit == null) { return false; } try { Type type = unit.GetType(); string text = type.FullName ?? type.Name; if (!_unitGetHpMiCache.TryGetValue(text, out MethodInfo value)) { value = null; Type type2 = type; while (type2 != null && value == null) { value = type2.GetMethod("GetCurrentHealth", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); type2 = type2.BaseType; } _unitGetHpMiCache[text] = value; if (!_nativeApiLoggedTypes.Contains(text)) { _nativeApiLoggedTypes.Add(text); if (value != null) { NetLogger.Info("[HP-NativeAPI] GetCurrentHealth() found on " + text); } else { NetLogger.Warn("[HP-NativeAPI] GetCurrentHealth() not found on " + text + " — will try Stats API"); } } } if (value != null && BoxedToFloat(value.Invoke(unit, null), out currentHp) && currentHp >= 0f) { if (!_unitGetNormHpMiCache.TryGetValue(text, out MethodInfo value2)) { value2 = null; Type type3 = type; while (type3 != null && value2 == null) { value2 = type3.GetMethod("GetNormalizedHealth", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); type3 = type3.BaseType; } _unitGetNormHpMiCache[text] = value2; } if (value2 != null && BoxedToFloat(value2.Invoke(unit, null), out var value3) && value3 > 0.001f) { maxHp = currentHp / value3; return true; } object unitStatsObject = GetUnitStatsObject(unit, type, text); if (unitStatsObject != null) { float num = InvokeStatsMethod(unitStatsObject, "GetAttribute", 60, _statsGetAttrCache); if (num > 0f) { maxHp = num; } } return true; } object unitStatsObject2 = GetUnitStatsObject(unit, type, text); if (unitStatsObject2 != null) { float num2 = InvokeStatsMethod(unitStatsObject2, "GetStatus", 92, _statsGetStatusCache); if (num2 >= 0f) { currentHp = num2; float num3 = InvokeStatsMethod(unitStatsObject2, "GetAttribute", 60, _statsGetAttrCache); if (num3 > 0f) { maxHp = num3; } return true; } } return false; } catch { return false; } } private static object BuildEnumArgument(Type enumType, int intValue) { if (!enumType.IsEnum) { return Convert.ChangeType(intValue, enumType, CultureInfo.InvariantCulture); } Type underlyingType = Enum.GetUnderlyingType(enumType); object value = Convert.ChangeType(intValue, underlyingType, CultureInfo.InvariantCulture); return Enum.ToObject(enumType, value); } public static bool TryWriteUnitHealth(object unit, float newHealth, bool raiseEvent) { return TryWriteUnitHealthNative(unit, newHealth, raiseEvent); } private static bool TryWriteUnitHealthNative(object unit, float newHealth, bool raiseEvent = false) { if (unit == null) { return false; } try { Type type = unit.GetType(); string typeName = type.FullName ?? type.Name; object unitStatsObject = GetUnitStatsObject(unit, type, typeName); if (unitStatsObject == null) { _clientHealthNoStats++; return false; } string text = unitStatsObject.GetType().FullName ?? unitStatsObject.GetType().Name; if (!_statsSetStatusCache.TryGetValue(text, out MethodInfo value)) { List list = new List(); MethodInfo[] methods = unitStatsObject.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "SetStatus") && methodInfo.GetParameters().Length >= 2) { list.Add(methodInfo); } } value = null; foreach (MethodInfo item in list) { ParameterInfo[] parameters = item.GetParameters(); if (parameters.Length == 3 && (parameters[0].ParameterType.IsEnum || parameters[0].ParameterType == typeof(int)) && parameters[1].ParameterType == typeof(float) && parameters[2].ParameterType == typeof(bool)) { value = item; break; } } if (value == null) { foreach (MethodInfo item2 in list) { ParameterInfo[] parameters2 = item2.GetParameters(); if (parameters2.Length == 2 && (parameters2[0].ParameterType.IsEnum || parameters2[0].ParameterType == typeof(int)) && parameters2[1].ParameterType == typeof(float)) { value = item2; break; } } } if (value == null && list.Count > 0) { value = list[0]; } _statsSetStatusCache[text] = value; if (!_nativeApiLoggedTypes.Contains("SetStatus:" + text)) { _nativeApiLoggedTypes.Add("SetStatus:" + text); if (value != null) { ParameterInfo[] parameters3 = value.GetParameters(); string text2 = string.Join(", ", Array.ConvertAll(parameters3, (ParameterInfo p) => p.ParameterType.Name + " " + p.Name)); NetLogger.Info("[Reflect] Selected " + (value.DeclaringType?.Name ?? text) + "." + value.Name + "(" + text2 + ") returns " + value.ReturnType.Name); NetLogger.Info($"[HP-NativeAPI] SetStatus overload chosen: declaringType={value.DeclaringType?.FullName} paramCount={parameters3.Length}"); } else { string text3 = ((list.Count == 0) ? "none" : string.Join(" | ", list.ConvertAll((MethodInfo m) => m.Name + "(" + string.Join(",", Array.ConvertAll(m.GetParameters(), (ParameterInfo p) => p.ParameterType.Name)) + ")"))); NetLogger.Warn("[HP-NativeAPI] SetStatus not found on " + text + ". Candidates with >=2 params: " + text3); } } } if (value == null) { _clientHealthSetStatusMissing++; return false; } ParameterInfo[] parameters4 = value.GetParameters(); Type parameterType = parameters4[0].ParameterType; object obj; try { obj = ((parameterType.IsEnum || parameterType == typeof(int)) ? BuildEnumArgument(parameterType, 92) : Convert.ChangeType(92, parameterType, CultureInfo.InvariantCulture)); } catch (Exception ex) { _clientHealthEnumArgFailed++; NetLogger.Warn(string.Format("[HealthWrite] FAIL enumArg build arg0={0} underlying={1} value={2} ex={3}: {4}", parameterType.FullName, parameterType.IsEnum ? Enum.GetUnderlyingType(parameterType).Name : "n/a", 92, ex.GetType().Name, ex.Message)); return false; } if (!_nativeApiLoggedTypes.Contains("SetStatus:arg0:" + text)) { _nativeApiLoggedTypes.Add("SetStatus:arg0:" + text); string text4 = (parameterType.IsEnum ? Enum.GetUnderlyingType(parameterType).Name : parameterType.Name); NetLogger.Info($"[HP-NativeAPI] SetStatus arg0: type={parameterType.FullName} underlying={text4} builtValue={obj} (Status_CurrentHealth={92})"); } object[] parameters5 = ((parameters4.Length < 3) ? new object[2] { obj, newHealth } : new object[3] { obj, newHealth, raiseEvent }); try { value.Invoke(unitStatsObject, parameters5); return true; } catch (Exception ex2) { _clientHealthSetStatusFailed++; Exception ex3 = ((ex2 is TargetInvocationException) ? ex2.InnerException : null); string text5 = string.Join(", ", Array.ConvertAll(parameters4, (ParameterInfo p) => p.ParameterType.Name)); NetLogger.Warn($"[HealthWrite] FAIL SetStatus invoke {value.DeclaringType?.Name}.{value.Name}({text5}) args=({obj},{newHealth}) ex={ex2.GetType().Name}: {(ex3 ?? ex2).Message}"); return false; } } catch (Exception ex4) { NetLogger.Warn("[HealthWrite] outer exception: " + ex4.GetType().Name + ": " + ex4.Message); return false; } } private static bool TryReadNpcHealth(object npc, out float currentHp, out float maxHp) { currentHp = 0f; maxHp = 0f; if (npc == null) { return false; } try { Type type = npc.GetType(); string key = type.FullName ?? type.Name; string[] npcHealthFieldNamesExact = NpcHealthFieldNamesExact; int num = 0; while (true) { if (num < npcHealthFieldNamesExact.Length) { string text = npcHealthFieldNamesExact[num]; if (TryGetNumericMember(npc, text, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, out var value) && value >= 0f) { currentHp = value; DiscoveredCurrentHpMember[key] = text; break; } num++; continue; } if (DiscoveredCurrentHpMember.TryGetValue(key, out string value2)) { if (value2 == null) { return false; } if (TryGetNumericMember(npc, value2, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, out var value3) && value3 >= 0f) { currentHp = value3; break; } } float value4; string text2 = DiscoverHealthMember(npc, type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, wantMax: false, out value4); DiscoveredCurrentHpMember[key] = text2; if (text2 == null) { return false; } currentHp = value4; break; } npcHealthFieldNamesExact = NpcMaxHpFieldNamesExact; foreach (string text3 in npcHealthFieldNamesExact) { if (TryGetNumericMember(npc, text3, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, out var value5) && value5 > 0f) { maxHp = value5; DiscoveredMaxHpMember[key] = text3; return true; } } if (DiscoveredMaxHpMember.TryGetValue(key, out string value6) && value6 != null && TryGetNumericMember(npc, value6, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, out var value7) && value7 > 0f) { maxHp = value7; return true; } float value8; string text4 = DiscoverHealthMember(npc, type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, wantMax: true, out value8); DiscoveredMaxHpMember[key] = text4; if (text4 != null && value8 > 0f) { maxHp = value8; } return true; } catch { return false; } } private static bool TryGetNumericMember(object obj, string name, BindingFlags flags, out float value) { value = 0f; try { Type type = obj.GetType(); FieldInfo field = type.GetField(name, flags); if (field != null) { return BoxedToFloat(field.GetValue(obj), out value); } PropertyInfo property = type.GetProperty(name, flags); if (property != null && property.CanRead && property.GetIndexParameters().Length == 0) { return BoxedToFloat(property.GetValue(obj, null), out value); } } catch { } return false; } private static bool BoxedToFloat(object? v, out float value) { value = 0f; if (v is float num) { value = num; return true; } if (v is int num2) { value = num2; return true; } if (v is double num3) { value = (float)num3; return true; } if (v is uint num4) { value = num4; return true; } if (v is short num5) { value = num5; return true; } return false; } private static string? DiscoverHealthMember(object npc, Type t, BindingFlags flags, bool wantMax, out float value) { value = 0f; List<(string, float)> list = new List<(string, float)>(); Type type = t; while (type != null && type != typeof(object)) { FieldInfo[] fields = type.GetFields(flags | BindingFlags.DeclaredOnly); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType != typeof(float) && fieldInfo.FieldType != typeof(int) && fieldInfo.FieldType != typeof(double) && fieldInfo.FieldType != typeof(uint)) { continue; } string text = fieldInfo.Name.ToLowerInvariant(); if (!ContainsHealthKeyword(text)) { continue; } bool flag = text.StartsWith("max") || text.Contains("max"); if (wantMax != flag) { continue; } try { if (BoxedToFloat(fieldInfo.GetValue(npc), out var value2) && value2 >= 0f) { list.Add((fieldInfo.Name, value2)); } } catch { } } PropertyInfo[] properties = type.GetProperties(flags | BindingFlags.DeclaredOnly); foreach (PropertyInfo propertyInfo in properties) { if ((propertyInfo.PropertyType != typeof(float) && propertyInfo.PropertyType != typeof(int) && propertyInfo.PropertyType != typeof(double)) || !propertyInfo.CanRead || propertyInfo.GetIndexParameters().Length != 0) { continue; } string text2 = propertyInfo.Name.ToLowerInvariant(); if (!ContainsHealthKeyword(text2)) { continue; } bool flag2 = text2.StartsWith("max") || text2.Contains("max"); if (wantMax != flag2) { continue; } try { if (BoxedToFloat(propertyInfo.GetValue(npc, null), out var value3) && value3 >= 0f) { list.Add((propertyInfo.Name, value3)); } } catch { } } type = type.BaseType; } if (list.Count > 0) { value = list[0].Item2; if (Plugin.Cfg.EnableDebugLog.Value && !wantMax) { NetLogger.Debug(string.Format("[EnemyHealth-Legacy] Discovered HP member '{0}'={1:F1} on {2} (all candidates: {3})", list[0].Item1, value, t.Name, string.Join(", ", list.Select<(string, float), string>(((string name, float val) x) => $"{x.name}={x.val:F1}")))); } return list[0].Item1; } if (Plugin.Cfg.EnableDebugLog.Value && !wantMax) { List list2 = new List(); Type type2 = t; while (type2 != null && type2 != typeof(object)) { FieldInfo[] fields = type2.GetFields(flags | BindingFlags.DeclaredOnly); foreach (FieldInfo fieldInfo2 in fields) { if (fieldInfo2.FieldType == typeof(float) || fieldInfo2.FieldType == typeof(int) || fieldInfo2.FieldType == typeof(double)) { list2.Add(type2.Name + "." + fieldInfo2.Name); } } PropertyInfo[] properties = type2.GetProperties(flags | BindingFlags.DeclaredOnly); foreach (PropertyInfo propertyInfo2 in properties) { if (propertyInfo2.PropertyType == typeof(float) || propertyInfo2.PropertyType == typeof(int) || propertyInfo2.PropertyType == typeof(double)) { list2.Add(type2.Name + "." + propertyInfo2.Name + "(prop)"); } } type2 = type2.BaseType; } List list3 = list2.Take(30).ToList(); NetLogger.Debug(string.Format("[EnemyHealth-Legacy] No health field found on {0}. Numeric members ({1} total, showing {2}): {3}", t.Name, list2.Count, list3.Count, string.Join(", ", list3))); } return null; } private static bool ContainsHealthKeyword(string lowerName) { string[] healthKeywords = HealthKeywords; foreach (string value in healthKeywords) { if (lowerName.Contains(value)) { return true; } } return false; } public static void ProcessHostEnemyDamageEvent(NetHostEnemyDamageEvent evt) { if (evt == null) { return; } try { _clientEnemyDamageEventsReceived++; if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || !evt.MatchesScene(state)) { return; } if (!ClientHostToLocalKeyByHostSpawnIndex.TryGetValue(evt.HostSpawnIndex, out string value)) { _clientEnemyHealthApplySkippedNoBinding++; if (Plugin.Cfg.LogHostEnemyDamageEvents.Value) { NetLogger.Info($"[EnemyDamage] Client recv hostIdx={evt.HostSpawnIndex} unit={evt.UnitIdentifier} dmg={evt.DamageAmount:F1} — no roster binding"); } return; } if (Plugin.Cfg.LogHostEnemyDamageEvents.Value) { string text = (evt.HasRemainingHealth ? string.Format(" remaining={0:F1}{1} isDead={2}", evt.RemainingHealth, evt.HasMaxHealth ? $"/{evt.MaxHealth:F1}" : "", evt.IsDead) : " (no health data)"); NetLogger.Info($"[EnemyDamage] Client recv hostIdx={evt.HostSpawnIndex} unit={evt.UnitIdentifier} dmg={evt.DamageAmount:F1}{text}"); } if (evt.HasRemainingHealth) { ClientPuppetHealthBySpawnIndex[evt.HostSpawnIndex] = evt.RemainingHealth; } if (evt.HasMaxHealth) { ClientPuppetMaxHealthBySpawnIndex[evt.HostSpawnIndex] = evt.MaxHealth; } bool flag = evt.IsDead || (evt.HasRemainingHealth && evt.RemainingHealth <= 0f); if (flag) { MarkClientPendingDead(evt.HostSpawnIndex, "DamageEvent isDead"); } if (!flag && evt.DamageAmount > 0f) { TryPlayDamageVisualReaction(evt.HostSpawnIndex, evt.Sequence, evt.UnitIdentifier, "HostEnemyDamageEvent"); } else if (flag) { _terminalDeadBlockedHitReaction++; } if (EntitiesByLocalId.TryGetValue(value, out NetGameplayEntitySnapshot value2) && value2 != null && Plugin.Cfg.ApplyReceivedHostEnemyHealthState.Value && evt.HasRemainingHealth && !IsClientTerminalDead(evt.HostSpawnIndex) && value2.TryGetRuntimeObject(out object runtimeObject) && runtimeObject != null) { TryWriteUnitHealthNative(runtimeObject, evt.RemainingHealth); } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[EnemyDamage] ProcessHostEnemyDamageEvent failed: " + ex.Message); } } } public static void ProcessHostEnemyHealthState(NetHostEnemyHealthState state) { if (state == null) { return; } try { _clientEnemyHealthStatesReceived++; if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state2) || !state.MatchesScene(state2)) { return; } bool value = Plugin.Cfg.LogHostEnemyHealthState.Value; string text = (value ? NetDbg.Ctx("HealthState", state.Sequence, state.HostSpawnIndex, _clientRosterRevision, state.SentAt) : ""); if (!ClientHostToLocalKeyByHostSpawnIndex.TryGetValue(state.HostSpawnIndex, out string value2)) { _clientEnemyHealthApplySkippedNoBinding++; _pendingHealthByHostIdx[state.HostSpawnIndex] = new PendingHealthEntry { State = state, ReceivedAt = Time.realtimeSinceStartup }; _clientHealthStatesPendingQueued++; if (value) { float realtimeSinceStartup = Time.realtimeSinceStartup; NetLogger.Info(string.Concat(str2: (_bindingTombstones.TryGetValue(state.HostSpawnIndex, out var value3) && realtimeSinceStartup - value3.ReleasedAt <= 60f) ? $"tombstone(released {realtimeSinceStartup - value3.ReleasedAt:F1}s ago reason={value3.ReleaseReason})" : ((ClientHostToLocalKeyByHostSpawnIndex.Count <= 0) ? "noRosterYet" : $"neverBound(roster has {ClientHostToLocalKeyByHostSpawnIndex.Count} bindings)"), str0: $"{text} recv hp={state.CurrentHealth:F1}/{state.MaxHealth:F1} unit={state.UnitIdentifier} ", str1: "binding=MISSING(", str3: ") → queued pending")); } return; } if (state.HasCurrentHealth) { ClientPuppetHealthBySpawnIndex[state.HostSpawnIndex] = state.CurrentHealth; } if (state.HasMaxHealth) { ClientPuppetMaxHealthBySpawnIndex[state.HostSpawnIndex] = state.MaxHealth; } if (_clientPendingHitByHostIdx.TryGetValue(state.HostSpawnIndex, out var value4)) { _clientPendingHitByHostIdx.Remove(state.HostSpawnIndex); if (Time.realtimeSinceStartup - value4 <= 2f) { _clientLocalHitConfirmed++; } } if (state.IsDead || (state.HasCurrentHealth && state.CurrentHealth <= 0f)) { MarkClientPendingDead(state.HostSpawnIndex, state.IsDead ? "HealthState isDead" : "HealthState hp<=0"); } if (IsClientTerminalDead(state.HostSpawnIndex)) { _terminalDeadHealthUpdatesIgnored++; return; } if (!Plugin.Cfg.ApplyReceivedHostEnemyHealthState.Value) { _clientHealthApplyDisabled++; if (value || Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug($"{text} cached hp={state.CurrentHealth:F1} (ApplyReceivedHostEnemyHealthState=false)"); } return; } if (!state.HasCurrentHealth) { _clientHealthNoCurrentHp++; if (value || Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug(text + " no HasCurrentHealth unit=" + state.UnitIdentifier); } return; } if (!EntitiesByLocalId.TryGetValue(value2, out NetGameplayEntitySnapshot value5) || value5 == null) { _clientHealthNoEntity++; NetLogger.Info($"{text} FAIL noEntity localKey={value2} hp={state.CurrentHealth:F1} unit={state.UnitIdentifier}"); return; } if (!value5.TryGetRuntimeObject(out object runtimeObject) || runtimeObject == null) { _clientHealthNoRuntimeObj++; NetLogger.Info($"{text} FAIL noRuntimeObj localKey={value2} hp={state.CurrentHealth:F1} unit={state.UnitIdentifier}"); return; } Object val = (Object)((runtimeObject is Object) ? runtimeObject : null); if (val != null && val == (Object)null) { _clientHealthUnityDestroyed++; NetLogger.Info(text + " FAIL runtimeObj unity-destroyed localKey=" + value2 + " unit=" + state.UnitIdentifier); return; } TryReadUnitHealthNative(runtimeObject, out var currentHp, out var maxHp); if (TryWriteUnitHealthNative(runtimeObject, state.CurrentHealth)) { _clientEnemyHealthStatesApplied++; TryReadUnitHealthNative(runtimeObject, out var currentHp2, out maxHp); bool flag = Math.Abs(state.CurrentHealth - currentHp) > 0.5f && Math.Abs(currentHp2 - currentHp) < 0.1f; if (flag) { _clientHealthWriteReadBackUnchanged++; } NetLogger.Info($"{text} OK applied hp={state.CurrentHealth:F1} before={currentHp:F1} after={currentHp2:F1}" + (flag ? " WARNING:readBackUnchanged" : "") + " unit=" + state.UnitIdentifier); } else { _clientHealthWriteFailed++; NetLogger.Info($"{text} FAIL write noStats={_clientHealthNoStats} noSetStatus={_clientHealthSetStatusMissing} invokeFail={_clientHealthSetStatusFailed} before={currentHp:F1} unit={state.UnitIdentifier}"); } } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[EnemyHealth] ProcessHostEnemyHealthState failed: " + ex.Message); } } } public static void TryApplyPendingHealthState(int hostSpawnIndex, string localKey) { if (!_pendingHealthByHostIdx.TryGetValue(hostSpawnIndex, out PendingHealthEntry value)) { return; } _pendingHealthByHostIdx.Remove(hostSpawnIndex); float num = Time.realtimeSinceStartup - value.ReceivedAt; if (num > 8f) { _clientHealthStatesPendingExpired++; if (Plugin.Cfg.LogHostEnemyHealthState.Value) { NetLogger.Info($"[EnemyHealth] Pending HealthState hostIdx={hostSpawnIndex} expired ageMs={num * 1000f:F0}ms"); } } else { if (!Plugin.Cfg.ApplyReceivedHostEnemyHealthState.Value) { return; } NetHostEnemyHealthState state = value.State; if (!state.HasCurrentHealth || !EntitiesByLocalId.TryGetValue(localKey, out NetGameplayEntitySnapshot value2) || value2 == null || !value2.TryGetRuntimeObject(out object runtimeObject) || runtimeObject == null) { return; } if (!string.IsNullOrWhiteSpace(state.UnitIdentifier) && !string.IsNullOrWhiteSpace(value2.EntityId.UnitIdentifier) && !string.Equals(state.UnitIdentifier, value2.EntityId.UnitIdentifier, StringComparison.Ordinal)) { if (Plugin.Cfg.LogHostEnemyHealthState.Value) { NetLogger.Info($"[EnemyHealth] Pending HealthState hostIdx={hostSpawnIndex} UnitId mismatch pending={state.UnitIdentifier} local={value2.EntityId.UnitIdentifier} — discarded"); } return; } TryReadUnitHealthNative(runtimeObject, out var currentHp, out var maxHp); if (TryWriteUnitHealthNative(runtimeObject, state.CurrentHealth)) { _clientEnemyHealthStatesApplied++; _clientHealthStatesPendingApplied++; TryReadUnitHealthNative(runtimeObject, out var currentHp2, out maxHp); bool flag = Math.Abs(state.CurrentHealth - currentHp) > 0.5f && Math.Abs(currentHp2 - currentHp) < 0.1f; if (flag) { _clientHealthWriteReadBackUnchanged++; } if (Plugin.Cfg.LogHostEnemyHealthState.Value) { NetLogger.Info($"[EnemyHealth] Pending HealthState applied after binding: hostIdx={hostSpawnIndex} hp={state.CurrentHealth:F1} before={currentHp:F1} after={currentHp2:F1} ageMs={num * 1000f:F0}ms" + (flag ? " WARNING:readBackUnchanged" : "")); } } } } private static void ExpireOldPendingHealthStates() { if (_pendingHealthByHostIdx.Count == 0) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; List list = new List(); foreach (KeyValuePair item in _pendingHealthByHostIdx) { if (realtimeSinceStartup - item.Value.ReceivedAt > 8f) { list.Add(item.Key); } } foreach (int item2 in list) { _pendingHealthByHostIdx.Remove(item2); _clientHealthStatesPendingExpired++; } } private static bool TryWriteNpcHealth(object runtimeObj, float newHealth) { try { Type type = runtimeObj.GetType(); string key = type.FullName ?? type.Name; if (DiscoveredCurrentHpMember.TryGetValue(key, out string value) && value != null && TrySetNumericMember(runtimeObj, value, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, newHealth)) { return true; } string[] npcHealthFieldNamesExact = NpcHealthFieldNamesExact; foreach (string text in npcHealthFieldNamesExact) { if (TrySetNumericMember(runtimeObj, text, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, newHealth)) { DiscoveredCurrentHpMember[key] = text; return true; } } return false; } catch { return false; } } private static bool TrySetNumericMember(object obj, string name, BindingFlags flags, float value) { Type type = obj.GetType(); FieldInfo field = type.GetField(name, flags); if (field != null && (field.FieldType == typeof(float) || field.FieldType == typeof(int))) { field.SetValue(obj, (field.FieldType == typeof(int)) ? ((object)(int)value) : ((object)value)); return true; } PropertyInfo property = type.GetProperty(name, flags); if (property != null && property.CanWrite && (property.PropertyType == typeof(float) || property.PropertyType == typeof(int))) { property.SetValue(obj, (property.PropertyType == typeof(int)) ? ((object)(int)value) : ((object)value), null); return true; } return false; } private static bool TryLateBindForDeathEvent(NetGameplayDeathEvent death, out NetGameplayEntitySnapshot? snapshot) { //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_02e8: Unknown result type (might be due to invalid IL or missing references) snapshot = null; if (!Plugin.Cfg.AllowDeathLateRebind.Value) { return false; } try { List list = new List(); foreach (NetGameplayEntitySnapshot value2 in EntitiesByLocalId.Values) { if (!value2.IsDead && string.Equals(value2.Category, "Npc", StringComparison.OrdinalIgnoreCase) && !IsNonCombatForSync(value2)) { string snapshotTargetKey = GetSnapshotTargetKey(value2); if (!ClientLocalKeyToHostSpawnIndex.ContainsKey(snapshotTargetKey)) { list.Add(value2); } } } bool value = Plugin.Cfg.LogReceivedEnemyDeathEvents.Value; if (list.Count == 0) { if (value) { NetLogger.Info($"[LateBind] No unbound alive NPC candidates for hostIdx={death.SpawnIndex} unit={death.UnitIdentifier}"); } return false; } bool flag = !string.IsNullOrWhiteSpace(death.UnitIdentifier); List unitFiltered = new List(); foreach (NetGameplayEntitySnapshot item in list) { bool flag2 = !string.IsNullOrWhiteSpace(item.EntityId.UnitIdentifier); if (flag && flag2) { if (!string.Equals(item.EntityId.UnitIdentifier, death.UnitIdentifier, StringComparison.Ordinal)) { continue; } } else if (flag && !flag2) { continue; } unitFiltered.Add(item); } if (value) { IEnumerable values = (from c in list where !unitFiltered.Contains(c) select c.EntityId.UnitIdentifier ?? "?").Distinct().Take(5); NetLogger.Info($"[LateBind] hostIdx={death.SpawnIndex} unit={death.UnitIdentifier} " + $"unboundTotal={list.Count} strictUnitMatch={unitFiltered.Count} " + "rejected=[" + string.Join(",", values) + "]"); } if (flag && unitFiltered.Count == 0) { if (value) { NetLogger.Info("[LateBind] Hard-reject: host unitId=" + death.UnitIdentifier + " has no matching local candidate"); } return false; } List list2 = ((unitFiltered.Count > 0) ? unitFiltered : list); if (list2.Count == 1) { snapshot = list2[0]; return true; } if (death.HasPosition && list2.Count > 1) { NetGameplayEntitySnapshot netGameplayEntitySnapshot = null; float num = float.MaxValue; foreach (NetGameplayEntitySnapshot item2 in list2) { if (item2.HasPosition) { float num2 = Vector3.Distance(item2.Position, death.Position); if (num2 < num) { num = num2; netGameplayEntitySnapshot = item2; } } } if (value) { NetLogger.Info($"[LateBind] Position tiebreak: best dist={num:F1}m (threshold=10m) candidates={list2.Count}"); } if (netGameplayEntitySnapshot != null && num < 10f) { snapshot = netGameplayEntitySnapshot; return true; } } if (value) { NetLogger.Info($"[LateBind] Failed: unitMatch={unitFiltered.Count} hasDeathPos={death.HasPosition}"); } return false; } catch (Exception ex) { if (Plugin.Cfg.EnableDebugLog.Value) { NetLogger.Debug("[LateBind] Exception: " + ex.Message); } return false; } } private static void TrySnapEntityToPosition(object runtimeObj, Vector3 targetPosition) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) try { if (TryGetTransform(runtimeObj, out Transform transform) && !((Object)(object)transform == (Object)null)) { transform.position = targetPosition; } } catch { } } private static bool ThrottlePerEntity(Dictionary lastSentByIdx, int spawnIdx, float minInterval, float now) { if (minInterval <= 0f) { return false; } lastSentByIdx.TryGetValue(spawnIdx, out var value); if (now - value < minInterval) { return true; } lastSentByIdx[spawnIdx] = now; return false; } private static bool ShouldSendByInterestManagement(NetGameplayEntitySnapshot snapshot, bool hasActiveCombatAction, float now) { //IL_0040: 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_0054: 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_009b: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Cfg.SendAllEnemySnapshotsToClients.Value) { return true; } if (!Plugin.Cfg.EnableEnemyInterestManagement.Value) { return true; } if (!_hostPlayerPositionHintValid) { return true; } if (!snapshot.HasPosition || !IsFinite(snapshot.Position)) { return true; } float num = Vector3.Distance(_hostPlayerPositionHint, snapshot.Position); float num2 = num; float num3 = float.PositiveInfinity; int num4 = 0; if (Plugin.Cfg.IncludeRemotePlayersInInterest.Value) { num4 = _remoteInterestPositions.Count; for (int i = 0; i < _remoteInterestPositions.Count; i++) { float num5 = Vector3.Distance(_remoteInterestPositions[i], snapshot.Position); if (num5 < num3) { num3 = num5; } if (num5 < num2) { num2 = num5; } } } float value = Plugin.Cfg.EnemyNearCombatDistance.Value; float value2 = Plugin.Cfg.EnemyFarDistance.Value; if (num2 <= value || hasActiveCombatAction) { return true; } if (Plugin.Cfg.FullRateForEngagedEnemies.Value && _hostHitRequestLastAtByHostIdx.TryGetValue(snapshot.SpawnIndex, out var value3) && now - value3 <= Plugin.Cfg.ClientEngagedEnemyFullRateSeconds.Value) { _interestEngagedExempt++; return true; } if (num2 <= value2) { return true; } if (Plugin.Cfg.ThrottleOnlyWithKnownRemotePositions.Value && num4 == 0) { MaybeLogInterestDiag(snapshot, num, num3, num4, hasActiveCombatAction, "send-no-remote-pos"); return true; } MaybeLogInterestDiag(snapshot, num, num3, num4, hasActiveCombatAction, "far"); float value4 = Plugin.Cfg.EnemyFarSnapshotHz.Value; if (value4 <= 0f) { _interestManagementFarSkipped++; return false; } float num6 = 1f / value4; if (!FarEnemyLastSentAtBySpawnIndex.TryGetValue(snapshot.SpawnIndex, out var value5) || now - value5 >= num6) { FarEnemyLastSentAtBySpawnIndex[snapshot.SpawnIndex] = now; return true; } _interestManagementFarSkipped++; return false; } private static void MaybeLogInterestDiag(NetGameplayEntitySnapshot snapshot, float distHost, float distRemoteMin, int remoteCount, bool engaged, string decision) { if (Plugin.Cfg.LogEnemyInterestDiag.Value && _interestDiagLogged < 120) { _interestDiagLogged++; string text = (float.IsInfinity(distRemoteMin) ? "none" : distRemoteMin.ToString("F1")); bool flag = _hostHitRequestLastAtByHostIdx.ContainsKey(snapshot.SpawnIndex); NetLogger.Info($"[InterestDiag] idx={snapshot.SpawnIndex} unit={snapshot.EntityId.UnitIdentifier} distHost={distHost:F1} distRemoteMin={text} remoteCount={remoteCount} engaged={engaged} everHit={flag} decision={decision}"); } } } internal static class NetGameplaySyncBridge { private static NetService? _service; public static bool IsSessionActive { get { if (_service != null) { return _service.Mode != NetMode.Off; } return false; } } public static bool IsHost { get { if (_service != null) { return _service.Mode == NetMode.Host; } return false; } } public static string LocalPeerId { get { if (_service == null) { return ""; } return _service.LocalPeerId; } } public static NetMode BossMode => _service?.Mode ?? NetMode.Off; public static void Attach(NetService? service) { _service = service; } public static void ReportLocalEnemyDeath(NetGameplayDeathEvent deathEvent) { _service?.ReportLocalEnemyDeathEvent(deathEvent); } public static void ReportClientEnemyDeathClaim(NetGameplayDeathEvent deathEvent) { _service?.ReportClientEnemyDeathClaim(deathEvent); } public static void ReportPlayerLifeState(NetPlayerLifeState state) { _service?.ReportPlayerLifeState(state); } public static void ReportHostPlayerLifeStateToAll(NetPlayerLifeState state) { _service?.ReportHostPlayerLifeStateToAll(state); } public static IReadOnlyCollection GetKnownPlayerLifePeerIds() { return _service?.GetKnownPlayerLifePeerIds() ?? new List().AsReadOnly(); } public static IReadOnlyCollection GetSessionsSnapshot() { return _service?.GetSessionsSnapshot() ?? new List().AsReadOnly(); } public static void BroadcastRunStatsFinalized(NetRunStatsList list) { _service?.BroadcastRunStatsFinalized(list); } public static void ReportHostAttackPhaseEvent(NetHostAttackPhaseEvent evt) { _service?.ReportHostAttackPhaseEvent(evt); } public static void ReportHostProjectileVisualSpawn(NetHostProjectileVisualSpawn evt) { _service?.ReportHostProjectileVisualSpawn(evt); } public static void ReportLocalPlayerWeaponFire(NetPlayerWeaponFire msg) { _service?.BroadcastLocalPlayerWeaponFire(msg); } public static void ReportLocalHeldWeapon(NetPlayerHeldWeapon msg) { _service?.BroadcastLocalHeldWeapon(msg); } public static void ReportLocalBreakableBreak(NetBreakableBreak msg) { _service?.BroadcastLocalBreakableBreak(msg); } public static void ReportLocalGateState(NetGateState msg) { _service?.BroadcastLocalGateState(msg); } public static void ReportLocalTriggerDoors(NetTriggerDoors msg) { _service?.BroadcastLocalTriggerDoors(msg); } public static void SendClientArenaEnter(NetClientArenaEnter msg) { _service?.SendClientArenaEnter(msg); } public static void BroadcastArenaCommand(NetArenaCommand msg) { _service?.BroadcastArenaCommand(msg); } public static bool TryGetLocalScene(out string chapter, out int level, out bool hasSeed, out int seed) { chapter = ""; level = -1; hasSeed = false; seed = 0; if (_service != null) { return _service.TryGetLocalScene(out chapter, out level, out hasSeed, out seed); } return false; } public static List GetPeerIdsInLevel(string chapter, int level, bool hasSeed, int seed) { return _service?.GetPeerIdsInLevel(chapter, level, hasSeed, seed) ?? new List(); } public static void ReportLocalWorldPickupSpawn(NetWorldPickupSpawn msg) { _service?.BroadcastLocalWorldPickupSpawn(msg); } public static void SendWorldPickupTakeRequest(string ownerPeerId, ushort seq) { _service?.SendWorldPickupTakeRequest(ownerPeerId, seq); } public static void BroadcastWorldPickupRemoved(NetWorldPickupRemoved msg) { _service?.BroadcastWorldPickupRemoved(msg); } public static void ReportHostEnemyDamageEvent(NetHostEnemyDamageEvent evt) { _service?.ReportHostEnemyDamageEvent(evt); } public static void ReportHostEnemyHealthState(NetHostEnemyHealthState state) { _service?.ReportHostEnemyHealthState(state); } public static void SendClientHitRequest(NetClientHitRequest request) { _service?.SendClientHitRequest(request); } public static void SendHostLevelManifest(NetLevelManifest manifest) { _service?.SendHostLevelManifest(manifest); } public static void ReportHostHitVisualEvent(NetHostHitVisualEvent evt) { _service?.ReportHostHitVisualEvent(evt); } public static string FormatBossConvergence(out bool allConverged) { allConverged = false; if (_service == null) { return "no-session"; } return _service.FormatBossConvergence(out allConverged); } public static void SendClientBossStartRequest(NetClientBossStartRequest req) { _service?.SendClientBossStartRequest(req); } public static void BroadcastHostBossEncounterStart(NetBossEncounterState state) { _service?.BroadcastHostBossEncounterStart(state); } public static void SendClientBossDialogCommitRequest(NetBossDialogCommit msg) { _service?.SendClientBossDialogCommitRequest(msg); } public static void BroadcastHostBossDialogCommit(NetBossDialogCommit msg) { _service?.BroadcastHostBossDialogCommit(msg); } public static void BroadcastHostBossState(NetBossState msg) { _service?.BroadcastHostBossState(msg); } public static void BroadcastEmperorWormHead(float x, float y, float z, float rotY, float tailHp, int seq) { _service?.BroadcastEmperorWormHead(x, y, z, rotY, tailHp, seq); } public static void BroadcastEmperorWormSectionDestroy(int seq) { _service?.BroadcastEmperorWormSectionDestroy(seq); } public static void BroadcastEmperorWormDeath(int seq) { _service?.BroadcastEmperorWormDeath(seq); } public static void SendClientEmperorWormHit(float damage, int damageTypeInt, int seq) { _service?.SendClientEmperorWormHit(damage, damageTypeInt, seq); } public static void SendClientEmperorFightStart() { _service?.SendClientEmperorFightStart(); } public static void BroadcastEmperorFightStart() { _service?.BroadcastEmperorFightStart(); } public static void BroadcastEmperorSpiderTransform(float x, float y, float z, float rotY, int wp, int tgt, float hp, int seq) { _service?.BroadcastEmperorSpiderTransform(x, y, z, rotY, wp, tgt, hp, seq); } public static void SendClientEmperorSpiderFightStart() { _service?.SendClientEmperorSpiderFightStart(); } public static void BroadcastEmperorSpiderFightStart(string starterPeerId) { _service?.BroadcastEmperorSpiderFightStart(starterPeerId); } public static void SendClientEmperorSpiderHit(float damage, int damageTypeInt, int seq) { _service?.SendClientEmperorSpiderHit(damage, damageTypeInt, seq); } public static void BroadcastEmperorSpiderEvent(int eventCode, int seq) { _service?.BroadcastEmperorSpiderEvent(eventCode, seq); } public static void SendClientRoomEnter(NetClientRoomEnter msg) { _service?.SendClientRoomEnter(msg); } public static void BroadcastHostRoomMembership(NetHostRoomMembership msg) { _service?.BroadcastHostRoomMembership(msg); } public static void BroadcastHostBossDynamicSpawn(NetBossDynamicSpawn msg) { _service?.BroadcastHostBossDynamicSpawn(msg); } public static void ForEachRemotePlayerPosition(Action action) { _service?.ForEachRemotePlayerPosition(action); } public static void ForEachRemotePlayerPositionWithPeer(Action action) { _service?.ForEachRemotePlayerPositionWithPeer(action); } public static void ForEachRemotePlayerTransform(Action action) { _service?.ForEachRemotePlayerTransform(action); } public static void SendClientBossHitRequest(NetClientBossHitRequest req) { _service?.SendClientBossHitRequest(req); } public static void BroadcastHostBossHitVisual(NetHostBossHitVisual msg) { _service?.BroadcastHostBossHitVisual(msg); } public static void BroadcastHostBossDiscreteEvent(NetBossDiscreteEvent msg) { _service?.BroadcastHostBossDiscreteEvent(msg); } public static void SendClientLuciaEyeReport(NetLuciaEyeReport req) { _service?.SendClientLuciaEyeReport(req); } public static void BroadcastHostLuciaEyeState(NetLuciaEyeState msg) { _service?.BroadcastHostLuciaEyeState(msg); } public static void BroadcastHostLuciaDeath(NetLuciaDeath msg) { _service?.BroadcastHostLuciaDeath(msg); } public static void BroadcastHostWitchPhase(NetWitchPhase msg) { _service?.BroadcastHostWitchPhase(msg); } public static void BroadcastHostWitchP2Manifest(NetWitchP2Manifest msg) { _service?.BroadcastHostWitchP2Manifest(msg); } public static void BroadcastHostWitchP2Result(NetWitchP2Result msg) { _service?.BroadcastHostWitchP2Result(msg); } public static void BroadcastHostRuntimeSpawn(NetRuntimeSpawn msg) { _service?.BroadcastHostRuntimeSpawn(msg); } public static void SendFriendlyFireHit(NetFriendlyFireHit msg) { _service?.SendFriendlyFireHit(msg); } } internal sealed class NetGateState { public string PeerId { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public int Sequence { get; set; } public float SentAt { get; set; } public Vector3 Position { get; set; } public bool Closed { get; set; } public byte Kind { get; set; } public bool MatchesScene(NetRunState localState) { if (!localState.HasLevel) { return false; } if (!string.Equals(localState.ChapterName, ChapterName, StringComparison.Ordinal)) { return false; } if (localState.LevelIndex != LevelIndex) { return false; } if (Plugin.Cfg.EnableLevelSeedAuthority.Value) { if (!HasLevelSeed || !localState.HasLevelSeed) { return false; } if (localState.LevelSeed != LevelSeed) { return false; } } return true; } } internal static class NetGateStateCodec { private const byte Version = 2; public static void Write(NetDataWriter w, NetGateState m) { //IL_0077: 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_0099: Unknown result type (might be due to invalid IL or missing references) w.Put((byte)2); w.Put(m.PeerId ?? ""); w.Put(m.ChapterName ?? ""); w.Put(m.LevelIndex); w.Put(m.HasLevelSeed); if (m.HasLevelSeed) { w.Put(m.LevelSeed); } w.Put(m.Sequence); w.Put(m.SentAt); w.Put(m.Position.x); w.Put(m.Position.y); w.Put(m.Position.z); w.Put(m.Closed); w.Put(m.Kind); } public static bool TryRead(NetDataReader r, out NetGateState m) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) m = new NetGateState(); try { if (r.GetByte() != 2) { return false; } m.PeerId = r.GetString(); m.ChapterName = r.GetString(); m.LevelIndex = r.GetInt(); m.HasLevelSeed = r.GetBool(); if (m.HasLevelSeed) { m.LevelSeed = r.GetInt(); } m.Sequence = r.GetInt(); m.SentAt = r.GetFloat(); m.Position = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); m.Closed = r.GetBool(); m.Kind = r.GetByte(); return true; } catch { return false; } } } internal sealed class NetHostAttackPhaseEvent { public const int PhaseNone = 0; public const int PhaseWindup = 1; public const int PhaseActive = 2; public const int PhaseRecovery = 3; public const int PhaseCancelled = 4; public const int KindNone = 0; public const int KindMelee = 1; public const int KindRanged = 2; public const int KindWeaponAction = 3; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public int HostSpawnIndex { get; set; } public string UnitIdentifier { get; set; } = ""; public int AttackPhase { get; set; } public int AttackKind { get; set; } public int ActionKind { get; set; } public int ActionState { get; set; } public int Sequence { get; set; } public bool HasAimData { get; set; } public Vector3 OriginPosition { get; set; } public Vector3 AimPosition { get; set; } public bool HasAnimatorHint { get; set; } public int AnimatorFullPathHash { get; set; } public float AnimatorNormalizedTime { get; set; } public float SentAt { get; set; } public string SceneKey { get { if (!string.IsNullOrWhiteSpace(ChapterName)) { return $"{ChapterName}:{LevelIndex}"; } return ":-1"; } } public bool MatchesScene(NetRunState localState) { if (!localState.HasLevel) { return false; } if (!string.Equals(localState.ChapterName, ChapterName, StringComparison.Ordinal)) { return false; } if (localState.LevelIndex != LevelIndex) { return false; } if (Plugin.Cfg.EnableLevelSeedAuthority.Value) { if (!HasLevelSeed || !localState.HasLevelSeed) { return false; } if (localState.LevelSeed != LevelSeed) { return false; } } return true; } } internal static class NetHostAttackPhaseEventCodec { private const byte Version = 1; public static void Write(NetDataWriter w, NetHostAttackPhaseEvent evt) { //IL_00be: 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_00e0: 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_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) w.Put((byte)1); w.Put(evt.ChapterName ?? ""); w.Put(evt.LevelIndex); w.Put(evt.HasLevelSeed); if (evt.HasLevelSeed) { w.Put(evt.LevelSeed); } w.Put(evt.HostSpawnIndex); w.Put(evt.UnitIdentifier ?? ""); w.Put((byte)evt.AttackPhase); w.Put((byte)evt.AttackKind); w.Put((byte)evt.ActionKind); w.Put(evt.ActionState); w.Put(evt.Sequence); w.Put(evt.HasAimData); if (evt.HasAimData) { w.Put(evt.OriginPosition.x); w.Put(evt.OriginPosition.y); w.Put(evt.OriginPosition.z); w.Put(evt.AimPosition.x); w.Put(evt.AimPosition.y); w.Put(evt.AimPosition.z); } w.Put(evt.HasAnimatorHint); if (evt.HasAnimatorHint) { w.Put(evt.AnimatorFullPathHash); w.Put(evt.AnimatorNormalizedTime); } w.Put(evt.SentAt); } public static bool TryRead(NetDataReader r, out NetHostAttackPhaseEvent evt) { //IL_00d9: 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) evt = new NetHostAttackPhaseEvent(); try { if (r.GetByte() != 1) { return false; } evt.ChapterName = r.GetString(); evt.LevelIndex = r.GetInt(); evt.HasLevelSeed = r.GetBool(); if (evt.HasLevelSeed) { evt.LevelSeed = r.GetInt(); } evt.HostSpawnIndex = r.GetInt(); evt.UnitIdentifier = r.GetString(); evt.AttackPhase = r.GetByte(); evt.AttackKind = r.GetByte(); evt.ActionKind = r.GetByte(); evt.ActionState = r.GetInt(); evt.Sequence = r.GetInt(); evt.HasAimData = r.GetBool(); if (evt.HasAimData) { evt.OriginPosition = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); evt.AimPosition = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } evt.HasAnimatorHint = r.GetBool(); if (evt.HasAnimatorHint) { evt.AnimatorFullPathHash = r.GetInt(); evt.AnimatorNormalizedTime = r.GetFloat(); } evt.SentAt = r.GetFloat(); return true; } catch { return false; } } } internal sealed class NetHostEnemyDamageEvent { public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public int HostSpawnIndex { get; set; } public string UnitIdentifier { get; set; } = ""; public int Sequence { get; set; } public float DamageAmount { get; set; } public bool HasRemainingHealth { get; set; } public float RemainingHealth { get; set; } public bool HasMaxHealth { get; set; } public float MaxHealth { get; set; } public bool IsDead { get; set; } public bool HasHitPosition { get; set; } public Vector3 HitPosition { get; set; } public float SentAt { get; set; } public string SceneKey { get { if (!string.IsNullOrWhiteSpace(ChapterName)) { return $"{ChapterName}:{LevelIndex}"; } return ":-1"; } } public bool MatchesScene(NetRunState localState) { if (!localState.HasLevel) { return false; } if (!string.Equals(localState.ChapterName, ChapterName, StringComparison.Ordinal)) { return false; } if (localState.LevelIndex != LevelIndex) { return false; } if (Plugin.Cfg.EnableLevelSeedAuthority.Value) { if (!HasLevelSeed || !localState.HasLevelSeed) { return false; } if (localState.LevelSeed != LevelSeed) { return false; } } return true; } } internal static class NetHostEnemyDamageEventCodec { private const byte Version = 1; public static void Write(NetDataWriter w, NetHostEnemyDamageEvent evt) { //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) w.Put((byte)1); w.Put(evt.ChapterName ?? ""); w.Put(evt.LevelIndex); w.Put(evt.HasLevelSeed); if (evt.HasLevelSeed) { w.Put(evt.LevelSeed); } w.Put(evt.HostSpawnIndex); w.Put(evt.UnitIdentifier ?? ""); w.Put(evt.Sequence); w.Put(evt.DamageAmount); w.Put(evt.HasRemainingHealth); if (evt.HasRemainingHealth) { w.Put(evt.RemainingHealth); w.Put(evt.HasMaxHealth); if (evt.HasMaxHealth) { w.Put(evt.MaxHealth); } } w.Put(evt.IsDead); w.Put(evt.HasHitPosition); if (evt.HasHitPosition) { w.Put(evt.HitPosition.x); w.Put(evt.HitPosition.y); w.Put(evt.HitPosition.z); } w.Put(evt.SentAt); } public static bool TryRead(NetDataReader r, out NetHostEnemyDamageEvent evt) { //IL_0105: Unknown result type (might be due to invalid IL or missing references) evt = new NetHostEnemyDamageEvent(); try { if (r.GetByte() != 1) { return false; } evt.ChapterName = r.GetString(); evt.LevelIndex = r.GetInt(); evt.HasLevelSeed = r.GetBool(); if (evt.HasLevelSeed) { evt.LevelSeed = r.GetInt(); } evt.HostSpawnIndex = r.GetInt(); evt.UnitIdentifier = r.GetString(); evt.Sequence = r.GetInt(); evt.DamageAmount = r.GetFloat(); evt.HasRemainingHealth = r.GetBool(); if (evt.HasRemainingHealth) { evt.RemainingHealth = r.GetFloat(); evt.HasMaxHealth = r.GetBool(); if (evt.HasMaxHealth) { evt.MaxHealth = r.GetFloat(); } } evt.IsDead = r.GetBool(); evt.HasHitPosition = r.GetBool(); if (evt.HasHitPosition) { evt.HitPosition = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } evt.SentAt = r.GetFloat(); return true; } catch { return false; } } } internal sealed class NetHostEnemyHealthState { public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public int HostSpawnIndex { get; set; } public string UnitIdentifier { get; set; } = ""; public int Sequence { get; set; } public bool HasCurrentHealth { get; set; } public float CurrentHealth { get; set; } public bool HasMaxHealth { get; set; } public float MaxHealth { get; set; } public bool HasNormalizedHealth { get; set; } public float NormalizedHealth { get; set; } public bool IsDead { get; set; } public bool HasPosition { get; set; } public Vector3 Position { get; set; } public float SentAt { get; set; } public string SceneKey { get { if (!string.IsNullOrWhiteSpace(ChapterName)) { return $"{ChapterName}:{LevelIndex}"; } return ":-1"; } } public bool MatchesScene(NetRunState localState) { if (!localState.HasLevel) { return false; } if (!string.Equals(localState.ChapterName, ChapterName, StringComparison.Ordinal)) { return false; } if (localState.LevelIndex != LevelIndex) { return false; } if (Plugin.Cfg.EnableLevelSeedAuthority.Value) { if (!HasLevelSeed || !localState.HasLevelSeed) { return false; } if (localState.LevelSeed != LevelSeed) { return false; } } return true; } } internal static class NetHostEnemyHealthStateCodec { private const byte Version = 1; public static void Write(NetDataWriter w, NetHostEnemyHealthState state) { //IL_00f7: 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_0119: Unknown result type (might be due to invalid IL or missing references) w.Put((byte)1); w.Put(state.ChapterName ?? ""); w.Put(state.LevelIndex); w.Put(state.HasLevelSeed); if (state.HasLevelSeed) { w.Put(state.LevelSeed); } w.Put(state.HostSpawnIndex); w.Put(state.UnitIdentifier ?? ""); w.Put(state.Sequence); w.Put(state.HasCurrentHealth); if (state.HasCurrentHealth) { w.Put(state.CurrentHealth); w.Put(state.HasMaxHealth); if (state.HasMaxHealth) { w.Put(state.MaxHealth); } w.Put(state.HasNormalizedHealth); if (state.HasNormalizedHealth) { w.Put(state.NormalizedHealth); } } w.Put(state.IsDead); w.Put(state.HasPosition); if (state.HasPosition) { w.Put(state.Position.x); w.Put(state.Position.y); w.Put(state.Position.z); } w.Put(state.SentAt); } public static bool TryRead(NetDataReader r, out NetHostEnemyHealthState state) { //IL_011b: Unknown result type (might be due to invalid IL or missing references) state = new NetHostEnemyHealthState(); try { if (r.GetByte() != 1) { return false; } state.ChapterName = r.GetString(); state.LevelIndex = r.GetInt(); state.HasLevelSeed = r.GetBool(); if (state.HasLevelSeed) { state.LevelSeed = r.GetInt(); } state.HostSpawnIndex = r.GetInt(); state.UnitIdentifier = r.GetString(); state.Sequence = r.GetInt(); state.HasCurrentHealth = r.GetBool(); if (state.HasCurrentHealth) { state.CurrentHealth = r.GetFloat(); state.HasMaxHealth = r.GetBool(); if (state.HasMaxHealth) { state.MaxHealth = r.GetFloat(); } state.HasNormalizedHealth = r.GetBool(); if (state.HasNormalizedHealth) { state.NormalizedHealth = r.GetFloat(); } } state.IsDead = r.GetBool(); state.HasPosition = r.GetBool(); if (state.HasPosition) { state.Position = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } state.SentAt = r.GetFloat(); return true; } catch { return false; } } } internal sealed class NetHostHitVisualEvent { public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public int HostSpawnIndex { get; set; } public string UnitIdentifier { get; set; } = ""; public int Sequence { get; set; } public bool IsFatal { get; set; } public float SentAt { get; set; } public bool MatchesScene(NetRunState localState) { if (!localState.HasLevel) { return false; } if (!string.Equals(localState.ChapterName, ChapterName, StringComparison.Ordinal)) { return false; } if (localState.LevelIndex != LevelIndex) { return false; } if (Plugin.Cfg.EnableLevelSeedAuthority.Value) { if (!HasLevelSeed || !localState.HasLevelSeed) { return false; } if (localState.LevelSeed != LevelSeed) { return false; } } return true; } } internal static class NetHostHitVisualEventCodec { public static void Write(NetDataWriter w, NetHostHitVisualEvent e) { w.Put(e.ChapterName ?? ""); w.Put(e.LevelIndex); w.Put(e.HasLevelSeed); if (e.HasLevelSeed) { w.Put(e.LevelSeed); } w.Put(e.HostSpawnIndex); w.Put(e.UnitIdentifier ?? ""); w.Put(e.Sequence); w.Put(e.IsFatal); w.Put(e.SentAt); } public static bool TryRead(NetDataReader r, out NetHostHitVisualEvent e) { e = null; try { NetHostHitVisualEvent netHostHitVisualEvent = new NetHostHitVisualEvent { ChapterName = r.GetString(), LevelIndex = r.GetInt(), HasLevelSeed = r.GetBool() }; if (netHostHitVisualEvent.HasLevelSeed) { netHostHitVisualEvent.LevelSeed = r.GetInt(); } netHostHitVisualEvent.HostSpawnIndex = r.GetInt(); netHostHitVisualEvent.UnitIdentifier = r.GetString(); netHostHitVisualEvent.Sequence = r.GetInt(); netHostHitVisualEvent.IsFatal = r.GetBool(); netHostHitVisualEvent.SentAt = r.GetFloat(); e = netHostHitVisualEvent; return true; } catch { return false; } } } internal sealed class NetHostProjectileVisualSpawn { public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public int HostSpawnIndex { get; set; } public string UnitIdentifier { get; set; } = ""; public int Sequence { get; set; } public Vector3 Origin { get; set; } public Vector3 Velocity { get; set; } public float Lifetime { get; set; } public string ProjectileKind { get; set; } = ""; public float SentAt { get; set; } public string SceneKey { get { if (!string.IsNullOrWhiteSpace(ChapterName)) { return $"{ChapterName}:{LevelIndex}"; } return ":-1"; } } public bool MatchesScene(NetRunState localState) { if (!localState.HasLevel) { return false; } if (!string.Equals(localState.ChapterName, ChapterName, StringComparison.Ordinal)) { return false; } if (localState.LevelIndex != LevelIndex) { return false; } if (Plugin.Cfg.EnableLevelSeedAuthority.Value) { if (!HasLevelSeed || !localState.HasLevelSeed) { return false; } if (localState.LevelSeed != LevelSeed) { return false; } } return true; } } internal static class NetHostProjectileVisualSpawnCodec { private const byte Version = 1; public static void Write(NetDataWriter w, NetHostProjectileVisualSpawn evt) { //IL_0077: 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_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) w.Put((byte)1); w.Put(evt.ChapterName ?? ""); w.Put(evt.LevelIndex); w.Put(evt.HasLevelSeed); if (evt.HasLevelSeed) { w.Put(evt.LevelSeed); } w.Put(evt.HostSpawnIndex); w.Put(evt.UnitIdentifier ?? ""); w.Put(evt.Sequence); w.Put(evt.Origin.x); w.Put(evt.Origin.y); w.Put(evt.Origin.z); w.Put(evt.Velocity.x); w.Put(evt.Velocity.y); w.Put(evt.Velocity.z); w.Put(evt.Lifetime); w.Put(evt.ProjectileKind ?? ""); w.Put(evt.SentAt); } public static bool TryRead(NetDataReader r, out NetHostProjectileVisualSpawn evt) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) evt = new NetHostProjectileVisualSpawn(); try { if (r.GetByte() != 1) { return false; } evt.ChapterName = r.GetString(); evt.LevelIndex = r.GetInt(); evt.HasLevelSeed = r.GetBool(); if (evt.HasLevelSeed) { evt.LevelSeed = r.GetInt(); } evt.HostSpawnIndex = r.GetInt(); evt.UnitIdentifier = r.GetString(); evt.Sequence = r.GetInt(); evt.Origin = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); evt.Velocity = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); evt.Lifetime = r.GetFloat(); evt.ProjectileKind = r.GetString(); evt.SentAt = r.GetFloat(); return true; } catch { return false; } } } internal sealed class NetLevelManifestHeader { public int ManifestVersion { get; set; } = 1; public string Role { get; set; } = ""; public string SceneName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public int GenerationRevision { get; set; } public int RoomCount { get; set; } public int UnitCount { get; set; } public int CombatEnemyCount { get; set; } public int SpecialEventCount { get; set; } public uint GenerationHash { get; set; } public uint RuntimeHash { get; set; } public float BuiltAt { get; set; } } internal sealed class NetLevelManifestRoom { public int RoomIndex { get; set; } public string RoomName { get; set; } = ""; public bool HasPosition { get; set; } public Vector3 Position { get; set; } public int ChildCount { get; set; } } internal sealed class NetLevelManifestUnit { public int ManifestIndex { get; set; } public int SpawnIndex { get; set; } public string UnitIdentifier { get; set; } = ""; public string ActorName { get; set; } = ""; public string GameObjectName { get; set; } = ""; public int SyncCategory { get; set; } public string Category { get; set; } = ""; public bool IsCombatEnemy { get; set; } public bool HasPosition { get; set; } public Vector3 Position { get; set; } public bool HasInitialPosition { get; set; } public Vector3 InitialPosition { get; set; } public string ModifierFlags { get; set; } = ""; public uint ComponentFingerprint { get; set; } public bool IsDead { get; set; } } internal sealed class NetLevelManifestSpecial { public string Type { get; set; } = ""; public string Name { get; set; } = ""; public int SyncCategory { get; set; } public bool HasPosition { get; set; } public Vector3 Position { get; set; } } internal sealed class NetLevelManifest { public NetLevelManifestHeader Header { get; set; } = new NetLevelManifestHeader(); public List Rooms { get; set; } = new List(); public List Units { get; set; } = new List(); public List Specials { get; set; } = new List(); public string SceneKey { get { if (!string.IsNullOrWhiteSpace(Header.SceneName)) { return $"{Header.SceneName}:{Header.LevelIndex}"; } return ":-1"; } } } internal static class NetLevelManifestCodec { public static void Write(NetDataWriter w, NetLevelManifest m) { //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_026c: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: Unknown result type (might be due to invalid IL or missing references) //IL_0391: Unknown result type (might be due to invalid IL or missing references) //IL_03a3: Unknown result type (might be due to invalid IL or missing references) //IL_03b5: Unknown result type (might be due to invalid IL or missing references) NetLevelManifestHeader header = m.Header; w.Put(header.ManifestVersion); w.Put(header.Role ?? ""); w.Put(header.SceneName ?? ""); w.Put(header.LevelIndex); w.Put(header.HasLevelSeed); w.Put(header.LevelSeed); w.Put(header.GenerationRevision); w.Put(header.RoomCount); w.Put(header.UnitCount); w.Put(header.CombatEnemyCount); w.Put(header.SpecialEventCount); w.Put(header.GenerationHash); w.Put(header.RuntimeHash); w.Put(header.BuiltAt); w.Put(m.Rooms.Count); foreach (NetLevelManifestRoom room in m.Rooms) { w.Put(room.RoomIndex); w.Put(room.RoomName ?? ""); w.Put(room.HasPosition); if (room.HasPosition) { w.Put(room.Position.x); w.Put(room.Position.y); w.Put(room.Position.z); } w.Put(room.ChildCount); } w.Put(m.Units.Count); foreach (NetLevelManifestUnit unit in m.Units) { w.Put(unit.ManifestIndex); w.Put(unit.SpawnIndex); w.Put(unit.UnitIdentifier ?? ""); w.Put(unit.ActorName ?? ""); w.Put(unit.GameObjectName ?? ""); w.Put(unit.SyncCategory); w.Put(unit.Category ?? ""); w.Put(unit.IsCombatEnemy); w.Put(unit.HasPosition); if (unit.HasPosition) { w.Put(unit.Position.x); w.Put(unit.Position.y); w.Put(unit.Position.z); } w.Put(unit.HasInitialPosition); if (unit.HasInitialPosition) { w.Put(unit.InitialPosition.x); w.Put(unit.InitialPosition.y); w.Put(unit.InitialPosition.z); } w.Put(unit.ModifierFlags ?? ""); w.Put(unit.ComponentFingerprint); w.Put(unit.IsDead); } w.Put(m.Specials.Count); foreach (NetLevelManifestSpecial special in m.Specials) { w.Put(special.Type ?? ""); w.Put(special.Name ?? ""); w.Put(special.SyncCategory); w.Put(special.HasPosition); if (special.HasPosition) { w.Put(special.Position.x); w.Put(special.Position.y); w.Put(special.Position.z); } } } public static bool TryRead(NetDataReader r, out NetLevelManifest m) { //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: Unknown result type (might be due to invalid IL or missing references) m = null; try { NetLevelManifest netLevelManifest = new NetLevelManifest(); NetLevelManifestHeader header = netLevelManifest.Header; header.ManifestVersion = r.GetInt(); header.Role = r.GetString(); header.SceneName = r.GetString(); header.LevelIndex = r.GetInt(); header.HasLevelSeed = r.GetBool(); header.LevelSeed = r.GetInt(); header.GenerationRevision = r.GetInt(); header.RoomCount = r.GetInt(); header.UnitCount = r.GetInt(); header.CombatEnemyCount = r.GetInt(); header.SpecialEventCount = r.GetInt(); header.GenerationHash = r.GetUInt(); header.RuntimeHash = r.GetUInt(); header.BuiltAt = r.GetFloat(); int num = r.GetInt(); for (int i = 0; i < num; i++) { NetLevelManifestRoom netLevelManifestRoom = new NetLevelManifestRoom { RoomIndex = r.GetInt(), RoomName = r.GetString(), HasPosition = r.GetBool() }; if (netLevelManifestRoom.HasPosition) { netLevelManifestRoom.Position = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } netLevelManifestRoom.ChildCount = r.GetInt(); netLevelManifest.Rooms.Add(netLevelManifestRoom); } int num2 = r.GetInt(); for (int j = 0; j < num2; j++) { NetLevelManifestUnit netLevelManifestUnit = new NetLevelManifestUnit { ManifestIndex = r.GetInt(), SpawnIndex = r.GetInt(), UnitIdentifier = r.GetString(), ActorName = r.GetString(), GameObjectName = r.GetString(), SyncCategory = r.GetInt(), Category = r.GetString(), IsCombatEnemy = r.GetBool(), HasPosition = r.GetBool() }; if (netLevelManifestUnit.HasPosition) { netLevelManifestUnit.Position = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } netLevelManifestUnit.HasInitialPosition = r.GetBool(); if (netLevelManifestUnit.HasInitialPosition) { netLevelManifestUnit.InitialPosition = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } netLevelManifestUnit.ModifierFlags = r.GetString(); netLevelManifestUnit.ComponentFingerprint = r.GetUInt(); netLevelManifestUnit.IsDead = r.GetBool(); netLevelManifest.Units.Add(netLevelManifestUnit); } int num3 = r.GetInt(); for (int k = 0; k < num3; k++) { NetLevelManifestSpecial netLevelManifestSpecial = new NetLevelManifestSpecial { Type = r.GetString(), Name = r.GetString(), SyncCategory = r.GetInt(), HasPosition = r.GetBool() }; if (netLevelManifestSpecial.HasPosition) { netLevelManifestSpecial.Position = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } netLevelManifest.Specials.Add(netLevelManifestSpecial); } m = netLevelManifest; return true; } catch { return false; } } } internal sealed class NetPlayerHeldWeapon { public string PeerId { get; set; } = ""; public bool HasWeapon { get; set; } public ushort WeaponItemId { get; set; } public ushort[] AttachmentItemIds { get; set; } = Array.Empty(); public float SentAt { get; set; } public string Signature() { if (!HasWeapon) { return "none"; } ushort[] array = (ushort[])(AttachmentItemIds ?? Array.Empty()).Clone(); Array.Sort(array); return WeaponItemId + ":" + string.Join(",", array); } } internal static class NetPlayerHeldWeaponCodec { private const byte Version = 1; private const int MaxAttachments = 16; public static void Write(NetDataWriter w, NetPlayerHeldWeapon m) { w.Put((byte)1); w.Put(m.PeerId ?? ""); w.Put(m.HasWeapon); w.Put(m.WeaponItemId); ushort[] array = m.AttachmentItemIds ?? Array.Empty(); int num = array.Length; if (num > 16) { num = 16; } w.Put((byte)num); for (int i = 0; i < num; i++) { w.Put(array[i]); } w.Put(m.SentAt); } public static bool TryRead(NetDataReader r, out NetPlayerHeldWeapon m) { m = new NetPlayerHeldWeapon(); try { if (r.GetByte() != 1) { return false; } m.PeerId = r.GetString(); m.HasWeapon = r.GetBool(); m.WeaponItemId = r.GetUShort(); int num = r.GetByte(); if (num < 0 || num > 16) { return false; } ushort[] array = new ushort[num]; for (int i = 0; i < num; i++) { array[i] = r.GetUShort(); } m.AttachmentItemIds = array; m.SentAt = r.GetFloat(); return true; } catch { return false; } } } internal static class NetPlayerLifeManager { internal sealed class PeerCombatPosition { public string PeerId = ""; public string PlayerName = ""; public Vector3 Position; public string SceneKey = ""; } private sealed class PeerLife { public string PeerId = ""; public string PlayerName = ""; public NetPlayerLifeStateKind Kind; public bool HasPosition; public Vector3 Position; public string SceneKey = ""; public bool HasLevelSeed; public int LevelSeed; public int Sequence; public float LastUpdatedAt; public bool IsDownOrDead { get { if (Kind != NetPlayerLifeStateKind.Downed) { return Kind == NetPlayerLifeStateKind.NativeDeathCommit; } return true; } } } internal enum RescueEndReason { None, Completed, Cancelled } internal struct RescueDisplayState { public string RescuerPeerId; public string TargetPeerId; public float Progress; public bool Active; public RescueEndReason LastEndReason; } private sealed class ActiveRescueRecord { public string RescuerPeerId = ""; public string TargetPeerId = ""; public float StartedAt; public float LastBroadcastAt; } private const int StatusCurrentHealth = 92; private const int AttributeMaxHealth = 60; private static readonly Dictionary PeerStates = new Dictionary(); private static readonly Dictionary LastLogByKey = new Dictionary(); private static object? _localPlayerUnit; private static Transform? _localTransform; private static bool _localDowned; private static bool _localNativeDeathCommitted; private static bool _localDeathRespawnActive; private static float _localDeathRespawnStartedAt; private static bool _healthDiagDumped; private static bool _localControlLocksApplied; private static readonly string[] DownedInputBlacklist = new string[3] { "PlayerMovement", "Weapon", "Inventory" }; private static float _localDownedAt; private static int _nativeDeathBypassDepth; public static int HostDamageApplyDepth; private static int _sequence; private static string _activeReviveTarget = ""; private static RescueDisplayState _rescueDisplay; private static readonly Dictionary _hostActiveRescues = new Dictionary(); private static readonly List _rescueTickScratch = new List(); private const float RescueProgressBroadcastIntervalSeconds = 0.12f; private static readonly Dictionary _enemyDmgPending = new Dictionary(); private static readonly Dictionary _enemyDmgLastSentAt = new Dictionary(); private static readonly Dictionary _enemyDmgLastPos = new Dictionary(); private static readonly Dictionary _enemyDmgLastReason = new Dictionary(); private static readonly List _enemyDmgFlushScratch = new List(); private static object? _cachedHostileSource; private static float _cachedHostileSourceAt; private const float HostileSourceCacheSeconds = 2f; private static MethodInfo? _modifyStatusMethod; private static Type? _entityAttributesType; private static bool _elementalStatusResolveFailed; public static RescueDisplayState CurrentRescueDisplay => _rescueDisplay; public static string LocalPeerId => GetLocalPeerId(); public static bool IsLocalDeathRespawnInProgress => _localDeathRespawnActive; public static string GetKnownPeerDisplayName(string peerId) { if (string.IsNullOrWhiteSpace(peerId)) { return ""; } if (peerId == GetLocalPeerId()) { return Plugin.Cfg.PlayerName.Value; } if (!PeerStates.TryGetValue(peerId, out PeerLife value) || string.IsNullOrWhiteSpace(value.PlayerName)) { return peerId; } return value.PlayerName; } public static bool TryGetNearestDownedPeerHint(out string peerId, out string playerName, out float distance) { peerId = ""; playerName = ""; distance = 0f; if (!IsEnabled() || _localDowned || _localNativeDeathCommitted) { return false; } if (!TryFindNearestDownedPeer(out PeerLife target, out float distance2)) { return false; } peerId = target.PeerId; playerName = (string.IsNullOrWhiteSpace(target.PlayerName) ? target.PeerId : target.PlayerName); distance = distance2; return true; } public static void ClearLevelScoped(string source) { if (_localControlLocksApplied) { ApplyLocalControlLocks(locked: false); } _localDowned = false; _localNativeDeathCommitted = false; _localDownedAt = 0f; _activeReviveTarget = ""; _rescueDisplay = default(RescueDisplayState); _hostActiveRescues.Clear(); PeerStates.Clear(); if (Plugin.Cfg.EnableCoopPlayerDownedRevive.Value && Plugin.Cfg.LogPlayerLifeSync.Value) { NetLogger.Info("[PlayerLife] Cleared level-scoped player life state source=" + source); } } public static void Tick() { if (IsEnabled()) { RefreshLocalPositionCache(); HandleDownedTimeout(); HandleReviveHoldInput(); if (NetConfig.GetMode() == NetMode.Host) { TickHostActiveRescues(); } UpdateDeathRespawnGuard(); FlushPendingEnemyToClientDamage(); } } public static void ReportLocalPlayerObject(object? player) { if (player == null) { return; } object obj = ResolvePlayerUnit(player); if (obj != null) { _localPlayerUnit = obj; _localTransform = ExtractTransform(obj) ?? ExtractTransform(player); if (Plugin.Cfg.EnableCoopPlayerDownedRevive.Value && Plugin.Cfg.LogPlayerLifeSync.Value) { NetLogger.Info("[PlayerLife] Local player unit captured from " + player.GetType().Name + ": " + DescribeObject(obj)); } } else { Transform val = ExtractTransform(player); if ((Object)(object)val != (Object)null) { _localTransform = val; } } } public static bool TryBlockLocalPlayerDeath(object? unit, string source, out string detail) { detail = ""; if (!IsEnabled()) { return false; } if (unit == null) { return false; } if (_nativeDeathBypassDepth > 0) { return false; } if (!LooksLikePlayerUnit(unit)) { return false; } _localPlayerUnit = unit; _localTransform = ExtractTransform(unit) ?? _localTransform; if (_localNativeDeathCommitted) { detail = "native death already committed"; return false; } if (_localDowned) { StabilizeDownedLocalPlayer("repeat Unit.Die while already downed"); detail = "already downed; suppressed repeated Unit.Die"; return true; } if (ShouldAllowImmediateNativeDeath(out string reason)) { _localNativeDeathCommitted = true; MarkLocalDeathRespawnStarted("immediate native death/" + reason); if (NetConfig.GetMode() == NetMode.Host && reason.StartsWith("all other", StringComparison.OrdinalIgnoreCase)) { NetGameplaySyncBridge.ReportHostPlayerLifeStateToAll(BuildHostState(NetPlayerLifeStateKind.NativeDeathCommit, "*", source + "/" + reason)); } else { PublishLocalLifeState(NetPlayerLifeStateKind.NativeDeathCommit, "", source + "/" + reason); } detail = reason; return false; } EnterLocalDownedState(unit, source); detail = "entered co-op downed state"; return true; } public static void ReportRemotePlayerTransform(NetPlayerTransformState state) { //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 (IsEnabled() && !string.IsNullOrWhiteSpace(state.PeerId) && !(state.PeerId == GetLocalPeerId())) { PeerLife orCreatePeer = GetOrCreatePeer(state.PeerId); orCreatePeer.PlayerName = state.PlayerName; orCreatePeer.HasPosition = true; orCreatePeer.Position = state.Position; orCreatePeer.SceneKey = state.SceneCompareKey(); orCreatePeer.HasLevelSeed = state.HasLevelSeed; orCreatePeer.LevelSeed = state.LevelSeed; orCreatePeer.LastUpdatedAt = Time.realtimeSinceStartup; } } public static void HandleNetworkState(NetPlayerLifeState state, bool receivedOnHost) { if (!IsEnabled() || state == null) { return; } string localPeerId = GetLocalPeerId(); bool flag = state.TargetPeerId == localPeerId || (state.TargetPeerId == "*" && state.Kind == NetPlayerLifeStateKind.NativeDeathCommit); if (state.Kind == NetPlayerLifeStateKind.RescueHoldStart) { if (receivedOnHost) { HandleRescueHoldStartOnHost(state); } } else if (state.Kind == NetPlayerLifeStateKind.RescueHoldStop) { if (receivedOnHost) { HandleRescueHoldStopOnHost(state); } } else if (state.Kind == NetPlayerLifeStateKind.RescueProgress) { ApplyRescueDisplay(state.SourcePeerId, state.TargetPeerId, state.Progress, active: true, RescueEndReason.None); } else if (state.Kind == NetPlayerLifeStateKind.RescueCancelled) { ApplyRescueDisplay(state.SourcePeerId, state.TargetPeerId, 0f, active: false, RescueEndReason.Cancelled); } else if (state.Kind == NetPlayerLifeStateKind.ReviveAccepted) { if (flag) { ReviveLocalPlayer("revive accepted by host"); } if (!string.IsNullOrWhiteSpace(state.TargetPeerId)) { MarkPeerAlive(state.TargetPeerId, state); } if (_rescueDisplay.Active && _rescueDisplay.TargetPeerId == state.TargetPeerId) { ApplyRescueDisplay(_rescueDisplay.RescuerPeerId, _rescueDisplay.TargetPeerId, 1f, active: false, RescueEndReason.Completed); } } else if (state.Kind == NetPlayerLifeStateKind.NativeDeathCommit) { bool flag2 = !string.IsNullOrWhiteSpace(state.SourcePeerId) && state.SourcePeerId == localPeerId; if (flag || (string.IsNullOrWhiteSpace(state.TargetPeerId) && flag2)) { CommitLocalNativeDeath("network native death commit"); } string text = ((string.IsNullOrWhiteSpace(state.TargetPeerId) || state.TargetPeerId == "*") ? state.SourcePeerId : state.TargetPeerId); if (!string.IsNullOrWhiteSpace(text)) { MarkPeerState(text, state, NetPlayerLifeStateKind.NativeDeathCommit); } if (receivedOnHost) { CheckAllDownedOnHost("native death commit update"); } } else if (state.Kind == NetPlayerLifeStateKind.HostDamageRequest) { if (flag) { ApplyHostDamageToLocalPlayer(state); } } else if (state.Kind == NetPlayerLifeStateKind.DamageTakenReport) { if (receivedOnHost) { NetRunStatsManager.RecordDamageTaken(state.SourcePeerId, state.DamageAmount); } } else { string text2 = (string.IsNullOrWhiteSpace(state.SourcePeerId) ? state.TargetPeerId : state.SourcePeerId); if (!string.IsNullOrWhiteSpace(text2)) { MarkPeerState(text2, state, state.Kind); } if (receivedOnHost && state.Kind == NetPlayerLifeStateKind.Downed) { CheckAllDownedOnHost("remote downed update"); } } } public static IReadOnlyCollection GetKnownDownedPeerIds() { return (from p in PeerStates.Values where p.Kind == NetPlayerLifeStateKind.Downed select p.PeerId).ToList().AsReadOnly(); } public static bool IsPeerDownOrDead(string peerId) { if (string.IsNullOrWhiteSpace(peerId)) { return false; } if (PeerStates.TryGetValue(peerId, out PeerLife value)) { return value.IsDownOrDead; } return false; } public static bool IsPeerDowned(string peerId) { if (string.IsNullOrWhiteSpace(peerId)) { return false; } if (PeerStates.TryGetValue(peerId, out PeerLife value)) { return value.Kind == NetPlayerLifeStateKind.Downed; } return false; } public static IReadOnlyList GetHostKnownAliveRemotePeerPositions() { //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if (NetConfig.GetMode() != NetMode.Host) { return list.AsReadOnly(); } if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || !state.HasLevel) { return list.AsReadOnly(); } string localPeerId = GetLocalPeerId(); foreach (PeerLife value in PeerStates.Values) { if (value != null && !(value.PeerId == localPeerId) && !value.IsDownOrDead && value.HasPosition && !string.IsNullOrWhiteSpace(value.SceneKey) && !(value.SceneKey != state.SceneKey()) && (!Plugin.Cfg.RequireSameLevelSeedForSceneMatch.Value || !value.HasLevelSeed || !state.HasLevelSeed || value.LevelSeed == state.LevelSeed)) { list.Add(new PeerCombatPosition { PeerId = value.PeerId, PlayerName = value.PlayerName, Position = value.Position, SceneKey = value.SceneKey }); } } return list.AsReadOnly(); } public static void ReportHostAuthoritativeEnemyDamage(string targetPeerId, float damage, string reason, Vector3 hitPosition, int damageTypeInt = 0) { //IL_0090: 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_0112: Unknown result type (might be due to invalid IL or missing references) if (NetConfig.GetMode() != NetMode.Host || string.IsNullOrWhiteSpace(targetPeerId) || targetPeerId == GetLocalPeerId() || damage <= 0f) { return; } float num = 0f; try { if (Plugin.Cfg.EnableCombatEventCoalescing.Value) { num = Plugin.Cfg.EnemyToClientDamageCoalesceSeconds.Value; } } catch { } if (num <= 0f) { SendEnemyDamageToClient(targetPeerId, damage, reason, hitPosition, damageTypeInt); return; } string key = targetPeerId + "|" + damageTypeInt; float realtimeSinceStartup = Time.realtimeSinceStartup; _enemyDmgLastPos[key] = hitPosition; _enemyDmgLastReason[key] = reason ?? "enemy damage"; _enemyDmgLastSentAt.TryGetValue(key, out var value); if (realtimeSinceStartup - value < num) { _enemyDmgPending.TryGetValue(key, out var value2); _enemyDmgPending[key] = value2 + damage; return; } _enemyDmgLastSentAt[key] = realtimeSinceStartup; _enemyDmgPending.TryGetValue(key, out var value3); if (value3 > 0f) { _enemyDmgPending.Remove(key); } SendEnemyDamageToClient(targetPeerId, damage + value3, reason, hitPosition, damageTypeInt); } public static void FlushPendingEnemyToClientDamage() { //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) if (NetConfig.GetMode() != NetMode.Host || _enemyDmgPending.Count == 0) { return; } float num; try { num = Plugin.Cfg.EnemyToClientDamageCoalesceSeconds.Value; } catch { num = 0.1f; } if (num <= 0f) { _enemyDmgPending.Clear(); return; } float realtimeSinceStartup = Time.realtimeSinceStartup; _enemyDmgFlushScratch.Clear(); foreach (KeyValuePair item in _enemyDmgPending) { _enemyDmgLastSentAt.TryGetValue(item.Key, out var value); if (realtimeSinceStartup - value >= num) { _enemyDmgFlushScratch.Add(item.Key); } } foreach (string item2 in _enemyDmgFlushScratch) { float num2 = _enemyDmgPending[item2]; _enemyDmgPending.Remove(item2); if (!(num2 <= 0f)) { int num3 = item2.LastIndexOf('|'); string targetPeerId = ((num3 > 0) ? item2.Substring(0, num3) : item2); int result = 0; if (num3 > 0) { int.TryParse(item2.Substring(num3 + 1), out result); } Vector3 value2; Vector3 hitPosition = (_enemyDmgLastPos.TryGetValue(item2, out value2) ? value2 : Vector3.zero); string value3; string reason = (_enemyDmgLastReason.TryGetValue(item2, out value3) ? value3 : "enemy damage (coalesced)"); _enemyDmgLastSentAt[item2] = realtimeSinceStartup; SendEnemyDamageToClient(targetPeerId, num2, reason, hitPosition, result); } } } private static void SendEnemyDamageToClient(string targetPeerId, float damage, string reason, Vector3 hitPosition, int damageTypeInt) { //IL_009f: 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_010d: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) NetRunStateBridge.TryGetLocalRunState(out NetRunState state); int num = ++_sequence; NetGameplaySyncBridge.ReportHostPlayerLifeStateToAll(new NetPlayerLifeState { EventId = $"host:enemyDamage:{num}:{targetPeerId}", SourcePeerId = "host", TargetPeerId = targetPeerId, PlayerName = Plugin.Cfg.PlayerName.Value, ChapterName = state.ChapterName, LevelIndex = state.LevelIndex, HasLevelSeed = state.HasLevelSeed, LevelSeed = state.LevelSeed, Sequence = num, Kind = NetPlayerLifeStateKind.HostDamageRequest, HasPosition = true, Position = hitPosition, SentAt = Time.realtimeSinceStartup, Reason = (reason ?? "enemy ranged damage"), DamageAmount = damage, DamageType = damageTypeInt }); if (Plugin.Cfg.LogEnemyHostDamageAuthority.Value) { NetLogger.Info($"[EnemyDamageAuthority] Host sent damage target={targetPeerId} amount={damage:F1} pos=({hitPosition.x:F2},{hitPosition.y:F2},{hitPosition.z:F2}) reason={reason}"); } } public static bool ShouldSuppressLocalPlayerControls() { if (IsEnabled() && _localDowned) { return !_localNativeDeathCommitted; } return false; } private static void MarkLocalDeathRespawnStarted(string reason) { if (!_localDeathRespawnActive) { _localDeathRespawnActive = true; _localDeathRespawnStartedAt = Time.realtimeSinceStartup; NetClientLoadGate.NoteLocalDeathRespawnArmed(); if (Plugin.Cfg.LogPlayerLifeSync.Value) { NetLogger.Info("[PlayerLife] Death-respawn guard armed (suppress combat follow until hub) reason=" + reason); } } } private static void UpdateDeathRespawnGuard() { if (!_localDeathRespawnActive) { return; } NetRunState state; if (Time.realtimeSinceStartup - _localDeathRespawnStartedAt > 30f) { _localDeathRespawnActive = false; if (Plugin.Cfg.LogPlayerLifeSync.Value) { NetLogger.Info("[PlayerLife] Death-respawn guard cleared (timeout)"); } } else if (!_localDowned && !_localNativeDeathCommitted && NetRunStateBridge.TryGetLocalRunState(out state) && state.HasLevel && (NetSceneClassify.IsHubOrSafeZoneChapter(state.ChapterName) || NetSceneClassify.IsHubOrSafeZoneGraph(state.LevelGenerator))) { _localDeathRespawnActive = false; if (Plugin.Cfg.LogPlayerLifeSync.Value) { NetLogger.Info("[PlayerLife] Death-respawn guard cleared; settled in hub chapter=" + state.ChapterName); } } } public static bool ShouldBlockLocalPlayerDamage(object? unit) { if (!ShouldSuppressLocalPlayerControls()) { return false; } if (unit == null) { return false; } if (!LooksLikePlayerUnit(unit)) { return false; } StabilizeDownedLocalPlayer("blocked damage while downed"); return true; } public static bool IsLocalPlayerUnit(object? unit) { if (unit == null) { return false; } return LooksLikePlayerUnit(unit); } public static bool IsDownedLocalPlayerUnit(object? unit) { if (unit == null || !_localDowned || _localNativeDeathCommitted) { return false; } object obj = ResolveCurrentLocalPlayerUnit(); if (obj != null) { return unit == obj; } return false; } private static void EnterLocalDownedState(object unit, string source) { StopLocalRescueHold(); _localDowned = true; _localNativeDeathCommitted = false; _localDownedAt = Time.realtimeSinceStartup; if (NetConfig.GetMode() == NetMode.Host) { NetRunStatsManager.ResolveHostOwnPlayerDowned(); } StabilizeDownedLocalPlayer("enter downed"); PublishLocalLifeState(NetPlayerLifeStateKind.Downed, "", source); MarkLocalState(NetPlayerLifeStateKind.Downed, source); NetLogger.Info("[PlayerLife] Local player death intercepted; entered downed state wait=" + ((Plugin.Cfg.PlayerDownedRescueTimeoutSeconds.Value <= 0f) ? "infinite" : (Plugin.Cfg.PlayerDownedRescueTimeoutSeconds.Value.ToString("F1") + "s")) + " source=" + source); if (NetConfig.GetMode() == NetMode.Host) { CheckAllDownedOnHost("local downed"); } } private static void HandleDownedTimeout() { if (!_localDowned || _localNativeDeathCommitted) { return; } float value = Plugin.Cfg.PlayerDownedRescueTimeoutSeconds.Value; if (!(value <= 0f)) { float num = Time.realtimeSinceStartup - _localDownedAt; if (!(num < value)) { PublishLocalLifeState(NetPlayerLifeStateKind.NativeDeathCommit, GetLocalPeerId(), "downed timeout"); CommitLocalNativeDeath($"downed timeout elapsed={num:F1}s"); } } } private static void HandleReviveHoldInput() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (_localDowned || _localNativeDeathCommitted || (Object)(object)_localTransform == (Object)null) { return; } KeyboardShortcut value = Plugin.Cfg.PlayerReviveHoldKey.Value; if (!((KeyboardShortcut)(ref value)).IsPressed() || !TryFindNearestDownedPeer(out PeerLife target, out float distance)) { StopLocalRescueHold(); } else if (!(_activeReviveTarget == target.PeerId)) { if (!string.IsNullOrEmpty(_activeReviveTarget)) { StopLocalRescueHold(); } _activeReviveTarget = target.PeerId; SendRescueHoldStart(target.PeerId, distance); } } private static void SendRescueHoldStart(string targetPeerId, float distance) { NetPlayerLifeState netPlayerLifeState = BuildLocalState(NetPlayerLifeStateKind.RescueHoldStart, targetPeerId, $"hold revive distance={distance:F2}m"); NetGameplaySyncBridge.ReportPlayerLifeState(netPlayerLifeState); if (NetConfig.GetMode() == NetMode.Host) { HandleRescueHoldStartOnHost(netPlayerLifeState); } if (Plugin.Cfg.LogPlayerLifeSync.Value) { NetLogger.Info($"[PlayerLife] Rescue hold start reported target={targetPeerId} distance={distance:F2}m"); } } private static void StopLocalRescueHold() { string activeReviveTarget = _activeReviveTarget; _activeReviveTarget = ""; if (!string.IsNullOrEmpty(activeReviveTarget)) { NetPlayerLifeState netPlayerLifeState = BuildLocalState(NetPlayerLifeStateKind.RescueHoldStop, activeReviveTarget, "hold released/lost target"); NetGameplaySyncBridge.ReportPlayerLifeState(netPlayerLifeState); if (NetConfig.GetMode() == NetMode.Host) { HandleRescueHoldStopOnHost(netPlayerLifeState); } } } private static bool TryValidateRescueEligibility(string rescuerPeerId, string targetPeerId, out string reason) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) reason = ""; if (!TryGetPeerLife(targetPeerId, out PeerLife peer) || peer.Kind != NetPlayerLifeStateKind.Downed) { reason = "target not downed"; return false; } if (TryGetPeerLife(rescuerPeerId, out PeerLife peer2) && peer2.IsDownOrDead) { reason = "rescuer is not alive"; return false; } if (Plugin.Cfg.RequireReviveDistanceValidationOnHost.Value) { if (!TryGetPeerPosition(rescuerPeerId, out var position) || !TryGetPeerPosition(targetPeerId, out var position2)) { reason = "missing position"; return false; } float num = Vector3.Distance(position, position2); float num2 = Math.Max(0.5f, Plugin.Cfg.PlayerReviveDistance.Value + 1f); if (num > num2) { reason = $"too far ({num:F2}m > {num2:F2}m)"; return false; } } return true; } private static void HandleRescueHoldStartOnHost(NetPlayerLifeState request) { string sourcePeerId = request.SourcePeerId; string targetPeerId = request.TargetPeerId; if (string.IsNullOrWhiteSpace(sourcePeerId) || string.IsNullOrWhiteSpace(targetPeerId) || sourcePeerId == targetPeerId) { NetLogger.Warn("[PlayerLife] Reject rescue hold start: malformed " + request.ToCompactString()); } else { if (_hostActiveRescues.ContainsKey(targetPeerId)) { return; } if (!TryValidateRescueEligibility(sourcePeerId, targetPeerId, out string reason)) { if (Plugin.Cfg.LogPlayerLifeSync.Value) { NetLogger.Warn("[PlayerLife] Reject rescue hold start: " + reason + " rescuer=" + sourcePeerId + " target=" + targetPeerId); } return; } float realtimeSinceStartup = Time.realtimeSinceStartup; ActiveRescueRecord activeRescueRecord = new ActiveRescueRecord { RescuerPeerId = sourcePeerId, TargetPeerId = targetPeerId, StartedAt = realtimeSinceStartup, LastBroadcastAt = realtimeSinceStartup }; _hostActiveRescues[targetPeerId] = activeRescueRecord; BroadcastRescueProgress(activeRescueRecord, 0f); if (Plugin.Cfg.LogPlayerLifeSync.Value) { NetLogger.Info("[PlayerLife] Rescue started rescuer=" + sourcePeerId + " target=" + targetPeerId); } } } private static void HandleRescueHoldStopOnHost(NetPlayerLifeState request) { if (_hostActiveRescues.TryGetValue(request.TargetPeerId, out ActiveRescueRecord value) && !(value.RescuerPeerId != request.SourcePeerId)) { CancelHostActiveRescue(value, "rescuer released hold"); } } private static void TickHostActiveRescues() { if (_hostActiveRescues.Count == 0) { return; } _rescueTickScratch.Clear(); _rescueTickScratch.AddRange(_hostActiveRescues.Keys); float num = Math.Max(0.1f, Plugin.Cfg.PlayerReviveHoldSeconds.Value); float realtimeSinceStartup = Time.realtimeSinceStartup; foreach (string item in _rescueTickScratch) { if (!_hostActiveRescues.TryGetValue(item, out ActiveRescueRecord value)) { continue; } if (!TryValidateRescueEligibility(value.RescuerPeerId, item, out string reason)) { CancelHostActiveRescue(value, reason); continue; } float num2 = Mathf.Clamp01((realtimeSinceStartup - value.StartedAt) / num); if (num2 >= 1f) { CompleteHostActiveRescue(value); } else if (realtimeSinceStartup - value.LastBroadcastAt >= 0.12f) { value.LastBroadcastAt = realtimeSinceStartup; BroadcastRescueProgress(value, num2); } } } private static void BroadcastRescueProgress(ActiveRescueRecord record, float progress) { NetPlayerLifeState netPlayerLifeState = BuildHostState(NetPlayerLifeStateKind.RescueProgress, record.TargetPeerId, "rescue progress"); netPlayerLifeState.SourcePeerId = record.RescuerPeerId; netPlayerLifeState.Progress = progress; NetGameplaySyncBridge.ReportHostPlayerLifeStateToAll(netPlayerLifeState); ApplyRescueDisplay(record.RescuerPeerId, record.TargetPeerId, progress, active: true, RescueEndReason.None); } private static void CancelHostActiveRescue(ActiveRescueRecord record, string reason) { _hostActiveRescues.Remove(record.TargetPeerId); NetPlayerLifeState netPlayerLifeState = BuildHostState(NetPlayerLifeStateKind.RescueCancelled, record.TargetPeerId, reason); netPlayerLifeState.SourcePeerId = record.RescuerPeerId; NetGameplaySyncBridge.ReportHostPlayerLifeStateToAll(netPlayerLifeState); ApplyRescueDisplay(record.RescuerPeerId, record.TargetPeerId, 0f, active: false, RescueEndReason.Cancelled); if (Plugin.Cfg.LogPlayerLifeSync.Value) { NetLogger.Info("[PlayerLife] Rescue cancelled rescuer=" + record.RescuerPeerId + " target=" + record.TargetPeerId + " reason=" + reason); } } private static void CompleteHostActiveRescue(ActiveRescueRecord record) { _hostActiveRescues.Remove(record.TargetPeerId); NetPlayerLifeState state = BuildHostState(NetPlayerLifeStateKind.ReviveAccepted, record.TargetPeerId, "revived by " + record.RescuerPeerId); MarkPeerAlive(record.TargetPeerId, state); NetGameplaySyncBridge.ReportHostPlayerLifeStateToAll(state); NetRunStatsManager.RecordRescue(record.RescuerPeerId); ApplyRescueDisplay(record.RescuerPeerId, record.TargetPeerId, 1f, active: false, RescueEndReason.Completed); if (record.TargetPeerId == GetLocalPeerId()) { ReviveLocalPlayer("revived by " + record.RescuerPeerId); } NetLogger.Info("[PlayerLife] Revive accepted rescuer=" + record.RescuerPeerId + " target=" + record.TargetPeerId); } private static void ApplyRescueDisplay(string rescuerPeerId, string targetPeerId, float progress, bool active, RescueEndReason endReason) { string localPeerId = GetLocalPeerId(); if (!(rescuerPeerId != localPeerId) || !(targetPeerId != localPeerId)) { _rescueDisplay = new RescueDisplayState { RescuerPeerId = (rescuerPeerId ?? ""), TargetPeerId = (targetPeerId ?? ""), Progress = Mathf.Clamp01(progress), Active = active, LastEndReason = endReason }; } } private static void CheckAllDownedOnHost(string reason) { if (NetConfig.GetMode() != NetMode.Host) { return; } List list = (from p in NetGameplaySyncBridge.GetKnownPlayerLifePeerIds() where !string.IsNullOrWhiteSpace(p) select p).Distinct().ToList(); if (list.Count <= 1) { return; } foreach (string item in list) { PeerLife peer; if (item == GetLocalPeerId()) { if (!_localDowned && !_localNativeDeathCommitted) { return; } } else if (!TryGetPeerLife(item, out peer) || !peer.IsDownOrDead) { return; } } NetLogger.Info("[PlayerLife] All known players are down/dead; committing native death for all. reason=" + reason + " peers=" + string.Join(",", list)); NetGameplaySyncBridge.ReportHostPlayerLifeStateToAll(BuildHostState(NetPlayerLifeStateKind.NativeDeathCommit, "*", "all players downed")); CommitLocalNativeDeath("all players downed"); } private static void CommitLocalNativeDeath(string reason) { if (_localNativeDeathCommitted && !_localDowned) { return; } object obj = ResolveCurrentLocalPlayerUnit(); if (obj == null) { NetLogger.Warn("[PlayerLife] Cannot commit native death: local player unit missing reason=" + reason); return; } _localNativeDeathCommitted = true; _localDowned = false; MarkLocalDeathRespawnStarted(reason); ApplyLocalControlLocks(locked: false); try { _nativeDeathBypassDepth++; MethodInfo methodInfo = AccessTools.Method(obj.GetType(), "Die", (Type[])null, (Type[])null) ?? AccessTools.Method(AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Units.Unit"), "Die", (Type[])null, (Type[])null); if (methodInfo == null) { NetLogger.Warn("[PlayerLife] Cannot commit native death: Unit.Die not found reason=" + reason); return; } NetLogger.Info("[PlayerLife] Committing original player death: " + reason); methodInfo.Invoke(obj, null); } catch (TargetInvocationException ex) { NetLogger.Warn("[PlayerLife] Original player death invoke failed: " + (ex.InnerException?.Message ?? ex.Message)); } catch (Exception ex2) { NetLogger.Warn("[PlayerLife] Original player death invoke failed: " + ex2.Message); } finally { if (_nativeDeathBypassDepth > 0) { _nativeDeathBypassDepth--; } } } private static bool ReviveLocalPlayer(string reason) { if (!_localDowned || _localNativeDeathCommitted) { return false; } object obj = ResolveCurrentLocalPlayerUnit(); if (obj == null) { NetLogger.Warn("[PlayerLife] Cannot revive local player: local unit missing reason=" + reason); return false; } _localDowned = false; _localNativeDeathCommitted = false; ApplyLocalControlLocks(locked: false); SetUnitState(obj, "Alive"); HealUnitForRevive(obj); InvokeMethod(obj, "SetInvulnerableForDuration", Math.Max(0.1f, Plugin.Cfg.PlayerReviveInvulnerabilitySeconds.Value)); PublishLocalLifeState(NetPlayerLifeStateKind.Alive, "", "revived/" + reason); MarkLocalState(NetPlayerLifeStateKind.Alive, reason); ResetReviveHold(); NetLogger.Info("[PlayerLife] Local player revived: " + reason); return true; } private static object? ResolveAnyHostileEnemy(object playerUnit) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (_cachedHostileSource != null && realtimeSinceStartup - _cachedHostileSourceAt < 2f && _cachedHostileSource != playerUnit) { object? cachedHostileSource = _cachedHostileSource; Object val = (Object)((cachedHostileSource is Object) ? cachedHostileSource : null); if ((val == null || !(val == (Object)null)) && BossDamageReflect.IsValidDamageSource(_cachedHostileSource)) { return _cachedHostileSource; } } try { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); object obj = ((type == null) ? null : AccessTools.Property(type, "Instance")?.GetValue(null, null)); if (obj == null) { return null; } if (TryGetMemberValue(obj, "units") is IEnumerable enumerable) { bool flag = default(bool); foreach (object item in enumerable) { if (item != null && item != playerUnit && BossDamageReflect.IsValidDamageSource(item)) { object obj2 = TryGetMemberValue(item, "isPlayer"); int num; if (obj2 is bool) { flag = (bool)obj2; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) == 0) { _cachedHostileSource = item; _cachedHostileSourceAt = realtimeSinceStartup; return item; } } } } } catch { } return null; } private static int MapDamageTypeToStatus(int damageTypeInt) { return damageTypeInt switch { 2 => 22, 4 => 19, 5 => 25, 9 => 28, 13 => 33, 16 => 102, _ => 0, }; } private static void TryApplyElementalHurtStatus(object unit, int damageTypeInt) { try { if (!Plugin.Cfg.EnableEnemyElementalStatusEffect.Value) { return; } int num = MapDamageTypeToStatus(damageTypeInt); if (num == 0) { return; } object obj = TryGetMemberValue(unit, "Stats"); if (obj == null) { return; } if (_modifyStatusMethod == null && !_elementalStatusResolveFailed) { _entityAttributesType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Stats.EntityAttributes"); _modifyStatusMethod = ((_entityAttributesType == null) ? null : AccessTools.Method(obj.GetType(), "ModifyStatus", new Type[3] { _entityAttributesType, typeof(float), typeof(bool) }, (Type[])null)); if (_modifyStatusMethod == null) { _elementalStatusResolveFailed = true; NetLogger.Warn("[EnemyDamageAuthority] ModifyStatus(EntityAttributes,float,bool) not found — elemental status disabled"); return; } } if (!(_modifyStatusMethod == null) && !(_entityAttributesType == null)) { object obj2 = Enum.ToObject(_entityAttributesType, num); float value = Plugin.Cfg.EnemyElementalStatusAmount.Value; _modifyStatusMethod.Invoke(obj, new object[3] { obj2, value, false }); } } catch (Exception ex) { NetLogger.Warn("[EnemyDamageAuthority] elemental status apply failed: " + ex.GetType().Name + ": " + ex.Message); } } private static void ApplyHostDamageToLocalPlayer(NetPlayerLifeState state) { if (!Plugin.Cfg.LogDamageApplyHitch.Value) { ApplyHostDamageToLocalPlayerInner(state); return; } Stopwatch stopwatch = Stopwatch.StartNew(); try { ApplyHostDamageToLocalPlayerInner(state); } finally { stopwatch.Stop(); double totalMilliseconds = stopwatch.Elapsed.TotalMilliseconds; if (totalMilliseconds >= (double)Plugin.Cfg.DamageApplyHitchThresholdMs.Value) { NetLogger.Info($"[DamageHitch] client apply took {totalMilliseconds:F1}ms reason={state?.Reason} type={state?.DamageType} dmg={state?.DamageAmount ?? 0f:F1}"); } } } internal static void ApplyFriendlyFireDamageToLocalHost(float damage, int damageTypeInt, Vector3 hitPos, string reason) { //IL_002c: 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_0041: Unknown result type (might be due to invalid IL or missing references) if (NetConfig.GetMode() == NetMode.Host && !(damage <= 0f)) { NetPlayerLifeState netPlayerLifeState = BuildLocalState(NetPlayerLifeStateKind.HostDamageRequest, GetLocalPeerId(), reason); netPlayerLifeState.DamageAmount = damage; netPlayerLifeState.DamageType = damageTypeInt; if (hitPos != Vector3.zero) { netPlayerLifeState.HasPosition = true; netPlayerLifeState.Position = hitPos; } ApplyHostDamageToLocalPlayer(netPlayerLifeState); } } private static void ApplyHostDamageToLocalPlayerInner(NetPlayerLifeState state) { if (state == null || !NetRunStateBridge.TryGetLocalRunState(out NetRunState state2) || !state.MatchesScene(state2) || _localNativeDeathCommitted) { return; } object obj = ResolveCurrentLocalPlayerUnit(); if (obj == null) { NetLogger.Warn($"[EnemyDamageAuthority] Cannot apply Host damage: local player unit missing amount={state.DamageAmount:F1} reason={state.Reason}"); return; } float num = state.DamageAmount; if (num <= 0f) { num = Plugin.Cfg.EnemyHostProjectileDamage.Value; } if (num <= 0f) { return; } if (_localDowned) { StabilizeDownedLocalPlayer("host damage while already downed"); return; } float unitCurrentHealth = GetUnitCurrentHealth(obj); if (Plugin.Cfg.ApplyHostPlayerDamageViaReceiveDamage.Value) { object obj2 = ResolveAnyHostileEnemy(obj) ?? obj; int num2 = ((state.DamageType != 0) ? state.DamageType : Plugin.Cfg.EnemyDamageDefaultType.Value); if (num2 == 0) { num2 = 7; } bool flag = false; try { HostDamageApplyDepth++; flag = BossDamageReflect.TryApplyRealDamage(obj, num, num2, obj2, out bool vanillaResult, out string detail); if (Plugin.Cfg.LogEnemyHostDamageAuthority.Value) { NetLogger.Info(string.Format("[EnemyDamageAuthority] via ReceiveDamage amount={0:F1} type={1} ok={2} applied={3} src={4} detail={5} reason={6}", num, num2, flag, vanillaResult, (obj2 == obj) ? "self" : obj2.GetType().Name, detail, state.Reason)); } if (flag && vanillaResult) { TryApplyElementalHurtStatus(obj, num2); } } catch (Exception ex) { NetLogger.Warn("[EnemyDamageAuthority] ReceiveDamage path failed: " + ex.GetType().Name + ": " + ex.Message); } finally { if (HostDamageApplyDepth > 0) { HostDamageApplyDepth--; } } if (flag) { ReportActualDamageTakenToHost(unitCurrentHealth - GetUnitCurrentHealth(obj)); return; } } float num3 = GetUnitCurrentHealth(obj); if (num3 <= 0f) { num3 = GetUnitMaxHealth(obj); } if (num3 <= 0f) { num3 = 100f; } float num4 = num3 - num; if (Plugin.Cfg.LogEnemyHostDamageAuthority.Value) { NetLogger.Info($"[EnemyDamageAuthority] Client applying Host enemy damage amount={num:F1} hp={num3:F1}->{Math.Max(0f, num4):F1} reason={state.Reason}"); } if (num4 <= 0f) { try { HostDamageApplyDepth++; SetUnitCurrentHealth(obj, 0f); } finally { if (HostDamageApplyDepth > 0) { HostDamageApplyDepth--; } } ReportActualDamageTakenToHost(unitCurrentHealth); EnterLocalDownedState(obj, "host enemy damage/" + state.Reason); return; } try { HostDamageApplyDepth++; SetUnitCurrentHealth(obj, num4); } finally { if (HostDamageApplyDepth > 0) { HostDamageApplyDepth--; } } ReportActualDamageTakenToHost(unitCurrentHealth - num4); if (Plugin.Cfg.LogEnemyHostDamageAuthority.Value) { float unitCurrentHealth2 = GetUnitCurrentHealth(obj); NetLogger.Info($"[EnemyDamageAuthority] readback after SetCurrentHealth({num4:F1}) = {unitCurrentHealth2:F1} (max={GetUnitMaxHealth(obj):F1})"); if (!_healthDiagDumped) { _healthDiagDumped = true; try { object obj3 = TryGetMemberValue(obj, "Stats"); NetLogger.Info("[EnemyDamageAuthority] HEALTHDIAG unit=" + obj.GetType().FullName + " stats=" + ((obj3 == null) ? "null" : obj3.GetType().Name) + " statHealth=[" + Match(obj3?.GetType(), new string[3] { "Health", "Status", "Attribute" }) + "]"); NetLogger.Info("[EnemyDamageAuthority] HEALTHDIAG Unit feedback=[" + Match(obj.GetType(), new string[11] { "Damage", "Hit", "Hurt", "Flash", "Shake", "React", "Blood", "Vignette", "Feedback", "Effect", "Pain" }) + "]"); object obj4 = TryGetMemberValue(obj, "PlayerScript"); NetLogger.Info("[EnemyDamageAuthority] HEALTHDIAG Player=" + ((obj4 == null) ? "null" : obj4.GetType().Name) + " feedback=[" + Match(obj4?.GetType(), new string[11] { "Damage", "Hit", "Hurt", "Flash", "Shake", "React", "Blood", "Vignette", "Feedback", "Effect", "Pain" }) + "]"); } catch (Exception ex2) { NetLogger.Warn("[EnemyDamageAuthority] HEALTHDIAG failed: " + ex2.Message); } } } TryInvokeHostDamageFeedback(obj, num); static string Match(Type? t, params string[] kws) { if (!(t == null)) { return string.Join(",", (from m in t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) where !m.IsSpecialName && kws.Any((string k) => m.Name.IndexOf(k, StringComparison.OrdinalIgnoreCase) >= 0) select $"{m.Name}({m.GetParameters().Length})").Distinct().Take(30)); } return ""; } } private static void ReportActualDamageTakenToHost(float actualAmount) { if (!(actualAmount <= 0f) && NetConfig.GetMode() == NetMode.Client) { NetPlayerLifeState netPlayerLifeState = BuildLocalState(NetPlayerLifeStateKind.DamageTakenReport, "", "actual damage taken"); netPlayerLifeState.DamageAmount = actualAmount; NetGameplaySyncBridge.ReportPlayerLifeState(netPlayerLifeState); } } private static void TryInvokeHostDamageFeedback(object unit, float damage) { try { InvokeMethod(unit, "OnHealthChange"); InvokeMethod(unit, "SetInvulnerableForDuration", 0.05f); } catch { } } private static void StabilizeDownedLocalPlayer(string reason) { object localPlayerUnit = _localPlayerUnit; if (localPlayerUnit != null) { SetUnitState(localPlayerUnit, "Incapacitated"); SetUnitCurrentHealth(localPlayerUnit, Math.Max(1f, Plugin.Cfg.PlayerDownedHealthFloor.Value)); InvokeMethod(localPlayerUnit, "SetInvulnerableForDuration", 999999f); ApplyLocalControlLocks(locked: true); if (ShouldLogThrottled("stabilize", 5f) && Plugin.Cfg.LogPlayerLifeSync.Value) { NetLogger.Info("[PlayerLife] Stabilized downed local player reason=" + reason); } } } private static void HealUnitForRevive(object unit) { float num = GetUnitMaxHealth(unit); if (num <= 0f) { num = 100f; } float num2 = Plugin.Cfg.PlayerReviveHealthRatio.Value; if (num2 < 0.01f) { num2 = 0.01f; } if (num2 > 1f) { num2 = 1f; } SetUnitCurrentHealth(unit, Math.Max(1f, num * num2)); } private static bool ShouldAllowImmediateNativeDeath(out string reason) { reason = ""; List list = (from p in NetGameplaySyncBridge.GetKnownPlayerLifePeerIds() where !string.IsNullOrWhiteSpace(p) select p).Distinct().ToList(); string localPeerId = GetLocalPeerId(); if (list.Count <= 1) { reason = "no connected co-op peer"; return true; } foreach (string item in list) { if (!(item == localPeerId) && (!TryGetPeerLife(item, out PeerLife peer) || !peer.IsDownOrDead)) { reason = "peer still alive or unknown: " + item; return false; } } reason = "all other known peers already down/dead"; return true; } private static bool TryFindNearestDownedPeer(out PeerLife target, out float distance) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_008e: 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) target = null; distance = float.MaxValue; if ((Object)(object)_localTransform == (Object)null) { return false; } string localPeerId = GetLocalPeerId(); float num = Math.Max(0.5f, Plugin.Cfg.PlayerReviveDistance.Value); Vector3 position = _localTransform.position; foreach (PeerLife value in PeerStates.Values) { if (!(value.PeerId == localPeerId) && value.Kind == NetPlayerLifeStateKind.Downed && value.HasPosition && IsSameCurrentScene(value)) { float num2 = Vector3.Distance(position, value.Position); if (num2 <= num && num2 < distance) { target = value; distance = num2; } } } return target != null; } private static bool IsSameCurrentScene(PeerLife peer) { if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || !state.HasLevel) { return false; } if (string.IsNullOrWhiteSpace(peer.SceneKey) || peer.SceneKey == "") { return false; } if (peer.SceneKey != state.SceneKey()) { return false; } if (Plugin.Cfg.RequireSameLevelSeedForSceneMatch.Value && peer.HasLevelSeed && state.HasLevelSeed && peer.LevelSeed != state.LevelSeed) { return false; } return true; } private static void ResetReviveHold() { _activeReviveTarget = ""; } private static void PublishLocalLifeState(NetPlayerLifeStateKind kind, string targetPeerId, string reason) { NetGameplaySyncBridge.ReportPlayerLifeState(BuildLocalState(kind, targetPeerId, reason)); } private static NetPlayerLifeState BuildLocalState(NetPlayerLifeStateKind kind, string targetPeerId, string reason) { //IL_0061: 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_0066: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) NetRunStateBridge.TryGetLocalRunState(out NetRunState state); string text = (string.IsNullOrWhiteSpace(state.PeerId) ? GetLocalPeerId() : state.PeerId); string playerName = (string.IsNullOrWhiteSpace(state.PlayerName) ? Plugin.Cfg.PlayerName.Value : state.PlayerName); Vector3 position = (((Object)(object)_localTransform != (Object)null) ? _localTransform.position : Vector3.zero); bool hasPosition = (Object)(object)_localTransform != (Object)null; int num = ++_sequence; return new NetPlayerLifeState { EventId = $"{text}:life:{num}:{kind}", SourcePeerId = text, TargetPeerId = (targetPeerId ?? ""), PlayerName = playerName, ChapterName = state.ChapterName, LevelIndex = state.LevelIndex, HasLevelSeed = state.HasLevelSeed, LevelSeed = state.LevelSeed, Sequence = num, Kind = kind, HasPosition = hasPosition, Position = position, SentAt = Time.realtimeSinceStartup, Reason = (reason ?? ""), DamageAmount = 0f }; } private static NetPlayerLifeState BuildHostState(NetPlayerLifeStateKind kind, string targetPeerId, string reason) { //IL_00d1: 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) NetRunStateBridge.TryGetLocalRunState(out NetRunState state); int num = ++_sequence; return new NetPlayerLifeState { EventId = $"host:life:{num}:{kind}:{targetPeerId}", SourcePeerId = "host", TargetPeerId = (targetPeerId ?? ""), PlayerName = Plugin.Cfg.PlayerName.Value, ChapterName = state.ChapterName, LevelIndex = state.LevelIndex, HasLevelSeed = state.HasLevelSeed, LevelSeed = state.LevelSeed, Sequence = num, Kind = kind, HasPosition = ((Object)(object)_localTransform != (Object)null), Position = (((Object)(object)_localTransform != (Object)null) ? _localTransform.position : Vector3.zero), SentAt = Time.realtimeSinceStartup, Reason = (reason ?? ""), DamageAmount = 0f }; } private static void RefreshLocalPeerAliveState() { if (!_localDowned && !_localNativeDeathCommitted && IsEnabled()) { MarkLocalState(NetPlayerLifeStateKind.Alive, "tick"); } } private static void MarkLocalState(NetPlayerLifeStateKind kind, string reason) { string localPeerId = GetLocalPeerId(); if (!string.IsNullOrWhiteSpace(localPeerId)) { NetPlayerLifeState state = BuildLocalState(kind, "", reason); MarkPeerState(localPeerId, state, kind); } } private static void MarkPeerAlive(string peerId, NetPlayerLifeState state) { MarkPeerState(peerId, state, NetPlayerLifeStateKind.Alive); } private static void MarkPeerState(string peerId, NetPlayerLifeState state, NetPlayerLifeStateKind kind) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrWhiteSpace(peerId)) { PeerLife orCreatePeer = GetOrCreatePeer(peerId); NetPlayerLifeStateKind kind2 = orCreatePeer.Kind; orCreatePeer.Kind = kind; orCreatePeer.PlayerName = state.PlayerName; if (kind == NetPlayerLifeStateKind.Downed && kind2 != NetPlayerLifeStateKind.Downed) { NetRunStatsManager.RecordDowned(peerId); } orCreatePeer.Sequence = Math.Max(orCreatePeer.Sequence, state.Sequence); orCreatePeer.LastUpdatedAt = Time.realtimeSinceStartup; if (state.HasPosition) { orCreatePeer.HasPosition = true; orCreatePeer.Position = state.Position; } if (state.HasScene) { orCreatePeer.SceneKey = state.SceneKey; orCreatePeer.HasLevelSeed = state.HasLevelSeed; orCreatePeer.LevelSeed = state.LevelSeed; } if (Plugin.Cfg.LogPlayerLifeSync.Value) { NetLogger.Info($"[PlayerLife] State update {peerId} => {kind} ({state.ToCompactString()})"); } } } private static PeerLife GetOrCreatePeer(string peerId) { if (!PeerStates.TryGetValue(peerId, out PeerLife value)) { value = new PeerLife { PeerId = peerId, Kind = NetPlayerLifeStateKind.Alive }; PeerStates[peerId] = value; } return value; } private static bool TryGetPeerLife(string peerId, out PeerLife peer) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (peerId == GetLocalPeerId()) { peer = GetOrCreatePeer(peerId); peer.Kind = (_localNativeDeathCommitted ? NetPlayerLifeStateKind.NativeDeathCommit : (_localDowned ? NetPlayerLifeStateKind.Downed : NetPlayerLifeStateKind.Alive)); if ((Object)(object)_localTransform != (Object)null) { peer.HasPosition = true; peer.Position = _localTransform.position; } return true; } return PeerStates.TryGetValue(peerId, out peer); } private static bool TryGetPeerPosition(string peerId, out Vector3 position) { //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_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_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) position = Vector3.zero; if (peerId == GetLocalPeerId()) { if ((Object)(object)_localTransform == (Object)null) { return false; } position = _localTransform.position; return true; } if (PeerStates.TryGetValue(peerId, out PeerLife value) && value.HasPosition) { position = value.Position; return true; } return false; } private static string GetLocalPeerId() { if (NetRunStateBridge.TryGetLocalRunState(out NetRunState state) && !string.IsNullOrWhiteSpace(state.PeerId)) { return state.PeerId; } if (NetConfig.GetMode() != NetMode.Host) { return "client-local"; } return "host"; } private static bool IsEnabled() { if (!Plugin.Cfg.EnableCoopPlayerDownedRevive.Value) { return false; } return NetConfig.GetMode() != NetMode.Off; } private static bool LooksLikePlayerUnit(object value) { try { if (!IsUnityObjectAlive(value)) { return false; } string text = value.GetType().Name ?? ""; if (text == "Npc" || text == "Breakable") { return false; } if (value == _localPlayerUnit) { return true; } if (text == "Player") { return true; } Component val = (Component)((value is Component) ? value : null); if (val != null) { string text2 = ((Object)val).name ?? ""; if (text2.IndexOf("Unit_Player", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (text2.IndexOf("Unit_Enemy", StringComparison.OrdinalIgnoreCase) >= 0) { return false; } } object obj = TryGetMemberValue(value, "isPlayer"); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0 && text != "Npc") { return true; } } catch { } return false; } private static object? ResolvePlayerUnit(object value) { if (LooksLikePlayerUnit(value)) { return value; } object obj = TryGetMemberValue(value, "playerUnit") ?? TryGetMemberValue(value, "PlayerUnit") ?? TryGetMemberValue(value, "unit") ?? TryGetMemberValue(value, "Unit"); if (obj != null && LooksLikePlayerUnit(obj)) { return obj; } return null; } private static Transform? ExtractTransform(object? value) { if (value == null) { return null; } if (!IsUnityObjectAlive(value)) { return null; } try { Transform val = (Transform)((value is Transform) ? value : null); if (val != null) { return val; } GameObject val2 = (GameObject)((value is GameObject) ? value : null); if (val2 != null) { return val2.transform; } Component val3 = (Component)((value is Component) ? value : null); if (val3 != null) { return val3.transform; } } catch { return null; } try { PropertyInfo property = value.GetType().GetProperty("transform", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { object? value2 = property.GetValue(value, null); Transform val4 = (Transform)((value2 is Transform) ? value2 : null); if (val4 != null && (Object)(object)val4 != (Object)null) { return val4; } } FieldInfo field = value.GetType().GetField("transform", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { object? value3 = field.GetValue(value); Transform val5 = (Transform)((value3 is Transform) ? value3 : null); if (val5 != null && (Object)(object)val5 != (Object)null) { return val5; } } } catch { } return null; } private static bool IsUnityObjectAlive(object? value) { try { if (value == null) { return false; } Object val = (Object)((value is Object) ? value : null); if (val != null) { return val != (Object)null; } return true; } catch { return false; } } private static object? ResolveCurrentLocalPlayerUnit() { if (_localPlayerUnit != null && IsUnityObjectAlive(_localPlayerUnit) && TryGetMemberValue(_localPlayerUnit, "Stats") != null) { return _localPlayerUnit; } try { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); object obj = ((type == null) ? null : AccessTools.Property(type, "Instance")?.GetValue(null, null)); if (obj != null) { object obj2 = AccessTools.Property(type, "PlayerUnit")?.GetValue(obj, null); if (obj2 != null && IsUnityObjectAlive(obj2) && TryGetMemberValue(obj2, "Stats") != null) { _localPlayerUnit = obj2; _localTransform = ExtractTransform(obj2); return obj2; } object obj3 = TryGetMemberValue(obj, "PlayerObject"); if (obj3 != null) { object obj4 = ResolvePlayerUnit(obj3); if (obj4 != null && TryGetMemberValue(obj4, "Stats") != null) { _localPlayerUnit = obj4; _localTransform = ExtractTransform(obj4) ?? ExtractTransform(obj3); return obj4; } } } } catch { } _localPlayerUnit = null; _localTransform = null; return null; } private static object? TryGetMemberValue(object value, string name) { try { PropertyInfo property = value.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { return property.GetValue(value, null); } FieldInfo field = value.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(value); } } catch { } return null; } private static bool InvokeMethod(object target, string methodName, params object[] args) { try { MethodInfo methodInfo = AccessTools.Method(target.GetType(), methodName, (Type[])null, (Type[])null); if (methodInfo == null) { return false; } methodInfo.Invoke(target, args); return true; } catch (Exception ex) { if (ShouldLogThrottled("invoke_" + methodName, 5f)) { NetLogger.Warn("[PlayerLife] Invoke " + methodName + " failed: " + ex.Message); } return false; } } private static void SetUnitState(object unit, string stateName) { try { MethodInfo methodInfo = AccessTools.Method(unit.GetType(), "SetUnitState", (Type[])null, (Type[])null) ?? AccessTools.Method(AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Units.Unit"), "SetUnitState", (Type[])null, (Type[])null); if (!(methodInfo == null)) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType.IsEnum) { object obj = Enum.Parse(parameters[0].ParameterType, stateName); methodInfo.Invoke(unit, new object[1] { obj }); } } } catch (Exception ex) { if (ShouldLogThrottled("SetUnitState", 5f)) { NetLogger.Warn("[PlayerLife] SetUnitState(" + stateName + ") failed: " + ex.Message); } } } private static void SetUnitCurrentHealth(object unit, float hp) { try { object obj = TryGetMemberValue(unit, "Stats"); if (obj == null) { return; } MethodInfo methodInfo = AccessTools.GetDeclaredMethods(obj.GetType()).FirstOrDefault(delegate(MethodInfo m) { if (m.Name != "SetStatus") { return false; } ParameterInfo[] parameters2 = m.GetParameters(); if (parameters2.Length != 3) { return false; } if (parameters2[1].ParameterType != typeof(float)) { return false; } return parameters2[0].ParameterType == typeof(int) || parameters2[0].ParameterType.IsEnum; }); if (!(methodInfo == null)) { ParameterInfo[] parameters = methodInfo.GetParameters(); object obj2 = (parameters[0].ParameterType.IsEnum ? Enum.ToObject(parameters[0].ParameterType, 92) : ((object)92)); methodInfo.Invoke(obj, new object[3] { obj2, hp, false }); } } catch (Exception ex) { if (ShouldLogThrottled("SetUnitCurrentHealth", 5f)) { NetLogger.Warn("[PlayerLife] Set current health failed: " + ex.Message); } } } private static float GetUnitCurrentHealth(object unit) { try { object obj = TryGetMemberValue(unit, "Stats"); if (obj == null) { return 0f; } MethodInfo methodInfo = AccessTools.Method(obj.GetType(), "GetCurrentHealth", (Type[])null, (Type[])null); if (methodInfo != null) { object obj2 = methodInfo.Invoke(obj, null); return (obj2 is float num) ? num : Convert.ToSingle(obj2); } MethodInfo methodInfo2 = AccessTools.GetDeclaredMethods(obj.GetType()).FirstOrDefault(delegate(MethodInfo m) { if (m.Name != "TryGetStatus" && m.Name != "GetStatus") { return false; } ParameterInfo[] parameters2 = m.GetParameters(); return parameters2.Length >= 1 && (parameters2[0].ParameterType == typeof(int) || parameters2[0].ParameterType.IsEnum); }); if (methodInfo2 == null) { return 0f; } ParameterInfo[] parameters = methodInfo2.GetParameters(); object obj3 = (parameters[0].ParameterType.IsEnum ? Enum.ToObject(parameters[0].ParameterType, 92) : ((object)92)); object obj4 = methodInfo2.Invoke(obj, new object[1] { obj3 }); return (obj4 is float num2) ? num2 : Convert.ToSingle(obj4); } catch { return 0f; } } private static float GetUnitMaxHealth(object unit) { try { object obj = TryGetMemberValue(unit, "Stats"); if (obj == null) { return 0f; } MethodInfo methodInfo = AccessTools.Method(obj.GetType(), "GetAttribute", (Type[])null, (Type[])null); if (methodInfo == null) { return 0f; } ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length < 1 || !parameters[0].ParameterType.IsEnum) { return 0f; } object obj2 = Enum.ToObject(parameters[0].ParameterType, 60); object obj3 = methodInfo.Invoke(obj, new object[1] { obj2 }); return (obj3 is float num) ? num : Convert.ToSingle(obj3); } catch { return 0f; } } private static void ApplyLocalControlLocks(bool locked) { if (_localControlLocksApplied == locked) { return; } _localControlLocksApplied = locked; try { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); if (type == null) { return; } object obj = AccessTools.Property(type, "Instance")?.GetValue(null, null); if (obj == null) { return; } MethodInfo methodInfo = AccessTools.Method(type, "AddLock", (Type[])null, (Type[])null); if (methodInfo == null) { return; } ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length < 2 || !parameters[0].ParameterType.IsEnum) { return; } string[] downedInputBlacklist = DownedInputBlacklist; foreach (string value in downedInputBlacklist) { if (Enum.IsDefined(parameters[0].ParameterType, value)) { object obj2 = Enum.Parse(parameters[0].ParameterType, value); methodInfo.Invoke(obj, new object[2] { obj2, locked }); } } if (locked) { object obj3 = TryGetMemberValue(obj, "EquipmentManager"); if (obj3 != null) { InvokeMethod(obj3, "ReleaseTrigger"); InvokeMethod(obj3, "UnsightWeapon"); } } } catch (Exception ex) { if (ShouldLogThrottled("ControlLocks", 5f)) { NetLogger.Warn("[PlayerLife] Control lock " + (locked ? "apply" : "release") + " failed: " + ex.Message); } } } private static void RefreshLocalPositionCache() { if (!((Object)(object)_localTransform != (Object)null) || !Object.op_Implicit((Object)(object)_localTransform)) { _localTransform = null; if (_localPlayerUnit != null && IsUnityObjectAlive(_localPlayerUnit)) { _localTransform = ExtractTransform(_localPlayerUnit); } if ((Object)(object)_localTransform == (Object)null) { ResolveCurrentLocalPlayerUnit(); } } } private static bool ShouldLogThrottled(string key, float interval) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (LastLogByKey.TryGetValue(key, out var value) && realtimeSinceStartup - value < interval) { return false; } LastLogByKey[key] = realtimeSinceStartup; return true; } private static string DescribeObject(object value) { try { Component val = (Component)((value is Component) ? value : null); if ((Object)(object)val != (Object)null && Object.op_Implicit((Object)(object)val)) { return $"{value.GetType().Name}:{((Object)val).name}#{((Object)val).GetInstanceID()}"; } return $"{value.GetType().Name}#{value.GetHashCode()}"; } catch { return value.GetType().Name; } } } internal enum NetPlayerLifeStateKind : byte { Alive, Downed, ReviveRequest, ReviveAccepted, NativeDeathCommit, HostDamageRequest, DamageTakenReport, RescueHoldStart, RescueHoldStop, RescueProgress, RescueCancelled } internal sealed class NetPlayerLifeState { public string EventId { get; set; } = ""; public string SourcePeerId { get; set; } = ""; public string TargetPeerId { get; set; } = ""; public string PlayerName { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public int Sequence { get; set; } public NetPlayerLifeStateKind Kind { get; set; } public bool HasPosition { get; set; } public Vector3 Position { get; set; } public float SentAt { get; set; } public string Reason { get; set; } = ""; public float DamageAmount { get; set; } public int DamageType { get; set; } public float Progress { get; set; } public bool HasScene { get { if (!string.IsNullOrWhiteSpace(ChapterName) && ChapterName != "") { return LevelIndex >= 0; } return false; } } public string SceneKey => NetSceneName.SceneCompareKey(ChapterName, LevelIndex); public string SeedText { get { if (!HasLevelSeed) { return "?"; } return LevelSeed.ToString(); } } public bool MatchesScene(NetRunState state) { if (!HasScene || !state.HasLevel) { return false; } if (!NetSceneName.SameScene(ChapterName, LevelIndex, state.ChapterName, state.LevelIndex)) { return false; } if (HasLevelSeed && state.HasLevelSeed && LevelSeed != state.LevelSeed) { return false; } return true; } public string ToCompactString() { //IL_0055: 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_0075: Unknown result type (might be due to invalid IL or missing references) string text = (string.IsNullOrWhiteSpace(SourcePeerId) ? "?" : SourcePeerId); string text2 = (string.IsNullOrWhiteSpace(TargetPeerId) ? "" : (",target=" + TargetPeerId)); string text3 = (HasPosition ? $",pos=({Position.x:F2},{Position.y:F2},{Position.z:F2})" : ",pos=?"); string text4 = (string.IsNullOrWhiteSpace(Reason) ? "" : (",reason=" + Reason)); string text5 = ((DamageAmount > 0f) ? $",damage={DamageAmount:F1}" : ""); return $"kind={Kind},src={text}{text2},scene={SceneKey}#seed={SeedText},seq={Sequence}{text3}{text5}{text4}"; } } internal static class NetPlayerLifeStateCodec { public static void Write(NetDataWriter w, NetPlayerLifeState state) { //IL_00b3: 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_00d5: Unknown result type (might be due to invalid IL or missing references) w.Put(state.EventId ?? ""); w.Put(state.SourcePeerId ?? ""); w.Put(state.TargetPeerId ?? ""); w.Put(state.PlayerName ?? ""); w.Put(state.ChapterName ?? ""); w.Put(state.LevelIndex); w.Put(state.HasLevelSeed); w.Put(state.LevelSeed); w.Put(state.Sequence); w.Put((byte)state.Kind); w.Put(state.HasPosition); w.Put(state.Position.x); w.Put(state.Position.y); w.Put(state.Position.z); w.Put(state.SentAt); w.Put(state.Reason ?? ""); w.Put(state.DamageAmount); w.Put(state.DamageType); w.Put(state.Progress); } public static bool TryRead(NetPacketReader r, out NetPlayerLifeState state) { //IL_00b0: Unknown result type (might be due to invalid IL or missing references) state = new NetPlayerLifeState(); try { state.EventId = ((NetDataReader)r).GetString(); state.SourcePeerId = ((NetDataReader)r).GetString(); state.TargetPeerId = ((NetDataReader)r).GetString(); state.PlayerName = ((NetDataReader)r).GetString(); state.ChapterName = ((NetDataReader)r).GetString(); state.LevelIndex = ((NetDataReader)r).GetInt(); state.HasLevelSeed = ((NetDataReader)r).GetBool(); state.LevelSeed = ((NetDataReader)r).GetInt(); state.Sequence = ((NetDataReader)r).GetInt(); state.Kind = (NetPlayerLifeStateKind)((NetDataReader)r).GetByte(); state.HasPosition = ((NetDataReader)r).GetBool(); float num = ((NetDataReader)r).GetFloat(); float num2 = ((NetDataReader)r).GetFloat(); float num3 = ((NetDataReader)r).GetFloat(); state.Position = new Vector3(num, num2, num3); state.SentAt = ((NetDataReader)r).GetFloat(); state.Reason = ((NetDataReader)r).GetString(); state.DamageAmount = ((NetDataReader)r).GetFloat(); state.DamageType = ((NetDataReader)r).GetInt(); state.Progress = ((NetDataReader)r).GetFloat(); return true; } catch (Exception ex) { NetLogger.Warn("[PlayerLife] Failed to decode PlayerLifeState: " + ex.Message); return false; } } } internal sealed class NetPlayerWeaponFire { public string PeerId { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public int Sequence { get; set; } public float SentAt { get; set; } public Vector3 Origin { get; set; } public Vector3 AimPoint { get; set; } public int Count { get; set; } public float Speed { get; set; } public float Spread { get; set; } public bool IsSpray { get; set; } public bool Homing { get; set; } public bool IsRaygun { get; set; } public int ProjectileType { get; set; } public int Caliber { get; set; } public int Effect { get; set; } public int VfxAsset { get; set; } public int DamageType { get; set; } public float ColorR { get; set; } public float ColorG { get; set; } public float ColorB { get; set; } public float CoreColorR { get; set; } public float CoreColorG { get; set; } public float CoreColorB { get; set; } public float InnerWidth { get; set; } public float OuterWidth { get; set; } public float Radius { get; set; } public float Mass { get; set; } public float Drag { get; set; } public float GravityX { get; set; } public float GravityY { get; set; } public float GravityZ { get; set; } public float TimeScale { get; set; } public float LifeTime { get; set; } public float BehaviourTimeout { get; set; } public float BarrelLengthOffset { get; set; } public int BounceHits { get; set; } public float Bounciness { get; set; } public int PenetrationBehavior { get; set; } public int PenetrationsLeft { get; set; } public float PenetrationDamageMultiplier { get; set; } public bool DrawDefaultBullet { get; set; } public bool DrawLaserBeam { get; set; } public bool IsRocket { get; set; } public bool HasCustomColors { get; set; } public bool HasCustomTrail { get; set; } public bool CreateBulletHoles { get; set; } public bool StickToGeometry { get; set; } public bool PlayImpactSounds { get; set; } public bool MatchesScene(NetRunState localState) { if (!localState.HasLevel) { return false; } if (!string.Equals(localState.ChapterName, ChapterName, StringComparison.Ordinal)) { return false; } if (localState.LevelIndex != LevelIndex) { return false; } if (Plugin.Cfg.EnableLevelSeedAuthority.Value) { if (!HasLevelSeed || !localState.HasLevelSeed) { return false; } if (localState.LevelSeed != LevelSeed) { return false; } } return true; } } internal static class NetPlayerWeaponFireCodec { private const byte Version = 1; public static void Write(NetDataWriter w, NetPlayerWeaponFire m) { //IL_0077: 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_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) w.Put((byte)1); w.Put(m.PeerId ?? ""); w.Put(m.ChapterName ?? ""); w.Put(m.LevelIndex); w.Put(m.HasLevelSeed); if (m.HasLevelSeed) { w.Put(m.LevelSeed); } w.Put(m.Sequence); w.Put(m.SentAt); w.Put(m.Origin.x); w.Put(m.Origin.y); w.Put(m.Origin.z); w.Put(m.AimPoint.x); w.Put(m.AimPoint.y); w.Put(m.AimPoint.z); w.Put(m.Count); w.Put(m.Speed); w.Put(m.Spread); w.Put(m.IsSpray); w.Put(m.Homing); w.Put(m.IsRaygun); w.Put(m.ProjectileType); w.Put(m.Caliber); w.Put(m.Effect); w.Put(m.VfxAsset); w.Put(m.DamageType); w.Put(m.ColorR); w.Put(m.ColorG); w.Put(m.ColorB); w.Put(m.CoreColorR); w.Put(m.CoreColorG); w.Put(m.CoreColorB); w.Put(m.InnerWidth); w.Put(m.OuterWidth); w.Put(m.Radius); w.Put(m.Mass); w.Put(m.Drag); w.Put(m.GravityX); w.Put(m.GravityY); w.Put(m.GravityZ); w.Put(m.TimeScale); w.Put(m.LifeTime); w.Put(m.BehaviourTimeout); w.Put(m.BarrelLengthOffset); w.Put(m.BounceHits); w.Put(m.Bounciness); w.Put(m.PenetrationBehavior); w.Put(m.PenetrationsLeft); w.Put(m.PenetrationDamageMultiplier); w.Put(m.DrawDefaultBullet); w.Put(m.DrawLaserBeam); w.Put(m.IsRocket); w.Put(m.HasCustomColors); w.Put(m.HasCustomTrail); w.Put(m.CreateBulletHoles); w.Put(m.StickToGeometry); w.Put(m.PlayImpactSounds); } public static bool TryRead(NetDataReader r, out NetPlayerWeaponFire m) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) m = new NetPlayerWeaponFire(); try { if (r.GetByte() != 1) { return false; } m.PeerId = r.GetString(); m.ChapterName = r.GetString(); m.LevelIndex = r.GetInt(); m.HasLevelSeed = r.GetBool(); if (m.HasLevelSeed) { m.LevelSeed = r.GetInt(); } m.Sequence = r.GetInt(); m.SentAt = r.GetFloat(); m.Origin = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); m.AimPoint = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); m.Count = r.GetInt(); m.Speed = r.GetFloat(); m.Spread = r.GetFloat(); m.IsSpray = r.GetBool(); m.Homing = r.GetBool(); m.IsRaygun = r.GetBool(); m.ProjectileType = r.GetInt(); m.Caliber = r.GetInt(); m.Effect = r.GetInt(); m.VfxAsset = r.GetInt(); m.DamageType = r.GetInt(); m.ColorR = r.GetFloat(); m.ColorG = r.GetFloat(); m.ColorB = r.GetFloat(); m.CoreColorR = r.GetFloat(); m.CoreColorG = r.GetFloat(); m.CoreColorB = r.GetFloat(); m.InnerWidth = r.GetFloat(); m.OuterWidth = r.GetFloat(); m.Radius = r.GetFloat(); m.Mass = r.GetFloat(); m.Drag = r.GetFloat(); m.GravityX = r.GetFloat(); m.GravityY = r.GetFloat(); m.GravityZ = r.GetFloat(); m.TimeScale = r.GetFloat(); m.LifeTime = r.GetFloat(); m.BehaviourTimeout = r.GetFloat(); m.BarrelLengthOffset = r.GetFloat(); m.BounceHits = r.GetInt(); m.Bounciness = r.GetFloat(); m.PenetrationBehavior = r.GetInt(); m.PenetrationsLeft = r.GetInt(); m.PenetrationDamageMultiplier = r.GetFloat(); m.DrawDefaultBullet = r.GetBool(); m.DrawLaserBeam = r.GetBool(); m.IsRocket = r.GetBool(); m.HasCustomColors = r.GetBool(); m.HasCustomTrail = r.GetBool(); m.CreateBulletHoles = r.GetBool(); m.StickToGeometry = r.GetBool(); m.PlayImpactSounds = r.GetBool(); return true; } catch { return false; } } } internal sealed class NetRunStats { public string PeerId { get; set; } = ""; public string PlayerName { get; set; } = ""; public int ShotsFired { get; set; } public int DamageDealt { get; set; } public int Kills { get; set; } public int TimesDowned { get; set; } public int Rescues { get; set; } public int DamageTaken { get; set; } public int DestructiblesDestroyed { get; set; } public NetRunStats Clone() { return new NetRunStats { PeerId = PeerId, PlayerName = PlayerName, ShotsFired = ShotsFired, DamageDealt = DamageDealt, Kills = Kills, TimesDowned = TimesDowned, Rescues = Rescues, DamageTaken = DamageTaken, DestructiblesDestroyed = DestructiblesDestroyed }; } public string ToCompactString() { return $"peer={PeerId} name={PlayerName} shots={ShotsFired} dmgDealt={DamageDealt} kills={Kills} " + $"downed={TimesDowned} rescues={Rescues} dmgTaken={DamageTaken} broken={DestructiblesDestroyed}"; } } internal static class NetRunStatsCodec { public static void Write(NetDataWriter w, NetRunStats stats) { w.Put(stats.PeerId ?? ""); w.Put(stats.PlayerName ?? ""); w.Put(stats.ShotsFired); w.Put(stats.DamageDealt); w.Put(stats.Kills); w.Put(stats.TimesDowned); w.Put(stats.Rescues); w.Put(stats.DamageTaken); w.Put(stats.DestructiblesDestroyed); } public static bool TryRead(NetPacketReader r, out NetRunStats stats) { stats = new NetRunStats(); try { stats.PeerId = ((NetDataReader)r).GetString(); stats.PlayerName = ((NetDataReader)r).GetString(); stats.ShotsFired = ((NetDataReader)r).GetInt(); stats.DamageDealt = ((NetDataReader)r).GetInt(); stats.Kills = ((NetDataReader)r).GetInt(); stats.TimesDowned = ((NetDataReader)r).GetInt(); stats.Rescues = ((NetDataReader)r).GetInt(); stats.DamageTaken = ((NetDataReader)r).GetInt(); stats.DestructiblesDestroyed = ((NetDataReader)r).GetInt(); return true; } catch (Exception ex) { NetLogger.Warn("[RunStats] Failed to decode NetRunStats: " + ex.Message); return false; } } } internal sealed class NetRunStatsList { public int RunSeq { get; set; } public List Players { get; set; } = new List(); } internal static class NetRunStatsListCodec { public static void Write(NetDataWriter w, NetRunStatsList list) { w.Put(list.RunSeq); w.Put((short)list.Players.Count); foreach (NetRunStats player in list.Players) { NetRunStatsCodec.Write(w, player); } } public static bool TryRead(NetPacketReader r, out NetRunStatsList list) { list = new NetRunStatsList(); try { list.RunSeq = ((NetDataReader)r).GetInt(); int num = ((NetDataReader)r).GetShort(); if (num < 0 || num > 64) { return false; } for (int i = 0; i < num; i++) { if (!NetRunStatsCodec.TryRead(r, out NetRunStats stats)) { return false; } list.Players.Add(stats); } return true; } catch (Exception ex) { NetLogger.Warn("[RunStats] Failed to decode NetRunStatsList: " + ex.Message); return false; } } } internal static class NetRunStatsManager { private static readonly Dictionary _live = new Dictionary(); private static readonly Dictionary _lastDamager = new Dictionary(); private static readonly HashSet _killedKeys = new HashSet(); private static readonly Dictionary _pendingHit = new Dictionary(); private static float _hostOwnPlayerPendingBeforeHp = -1f; private static int _runSeq; public static bool RunActive { get; private set; } private static bool IsHost => NetConfig.GetMode() == NetMode.Host; public static int EntityKey(object entity) { Object val = (Object)((entity is Object) ? entity : null); if (val != null && val != (Object)null) { return val.GetInstanceID(); } return RuntimeHelpers.GetHashCode(entity); } public static void BeginRun() { if (IsHost && !RunActive) { _live.Clear(); _lastDamager.Clear(); _killedKeys.Clear(); _pendingHit.Clear(); _hostOwnPlayerPendingBeforeHp = -1f; RunActive = true; NetLogger.Info("[RunStats] Run begun"); } } public static NetRunStatsList FinalizeAndSnapshot() { if (!IsHost) { RunActive = false; return new NetRunStatsList { RunSeq = 0, Players = new List() }; } List list = (from s in NetGameplaySyncBridge.GetSessionsSnapshot() where s.State == NetConnectionState.Connected orderby s.Slot select s).ToList(); List list2 = new List(list.Count); foreach (NetPeerSession item in list) { NetRunStats netRunStats = GetOrCreate(item.PeerId).Clone(); netRunStats.PlayerName = item.PlayerName; list2.Add(netRunStats); } RunActive = false; NetRunStatsList netRunStatsList = new NetRunStatsList { RunSeq = ++_runSeq, Players = list2 }; NetLogger.Info($"[RunStats] Finalized runSeq={netRunStatsList.RunSeq} {list2.Count} player(s)"); foreach (NetRunStats item2 in list2) { NetLogger.Info("[RunStats] " + item2.ToCompactString()); } _live.Clear(); _lastDamager.Clear(); _killedKeys.Clear(); _pendingHit.Clear(); _hostOwnPlayerPendingBeforeHp = -1f; return netRunStatsList; } private static NetRunStats GetOrCreate(string peerId) { if (!_live.TryGetValue(peerId, out NetRunStats value)) { value = new NetRunStats { PeerId = peerId }; _live[peerId] = value; } return value; } public static void RecordShotFired(string peerId) { if (IsHost && RunActive && !string.IsNullOrWhiteSpace(peerId)) { GetOrCreate(peerId).ShotsFired++; } } public static void RecordDestructibleDestroyed(string peerId) { if (IsHost && RunActive && !string.IsNullOrWhiteSpace(peerId)) { GetOrCreate(peerId).DestructiblesDestroyed++; } } public static void RecordDowned(string peerId) { if (IsHost && RunActive && !string.IsNullOrWhiteSpace(peerId)) { GetOrCreate(peerId).TimesDowned++; } } public static void RecordRescue(string rescuerPeerId) { if (IsHost && RunActive && !string.IsNullOrWhiteSpace(rescuerPeerId)) { GetOrCreate(rescuerPeerId).Rescues++; } } public static void RecordDamageTaken(string peerId, float actualAmount) { if (IsHost && RunActive && !string.IsNullOrWhiteSpace(peerId) && !(actualAmount <= 0f)) { GetOrCreate(peerId).DamageTaken += Mathf.RoundToInt(actualAmount); } } public static void RecordDamageDealtDelta(string peerId, int entityKey, float delta) { if (IsHost && RunActive && !string.IsNullOrWhiteSpace(peerId) && !(delta <= 0f)) { GetOrCreate(peerId).DamageDealt += Mathf.RoundToInt(delta); _lastDamager[entityKey] = peerId; } } public static void NotePendingLocalHit(int entityKey, string peerId, float beforeHp) { if (IsHost && RunActive && !string.IsNullOrWhiteSpace(peerId)) { _pendingHit[entityKey] = (peerId, beforeHp); } } public static void ResolvePendingLocalHit(int entityKey, float afterHp) { if (IsHost && _pendingHit.TryGetValue(entityKey, out (string, float) value)) { _pendingHit.Remove(entityKey); RecordDamageDealtDelta(value.Item1, entityKey, value.Item2 - afterHp); } } public static void NoteHostOwnPlayerBeforeHp(float beforeHp) { if (IsHost && RunActive) { _hostOwnPlayerPendingBeforeHp = beforeHp; } } public static void ResolveHostOwnPlayerDamageTaken(float afterHp) { if (IsHost && !(_hostOwnPlayerPendingBeforeHp < 0f)) { float hostOwnPlayerPendingBeforeHp = _hostOwnPlayerPendingBeforeHp; _hostOwnPlayerPendingBeforeHp = -1f; RecordDamageTaken("host", hostOwnPlayerPendingBeforeHp - afterHp); } } public static void ResolveHostOwnPlayerDowned() { if (IsHost && !(_hostOwnPlayerPendingBeforeHp < 0f)) { float hostOwnPlayerPendingBeforeHp = _hostOwnPlayerPendingBeforeHp; _hostOwnPlayerPendingBeforeHp = -1f; RecordDamageTaken("host", hostOwnPlayerPendingBeforeHp); } } public static void RecordKill(int entityKey, string fallbackPeerId) { if (IsHost && RunActive && _killedKeys.Add(entityKey)) { string value; string text = (_lastDamager.TryGetValue(entityKey, out value) ? value : fallbackPeerId); _lastDamager.Remove(entityKey); if (!string.IsNullOrWhiteSpace(text)) { GetOrCreate(text).Kills++; } } } } internal sealed class NetRuntimeSpawn { public int UnitIdValue { get; set; } public Vector3 Position { get; set; } public float RotationY { get; set; } public int HostSpawnIndex { get; set; } public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasSeed { get; set; } public int Seed { get; set; } public string Source { get; set; } = ""; public string ToCompact() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) return $"unitId={UnitIdValue} pos={Position:F1} hostIdx={HostSpawnIndex} run={ChapterName}:{LevelIndex} src={Source}"; } } internal static class NetRuntimeSpawnCodec { private const byte Version = 1; public static void Write(NetDataWriter w, NetRuntimeSpawn m) { //IL_0015: 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_0037: Unknown result type (might be due to invalid IL or missing references) w.Put((byte)1); w.Put(m.UnitIdValue); w.Put(m.Position.x); w.Put(m.Position.y); w.Put(m.Position.z); w.Put(m.RotationY); w.Put(m.HostSpawnIndex); w.Put(m.ChapterName ?? ""); w.Put(m.LevelIndex); w.Put(m.HasSeed); if (m.HasSeed) { w.Put(m.Seed); } w.Put(m.Source ?? ""); } public static bool TryRead(NetDataReader r, out NetRuntimeSpawn result) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) result = null; try { if (r.GetByte() != 1) { return false; } NetRuntimeSpawn netRuntimeSpawn = new NetRuntimeSpawn { UnitIdValue = r.GetInt(), Position = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()), RotationY = r.GetFloat(), HostSpawnIndex = r.GetInt(), ChapterName = r.GetString(), LevelIndex = r.GetInt(), HasSeed = r.GetBool() }; if (netRuntimeSpawn.HasSeed) { netRuntimeSpawn.Seed = r.GetInt(); } netRuntimeSpawn.Source = r.GetString(); result = netRuntimeSpawn; return true; } catch { return false; } } } internal sealed class NetTriggerDoors { public struct DoorEntry { public string Name; public bool Active; } public string PeerId { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public int Sequence { get; set; } public float SentAt { get; set; } public Vector3 TriggerPos { get; set; } public List Doors { get; set; } = new List(); public bool MatchesScene(NetRunState localState) { if (!localState.HasLevel) { return false; } if (!string.Equals(localState.ChapterName, ChapterName, StringComparison.Ordinal)) { return false; } if (localState.LevelIndex != LevelIndex) { return false; } if (Plugin.Cfg.EnableLevelSeedAuthority.Value) { if (!HasLevelSeed || !localState.HasLevelSeed) { return false; } if (localState.LevelSeed != LevelSeed) { return false; } } return true; } } internal static class NetTriggerDoorsCodec { private const byte Version = 1; public static void Write(NetDataWriter w, NetTriggerDoors m) { //IL_0077: 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_0099: Unknown result type (might be due to invalid IL or missing references) w.Put((byte)1); w.Put(m.PeerId ?? ""); w.Put(m.ChapterName ?? ""); w.Put(m.LevelIndex); w.Put(m.HasLevelSeed); if (m.HasLevelSeed) { w.Put(m.LevelSeed); } w.Put(m.Sequence); w.Put(m.SentAt); w.Put(m.TriggerPos.x); w.Put(m.TriggerPos.y); w.Put(m.TriggerPos.z); int num = m.Doors?.Count ?? 0; w.Put(num); for (int i = 0; i < num; i++) { w.Put(m.Doors[i].Name ?? ""); w.Put(m.Doors[i].Active); } } public static bool TryRead(NetDataReader r, out NetTriggerDoors m) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) m = new NetTriggerDoors(); try { if (r.GetByte() != 1) { return false; } m.PeerId = r.GetString(); m.ChapterName = r.GetString(); m.LevelIndex = r.GetInt(); m.HasLevelSeed = r.GetBool(); if (m.HasLevelSeed) { m.LevelSeed = r.GetInt(); } m.Sequence = r.GetInt(); m.SentAt = r.GetFloat(); m.TriggerPos = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); int num = r.GetInt(); for (int i = 0; i < num; i++) { string name = r.GetString(); bool active = r.GetBool(); m.Doors.Add(new NetTriggerDoors.DoorEntry { Name = name, Active = active }); } return true; } catch { return false; } } } internal sealed class NetWorldEntityRecord { public string NetEntityId { get; set; } = ""; public int SyncCategory { get; set; } public string Category { get; set; } = ""; public string UnitIdentifier { get; set; } = ""; public string ActorName { get; set; } = ""; public int SpawnIndex { get; set; } public bool HasPosition { get; set; } public Vector3 Position { get; set; } public int SceneRevision { get; set; } public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } } internal static class NetWorldEntityRosterCodec { public static void Write(NetDataWriter w, List records) { //IL_00a4: 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_00c6: Unknown result type (might be due to invalid IL or missing references) w.Put((short)records.Count); foreach (NetWorldEntityRecord record in records) { w.Put(record.NetEntityId ?? ""); w.Put((byte)record.SyncCategory); w.Put(record.Category ?? ""); w.Put(record.UnitIdentifier ?? ""); w.Put(record.ActorName ?? ""); w.Put(record.SpawnIndex); w.Put(record.HasPosition); if (record.HasPosition) { w.Put(record.Position.x); w.Put(record.Position.y); w.Put(record.Position.z); } w.Put(record.SceneRevision); w.Put(record.ChapterName ?? ""); w.Put(record.LevelIndex); w.Put(record.HasLevelSeed); if (record.HasLevelSeed) { w.Put(record.LevelSeed); } } } public static List Read(NetDataReader r) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) List list = new List(); try { int num = r.GetShort(); if (num < 0 || num > 1024) { return list; } for (int i = 0; i < num; i++) { NetWorldEntityRecord netWorldEntityRecord = new NetWorldEntityRecord { NetEntityId = r.GetString(), SyncCategory = r.GetByte(), Category = r.GetString(), UnitIdentifier = r.GetString(), ActorName = r.GetString(), SpawnIndex = r.GetInt(), HasPosition = r.GetBool() }; if (netWorldEntityRecord.HasPosition) { netWorldEntityRecord.Position = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } netWorldEntityRecord.SceneRevision = r.GetInt(); netWorldEntityRecord.ChapterName = r.GetString(); netWorldEntityRecord.LevelIndex = r.GetInt(); netWorldEntityRecord.HasLevelSeed = r.GetBool(); if (netWorldEntityRecord.HasLevelSeed) { netWorldEntityRecord.LevelSeed = r.GetInt(); } list.Add(netWorldEntityRecord); } } catch { } return list; } } internal sealed class NetWorldPickupSpawn { public string OwnerPeerId { get; set; } = ""; public ushort Seq { get; set; } public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasLevelSeed { get; set; } public int LevelSeed { get; set; } public Vector3 Position { get; set; } public ushort ItemId { get; set; } public bool HasData { get; set; } public ushort[] AttachmentIds { get; set; } = Array.Empty(); public ushort[] EnchantmentIds { get; set; } = Array.Empty(); public int CaliberId { get; set; } public int CurrentAmmo { get; set; } public int Quantity { get; set; } = 1; public bool Rotated { get; set; } public ushort[] AttrIds { get; set; } = Array.Empty(); public float[] AttrValues { get; set; } = Array.Empty(); public float SentAt { get; set; } public string Key => OwnerPeerId + "#" + Seq; public bool MatchesScene(NetRunState localState) { if (!localState.HasLevel) { return false; } if (!string.Equals(localState.ChapterName, ChapterName, StringComparison.Ordinal)) { return false; } if (localState.LevelIndex != LevelIndex) { return false; } if (Plugin.Cfg.EnableLevelSeedAuthority.Value) { if (!HasLevelSeed || !localState.HasLevelSeed) { return false; } if (localState.LevelSeed != LevelSeed) { return false; } } return true; } } internal sealed class NetWorldPickupTake { public string OwnerPeerId { get; set; } = ""; public ushort Seq { get; set; } public float SentAt { get; set; } public string Key => OwnerPeerId + "#" + Seq; } internal sealed class NetWorldPickupRemoved { public string OwnerPeerId { get; set; } = ""; public ushort Seq { get; set; } public string TakenByPeerId { get; set; } = ""; public float SentAt { get; set; } public string Key => OwnerPeerId + "#" + Seq; } internal static class NetWorldPickupCodec { private const byte Version = 1; private const int MaxList = 32; public static void WriteSpawn(NetDataWriter w, NetWorldPickupSpawn m) { //IL_006b: 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_008d: Unknown result type (might be due to invalid IL or missing references) w.Put((byte)1); w.Put(m.OwnerPeerId ?? ""); w.Put(m.Seq); w.Put(m.ChapterName ?? ""); w.Put(m.LevelIndex); w.Put(m.HasLevelSeed); if (m.HasLevelSeed) { w.Put(m.LevelSeed); } w.Put(m.Position.x); w.Put(m.Position.y); w.Put(m.Position.z); w.Put(m.ItemId); w.Put(m.HasData); if (m.HasData) { PutUShortList(w, m.AttachmentIds); PutUShortList(w, m.EnchantmentIds); w.Put(m.CaliberId); w.Put(m.CurrentAmmo); w.Put(m.Quantity); w.Put(m.Rotated); ushort[] array = m.AttrIds ?? Array.Empty(); float[] array2 = m.AttrValues ?? Array.Empty(); int num = array.Length; if (num > array2.Length) { num = array2.Length; } if (num > 32) { num = 32; } w.Put((byte)num); for (int i = 0; i < num; i++) { w.Put(array[i]); w.Put(array2[i]); } } w.Put(m.SentAt); } public static bool TryReadSpawn(NetDataReader r, out NetWorldPickupSpawn m) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) m = new NetWorldPickupSpawn(); try { if (r.GetByte() != 1) { return false; } m.OwnerPeerId = r.GetString(); m.Seq = r.GetUShort(); m.ChapterName = r.GetString(); m.LevelIndex = r.GetInt(); m.HasLevelSeed = r.GetBool(); if (m.HasLevelSeed) { m.LevelSeed = r.GetInt(); } m.Position = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); m.ItemId = r.GetUShort(); m.HasData = r.GetBool(); if (m.HasData) { if (!TryGetUShortList(r, out ushort[] list)) { return false; } if (!TryGetUShortList(r, out ushort[] list2)) { return false; } m.AttachmentIds = list; m.EnchantmentIds = list2; m.CaliberId = r.GetInt(); m.CurrentAmmo = r.GetInt(); m.Quantity = r.GetInt(); m.Rotated = r.GetBool(); int num = r.GetByte(); if (num < 0 || num > 32) { return false; } ushort[] array = new ushort[num]; float[] array2 = new float[num]; for (int i = 0; i < num; i++) { array[i] = r.GetUShort(); array2[i] = r.GetFloat(); } m.AttrIds = array; m.AttrValues = array2; } m.SentAt = r.GetFloat(); return true; } catch { return false; } } public static void WriteTake(NetDataWriter w, NetWorldPickupTake m) { w.Put((byte)1); w.Put(m.OwnerPeerId ?? ""); w.Put(m.Seq); w.Put(m.SentAt); } public static bool TryReadTake(NetDataReader r, out NetWorldPickupTake m) { m = new NetWorldPickupTake(); try { if (r.GetByte() != 1) { return false; } m.OwnerPeerId = r.GetString(); m.Seq = r.GetUShort(); m.SentAt = r.GetFloat(); return true; } catch { return false; } } public static void WriteRemoved(NetDataWriter w, NetWorldPickupRemoved m) { w.Put((byte)1); w.Put(m.OwnerPeerId ?? ""); w.Put(m.Seq); w.Put(m.TakenByPeerId ?? ""); w.Put(m.SentAt); } public static bool TryReadRemoved(NetDataReader r, out NetWorldPickupRemoved m) { m = new NetWorldPickupRemoved(); try { if (r.GetByte() != 1) { return false; } m.OwnerPeerId = r.GetString(); m.Seq = r.GetUShort(); m.TakenByPeerId = r.GetString(); m.SentAt = r.GetFloat(); return true; } catch { return false; } } private static void PutUShortList(NetDataWriter w, ushort[] list) { ushort[] array = list ?? Array.Empty(); int num = array.Length; if (num > 32) { num = 32; } w.Put((byte)num); for (int i = 0; i < num; i++) { w.Put(array[i]); } } private static bool TryGetUShortList(NetDataReader r, out ushort[] list) { list = Array.Empty(); int num = r.GetByte(); if (num < 0 || num > 32) { return false; } ushort[] array = new ushort[num]; for (int i = 0; i < num; i++) { array[i] = r.GetUShort(); } list = array; return true; } } internal static class PlayerHeldWeaponManager { private const float PollInterval = 0.25f; private const float HeartbeatSeconds = 3f; private static float _nextPollAt; private static float _lastSentAt; private static string _lastSentSig = ""; private static readonly Dictionary _desiredByPeer = new Dictionary(); public static void Tick(NetRemotePlayerProxyManager proxies) { if (!Plugin.Cfg.EnableRemoteWeaponModel.Value) { return; } try { float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup >= _nextPollAt) { _nextPollAt = realtimeSinceStartup + 0.25f; PollLocalAndBroadcast(realtimeSinceStartup); } SyncRemoteModels(proxies); } catch (Exception ex) { if (Plugin.Cfg.LogRemoteWeaponModel.Value) { NetLogger.Warn("[HeldWeapon] tick failed: " + ex.Message); } } } public static void Reset() { _desiredByPeer.Clear(); _lastSentSig = ""; _lastSentAt = 0f; _nextPollAt = 0f; } public static void RemovePeer(string peerId) { if (!string.IsNullOrEmpty(peerId)) { _desiredByPeer.Remove(peerId); } } private static void PollLocalAndBroadcast(float now) { if (!NetGameplaySyncBridge.IsSessionActive) { return; } NetPlayerHeldWeapon netPlayerHeldWeapon = ReadLocalHeldWeapon(); string text = netPlayerHeldWeapon.Signature(); if (!(text == _lastSentSig) || !(now - _lastSentAt < 3f)) { _lastSentSig = text; _lastSentAt = now; NetGameplaySyncBridge.ReportLocalHeldWeapon(netPlayerHeldWeapon); if (Plugin.Cfg.LogRemoteWeaponModel.Value) { NetLogger.Info($"[HeldWeapon] broadcast hasWeapon={netPlayerHeldWeapon.HasWeapon} weaponId={netPlayerHeldWeapon.WeaponItemId} attachments={netPlayerHeldWeapon.AttachmentItemIds.Length} sig={text}"); } } } private static NetPlayerHeldWeapon ReadLocalHeldWeapon() { NetPlayerHeldWeapon netPlayerHeldWeapon = new NetPlayerHeldWeapon { HasWeapon = false }; try { GameManager instance = StaticInstance.Instance; EquipmentManager val = (((Object)(object)instance != (Object)null) ? instance.EquipmentManager : null); Holdable obj = (((Object)(object)val != (Object)null) ? val.CurrentHoldable : null); Weapon val2 = (Weapon)(object)((obj is Weapon) ? obj : null); if (val2 != null && (Object)(object)val2.weaponDefinition != (Object)null) { netPlayerHeldWeapon.HasWeapon = true; netPlayerHeldWeapon.WeaponItemId = ((ItemDefinition)val2.weaponDefinition).id.value; List list = new List(); InventoryItem inventoryItem = ((Holdable)val2).inventoryItem; if ((Object)(object)inventoryItem != (Object)null && inventoryItem.attachments != null) { foreach (ItemDefinition attachment in inventoryItem.attachments) { if (!((Object)(object)attachment == (Object)null)) { list.Add(attachment.id.value); } } } netPlayerHeldWeapon.AttachmentItemIds = list.ToArray(); } } catch { } return netPlayerHeldWeapon; } public static void Apply(NetPlayerHeldWeapon msg) { if (Plugin.Cfg.EnableRemoteWeaponModel.Value && msg != null && !string.IsNullOrEmpty(msg.PeerId)) { _desiredByPeer[msg.PeerId] = msg; } } private static void SyncRemoteModels(NetRemotePlayerProxyManager proxies) { if (proxies == null || _desiredByPeer.Count == 0) { return; } proxies.ForEachProxy(delegate(string peerId, NetRemotePlayerProxy proxy) { if (proxy != null && _desiredByPeer.TryGetValue(peerId, out NetPlayerHeldWeapon value)) { string text = value.Signature(); if (!(proxy.HeldWeaponSig == text)) { if (!value.HasWeapon) { proxy.ClearHeldWeapon(); if (Plugin.Cfg.LogRemoteWeaponModel.Value) { NetLogger.Info("[HeldWeapon] cleared weapon for " + peerId); } } else { GameObject val = BuildWeaponModel(value); if ((Object)(object)val != (Object)null) { proxy.UpdateHeldWeapon(val, text); if (Plugin.Cfg.LogRemoteWeaponModel.Value) { NetLogger.Info($"[HeldWeapon] built model peer={peerId} weaponId={value.WeaponItemId} attachments={value.AttachmentItemIds.Length}"); } } } } } }); } private static GameObject BuildWeaponModel(NetPlayerHeldWeapon spec) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown try { AsyncAssetLoading instance = StaticInstance.Instance; ItemDatabase val = (((Object)(object)instance != (Object)null) ? instance.itemDatabase : null); if ((Object)(object)val == (Object)null) { return null; } ItemDefinition val2 = val[new ItemId(spec.WeaponItemId)]; if ((Object)(object)val2 == (Object)null) { return null; } GameObject val3 = (((Object)(object)val2.showcasePrefab != (Object)null) ? val2.showcasePrefab : val2.prefab); if ((Object)(object)val3 == (Object)null) { return null; } GameObject val4 = new GameObject("HeldWeaponBuild"); val4.SetActive(false); GameObject obj = Object.Instantiate(val3, val4.transform); StripGameplay(obj); ApplyAttachments(obj, spec.AttachmentItemIds); obj.SetActive(false); obj.transform.SetParent((Transform)null, false); Object.Destroy((Object)(object)val4); return obj; } catch (Exception ex) { if (Plugin.Cfg.LogRemoteWeaponModel.Value) { NetLogger.Warn($"[HeldWeapon] build failed for weaponId={spec.WeaponItemId}: {ex.Message}"); } return null; } } private static void StripGameplay(GameObject inst) { try { MonoBehaviour[] componentsInChildren = inst.GetComponentsInChildren(true); foreach (MonoBehaviour val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && !(val is WeaponAttachmentVisual)) { try { Object.DestroyImmediate((Object)(object)val); } catch { } } } Camera[] componentsInChildren2 = inst.GetComponentsInChildren(true); foreach (Camera val2 in componentsInChildren2) { try { Object.DestroyImmediate((Object)(object)val2); } catch { } } Light[] componentsInChildren3 = inst.GetComponentsInChildren(true); foreach (Light val3 in componentsInChildren3) { try { Object.DestroyImmediate((Object)(object)val3); } catch { } } Rigidbody[] componentsInChildren4 = inst.GetComponentsInChildren(true); foreach (Rigidbody val4 in componentsInChildren4) { try { Object.DestroyImmediate((Object)(object)val4); } catch { } } Collider[] componentsInChildren5 = inst.GetComponentsInChildren(true); foreach (Collider val5 in componentsInChildren5) { try { Object.DestroyImmediate((Object)(object)val5); } catch { } } Animator[] componentsInChildren6 = inst.GetComponentsInChildren(true); foreach (Animator val6 in componentsInChildren6) { try { ((Behaviour)val6).enabled = false; } catch { } } } catch { } } private static void ApplyAttachments(GameObject inst, ushort[] attachmentIds) { try { HashSet hashSet = new HashSet(attachmentIds ?? Array.Empty()); WeaponAttachmentVisual[] componentsInChildren = inst.GetComponentsInChildren(true); foreach (WeaponAttachmentVisual val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } ushort num = (ushort)(((Object)(object)val.attachmentDefinition != (Object)null) ? val.attachmentDefinition.id.value : 0); bool flag = num != 0 && hashSet.Contains(num); try { if (flag) { val.Show(); } else { val.Hide(); } } catch { } } } catch { } } } internal static class PlayerWeaponFireManager { private const int VisualOwnerInstId = 1460711447; private static int _captureSeq; private static FieldInfo _maxAmmoPerShotField; private static bool _maxAmmoPerShotFieldResolved; public static void CaptureLocalFire(Weapon weapon) { //IL_0114: 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_011b: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013e: 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_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014a: 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_0188: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: 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_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Expected I4, but got Unknown //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Expected I4, but got Unknown //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Expected I4, but got Unknown //IL_0202: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Expected I4, but got Unknown //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Expected I4, but got Unknown //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) //IL_026b: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) //IL_02c1: Unknown result type (might be due to invalid IL or missing references) //IL_02ce: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02e0: Unknown result type (might be due to invalid IL or missing references) //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Unknown result type (might be due to invalid IL or missing references) //IL_02f4: Unknown result type (might be due to invalid IL or missing references) //IL_0304: Unknown result type (might be due to invalid IL or missing references) //IL_0311: Unknown result type (might be due to invalid IL or missing references) //IL_031e: Unknown result type (might be due to invalid IL or missing references) //IL_032b: Unknown result type (might be due to invalid IL or missing references) //IL_0338: Unknown result type (might be due to invalid IL or missing references) //IL_0345: Unknown result type (might be due to invalid IL or missing references) //IL_0352: Unknown result type (might be due to invalid IL or missing references) //IL_0354: Unknown result type (might be due to invalid IL or missing references) //IL_035e: Expected I4, but got Unknown //IL_035f: Unknown result type (might be due to invalid IL or missing references) //IL_036c: Unknown result type (might be due to invalid IL or missing references) //IL_0379: Unknown result type (might be due to invalid IL or missing references) //IL_0386: Unknown result type (might be due to invalid IL or missing references) //IL_0393: Unknown result type (might be due to invalid IL or missing references) //IL_03a0: Unknown result type (might be due to invalid IL or missing references) //IL_03ad: Unknown result type (might be due to invalid IL or missing references) //IL_03ba: Unknown result type (might be due to invalid IL or missing references) //IL_03c7: Unknown result type (might be due to invalid IL or missing references) //IL_03d4: Unknown result type (might be due to invalid IL or missing references) try { if (!Plugin.Cfg.EnablePlayerWeaponSync.Value || !NetGameplaySyncBridge.IsSessionActive || (Object)(object)weapon == (Object)null) { return; } Unit sourceUnit = weapon.SourceUnit; if ((Object)(object)sourceUnit == (Object)null || !sourceUnit.isPlayer || !NetPlayerLifeManager.IsLocalPlayerUnit(sourceUnit)) { return; } WeaponSO weaponDefinition = weapon.weaponDefinition; if ((Object)(object)weaponDefinition == (Object)null || weaponDefinition.IsMelee) { return; } ItemStats itemStats = ((Holdable)weapon).ItemStats; if (itemStats == null) { return; } int num = (int)itemStats.GetAttribute((ItemAttributes)22); if (num <= 0) { return; } int num2 = weapon.lastConsumptionAmount; if (num2 <= 0) { num2 = ResolveShotsFromAmmo(weapon); } if (num2 <= 0 || itemStats.GetAttribute((ItemAttributes)73) > 0f) { return; } bool flag = itemStats.GetAttribute((ItemAttributes)64) > 0f; int num3 = num * num2; if (flag) { num3 *= 7; } if (num3 <= 0) { return; } EquipmentManager equipmentManager = weapon.equipmentManager; if ((Object)(object)equipmentManager == (Object)null) { return; } FullProjectileDescription lastFiredProjectile = equipmentManager.lastFiredProjectile; ProjectileRay ray = lastFiredProjectile.ray; if (((ProjectileRay)(ref ray)).IsValid()) { Vector3 origin = ToVector3(ray.barrelPosition); Vector3 aimAtPosition = weapon.AimAtPosition; float num4 = math.length(ray.velocity); if (num4 < 0.01f) { num4 = weapon.bulletSpeed; } NetPlayerWeaponFire netPlayerWeaponFire = new NetPlayerWeaponFire { Sequence = ++_captureSeq, Origin = origin, AimPoint = aimAtPosition, Count = num3, Speed = num4, Spread = weapon.computedSpread, IsSpray = flag, Homing = (itemStats.GetAttribute((ItemAttributes)92) > 0f), IsRaygun = lastFiredProjectile.isRaygun, ProjectileType = (int)ray.type, Caliber = (int)lastFiredProjectile.data.caliber, Effect = (int)ray.effect, VfxAsset = (int)ray.vfxAsset, DamageType = (int)lastFiredProjectile.data.damageType, ColorR = ray.color.x, ColorG = ray.color.y, ColorB = ray.color.z, CoreColorR = ray.coreColor.x, CoreColorG = ray.coreColor.y, CoreColorB = ray.coreColor.z, InnerWidth = ray.innerWidth, OuterWidth = ray.outerWidth, Radius = ray.radius, Mass = ray.mass, Drag = ray.drag, GravityX = ray.gravity.x, GravityY = ray.gravity.y, GravityZ = ray.gravity.z, TimeScale = ray.timeScale, LifeTime = ray.lifeTime, BehaviourTimeout = ray.behaviourTimeout, BarrelLengthOffset = ray.barrelLengthOffset, BounceHits = ray.bounceHits, Bounciness = ray.bounciness, PenetrationBehavior = (int)ray.penetrationBehavior, PenetrationsLeft = ray.penetrationsLeft, PenetrationDamageMultiplier = ray.penetrationDamageMultiplier, DrawDefaultBullet = ray.drawDefaultBullet, DrawLaserBeam = ray.drawLaserBeam, IsRocket = ray.isRocket, HasCustomColors = ray.hasCustomColors, HasCustomTrail = ray.hasCustomTrail, CreateBulletHoles = ray.createBulletHoles, StickToGeometry = ray.stickToGeometry, PlayImpactSounds = ray.playImpactSounds }; NetGameplaySyncBridge.ReportLocalPlayerWeaponFire(netPlayerWeaponFire); if (Plugin.Cfg.LogPlayerWeaponSync.Value) { NetLogger.Info($"[PlayerWeaponFire] capture weapon={((Object)weaponDefinition).name} count={num3} spray={flag} homing={netPlayerWeaponFire.Homing} raygun={netPlayerWeaponFire.IsRaygun} type={(object)(ProjectileTypes)netPlayerWeaponFire.ProjectileType} cal={(object)(CaliberTypes)(byte)netPlayerWeaponFire.Caliber} speed={num4:F1} spread={netPlayerWeaponFire.Spread:F3}"); } } } catch (Exception ex) { NetLogger.Warn("[PlayerWeaponFire] capture failed: " + ex.Message); } } public static void Replay(NetPlayerWeaponFire m) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006e: 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_0079: 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_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: 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_010e: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) try { if (!Plugin.Cfg.EnablePlayerWeaponSync.Value || m == null || m.Count <= 0) { return; } ProjectileSystem instance = StaticInstance.Instance; if ((Object)(object)instance == (Object)null) { return; } int num = Plugin.Cfg.PlayerWeaponSyncMaxProjectilesPerShot.Value; if (num < 1) { num = 1; } int num2 = Mathf.Min(m.Count, num); Vector3 origin = m.Origin; Vector3 val = m.AimPoint - origin; if (((Vector3)(ref val)).sqrMagnitude < 1E-06f) { val = Vector3.forward; } ((Vector3)(ref val)).Normalize(); Npc val2 = (m.Homing ? FindHomingTarget(origin, val) : null); RailgunSystem val3 = (m.IsRaygun ? StaticInstance.Instance : null); int num3 = 0; for (int i = 0; i < num2; i++) { Vector3 dir = ((m.Spread > 0f) ? Helpers.GetRandomDirectionInCode(m.Spread, val) : val); float num4 = m.Speed; if (m.IsSpray) { num4 *= Random.Range(0.7f, 1.3f); } ProjectileRay val4 = BuildRay(m, origin, dir, num4); ProjectileData val5 = BuildData(m, val2); if ((Object)(object)val3 != (Object)null) { val3.FireRailgun(val4, val5); } else { instance.StartProjectile(val4, val5, (ProjectileCustomVisuals)null); } num3++; } if (Plugin.Cfg.LogPlayerWeaponSync.Value) { NetLogger.Info($"[PlayerWeaponFire] replay peer={m.PeerId} spawned={num3}/{m.Count} type={(object)(ProjectileTypes)m.ProjectileType} raygun={m.IsRaygun} homing={(Object)(object)val2 != (Object)null}"); } } catch (Exception ex) { NetLogger.Warn("[PlayerWeaponFire] replay failed: " + ex.Message); } } internal static bool FireVisualStraight(Vector3 origin, Vector3 velocity, int projectileType, int caliber, int effect, int vfxAsset, int damageType) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_007f: 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_00a8: 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_00b0: 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_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: 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_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0109: 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_010c: Unknown result type (might be due to invalid IL or missing references) try { ProjectileSystem instance = StaticInstance.Instance; if ((Object)(object)instance == (Object)null) { return false; } float3 val = default(float3); ((float3)(ref val))..ctor(origin.x, origin.y, origin.z); ProjectileRay ray = default(ProjectileRay); ((ProjectileRay)(ref ray))..ctor(val, (ProjectileTypes)projectileType); ray.velocity = new float3(velocity.x, velocity.y, velocity.z); ray.radius = 0.05f; ray.timeScale = 1f; ray.lifeTime = 20f; ray.effect = (ProjectileEffect)effect; ray.vfxAsset = (VFX_Persistent)(byte)vfxAsset; ray.drawDefaultBullet = true; ray.playImpactSounds = true; ray.createBulletHoles = true; ray.shotOrLastBounceFrom = val; ray.barrelPosition = val; ray.startTime = Time.time; ray.ownerInstID = 1460711447; ApplyNativeCaliberPresentation(ref ray, caliber); ProjectileData val2 = new ProjectileData { damageType = (DamageTypes)(byte)damageType, caliber = (CaliberTypes)(byte)caliber, isPlayer = false, explicitDamage = 0f }; instance.StartProjectile(ray, val2, (ProjectileCustomVisuals)null); return true; } catch (Exception ex) { NetLogger.Warn("[PlayerWeaponFire] FireVisualStraight failed: " + ex.Message); return false; } } internal static void ApplyNativeCaliberPresentation(ref ProjectileRay ray, int caliber) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_006e: 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) if ((byte)caliber == 7) { ray.drawDefaultBullet = false; ray.drawLaserBeam = true; if (ray.innerWidth <= 0f) { ray.innerWidth = 0.375f; } if (ray.outerWidth <= 0f) { ray.outerWidth = 0.75f; } ray.color = new float3(0.686f, 0.298f, 0.29f); ray.coreColor = new float3(0.812f, 0.58f, 0.573f); } } private static ProjectileRay BuildRay(NetPlayerWeaponFire m, Vector3 origin, Vector3 dir, float speed) { //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_000e: 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_0029: 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_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_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_00d1: 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_0173: 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_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) float3 val = default(float3); ((float3)(ref val))..ctor(origin.x, origin.y, origin.z); ProjectileRay val2 = default(ProjectileRay); ((ProjectileRay)(ref val2))..ctor(val, (ProjectileTypes)m.ProjectileType); val2.velocity = new float3(dir.x * speed, dir.y * speed, dir.z * speed); val2.color = new float3(m.ColorR, m.ColorG, m.ColorB); val2.coreColor = new float3(m.CoreColorR, m.CoreColorG, m.CoreColorB); val2.innerWidth = m.InnerWidth; val2.outerWidth = m.OuterWidth; val2.radius = ((m.Radius > 0f) ? m.Radius : 0.05f); val2.mass = ((m.Mass > 0f) ? m.Mass : val2.mass); val2.drag = m.Drag; val2.gravity = new float3(m.GravityX, m.GravityY, m.GravityZ); val2.timeScale = ((m.TimeScale > 0f) ? m.TimeScale : 1f); val2.lifeTime = ((m.LifeTime > 0f) ? m.LifeTime : 30f); val2.behaviourTimeout = m.BehaviourTimeout; val2.barrelLengthOffset = m.BarrelLengthOffset; val2.effect = (ProjectileEffect)m.Effect; val2.vfxAsset = (VFX_Persistent)(byte)m.VfxAsset; val2.isRocket = m.IsRocket; val2.drawDefaultBullet = m.DrawDefaultBullet; val2.drawLaserBeam = m.DrawLaserBeam; val2.hasCustomColors = m.HasCustomColors; val2.hasCustomTrail = m.HasCustomTrail; val2.createBulletHoles = m.CreateBulletHoles; val2.stickToGeometry = m.StickToGeometry; val2.playImpactSounds = m.PlayImpactSounds; val2.bounceHits = m.BounceHits; val2.bounciness = m.Bounciness; val2.penetrationBehavior = (PenetrationBehavior)m.PenetrationBehavior; val2.penetrationsLeft = m.PenetrationsLeft; val2.penetrationDamageMultiplier = m.PenetrationDamageMultiplier; val2.shotOrLastBounceFrom = val; val2.barrelPosition = val; val2.startTime = Time.time; val2.ownerInstID = 1460711447; return val2; } private static ProjectileData BuildData(NetPlayerWeaponFire m, Npc homingTarget) { //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_001f: 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) return new ProjectileData { damageType = (DamageTypes)(byte)m.DamageType, caliber = (CaliberTypes)(byte)m.Caliber, isPlayer = false, explicitDamage = 0f, homingTarget = homingTarget }; } private static Npc FindHomingTarget(Vector3 origin, Vector3 dir) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_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_00a5: 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_00aa: Unknown result type (might be due to invalid IL or missing references) try { GameManager instance = StaticInstance.Instance; if ((Object)(object)instance == (Object)null || instance.aliveNpcs == null) { return null; } Npc result = null; float num = float.MaxValue; foreach (Npc aliveNpc in instance.aliveNpcs) { if (!((Object)(object)aliveNpc == (Object)null) && !((Object)(object)((Component)aliveNpc).transform == (Object)null) && !((Unit)aliveNpc).IsProtectedNpc && !((Unit)aliveNpc).IsPlayerFaction) { Vector3 val = ((Component)aliveNpc).transform.position + Vector3.up - origin; float magnitude = ((Vector3)(ref val)).magnitude; if (!(magnitude > 35f) && !(magnitude < 0.01f) && !(Vector3.Dot(dir, val / magnitude) < 0.5f) && magnitude < num) { num = magnitude; result = aliveNpc; } } } return result; } catch { return null; } } private static int ResolveShotsFromAmmo(Weapon weapon) { try { if (!_maxAmmoPerShotFieldResolved) { _maxAmmoPerShotFieldResolved = true; _maxAmmoPerShotField = AccessTools.Field(typeof(Weapon), "iMaxAmmoPerShot"); } int iAmmoCurrent = weapon.iAmmoCurrent; if (iAmmoCurrent <= 0) { return 0; } if (_maxAmmoPerShotField == null) { return iAmmoCurrent; } int num = (int)_maxAmmoPerShotField.GetValue(weapon); if (num <= 0) { return iAmmoCurrent; } return Mathf.Min(iAmmoCurrent, num); } catch { return 0; } } private static Vector3 ToVector3(float3 v) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) return new Vector3(v.x, v.y, v.z); } } internal static class RemotePlayerBodyManager { private static bool _resolved; private static bool _failed; private static object _loadHandle; private static GameObject _bodyPrefab; private static string _resolvedName = ""; public static string ResolvedName => _resolvedName; public static void Reset() { } public static bool TryGetBodyPrefab(out GameObject prefab) { prefab = _bodyPrefab; if ((Object)(object)_bodyPrefab != (Object)null) { return true; } if (_failed) { return false; } EnsureResolved(); PollLoad(); prefab = _bodyPrefab; return (Object)(object)_bodyPrefab != (Object)null; } private static void EnsureResolved() { if (_resolved || _failed) { return; } AsyncAssetLoading instance = StaticInstance.Instance; UnitDatabase val = (((Object)(object)instance != (Object)null) ? instance.unitDatabase : null); if ((Object)(object)val == (Object)null) { return; } _resolved = true; try { UnitSO val2 = ResolveBodyUnitSO(val); if ((Object)(object)val2 == (Object)null) { _failed = true; if (Plugin.Cfg.LogRemotePlayerBody.Value) { NetLogger.Warn("[RemoteBody] no humanoid UnitSO matched keywords — falling back to capsule"); } return; } _resolvedName = SafeName(val2); MethodInfo methodInfo = AccessTools.Method(typeof(UnitSO), "FetchAndLoadUnitLoader", (Type[])null, (Type[])null); if (methodInfo == null) { _failed = true; NetLogger.Warn("[RemoteBody] FetchAndLoadUnitLoader not found"); return; } _loadHandle = methodInfo.Invoke(val2, null); if (Plugin.Cfg.LogRemotePlayerBody.Value) { NetLogger.Info($"[RemoteBody] resolved body unit='{_resolvedName}' id={val2.id.value} — loading prefab..."); } } catch (Exception ex) { _failed = true; NetLogger.Warn("[RemoteBody] resolve/load failed: " + ex.Message); } } private static void PollLoad() { if ((Object)(object)_bodyPrefab != (Object)null || _loadHandle == null || _failed) { return; } try { Type type = _loadHandle.GetType(); if (!(bool)(type.GetProperty("IsDone")?.GetValue(_loadHandle) ?? ((object)false))) { return; } object? obj = type.GetProperty("Result")?.GetValue(_loadHandle); GameObject val = (GameObject)((obj is GameObject) ? obj : null); if ((Object)(object)val == (Object)null) { _failed = true; NetLogger.Warn("[RemoteBody] prefab load completed but Result is null — falling back to capsule"); return; } _bodyPrefab = val; if (Plugin.Cfg.LogRemotePlayerBody.Value) { NetLogger.Info("[RemoteBody] body prefab loaded: " + ((Object)val).name); } } catch (Exception ex) { _failed = true; NetLogger.Warn("[RemoteBody] poll load failed: " + ex.Message); } } private static UnitSO ResolveBodyUnitSO(UnitDatabase db) { List rawList; try { rawList = db.GetRawList(); } catch { return null; } if (rawList == null || rawList.Count == 0) { return null; } string[] array = (Plugin.Cfg.RemotePlayerBodyUnitKeyword.Value ?? "").Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim().ToLowerInvariant(); if (text.Length == 0) { continue; } foreach (UnitSO item in rawList) { if (!((Object)(object)item == (Object)null)) { string text2 = SafeName(item).ToLowerInvariant(); string text3 = SafeDisplayName(item).ToLowerInvariant(); if (text2.Contains(text) || text3.Contains(text)) { return item; } } } } return null; } private static string SafeName(UnitSO so) { try { return ((Object)so).name ?? ""; } catch { return ""; } } private static string SafeDisplayName(UnitSO so) { try { return so.displayName ?? ""; } catch { return ""; } } public static void SyncProxyBodies(NetRemotePlayerProxyManager proxies) { if (proxies == null || !Plugin.Cfg.EnableRemotePlayerNpcBody.Value || !TryGetBodyPrefab(out GameObject _)) { return; } proxies.ForEachProxy(delegate(string peerId, NetRemotePlayerProxy proxy) { if (proxy != null && !proxy.HasBody) { GameObject val = BuildBody(); if ((Object)(object)val != (Object)null) { proxy.SetBody(val); if (Plugin.Cfg.LogRemotePlayerBody.Value) { NetLogger.Info("[RemoteBody] attached NPC body to proxy " + peerId + " (unit='" + _resolvedName + "')"); } } } }); } public static GameObject BuildBody() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown if ((Object)(object)_bodyPrefab == (Object)null) { return null; } try { GameObject val = new GameObject("RemoteBodyBuild"); val.SetActive(false); GameObject obj = Object.Instantiate(_bodyPrefab, val.transform); StripGameplay(obj); obj.SetActive(false); obj.transform.SetParent((Transform)null, false); Object.Destroy((Object)(object)val); return obj; } catch (Exception ex) { if (Plugin.Cfg.LogRemotePlayerBody.Value) { NetLogger.Warn("[RemoteBody] build failed: " + ex.Message); } return null; } } private static void StripGameplay(GameObject inst) { try { MonoBehaviour[] componentsInChildren = inst.GetComponentsInChildren(true); foreach (MonoBehaviour val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } string text = ((object)val).GetType().FullName ?? ""; if (!text.StartsWith("PerfectRandom.Sulfur.Core.BillboardSprite") && !text.StartsWith("PerfectRandom.Sulfur.Core.BillboardNpc") && text.StartsWith("PerfectRandom")) { try { Object.DestroyImmediate((Object)(object)val); } catch { } } } Collider[] componentsInChildren2 = inst.GetComponentsInChildren(true); foreach (Collider val2 in componentsInChildren2) { try { Object.DestroyImmediate((Object)(object)val2); } catch { } } Rigidbody[] componentsInChildren3 = inst.GetComponentsInChildren(true); foreach (Rigidbody val3 in componentsInChildren3) { try { Object.DestroyImmediate((Object)(object)val3); } catch { } } AudioSource[] componentsInChildren4 = inst.GetComponentsInChildren(true); foreach (AudioSource val4 in componentsInChildren4) { try { Object.DestroyImmediate((Object)(object)val4); } catch { } } NavMeshAgent[] componentsInChildren5 = inst.GetComponentsInChildren(true); foreach (NavMeshAgent val5 in componentsInChildren5) { try { Object.DestroyImmediate((Object)(object)val5); } catch { } } if (Plugin.Cfg.LogRemotePlayerBody.Value) { int num = inst.GetComponentsInChildren(true).Length; int num2 = inst.GetComponentsInChildren(true).Length; int num3 = inst.GetComponentsInChildren(true).Length; int num4 = inst.GetComponentsInChildren(true).Length; NetLogger.Info($"[RemoteBody] stripped body survivors: sprite={num} mesh={num2} animator={num3} billboard={num4}"); } } catch { } } } internal sealed class RemotePlayerRegistryManager { private sealed class Ghost { public GameObject Go; public Component Player; public object Unit; public float LastUpdatedAt; } private readonly Dictionary _ghosts = new Dictionary(); private readonly HashSet _seenThisTick = new HashSet(); private readonly List _removeScratch = new List(); private static readonly HashSet _ghostGoIds = new HashSet(); private static readonly List _remotePositions = new List(); private static int _activateCursor; private static bool _resolveAttempted; private static bool _resolveOk; private static Type? _unitType; private static Type? _playerType; private static Type? _npcType; private static PropertyInfo? _gmInstance; private static PropertyInfo? _gmPlayers; private static PropertyInfo? _gmNpcs; private static PropertyInfo? _gmPlayerUnit; private static MethodInfo? _setStats; private static MethodInfo? _overrideFaction; private static MethodInfo? _spawn; private static MethodInfo? _activateBehaviour; private static FieldInfo? _unitSOField; private static FieldInfo? _playerUnitField; private static FieldInfo? _cameraRootField; private static FieldInfo? _playerVisualsField; private static FieldInfo? _excludeFromLodField; private static PropertyInfo? _activeHoldableRenderersProp; private static PropertyInfo? _playerIndexProp; private static PropertyInfo? _batchedInstance; private static MethodInfo? _setupNpcList; private static Type? _hitmeshType; private static FieldInfo? _hitmeshOwnerField; private static FieldInfo? _hitmeshColliderField; private static FieldInfo? _hitmeshHitShapesField; private static object? _factionPlayer; private static bool RegistryEnabled { get { try { return Plugin.Cfg.EnableRemotePlayerInPlayersList.Value; } catch { return false; } } } private static bool ActivationEnabled { get { try { return Plugin.Cfg.EnableMultiPlayerNpcActivation.Value; } catch { return false; } } } private static bool LogOn { get { try { return Plugin.Cfg.LogRemotePlayerRegistry.Value; } catch { return false; } } } public int GhostCount => _ghosts.Count; public static bool IsGhostCollider(Collider? col) { try { return (Object)(object)col != (Object)null && _ghostGoIds.Contains(((Object)((Component)((Component)col).transform.root).gameObject).GetInstanceID()); } catch { return false; } } private static bool IsHostLoading() { try { if (!Plugin.Cfg.SuppressGhostsWhileLoading.Value) { return false; } if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || state == null) { return false; } string text = (state.GameState ?? "").Trim(); return text == "Loading" || text == "Uninitialized"; } catch { return false; } } public void Tick(NetRemotePlayerProxyManager visualProxies, float now, float maxAgeSeconds) { bool registry = RegistryEnabled && EnsureResolved() && !IsHostLoading(); _remotePositions.Clear(); _seenThisTick.Clear(); visualProxies.ForEachInScenePlayer(delegate(string peerId, Vector3 pos) { //IL_000e: 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) if (!NetPlayerLifeManager.IsPeerDownOrDead(peerId)) { _remotePositions.Add(pos); if (registry) { _seenThisTick.Add(peerId); UpdateOrCreate(peerId, pos, now); } } }, now, maxAgeSeconds); if (_ghosts.Count <= 0) { return; } _removeScratch.Clear(); foreach (KeyValuePair ghost in _ghosts) { if (!registry || !_seenThisTick.Contains(ghost.Key)) { _removeScratch.Add(ghost.Key); } } foreach (string item in _removeScratch) { Destroy(item, registry ? "player gone" : "registry off"); } } public static void RefreshActivationBuffer(NetRemotePlayerProxyManager visualProxies, float now, float maxAgeSeconds) { if (!ActivationEnabled || visualProxies == null) { return; } _remotePositions.Clear(); visualProxies.ForEachInScenePlayer(delegate(string peerId, Vector3 pos) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (!NetPlayerLifeManager.IsPeerDownOrDead(peerId)) { _remotePositions.Add(pos); } }, now, maxAgeSeconds); } private void UpdateOrCreate(string peerId, Vector3 pos, float now) { //IL_0048: 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) if (_ghosts.TryGetValue(peerId, out Ghost value)) { if (!((Object)(object)value.Go == (Object)null)) { value.Go.transform.position = pos; value.LastUpdatedAt = now; return; } _ghosts.Remove(peerId); } TryCreate(peerId, pos, now); } private void TryCreate(string peerId, Vector3 pos, float now) { //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Expected O, but got Unknown //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_04bf: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown //IL_00a8: Unknown result type (might be due to invalid IL or missing references) try { object value = _gmInstance.GetValue(null, null); if (value == null) { if (LogOn) { Plugin.Log.Info("[PlayerRegistry] GM null, defer create"); } return; } if (!(_gmPlayers.GetValue(value, null) is IList list)) { Plugin.Log.Warn("[PlayerRegistry] GameManager.Players not an IList"); return; } object obj = _gmPlayerUnit?.GetValue(value, null); object obj2 = ((obj != null && _unitSOField != null) ? _unitSOField.GetValue(obj) : null); GameObject val = new GameObject("SULFURTogether RemotePlayerGhost - " + peerId); val.SetActive(false); val.transform.position = pos; bool flag; try { flag = Plugin.Cfg.EnableGhostPlayerHitbox.Value; } catch { flag = false; } int layer = 6; try { layer = Plugin.Cfg.RemotePlayerTargetProxyHitboxLayer.Value; } catch { } if (flag) { val.layer = layer; } CapsuleCollider val2 = val.AddComponent(); val2.height = 1.9f; val2.radius = 0.4f; val2.center = new Vector3(0f, 0.95f, 0f); ((Collider)val2).isTrigger = false; Rigidbody obj5 = val.AddComponent(); obj5.isKinematic = true; obj5.useGravity = false; Component val3 = val.AddComponent(_unitType); if (_unitSOField != null && obj2 != null) { try { _unitSOField.SetValue(val3, obj2); } catch { } } RemotePlayerTargetProxyManager.RegisterExternalProxyUnit(val3, peerId); if (flag && _hitmeshType != null) { try { Component obj7 = val.AddComponent(_hitmeshType); _hitmeshOwnerField?.SetValue(obj7, val3); _hitmeshColliderField?.SetValue(obj7, val2); try { Type type = _hitmeshHitShapesField?.FieldType.GetElementType(); if ((object)type != null) { _hitmeshHitShapesField.SetValue(obj7, Array.CreateInstance(type, 0)); } } catch { } } catch (Exception ex) { Plugin.Log.Warn("[PlayerRegistry] Hitmesh add failed: " + ex.Message); } } GameObject val4 = new GameObject("cameraRoot"); val4.transform.SetParent(val.transform, false); val4.transform.localPosition = new Vector3(0f, 1.6f, 0f); Component val5 = val.AddComponent(_playerType); ((Behaviour)val5).enabled = false; try { _playerUnitField?.SetValue(val5, val3); } catch { } try { _cameraRootField?.SetValue(val5, val4.transform); } catch { } try { _playerVisualsField?.SetValue(val5, Array.CreateInstance(_playerVisualsField.FieldType.GetElementType(), 0)); } catch { } try { Type type2 = _activeHoldableRenderersProp?.PropertyType.GetElementType(); if ((object)type2 != null) { _activeHoldableRenderersProp.SetValue(val5, Array.CreateInstance(type2, 0), null); } } catch { } try { _playerIndexProp?.SetValue(val5, list.Count, null); } catch { } val.SetActive(true); if (_setStats != null && obj2 != null) { try { _setStats.Invoke(val3, new object[1] { obj2 }); } catch (Exception ex2) { Plugin.Log.Warn("[PlayerRegistry] SetStats failed: " + ex2.Message); } } if (_spawn != null) { try { _spawn.Invoke(val3, null); } catch (Exception ex3) { Plugin.Log.Warn("[PlayerRegistry] Spawn failed: " + ex3.Message); } } if (_overrideFaction != null && _factionPlayer != null) { try { _overrideFaction.Invoke(val3, new object[2] { _factionPlayer, 999999f }); } catch (Exception ex4) { Plugin.Log.Warn("[PlayerRegistry] OverrideFaction failed: " + ex4.Message); } } try { if (!list.Contains(val5)) { list.Add(val5); } } catch (Exception ex5) { Plugin.Log.Warn("[PlayerRegistry] Players.Add failed: " + ex5.Message); } RefreshBatchedRaycasts(); _ghosts[peerId] = new Ghost { Go = val, Player = val5, Unit = val3, LastUpdatedAt = now }; _ghostGoIds.Add(((Object)val).GetInstanceID()); Plugin.Log.Info(string.Format("[PlayerRegistry] registered ghost player peer={0} pos={1:F1} players={2} unitSO={3}", peerId, pos, list.Count, (obj2 == null) ? "null" : "ok")); } catch (Exception ex6) { Plugin.Log.Warn("[PlayerRegistry] create failed peer=" + peerId + ": " + ex6.GetType().Name + ": " + ex6.Message); } } private void Destroy(string peerId, string reason) { if (!_ghosts.TryGetValue(peerId, out Ghost value)) { return; } _ghosts.Remove(peerId); try { if ((Object)(object)value.Go != (Object)null) { _ghostGoIds.Remove(((Object)value.Go).GetInstanceID()); } } catch { } bool flag = false; try { object obj2 = _gmInstance?.GetValue(null, null); if (obj2 != null && _gmPlayers?.GetValue(obj2, null) is IList list && (Object)(object)value.Player != (Object)null) { flag = RemoveFromList(list, value.Player); } } catch { } try { RemotePlayerTargetProxyManager.UnregisterExternalProxyUnit(value.Unit); } catch { } try { if ((Object)(object)value.Go != (Object)null) { Object.Destroy((Object)(object)value.Go); } } catch { } if (flag) { RefreshBatchedRaycasts(); } if (LogOn) { Plugin.Log.Info("[PlayerRegistry] removed ghost player peer=" + peerId + " reason=" + reason); } } private static bool RemoveFromList(IList list, object item) { if (!list.Contains(item)) { return false; } list.Remove(item); return true; } private static void RefreshBatchedRaycasts() { try { object obj = _batchedInstance?.GetValue(null, null); if (obj != null) { _setupNpcList?.Invoke(obj, null); } } catch (Exception ex) { Plugin.Log.Warn("[PlayerRegistry] SetupNpcList refresh failed: " + ex.Message); } } public void Clear() { foreach (string item in new List(_ghosts.Keys)) { Destroy(item, "clear"); } _ghosts.Clear(); _remotePositions.Clear(); } public static void ActivateNpcsNearRemotePlayers() { //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_0157: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) if (!ActivationEnabled || _remotePositions.Count == 0 || !EnsureResolved()) { return; } float num; try { num = Plugin.Cfg.MultiPlayerNpcActivationDistance.Value; } catch { num = 60f; } int num2; try { num2 = Plugin.Cfg.MultiPlayerNpcActivationsPerFrame.Value; } catch { num2 = 8; } float num3 = num * num; try { object value = _gmInstance.GetValue(null, null); if (value == null || !(_gmNpcs.GetValue(value, null) is IList { Count: not 0, Count: var count } list)) { return; } int num4 = 0; int num5 = 0; int num6 = (_activateCursor % count + count) % count; bool flag = default(bool); for (int i = 0; i < count; i++) { if (num5 >= num2) { break; } object? obj3 = list[(num6 + i) % count]; Component val = (Component)((obj3 is Component) ? obj3 : null); num4++; if ((Object)(object)val == (Object)null) { continue; } GameObject gameObject = val.gameObject; if (gameObject.activeSelf) { continue; } int num7; if (_excludeFromLodField != null) { object value2 = _excludeFromLodField.GetValue(val); if (value2 is bool) { flag = (bool)value2; num7 = 1; } else { num7 = 0; } } else { num7 = 0; } if (((uint)num7 & (flag ? 1u : 0u)) != 0) { continue; } Vector3 position = val.transform.position; bool flag2 = false; for (int j = 0; j < _remotePositions.Count; j++) { Vector3 val2 = _remotePositions[j] - position; if (((Vector3)(ref val2)).sqrMagnitude < num3) { flag2 = true; break; } } if (flag2) { try { gameObject.SetActive(true); _activateBehaviour?.Invoke(val, null); num5++; } catch (Exception ex) { Plugin.Log.Warn("[PlayerRegistry] activate failed: " + ex.Message); } } } _activateCursor = (num6 + num4) % count; if (num5 > 0 && LogOn) { Plugin.Log.Info($"[PlayerRegistry] activation woke {num5} npc(s) near {_remotePositions.Count} remote player(s)"); } } catch (Exception ex2) { Plugin.Log.Warn("[PlayerRegistry] activation pass failed: " + ex2.GetType().Name + ": " + ex2.Message); } } private static bool EnsureResolved() { if (_resolveAttempted) { return _resolveOk; } _resolveAttempted = true; try { _unitType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Units.Unit"); _playerType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Units.Player"); _npcType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Units.Npc"); Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); if (_unitType == null || _playerType == null || type == null) { Plugin.Log.Warn("[PlayerRegistry] resolve failed: Unit/Player/GameManager type missing"); return false; } _gmInstance = AccessTools.Property(type, "Instance"); _gmPlayers = AccessTools.Property(type, "Players"); _gmNpcs = AccessTools.Property(type, "npcs"); _gmPlayerUnit = AccessTools.Property(type, "PlayerUnit"); _setStats = AccessTools.Method(_unitType, "SetStats", (Type[])null, (Type[])null); _overrideFaction = AccessTools.Method(_unitType, "OverrideFaction", (Type[])null, (Type[])null); _spawn = AccessTools.Method(_unitType, "Spawn", Type.EmptyTypes, (Type[])null); _unitSOField = AccessTools.Field(_unitType, "unitSO"); _playerUnitField = AccessTools.Field(_playerType, "playerUnit"); _cameraRootField = AccessTools.Field(_playerType, "cameraRoot"); _playerVisualsField = AccessTools.Field(_playerType, "playerVisuals"); _activeHoldableRenderersProp = AccessTools.Property(_playerType, "activeHoldableRenderers"); _playerIndexProp = AccessTools.Property(_playerType, "playerIndex"); if (_npcType != null) { _excludeFromLodField = AccessTools.Field(_npcType, "excludeFromNpcLOD"); _activateBehaviour = AccessTools.Method(_npcType, "ActivateBehaviour", (Type[])null, (Type[])null); } if (_overrideFaction != null) { Type parameterType = _overrideFaction.GetParameters()[0].ParameterType; try { _factionPlayer = Enum.Parse(parameterType, "Player"); } catch (Exception ex) { Plugin.Log.Warn("[PlayerRegistry] FactionIds.Player parse failed: " + ex.Message); } } Type type2 = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.BatchedNPCRaycasts"); if (type2 != null) { _batchedInstance = AccessTools.Property(type2, "Instance"); _setupNpcList = AccessTools.Method(type2, "SetupNpcList", (Type[])null, (Type[])null); } _hitmeshType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Hitmesh"); if (_hitmeshType != null) { _hitmeshOwnerField = AccessTools.Field(_hitmeshType, "owner"); _hitmeshColliderField = AccessTools.Field(_hitmeshType, "hitmeshCollider"); _hitmeshHitShapesField = AccessTools.Field(_hitmeshType, "hitShapes"); } _resolveOk = _gmInstance != null && _gmPlayers != null && _gmNpcs != null; Plugin.Log.Info($"[PlayerRegistry] resolve ok={_resolveOk} player={_playerType != null} players={_gmPlayers != null} npcs={_gmNpcs != null} activateBehaviour={_activateBehaviour != null} excludeLod={_excludeFromLodField != null} setupNpcList={_setupNpcList != null} faction={_factionPlayer}"); return _resolveOk; } catch (Exception ex2) { Plugin.Log.Warn("[PlayerRegistry] resolve exception: " + ex2.GetType().Name + ": " + ex2.Message); return false; } } } internal sealed class RemotePlayerSpriteBody : MonoBehaviour { private const int FrameCount = 8; private const float FrameSeconds = 0.125f; private const float PixelsPerUnit = 256f; private const float DownedPitchHysteresis = 5f; private const float DownedHoverWorldY = 0.2f; private static Sprite[] _front; private static Sprite[] _back; private static Sprite _idleFront; private static Sprite _idleBack; private static bool _loadTried; private static bool _loadOk; private SpriteRenderer _sr; private float _facingYaw; private float _lookPitch; private bool _moving; private bool _downed; private float _animTimer; private int _frame; private bool _showingBack; private bool _downedShowingBack; private static MethodInfo _loadImageMi; private static bool _loadImageResolved; public static bool ResourcesAvailable() { EnsureLoaded(); return _loadOk; } public static void SyncProxyBodies(NetRemotePlayerProxyManager proxies) { if (proxies == null || !Plugin.Cfg.EnableRemotePlayerSpriteBody.Value || !ResourcesAvailable()) { return; } proxies.ForEachProxy(delegate(string peerId, NetRemotePlayerProxy proxy) { if (proxy != null && !proxy.HasBody) { GameObject val = Build(); if ((Object)(object)val != (Object)null) { proxy.SetBody(val); if (Plugin.Cfg.LogRemotePlayerBody.Value) { Plugin.Log.Info("[FatherBody] attached sprite body to proxy " + peerId); } } } }); } public static GameObject Build() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown if (!ResourcesAvailable()) { return null; } GameObject val = new GameObject("FatherBody"); val.SetActive(false); val.AddComponent(); return val; } public void SetState(float facingYaw, float lookPitch, bool moving, bool downed) { _facingYaw = facingYaw; _lookPitch = lookPitch; _moving = moving; if (downed && !_downed) { _downedShowingBack = lookPitch > 0f; } _downed = downed; } private void Awake() { EnsureLoaded(); _sr = ((Component)this).gameObject.GetComponent(); if ((Object)(object)_sr == (Object)null) { _sr = ((Component)this).gameObject.AddComponent(); } _sr.sprite = (((Object)(object)_idleFront != (Object)null) ? _idleFront : ((_front != null && _front.Length != 0) ? _front[0] : null)); } private void LateUpdate() { //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_0058: 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_005d: 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) //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_0072: 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_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_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: 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_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) if (!_loadOk || (Object)(object)_sr == (Object)null) { return; } if (_downed) { UpdateDowned(); return; } Camera val = ResolveCamera(); if ((Object)(object)val == (Object)null) { return; } Transform parent = ((Component)this).transform.parent; Vector3 val2 = (((Object)(object)parent != (Object)null) ? parent.position : ((Component)this).transform.position); Vector3 val3 = ((Component)val).transform.position - val2; Vector3 forward = default(Vector3); ((Vector3)(ref forward))..ctor(val3.x, 0f, val3.z); if (((Vector3)(ref forward)).sqrMagnitude < 1E-05f) { forward = Vector3.forward; } Vector3 normalized = ((Vector3)(ref forward)).normalized; bool flag = Vector3.Dot(Quaternion.Euler(0f, _facingYaw, 0f) * Vector3.forward, normalized) < 0f; if (flag != _showingBack) { _showingBack = flag; _frame = Mathf.Clamp(_frame, 0, 7); } float num = 25f; float num2 = 0f; try { num = Plugin.Cfg.RemoteBodyPitchLimit.Value; } catch { } try { num2 = Plugin.Cfg.RemoteBodyDepthBias.Value; } catch { } float num3 = ((Vector3)(ref forward)).magnitude * Mathf.Tan(Mathf.Clamp(num, 0f, 89f) * ((float)Math.PI / 180f)); float num4 = Mathf.Clamp(val3.y, 0f - num3, num3); Vector3 val4 = default(Vector3); ((Vector3)(ref val4))..ctor(val3.x, num4, val3.z); if (((Vector3)(ref val4)).sqrMagnitude < 1E-06f) { val4 = normalized; } ((Component)this).transform.rotation = Quaternion.LookRotation(((Vector3)(ref val4)).normalized, Vector3.up); ((Component)this).transform.position = val2 + normalized * (flag ? num2 : 0f); if (_moving) { _animTimer += Time.deltaTime; while (_animTimer >= 0.125f) { _animTimer -= 0.125f; _frame = (_frame + 1) % 8; } } else { _frame = 0; _animTimer = 0f; } Sprite val5 = (_showingBack ? _idleBack : _idleFront); if (!_moving && (Object)(object)val5 != (Object)null) { _sr.sprite = val5; return; } Sprite[] array = (_showingBack ? _back : _front); if (array != null && array.Length == 8) { _sr.sprite = array[_frame]; } } private void UpdateDowned() { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: 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) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011f: 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_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //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) if (!_downedShowingBack && _lookPitch > 5f) { _downedShowingBack = true; } else if (_downedShowingBack && _lookPitch < -5f) { _downedShowingBack = false; } _frame = 0; _animTimer = 0f; Sprite val = (_downedShowingBack ? _idleBack : _idleFront); if ((Object)(object)val != (Object)null) { _sr.sprite = val; } else { Sprite[] array = (_downedShowingBack ? _back : _front); if (array != null && array.Length == 8) { _sr.sprite = array[0]; } } Vector3 val2 = Quaternion.Euler(0f, _facingYaw, 0f) * Vector3.back; ((Component)this).transform.rotation = Quaternion.LookRotation(Vector3.up, val2); Transform parent = ((Component)this).transform.parent; Transform val3 = (((Object)(object)parent != (Object)null) ? parent.parent : null); Vector3 val4 = (((Object)(object)val3 != (Object)null) ? val3.position : ((!((Object)(object)parent != (Object)null)) ? ((Component)this).transform.position : (parent.position - Vector3.up * ((Component)this).transform.lossyScale.y))); ((Component)this).transform.position = val4 + Vector3.up * 0.2f; } private static Camera ResolveCamera() { try { GameManager instance = StaticInstance.Instance; if ((Object)(object)instance != (Object)null && (Object)(object)instance.currentCamera != (Object)null) { return instance.currentCamera; } } catch { } return Camera.main; } private static void EnsureLoaded() { if (_loadTried) { return; } _loadTried = true; try { _front = LoadSheet("father_front", 8); _back = LoadSheet("father_back", 8); Sprite[] array = LoadSheet("father_idle_front", 1); Sprite[] array2 = LoadSheet("father_idle_back", 1); _idleFront = ((array != null && array.Length == 1) ? array[0] : null); _idleBack = ((array2 != null && array2.Length == 1) ? array2[0] : null); _loadOk = _front != null && _back != null && _front.Length == 8 && _back.Length == 8; if (!_loadOk) { Plugin.Log.Warn("[FatherBody] sprite sheets failed to load (embedded resources missing?)"); } else if (Plugin.Cfg.LogRemotePlayerBody.Value) { Plugin.Log.Info("[FatherBody] sprite sheets loaded (front/back, 8 frames each; idle " + (((Object)(object)_idleFront != (Object)null && (Object)(object)_idleBack != (Object)null) ? "loaded" : "MISSING") + ")"); } } catch (Exception ex) { _loadOk = false; Plugin.Log.Warn("[FatherBody] load failed: " + ex.Message); } } private static bool LoadImageInto(Texture2D tex, byte[] bytes) { if (!_loadImageResolved) { _loadImageResolved = true; Type type = AccessTools.TypeByName("UnityEngine.ImageConversion"); if (type != null) { _loadImageMi = type.GetMethod("LoadImage", BindingFlags.Static | BindingFlags.Public, null, new Type[3] { typeof(Texture2D), typeof(byte[]), typeof(bool) }, null); } } if (_loadImageMi == null) { return false; } object obj = _loadImageMi.Invoke(null, new object[3] { tex, bytes, false }); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private static Sprite[] LoadSheet(string resourceName, int frameCount) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_00c8: 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) using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName); if (stream == null) { Plugin.Log.Warn("[FatherBody] embedded resource not found: " + resourceName); return null; } byte[] bytes; using (MemoryStream memoryStream = new MemoryStream()) { stream.CopyTo(memoryStream); bytes = memoryStream.ToArray(); } Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); if (!LoadImageInto(val, bytes)) { Plugin.Log.Warn("[FatherBody] LoadImage failed: " + resourceName); return null; } ((Texture)val).filterMode = (FilterMode)1; ((Texture)val).wrapMode = (TextureWrapMode)1; int num = ((Texture)val).width / frameCount; int height = ((Texture)val).height; Sprite[] array = (Sprite[])(object)new Sprite[frameCount]; Rect val2 = default(Rect); for (int i = 0; i < frameCount; i++) { ((Rect)(ref val2))..ctor((float)(i * num), 0f, (float)num, (float)height); array[i] = Sprite.Create(val, val2, new Vector2(0.5f, 0.5f), 256f, 0u, (SpriteMeshType)0); ((Object)array[i]).name = $"{resourceName}_{i}"; } return array; } } internal static class RuntimeSpawnManager { private sealed class Pending { public object UnitSO; public string Source = ""; public float At; } private sealed class MinionCtx { public object UnitSO; public int Remaining; public float Deadline; } public static int RuntimeSpawnBroadcast; public static int RuntimeSpawnMirrored; public static int RuntimeSpawnMirrorFailed; public static int DeathSpawnHostBroadcast; public static int DeathSpawnClientSuppressed; private static readonly List _pending = new List(); private const float PendingTtl = 8f; private static bool _resolved; private static Type? _unitIdType; private static MethodInfo? _spawnUnitAsync; private static PropertyInfo? _unitDbProp; private static MethodInfo? _unitDbIndexer; private static object? _asyncAssetLoadingInstance; [ThreadStatic] private static int _hostDeathSpawnDepth; public static int MinionHostBroadcast; public static int MinionClientSuppressed; private static readonly List _minionCtx = new List(); private static object? _gmCached; private static bool Enabled { get { try { return Plugin.Cfg.EnableRuntimeSpawnSync.Value; } catch { return false; } } } private static bool LogOn { get { try { return Plugin.Cfg.LogRuntimeSpawnSync.Value; } catch { return false; } } } private static bool DeathSpawnEnabled { get { try { return Plugin.Cfg.EnableDeathSpawnSync.Value; } catch { return false; } } } private static bool MinionSpawnEnabled { get { try { return Plugin.Cfg.EnableMinionSpawnSync.Value; } catch { return false; } } } public static void Reset() { lock (_pending) { _pending.Clear(); _minionCtx.Clear(); } _hostDeathSpawnDepth = 0; } public static void BeginHostDeathSpawn() { if (NetGameplaySyncBridge.BossMode == NetMode.Host) { _hostDeathSpawnDepth++; } } public static void EndHostDeathSpawn() { if (_hostDeathSpawnDepth > 0) { _hostDeathSpawnDepth--; } } public static bool ClientShouldSuppressDeathSpawn() { if (!Enabled || !DeathSpawnEnabled) { return false; } if (NetGameplaySyncBridge.BossMode != NetMode.Client) { return false; } DeathSpawnClientSuppressed++; if (LogOn) { Plugin.Log.Info($"[RuntimeSpawn] client suppressed local death-spawn (#{DeathSpawnClientSuppressed}) — mirroring host's instead"); } return true; } public static void BeginHostMinionContext(object parentUnitSO, int count) { if (NetGameplaySyncBridge.BossMode != NetMode.Host || parentUnitSO == null || count <= 0) { return; } lock (_pending) { float now = Time.realtimeSinceStartup; _minionCtx.RemoveAll((MinionCtx c) => now > c.Deadline); _minionCtx.Add(new MinionCtx { UnitSO = parentUnitSO, Remaining = count, Deadline = now + 5f }); } } private static bool TryClaimMinionContext(object unitSO) { if (!MinionSpawnEnabled || unitSO == null) { return false; } float realtimeSinceStartup = Time.realtimeSinceStartup; for (int i = 0; i < _minionCtx.Count; i++) { MinionCtx minionCtx = _minionCtx[i]; if (!(realtimeSinceStartup > minionCtx.Deadline) && minionCtx.UnitSO == unitSO) { minionCtx.Remaining--; if (minionCtx.Remaining <= 0) { _minionCtx.RemoveAt(i); } return true; } } return false; } public static bool ClientShouldSuppressMinions() { if (!Enabled || !MinionSpawnEnabled) { return false; } if (NetGameplaySyncBridge.BossMode != NetMode.Client) { return false; } MinionClientSuppressed++; if (LogOn) { Plugin.Log.Info($"[RuntimeSpawn] client suppressed local SpawnMinions (#{MinionClientSuppressed}) — mirroring host's instead"); } return true; } public static void NotePendingSpawn(object unitSO, object owner) { try { if (!Enabled || unitSO == null || owner == null || NetGameplaySyncBridge.BossMode != NetMode.Host) { return; } string text = ClassifyOwner(owner); lock (_pending) { float now = Time.realtimeSinceStartup; if (text == null && TryClaimMinionContext(unitSO)) { text = "DeathMinion"; MinionHostBroadcast++; } if (text != null) { _pending.RemoveAll((Pending p) => now - p.At > 8f); _pending.Add(new Pending { UnitSO = unitSO, Source = text, At = now }); } } } catch { } } public static void OnUnitSpawned(object unitSO, object spawnedUnit, Vector3 position) { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) try { if (!Enabled || unitSO == null || spawnedUnit == null || NetGameplaySyncBridge.BossMode != NetMode.Host) { return; } string text = null; lock (_pending) { for (int i = 0; i < _pending.Count; i++) { if (_pending[i].UnitSO == unitSO) { text = _pending[i].Source; _pending.RemoveAt(i); break; } } } if (text == null && _hostDeathSpawnDepth > 0 && DeathSpawnEnabled) { text = "DeathSpawn"; DeathSpawnHostBroadcast++; } if (text != null) { BroadcastHostRuntimeSpawn(spawnedUnit, unitSO, position, text); } } catch (Exception ex) { Plugin.Log.Warn("[RuntimeSpawn] OnUnitSpawned failed: " + ex.GetType().Name + ": " + ex.Message); } } private static string? ClassifyOwner(object owner) { if (owner.GetType().Name == "DevToolsManager") { return "DevTools"; } return null; } private static void BroadcastHostRuntimeSpawn(object spawnedUnit, object unitSO, Vector3 position, string source) { //IL_00a7: 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) int num = ReadUnitIdValue(unitSO); if (num == 0) { if (LogOn) { Plugin.Log.Info("[RuntimeSpawn] host skip (no unitId) src=" + source); } return; } int spawnIndexForObject = NetGameplayProbeManager.GetSpawnIndexForObject(spawnedUnit); if (spawnIndexForObject <= 0) { Plugin.Log.Warn($"[RuntimeSpawn] host skip src={source} unitId={num}: spawnIndex not ready"); return; } if (!NetBossEncounterManager.TryGetRunContext(out string chapter, out int level, out bool hasSeed, out int seed)) { chapter = ""; level = -1; } float rotationY = 0f; try { Component val = (Component)((spawnedUnit is Component) ? spawnedUnit : null); if (val != null && (Object)(object)val != (Object)null) { rotationY = val.transform.eulerAngles.y; } } catch { } NetRuntimeSpawn netRuntimeSpawn = new NetRuntimeSpawn { UnitIdValue = num, Position = position, RotationY = rotationY, HostSpawnIndex = spawnIndexForObject, ChapterName = chapter, LevelIndex = level, HasSeed = hasSeed, Seed = seed, Source = source }; RuntimeSpawnBroadcast++; Plugin.Log.Info("[RuntimeSpawn] host broadcasting " + netRuntimeSpawn.ToCompact()); NetGameplaySyncBridge.BroadcastHostRuntimeSpawn(netRuntimeSpawn); } public static void HandleHostRuntimeSpawn(NetRuntimeSpawn msg) { //IL_00c8: Unknown result type (might be due to invalid IL or missing references) try { if (!Enabled || msg == null || NetGameplaySyncBridge.BossMode != NetMode.Client) { return; } if (NetBossEncounterManager.TryGetRunContext(out string chapter, out int level, out bool _, out int _) && (!string.Equals(chapter, msg.ChapterName, StringComparison.Ordinal) || level != msg.LevelIndex)) { if (LogOn) { Plugin.Log.Info($"[RuntimeSpawn] client drop (run mismatch) {msg.ToCompact()} local={chapter}:{level}"); } return; } EnsureResolved(); object obj = ResolveUnitSO(msg.UnitIdValue); if (obj == null) { RuntimeSpawnMirrorFailed++; Plugin.Log.Warn($"[RuntimeSpawn] client cannot resolve UnitSO for unitId={msg.UnitIdValue}"); } else { Plugin.Log.Info("[RuntimeSpawn] client mirroring " + msg.ToCompact()); MirrorSpawnAsync(obj, msg.Position, msg.RotationY, msg.HostSpawnIndex); } } catch (Exception ex) { Plugin.Log.Warn("[RuntimeSpawn] HandleHostRuntimeSpawn failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void MirrorBossAdd(int unitIdValue, Vector3 position, int hostSpawnIndex) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) try { if (Enabled && NetGameplaySyncBridge.BossMode == NetMode.Client) { EnsureResolved(); object obj = ResolveUnitSO(unitIdValue); if (obj == null) { RuntimeSpawnMirrorFailed++; Plugin.Log.Warn($"[RuntimeSpawn] boss-add mirror cannot resolve unitId={unitIdValue}"); } else { MirrorSpawnAsync(obj, position, 0f, hostSpawnIndex); } } } catch (Exception ex) { Plugin.Log.Warn("[RuntimeSpawn] MirrorBossAdd failed: " + ex.GetType().Name + ": " + ex.Message); } } private static async void MirrorSpawnAsync(object unitSO, Vector3 position, float rotY, int hostSpawnIndex) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) try { if (_spawnUnitAsync == null) { RuntimeSpawnMirrorFailed++; return; } object obj = ResolveGameManager(); if (obj == null) { RuntimeSpawnMirrorFailed++; Plugin.Log.Warn("[RuntimeSpawn] client mirror: no GameManager"); return; } Quaternion val = Quaternion.Euler(0f, rotY, 0f); object obj2 = _spawnUnitAsync.Invoke(unitSO, new object[3] { obj, position, val }); object obj3 = null; if (obj2 is Task task) { await task; obj3 = task.GetType().GetProperty("Result")?.GetValue(task); } if (obj3 == null) { RuntimeSpawnMirrorFailed++; Plugin.Log.Warn($"[RuntimeSpawn] client mirror produced null unit hostIdx={hostSpawnIndex}"); return; } bool flag = NetGameplayProbeManager.RegisterMirroredRuntimeSpawn(obj3, hostSpawnIndex); if (flag) { RuntimeSpawnMirrored++; } else { RuntimeSpawnMirrorFailed++; } Plugin.Log.Info($"[RuntimeSpawn] client mirrored unit hostIdx={hostSpawnIndex} bound={flag} unit={BossReflect.RootName(obj3)}"); NetBossEncounterManager.OnRuntimeMirrorSpawned(obj3); } catch (Exception ex) { RuntimeSpawnMirrorFailed++; Plugin.Log.Warn("[RuntimeSpawn] MirrorSpawnAsync failed: " + ex.GetType().Name + ": " + ex.Message); } } private static int ReadUnitIdValue(object unitSO) { try { object member = BossReflect.GetMember(unitSO, "id"); if (member == null) { return 0; } object member2 = BossReflect.GetMember(member, "value"); if (member2 == null) { return 0; } return Convert.ToInt32(member2); } catch { return 0; } } private static void EnsureResolved() { if (_resolved) { return; } _resolved = true; try { Type type = BossReflect.FindType("UnitSO", "PerfectRandom.Sulfur.Core.Units.UnitSO"); _unitIdType = BossReflect.FindType("UnitId", "PerfectRandom.Sulfur.Core.UnitId"); if (type != null) { MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "SpawnUnitAsync")) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 3 && parameters[0].ParameterType.Name == "MonoBehaviour" && parameters[1].ParameterType == typeof(Vector3)) { _spawnUnitAsync = methodInfo; break; } } } } Type type2 = BossReflect.FindType("AsyncAssetLoading", "PerfectRandom.Sulfur.Core.AsyncAssetLoading"); if (type2 != null) { _asyncAssetLoadingInstance = ResolveStaticInstance(type2); _unitDbProp = type2.GetProperty("unitDatabase", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } Type type3 = BossReflect.FindType("UnitDatabase", "PerfectRandom.Sulfur.Core.UnitDatabase"); if (type3 != null && _unitIdType != null) { _unitDbIndexer = type3.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { _unitIdType }, null); } Plugin.Log.Info($"[RuntimeSpawn] resolved spawnAsync={_spawnUnitAsync != null} unitId={_unitIdType != null} db={_unitDbIndexer != null}"); } catch (Exception ex) { Plugin.Log.Warn("[RuntimeSpawn] EnsureResolved failed: " + ex.GetType().Name + ": " + ex.Message); } } private static object? ResolveUnitSO(int unitIdValue) { try { if (_unitIdType == null || _unitDbIndexer == null) { return null; } object obj = ((_unitDbProp != null && _asyncAssetLoadingInstance != null) ? _unitDbProp.GetValue(_asyncAssetLoadingInstance) : null); if (obj == null) { return null; } object obj2 = Activator.CreateInstance(_unitIdType, (ushort)unitIdValue); return _unitDbIndexer.Invoke(obj, new object[1] { obj2 }); } catch (Exception ex) { Plugin.Log.Warn("[RuntimeSpawn] ResolveUnitSO failed: " + ex.GetType().Name + ": " + ex.Message); return null; } } private static object? ResolveGameManager() { try { object? gmCached = _gmCached; Object val = (Object)((gmCached is Object) ? gmCached : null); if (val != null && val != (Object)null) { return _gmCached; } Type type = BossReflect.FindType("GameManager", "PerfectRandom.Sulfur.Core.GameManager", "PerfectRandom.Sulfur.Gameplay.GameManager"); _gmCached = ((type == null) ? null : ResolveStaticInstance(type)); return _gmCached; } catch { return null; } } private static object? ResolveStaticInstance(Type t) { try { return t.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy)?.GetValue(null); } catch { return null; } } } internal static class TriggerDoorSyncManager { private static int _seq; private const float MatchEpsilon = 1f; private static Type? _triggerType; private static bool _typeResolved; private static FieldInfo? _eventField; private static bool Enabled { get { try { return Plugin.Cfg.EnableTriggerDoorSync.Value; } catch { return false; } } } private static bool LogOn { get { try { return Plugin.Cfg.LogTriggerDoorSync.Value; } catch { return false; } } } public static void CaptureLocalTrigger(object trigger) { //IL_0063: 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) try { if (!Enabled || !NetGameplaySyncBridge.IsSessionActive) { return; } Component val = (Component)((trigger is Component) ? trigger : null); if (val == null || (Object)(object)val == (Object)null) { return; } List<(GameObject, string)> list = ExtractDoorTargets(trigger); if (list.Count == 0) { return; } NetTriggerDoors netTriggerDoors = new NetTriggerDoors { Sequence = ++_seq, TriggerPos = val.transform.position }; foreach (var item in list) { netTriggerDoors.Doors.Add(new NetTriggerDoors.DoorEntry { Name = ((Object)item.Item1).name, Active = item.Item1.activeSelf }); } NetGameplaySyncBridge.ReportLocalTriggerDoors(netTriggerDoors); if (!LogOn) { return; } StringBuilder stringBuilder = new StringBuilder(); foreach (NetTriggerDoors.DoorEntry door in netTriggerDoors.Doors) { stringBuilder.Append(door.Name).Append('=').Append(door.Active ? "on" : "off") .Append(' '); } NetLogger.Info($"[DoorSync] capture trigger={((Object)val).name} pos={val.transform.position} doors=[{stringBuilder.ToString().TrimEnd(Array.Empty())}]"); } catch (Exception ex) { NetLogger.Warn("[DoorSync] capture failed: " + ex.Message); } } public static void ApplyRemote(NetTriggerDoors m) { //IL_0010: 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_0119: Unknown result type (might be due to invalid IL or missing references) try { if (!Enabled || m == null) { return; } object obj = FindMatchTrigger(m.TriggerPos); if (obj == null) { if (LogOn) { NetLogger.Info($"[DoorSync] mirror peer={m.PeerId} no trigger near {m.TriggerPos}"); } return; } List<(GameObject, string)> list = ExtractDoorTargets(obj); int num = 0; foreach (NetTriggerDoors.DoorEntry door in m.Doors) { foreach (var item in list) { if (string.Equals(((Object)item.Item1).name, door.Name, StringComparison.Ordinal)) { if (item.Item1.activeSelf != door.Active) { item.Item1.SetActive(door.Active); } num++; break; } } } if (LogOn) { NetLogger.Info($"[DoorSync] mirror peer={m.PeerId} trigger near {m.TriggerPos} applied={num}/{m.Doors.Count}"); } } catch (Exception ex) { NetLogger.Warn("[DoorSync] mirror failed: " + ex.Message); } } private static List<(GameObject go, string name)> ExtractDoorTargets(object trigger) { List<(GameObject, string)> list = new List<(GameObject, string)>(); try { if (_eventField == null) { _eventField = trigger.GetType().GetField("onTriggerEvents", BindingFlags.Instance | BindingFlags.Public); } object? obj = _eventField?.GetValue(trigger); UnityEventBase val = (UnityEventBase)((obj is UnityEventBase) ? obj : null); if (val == null) { return list; } int persistentEventCount = val.GetPersistentEventCount(); for (int i = 0; i < persistentEventCount; i++) { if (string.Equals(val.GetPersistentMethodName(i), "SetActive", StringComparison.Ordinal)) { Object persistentTarget = val.GetPersistentTarget(i); object obj2 = ((persistentTarget is GameObject) ? persistentTarget : null); if (obj2 == null) { Object obj3 = ((persistentTarget is Component) ? persistentTarget : null); obj2 = ((obj3 != null) ? ((Component)obj3).gameObject : null); } GameObject val2 = (GameObject)obj2; if (!((Object)(object)val2 == (Object)null) && ((Object)val2).name.IndexOf("door", StringComparison.OrdinalIgnoreCase) >= 0) { list.Add((val2, ((Object)val2).name)); } } } } catch { } return list; } private static object FindMatchTrigger(Vector3 key) { //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) //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) try { Type type = ResolveTriggerType(); if (type == null) { return null; } Object[] array = Object.FindObjectsOfType(type); Component result = null; float num = 1f; Object[] array2 = array; foreach (Object obj in array2) { Component val = (Component)(object)((obj is Component) ? obj : null); if (val != null && !((Object)(object)val == (Object)null)) { Vector3 val2 = val.transform.position - key; float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude; if (sqrMagnitude <= num) { num = sqrMagnitude; result = val; } } } return result; } catch { return null; } } private static Type ResolveTriggerType() { if (!_typeResolved) { _typeResolved = true; _triggerType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.World.PlayerTrigger") ?? AccessTools.TypeByName("PerfectRandom.Sulfur.Core.PlayerTrigger"); } return _triggerType; } } internal static class WorldPickupManager { private sealed class Entry { public string OwnerPeerId = ""; public ushort Seq; public string Key = ""; public ItemDefinition ItemSO; public InventoryData Data; public bool Rotated; } private struct PendingRemoval { public string Key; public string TakenBy; } private static readonly Dictionary _byPickup = new Dictionary(); private static readonly Dictionary _byKey = new Dictionary(); private static readonly HashSet _claimed = new HashSet(); private static readonly HashSet _deadKeys = new HashSet(); private static readonly Dictionary _requestCooldown = new Dictionary(); private const float RequestCooldownSeconds = 1f; private static readonly List _pendingRemovals = new List(); private static bool _applyingMirror; private static ushort _seq; public static bool IsApplyingMirror => _applyingMirror; private static bool Qualifies(Pickup p) { if ((Object)(object)p == (Object)null || (Object)(object)p.ItemSO == (Object)null) { return false; } if (Plugin.Cfg.ShareAllLoot.Value) { return true; } return p.inventoryData != null; } public static void CaptureLocalSpawn(Pickup p) { //IL_00fd: Unknown result type (might be due to invalid IL or missing references) try { if (_applyingMirror || !Plugin.Cfg.EnableWorldItemDropSync.Value || !NetGameplaySyncBridge.IsSessionActive || (Object)(object)p == (Object)null || _byPickup.ContainsKey(p) || !Qualifies(p)) { return; } string localPeerId = NetGameplaySyncBridge.LocalPeerId; if (!string.IsNullOrEmpty(localPeerId)) { ushort seq = ++_seq; NetWorldPickupSpawn netWorldPickupSpawn = BuildSpawnMessage(p, localPeerId, seq); Register(p, localPeerId, seq, p.ItemSO, p.inventoryData, p.inventoryData != null && p.inventoryData.rotated); NetGameplaySyncBridge.ReportLocalWorldPickupSpawn(netWorldPickupSpawn); if (Plugin.Cfg.LogWorldItemDropSync.Value) { NetLogger.Info($"[WorldPickup] capture key={netWorldPickupSpawn.Key} item={((Object)p.ItemSO).name} hasData={netWorldPickupSpawn.HasData} pos={netWorldPickupSpawn.Position}"); } } } catch (Exception ex) { NetLogger.Warn("[WorldPickup] capture failed: " + ex.Message); } } public static void ApplyRemoteSpawn(NetWorldPickupSpawn m) { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) try { if (!Plugin.Cfg.EnableWorldItemDropSync.Value || m == null || _byKey.ContainsKey(m.Key) || _deadKeys.Contains(m.Key)) { return; } ItemDatabase val = (((Object)(object)StaticInstance.Instance != (Object)null) ? StaticInstance.Instance.itemDatabase : null); ItemDefinition val2 = (((Object)(object)val != (Object)null) ? val[new ItemId(m.ItemId)] : null); if ((Object)(object)val2 == (Object)null) { if (Plugin.Cfg.LogWorldItemDropSync.Value) { NetLogger.Warn($"[WorldPickup] mirror key={m.Key} unknown itemId={m.ItemId}"); } return; } InventoryData val3 = (m.HasData ? BuildInventoryData(m, val2) : null); Pickup val4 = null; _applyingMirror = true; try { val4 = StaticInstance.Instance.SpawnPickup(m.Position, false, val2, (Room)null, val3, (Container)null, 0f); } finally { _applyingMirror = false; } if ((Object)(object)val4 != (Object)null) { Register(val4, m.OwnerPeerId, m.Seq, val2, val3, m.HasData && m.Rotated); if (Plugin.Cfg.LogWorldItemDropSync.Value) { NetLogger.Info($"[WorldPickup] mirror key={m.Key} item={((Object)val2).name} hasData={m.HasData}"); } } } catch (Exception ex) { NetLogger.Warn("[WorldPickup] mirror failed: " + ex.Message); } } public static bool TryBeginTake(Pickup p) { try { if (!Plugin.Cfg.EnableWorldItemDropSync.Value || (Object)(object)p == (Object)null) { return false; } if (!_byPickup.TryGetValue(p, out Entry value)) { return false; } if (!NetGameplaySyncBridge.IsSessionActive) { return false; } if (!HasRoomFor(value.ItemSO)) { return true; } if (NetGameplaySyncBridge.IsHost) { HostGrant(value.OwnerPeerId, value.Seq, NetGameplaySyncBridge.LocalPeerId); } else { TrySendTakeRequest(value); } return true; } catch (Exception ex) { NetLogger.Warn("[WorldPickup] begin-take failed: " + ex.Message); return false; } } private static void TrySendTakeRequest(Entry e) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!_requestCooldown.TryGetValue(e.Key, out var value) || !(realtimeSinceStartup < value)) { _requestCooldown[e.Key] = realtimeSinceStartup + 1f; NetGameplaySyncBridge.SendWorldPickupTakeRequest(e.OwnerPeerId, e.Seq); if (Plugin.Cfg.LogWorldItemDropSync.Value) { NetLogger.Info("[WorldPickup] take request key=" + e.Key); } } } public static void HostHandleTakeRequest(NetWorldPickupTake req, string requesterPeerId) { if (req != null && !string.IsNullOrEmpty(requesterPeerId)) { HostGrant(req.OwnerPeerId, req.Seq, requesterPeerId); } } private static void HostGrant(string ownerPeerId, ushort seq, string takerPeerId) { string text = ownerPeerId + "#" + seq; if (_claimed.Add(text)) { NetGameplaySyncBridge.BroadcastWorldPickupRemoved(new NetWorldPickupRemoved { OwnerPeerId = ownerPeerId, Seq = seq, TakenByPeerId = takerPeerId }); EnqueueRemoval(text, takerPeerId); if (Plugin.Cfg.LogWorldItemDropSync.Value) { NetLogger.Info("[WorldPickup] host grant key=" + text + " taker=" + takerPeerId); } } } public static void ApplyRemoved(string key, string takenBy) { EnqueueRemoval(key, takenBy); } private static void EnqueueRemoval(string key, string takenBy) { if (!string.IsNullOrEmpty(key)) { _deadKeys.Add(key); _requestCooldown.Remove(key); _pendingRemovals.Add(new PendingRemoval { Key = key, TakenBy = (takenBy ?? "") }); } } public static void Tick() { if (_pendingRemovals.Count == 0) { return; } PendingRemoval[] array = _pendingRemovals.ToArray(); _pendingRemovals.Clear(); string localPeerId = NetGameplaySyncBridge.LocalPeerId; PendingRemoval[] array2 = array; for (int i = 0; i < array2.Length; i++) { PendingRemoval pendingRemoval = array2[i]; try { ApplyRemovalNow(pendingRemoval.Key, pendingRemoval.TakenBy, localPeerId); } catch (Exception ex) { NetLogger.Warn("[WorldPickup] removal failed key=" + pendingRemoval.Key + ": " + ex.Message); } } } private static void ApplyRemovalNow(string key, string takenBy, string localPeer) { if (!_byKey.TryGetValue(key, out Pickup value) || (Object)(object)value == (Object)null) { if (Plugin.Cfg.LogWorldItemDropSync.Value) { NetLogger.Info("[WorldPickup] removed key=" + key + " (no local instance)"); } _byKey.Remove(key); return; } bool num = !string.IsNullOrEmpty(takenBy) && takenBy == localPeer; Entry value2; Entry entry = (_byPickup.TryGetValue(value, out value2) ? value2 : null); if (num && entry != null && (Object)(object)entry.ItemSO != (Object)null) { ItemGrid val = (((Object)(object)StaticInstance.Instance != (Object)null) ? StaticInstance.Instance.PlayerBackpackGrid : null); if (!((Object)(object)val != (Object)null) || !val.AddItem(entry.ItemSO, true, true, entry.Data)) { NetLogger.Warn("[WorldPickup] taker bag full — item '" + ((Object)entry.ItemSO).name + "' lost (key=" + key + ")"); } else if (Plugin.Cfg.LogWorldItemDropSync.Value) { NetLogger.Info("[WorldPickup] taker received '" + ((Object)entry.ItemSO).name + "' (key=" + key + ")"); } } RemoveWorldPickup(value); UnregisterPickup(value); } private static void Register(Pickup p, string owner, ushort seq, ItemDefinition item, InventoryData data, bool rotated) { string key = owner + "#" + seq; UnregisterPickup(p); Entry value = new Entry { OwnerPeerId = owner, Seq = seq, Key = key, ItemSO = item, Data = data, Rotated = rotated }; _byPickup[p] = value; _byKey[key] = p; } public static void UnregisterPickup(Pickup p) { if (!((Object)(object)p == (Object)null) && _byPickup.TryGetValue(p, out Entry value)) { _byPickup.Remove(p); if (value != null && _byKey.TryGetValue(value.Key, out Pickup value2) && (Object)(object)value2 == (Object)(object)p) { _byKey.Remove(value.Key); } } } private static void RemoveWorldPickup(Pickup p) { try { InteractionManager instance = StaticInstance.Instance; if (!((Object)(object)instance == (Object)null)) { instance.RemoveInteractable((Interactable)(object)p); instance.RemovePickup(p); } } catch (Exception ex) { NetLogger.Warn("[WorldPickup] remove-from-world failed: " + ex.Message); } } public static void Clear() { _byPickup.Clear(); _byKey.Clear(); _claimed.Clear(); _deadKeys.Clear(); _requestCooldown.Clear(); _pendingRemovals.Clear(); _seq = 0; } private static bool HasRoomFor(ItemDefinition item) { //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) try { if ((Object)(object)item == (Object)null) { return false; } ItemGrid val = (((Object)(object)StaticInstance.Instance != (Object)null) ? StaticInstance.Instance.PlayerBackpackGrid : null); if ((Object)(object)val == (Object)null) { return false; } Vector2Int possibleSpace = val.GetPossibleSpace(item); return ((Vector2Int)(ref possibleSpace)).x >= 0 && ((Vector2Int)(ref possibleSpace)).y >= 0; } catch { return false; } } private static NetWorldPickupSpawn BuildSpawnMessage(Pickup p, string owner, ushort seq) { //IL_0015: 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_007b: Expected I4, but got Unknown //IL_00ef: Unknown result type (might be due to invalid IL or missing references) NetWorldPickupSpawn netWorldPickupSpawn = new NetWorldPickupSpawn { OwnerPeerId = owner, Seq = seq, Position = p.spawnPoint, ItemId = p.ItemSO.id.value }; InventoryData inventoryData = p.inventoryData; netWorldPickupSpawn.HasData = inventoryData != null; if (inventoryData != null) { netWorldPickupSpawn.AttachmentIds = ToValues(inventoryData.attachmentIds); netWorldPickupSpawn.EnchantmentIds = ToValues(inventoryData.enchantmentIds); netWorldPickupSpawn.CaliberId = (int)inventoryData.caliberId; netWorldPickupSpawn.CurrentAmmo = inventoryData.currentAmmo; netWorldPickupSpawn.Quantity = inventoryData.quantity; netWorldPickupSpawn.Rotated = inventoryData.rotated; ItemAttributeData[] array = ((inventoryData.attributes != null) ? inventoryData.attributes.itemAttributes : null); if (array != null && array.Length != 0) { List list = new List(array.Length); List list2 = new List(array.Length); ItemAttributeData[] array2 = array; foreach (ItemAttributeData val in array2) { if (val != null && val.value != null) { list.Add((ushort)val.id); list2.Add(val.value.BaseValue); } } netWorldPickupSpawn.AttrIds = list.ToArray(); netWorldPickupSpawn.AttrValues = list2.ToArray(); } } return netWorldPickupSpawn; } private static InventoryData BuildInventoryData(NetWorldPickupSpawn m, ItemDefinition item) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //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_0071: Expected O, but got Unknown //IL_0091: 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_00c1: Expected O, but got Unknown ItemId[] array = ToItemIds(m.AttachmentIds); ItemId[] array2 = ToItemIds(m.EnchantmentIds); int num = ((m.AttrIds != null) ? m.AttrIds.Length : 0); ItemAttributeData[] array3 = (ItemAttributeData[])(object)new ItemAttributeData[num]; for (int i = 0; i < num; i++) { array3[i] = new ItemAttributeData((ItemAttributes)m.AttrIds[i], new CharacterStat(m.AttrValues[i])); } ItemAttributeCollectionData val = new ItemAttributeCollectionData { itemAttributes = array3 }; int x = ((Vector2Int)(ref item.inventorySize)).x; int y = ((Vector2Int)(ref item.inventorySize)).y; return new InventoryData(new ItemId(m.ItemId), 0, 0, m.Quantity, m.CurrentAmmo, (CaliberTypes)(byte)m.CaliberId, val, array, array2, 0, x, y, m.Rotated, false); } private static ushort[] ToValues(ItemId[] ids) { if (ids == null || ids.Length == 0) { return Array.Empty(); } ushort[] array = new ushort[ids.Length]; for (int i = 0; i < ids.Length; i++) { array[i] = ids[i].value; } return array; } private static ItemId[] ToItemIds(ushort[] values) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (values == null || values.Length == 0) { return Array.Empty(); } ItemId[] array = (ItemId[])(object)new ItemId[values.Length]; for (int i = 0; i < values.Length; i++) { array[i] = new ItemId(values[i]); } return array; } } } namespace SULFURTogether.Networking.Gameplay.Boss { internal static class ArenaDoorProbe { private static bool LogOn { get { try { return Plugin.Cfg.LogBossPreFight.Value; } catch { return false; } } } private static NetMode Mode { get { try { return NetGameplaySyncBridge.BossMode; } catch { return NetMode.Off; } } } public static void OnDoorClose(object doorBlocker) { //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) if (!LogOn || doorBlocker == null) { return; } try { string arg = "?"; Component val = (Component)((doorBlocker is Component) ? doorBlocker : null); if (val != null && (Object)(object)val != (Object)null) { Vector3 position = val.transform.position; arg = ((Vector3)(ref position)).ToString("F1"); } Plugin.Log.Info($"[ArenaDoor] DoorBlocker.CloseDoor mode={Mode} name={SafeName(doorBlocker)} pos={arg}"); } catch (Exception ex) { Plugin.Log.Warn("[ArenaDoor] door-close log failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void OnTriggerEnter(object trigger) { //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) if (!LogOn || trigger == null) { return; } try { string arg = "?"; Component val = (Component)((trigger is Component) ? trigger : null); if (val != null && (Object)(object)val != (Object)null) { Vector3 position = val.transform.position; arg = ((Vector3)(ref position)).ToString("F1"); } Plugin.Log.Info($"[ArenaDoor] DoorBlockerTrigger.Enter mode={Mode} name={SafeName(trigger)} pos={arg}"); } catch (Exception ex) { Plugin.Log.Warn("[ArenaDoor] trigger-enter log failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void OnAllDead(object allDeadTrigger, string method) { if (!LogOn || allDeadTrigger == null) { return; } try { Plugin.Log.Info($"[ArenaDoor] AllDeadTrigger.{method} mode={Mode} name={SafeName(allDeadTrigger)}"); } catch (Exception ex) { Plugin.Log.Warn("[ArenaDoor] all-dead log failed: " + ex.GetType().Name + ": " + ex.Message); } } private static string SafeName(object o) { try { Object val = (Object)((o is Object) ? o : null); return (val != null && val != (Object)null) ? val.name : o.GetType().Name; } catch { return "?"; } } } internal abstract class BossAdapterBase : IBossEncounterAdapter { private Type? _type; private bool _resolved; public abstract string AdapterName { get; } protected abstract string TypeShortName { get; } protected abstract string[] TypeFullNames { get; } public virtual string[] StartChainMethods => Array.Empty(); public virtual bool ProvidesPhaseState => false; public virtual bool IsEyeBoss => false; public virtual string[] DiscreteEventMethods => Array.Empty(); public Type? ResolveType() { if (!_resolved) { _resolved = true; _type = BossReflect.FindType(TypeShortName, TypeFullNames); } return _type; } public virtual bool CanHandle(object component) { if (component == null) { return false; } Type type = ResolveType(); if (type != null) { return type.IsInstanceOfType(component); } return false; } public NetBossEncounterId BuildEncounterId(object component, in BossEncounterContext ctx) { return new NetBossEncounterId { RunKey = ctx.RunKey, ChapterName = ctx.ChapterName, LevelIndex = ctx.LevelIndex, HasSeed = ctx.HasSeed, Seed = ctx.Seed, GraphName = ctx.GraphName, BossType = component.GetType().Name, RootName = BossReflect.RootName(component), RootPath = BossReflect.GameObjectPath(component), InstanceId = BossReflect.InstanceId(component) }; } public abstract bool IsStarted(object component); public bool IsContinuationSource(string source) { string text = ParseSourceMethod(source); if (string.IsNullOrEmpty(text)) { return false; } string[] startChainMethods = StartChainMethods; for (int i = 0; i < startChainMethods.Length; i++) { if (string.Equals(startChainMethods[i], text, StringComparison.Ordinal)) { return true; } } return false; } public virtual bool TryApplyHostStart(object component, NetBossEncounterState state, out string detail) { if (StartChainMethods.Length == 0) { detail = AdapterName + ": no start chain (diagnostic-only adapter)"; return false; } if (IsStarted(component)) { detail = "already-started"; return true; } string text = ResolveApplyMethod(component, state?.StartSource ?? ""); if (string.IsNullOrEmpty(text)) { detail = "no invokable start-chain method on component"; return false; } return BossReflect.TryInvoke(component, text, out detail); } protected string ResolveApplyMethod(object component, string source) { string text = ParseSourceMethod(source); if (!string.IsNullOrEmpty(text) && IsContinuationSource(source) && BossReflect.HasMethod(component, text)) { return text; } string[] startChainMethods = StartChainMethods; foreach (string text2 in startChainMethods) { if (BossReflect.HasMethod(component, text2)) { return text2; } } if (StartChainMethods.Length == 0) { return ""; } return StartChainMethods[0]; } protected static string ParseSourceMethod(string? source) { if (string.IsNullOrEmpty(source)) { return ""; } string text = source; int num = text.IndexOf(':'); if (num >= 0 && num < text.Length - 1) { text = text.Substring(num + 1); } int num2 = text.LastIndexOf('.'); if (num2 < 0 || num2 >= text.Length - 1) { return text; } return text.Substring(num2 + 1); } public virtual void TryReadState(object component, out bool hasPhase, out int phaseIndex, out bool hasPos, out Vector3 pos) { hasPhase = BossReflect.TryGetInt(component, "currentPhaseIndex", out phaseIndex); hasPos = BossReflect.TryGetPosition(component, out pos); } public virtual string DescribeForLog(object component) { bool flag = IsStarted(component); TryReadState(component, out var hasPhase, out var phaseIndex, out var _, out var _); return string.Format("adapter={0} type={1} root={2} path={3} started={4} phase={5}", AdapterName, component.GetType().Name, BossReflect.RootName(component), BossReflect.GameObjectPath(component), flag, hasPhase ? phaseIndex.ToString() : "?"); } public virtual bool IsDialogBoss(object component) { return false; } public virtual bool IsDialogCommitSource(string source) { return false; } public virtual bool GatesFightOnDialogClose(object component) { return false; } public virtual bool DefersIntroArmUntilCommit(object component) { return false; } public virtual bool TryReplayIntroArm(object component, out string detail) { detail = "no deferred intro arm for this adapter"; return false; } public virtual bool IsRoomEntrySource(string source) { return false; } public virtual bool ShouldSuppressDuplicateDialogEntry(object component, string source) { return false; } public virtual bool TryApplyDialogCommit(object component, NetBossDialogCommit commit, out string detail) { detail = "dialog commit not supported by this adapter"; return false; } public virtual object? ResolveDialogNpc(object component) { return GetHealthUnit(component); } public virtual bool TryRemoveDialogInteractable(object component, out string detail) { detail = ""; try { object? obj = ResolveDialogNpc(component); Object val = (Object)((obj is Object) ? obj : null); if (val == (Object)null) { detail = "no boss unit to match"; return false; } bool flag = false; PropertyInfo property = ((object)val).GetType().GetProperty("dialog", BindingFlags.Instance | BindingFlags.Public); if (property != null && property.CanWrite && property.GetValue(val) != null) { property.SetValue(val, null); flag = true; } UnitInteractable[] array = Object.FindObjectsOfType(); InteractionManager instance = StaticInstance.Instance; int num = 0; int num2 = 0; UnitInteractable[] array2 = array; foreach (UnitInteractable val2 in array2) { if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.npc == (Object)null) && !((Object)(object)val2.npc != val)) { if ((Object)(object)instance != (Object)null) { instance.RemoveInteractable((Interactable)(object)val2); num++; } if ((Object)(object)((Component)val2).gameObject != (Object)null) { ((Component)val2).gameObject.SetActive(false); num2++; } } } detail = $"boss={val.name} dialogCleared={flag} interactableRemoved={num} disabled={num2} scannedUI={array.Length}"; return flag || num > 0 || num2 > 0; } catch (Exception ex) { detail = "ex " + ex.GetType().Name + ": " + ex.Message; return false; } } public virtual object? GetHealthUnit(object component) { return null; } public virtual bool TryGetSandstormArenaSphere(object component, out Vector3 center, out float radius) { //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) center = Vector3.zero; radius = 0f; return false; } public virtual bool TrySetArenaCenter(object component, Vector3 center) { return false; } public virtual bool RunsLocalIntroPresentation(object component) { return false; } public virtual bool IsCombatEntrySource(object component, string source) { return false; } public virtual bool TryApplyCombatEntry(object component, out string detail) { detail = "combat-entry not supported by this adapter"; return false; } public virtual bool TryGetActiveMidFightDialogId(object component, out string dialogId) { dialogId = ""; return false; } public virtual bool BroadcastsBossOwnDialog(object component) { return false; } public virtual bool TryOpenBossOwnDialog(object component, out string detail) { detail = "not-supported"; return false; } public virtual bool ClientBossIsPurePuppet(object component) { return false; } public virtual int SuppressClientMechanics(object component, out string detail) { detail = "not-a-pure-puppet-boss"; return 0; } public virtual void FillBossState(object component, NetBossState state) { FillHealth(component, state); try { state.FightStarted = IsStarted(component); } catch { } } public virtual bool TryApplyBossState(object component, NetBossState state, out string detail) { detail = ApplyHealth(component, state); return true; } protected void FillHealth(object component, NetBossState state) { object healthUnit = GetHealthUnit(component); if (healthUnit != null && NetGameplayProbeManager.TryReadBossUnitHealth(healthUnit, out var currentHp, out var maxHp)) { state.HasHealth = true; state.CurrentHealth = currentHp; state.MaxHealth = maxHp; } } protected string ApplyHealth(object component, NetBossState state) { object healthUnit = GetHealthUnit(component); if (healthUnit == null) { return "no health unit"; } if (!state.HasHealth) { return "host sent no health"; } bool flag = NetGameplayProbeManager.TryWriteBossUnitHealth(healthUnit, state.CurrentHealth); float num = ((state.MaxHealth > 0.0001f) ? Mathf.Clamp01(state.CurrentHealth / state.MaxHealth) : 0f); bool flag2 = BossReflect.TryFireHealthChange(healthUnit, num); return string.Format("health {0} {1:0}/{2:0} (norm={3:0.00}) barEvent={4}", flag ? "written" : "write-failed", state.CurrentHealth, state.MaxHealth, num, flag2); } public bool TryAttachBossBar(object component) { object healthUnit = GetHealthUnit(component); if (healthUnit == null) { return false; } string detail; return BossReflect.TryInvokeBool(healthUnit, "AttachToBossUI", arg: true, out detail); } public virtual string? ResolveHitTargetRole(object component, object hitUnit) { if (hitUnit == null) { return null; } object healthUnit = GetHealthUnit(component); if (healthUnit == null || healthUnit != hitUnit) { return null; } return "main"; } public virtual object? ResolveHostTargetForRole(object component, string role) { if (!(role == "main")) { return null; } return GetHealthUnit(component); } public virtual bool TryApplyHostBossHit(object component, string role, object target, float damage, int damageTypeInt, object? source, out bool vanillaResult, out string detail) { return BossDamageReflect.TryApplyRealDamage(target, damage, damageTypeInt, source, out vanillaResult, out detail); } public virtual void OnClientPresentationStart(object component) { } public virtual bool TryReadEyePhase(object component, out int cycle, out int livingEyes) { cycle = 0; livingEyes = 0; return false; } public virtual bool TryConsumeOneEye(object component, out int remaining, out string detail) { remaining = 0; detail = "not an eye boss"; return false; } public virtual bool TryRemoveDeadEyeFromList(object component, object eyeUnit) { return false; } public virtual bool TryApplyEyePhaseComplete(object component, out int cleared, out string detail) { cleared = 0; detail = "not an eye boss"; return false; } public virtual bool TryReadEyeCompletionDiag(object component, out int phase, out int restartCounter, out bool invulnerable, out Vector3 pos) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) phase = -1; restartCounter = -1; invulnerable = false; pos = Vector3.zero; return false; } public virtual bool TryApplyLuciaDeath(object component, out string detail) { detail = "not a Lucia boss"; return false; } public virtual bool BuildDiscreteEvent(object component, string eventName, out bool hasPos, out Vector3 pos, out string diag) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) hasPos = false; pos = Vector3.zero; diag = ""; return false; } public virtual bool TryApplyDiscreteEvent(object component, string eventName, bool hasPos, Vector3 pos, out string detail) { detail = "discrete events not supported"; return false; } public virtual bool IsTerminalEvent(string eventName) { return false; } NetBossEncounterId IBossEncounterAdapter.BuildEncounterId(object component, in BossEncounterContext ctx) { return BuildEncounterId(component, in ctx); } } internal static class BossDamageReflect { private static readonly object _resolveLock = new object(); private static bool _resolved; private static MethodInfo? _receiveDamage; private static Type? _damageTypesEnum; private static Type? _damagerInterface; private static object? _hitmeshDefault; private static PropertyInfo? _gmInstanceProp; private static PropertyInfo? _gmPlayerUnitProp; private static bool _gmResolved; public static string DamagerInterfaceName => _damagerInterface?.Name ?? ""; public static string ReceiveDamageSignature { get { if (!(_receiveDamage == null)) { return _receiveDamage.DeclaringType?.Name + ".ReceiveDamage(" + string.Join(",", from p in _receiveDamage.GetParameters() select p.ParameterType.Name) + ")"; } return ""; } } public static object? ResolveHostPlayerUnit() { try { if (!_gmResolved) { _gmResolved = true; Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); if (type != null) { _gmInstanceProp = AccessTools.Property(type, "Instance"); _gmPlayerUnitProp = AccessTools.Property(type, "PlayerUnit"); } } object obj = _gmInstanceProp?.GetValue(null, null); if (obj == null) { return null; } return _gmPlayerUnitProp?.GetValue(obj, null); } catch { return null; } } public static bool IsValidDamageSource(object? o) { if (o != null && _damagerInterface != null) { return _damagerInterface.IsInstanceOfType(o); } return false; } private static void ResolveFor(object unit) { if (_resolved) { return; } lock (_resolveLock) { if (_resolved) { return; } try { MethodInfo methodInfo = null; Type type = unit.GetType(); while (type != null && methodInfo == null) { MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods) { if (!(methodInfo2.Name != "ReceiveDamage")) { ParameterInfo[] parameters = methodInfo2.GetParameters(); if (parameters.Length == 5 && !(parameters[0].ParameterType != typeof(float)) && parameters[1].ParameterType.IsEnum && parameters[2].ParameterType.IsInterface) { methodInfo = methodInfo2; break; } } } type = type.BaseType; } if (methodInfo != null) { ParameterInfo[] parameters2 = methodInfo.GetParameters(); _receiveDamage = methodInfo; _damageTypesEnum = parameters2[1].ParameterType; _damagerInterface = parameters2[2].ParameterType; Type parameterType = parameters2[3].ParameterType; _hitmeshDefault = parameterType.GetField("Default", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); Plugin.Log.Info("[BossDamage] resolved real ReceiveDamage: " + methodInfo.DeclaringType?.Name + ".ReceiveDamage(float," + _damageTypesEnum.Name + "," + _damagerInterface.Name + "," + parameterType.Name + ",Vector3?) hitmeshDefault=" + ((_hitmeshDefault != null) ? "ok" : "null")); } else { Plugin.Log.Warn("[BossDamage] could not resolve the IDamager ReceiveDamage overload."); } } catch (Exception ex) { Plugin.Log.Warn("[BossDamage] resolve failed: " + ex.GetType().Name + ": " + ex.Message); } _resolved = true; } } public static bool TryApplyRealDamage(object targetUnit, float damage, int damageTypeInt, object? sourcePlayerUnit, out bool vanillaResult, out string detail) { vanillaResult = false; detail = ""; if (targetUnit == null) { detail = "null-target"; return false; } ResolveFor(targetUnit); if (_receiveDamage == null || _damageTypesEnum == null) { detail = "receiveDamage-unresolved"; return false; } if (_hitmeshDefault == null) { detail = "hitmesh-default-unresolved"; return false; } if (sourcePlayerUnit == null || !_damagerInterface.IsInstanceOfType(sourcePlayerUnit)) { detail = "source-not-IDamager (" + ((sourcePlayerUnit == null) ? "null" : sourcePlayerUnit.GetType().Name) + ")"; return false; } try { object obj = Enum.ToObject(_damageTypesEnum, damageTypeInt); object obj2 = _receiveDamage.Invoke(targetUnit, new object[5] { damage, obj, sourcePlayerUnit, _hitmeshDefault, null }); bool flag = default(bool); int num; if (obj2 is bool) { flag = (bool)obj2; num = 1; } else { num = 0; } vanillaResult = (byte)((uint)num & (flag ? 1u : 0u)) != 0; detail = $"ReceiveDamage returned {vanillaResult}"; return true; } catch (TargetInvocationException ex) { detail = "ReceiveDamage threw " + (ex.InnerException?.GetType().Name ?? ex.GetType().Name) + ": " + (ex.InnerException?.Message ?? ex.Message); return false; } catch (Exception ex2) { detail = "invoke failed: " + ex2.GetType().Name + ": " + ex2.Message; return false; } } } internal static class BossDialogFlowProbe { private static float _dlgInputLogNext; private static bool LogOn { get { try { return Plugin.Cfg.LogBossPreFight.Value; } catch { return false; } } } private static bool EmpOn { get { try { return NetEmperorWormSync.IsWormActive; } catch { return false; } } } private static NetMode Mode { get { try { return NetGameplaySyncBridge.BossMode; } catch { return NetMode.Off; } } } public static void OnNpcInteract(object npc, bool ran) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Invalid comparison between Unknown and I4 if ((!LogOn && !EmpOn) || !ran || npc == null) { return; } try { Npc val = (Npc)((npc is Npc) ? npc : null); if (val == null) { return; } bool flag = false; try { flag = (int)val.UnitType == 8; } catch { } bool flag2 = false; try { flag2 = val.HasDialog; } catch { } if (EmpOn) { string text = "?"; try { text = ((object)val.UnitType/*cast due to .constrained prefix*/).ToString(); } catch { } Plugin.Log.Info($"[EmperorDialog] Npc.Interact mode={Mode} npc={SafeName(npc)} unitType={text} hasDialog={flag2}"); } if (LogOn && flag) { Plugin.Log.Info($"[DialogFlow] Npc.Interact mode={Mode} npc={SafeName(npc)} boss=True hasDialog={flag2}"); } } catch (Exception ex) { Plugin.Log.Warn("[DialogFlow] Npc.Interact log failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void OnSetCurrentSpeakable(object speakable) { if (!LogOn && !EmpOn) { return; } try { string arg = ((speakable == null) ? "NULL(close)" : SafeName(speakable)); if (LogOn) { Plugin.Log.Info($"[DialogFlow] SetCurrentSpeakable mode={Mode} -> {arg}"); } if (!EmpOn) { return; } if (speakable == null) { Plugin.Log.Info($"[EmperorDialog] SetCurrentSpeakable mode={Mode} -> NULL(close)"); return; } string text = (BossReflect.GetMember(speakable, "ActorName") as string) ?? "?"; object member = BossReflect.GetMember(speakable, "unit"); Object val = (Object)((member is Object) ? member : null); string text2 = ((val != null && val != (Object)null) ? val.name : (member?.GetType().Name ?? "null")); bool flag = false; try { Npc val2 = (Npc)((member is Npc) ? member : null); if (val2 != null) { flag = val2.HasDialog; } } catch { } Plugin.Log.Info($"[EmperorDialog] SetCurrentSpeakable mode={Mode} actor=\"{text}\" speakerType={speakable.GetType().Name} unit={text2} unitHasDialog={flag}"); } catch (Exception ex) { Plugin.Log.Warn("[DialogFlow] SetCurrentSpeakable log failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void OnPlayerTrigger(object pt) { //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) if ((!LogOn && !EmpOn) || pt == null) { return; } try { Component val = (Component)((pt is Component) ? pt : null); object obj; if (val == null || !((Object)(object)val != (Object)null)) { obj = "?"; } else { Vector3 position = val.transform.position; obj = ((Vector3)(ref position)).ToString("F1"); } string text = (string)obj; string text2 = ""; FieldInfo field = pt.GetType().GetField("eventReference", BindingFlags.Instance | BindingFlags.NonPublic); if (field != null) { text2 = field.GetValue(pt)?.ToString() ?? ""; } string text3 = ReadTriggeredObjects(pt); string text4 = (EmpOn ? "[EmperorDialog]" : "[DialogFlow]"); if (LogOn || EmpOn) { Plugin.Log.Info($"{text4} PlayerTrigger.Trigger mode={Mode} name={SafeName(pt)} pos={text} event=\"{text2}\" fires=[{text3}] persistent=[{ReadPersistentTargets(pt)}]"); } } catch (Exception ex) { Plugin.Log.Warn("[DialogFlow] PlayerTrigger log failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void OnAcceptDialogOption(object dc) { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) if (!LogOn || dc == null) { return; } try { object field = GetField(dc, "InChoices"); bool flag = default(bool); int num; if (field is bool) { flag = (bool)field; num = 1; } else { num = 0; } bool flag2 = (byte)((uint)num & (flag ? 1u : 0u)) != 0; object field2 = GetField(dc, "selectedButton"); int num2; if (field2 != null) { Object val = (Object)((field2 is Object) ? field2 : null); num2 = ((val != null && val == (Object)null) ? 1 : 0); } else { num2 = 1; } bool flag3 = (byte)num2 != 0; object? field3 = GetField(dc, "playerDialogCanvasGroup"); CanvasGroup val2 = (CanvasGroup)((field3 is CanvasGroup) ? field3 : null); string text = (((Object)(object)val2 == (Object)null) ? "cg=null" : $"interactable={val2.interactable} blocksRaycasts={val2.blocksRaycasts} alpha={val2.alpha:F1}"); string text2 = $"lock={Cursor.lockState} vis={Cursor.visible}"; string text3 = DescribeCursorRaycast(); Plugin.Log.Info(string.Format("[DialogInput] AcceptDialogOption mode={0} InChoices={1} selectedButton={2} {3} {4} rayTop=[{5}]", Mode, flag2, flag3 ? "NULL" : "set", text, text2, text3)); } catch (Exception ex) { Plugin.Log.Warn("[DialogInput] AcceptDialogOption log failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void OnMultipleChoiceRequest(object dc) { if (!LogOn || dc == null) { return; } try { object? field = GetField(dc, "playerDialogCanvasGroup"); CanvasGroup val = (CanvasGroup)((field is CanvasGroup) ? field : null); if ((Object)(object)val == (Object)null) { object? field2 = GetField(dc, "playerDialog"); Component val2 = (Component)((field2 is Component) ? field2 : null); if (val2 != null && (Object)(object)val2 != (Object)null) { val = val2.GetComponent(); } } string arg = (((Object)(object)val == (Object)null) ? "cg=null" : $"interactable={val.interactable} blocksRaycasts={val.blocksRaycasts} alpha={val.alpha:F1}"); Plugin.Log.Info($"[DialogInput] MultipleChoiceRequest mode={Mode} {arg}"); } catch (Exception ex) { Plugin.Log.Warn("[DialogInput] MultipleChoiceRequest log failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void OnDialogueStarted(object dc) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) if (!LogOn || dc == null) { return; } try { Plugin.Log.Info($"[DialogInput] OnDialogueStarted mode={Mode} cursor(lock={Cursor.lockState} vis={Cursor.visible}) timeScale={Time.timeScale:F2}"); } catch { } } public static void OnDialogueFinished(object dc) { if (!LogOn || dc == null) { return; } try { Plugin.Log.Info($"[DialogInput] OnDialogueFinished mode={Mode}"); } catch { } } public static void OnDialogUpdate(object dc) { //IL_00f1: Unknown result type (might be due to invalid IL or missing references) if (!LogOn || dc == null) { return; } try { object field = GetField(dc, "currentDialogueTree"); if (field == null) { return; } Object val = (Object)((field is Object) ? field : null); if (val != null && val == (Object)null) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup < _dlgInputLogNext)) { _dlgInputLogNext = realtimeSinceStartup + 0.5f; object field2 = GetField(dc, "InChoices"); bool flag = default(bool); int num; if (field2 is bool) { flag = (bool)field2; num = 1; } else { num = 0; } bool flag2 = (byte)((uint)num & (flag ? 1u : 0u)) != 0; object? field3 = GetField(dc, "playerDialogCanvasGroup"); CanvasGroup val2 = (CanvasGroup)((field3 is CanvasGroup) ? field3 : null); string text = (((Object)(object)val2 == (Object)null) ? "cg=null" : $"blocksRaycasts={val2.blocksRaycasts} interactable={val2.interactable}"); Plugin.Log.Info($"[DialogInput] dialogActive mode={Mode} InChoices={flag2} cursor(lock={Cursor.lockState} vis={Cursor.visible}) timeScale={Time.timeScale:F2} {text} rayTop=[{DescribeCursorRaycast()}]"); } } catch { } } private static string DescribeCursorRaycast() { //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_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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_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_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) try { EventSystem current = EventSystem.current; if ((Object)(object)current == (Object)null) { return "no-EventSystem"; } Vector2 position; try { position = ((Mouse.current != null) ? ((InputControl)(object)((Pointer)Mouse.current).position).ReadValue() : Vector2.op_Implicit(Input.mousePosition)); } catch { position = Vector2.op_Implicit(Input.mousePosition); } PointerEventData val = new PointerEventData(current) { position = position }; List list = new List(); current.RaycastAll(val, list); if (list.Count == 0) { return "NONE"; } List list2 = new List(); for (int i = 0; i < list.Count && i < 4; i++) { RaycastResult val2 = list[i]; object item; if (!((Object)(object)((RaycastResult)(ref val2)).gameObject != (Object)null)) { item = "?"; } else { val2 = list[i]; item = ((Object)((RaycastResult)(ref val2)).gameObject).name; } list2.Add((string)item); } return string.Join(" > ", list2); } catch (Exception ex) { return "err:" + ex.GetType().Name; } } private static object? GetField(object obj, string name) { try { Type type = obj.GetType(); while (type != null) { FieldInfo field = type.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(obj); } type = type.BaseType; } } catch { } return null; } private static string ReadPersistentTargets(object pt) { try { object? obj = pt.GetType().GetField("onTriggerEvents", BindingFlags.Instance | BindingFlags.Public)?.GetValue(pt); UnityEventBase val = (UnityEventBase)((obj is UnityEventBase) ? obj : null); if (val == null) { return "?"; } int persistentEventCount = val.GetPersistentEventCount(); if (persistentEventCount == 0) { return ""; } List list = new List(); for (int i = 0; i < persistentEventCount; i++) { Object persistentTarget = val.GetPersistentTarget(i); list.Add(((persistentTarget != (Object)null) ? ((object)persistentTarget).GetType().Name : "null") + "." + val.GetPersistentMethodName(i)); } return string.Join(",", list); } catch { return "?"; } } private static string ReadTriggeredObjects(object pt) { try { FieldInfo field = pt.GetType().GetField("objectsToTrigger", BindingFlags.Instance | BindingFlags.NonPublic); if (field == null) { return "?"; } if (!(field.GetValue(pt) is IEnumerable enumerable)) { return "?"; } List list = new List(); foreach (object item in enumerable) { list.Add(SafeName(item)); } return (list.Count == 0) ? "" : string.Join(",", list); } catch { return "?"; } } private static string SafeName(object o) { try { Object val = (Object)((o is Object) ? o : null); return (val != null && val != (Object)null) ? val.name : (o?.GetType().Name ?? "?"); } catch { return "?"; } } } internal static class BossDialogReflect { private static bool _resolved; private static Type? _dialogueTreeType; private static PropertyInfo? _currentDialogueProp; private static MethodInfo? _stopMethod; private static Type? _dialogControllerType; private static FieldInfo? _instancesField; private static PropertyInfo? _currentSpeakableProp; private static FieldInfo? _inChoicesField; private static FieldInfo? _selectedButtonField; private static FieldInfo? _playerDialogButtonsField; private static MethodInfo? _acceptMethod; private static void Resolve() { if (_resolved) { return; } _resolved = true; try { _dialogueTreeType = AccessTools.TypeByName("NodeCanvas.DialogueTrees.DialogueTree"); if (_dialogueTreeType != null) { _currentDialogueProp = _dialogueTreeType.GetProperty("currentDialogue", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _stopMethod = AccessTools.Method(_dialogueTreeType, "Stop", new Type[1] { typeof(bool) }, (Type[])null); } _dialogControllerType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.DialogController"); if (_dialogControllerType != null) { _instancesField = _dialogControllerType.GetField("instances", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _currentSpeakableProp = _dialogControllerType.GetProperty("CurrentSpeakable", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _inChoicesField = AccessTools.Field(_dialogControllerType, "InChoices"); _selectedButtonField = AccessTools.Field(_dialogControllerType, "selectedButton"); _acceptMethod = AccessTools.Method(_dialogControllerType, "AcceptDialogOption", (Type[])null, (Type[])null); _playerDialogButtonsField = AccessTools.Field(_dialogControllerType, "playerDialogButtons"); Plugin.Log.Info($"[BossDialogCommit] dialog choice fields: InChoices={_inChoicesField != null} selectedButton={_selectedButtonField != null}"); } } catch (Exception ex) { Plugin.Log.Warn("[BossDialogCommit] dialog API resolve failed: " + ex.GetType().Name + ": " + ex.Message); } } public static bool IsDialogActive() { Resolve(); try { object? obj = _currentDialogueProp?.GetValue(null, null); Object val = (Object)((obj is Object) ? obj : null); return val != null && val != (Object)null; } catch { return false; } } public static string CurrentSpeakableName() { Resolve(); try { if (_instancesField?.GetValue(null) is IEnumerable enumerable) { foreach (object item in enumerable) { object obj = _currentSpeakableProp?.GetValue(item, null); if (obj != null) { string text = BossReflect.GetMember(obj, "ActorName") as string; if (!string.IsNullOrEmpty(text)) { return text; } object member = BossReflect.GetMember(obj, "unit"); if (member != null) { return member.GetType().Name; } return obj.GetType().Name; } } } } catch { } return "unavailable"; } public static bool TryFinalizeCurrentDialog(out string detail) { Resolve(); try { if (_currentDialogueProp == null || _stopMethod == null) { detail = "dialog API unavailable"; return false; } object value = _currentDialogueProp.GetValue(null, null); Object val = (Object)((value is Object) ? value : null); if (val != null && val != (Object)null) { string text = CurrentSpeakableName(); _stopMethod.Invoke(value, new object[1] { true }); ResetDialogChoiceState(); detail = "stopped dialog (speaker=" + text + ")"; return true; } detail = "no active dialog"; return false; } catch (Exception ex) { detail = "finalize failed: " + ex.GetType().Name + ": " + (ex.InnerException?.Message ?? ex.Message); return false; } } public static bool TryAdvanceActiveDialog() { Resolve(); try { if (_acceptMethod == null) { return false; } foreach (object item in EnumerateControllers()) { if (_currentSpeakableProp?.GetValue(item, null) != null) { _acceptMethod.Invoke(item, null); return true; } } } catch (Exception ex) { Plugin.Log.Warn("[BossDialogCommit] TryAdvanceActiveDialog failed: " + ex.GetType().Name + ": " + (ex.InnerException?.Message ?? ex.Message)); } return false; } public static bool IsActiveDialogInChoices() { Resolve(); try { bool flag = default(bool); foreach (object item in EnumerateControllers()) { if (_currentSpeakableProp?.GetValue(item, null) == null) { continue; } int num; if (_inChoicesField != null) { object value = _inChoicesField.GetValue(item); if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } } catch { } return false; } public static bool TrySelectActiveDialogOption(int index) { Resolve(); try { foreach (object item in EnumerateControllers()) { if (_currentSpeakableProp?.GetValue(item, null) != null) { if (!(_playerDialogButtonsField?.GetValue(item) is IList { Count: not 0 } list)) { return false; } int index2 = ((index >= 0) ? ((index >= list.Count) ? (list.Count - 1) : index) : 0); object obj = list[index2]; if (obj == null) { return false; } object obj2 = obj.GetType().GetProperty("onClick")?.GetValue(obj); MethodInfo methodInfo = obj2?.GetType().GetMethod("Invoke", Type.EmptyTypes); if (methodInfo == null) { return false; } methodInfo.Invoke(obj2, null); return true; } } } catch (Exception ex) { Plugin.Log.Warn("[BossDialogCommit] TrySelectActiveDialogOption failed: " + ex.GetType().Name + ": " + (ex.InnerException?.Message ?? ex.Message)); } return false; } private static IEnumerable EnumerateControllers() { HashSet seen = new HashSet(); List list = new List(); try { if (_instancesField?.GetValue(null) is IEnumerable enumerable) { foreach (object item in enumerable) { if (item != null) { list.Add(item); } } } } catch { } try { if (_dialogControllerType != null) { Object[] array = Resources.FindObjectsOfTypeAll(_dialogControllerType); foreach (Object val in array) { if (val != (Object)null) { list.Add(val); } } } } catch { } foreach (object item2 in list) { if (seen.Add(item2)) { yield return item2; } } } private static void ResetDialogChoiceState() { try { int num = 0; int num2 = 0; foreach (object item in EnumerateControllers()) { num2++; FieldInfo fieldInfo = FindInstanceField(item, "InChoices"); FieldInfo fieldInfo2 = FindInstanceField(item, "selectedButton"); string text = ((!(fieldInfo != null)) ? "no-field" : (fieldInfo.GetValue(item)?.ToString() ?? "?")); try { fieldInfo?.SetValue(item, false); } catch { } try { fieldInfo2?.SetValue(item, null); } catch { } string text2 = ((!(fieldInfo != null)) ? "no-field" : (fieldInfo.GetValue(item)?.ToString() ?? "?")); if (text != text2) { num++; } Plugin.Log.Info("[BossDialogCommit] reset choice state on " + item.GetType().Name + " InChoices " + text + "->" + text2); } Plugin.Log.Info($"[BossDialogCommit] ResetDialogChoiceState cleared={num}/{num2}"); } catch (Exception ex) { Plugin.Log.Warn("[BossDialogCommit] ResetDialogChoiceState failed: " + ex.GetType().Name + ": " + ex.Message); } } private static FieldInfo? FindInstanceField(object obj, string name) { Type type = obj.GetType(); while (type != null) { FieldInfo field = type.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field; } type = type.BaseType; } return null; } } internal static class BossDynamicSpawnManifest { private sealed class Pending { public object UnitSO; public object Owner; public float At; } private sealed class LocalAdd { public int Seq; public object Unit; public string UnitId = ""; public int InstanceId; public Vector3 Pos; public int BoundHostSeq = -1; } private sealed class HostEntry { public NetBossDynamicSpawn Msg; public bool BoundLocal; public int BoundLocalInstanceId; public float ReceivedAt; public bool HostOnlyResolved; } private sealed class GatedAdd { public object Unit; public float At; public bool Frozen; } private static readonly object _lock = new object(); private static readonly List _pending = new List(); private const float PendingTtlSeconds = 8f; private static readonly Dictionary> _localBySlot = new Dictionary>(); private static readonly Dictionary _localSeqCounter = new Dictionary(); private static readonly Dictionary _hostSeqCounter = new Dictionary(); private static readonly Dictionary _hostEntries = new Dictionary(); private static int _revision; public static int SpawnsHostBroadcast; public static int SpawnsClientLocal; public static int SpawnsBound; public static int SpawnsHostOnlyNoLocal; public static int SpawnsAttributed; private static readonly Dictionary _gatedAdds = new Dictionary(); private const float GatedInertTtlSeconds = 4f; private const float HostOnlyGraceSeconds = 2f; public static int SpawnsGatedInert; public static int SpawnsSnappedOnBind; public static int HitsGateSwallowed; private static readonly HashSet _specialAdds = new HashSet { "BlackGuildLuciaEye" }; private static bool Enabled { get { try { return Plugin.Cfg.EnableBossDynamicSpawnManifest.Value; } catch { return false; } } } private static bool LogOn { get { try { return Plugin.Cfg.LogBossDynamicSpawn.Value; } catch { return false; } } } private static string Slot(string key, string addType) { return key + "|" + addType; } public static void Reset() { lock (_lock) { _pending.Clear(); _localBySlot.Clear(); _localSeqCounter.Clear(); _hostSeqCounter.Clear(); _hostEntries.Clear(); foreach (GatedAdd value in _gatedAdds.Values) { if (value.Frozen) { TryUnfreezeMovement(value.Unit); } } _gatedAdds.Clear(); } } public static void NotePendingSpawn(object unitSO, object owner) { try { if (!Enabled || unitSO == null || owner == null || (!NetBossEncounterManager.TryGetEncounterKeyForBoss(owner, out string key, out string bossType) && !NetBossEncounterManager.TryGetEncounterKeyForDesertPerimeter(owner, out bossType, out key))) { return; } lock (_lock) { float now = Time.realtimeSinceStartup; _pending.RemoveAll((Pending p) => now - p.At > 8f); _pending.Add(new Pending { UnitSO = unitSO, Owner = owner, At = now }); } } catch { } } public static void OnUnitSpawned(object unitSO, object spawnedUnit, Vector3 position) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) try { if (!Enabled || unitSO == null || spawnedUnit == null) { return; } object obj = null; lock (_lock) { for (int i = 0; i < _pending.Count; i++) { if (_pending[i].UnitSO == unitSO) { obj = _pending[i].Owner; _pending.RemoveAt(i); break; } } } if (obj != null) { RecordSpawn(obj, spawnedUnit, position); } } catch (Exception ex) { Plugin.Log.Warn("[BossSpawn] OnUnitSpawned failed: " + ex.GetType().Name + ": " + ex.Message); } } private static void RecordSpawn(object owner, object spawnedUnit, Vector3 position) { //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0279: Unknown result type (might be due to invalid IL or missing references) if (!NetBossEncounterManager.TryGetEncounterKeyForBoss(owner, out string key, out string bossType) && !NetBossEncounterManager.TryGetEncounterKeyForDesertPerimeter(owner, out key, out bossType)) { return; } string text = BossReflect.ReadUnitId(spawnedUnit); string addType = ((!string.IsNullOrEmpty(text)) ? text : spawnedUnit.GetType().Name); int num = BossReflect.InstanceId(spawnedUnit); string text2 = Slot(key, addType); SpawnsAttributed++; NetMode bossMode = NetGameplaySyncBridge.BossMode; int num2; lock (_lock) { _localSeqCounter.TryGetValue(text2, out var value); num2 = value; _localSeqCounter[text2] = value + 1; if (!_localBySlot.TryGetValue(text2, out List value2)) { value2 = new List(); _localBySlot[text2] = value2; } value2.Add(new LocalAdd { Seq = num2, Unit = spawnedUnit, UnitId = text, InstanceId = num, Pos = position }); } switch (bossMode) { case NetMode.Host: { int sequenceIndex; lock (_lock) { _hostSeqCounter.TryGetValue(text2, out var value3); sequenceIndex = value3; _hostSeqCounter[text2] = value3 + 1; } if (!NetBossEncounterManager.TryGetRunContext(out string chapter, out int level, out bool hasSeed, out int seed)) { chapter = ""; level = -1; } NetBossDynamicSpawn netBossDynamicSpawn = new NetBossDynamicSpawn { EncounterKey = key, OwnerBossType = bossType, AddType = addType, AddUnitId = text, SequenceIndex = sequenceIndex, Position = position, HostInstanceId = num, ChapterName = chapter, LevelIndex = level, HasSeed = hasSeed, Seed = seed, Revision = ++_revision, Timestamp = Time.realtimeSinceStartup, UnitIdValue = ReadUnitIdValueFromUnit(spawnedUnit), HostSpawnIndex = NetGameplayProbeManager.GetSpawnIndexForObject(spawnedUnit) }; SpawnsHostBroadcast++; if (LogOn) { Plugin.Log.Info("[BossSpawn] host add spawned + broadcasting " + netBossDynamicSpawn.ToCompact()); } NetGameplaySyncBridge.BroadcastHostBossDynamicSpawn(netBossDynamicSpawn); break; } case NetMode.Client: SpawnsClientLocal++; if (LogOn) { Plugin.Log.Info(string.Format("[BossSpawn] client local add spawned slot={0} localSeq={1} unitId={2} inst={3} pos={4:F1}", text2, num2, string.IsNullOrEmpty(text) ? "?" : text, num, position)); } if (RuntimeSyncEnabled() && !IsSpecialAdd(addType)) { BeginGatedInert(spawnedUnit, num); } TryBindLocal(text2, num2); break; } } public static void HandleHostBossDynamicSpawn(NetBossDynamicSpawn msg) { try { if (!Enabled || msg == null || NetGameplaySyncBridge.BossMode != NetMode.Client) { return; } string text = Slot(msg.EncounterKey, msg.AddType); string key = text + "|" + msg.SequenceIndex; lock (_lock) { if (!_hostEntries.ContainsKey(key)) { _hostEntries[key] = new HostEntry { Msg = msg, ReceivedAt = Time.realtimeSinceStartup }; } } if (LogOn) { Plugin.Log.Info("[BossSpawn] client received host add " + msg.ToCompact()); } TryBindHost(text, msg.SequenceIndex); } catch (Exception ex) { Plugin.Log.Warn("[BossSpawn] HandleHostBossDynamicSpawn failed: " + ex.GetType().Name + ": " + ex.Message); } } private static void TryBindLocal(string slot, int localSeq) { HostEntry value; LocalAdd la; lock (_lock) { _hostEntries.TryGetValue(slot + "|" + localSeq, out value); la = FindLocal(slot, localSeq); } FinishBind(slot, localSeq, value, la); } private static void TryBindHost(string slot, int hostSeq) { HostEntry value; LocalAdd la; lock (_lock) { _hostEntries.TryGetValue(slot + "|" + hostSeq, out value); la = FindLocal(slot, hostSeq); } FinishBind(slot, hostSeq, value, la); } private static LocalAdd? FindLocal(string slot, int seq) { if (_localBySlot.TryGetValue(slot, out List value)) { foreach (LocalAdd item in value) { if (item.Seq == seq) { return item; } } } return null; } private static void FinishBind(string slot, int seq, HostEntry? he, LocalAdd? la) { //IL_00b2: 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_0171: Unknown result type (might be due to invalid IL or missing references) if (he == null) { return; } if (la != null && !he.BoundLocal) { he.BoundLocal = true; he.BoundLocalInstanceId = la.InstanceId; la.BoundHostSeq = seq; SpawnsBound++; Plugin.Log.Info(string.Format("[BossSpawn] BOUND slot={0} seq={1} hostUnitId={2} localUnitId={3} localInst={4} hostPos={5:F1} localPos={6:F1}", slot, seq, string.IsNullOrEmpty(he.Msg.AddUnitId) ? "?" : he.Msg.AddUnitId, string.IsNullOrEmpty(la.UnitId) ? "?" : la.UnitId, la.InstanceId, he.Msg.Position, la.Pos)); if (RuntimeSyncEnabled() && he.Msg.HostSpawnIndex > 0 && !IsSpecialAdd(he.Msg.AddType)) { bool flag = NetGameplayProbeManager.RegisterMirroredRuntimeSpawn(la.Unit, he.Msg.HostSpawnIndex); Plugin.Log.Info($"[BossSpawn] RT3 drive bound add as host puppet slot={slot} seq={seq} hostIdx={he.Msg.HostSpawnIndex} bound={flag}"); FinishGatedRelease(la.Unit, la.InstanceId, he.Msg.Position, flag, slot, seq); } } else if (la == null && !he.BoundLocal && LogOn) { Plugin.Log.Info($"[BossSpawn] host add pending local (await local spawn) slot={slot} seq={seq} {he.Msg.ToCompact()}"); } } private static bool RuntimeSyncEnabled() { try { return Plugin.Cfg.EnableRuntimeSpawnSync.Value; } catch { return false; } } private static bool SnapEnabled() { try { return Plugin.Cfg.EnableRuntimeSpawnSnapOnBind.Value; } catch { return false; } } private static bool InertEnabled() { try { return Plugin.Cfg.EnableRuntimeSpawnInertUntilBound.Value; } catch { return false; } } private static void BeginGatedInert(object unit, int instId) { try { if (unit != null && instId != 0) { bool flag = InertEnabled() && TryFreezeMovement(unit); lock (_lock) { SweepStaleGatedLocked(Time.realtimeSinceStartup); _gatedAdds[instId] = new GatedAdd { Unit = unit, At = Time.realtimeSinceStartup, Frozen = flag }; } SpawnsGatedInert++; if (LogOn) { Plugin.Log.Info($"[BossSpawn] RT3-A gated inst={instId} frozen={flag}"); } } } catch (Exception ex) { Plugin.Log.Warn("[BossSpawn] BeginGatedInert failed: " + ex.GetType().Name + ": " + ex.Message); } } private static void FinishGatedRelease(object unit, int instId, Vector3 hostPos, bool snap, string slot, int seq) { //IL_0042: 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_0050: Unknown result type (might be due to invalid IL or missing references) try { GatedAdd value; lock (_lock) { _gatedAdds.TryGetValue(instId, out value); _gatedAdds.Remove(instId); } bool flag = false; if (snap && SnapEnabled() && IsFinite(hostPos)) { flag = BossReflect.TryInvokeArg(unit, "TeleportTo", hostPos, out string _); if (flag) { SpawnsSnappedOnBind++; } } if (value != null && value.Frozen && !snap) { TryUnfreezeMovement(unit); } if (LogOn) { Plugin.Log.Info($"[BossSpawn] RT3-A released inst={instId} slot={slot} seq={seq} snap={snap} teleported={flag} hostPos={hostPos:F1}"); } } catch (Exception ex) { Plugin.Log.Warn("[BossSpawn] FinishGatedRelease failed: " + ex.GetType().Name + ": " + ex.Message); } } public static bool IsHitGated(object? npc) { try { if (npc == null || !RuntimeSyncEnabled() || !SnapEnabled()) { return false; } int num = BossReflect.InstanceId(npc); if (num == 0) { return false; } bool flag; lock (_lock) { flag = _gatedAdds.ContainsKey(num); } if (flag) { HitsGateSwallowed++; } return flag; } catch { return false; } } public static void TickReleaseStaleGated() { try { TickResolveDeferredHostOnly(); if (_gatedAdds.Count == 0) { return; } List list = null; float realtimeSinceStartup = Time.realtimeSinceStartup; lock (_lock) { foreach (KeyValuePair gatedAdd in _gatedAdds) { if (realtimeSinceStartup - gatedAdd.Value.At > 4f) { (list ?? (list = new List())).Add(gatedAdd.Value); } } if (list != null) { foreach (GatedAdd item in list) { _gatedAdds.Remove(BossReflect.InstanceId(item.Unit)); } } } if (list == null) { return; } foreach (GatedAdd item2 in list) { if (item2.Frozen) { TryUnfreezeMovement(item2.Unit); } if (LogOn) { Plugin.Log.Info($"[BossSpawn] RT3-A stale gate released (no host manifest) inst={BossReflect.InstanceId(item2.Unit)}"); } } } catch { } } private static void TickResolveDeferredHostOnly() { //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) if (!RuntimeSyncEnabled()) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; List list = null; lock (_lock) { foreach (HostEntry value in _hostEntries.Values) { if (!value.BoundLocal && !value.HostOnlyResolved && !(realtimeSinceStartup - value.ReceivedAt < 2f)) { if (IsSpecialAdd(value.Msg.AddType)) { value.HostOnlyResolved = true; } else if (value.Msg.UnitIdValue == 0 || value.Msg.HostSpawnIndex <= 0) { value.HostOnlyResolved = true; Plugin.Log.Warn($"[BossSpawn] host-only add NOT mirrorable (unitIdValue={value.Msg.UnitIdValue} hostIdx={value.Msg.HostSpawnIndex}) {value.Msg.ToCompact()}"); } else { value.BoundLocal = true; value.HostOnlyResolved = true; (list ?? (list = new List())).Add(value); } } } } if (list == null) { return; } foreach (HostEntry item in list) { SpawnsHostOnlyNoLocal++; Plugin.Log.Info($"[BossSpawn] RT3 mirror host-only add (local never arrived) seq={item.Msg.SequenceIndex} unitId={item.Msg.UnitIdValue} hostIdx={item.Msg.HostSpawnIndex} pos={item.Msg.Position:F1}"); RuntimeSpawnManager.MirrorBossAdd(item.Msg.UnitIdValue, item.Msg.Position, item.Msg.HostSpawnIndex); } } private static bool TryFreezeMovement(object unit) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) try { object obj = BossReflect.GetMember(unit, "AiAgent") ?? BossReflect.GetMember(unit, "aiAgent"); if (obj != null) { BossReflect.TryInvoke(obj, "StopOnCurrentPosition", out string detail); BossReflect.TryInvokeArg(obj, "SetCanMove", false, out detail); } object? obj2 = BossReflect.GetMember(unit, "Rigidbody") ?? BossReflect.GetMember(unit, "rigidbody"); Rigidbody val = (Rigidbody)((obj2 is Rigidbody) ? obj2 : null); if ((Object)(object)val != (Object)null && !val.isKinematic) { val.linearVelocity = Vector3.zero; } return true; } catch { return false; } } private static void TryUnfreezeMovement(object unit) { try { object obj = BossReflect.GetMember(unit, "AiAgent") ?? BossReflect.GetMember(unit, "aiAgent"); if (obj != null) { BossReflect.TryInvokeArg(obj, "SetCanMove", true, out string _); } } catch { } } private static void SweepStaleGatedLocked(float now) { if (_gatedAdds.Count == 0) { return; } List list = null; foreach (KeyValuePair gatedAdd in _gatedAdds) { if (now - gatedAdd.Value.At > 4f) { (list ?? (list = new List())).Add(gatedAdd.Key); } } if (list == null) { return; } foreach (int item in list) { _gatedAdds.Remove(item); } } private static bool IsFinite(Vector3 v) { //IL_0000: 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_001a: 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_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (!float.IsNaN(v.x) && !float.IsNaN(v.y) && !float.IsNaN(v.z) && !float.IsInfinity(v.x) && !float.IsInfinity(v.y)) { return !float.IsInfinity(v.z); } return false; } private static bool IsSpecialAdd(string addType) { if (addType == null) { return false; } if (_specialAdds.Contains(addType)) { return true; } if (addType == "GoblinCousinArm") { return true; } if (addType == "HellshrewDesertClausePike") { return true; } return false; } private static int ReadUnitIdValueFromUnit(object spawnedUnit) { try { object member = BossReflect.GetMember(spawnedUnit, "unitSO"); if (member == null) { return 0; } object member2 = BossReflect.GetMember(member, "id"); object obj = ((member2 == null) ? null : BossReflect.GetMember(member2, "value")); return (obj != null) ? Convert.ToInt32(obj) : 0; } catch { return 0; } } public static string FormatCounters() { return $"attributed={SpawnsAttributed} hostBroadcast={SpawnsHostBroadcast} clientLocal={SpawnsClientLocal} bound={SpawnsBound} hostOnly={SpawnsHostOnlyNoLocal} " + $"gatedInert={SpawnsGatedInert} snapped={SpawnsSnappedOnBind} hitGateSwallowed={HitsGateSwallowed}"; } } internal sealed class BossFightHelperAdapter : BossAdapterBase { private static Type? _rocketType; private static MethodInfo? _rocketInit; private static MethodInfo? _autoPoolGetInstance; private static MethodInfo? _autoPoolReleaseGO; private static MethodInfo? _psPlay; private static Type? _autoPoolType; private static Type? _psType; private static Object? _autoPool; private static GameObject? _ghostDummyMarker; private static bool _d2Resolved; private static float _ghostRocketLogNext; private static readonly string[] DesertDialogFields = new string[3] { "airstrikeDialogue", "sniperDialogue", "terminatorDialogue" }; private static readonly string[] DesertDialogIds = new string[3] { "airstrike", "sniper", "terminator" }; private const string BossOwnDialogId = "boss-open"; private Renderer? _pikeVisual; private Renderer? _pikeVisualSource; private bool _pikeVisualLogged; public override string AdapterName => "BossFightHelper"; protected override string TypeShortName => "BossFightHelper"; protected override string[] TypeFullNames => new string[2] { "PerfectRandom.Sulfur.Core.BossFightHelper", "PerfectRandom.Sulfur.Gameplay.BossFightHelper" }; public override string[] StartChainMethods => new string[2] { "OnStartInteractWithBoss", "TriggerFight" }; public override bool IsStarted(object component) { bool value; return BossReflect.TryGetBool(component, "fightStarted", out value) && value; } public override object? GetHealthUnit(object component) { return BossReflect.GetMember(component, "bossUnit"); } public override bool TryGetSandstormArenaSphere(object component, out Vector3 center, out float radius) { //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_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_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_007d: 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_00e3: Unknown result type (might be due to invalid IL or missing references) center = Vector3.zero; radius = 0f; if (!BossReflect.HasMethod(component, "OnStartInteractWithBoss")) { return false; } object member = BossReflect.GetMember(component, "desertClausePerimeter"); if (member == null) { return false; } BossReflect.TryGetFloat(member, "SphereRadius", out radius); object? member2 = BossReflect.GetMember(member, "sphereCollider"); Component val = (Component)((member2 is Component) ? member2 : null); if (val != null && (Object)(object)val != (Object)null) { center = val.transform.position; if (radius <= 0f) { radius = val.transform.lossyScale.x * 0.5f; } return radius > 0f; } Component val2 = (Component)((member is Component) ? member : null); if (val2 != null && (Object)(object)val2 != (Object)null) { center = val2.transform.position; } else { object? healthUnit = GetHealthUnit(component); Component val3 = (Component)((healthUnit is Component) ? healthUnit : null); if (val3 == null || !((Object)(object)val3 != (Object)null)) { return false; } center = val3.transform.position; } return radius > 0f; } public void SpawnGhostVisualRockets(object missileBase) { //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Expected O, but got Unknown try { if (!(missileBase is Component)) { return; } GameObject missilePrefab = default(GameObject); ref GameObject reference = ref missilePrefab; object? member = BossReflect.GetMember(missileBase, "missilePrefab"); reference = (GameObject)((member is GameObject) ? member : null); GameObject markerPrefab = default(GameObject); ref GameObject reference2 = ref markerPrefab; object? member2 = BossReflect.GetMember(missileBase, "missileMarkerPrefab"); reference2 = (GameObject)((member2 is GameObject) ? member2 : null); object bossHelper = BossReflect.GetMember(missileBase, "bossFightHelper"); if ((Object)(object)missilePrefab == (Object)null || bossHelper == null) { return; } if (!_d2Resolved) { _d2Resolved = true; _rocketType = AccessTools.TypeByName("PerfectRandom.Sulfur.Gameplay.DesertMissileRocket"); _autoPoolType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.AutoPool"); if (_rocketType != null) { _rocketInit = _rocketType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "Initialize" && m.GetParameters().Length == 5); } if (_autoPoolType != null) { _autoPoolGetInstance = _autoPoolType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "GetInstance" && !m.IsGenericMethodDefinition && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(GameObject)); _autoPoolReleaseGO = _autoPoolType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "ReleaseInstance" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(GameObject)); } _psType = AccessTools.TypeByName("UnityEngine.ParticleSystem"); if (_psType != null) { _psPlay = AccessTools.Method(_psType, "Play", Type.EmptyTypes, (Type[])null); } } if (_rocketType == null || _rocketInit == null || _autoPoolGetInstance == null || _autoPoolType == null) { return; } if (_autoPool == (Object)null || !Object.op_Implicit(_autoPool)) { _autoPool = Object.FindAnyObjectByType(_autoPoolType); } if (_autoPool == (Object)null) { return; } if ((Object)(object)_ghostDummyMarker == (Object)null) { _ghostDummyMarker = new GameObject("CoopGhostRocketMarker"); Object.DontDestroyOnLoad((Object)(object)_ghostDummyMarker); } float lifetime = ((BossReflect.GetMember(missileBase, "missileLifetime") is float num) ? num : 15f); float startY = ((BossReflect.GetMember(missileBase, "missileStartYOffset") is float num2) ? num2 : 30f); float yHit = ((BossReflect.GetMember(missileBase, "yPosWorldToHit") is float num3) ? num3 : (-5.2f)); int others = 0; int spawned = 0; NetGameplaySyncBridge.ForEachRemotePlayerTransform(delegate(string peerId, Transform proxyT) { //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) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_016f: 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) others++; try { object? obj = _autoPoolGetInstance.Invoke(_autoPool, new object[1] { missilePrefab }); Component val = (Component)((obj is Component) ? obj : null); if (!((Object)(object)val == (Object)null)) { GameObject gameObject = val.gameObject; Component component = gameObject.GetComponent(_rocketType); if (!((Object)(object)component == (Object)null)) { Vector3 position = proxyT.position; position.y = startY; gameObject.transform.SetPositionAndRotation(position, Quaternion.LookRotation(Vector3.down)); try { _rocketType.GetField("yWorldPosToHit")?.SetValue(component, yHit); } catch { } try { _rocketType.GetField("isPatternRocket")?.SetValue(component, false); } catch { } GameObject val2 = _ghostDummyMarker; if ((Object)(object)markerPrefab != (Object)null) { object? obj4 = _autoPoolGetInstance.Invoke(_autoPool, new object[1] { markerPrefab }); Component val3 = (Component)((obj4 is Component) ? obj4 : null); if (val3 != null && (Object)(object)val3 != (Object)null) { val2 = val3.gameObject; val2.transform.SetParent(gameObject.transform, true); val2.transform.position = new Vector3(position.x, yHit + 0.1f, position.z); val2.transform.rotation = Quaternion.identity; if (_psType != null) { Component component2 = val2.GetComponent(_psType); if ((Object)(object)component2 != (Object)null) { try { _psPlay?.Invoke(component2, null); } catch { } } } } } NetBossEncounterManager.RegisterGhostRocket((Object)(object)component, (val2 == _ghostDummyMarker) ? null : val2); _rocketInit.Invoke(component, new object[5] { bossHelper, proxyT, lifetime, val2, false }); spawned++; } } } catch (Exception ex2) { Plugin.Log.Warn("[DesertMissile] ghost rocket spawn failed for " + peerId + ": " + ex2.GetType().Name + ": " + (ex2.InnerException?.Message ?? ex2.Message)); } }); if (Plugin.Cfg.LogBossPreFight.Value && Time.realtimeSinceStartup >= _ghostRocketLogNext) { _ghostRocketLogNext = Time.realtimeSinceStartup + 1f; Plugin.Log.Info($"[DesertMissile] ghost visual rockets: proxies={others} spawned={spawned}"); } } catch (Exception ex) { Plugin.Log.Warn("[DesertMissile] SpawnGhostVisualRockets failed: " + ex.GetType().Name + ": " + (ex.InnerException?.Message ?? ex.Message)); } } internal static void ReleaseGhostMarker(GameObject marker) { try { if (!((Object)(object)marker == (Object)null) && !(_autoPoolReleaseGO == null) && !(_autoPool == (Object)null) && Object.op_Implicit(_autoPool)) { _autoPoolReleaseGO.Invoke(_autoPool, new object[1] { marker }); } } catch (Exception ex) { Plugin.Log.Warn("[DesertMissile] ReleaseGhostMarker failed: " + ex.GetType().Name + ": " + (ex.InnerException?.Message ?? ex.Message)); } } public override bool TrySetArenaCenter(object component, Vector3 center) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0065: 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_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_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_0098: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) if (!BossReflect.HasMethod(component, "OnStartInteractWithBoss")) { return false; } object member = BossReflect.GetMember(component, "desertClausePerimeter"); Component val = (Component)((member is Component) ? member : null); if (val == null || (Object)(object)val == (Object)null) { return false; } Vector3 position = val.transform.position; object? member2 = BossReflect.GetMember(member, "sphereCollider"); Component val2 = (Component)((member2 is Component) ? member2 : null); if (val2 != null && (Object)(object)val2 != (Object)null) { position = val2.transform.position; } Vector3 val3 = center - position; val3.y = 0f; if (((Vector3)(ref val3)).sqrMagnitude < 0.0001f) { return true; } Transform transform = val.transform; transform.position += val3; return true; } public override bool RunsLocalIntroPresentation(object component) { return BossReflect.HasMethod(component, "OnStartInteractWithBoss"); } public override bool IsCombatEntrySource(object component, string source) { if (BossAdapterBase.ParseSourceMethod(source) == "TriggerFight") { return RunsLocalIntroPresentation(component); } return false; } public override bool TryApplyCombatEntry(object component, out string detail) { if (IsStarted(component)) { detail = "already-started"; return true; } bool result = BossReflect.TryInvoke(component, "TriggerFight", out detail); string text = "no-op"; try { GameManager instance = StaticInstance.Instance; if ((Object)(object)instance != (Object)null) { instance.ModifyPlayerInvulnerability((LockStatePadlock)3, false); instance.ModifyControllerLock((LockStatePadlock)3, false); text = "released Cinematic lock+invuln"; } } catch (Exception ex) { text = "lock-release ex " + ex.GetType().Name; } detail = "TriggerFight: " + detail + "; " + text; return result; } private static object? GetBossNpc(object component) { return BossReflect.GetMember(component, "bossNPC") ?? BossReflect.GetMember(component, "bossUnit"); } private static string GraphName(object? graph) { Object val = (Object)((graph is Object) ? graph : null); if (val == null || !(val != (Object)null)) { return ""; } return val.name.Replace("(Clone)", "").Trim(); } public override bool TryGetActiveMidFightDialogId(object component, out string dialogId) { dialogId = ""; try { if (BroadcastsBossOwnDialog(component)) { dialogId = "boss-open"; return true; } object bossNpc = GetBossNpc(component); object obj = ((bossNpc == null) ? null : BossReflect.GetMember(bossNpc, "dialog")); string text = GraphName((obj == null) ? null : BossReflect.GetMember(obj, "graph")); if (string.IsNullOrEmpty(text)) { return false; } if (BossReflect.HasMethod(component, "OnStartInteractWithBoss")) { for (int i = 0; i < DesertDialogFields.Length; i++) { if (GraphName(BossReflect.GetMember(component, DesertDialogFields[i])) == text) { dialogId = DesertDialogIds[i]; return true; } } return false; } return false; } catch { return false; } } public override bool BroadcastsBossOwnDialog(object component) { try { if (component == null || BossReflect.HasMethod(component, "OnStartInteractWithBoss")) { return false; } return component.GetType().Name == "TerrorbaumBossFightHelper"; } catch { return false; } } public override bool ClientBossIsPurePuppet(object component) { try { if (component == null || BossReflect.HasMethod(component, "OnStartInteractWithBoss")) { return false; } return component.GetType().Name == "TerrorbaumBossFightHelper"; } catch { return false; } } public override int SuppressClientMechanics(object component, out string detail) { detail = ""; try { int num = 0; Behaviour val = (Behaviour)((component is Behaviour) ? component : null); if (val != null && (Object)(object)val != (Object)null && val.enabled) { val.enabled = false; num++; } object member = BossReflect.GetMember(component, "bossPhases"); Behaviour val2 = (Behaviour)((member is Behaviour) ? member : null); if (val2 != null && (Object)(object)val2 != (Object)null && val2.enabled) { val2.enabled = false; num++; } bool value = default(bool); if (member != null && BossReflect.TryGetBool(member, "isTransitioning", out value) && value && TrySetMember(member, "isTransitioning", false)) { num++; } detail = $"disabled {num} mechanic driver(s)/flag(s) (helper+bossPhases+isTransitioning)"; return num; } catch (Exception ex) { detail = "ex " + ex.GetType().Name + ": " + ex.Message; return 0; } } private static bool IsTerrorbaum(object component) { try { return component != null && component.GetType().Name == "TerrorbaumBossFightHelper"; } catch { return false; } } public override string? ResolveHitTargetRole(object component, object hitUnit) { if (!IsTerrorbaum(component)) { return base.ResolveHitTargetRole(component, hitUnit); } if (hitUnit == null) { return null; } object healthUnit = GetHealthUnit(component); if (healthUnit == null || healthUnit != hitUnit) { return null; } if (!BossReflect.TryGetBool(hitUnit, "isInvulnerable", out var value) || value) { return "body"; } return "eye"; } public override object? ResolveHostTargetForRole(object component, string role) { if (!(role == "eye") || !IsTerrorbaum(component)) { return base.ResolveHostTargetForRole(component, role); } return GetHealthUnit(component); } public override bool TryApplyHostBossHit(object component, string role, object target, float damage, int damageTypeInt, object? source, out bool vanillaResult, out string detail) { if (role != "eye" || !IsTerrorbaum(component)) { return base.TryApplyHostBossHit(component, role, target, damage, damageTypeInt, source, out vanillaResult, out detail); } vanillaResult = false; if (!IsStarted(component)) { detail = "eye-gate: fight not started"; return false; } if (BossReflect.TryGetBool(component, "IsTransitioning", out var value) && value) { detail = "eye-gate: transitioning"; return false; } BossReflect.TryInvokeBool(target, "SetInvulnerable", arg: false, out string detail2); try { bool result = base.TryApplyHostBossHit(component, role, target, damage, damageTypeInt, source, out vanillaResult, out detail); detail = "eye-window: " + detail; return result; } finally { BossReflect.TryInvokeBool(target, "SetInvulnerable", arg: true, out detail2); } } public override bool TryOpenBossOwnDialog(object component, out string detail) { detail = ""; try { if (!BroadcastsBossOwnDialog(component)) { detail = "not-a-bossOwnDialog-boss"; return false; } object bossNpc = GetBossNpc(component); if (bossNpc == null) { detail = "no bossNPC"; return false; } MethodInfo methodInfo = FindInteract(bossNpc.GetType()); if (methodInfo == null) { detail = "Interact(1-arg) not found"; return false; } methodInfo.Invoke(bossNpc, new object[1]); detail = "opened boss-own dialog"; return true; } catch (Exception ex) { detail = "ex " + ex.GetType().Name + ": " + ex.Message; return false; } } public override bool TryApplyDiscreteEvent(object component, string eventName, bool hasPos, Vector3 pos, out string detail) { //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) detail = ""; if (eventName == "BossJump" || eventName == "BossLand") { bool flag = eventName == "BossJump"; object bossNpc = GetBossNpc(component); Component val = (Component)((bossNpc is Component) ? bossNpc : null); if (val == null || (Object)(object)val == (Object)null) { detail = "no boss body"; return false; } object? member = BossReflect.GetMember(bossNpc, "animator"); Animator val2 = (Animator)((member is Animator) ? member : null); SeverBossFromPike(component, bossNpc); DetachBossFromMount(bossNpc, val.transform, val2); if (!flag) { TrySetMember(bossNpc, "isAttachedToUnit", false); } if ((Object)(object)val2 != (Object)null) { val2.SetBool("JumpingOffPike", flag); } detail = (flag ? "detached mount + JumpingOffPike=true (puppet owns position)" : "JumpingOffPike=false (puppet owns position)"); return true; } if (IsTerrorbaum(component)) { switch (eventName) { case "TerrorDig": { string detail4; bool result3 = BossReflect.TryInvoke(component, "StartDigging", out detail4); detail = "dig " + detail4; return result3; } case "TerrorEruptAoe": { string detail3; bool result2 = BossReflect.TryInvoke(component, "OnEruptStartAoe", out detail3); detail = "erupt-aoe " + detail3; return result2; } case "TerrorRoot": { string detail2; bool result = BossReflect.TryInvoke(component, "OnRootStart", out detail2); detail = "root " + detail2; return result; } default: { if (!eventName.StartsWith("TerrorErupt:", StringComparison.Ordinal)) { break; } string text = eventName.Substring("TerrorErupt:".Length); if (!hasPos) { detail = "erupt without position"; return false; } try { MethodInfo method = component.GetType().GetMethod("OnStartEruptAttack", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { detail = "OnStartEruptAttack not found"; return false; } method.Invoke(component, new object[2] { pos, text }); detail = $"erupt at {pos:F1} trigger={text}"; return true; } catch (Exception ex) { detail = "erupt ex " + ex.GetType().Name + ": " + ex.Message; return false; } } case null: break; } } switch (eventName) { case "DialogClose": { string detail5; bool flag2 = BossDialogReflect.TryFinalizeCurrentDialog(out detail5); detail = $"dialog close finalized={flag2} ({detail5})"; return true; } case "Sandstorm": { string detail6; bool result4 = BossReflect.TryInvoke(component, "Anim_OnTriggerSandstorm", out detail6); detail = "sandstorm " + detail6; return result4; } default: if (eventName.StartsWith("Dialog:", StringComparison.Ordinal)) { string text2 = eventName.Substring(7); if (text2 == "boss-open") { object bossNpc2 = GetBossNpc(component); if (bossNpc2 == null) { detail = "no bossNPC"; return false; } MethodInfo methodInfo = FindInteract(bossNpc2.GetType()); if (methodInfo == null) { detail = "Interact(1-arg) not found"; return false; } methodInfo.Invoke(bossNpc2, new object[1]); detail = "opened boss-own dialog"; return true; } int num = Array.IndexOf(DesertDialogIds, text2); if (num < 0) { detail = "unknown-dialog:" + text2; return false; } try { object bossNpc3 = GetBossNpc(component); object obj = ((bossNpc3 == null) ? null : BossReflect.GetMember(bossNpc3, "dialog")); object member2 = BossReflect.GetMember(component, DesertDialogFields[num]); if (bossNpc3 == null || obj == null || member2 == null) { detail = $"missing npc/dialog/graph (npc={bossNpc3 != null} dialog={obj != null} graph={member2 != null})"; return false; } if (!TrySetMember(obj, "graph", member2)) { detail = "set-graph-failed"; return false; } MethodInfo methodInfo2 = FindInteract(bossNpc3.GetType()); if (methodInfo2 == null) { detail = "Interact(1-arg) not found"; return false; } methodInfo2.Invoke(bossNpc3, new object[1]); detail = "opened dialog " + text2; return true; } catch (Exception ex2) { detail = "ex " + ex2.GetType().Name + ": " + ex2.Message; return false; } } goto case null; case null: detail = "not-a-dialog-event"; return false; } } private static MethodInfo? FindInteract(Type npcType) { Type type = npcType; while (type != null) { MethodInfo methodInfo = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo mi) => mi.Name == "Interact" && mi.GetParameters().Length == 1); if (methodInfo != null) { return methodInfo; } type = type.BaseType; } return null; } private static bool TrySetMember(object obj, string name, object value) { Type type = obj.GetType(); while (type != null) { FieldInfo field = type.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { field.SetValue(obj, value); return true; } PropertyInfo property = type.GetProperty(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite) { property.SetValue(obj, value); return true; } type = type.BaseType; } return false; } private static void SeverBossFromPike(object helper, object bossNpc) { try { object? member = BossReflect.GetMember(helper, "spawnedBossPike"); Component val = (Component)((member is Component) ? member : null); if (val == null || (Object)(object)val == (Object)null) { return; } Component[] components = val.GetComponents(); foreach (Component val2 in components) { if ((Object)(object)val2 == (Object)null) { continue; } FieldInfo field = ((object)val2).GetType().GetField("attachedUnits", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(field == null)) { if (field.GetValue(val2) is IList list && list.Contains(bossNpc)) { list.Remove(bossNpc); Plugin.Log.Info("[BossPhaseAction] severed boss from pike attachedUnits (client)"); } break; } } } catch (Exception ex) { Plugin.Log.Warn("[BossPhaseAction] SeverBossFromPike failed: " + ex.GetType().Name + ": " + ex.Message); } } private static void DetachBossFromMount(object npc, Transform body, Animator? animator) { //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_0041: 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) try { Transform val = null; try { val = (((Object)(object)StaticInstance.Instance != (Object)null) ? StaticInstance.Instance.unitRoot : null); } catch { } if ((Object)(object)body.parent != (Object)(object)val) { body.SetParent(val, true); } Vector3 localScale = body.localScale; localScale.x = Mathf.Abs(localScale.x); body.localScale = localScale; if ((Object)(object)animator != (Object)null) { ((Behaviour)animator).enabled = true; } object member = BossReflect.GetMember(npc, "billboard"); if (member != null) { TrySetMember(member, "disableBillboardRotation", false); } } catch { } } public override bool TryApplyHostStart(object component, NetBossEncounterState state, out string detail) { bool flag = false; try { flag = Plugin.Cfg.EnableBossClientPresentation.Value; } catch { } if (flag && NetGameplaySyncBridge.BossMode == NetMode.Client && BossReflect.HasMethod(component, "OnStartInteractWithBoss") && BossReflect.HasMethod(component, "TriggerFight")) { if (IsStarted(component)) { detail = "already-started"; return true; } bool result = BossReflect.TryInvoke(component, "TriggerFight", out detail); detail = "client-presentation-safe TriggerFight (skipped OnStartInteractWithBoss cinematic): " + detail; return result; } return base.TryApplyHostStart(component, state, out detail); } public void EnsureBossPikeVisual(object component) { //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Expected O, but got Unknown //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Expected O, but got Unknown //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Expected O, but got Unknown try { object? bossNpc = GetBossNpc(component); Component val = (Component)((bossNpc is Component) ? bossNpc : null); if (val == null || (Object)(object)val == (Object)null) { return; } Renderer val2 = _pikeVisualSource; if ((Object)(object)val2 == (Object)null) { Renderer[] componentsInChildren = val.GetComponentsInChildren(true); foreach (Renderer val3 in componentsInChildren) { if ((Object)(object)val3 != (Object)null && ((Object)((Component)val3).gameObject).name == "Sprite") { val2 = val3; break; } } if ((Object)(object)val2 == (Object)null) { if (!_pikeVisualLogged) { _pikeVisualLogged = true; Plugin.Log.Warn("[BossPhaseAction] pike visual: no renderer named 'Sprite' on the boss body"); } return; } _pikeVisualSource = val2; } if (val2.enabled) { if ((Object)(object)_pikeVisual != (Object)null && _pikeVisual.enabled) { _pikeVisual.enabled = false; } return; } if ((Object)(object)_pikeVisual == (Object)null) { GameObject val4 = new GameObject("CoopBossPikeVisual"); val4.transform.SetParent(((Component)val2).transform, false); val4.transform.localPosition = Vector3.zero; val4.transform.localRotation = Quaternion.identity; val4.transform.localScale = Vector3.one; if (val2 is SpriteRenderer) { _pikeVisual = (Renderer?)(object)val4.AddComponent(); } else { MeshFilter component2 = ((Component)val2).GetComponent(); if ((Object)(object)component2 != (Object)null) { val4.AddComponent().sharedMesh = component2.sharedMesh; } _pikeVisual = (Renderer)val4.AddComponent(((object)val2).GetType()); } Plugin.Log.Info("[BossPhaseAction] pike visual clone created (source type=" + ((object)val2).GetType().Name + ")"); } Renderer pikeVisual = _pikeVisual; SpriteRenderer val5 = (SpriteRenderer)(object)((val2 is SpriteRenderer) ? val2 : null); if (val5 != null) { SpriteRenderer val6 = (SpriteRenderer)(object)((pikeVisual is SpriteRenderer) ? pikeVisual : null); if (val6 != null) { val6.sprite = val5.sprite; val6.flipX = val5.flipX; val6.flipY = val5.flipY; val6.color = val5.color; goto IL_026a; } } MeshFilter component3 = ((Component)val2).GetComponent(); MeshFilter component4 = ((Component)pikeVisual).GetComponent(); if ((Object)(object)component3 != (Object)null && (Object)(object)component4 != (Object)null && (Object)(object)component4.sharedMesh != (Object)(object)component3.sharedMesh) { component4.sharedMesh = component3.sharedMesh; } MaterialPropertyBlock val7 = new MaterialPropertyBlock(); val2.GetPropertyBlock(val7); pikeVisual.SetPropertyBlock(val7); goto IL_026a; IL_026a: pikeVisual.sharedMaterials = val2.sharedMaterials; pikeVisual.sortingLayerID = val2.sortingLayerID; pikeVisual.sortingOrder = val2.sortingOrder; pikeVisual.enabled = true; } catch (Exception ex) { if (!_pikeVisualLogged) { _pikeVisualLogged = true; Plugin.Log.Warn("[BossPhaseAction] pike visual failed: " + ex.GetType().Name + ": " + ex.Message); } } } public void LogDesertVisibility(object component, NetMode mode) { //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_019a: 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_0276: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) try { object bossNpc = GetBossNpc(component); Component val = (Component)((bossNpc is Component) ? bossNpc : null); if (val == null || (Object)(object)val == (Object)null) { Plugin.Log.Info($"[DesertVis] {mode} no bossNPC"); return; } Transform transform = val.transform; Renderer[] componentsInChildren = val.GetComponentsInChildren(true); StringBuilder stringBuilder = new StringBuilder(); Renderer[] array = componentsInChildren; foreach (Renderer val2 in array) { if (!((Object)(object)val2 == (Object)null)) { bool flag = val2.enabled && ((Component)val2).gameObject.activeInHierarchy; stringBuilder.Append(((Object)((Component)val2).gameObject).name).Append('=').Append((!flag) ? ((!val2.enabled) ? "OFF-rend" : "OFF-go") : (val2.isVisible ? "on/vis" : "on/CULLED")) .Append(' '); } } string text = (((Object)(object)transform.parent != (Object)null) ? ((Object)transform.parent).name : ""); object? member = BossReflect.GetMember(bossNpc, "animator"); Animator val3 = (Animator)((member is Animator) ? member : null); string text2 = (((Object)(object)val3 == (Object)null) ? "null" : $"en={((Behaviour)val3).enabled} spd={val3.speed:F1} scale={val.transform.lossyScale.x:F2}"); object? member2 = BossReflect.GetMember(component, "bossUnit"); Component val4 = (Component)((member2 is Component) ? member2 : null); Vector3 val5 = ((val4 != null && (Object)(object)val4 != (Object)null) ? val4.transform.position : Vector3.zero); object member3 = BossReflect.GetMember(bossNpc, "weapon"); bool flag2 = default(bool); int num; if (member3 != null) { object member4 = BossReflect.GetMember(member3, "bIsTriggerActive"); if (member4 is bool) { flag2 = (bool)member4; num = 1; } else { num = 0; } } else { num = 0; } bool flag3 = (byte)((uint)num & (flag2 ? 1u : 0u)) != 0; object obj = BossReflect.GetMember(bossNpc, "AiAgent") ?? BossReflect.GetMember(bossNpc, "aiAgent"); object obj2 = ((obj == null) ? null : BossReflect.GetMember(obj, "target")); object obj4; if (obj2 != null) { Object val6 = (Object)((obj2 is Object) ? obj2 : null); if (val6 == null || !(val6 == (Object)null)) { object obj3 = ((obj2 is Component) ? obj2 : null); obj4 = ((obj3 != null) ? ((Object)obj3).name : null) ?? obj2.GetType().Name; goto IL_024f; } } obj4 = "null"; goto IL_024f; IL_024f: string text3 = (string)obj4; string text4 = NetGameplayProbeManager.DescribeEnemyFireMirrorState(bossNpc); Plugin.Log.Info($"[DesertVis] {mode} pos={transform.position:F1} activeHier={val.gameObject.activeInHierarchy} parent={text} bossUnitPos={val5:F1} anim[{text2}] fire[trig={flag3} aim={text3}] mirror[{text4}] rends[{stringBuilder.ToString().TrimEnd(Array.Empty())}]"); } catch (Exception ex) { Plugin.Log.Warn("[DesertVis] failed: " + ex.GetType().Name + ": " + ex.Message); } } public override void OnClientPresentationStart(object component) { //IL_0054: 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_0061: 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) try { if (NetGameplaySyncBridge.BossMode == NetMode.Client && BossReflect.HasMethod(component, "OnStartInteractWithBoss") && Plugin.Cfg.LogBossEncounter.Value) { object member = BossReflect.GetMember(component, "bossUnit"); Component val = (Component)((member is Component) ? member : null); Vector3 val2 = ((val != null && (Object)(object)val != (Object)null) ? val.transform.position : Vector3.zero); Component val3 = (Component)((member is Component) ? member : null); bool flag = val3 != null && (Object)(object)val3 != (Object)null && val3.gameObject.activeInHierarchy; bool flag2 = IsStarted(component); Plugin.Log.Info(string.Format("[BossPresentation] Desert client state: bossUnit={0} active={1} fightStarted={2} pos={3:F1} — Cinematic lock is released only at StartSandstorm() end; client intro chain must reach it (F4).", (member != null) ? "yes" : "no", flag, flag2, val2)); } } catch (Exception ex) { Plugin.Log.Warn("[BossPresentation] Desert failed: " + ex.GetType().Name + ": " + ex.Message); } } public override string DescribeForLog(object component) { bool flag = IsStarted(component); int value = -1; bool flag2 = false; object member = BossReflect.GetMember(component, "bossPhases"); if (member != null) { flag2 = BossReflect.TryGetInt(member, "currentPhaseIndex", out value); } object member2 = BossReflect.GetMember(component, "bossUnit"); BossReflect.TryGetBool(component, "canManuallyTransition", out var value2); return string.Format("adapter={0} type={1} root={2} fightStarted={3} phase={4} bossUnit={5} canManualTransition={6}", AdapterName, component.GetType().Name, BossReflect.RootName(component), flag, flag2 ? value.ToString() : "?", (member2 != null) ? "yes" : "no", value2); } public override void TryReadState(object component, out bool hasPhase, out int phaseIndex, out bool hasPos, out Vector3 pos) { phaseIndex = 0; hasPhase = false; object member = BossReflect.GetMember(component, "bossPhases"); if (member != null) { hasPhase = BossReflect.TryGetInt(member, "currentPhaseIndex", out phaseIndex); } hasPos = BossReflect.TryGetPosition(component, out pos); } public override bool IsDialogBoss(object component) { return BossReflect.HasMethod(component, "OnStartInteractWithBoss"); } public override bool IsDialogCommitSource(string source) { string text = BossAdapterBase.ParseSourceMethod(source); if (!(text == "OnStartInteractWithBoss")) { return text == "TriggerFight"; } return true; } public override bool ShouldSuppressDuplicateDialogEntry(object component, string source) { if (!IsStarted(component)) { return false; } return BossAdapterBase.ParseSourceMethod(source) == "OnStartInteractWithBoss"; } public override bool TryApplyDialogCommit(object component, NetBossDialogCommit commit, out string detail) { bool flag = false; try { flag = Plugin.Cfg.EnableFaithfulBossIntro.Value; } catch { } bool flag2 = false; string detail2 = "skipped (faithful intro)"; if (!flag) { flag2 = BossDialogReflect.TryFinalizeCurrentDialog(out detail2); } string detail3 = "already-started"; if (!IsStarted(component)) { string text = (BossReflect.HasMethod(component, "OnStartInteractWithBoss") ? "OnStartInteractWithBoss" : "TriggerFight"); BossReflect.TryInvoke(component, text, out detail3); detail3 = text + ": " + detail3; } detail = $"dialogFinalized={flag2} ({detail2}); start={detail3}"; return true; } } internal static class BossLifecycleProbe { private static readonly Dictionary _lastThrottled = new Dictionary(); private const float ThrottleSeconds = 1f; private static bool Enabled { get { try { return Plugin.Cfg.EnableBossLifecycleProbe.Value; } catch { return false; } } } private static bool LogOn { get { try { return Plugin.Cfg.LogBossLifecycle.Value; } catch { return false; } } } public static void OnLifecycle(object? instance, string method, bool throttle, bool ran) { try { if (!Enabled || !LogOn || instance == null || !ran) { return; } if (throttle) { string key = BossReflect.InstanceId(instance) + "|" + method; float realtimeSinceStartup = Time.realtimeSinceStartup; if (_lastThrottled.TryGetValue(key, out var value) && realtimeSinceStartup - value < 1f) { return; } _lastThrottled[key] = realtimeSinceStartup; } Plugin.Log.Info("[BossLifecycle] " + instance.GetType().Name + "." + method + " | " + DescribeState(instance)); } catch (Exception ex) { Plugin.Log.Warn("[BossLifecycle] probe failed for " + method + ": " + ex.GetType().Name + ": " + ex.Message); } } private static string DescribeState(object instance) { string text = NetBossEncounterManager.DescribeComponent(instance); if (text != null) { return text; } List list = new List(); if (BossReflect.TryGetBool(instance, "fightStarted", out var value)) { list.Add($"fightStarted={value}"); } if (BossReflect.TryGetBool(instance, "FightStarted", out var value2)) { list.Add($"FightStarted={value2}"); } if (BossReflect.TryGetInt(instance, "currentPhaseIndex", out var value3)) { list.Add($"currentPhaseIndex={value3}"); } if (BossReflect.TryGetBool(instance, "isTransitioning", out var value4)) { list.Add($"isTransitioning={value4}"); } object member = BossReflect.GetMember(instance, "currentBossPhase"); if (member != null && BossReflect.TryGetInt(member, "bossPhaseIndex", out var value5)) { list.Add($"bossPhaseIndex={value5}"); } if (list.Count != 0) { return string.Join(" ", list); } return ""; } } internal static class BossReflect { private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private const BindingFlags WalkFlags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; public static object? GetMember(object? obj, string name) { if (obj == null) { return null; } try { Type type = obj.GetType(); while (type != null) { FieldInfo field = type.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(obj); } PropertyInfo property = type.GetProperty(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanRead && property.GetIndexParameters().Length == 0) { return property.GetValue(obj, null); } type = type.BaseType; } } catch { } return null; } public static bool TryGetBool(object? obj, string name, out bool value) { value = false; if (GetMember(obj, name) is bool flag) { value = flag; return true; } return false; } public static bool TryGetInt(object? obj, string name, out int value) { value = 0; object member = GetMember(obj, name); try { if (!(member is int num)) { if (!(member is short num2)) { if (!(member is byte b)) { if (member is Enum value2) { value = Convert.ToInt32(value2); return true; } return false; } value = b; return true; } value = num2; return true; } value = num; return true; } catch { return false; } } public static bool TryGetFloat(object? obj, string name, out float value) { value = 0f; object member = GetMember(obj, name); try { if (!(member is float num)) { if (!(member is double num2)) { if (member is int num3) { value = num3; return true; } return false; } value = (float)num2; return true; } value = num; return true; } catch { return false; } } public static bool HasMethod(object? obj, string method) { if (obj == null) { return false; } try { Type type = obj.GetType(); while (type != null) { if (type.GetMethod(method, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null) != null) { return true; } type = type.BaseType; } } catch { } return false; } public static bool TryInvoke(object? obj, string method, out string detail) { detail = ""; if (obj == null) { detail = "null-instance"; return false; } try { MethodInfo methodInfo = null; Type type = obj.GetType(); while (type != null && methodInfo == null) { methodInfo = type.GetMethod(method, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); type = type.BaseType; } if (methodInfo == null) { detail = "method '" + method + "' not found"; return false; } methodInfo.Invoke(obj, null); detail = "invoked " + obj.GetType().Name + "." + method + "()"; return true; } catch (TargetInvocationException ex) { detail = method + " threw " + (ex.InnerException?.GetType().Name ?? ex.GetType().Name) + ": " + (ex.InnerException?.Message ?? ex.Message); return false; } catch (Exception ex2) { detail = method + " invoke failed: " + ex2.GetType().Name + ": " + ex2.Message; return false; } } public static bool TryCallFloat(object? obj, string method, out float value) { value = 0f; if (obj == null) { return false; } try { Type type = obj.GetType(); while (type != null) { MethodInfo method2 = type.GetMethod(method, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method2 != null && (method2.ReturnType == typeof(float) || method2.ReturnType == typeof(double))) { value = Convert.ToSingle(method2.Invoke(obj, null)); return true; } type = type.BaseType; } } catch { } return false; } public static bool TryInvokeArg(object? obj, string method, object arg, out string detail) { detail = ""; if (obj == null || arg == null) { detail = "null"; return false; } try { Type type = arg.GetType(); Type type2 = obj.GetType(); while (type2 != null) { MethodInfo[] methods = type2.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != method)) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType.IsAssignableFrom(type)) { methodInfo.Invoke(obj, new object[1] { arg }); detail = "invoked " + obj.GetType().Name + "." + method + "(" + type.Name + ")"; return true; } } } type2 = type2.BaseType; } detail = "method '" + method + "(" + type.Name + ")' not found"; return false; } catch (TargetInvocationException ex) { detail = method + " threw " + (ex.InnerException?.GetType().Name ?? ex.GetType().Name); return false; } catch (Exception ex2) { detail = method + " failed: " + ex2.GetType().Name; return false; } } public static bool TryFireHealthChange(object? unit, float normalized) { if (unit == null) { return false; } try { if (GetMemberField(unit, "onHealthChange")?.GetValue(unit) is Delegate obj) { obj.DynamicInvoke(normalized); return true; } } catch { } return false; } private static FieldInfo? GetMemberField(object obj, string name) { Type type = obj.GetType(); while (type != null) { FieldInfo field = type.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field; } type = type.BaseType; } return null; } public static bool TryInvokeBool(object? obj, string method, bool arg, out string detail) { detail = ""; if (obj == null) { detail = "null-instance"; return false; } try { MethodInfo methodInfo = null; Type type = obj.GetType(); while (type != null && methodInfo == null) { methodInfo = type.GetMethod(method, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(bool) }, null); type = type.BaseType; } if (methodInfo == null) { detail = "method '" + method + "(bool)' not found"; return false; } methodInfo.Invoke(obj, new object[1] { arg }); detail = $"invoked {obj.GetType().Name}.{method}({arg})"; return true; } catch (TargetInvocationException ex) { detail = method + " threw " + (ex.InnerException?.GetType().Name ?? ex.GetType().Name); return false; } catch (Exception ex2) { detail = method + " invoke failed: " + ex2.GetType().Name; return false; } } public static bool TryInvokeWithEnumInt(object? obj, string method, int enumInt, out string detail) { detail = ""; if (obj == null) { detail = "null-instance"; return false; } try { Type type = obj.GetType(); while (type != null) { MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != method)) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType.IsEnum) { object obj2 = Enum.ToObject(parameters[0].ParameterType, enumInt); methodInfo.Invoke(obj, new object[1] { obj2 }); detail = $"invoked {obj.GetType().Name}.{method}({parameters[0].ParameterType.Name}={obj2})"; return true; } } } type = type.BaseType; } detail = "enum-method '" + method + "' not found"; return false; } catch (TargetInvocationException ex) { detail = method + " threw " + (ex.InnerException?.GetType().Name ?? ex.GetType().Name) + ": " + (ex.InnerException?.Message ?? ex.Message); return false; } catch (Exception ex2) { detail = method + " invoke failed: " + ex2.GetType().Name + ": " + ex2.Message; return false; } } public static bool TryGetPosition(object? component, out Vector3 pos) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_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_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) pos = Vector3.zero; try { Component val = (Component)((component is Component) ? component : null); if (val != null && (Object)(object)val != (Object)null) { pos = val.transform.position; return true; } GameObject val2 = (GameObject)((component is GameObject) ? component : null); if (val2 != null && (Object)(object)val2 != (Object)null) { pos = val2.transform.position; return true; } } catch { } return false; } public static string GameObjectPath(object? component) { try { Component val = (Component)((component is Component) ? component : null); object obj; if (val == null || !((Object)(object)val != (Object)null)) { GameObject val2 = (GameObject)((component is GameObject) ? component : null); obj = ((val2 != null && (Object)(object)val2 != (Object)null) ? val2.transform : null); } else { obj = val.transform; } Transform val3 = (Transform)obj; if ((Object)(object)val3 == (Object)null) { return ""; } List list = new List(); Transform val4 = val3; while ((Object)(object)val4 != (Object)null && list.Count < 10) { list.Add(((Object)val4).name); val4 = val4.parent; } list.Reverse(); return string.Join("/", list); } catch { return ""; } } public static string RootName(object? component) { try { Component val = (Component)((component is Component) ? component : null); if (val != null && (Object)(object)val != (Object)null && (Object)(object)val.gameObject != (Object)null) { return ((Object)val.gameObject).name; } GameObject val2 = (GameObject)((component is GameObject) ? component : null); if (val2 != null && (Object)(object)val2 != (Object)null) { return ((Object)val2).name; } } catch { } return component?.GetType().Name ?? ""; } public static string ReadUnitId(object? unit) { try { object obj = GetMember(unit, "UnitIdentifier") ?? GetMember(unit, "unitIdentifier") ?? GetMember(unit, "UnitId") ?? GetMember(unit, "unitId"); if (obj == null) { return ""; } try { string text = obj.GetType().GetMethod("AsGlobalId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null)?.Invoke(obj, null)?.ToString(); if (!string.IsNullOrWhiteSpace(text) && text != "null") { return (text.Length > 64) ? text.Substring(0, 64) : text; } } catch { } string text2 = obj.ToString(); if (string.IsNullOrWhiteSpace(text2) || text2 == "null") { return ""; } return (text2.Length > 64) ? text2.Substring(0, 64) : text2; } catch { return ""; } } public static int InstanceId(object? component) { try { Object val = (Object)((component is Object) ? component : null); if (val != null && val != (Object)null) { return val.GetInstanceID(); } } catch { } return 0; } public static Type? FindType(string shortName, params string[] fullNames) { foreach (string text in fullNames) { try { Type type = AccessTools.TypeByName(text); if (type != null) { return type; } } catch { } } try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type[] array; try { array = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { array = ex.Types.Where((Type t) => t != null).ToArray(); } Type[] array2 = array; foreach (Type type2 in array2) { if (type2 != null && type2.Name == shortName) { return type2; } } } } catch { } return null; } } internal static class BossTypeDiscovery { private static bool _done; public static void LogEmperorTypes() { if (_done) { return; } _done = true; try { int num = 0; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type[] array; try { array = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { array = ex.Types.Where((Type t) => t != null).ToArray(); } catch { continue; } Type[] array2 = array; foreach (Type type in array2) { if (!(type == null) && type.Name.IndexOf("Emperor", StringComparison.Ordinal) >= 0 && !type.IsNested) { num++; Plugin.Log.Info("[BossTypeDiscovery] Emperor type: " + type.FullName + " | asm=" + assembly.GetName().Name + " | base=" + (type.BaseType?.Name ?? "?")); } } } string[] array3 = new string[4] { "EmperorBossFightHelper", "EmperorBossWorm", "EmperorWormSectionController", "EmperorBossSpider" }; foreach (string text in array3) { Type type2 = BossReflect.FindType(text); if (type2 == null) { Plugin.Log.Info("[BossTypeDiscovery] " + text + ": NOT FOUND"); } else { LogMembers(type2); } } Plugin.Log.Info($"[BossTypeDiscovery] done — {num} Emperor-named type(s) discovered."); } catch (Exception ex2) { Plugin.Log.Warn("[BossTypeDiscovery] failed: " + ex2.GetType().Name + ": " + ex2.Message); } } private static void LogMembers(Type t) { try { IEnumerable values = (from f in t.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) select f.FieldType.Name + " " + f.Name).Take(40); IEnumerable values2 = (from m in t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) where !m.IsSpecialName select m.Name + "(" + string.Join(",", from p in m.GetParameters() select p.ParameterType.Name) + ")").Take(40); Plugin.Log.Info("[BossTypeDiscovery] " + t.FullName + " (asm=" + t.Assembly.GetName().Name + ", base=" + t.BaseType?.Name + ")"); Plugin.Log.Info("[BossTypeDiscovery] fields: " + string.Join(" | ", values)); Plugin.Log.Info("[BossTypeDiscovery] methods: " + string.Join(" | ", values2)); } catch (Exception ex) { Plugin.Log.Warn("[BossTypeDiscovery] member dump failed for " + t.FullName + ": " + ex.Message); } } } internal sealed class CousinHelperAdapter : BossAdapterBase { private MethodInfo? _spawnArmMethod; public override string AdapterName => "CousinHelper"; protected override string TypeShortName => "CousinHelper"; protected override string[] TypeFullNames => new string[2] { "PerfectRandom.Sulfur.Gameplay.CousinHelper", "PerfectRandom.Sulfur.Core.CousinHelper" }; public override string[] DiscreteEventMethods => new string[3] { "Submerge", "MoveToNewPool", "Reappear" }; public override string[] StartChainMethods => new string[4] { "Trigger", "TriggerIntro", "Introduction", "StartFight" }; public override bool IsStarted(object component) { bool value; return BossReflect.TryGetBool(component, "FightStarted", out value) && value; } public override object? GetHealthUnit(object component) { return BossReflect.GetMember(component, "owner"); } public override void OnClientPresentationStart(object component) { //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) try { if (NetGameplaySyncBridge.BossMode != NetMode.Client || (!Plugin.Cfg.LogBossEncounter.Value && !Plugin.Cfg.EnableBossClientPresentation.Value)) { return; } object member = BossReflect.GetMember(component, "owner"); if (member == null) { Plugin.Log.Info("[BossPresentation] Cousin owner null"); return; } object obj = BossReflect.GetMember(member, "AiAgent") ?? BossReflect.GetMember(member, "aiAgent"); string text = BossReflect.GetMember(member, "unitState")?.ToString() ?? "?"; bool value; bool flag = BossReflect.TryGetBool(member, "isInvulnerable", out value) && value; Component val = (Component)((member is Component) ? member : null); Vector3 val2 = ((val != null && (Object)(object)val != (Object)null) ? val.transform.position : Vector3.zero); BossReflect.TryGetBool(component, "introPlayed", out var value2); BossReflect.TryGetBool(component, "FightStarted", out var value3); BossReflect.TryGetBool(component, "isSubmerged", out var value4); BossReflect.TryGetBool(component, "waitingToReappear", out var value5); BossReflect.TryGetBool(component, "HasSpawnedArm", out var value6); object? member2 = BossReflect.GetMember(component, "animator"); string text2 = "?"; Behaviour val3 = (Behaviour)((member2 is Behaviour) ? member2 : null); if (val3 != null && (Object)(object)val3 != (Object)null) { text2 = $"enabled={val3.enabled}"; } bool flag2 = false; try { flag2 = Plugin.Cfg.EnableBossClientPresentation.Value; } catch { } string text3 = "diagnostic-only(activation off)"; if (flag2) { BossReflect.TryInvoke(member, "ActivateBehaviourTree", out string detail); BossReflect.TryInvoke(member, "MoveToPlayer", out string detail2); text3 = "activated[" + detail + "; " + detail2 + "]"; } Plugin.Log.Info(string.Format("[BossPresentation] Cousin client state: introPlayed={0} FightStarted={1} unitState={2} OWNER_INVULN={3} (cleared only by DoneAppearing anim event) submerged={4} waitingReappear={5} hasArm={6} animator[{7}] aiAgent={8} pos={9:F1} -> {10}", value2, value3, text, flag, value4, value5, value6, text2, (obj != null) ? "yes" : "no", val2, text3)); } catch (Exception ex) { Plugin.Log.Warn("[BossPresentation] Cousin failed: " + ex.GetType().Name + ": " + ex.Message); } } public override bool BuildDiscreteEvent(object component, string eventName, out bool hasPos, out Vector3 pos, out string diag) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_002d: 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_003a: 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_0050: 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) hasPos = false; pos = Vector3.zero; object? member = BossReflect.GetMember(component, "owner"); Component val = (Component)((member is Component) ? member : null); Vector3 val2 = ((val != null && (Object)(object)val != (Object)null) ? val.transform.position : Vector3.zero); if (TryGetCurrentPoolPos(component, out var pos2)) { hasPos = true; pos = pos2; } int num = ((BossReflect.GetMember(component, "pools") is ICollection collection) ? collection.Count : (-1)); BossReflect.TryGetBool(member, "isInvulnerable", out var value); float num2 = ((BossReflect.GetMember(component, "damageUntilSubmerge") is float num3) ? num3 : (-1f)); diag = string.Format("[CousinPool] HOST {0} poolPos={1} ownerPos={2:F1} poolCount={3} invuln={4} dmgUntilSubmerge={5:0}", eventName, hasPos ? ((Vector3)(ref pos)).ToString("F1") : "?", val2, num, value, num2); return hasPos; } public override bool TryApplyDiscreteEvent(object component, string eventName, bool hasPos, Vector3 pos, out string detail) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) switch (eventName) { case "Submerge": BossReflect.TryInvoke(component, "Submerge", out detail); return true; case "MoveToNewPool": detail = MirrorMoveToPool(component, hasPos, pos); return true; case "Reappear": BossReflect.TryInvoke(component, "Reappear", out detail); return true; case "CousinDeath": detail = ApplyLocalDeath(component); return true; default: detail = "unknown event " + eventName; return false; } } public override bool IsTerminalEvent(string eventName) { return eventName == "CousinDeath"; } private string ApplyLocalDeath(object component) { object member = BossReflect.GetMember(component, "owner"); if (member == null) { return "no-owner"; } string detail; if (BossReflect.GetMember(member, "unitState")?.ToString() == "Dead") { BossReflect.TryInvokeBool(member, "AttachToBossUI", arg: false, out detail); return "already-dead (bar detached)"; } string detail2; bool flag = BossReflect.TryInvoke(member, "Die", out detail2); BossReflect.TryInvokeBool(member, "AttachToBossUI", arg: false, out detail); return $"owner.Die()={flag} ({detail2}); bossUI detached"; } private string MirrorMoveToPool(object component, bool hasPos, Vector3 pos) { //IL_00ad: 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_0051: Unknown result type (might be due to invalid IL or missing references) if (!hasPos) { return "no-pos"; } object member = BossReflect.GetMember(component, "owner"); object obj = null; float num = float.MaxValue; if (BossReflect.GetMember(component, "pools") is IEnumerable enumerable) { foreach (object item in enumerable) { if (TryGetPoolPos(item, out var pos2)) { float num2 = Vector3.Distance(pos2, pos); if (num2 < num) { num = num2; obj = item; } } } } string detail = "no-pool"; if (obj != null) { BossReflect.TryInvokeArg(component, "SetNewPool", obj, out detail); } string detail2 = "no-owner"; if (member != null) { BossReflect.TryInvokeArg(member, "TeleportTo", pos, out detail2); } return $"mirror pool(dist={num:0.0}) set[{detail}] tp[{detail2}]"; } private static bool TryGetCurrentPoolPos(object component, out Vector3 pos) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) pos = Vector3.zero; object member = BossReflect.GetMember(component, "currentPool"); if (member != null) { return TryGetPoolPos(member, out pos); } return false; } private static bool TryGetPoolPos(object pool, out Vector3 pos) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) pos = Vector3.zero; object? member = BossReflect.GetMember(pool, "cousinPosition"); Transform val = (Transform)((member is Transform) ? member : null); if (val != null && (Object)(object)val != (Object)null) { pos = val.position; return true; } Component val2 = (Component)((pool is Component) ? pool : null); if (val2 != null && (Object)(object)val2 != (Object)null) { pos = val2.transform.position; return true; } return false; } public override bool IsDialogBoss(object component) { return true; } public override bool GatesFightOnDialogClose(object component) { return true; } public override bool DefersIntroArmUntilCommit(object component) { return true; } public override bool TryReplayIntroArm(object component, out string detail) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0067: 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_00d7: Unknown result type (might be due to invalid IL or missing references) detail = ""; try { object member = BossReflect.GetMember(component, "owner"); if (member == null || BossReflect.GetMember(member, "unitState")?.ToString() == "Dead") { detail = "owner null/dead — skip"; return false; } Component val = (Component)((member is Component) ? member : null); Vector3 val2 = ((val != null && (Object)(object)val != (Object)null) ? val.transform.position : Vector3.zero); if (_spawnArmMethod == null) { _spawnArmMethod = component.GetType().GetMethod("SpawnArm", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if (_spawnArmMethod == null) { detail = "SpawnArm method not found"; return false; } _spawnArmMethod.Invoke(component, new object[2] { val2, false }); detail = $"SpawnArm({val2:F1}) invoked"; return true; } catch (Exception ex) { detail = "ex " + ex.GetType().Name + ": " + ex.Message; return false; } } public override bool IsRoomEntrySource(string source) { return BossAdapterBase.ParseSourceMethod(source) == "Trigger"; } public override bool IsDialogCommitSource(string source) { string text = BossAdapterBase.ParseSourceMethod(source); if (!(text == "Introduction")) { return text == "StartFight"; } return true; } public override bool ShouldSuppressDuplicateDialogEntry(object component, string source) { if (!(BossReflect.TryGetBool(component, "introPlayed", out var value) && value)) { return false; } string text = BossAdapterBase.ParseSourceMethod(source); if (!(text == "Trigger") && !(text == "TriggerIntro")) { return text == "Introduction"; } return true; } public override bool TryApplyDialogCommit(object component, NetBossDialogCommit commit, out string detail) { bool flag = false; try { flag = Plugin.Cfg.EnableFaithfulBossIntro.Value; } catch { } if (flag) { string detail2 = "intro-already-played"; if (!(BossReflect.TryGetBool(component, "introPlayed", out var value) && value)) { BossReflect.TryInvoke(component, "TriggerIntro", out detail2); } detail = "faithful: TriggerIntro=" + detail2 + " (native behavior-tree intro+dialog will play; mechanic host-authoritative)"; return true; } string detail3 = "intro-already-played"; if (!(BossReflect.TryGetBool(component, "introPlayed", out var value2) && value2)) { BossReflect.TryInvoke(component, "Introduction", out detail3); } string detail4 = "already-started"; if (!IsStarted(component)) { BossReflect.TryInvoke(component, "StartFight", out detail4); } string detail5; bool flag2 = BossDialogReflect.TryFinalizeCurrentDialog(out detail5); detail = $"intro={detail3}; start={detail4}; dialogFinalized={flag2} ({detail5})"; return true; } public override string DescribeForLog(object component) { bool flag = IsStarted(component); BossReflect.TryGetBool(component, "introPlayed", out var value); BossReflect.TryGetBool(component, "isSubmerged", out var value2); BossReflect.TryGetInt(component, "stagesDone", out var value3); object member = BossReflect.GetMember(component, "owner"); return string.Format("adapter={0} type={1} root={2} FightStarted={3} introPlayed={4} submerged={5} stagesDone={6} owner={7}", AdapterName, component.GetType().Name, BossReflect.RootName(component), flag, value, value2, value3, (member != null) ? "yes" : "no"); } } internal sealed class EmperorBossAdapter : BossAdapterBase { public override string AdapterName => "EmperorBoss"; protected override string TypeShortName => "EmperorBossFightHelper"; protected override string[] TypeFullNames => new string[1] { "PerfectRandom.EmperorBossFightHelper" }; public override string[] StartChainMethods => Array.Empty(); public override bool IsStarted(object component) { object? member = BossReflect.GetMember(component, "phase2Root"); Transform val = (Transform)((member is Transform) ? member : null); if (val != null && (Object)(object)val != (Object)null) { return ((Component)val).gameObject.activeSelf; } return false; } public override string DescribeForLog(object component) { return "adapter=" + AdapterName + " type=" + component.GetType().Name + " root=" + BossReflect.RootName(component) + " phase1Root=" + Active("phase1Root") + " phase2Root=" + Active("phase2Root") + " worm=" + Active("emperorBossWorm") + " spider=" + Active("emperorBossSpider"); string Active(string field) { object member = BossReflect.GetMember(component, field); Transform val = (Transform)((member is Transform) ? member : null); if (val != null && (Object)(object)val != (Object)null) { if (!((Component)val).gameObject.activeSelf) { return "inactive"; } return "active"; } Component val2 = (Component)((member is Component) ? member : null); if (val2 != null && (Object)(object)val2 != (Object)null) { if (!val2.gameObject.activeSelf) { return "inactive"; } return "active"; } if (member == null) { return "no"; } return "yes"; } } } internal static class EmperorSpiderDiagnostics { private static readonly Dictionary _lastLog = new Dictionary(); private static readonly HashSet _oneShot = new HashSet(); private const float EventThrottleSeconds = 1.5f; private const float PosSampleSeconds = 0.5f; private static float _lastPosSampleAt = -999f; private static float _lastRocketLogAt = -999f; private static bool DiagEnabled { get { try { return Plugin.Cfg.EnableEmperorSpiderDiagnostics.Value; } catch { return false; } } } private static bool LogOn { get { try { return Plugin.Cfg.LogBossEncounter.Value; } catch { return false; } } } public static void Apply(Harmony harmony) { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Gameplay.EmperorBossSpider"); if (type == null) { Plugin.Log.Info("[EmperorSpider] EmperorBossSpider type not found."); return; } PatchNamed(harmony, type, "LateUpdate", Post("LateUpdate_Sync_Pre")); PatchNamed(harmony, type, "MaintainDistance", Post("MaintainDistance_Suppress_Pre")); PatchNamed(harmony, type, "Initialize", Post("Initialize_Gate_Pre")); PatchNamed(harmony, type, "OnNpcDeath", null, Post("OnNpcDeath_Announce_Post")); PatchNamed(harmony, type, "TriggerDefendPhase", null, Post("TriggerDefendPhase_Announce_Post")); Type type2 = AccessTools.TypeByName("PerfectRandom.Sulfur.Gameplay.EmperorBossSpiderRocketLauncher"); if (type2 != null) { PatchNamed(harmony, type2, "ActivateRapidFire", null, Post("ActivateRapidFire_Announce_Post")); } Plugin.Log.Info("[EmperorSpider] EMP-6b sync patches registered (transform stream / fight-start gate / damage / defend / rapid-fire / death)."); if (!DiagEnabled) { Plugin.Log.Info("[EmperorSpider] sync active; observe-only diagnostics off."); return; } PatchNamed(harmony, type, "Awake", null, Post("Awake_Post")); PatchNamed(harmony, type, "Initialize", null, Post("Initialize_Post")); PatchNamed(harmony, type, "StandUp", null, Post("StandUp_Post")); PatchNamed(harmony, type, "OnDamageTaken", null, Post("OnDamageTaken_Post")); PatchNamed(harmony, type, "TriggerDefendPhase", null, Post("TriggerDefendPhase_Post")); PatchNamed(harmony, type, "OnNpcDeath", null, Post("OnNpcDeath_Post")); PatchNamed(harmony, type, "ExecuteActualDeath", null, Post("ExecuteActualDeath_Post")); PatchNamed(harmony, type, "BlowHoleFromAnimation", null, Post("BlowHole_Post")); PatchNamed(harmony, type, "LateUpdate", null, Post("LateUpdate_Post")); Type type3 = AccessTools.TypeByName("PerfectRandom.Sulfur.Gameplay.EmperorBossSpiderRocketLauncher"); if (type3 != null) { PatchNamed(harmony, type3, "LaunchMissile", null, Post("LaunchMissile_Post")); } Plugin.Log.Info("[EmperorSpider] EMP-6a probe registered (observe-only: lifecycle / startup-caller / position-divergence / defend / death / rockets)."); static HarmonyMethod Post(string m) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown return new HarmonyMethod(typeof(EmperorSpiderDiagnostics).GetMethod(m, BindingFlags.Static | BindingFlags.NonPublic)); } } private static bool LateUpdate_Sync_Pre(object __instance) { try { switch (NetGameplaySyncBridge.BossMode) { case NetMode.Host: NetEmperorSpiderSync.HostCapture(__instance); break; case NetMode.Client: if (NetLinkState.ClientLinked) { NetEmperorSpiderSync.DriveClientSpider(__instance); } break; } } catch { } return true; } private static bool MaintainDistance_Suppress_Pre() { return NetEmperorSpiderSync.SuppressClientMaintainDistance(); } private static bool Initialize_Gate_Pre(object __instance) { return NetEmperorSpiderSync.TryGateFightStart(__instance); } private static void OnNpcDeath_Announce_Post(object __instance) { try { if (NetGameplaySyncBridge.BossMode == NetMode.Host) { NetEmperorSpiderSync.HostAnnounceDeath(__instance); } } catch { } } private static void TriggerDefendPhase_Announce_Post(object __instance) { try { if (NetGameplaySyncBridge.BossMode == NetMode.Host) { NetEmperorSpiderSync.HostAnnounceDefend(__instance); } } catch { } } private static void ActivateRapidFire_Announce_Post() { try { if (NetGameplaySyncBridge.BossMode == NetMode.Host) { NetEmperorSpiderSync.HostAnnounceRapidFire(); } } catch { } } private static void Awake_Post(object __instance) { if (Ready(__instance)) { Plugin.Log.Info("[EmperorSpider] Awake | " + Manifest(__instance)); } } private static void Initialize_Post(object __instance, bool __runOriginal) { if (Ready(__instance) && __runOriginal) { LogCallerOnce(__instance, "Initialize"); Plugin.Log.Info("[EmperorSpider] Initialize | " + Describe(__instance)); } } private static void StandUp_Post(object __instance) { if (Ready(__instance)) { LogCallerOnce(__instance, "StandUp"); Plugin.Log.Info("[EmperorSpider] StandUp (now vulnerable) | " + Describe(__instance)); } } private static void OnDamageTaken_Post(object __instance, float damage) { if (Ready(__instance) && !Throttled(BossReflect.InstanceId(__instance) + "|dmg", 1.5f)) { BossReflect.TryGetBool(__instance, "hasTriggeredDefend", out var value); Plugin.Log.Info($"[EmperorSpider] OnDamageTaken side={Side()} dmg={damage:F1} hp={HealthFraction(__instance):F3} triggeredDefend={value}"); } } private static void TriggerDefendPhase_Post(object __instance) { if (Ready(__instance)) { Plugin.Log.Info($"[EmperorSpider] TriggerDefendPhase side={Side()} hp={HealthFraction(__instance):F3}"); } } private static void OnNpcDeath_Post(object __instance) { if (Ready(__instance) && _oneShot.Add(BossReflect.InstanceId(__instance) * 31 + 1)) { Plugin.Log.Info("[EmperorSpider] OnNpcDeath (walk-to-death begins) side=" + Side() + " | " + Describe(__instance)); } } private static void ExecuteActualDeath_Post(object __instance) { if (Ready(__instance) && _oneShot.Add(BossReflect.InstanceId(__instance) * 31 + 2)) { Plugin.Log.Info("[EmperorSpider] ExecuteActualDeath (real death / checkpoint) side=" + Side() + " | " + Describe(__instance)); } } private static void BlowHole_Post(object __instance) { if (Ready(__instance) && _oneShot.Add(BossReflect.InstanceId(__instance) * 31 + 3)) { Plugin.Log.Info("[EmperorSpider] BlowHoleFromAnimation (desert transition seam, animation event) side=" + Side()); } } private static void LateUpdate_Post(object __instance) { //IL_00d3: Unknown result type (might be due to invalid IL or missing references) if (!DiagEnabled || !LogOn) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup - _lastPosSampleAt < 0.5f) { return; } _lastPosSampleAt = realtimeSinceStartup; try { Component val = (Component)((__instance is Component) ? __instance : null); if (val != null && !((Object)(object)val == (Object)null)) { BossReflect.TryGetInt(__instance, "currentWaypointIndex", out var value); BossReflect.TryGetInt(__instance, "targetWaypointIndex", out var value2); BossReflect.TryGetFloat(__instance, "currentSpeed", out var value3); BossReflect.TryGetBool(__instance, "isInStartupAnimation", out var value4); BossReflect.TryGetBool(__instance, "isDefending", out var value5); BossReflect.TryGetBool(__instance, "isWalkingToDeath", out var value6); BossReflect.TryGetBool(__instance, "isDead", out var value7); BossReflect.TryGetBool(__instance, "stoodUp", out var value8); Plugin.Log.Info($"[EmperorSpider] state side={Side()} pos={val.transform.position:F1} wp={value}->{value2} spd={value3:F1} hp={HealthFraction(__instance):F3} startup={value4} stood={value8} defending={value5} toDeath={value6} dead={value7}"); } } catch { } } private static void LaunchMissile_Post() { if (DiagEnabled && LogOn) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup - _lastRocketLogAt < 2f)) { _lastRocketLogAt = realtimeSinceStartup; Plugin.Log.Info("[EmperorSpider] rocket LaunchMissile side=" + Side() + " (targets local player/camera)"); } } } private static bool Ready(object inst) { if (DiagEnabled && LogOn) { return inst != null; } return false; } private static string Side() { return NetGameplaySyncBridge.BossMode.ToString(); } private static bool Throttled(string key, float window) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (_lastLog.TryGetValue(key, out var value) && realtimeSinceStartup - value < window) { return true; } _lastLog[key] = realtimeSinceStartup; return false; } private static float HealthFraction(object spider) { try { object member = BossReflect.GetMember(spider, "npc"); if (member == null || !BossReflect.TryCallFloat(member, "GetCurrentHealth", out var value)) { return -1f; } BossReflect.TryGetFloat(spider, "maxHealthValue", out var value2); return (value2 > 0.0001f) ? (value / value2) : (-1f); } catch { return -1f; } } private static void LogCallerOnce(object spider, string method) { try { if (_oneShot.Add(BossReflect.InstanceId(spider) * 131 + method.GetHashCode())) { StackFrame[] frames = new StackTrace().GetFrames(); string text = ((frames == null) ? "?" : string.Join(" <- ", from f in frames.Take(10) select f.GetMethod() into m where m != null select m.DeclaringType?.Name + "." + m.Name)); Plugin.Log.Info("[EmperorSpider] " + method + " caller chain side=" + Side() + ": " + text); } } catch { } } private static string Manifest(object spider) { try { int num = ((BossReflect.GetMember(spider, "leftClaws") is Array array) ? array.Length : (-1)); int num2 = ((BossReflect.GetMember(spider, "rightClaws") is Array array2) ? array2.Length : (-1)); int num3 = ((BossReflect.GetMember(spider, "waypoints") is ICollection collection) ? collection.Count : (-1)); string text = BossReflect.ReadUnitId(BossReflect.GetMember(spider, "npc")); object member = BossReflect.GetMember(spider, "spawnableUnit"); string text2 = (BossReflect.GetMember(member, "value") ?? member)?.ToString() ?? "?"; return string.Format("side={0} inst={1} legs={2}L/{3}R waypoints={4} npcUnit={5} addUnit={6}", Side(), BossReflect.InstanceId(spider), num, num2, num3, string.IsNullOrEmpty(text) ? "?" : text, text2); } catch (Exception ex) { return "manifest-failed: " + ex.Message; } } private static string Describe(object spider) { //IL_005c: 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_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) BossReflect.TryGetBool(spider, "isInStartupAnimation", out var value); BossReflect.TryGetBool(spider, "stoodUp", out var value2); BossReflect.TryGetBool(spider, "isDefending", out var value3); BossReflect.TryGetBool(spider, "isWalkingToDeath", out var value4); BossReflect.TryGetBool(spider, "isDead", out var value5); Component val = (Component)((spider is Component) ? spider : null); Vector3 val2 = ((val != null && (Object)(object)val != (Object)null) ? val.transform.position : Vector3.zero); return $"side={Side()} inst={BossReflect.InstanceId(spider)} pos={val2:F1} hp={HealthFraction(spider):F3} startup={value} stood={value2} defending={value3} toDeath={value4} dead={value5}"; } private static void PatchNamed(Harmony harmony, Type type, string name, HarmonyMethod prefix = null, HarmonyMethod postfix = null) { foreach (MethodInfo item in from m in AccessTools.GetDeclaredMethods(type) where m.Name == name && !m.IsStatic select m) { try { harmony.Patch((MethodBase)item, prefix, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { Plugin.Log.Error("[EmperorSpider] probe patch " + name + " failed: " + ex.Message); } } } } internal static class EmperorWormDiagnostics { private static readonly Dictionary _lastLog = new Dictionary(); private static readonly HashSet _manifestLogged = new HashSet(); private const float EventThrottleSeconds = 1.5f; private static bool _loggedWormClientDrive; private static bool _wormActive; private static float _lastGroundEventTime = -999f; private static float _lastFrameLogTime = -999f; private static float _lastWormTickAt = -999f; private static int _lastGc0 = -1; private static bool DiagEnabled { get { try { return Plugin.Cfg.EnableEmperorWormDiagnostics.Value; } catch { return false; } } } private static bool LogOn { get { try { return Plugin.Cfg.LogBossEncounter.Value; } catch { return false; } } } private static float PerfThresholdMs { get { try { return Plugin.Cfg.EmperorWormPerfThresholdMs.Value; } catch { return 6f; } } } private static bool WormTicking => Time.realtimeSinceStartup - _lastWormTickAt < 0.5f; public static void Apply(Harmony harmony) { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Gameplay.EmperorBossWorm"); if (type == null) { Plugin.Log.Info("[EmperorWorm] EmperorBossWorm type not found."); return; } PatchNamed(harmony, type, "FixedUpdate", Post("FixedUpdate_WormSync_Pre")); PatchNamed(harmony, type, "DestroySection", null, Post("DestroySection_FunctionalPost")); PatchNamed(harmony, type, "DeathAnimation", null, Post("DeathAnimation_FunctionalPost")); PatchNamed(harmony, type, "StartMovement", Post("StartMovement_FightGate_Pre")); PatchNamed(harmony, type, "JumpToNextTarget", Post("JumpToNextTarget_Target_Pre")); if (!DiagEnabled) { Plugin.Log.Info("[EmperorWorm] worm head-sync active; diagnostics off."); return; } HarmonyMethod postfix = Post("Worm_Post"); HarmonyMethod postfix2 = Post("JumpTo_Post"); HarmonyMethod postfix3 = Post("WeakpointHit_Post"); HarmonyMethod prefix = Post("DestroySection_Pre"); PatchNamed(harmony, type, "StartMovement", null, postfix); PatchNamed(harmony, type, "HitGround", null, postfix); PatchNamed(harmony, type, "StartUndergroundTravel", null, postfix); PatchNamed(harmony, type, "OnDeath", null, postfix); PatchNamed(harmony, type, "DeathAnimation", null, postfix); PatchNamed(harmony, type, "JumpTo", null, postfix2); PatchNamed(harmony, type, "WeakpointHit", null, postfix3); PatchNamed(harmony, type, "DestroySection", prefix); HarmonyMethod prefix2 = Post("Perf_Pre"); HarmonyMethod postfix4 = Post("Perf_Post"); string[] array = new string[3] { "UpdateUndergroundTravelVisuals", "SetGroundEnterParticles", "HitGround" }; foreach (string name in array) { PatchNamed(harmony, type, name, prefix2, postfix4); } Plugin.Log.Info($"[EmperorWorm] perf stopwatch registered (logs calls > {PerfThresholdMs:F0}ms; frame watchdog on)."); Plugin.Log.Info("[EmperorWorm] diagnostics registered (manifest/jump/weakpoint/destroy; event-driven, no per-frame sampling; client phase-1 worm head-driven from host)."); static HarmonyMethod Post(string m) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown return new HarmonyMethod(typeof(EmperorWormDiagnostics).GetMethod(m, BindingFlags.Static | BindingFlags.NonPublic)); } } private static bool FixedUpdate_WormSync_Pre(object __instance) { try { _lastWormTickAt = Time.realtimeSinceStartup; switch (NetGameplaySyncBridge.BossMode) { case NetMode.Host: NetEmperorWormSync.HostCapture(__instance); return true; case NetMode.Client: if (NetLinkState.ClientLinked) { if (!_loggedWormClientDrive) { _loggedWormClientDrive = true; Plugin.Log.Info("[EmperorWorm] client worm head-driven (linked client): skipping native FixedUpdate on " + Identify(__instance) + "; head streamed from host, sections follow locally."); } NetEmperorWormSync.DriveClientWorm(__instance); return false; } break; } } catch { } return true; } private static bool StartMovement_FightGate_Pre(object __instance) { return NetEmperorWormSync.TryGateFightStart(__instance); } private static void JumpToNextTarget_Target_Pre(object __instance) { NetEmperorWormSync.HostAdvanceWormTarget(__instance); } private static void DestroySection_FunctionalPost(object __instance, int index) { try { if (NetGameplaySyncBridge.BossMode == NetMode.Host) { NetEmperorWormSync.HostAnnounceSectionDestroy(__instance, index); } } catch { } } private static void DeathAnimation_FunctionalPost(object __instance) { try { if (NetGameplaySyncBridge.BossMode == NetMode.Host) { NetEmperorWormSync.HostAnnounceDeath(__instance); } } catch { } } private static void PatchNamed(Harmony harmony, Type worm, string name, HarmonyMethod prefix = null, HarmonyMethod postfix = null) { foreach (MethodInfo item in from m in AccessTools.GetDeclaredMethods(worm) where m.Name == name && !m.IsStatic select m) { try { harmony.Patch((MethodBase)item, prefix, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { Plugin.Log.Error("[EmperorWorm] probe patch " + name + " failed: " + ex.Message); } } } private static void Worm_Post(object __instance, MethodBase __originalMethod, bool __runOriginal) { try { if (Ready(__instance, __runOriginal)) { string text = __originalMethod?.Name ?? "?"; switch (text) { case "StartMovement": _wormActive = true; break; case "HitGround": case "StartUndergroundTravel": _lastGroundEventTime = Time.realtimeSinceStartup; break; case "OnDeath": case "DeathAnimation": _wormActive = false; break; } if (!Throttled(BossReflect.InstanceId(__instance) + "|" + text, 1.5f)) { Plugin.Log.Info("[EmperorWorm] " + text + " | " + Describe(__instance)); } } } catch { } } public static void ReportUpdateProfile(long totalMs, long gameplayMs, long bossMs) { try { if (WormTicking && DiagEnabled && totalMs >= 50) { Plugin.Log.Info($"[UpdateProf] side={Side()} updateBody={totalMs}ms (gameplayTick={gameplayMs}ms bossTick={bossMs}ms) — if the frame hitch is bigger than this, the cost is outside Update (LateUpdate/render/GC)"); } } catch { } } public static void FrameWatchdogTick() { try { if (!DiagEnabled || !WormTicking) { return; } float unscaledDeltaTime = Time.unscaledDeltaTime; if (!(unscaledDeltaTime < 0.12f)) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup - _lastFrameLogTime < 0.05f)) { _lastFrameLogTime = realtimeSinceStartup; float num = realtimeSinceStartup - _lastGroundEventTime; int num2 = GC.CollectionCount(0); int num3 = ((_lastGc0 >= 0) ? (num2 - _lastGc0) : 0); _lastGc0 = num2; Plugin.Log.Info($"[EmperorWormFrame] side={Side()} hitch dt={unscaledDeltaTime * 1000f:F0}ms (~{((unscaledDeltaTime > 0f) ? (1f / unscaledDeltaTime) : 0f):F0}fps) gc0Δ={num3} sinceGroundEvent={num * 1000f:F0}ms"); } } } catch { } } private static void JumpTo_Post(object __instance, Vector3 targetPosition, float jumpTime, bool __runOriginal) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) try { if (Ready(__instance, __runOriginal)) { LogManifestOnce(__instance); BossReflect.TryGetInt(__instance, "currentJumpCount", out var value); Vector3 val = HeadPos(__instance); Vector3 val2 = Velocity(__instance); Plugin.Log.Info($"[EmperorWorm] JumpTo side={Side()} target={targetPosition:F1} jumpTime={jumpTime:F2} vel={val2:F1} head={val:F1} jumpCount={value} tailHp={TailHealth(__instance):F3}"); } } catch { } } private static void WeakpointHit_Post(object __instance, float damage, bool __runOriginal) { try { if (Ready(__instance, __runOriginal)) { BossReflect.TryGetInt(__instance, "lastActiveIndex", out var value); Plugin.Log.Info($"[EmperorWorm] WeakpointHit side={Side()} dmg={damage:F1} tailHp={TailHealth(__instance):F3} lastActiveIndex={value} sections={SectionCount(__instance)} inst={BossReflect.InstanceId(__instance)}"); } } catch { } } private static void DestroySection_Pre(object __instance, int index) { try { if (DiagEnabled && LogOn) { Plugin.Log.Info(string.Format("[EmperorWorm] DestroySection side={0} index={1} sectionsBefore={2} lastActiveIndex={3} tailHp={4:F3}", Side(), index, SectionCount(__instance), ReadInt(__instance, "lastActiveIndex"), TailHealth(__instance))); } } catch { } } private static void Perf_Pre(out long __state) { __state = Stopwatch.GetTimestamp(); } private static void Perf_Post(long __state, MethodBase __originalMethod, object __instance) { try { double num = (double)(Stopwatch.GetTimestamp() - __state) * 1000.0 / (double)Stopwatch.Frequency; if (!(num < (double)PerfThresholdMs)) { BossReflect.TryGetBool(__instance, "isUnderground", out var value); Plugin.Log.Info($"[EmperorWormPerf] {__originalMethod?.Name} side={Side()} ms={num:F1} underground={value} sections={SectionCount(__instance)}"); } } catch { } } private static bool Ready(object inst, bool runOriginal) { return DiagEnabled && LogOn && inst != null && runOriginal; } private static string Side() { return NetGameplaySyncBridge.BossMode.ToString(); } private static bool Throttled(string key, float window) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (_lastLog.TryGetValue(key, out var value) && realtimeSinceStartup - value < window) { return true; } _lastLog[key] = realtimeSinceStartup; return false; } private static Vector3 HeadPos(object worm) { //IL_0013: 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) Component val = (Component)((worm is Component) ? worm : null); if (val == null || !((Object)(object)val != (Object)null)) { return Vector3.zero; } return val.transform.position; } private static Vector3 Velocity(object worm) { //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_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_0073: Unknown result type (might be due to invalid IL or missing references) try { object? member = BossReflect.GetMember(worm, "rb"); Rigidbody val = (Rigidbody)((member is Rigidbody) ? member : null); if ((Object)(object)val == (Object)null) { return Vector3.zero; } PropertyInfo propertyInfo = typeof(Rigidbody).GetProperty("linearVelocity") ?? typeof(Rigidbody).GetProperty("velocity"); if (propertyInfo != null && propertyInfo.GetValue(val) is Vector3 result) { return result; } } catch { } return Vector3.zero; } private static float TailHealth(object worm) { try { object member = BossReflect.GetMember(worm, "lastSectionNpc"); if (member != null && BossReflect.TryCallFloat(member, "GetNormalizedHealth", out var value)) { return value; } } catch { } return -1f; } private static int SectionCount(object worm) { if (!(BossReflect.GetMember(worm, "wormSections") is ICollection collection)) { return -1; } return collection.Count; } private static int ReadInt(object worm, string field) { if (!BossReflect.TryGetInt(worm, field, out var value)) { return -1; } return value; } private static void LogManifestOnce(object worm) { try { if (!(worm is Component)) { return; } int num = BossReflect.InstanceId(worm); if (!_manifestLogged.Add(num) || !(BossReflect.GetMember(worm, "wormNpcs") is IEnumerable enumerable)) { return; } BossReflect.TryGetInt(worm, "lastActiveIndex", out var value); BossReflect.TryGetInt(worm, "numberOfSections", out var value2); List list = new List(); int num2 = 0; foreach (object item in enumerable) { string text = BossReflect.ReadUnitId(item); list.Add(string.Format("[{0}]={1}", num2, string.IsNullOrEmpty(text) ? "?" : text)); num2++; } Plugin.Log.Info(string.Format("[EmperorWorm] manifest side={0} inst={1} numberOfSections={2} spawned={3} vulnerableIndex={4} units={5}", Side(), num, value2, num2, value, string.Join(",", list))); } catch (Exception ex) { Plugin.Log.Warn("[EmperorWorm] manifest-once failed: " + ex.GetType().Name + ": " + ex.Message); } } public static string Identify(object worm) { string text = BossReflect.RootName(worm); int num = BossReflect.InstanceId(worm); string text2 = BossReflect.GameObjectPath(worm); string text3 = NetGameplaySyncBridge.BossMode.ToString(); return $"root={text} inst={num} path={text2} side={text3}"; } public static string Describe(object worm) { //IL_00a0: 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_011d: 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) BossReflect.TryGetBool(worm, "bossActive", out var value); BossReflect.TryGetBool(worm, "isUnderground", out var value2); BossReflect.TryGetBool(worm, "isTravelingUnderground", out var value3); BossReflect.TryGetInt(worm, "currentJumpCount", out var value4); BossReflect.TryGetInt(worm, "numberOfSections", out var value5); int num = ((BossReflect.GetMember(worm, "sectionControllers") is ICollection collection) ? collection.Count : (-1)); int num2 = ((BossReflect.GetMember(worm, "wormNpcs") is ICollection collection2) ? collection2.Count : (-1)); Component val = (Component)((worm is Component) ? worm : null); Vector3 val2 = ((val != null && (Object)(object)val != (Object)null) ? val.transform.position : Vector3.zero); return $"{Identify(worm)} bossActive={value} underground={value2} traveling={value3} jumps={value4} sections={value5} sectionCtrls={num} wormNpcs={num2} tailHp={TailHealth(worm):F3} pos={val2:F1}"; } public static void LogWormManifest() { try { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Gameplay.EmperorBossWorm"); if (type == null) { return; } List list = Resources.FindObjectsOfTypeAll(type).Where(delegate(Object o) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_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) Component val = (Component)(object)((o is Component) ? o : null); if (val != null && (Object)(object)val != (Object)null) { Scene scene = val.gameObject.scene; if (((Scene)(ref scene)).IsValid()) { scene = val.gameObject.scene; return ((Scene)(ref scene)).isLoaded; } } return false; }).ToList(); Plugin.Log.Info($"[EmperorWorm] manifest: {list.Count} live EmperorBossWorm instance(s)"); foreach (Object item in list) { Plugin.Log.Info("[EmperorWorm] - " + Describe(item)); } } catch (Exception ex) { Plugin.Log.Warn("[EmperorWorm] manifest failed: " + ex.GetType().Name + ": " + ex.Message); } } } internal struct BossEncounterContext { public string RunKey; public string ChapterName; public int LevelIndex; public bool HasSeed; public int Seed; public string GraphName; } internal interface IBossEncounterAdapter { string AdapterName { get; } string[] StartChainMethods { get; } string[] DiscreteEventMethods { get; } bool IsEyeBoss { get; } bool ProvidesPhaseState { get; } Type? ResolveType(); bool CanHandle(object component); NetBossEncounterId BuildEncounterId(object component, in BossEncounterContext ctx); bool IsStarted(object component); bool IsContinuationSource(string source); bool TryApplyHostStart(object component, NetBossEncounterState state, out string detail); void TryReadState(object component, out bool hasPhase, out int phaseIndex, out bool hasPos, out Vector3 pos); string DescribeForLog(object component); bool IsDialogBoss(object component); bool IsDialogCommitSource(string source); bool GatesFightOnDialogClose(object component); bool DefersIntroArmUntilCommit(object component); bool TryReplayIntroArm(object component, out string detail); bool IsRoomEntrySource(string source); bool ShouldSuppressDuplicateDialogEntry(object component, string source); bool TryApplyDialogCommit(object component, NetBossDialogCommit commit, out string detail); bool TryRemoveDialogInteractable(object component, out string detail); object? GetHealthUnit(object component); bool TryGetSandstormArenaSphere(object component, out Vector3 center, out float radius); bool TrySetArenaCenter(object component, Vector3 center); bool RunsLocalIntroPresentation(object component); bool IsCombatEntrySource(object component, string source); bool TryApplyCombatEntry(object component, out string detail); bool TryGetActiveMidFightDialogId(object component, out string dialogId); bool BroadcastsBossOwnDialog(object component); bool ClientBossIsPurePuppet(object component); int SuppressClientMechanics(object component, out string detail); bool TryOpenBossOwnDialog(object component, out string detail); bool TryAttachBossBar(object component); string? ResolveHitTargetRole(object component, object hitUnit); object? ResolveHostTargetForRole(object component, string role); bool TryApplyHostBossHit(object component, string role, object target, float damage, int damageTypeInt, object? source, out bool vanillaResult, out string detail); void OnClientPresentationStart(object component); bool BuildDiscreteEvent(object component, string eventName, out bool hasPos, out Vector3 pos, out string diag); bool TryApplyDiscreteEvent(object component, string eventName, bool hasPos, Vector3 pos, out string detail); bool IsTerminalEvent(string eventName); bool TryReadEyePhase(object component, out int cycle, out int livingEyes); bool TryConsumeOneEye(object component, out int remaining, out string detail); bool TryRemoveDeadEyeFromList(object component, object eyeUnit); bool TryApplyEyePhaseComplete(object component, out int cleared, out string detail); bool TryReadEyeCompletionDiag(object component, out int phase, out int restartCounter, out bool invulnerable, out Vector3 pos); bool TryApplyLuciaDeath(object component, out string detail); void FillBossState(object component, NetBossState state); bool TryApplyBossState(object component, NetBossState state, out string detail); } internal sealed class LuciaBossFightHelperAdapter : BossAdapterBase { private const int EyePhaseIndex = 5; public override string AdapterName => "LuciaBossFightHelper"; protected override string TypeShortName => "LuciaBossFightHelper"; protected override string[] TypeFullNames => new string[1] { "PerfectRandom.Sulfur.Core.LuciaBossFightHelper" }; public override string[] StartChainMethods => new string[1] { "TriggerFight" }; public override bool IsEyeBoss => true; public override bool IsStarted(object component) { bool value; return BossReflect.TryGetBool(component, "fightStarted", out value) && value; } public override object? GetHealthUnit(object component) { return BossReflect.GetMember(component, "bossUnit"); } public override bool IsDialogBoss(object component) { return true; } public override bool IsDialogCommitSource(string source) { return BossAdapterBase.ParseSourceMethod(source) == "TriggerFight"; } public override bool ShouldSuppressDuplicateDialogEntry(object component, string source) { if (IsStarted(component)) { return BossAdapterBase.ParseSourceMethod(source) == "TriggerFight"; } return false; } public override bool TryApplyDialogCommit(object component, NetBossDialogCommit commit, out string detail) { string detail2; bool flag = BossDialogReflect.TryFinalizeCurrentDialog(out detail2); string detail3 = "already-started"; if (!IsStarted(component)) { BossReflect.TryInvoke(component, "TriggerFight", out detail3); } detail = $"dialogFinalized={flag} ({detail2}); start={detail3}"; return true; } public override bool TryReadEyePhase(object component, out int cycle, out int livingEyes) { cycle = 0; livingEyes = 0; BossReflect.TryGetInt(component, "restartCounter", out cycle); if (BossReflect.GetMember(component, "spawnedEyes") is ICollection collection) { livingEyes = collection.Count; } if (BossReflect.TryGetInt(component, "currentPhase", out var value)) { return value == 5; } return false; } public override bool TryConsumeOneEye(object component, out int remaining, out string detail) { remaining = 0; detail = ""; if (!(BossReflect.GetMember(component, "spawnedEyes") is IList list)) { detail = "no spawnedEyes list"; return false; } object obj = null; string text = "?"; foreach (object item in new ArrayList(list)) { object member = BossReflect.GetMember(item, "owner"); if (member != null && (!BossReflect.TryGetBool(member, "IsAlive", out var value) || value)) { obj = member; text = BossReflect.RootName(member); break; } } if (obj == null) { remaining = list.Count; detail = $"no living host eye (count={list.Count})"; return false; } string detail2; bool flag = BossReflect.TryInvoke(obj, "Die", out detail2); remaining = ((BossReflect.GetMember(component, "spawnedEyes") is ICollection collection) ? collection.Count : (-1)); detail = $"selected={text} nativeDie={flag} ({detail2}) remaining={remaining}"; return flag; } public override bool TryRemoveDeadEyeFromList(object component, object eyeUnit) { if (eyeUnit == null || !(BossReflect.GetMember(component, "spawnedEyes") is IList list)) { return false; } for (int i = 0; i < list.Count; i++) { if (BossReflect.GetMember(list[i], "owner") == eyeUnit) { list.RemoveAt(i); return true; } } return false; } public override bool TryApplyEyePhaseComplete(object component, out int cleared, out string detail) { cleared = 0; string detail2; if (BossReflect.GetMember(component, "spawnedEyes") is IList list) { foreach (object item in new ArrayList(list)) { object member = BossReflect.GetMember(item, "owner"); if (member != null && BossReflect.TryInvoke(member, "DestroySelf", out detail2)) { cleared++; } } list.Clear(); } string detail3; bool flag = BossReflect.TryInvoke(component, "RestartPhases", out detail3); string text = ""; if (!flag) { bool flag2 = BossReflect.TryInvokeBool(component, "ToggleDarkness", arg: false, out detail2); text = $" RestartPhases-FAILED({detail3}) fallback-darknessLifted={flag2}"; } detail = $"residualCleared={cleared} RestartPhasesInvoked={flag} ({detail3}){text}"; return flag; } public override bool TryReadEyeCompletionDiag(object component, out int phase, out int restartCounter, out bool invulnerable, out Vector3 pos) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) phase = -1; restartCounter = -1; invulnerable = false; pos = Vector3.zero; BossReflect.TryGetInt(component, "currentPhase", out phase); BossReflect.TryGetInt(component, "restartCounter", out restartCounter); object? member = BossReflect.GetMember(component, "bossUnit"); BossReflect.TryGetBool(member, "isInvulnerable", out invulnerable); BossReflect.TryGetPosition(member, out pos); return true; } public override bool TryApplyLuciaDeath(object component, out string detail) { object member = BossReflect.GetMember(component, "bossUnit"); if (member == null) { detail = "no bossUnit"; return false; } bool value; bool flag = BossReflect.TryGetBool(member, "IsAlive", out value) && !value; bool flag2 = false; string detail2 = "already-dead"; if (!flag) { flag2 = BossReflect.TryInvoke(member, "Die", out detail2); } string detail3; bool flag3 = BossReflect.TryInvokeBool(member, "AttachToBossUI", arg: false, out detail3); bool flag4 = TrySetAnimatorBool(BossReflect.GetMember(component, "eventAnimator"), "Dead", value: true); bool flag5 = TrySetAnimatorInt(BossReflect.GetMember(component, "destructionAnimator"), "DestructionLevel", 3); BossReflect.TryInvoke(component, "StopAllCoroutines", out detail3); detail = $"died={flag2}({detail2}) alreadyDead={flag} barDetached={flag3} deadAnim={flag4} destruction={flag5}"; return true; } private static bool TrySetAnimatorBool(object? animator, string param, bool value) { try { Animator val = (Animator)((animator is Animator) ? animator : null); if (val != null && (Object)(object)val != (Object)null) { val.SetBool(param, value); return true; } } catch { } return false; } private static bool TrySetAnimatorInt(object? animator, string param, int value) { try { Animator val = (Animator)((animator is Animator) ? animator : null); if (val != null && (Object)(object)val != (Object)null) { val.SetInteger(param, value); return true; } } catch { } return false; } public override void TryReadState(object component, out bool hasPhase, out int phaseIndex, out bool hasPos, out Vector3 pos) { hasPhase = BossReflect.TryGetInt(component, "currentPhase", out phaseIndex); hasPos = BossReflect.TryGetPosition(component, out pos); } public override string DescribeForLog(object component) { bool flag = IsStarted(component); int value; bool flag2 = BossReflect.TryGetInt(component, "currentPhase", out value); BossReflect.TryGetBool(component, "fightComplete", out var value2); BossReflect.TryGetInt(component, "candlesLeft", out var value3); int num = -1; if (BossReflect.GetMember(component, "spawnedEyes") is ICollection collection) { num = collection.Count; } object member = BossReflect.GetMember(component, "bossUnit"); return string.Format("adapter={0} type={1} root={2} fightStarted={3} currentPhase={4} fightComplete={5} candlesLeft={6} luciaEyes[special/add]={7} bossUnit={8}", AdapterName, component.GetType().Name, BossReflect.RootName(component), flag, flag2 ? value.ToString() : "?", value2, value3, (num >= 0) ? num.ToString() : "?", (member != null) ? "yes" : "no"); } } internal struct NetBossEncounterId { public string RunKey; public string ChapterName; public int LevelIndex; public bool HasSeed; public int Seed; public string GraphName; public string BossType; public string RootName; public string RootPath; public int InstanceId; public string Key => RunKey + "|" + BossType + "|" + RootName; public string ToCompact() { return string.Format("key={0} graph={1} type={2} root={3} path={4} inst={5}", Key, string.IsNullOrEmpty(GraphName) ? "?" : GraphName, BossType, RootName, RootPath, InstanceId); } } internal static class NetBossEncounterManager { private sealed class Entry { public IBossEncounterAdapter Adapter; public object Component; } private sealed class ContinuationWindow { public IBossEncounterAdapter Adapter; public object Component; public string OpenedBy = ""; public float OpenedAt; public float LastActivityAt; public bool StartObserved; public float StartObservedAt; } private sealed class PendingVerify { public string Key = ""; public string Source = ""; public string Invoked = ""; public string Before = ""; public float DueAt; } private sealed class PendingDialogFinalize { public string Key = ""; public float Until; } private sealed class PendingEyeComplete { public string Key = ""; public int Cycle; public int PhaseBefore; public int RestartBefore; public bool InvulnBefore; public Vector3 PosBefore; public float DueAt; } private sealed class PendingP2Manifest { public NetWitchP2Manifest Msg; public float Until; } private static readonly object _lock = new object(); private static readonly IBossEncounterAdapter[] _adapters = new IBossEncounterAdapter[5] { new WitchBossControllerAdapter(), new CousinHelperAdapter(), new LuciaBossFightHelperAdapter(), new EmperorBossAdapter(), new BossFightHelperAdapter() }; private static readonly Dictionary _registry = new Dictionary(); private static readonly HashSet _hostBroadcast = new HashSet(); private static readonly HashSet _hostCombatEntryBroadcast = new HashSet(); private static readonly HashSet _hostSandstormBroadcast = new HashSet(); private static readonly HashSet _sandstormSeen = new HashSet(); private static readonly HashSet _clientRequested = new HashSet(); private static readonly HashSet _appliedStart = new HashSet(); private static string _runScope = ""; private static int _reentryDepth; private static readonly Dictionary _continuation = new Dictionary(); private static readonly List _pendingVerify = new List(); private static readonly List _pendingDialogFinalize = new List(); private const float DialogFinalizeWindowSeconds = 6f; private const float BossStateBroadcastInterval = 0.4f; public static int BossEncounterDiscovered; public static int BossStartBroadcast; public static int BossStartRequestSent; public static int BossStartRequestReceived; public static int BossStartApplied; public static int BossStartApplyFailed; public static int BossStartRejectedSession; public static int BossEncounterNotFound; public static int BossLocalStartBlocked; public static int BossContinuationAllowed; public static int BossContinuationBlocked; public static int BossApplyIncomplete; public static int BossDuplicateSuppressed; public static int BossDialogCommitRequested; public static int BossDialogCommitBroadcast; public static int BossDialogCommitApplied; public static int BossDialogEntrySuppressed; public static int BossStateBroadcast; public static int BossStateApplied; public static int BossHitClientSent; public static int BossHitHostRecv; public static int BossHitHostApplied; public static int BossHitHostRejected; public static int BossHitVisualSent; public static int BossHitVisualPlayed; private static int _bossHitSeq; private static int _bossHitVisualSeq; public static int LuciaEyeReportSent; public static int LuciaEyeReportRecv; public static int LuciaEyeAccepted; public static int LuciaEyeRejected; public static int LuciaEyeStateBroadcast; public static int LuciaEyeStateApplied; public static int LuciaEyeResidualCleared; private static int _luciaEyeReportSeq; private static int _luciaEyeStateRevision; private static readonly HashSet _luciaEyeConsumed = new HashSet(); private static readonly HashSet _luciaEyeCycleComplete = new HashSet(); private static readonly List _pendingEyeComplete = new List(); private static readonly Dictionary _lastHitVisualAt = new Dictionary(); private const float HitVisualMinInterval = 0.1f; private static readonly HashSet _dialogCommitBroadcast = new HashSet(); private static readonly HashSet _dialogCommitApplied = new HashSet(); private static readonly HashSet _dialogInteractableRemoved = new HashSet(); private static string? _dialogOpenKey; private static readonly HashSet _fightCommitted = new HashSet(); private static readonly HashSet _fightCommitRequested = new HashSet(); private static readonly HashSet _fightCommitBroadcast = new HashSet(); private const string HostPlayerId = "host"; private static readonly Dictionary> _roomMembers = new Dictionary>(); private static readonly Dictionary> _roomMembersClientView = new Dictionary>(); private static readonly HashSet _roomEnterReported = new HashSet(); private static readonly HashSet _dialogSessionActive = new HashSet(); private static readonly HashSet _cutscenePlayed = new HashSet(); private static readonly HashSet _localTeleportedIn = new HashSet(); private static readonly Dictionary _lastIntroCommit = new Dictionary(); private static readonly HashSet _introRanWhileOutOfRoom = new HashSet(); private static int _bossStateRevision; private static readonly HashSet _bossUiAttached = new HashSet(); private static string? _desertDialogOpenKey; private static string? _localIntroDialogKey; private static string? _localMidFightDialogKey; private static bool _applyingHostDialogClose; private static bool _applyingHostDialogOpen; private static readonly HashSet _introFinishRequested = new HashSet(); private static readonly HashSet _bossOwnDialogLocallyOpen = new HashSet(); private static string? _pendingMirrorKey; private static string? _pendingMirrorEvent; private static int _pikeVisualFrame = -1; private static float _desertArenaProbeNext; private static Vector3 _desertArenaLastPos = new Vector3(float.PositiveInfinity, 0f, 0f); private static Vector3 _desertArenaLastSent = new Vector3(float.PositiveInfinity, 0f, 0f); private static float _desertArenaSendNext; private static Vector3 _desertArenaTarget = new Vector3(float.PositiveInfinity, 0f, 0f); private const float DesertArenaFollowSpeed = 6f; private const float DesertArenaSnapDistance = 25f; private static float _desertMissileProbeNext; private static float _desertPikeProbeNext; private static Type? _pikeCarrierType; private static int _desertAimRotation = -1; private static float _desertAimLogNext; private static int _pikeJumpSeq; private static State _savedRngState; private static bool _rngStateSaved; private static readonly HashSet _ghostRocketIds = new HashSet(); private static readonly Dictionary _ghostRocketMarkers = new Dictionary(); private static int _puppetExemptFrame = -1; private static bool _pikeExempt; private static bool _mountedExempt; private static float _desertVisProbeNext; private static readonly HashSet _preFightLogged = new HashSet(); private static bool _suppressBossCutscene; private static readonly HashSet _introArmReplayed = new HashSet(); private static float _nextBossStateBroadcast; private static float _nextBossStateClientLog; private static int _lastLoggedClientPhase = -999; public static int BossDiscreteSent; public static int BossDiscreteApplied; public static int BossDiscreteClientSelfFired; public static int BossDeathBroadcast; public static int BossDeathApplied; private static int _bossDiscreteSeq; private static readonly HashSet _terminalDead = new HashSet(); private static readonly HashSet _cousinFightLoopStarted = new HashSet(); public static int LuciaDeathBroadcast; public static int LuciaDeathApplied; private static int _luciaDeathRevision; public static int WitchPhaseBroadcast; public static int WitchPhaseApplied; public static int WitchPhaseBlockedLocal; public static int WitchPhaseStaleIgnored; private static int _witchPhaseRevision; private static int _lastHostWitchPhase = -1; private static readonly Dictionary _witchAppliedRevision = new Dictionary(); public static int WitchP2ManifestBroadcast; public static int WitchP2ManifestApplied; public static int WitchP2ResultBroadcast; public static int WitchP2ResultApplied; private static PendingP2Manifest? _pendingP2Manifest; private static int _witchP2Cycle; private static bool _witchP2Active; private static bool _witchP2RealHitConsumed; private static readonly HashSet _witchP2DefeatedDomes = new HashSet(); private static int _p2ClientAppliedCycle = -1; public static int WitchDeathTerminal; public static int WitchDeathClientReplica; private static float _nextPurePuppetTargetCheck; private static float _lastBossTargetLogAt = -999f; private static int _bodySwallowCount; private static float _lastBodySwallowLogAt = -999f; private static float _nextTerrorMechTick; private static float _nextBossVisProbe; private static bool Enabled { get { try { return Plugin.Cfg.EnableBossEncounterSync.Value; } catch { return false; } } } private static bool LogOn { get { try { return Plugin.Cfg.LogBossEncounter.Value; } catch { return false; } } } private static float ContinuationGraceSeconds { get { try { return Mathf.Max(0f, Plugin.Cfg.BossContinuationGraceSeconds.Value); } catch { return 5f; } } } private static bool GateFightActive { get { try { return Plugin.Cfg.GateBossFightOnDialogClose.Value; } catch { return false; } } } private static bool DeferIntroArmActive { get { try { return Plugin.Cfg.DeferBossIntroArm.Value; } catch { return false; } } } private static bool RoomMembershipActive { get { try { return Plugin.Cfg.EnableBossRoomMembership.Value; } catch { return false; } } } private static bool InReentry { get { lock (_lock) { return _reentryDepth > 0; } } } private static bool PreFightLogOn { get { try { return Plugin.Cfg.LogBossPreFight.Value; } catch { return false; } } } private static bool CutsceneGateActive { get { try { return Enabled && Plugin.Cfg.GateBossDialogToInRoom.Value && Plugin.Cfg.EnableFaithfulBossIntro.Value; } catch { return false; } } } public static bool IsSuppressingBossCutscene => _suppressBossCutscene; private static bool LuciaEyeEnabled { get { try { return Plugin.Cfg.EnableLuciaEyeAuthority.Value; } catch { return false; } } } private static bool LuciaDeathEnabled { get { try { return Plugin.Cfg.EnableLuciaDeathAuthority.Value; } catch { return false; } } } private static bool WitchPhaseAuthorityEnabled { get { try { return Plugin.Cfg.EnableWitchPhaseAuthority.Value; } catch { return false; } } } private static bool WitchP2ManifestEnabled { get { try { return Plugin.Cfg.EnableWitchPhase2Manifest.Value; } catch { return false; } } } public static void Reset(bool fullSession = true) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_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) lock (_lock) { _registry.Clear(); _hostBroadcast.Clear(); _hostCombatEntryBroadcast.Clear(); _hostSandstormBroadcast.Clear(); _sandstormSeen.Clear(); _desertAimRotation = -1; _desertArenaTarget = new Vector3(float.PositiveInfinity, 0f, 0f); _desertArenaLastSent = new Vector3(float.PositiveInfinity, 0f, 0f); _ghostRocketIds.Clear(); _ghostRocketMarkers.Clear(); _clientRequested.Clear(); _localIntroDialogKey = null; _localMidFightDialogKey = null; _applyingHostDialogClose = false; _applyingHostDialogOpen = false; _introFinishRequested.Clear(); _bossOwnDialogLocallyOpen.Clear(); _pendingMirrorKey = null; _pendingMirrorEvent = null; TerrorbaumMechanicSync.OnLevelChanged(); _appliedStart.Clear(); _continuation.Clear(); _pendingVerify.Clear(); _pendingDialogFinalize.Clear(); _dialogCommitBroadcast.Clear(); _dialogCommitApplied.Clear(); _dialogInteractableRemoved.Clear(); _dialogOpenKey = null; if (fullSession) { _fightCommitted.Clear(); _fightCommitRequested.Clear(); _fightCommitBroadcast.Clear(); _dialogSessionActive.Clear(); _cutscenePlayed.Clear(); _localTeleportedIn.Clear(); _lastIntroCommit.Clear(); _introRanWhileOutOfRoom.Clear(); _roomMembers.Clear(); _roomMembersClientView.Clear(); _roomEnterReported.Clear(); } _bossUiAttached.Clear(); _lastHitVisualAt.Clear(); _terminalDead.Clear(); _cousinFightLoopStarted.Clear(); _introArmReplayed.Clear(); _preFightLogged.Clear(); _luciaEyeConsumed.Clear(); _luciaEyeCycleComplete.Clear(); _pendingEyeComplete.Clear(); _witchAppliedRevision.Clear(); _witchPhaseRevision = 0; _lastHostWitchPhase = -1; _pendingP2Manifest = null; _witchP2Cycle = 0; _witchP2Active = false; _witchP2RealHitConsumed = false; _witchP2DefeatedDomes.Clear(); _p2ClientAppliedCycle = -1; WitchBossControllerAdapter.ClearPhase2State(); TerrorbaumMechanicSync.OnLevelChanged(); if (fullSession) { _runScope = ""; } _reentryDepth = 0; } } public static void OnLevelChanged(string runScope) { //IL_006f: 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_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) lock (_lock) { if (!(_runScope == runScope)) { _runScope = runScope; _registry.Clear(); _hostBroadcast.Clear(); _hostCombatEntryBroadcast.Clear(); _hostSandstormBroadcast.Clear(); _sandstormSeen.Clear(); _desertAimRotation = -1; _desertArenaTarget = new Vector3(float.PositiveInfinity, 0f, 0f); _desertArenaLastSent = new Vector3(float.PositiveInfinity, 0f, 0f); _ghostRocketIds.Clear(); _ghostRocketMarkers.Clear(); _clientRequested.Clear(); _localIntroDialogKey = null; _localMidFightDialogKey = null; _applyingHostDialogClose = false; _applyingHostDialogOpen = false; _introFinishRequested.Clear(); _bossOwnDialogLocallyOpen.Clear(); _pendingMirrorKey = null; _pendingMirrorEvent = null; TerrorbaumMechanicSync.OnLevelChanged(); _appliedStart.Clear(); _continuation.Clear(); _pendingVerify.Clear(); _pendingDialogFinalize.Clear(); _dialogCommitBroadcast.Clear(); _dialogCommitApplied.Clear(); _dialogInteractableRemoved.Clear(); _dialogSessionActive.Clear(); _cutscenePlayed.Clear(); _localTeleportedIn.Clear(); _lastIntroCommit.Clear(); _introRanWhileOutOfRoom.Clear(); _dialogOpenKey = null; _fightCommitted.Clear(); _fightCommitRequested.Clear(); _fightCommitBroadcast.Clear(); _roomMembers.Clear(); _roomMembersClientView.Clear(); _roomEnterReported.Clear(); _bossUiAttached.Clear(); _lastHitVisualAt.Clear(); _terminalDead.Clear(); _cousinFightLoopStarted.Clear(); _introArmReplayed.Clear(); _preFightLogged.Clear(); _luciaEyeConsumed.Clear(); _luciaEyeCycleComplete.Clear(); _pendingEyeComplete.Clear(); _witchAppliedRevision.Clear(); _witchPhaseRevision = 0; _lastHostWitchPhase = -1; _pendingP2Manifest = null; _witchP2Cycle = 0; _witchP2Active = false; _witchP2RealHitConsumed = false; _witchP2DefeatedDomes.Clear(); _p2ClientAppliedCycle = -1; WitchBossControllerAdapter.ClearPhase2State(); } } } private static void BeginApply() { lock (_lock) { _reentryDepth++; } } private static void EndApply() { lock (_lock) { if (_reentryDepth > 0) { _reentryDepth--; } } } private static void OpenContinuation(string key, IBossEncounterAdapter adapter, object component, string openedBy) { lock (_lock) { _continuation[key] = new ContinuationWindow { Adapter = adapter, Component = component, OpenedBy = openedBy, OpenedAt = Time.realtimeSinceStartup, LastActivityAt = Time.realtimeSinceStartup }; } } private static bool IsContinuationActive(string key, out ContinuationWindow window) { lock (_lock) { if (!_continuation.TryGetValue(key, out window)) { return false; } if (window.StartObserved && Time.realtimeSinceStartup - window.StartObservedAt > ContinuationGraceSeconds) { _continuation.Remove(key); return false; } return true; } } private static bool SafeStarted(IBossEncounterAdapter adapter, object component) { try { return adapter.IsStarted(component); } catch { return false; } } public static bool ShouldSuppressClientBossCombat(object bossHelper) { try { if (!Enabled) { return false; } if (NetGameplaySyncBridge.BossMode != NetMode.Client) { return false; } bool value; return BossReflect.TryGetBool(bossHelper, "fightStarted", out value) && value; } catch { return false; } } public static bool ShouldSuppressClientBossReposition() { try { return Enabled && (NetGameplaySyncBridge.BossMode == NetMode.Client || NetGameplaySyncBridge.BossMode == NetMode.Host); } catch { return false; } } public static void OnHostBossDialogInteract(object npc) { try { if (!Enabled || NetGameplaySyncBridge.BossMode != NetMode.Host || npc == null) { return; } string text = null; IBossEncounterAdapter bossEncounterAdapter = null; object obj = null; lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; object component = value.Component; Object val = (Object)((component is Object) ? component : null); if (val != null && !(val == (Object)null)) { object obj2 = null; try { obj2 = value.Adapter.GetHealthUnit(value.Component); } catch { } if (obj2 != null && obj2 == npc) { text = item.Key; bossEncounterAdapter = value.Adapter; obj = value.Component; break; } } } } if (bossEncounterAdapter == null && TryResolveAndRegisterBossOwnDialog(npc, out string key, out IBossEncounterAdapter adapter, out object component2)) { text = key; bossEncounterAdapter = adapter; obj = component2; } if (bossEncounterAdapter != null && obj != null && text != null) { bool flag = SafeStarted(bossEncounterAdapter, obj); bool flag2 = false; try { flag2 = bossEncounterAdapter.BroadcastsBossOwnDialog(obj); } catch { } if ((flag || flag2) && !(flag && flag2)) { BroadcastHostBossOwnDialogOpen(text, bossEncounterAdapter, obj, hostHasItOpen: true); } } } catch (Exception ex) { Plugin.Log.Warn("[BossDialogSync] OnHostBossDialogInteract failed: " + ex.GetType().Name + ": " + ex.Message); } } private static void BroadcastHostBossOwnDialogOpen(string key, IBossEncounterAdapter adapter, object component, bool hostHasItOpen) { lock (_lock) { _desertDialogOpenKey = key; if (hostHasItOpen) { _bossOwnDialogLocallyOpen.Add(key); } } if (adapter.TryGetActiveMidFightDialogId(component, out string dialogId) && !string.IsNullOrEmpty(dialogId)) { NetGameplaySyncBridge.BroadcastHostBossDiscreteEvent(new NetBossDiscreteEvent { EncounterKey = key, EventName = "Dialog:" + dialogId }); Plugin.Log.Info($"[BossDialogSync] host broadcast OPEN dialog={dialogId} key={key} hostHasItOpen={hostHasItOpen}"); } } public static void OnHostBossDialogClosed() { try { if (!Enabled || NetGameplaySyncBridge.BossMode != NetMode.Host) { return; } string desertDialogOpenKey; lock (_lock) { desertDialogOpenKey = _desertDialogOpenKey; _desertDialogOpenKey = null; if (desertDialogOpenKey != null) { _bossOwnDialogLocallyOpen.Remove(desertDialogOpenKey); } } if (desertDialogOpenKey != null) { NetGameplaySyncBridge.BroadcastHostBossDiscreteEvent(new NetBossDiscreteEvent { EncounterKey = desertDialogOpenKey, EventName = "DialogClose" }); Plugin.Log.Info("[BossDialogSync] host broadcast CLOSE key=" + desertDialogOpenKey); } } catch (Exception ex) { Plugin.Log.Warn("[BossDialogSync] OnHostBossDialogClosed failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void OnLocalDialogSpeakableChanged(object? speakable) { try { if (!Enabled) { return; } NetMode bossMode = NetGameplaySyncBridge.BossMode; if (bossMode != NetMode.Host && bossMode != NetMode.Client) { return; } if (speakable != null) { if (!TryMatchSpeakableToBoss(speakable, out string key, out bool started)) { return; } bool flag; lock (_lock) { if (started) { flag = _localMidFightDialogKey != key; _localMidFightDialogKey = key; } else { flag = _localIntroDialogKey != key; _localIntroDialogKey = key; } } if (flag && LogOn) { Plugin.Log.Info(string.Format("[BossDialogSync] local {0} dialog open key={1} mode={2}", started ? "mid-fight" : "intro", key, bossMode)); } return; } string localIntroDialogKey; string localMidFightDialogKey; bool applyingHostDialogClose; lock (_lock) { localIntroDialogKey = _localIntroDialogKey; localMidFightDialogKey = _localMidFightDialogKey; _localIntroDialogKey = null; _localMidFightDialogKey = null; applyingHostDialogClose = _applyingHostDialogClose; } if (!applyingHostDialogClose) { if (localIntroDialogKey != null) { OnLocalBossIntroDialogFinished(localIntroDialogKey); } else if (localMidFightDialogKey != null) { OnLocalBossMidFightDialogClosed(localMidFightDialogKey); } } } catch (Exception ex) { Plugin.Log.Warn("[BossDialogSync] OnLocalDialogSpeakableChanged failed: " + ex.GetType().Name + ": " + ex.Message); } } private static bool TryMatchSpeakableToBoss(object speakable, out string key, out bool started) { key = ""; started = false; object? obj = BossReflect.GetMember(speakable, "unit") ?? BossReflect.GetMember(speakable, "npc"); object? obj2 = ((obj is Component) ? obj : null); Transform val = ((obj2 != null) ? ((Component)obj2).transform.root : null); if ((Object)(object)val == (Object)null) { return false; } lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; if (value.Adapter == null || value.Component == null) { continue; } try { if (value.Adapter.RunsLocalIntroPresentation(value.Component)) { object? healthUnit = value.Adapter.GetHealthUnit(value.Component); Component val2 = (Component)((healthUnit is Component) ? healthUnit : null); if ((Object)(object)val2 != (Object)null && (Object)(object)val2.transform.root == (Object)(object)val) { key = item.Key; started = SafeStarted(value.Adapter, value.Component); return true; } } } catch { } } } return false; } private static void OnLocalBossMidFightDialogClosed(string key) { if (NetGameplaySyncBridge.BossMode == NetMode.Client) { bool flag = false; try { flag = NetClientJoinFlow.SessionJoinedHost; } catch { } if (flag && TryFindLocalEncounter(key, out IBossEncounterAdapter _, out object component) && TryBuildContext(out BossEncounterContext ctx, out string _)) { NetBossDialogCommit netBossDialogCommit = BuildDialogCommit(key, component, in ctx, "midfight-close"); Plugin.Log.Info("[BossDialogSync] client dismissed mid-fight dialog → request host close " + netBossDialogCommit.ToCompact()); NetGameplaySyncBridge.SendClientBossDialogCommitRequest(netBossDialogCommit); } } } private static void OnLocalBossIntroDialogFinished(string key) { if (NetGameplaySyncBridge.BossMode != NetMode.Client || !TryFindLocalEncounter(key, out IBossEncounterAdapter adapter, out object component)) { return; } try { if (!adapter.RunsLocalIntroPresentation(component)) { return; } } catch { return; } if (SafeStarted(adapter, component)) { return; } bool flag = false; try { flag = NetClientJoinFlow.SessionJoinedHost; } catch { } if (!flag) { return; } lock (_lock) { if (!_introFinishRequested.Add(key)) { return; } } if (TryBuildContext(out BossEncounterContext ctx, out string _)) { NetBossDialogCommit netBossDialogCommit = BuildDialogCommit(key, component, in ctx, "intro-finish"); Plugin.Log.Info("[BossDialogSync] client read intro to end → request host commit " + netBossDialogCommit.ToCompact()); NetGameplaySyncBridge.SendClientBossDialogCommitRequest(netBossDialogCommit); } } private static void CommitHostIntroDialog(string key, IBossEncounterAdapter adapter, object component, string reason) { try { if (SafeStarted(adapter, component)) { if (LogOn) { Plugin.Log.Info("[BossEncounter] host intro-finish no-op (already started) key=" + key + " reason=" + reason); } return; } string detail; bool flag = BossDialogReflect.IsDialogActive() && BossDialogReflect.TryFinalizeCurrentDialog(out detail); string detail2; bool flag2 = BossReflect.TryInvoke(component, "Anim_OnTriggerSandstorm", out detail2); float delay = 8f; try { if (BossReflect.GetMember(component, "sandstormLoopLength") is float num && num > 0f) { delay = num; } } catch { } bool flag3 = false; MonoBehaviour val = (MonoBehaviour)((component is MonoBehaviour) ? component : null); if (val != null && (Object)(object)val != (Object)null) { val.StartCoroutine(DelayedCombatEntry(key, adapter, component, delay)); flag3 = true; } else { BossReflect.TryInvoke(component, "TriggerFight", out detail); } Plugin.Log.Info(string.Format("[BossEncounter] host committed intro key={0} reason={1} dialogClosed={2} sandstorm={3}({4}) combatEntryDelay={5}", key, reason, flag, flag2, detail2, flag3 ? (delay + "s") : "immediate")); } catch (Exception ex) { Plugin.Log.Warn("[BossEncounter] CommitHostIntroDialog failed key=" + key + ": " + ex.GetType().Name + ": " + ex.Message); } } private static IEnumerator DelayedCombatEntry(string key, IBossEncounterAdapter adapter, object component, float delay) { yield return (object)new WaitForSeconds(delay); bool flag = true; try { flag = SafeStarted(adapter, component); } catch { } if (flag) { if (LogOn) { Plugin.Log.Info("[BossEncounter] delayed combat-entry no-op (already started) key=" + key); } yield break; } try { BossReflect.TryInvoke(component, "TriggerFight", out string detail); Plugin.Log.Info("[BossEncounter] host delayed combat-entry (post-sandstorm) key=" + key + ": " + detail); } catch (Exception ex) { Plugin.Log.Warn("[BossEncounter] DelayedCombatEntry failed key=" + key + ": " + ex.GetType().Name + ": " + ex.Message); } } private static IEnumerator FastForwardHostMidFightDialog(string key, string peerId) { int frames = 0; int advances = 0; int picks = 0; while (BossDialogReflect.IsDialogActive() && frames < 300) { if (BossDialogReflect.IsActiveDialogInChoices()) { if (BossDialogReflect.TrySelectActiveDialogOption(0)) { picks++; } for (int w = 0; w < 20; w++) { if (frames >= 300) { break; } if (!BossDialogReflect.IsDialogActive()) { break; } if (!BossDialogReflect.IsActiveDialogInChoices()) { break; } frames++; yield return null; } } else if (BossDialogReflect.TryAdvanceActiveDialog()) { advances++; } frames++; yield return null; } bool flag = !BossDialogReflect.IsDialogActive(); if (!flag) { try { BossDialogReflect.TryFinalizeCurrentDialog(out string _); } catch { } } Plugin.Log.Info($"[BossDialogSync] host fast-forwarded mid-fight dialog on client request from {peerId} key={key}: advances={advances} picks={picks} frames={frames} natural={flag}"); } public static bool OnLocalBossSandstorm(object component) { try { if (!Enabled || component == null) { return true; } if (!TryGetEncounterKeyForBoss(component, out string key, out string bossType)) { return true; } bool flag; lock (_lock) { flag = _sandstormSeen.Add(key); } if (!flag) { if (LogOn) { Plugin.Log.Info("[BossPhaseAction] sandstorm suppressed (already played) key=" + key); } return false; } if (NetGameplaySyncBridge.BossMode == NetMode.Host) { bool flag2; lock (_lock) { flag2 = _hostSandstormBroadcast.Add(key); } if (flag2) { NetGameplaySyncBridge.BroadcastHostBossDiscreteEvent(new NetBossDiscreteEvent { EncounterKey = key, BossType = bossType, EventName = "Sandstorm" }); Plugin.Log.Info("[BossPhaseAction] host broadcast Sandstorm key=" + key); } } return true; } catch (Exception ex) { Plugin.Log.Warn("[BossPhaseAction] OnLocalBossSandstorm failed: " + ex.GetType().Name + ": " + ex.Message); return true; } } public static void UpdateBossPikeVisual() { try { if (!Enabled || Time.frameCount == _pikeVisualFrame) { return; } _pikeVisualFrame = Time.frameCount; BossFightHelperAdapter bossFightHelperAdapter = null; object obj = null; lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; if (!(value.Adapter is BossFightHelperAdapter bossFightHelperAdapter2) || value.Component == null) { continue; } try { if (!bossFightHelperAdapter2.RunsLocalIntroPresentation(value.Component) || !SafeStarted(bossFightHelperAdapter2, value.Component)) { continue; } goto IL_009e; } catch { } continue; IL_009e: bossFightHelperAdapter = bossFightHelperAdapter2; obj = value.Component; break; } } if (bossFightHelperAdapter != null && obj != null) { bossFightHelperAdapter.EnsureBossPikeVisual(obj); ProbeDesertArena(bossFightHelperAdapter, obj); ProbeDesertPike(obj); ProbeDesertMissile(obj); NetGameplayProbeManager.ProbeRuntimeSpawnBindings(); } } catch { } } private static void ProbeDesertArena(BossFightHelperAdapter bfa, object comp) { //IL_00f5: 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_008c: 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_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_010d: 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_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) try { if (!bfa.TryGetSandstormArenaSphere(comp, out var center, out var radius)) { if (LogOn && Time.realtimeSinceStartup >= _desertArenaProbeNext) { _desertArenaProbeNext = Time.realtimeSinceStartup + 0.5f; Plugin.Log.Info($"[DesertArena] mode={NetGameplaySyncBridge.BossMode} sphere=UNRESOLVED"); } return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (NetGameplaySyncBridge.BossMode == NetMode.Host && realtimeSinceStartup >= _desertArenaSendNext && (float.IsInfinity(_desertArenaLastSent.x) || Vector3.Distance(center, _desertArenaLastSent) > 0.2f)) { _desertArenaLastSent = center; _desertArenaSendNext = realtimeSinceStartup + 0.1f; if (TryGetEncounterKeyForBoss(comp, out string key, out string bossType)) { NetGameplaySyncBridge.BroadcastHostBossDiscreteEvent(new NetBossDiscreteEvent { EncounterKey = key, BossType = bossType, EventName = "ArenaPos", HasPos = true, Position = center }); } } if (NetGameplaySyncBridge.BossMode == NetMode.Client && !float.IsInfinity(_desertArenaTarget.x)) { float num = Vector3.Distance(center, _desertArenaTarget); if (num > 25f) { bfa.TrySetArenaCenter(comp, _desertArenaTarget); } else if (num > 0.01f) { bfa.TrySetArenaCenter(comp, Vector3.MoveTowards(center, _desertArenaTarget, 6f * Time.deltaTime)); } } if (LogOn && realtimeSinceStartup >= _desertArenaProbeNext) { _desertArenaProbeNext = realtimeSinceStartup + 0.5f; float num2 = (float.IsInfinity(_desertArenaLastPos.x) ? 0f : Vector3.Distance(center, _desertArenaLastPos)); _desertArenaLastPos = center; Plugin.Log.Info($"[DesertArena] mode={NetGameplaySyncBridge.BossMode} center={center:F2} radius={radius:F2} movedSinceLast={num2:F3}"); } } catch { } } private static void ProbeDesertMissile(object comp) { if (!LogOn) { return; } try { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup < _desertMissileProbeNext)) { _desertMissileProbeNext = realtimeSinceStartup + 0.5f; NetMode bossMode = NetGameplaySyncBridge.BossMode; object phase = BossReflect.GetMember(comp, "BossPhaseIndex") ?? "?"; LogOneMissileBase(bossMode, phase, BossReflect.GetMember(comp, "sniperBase"), "Sniper"); LogOneMissileBase(bossMode, phase, BossReflect.GetMember(comp, "terminatorBase"), "Terminator"); } } catch (Exception ex) { Plugin.Log.Warn("[DesertMissile] probe failed: " + ex.GetType().Name + ": " + ex.Message); } } private static void LogOneMissileBase(NetMode mode, object phase, object mb, string label) { Object val = (Object)((mb is Object) ? mb : null); if (val == null || val == (Object)null) { Plugin.Log.Info($"[DesertMissile] mode={mode} phase={phase} base={label} = null"); return; } object member = BossReflect.GetMember(mb, "isFiringMissiles"); bool flag = default(bool); int num; if (member is bool) { flag = (bool)member; num = 1; } else { num = 0; } bool flag2 = (byte)((uint)num & (flag ? 1u : 0u)) != 0; member = BossReflect.GetMember(mb, "incomingPatternMissiles"); bool flag3 = default(bool); int num2; if (member is bool) { flag3 = (bool)member; num2 = 1; } else { num2 = 0; } bool flag4 = (byte)((uint)num2 & (flag3 ? 1u : 0u)) != 0; int num3 = ((BossReflect.GetMember(mb, "AliveMissile") is int num4) ? num4 : (-1)); object? member2 = BossReflect.GetMember(mb, "followUnit"); Object val2 = (Object)((member2 is Object) ? member2 : null); string text = ((val2 != null && val2 != (Object)null) ? val2.name : "null"); MonoBehaviour val3 = (MonoBehaviour)((mb is MonoBehaviour) ? mb : null); bool flag5 = val3 != null && ((Behaviour)val3).enabled; Plugin.Log.Info($"[DesertMissile] mode={mode} phase={phase} base={label} enabled={flag5} firing={flag2} pattern={flag4} alive={num3} follow={text}"); } private static void ProbeDesertPike(object comp) { //IL_016d: Unknown result type (might be due to invalid IL or missing references) if (!LogOn) { return; } try { float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _desertPikeProbeNext) { return; } _desertPikeProbeNext = realtimeSinceStartup + 0.5f; NetMode bossMode = NetGameplaySyncBridge.BossMode; object obj = BossReflect.GetMember(comp, "BossPhaseIndex") ?? "?"; object? member = BossReflect.GetMember(comp, "spawnedBossPike"); Component val = (Component)((member is Component) ? member : null); if (val == null || (Object)(object)val == (Object)null) { Plugin.Log.Info($"[DesertPike] mode={bossMode} phase={obj} bossPike=null"); return; } if (_pikeCarrierType == null) { _pikeCarrierType = AccessTools.TypeByName("PerfectRandom.Sulfur.Gameplay.DesertPikeCarrier"); } Component val2 = ((_pikeCarrierType != null) ? val.GetComponent(_pikeCarrierType) : null); bool flag = default(bool); int num; if ((Object)(object)val2 != (Object)null) { object member2 = BossReflect.GetMember(val2, "isJumping"); if (member2 is bool) { flag = (bool)member2; num = 1; } else { num = 0; } } else { num = 0; } bool flag2 = (byte)((uint)num & (flag ? 1u : 0u)) != 0; bool flag3 = false; int num2 = 0; if (BossReflect.GetMember(val2, "pikeRenderers") is Renderer[] array) { num2 = array.Length; Renderer[] array2 = array; foreach (Renderer val3 in array2) { if ((Object)(object)val3 != (Object)null && val3.enabled) { flag3 = true; break; } } } Plugin.Log.Info($"[DesertPike] mode={bossMode} phase={obj} pikePos={val.transform.position:F1} isJumping={flag2} anyRendEnabled={flag3}({num2})"); } catch (Exception ex) { Plugin.Log.Warn("[DesertPike] probe failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void OnHostBossPikeActivateShooting(object carrier) { try { if (!Enabled || NetGameplaySyncBridge.BossMode != NetMode.Host || carrier == null || !TryGetRegisteredBossOnCarrier(carrier, out object bossNpc)) { return; } object member = BossReflect.GetMember(bossNpc, "weapon"); bool flag = default(bool); int num; if (member != null) { object member2 = BossReflect.GetMember(member, "bIsTriggerActive"); if (member2 is bool) { flag = (bool)member2; num = 1; } else { num = 0; } } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { return; } List list = CousinArmPatches.GatherPlayerUnits(); if (list.Count == 0) { return; } bool flag2 = false; bool gated = false; HashSet members = null; try { flag2 = Plugin.Cfg.ExcludeOutOfRoomPlayersFromBossAttacks.Value; if (flag2) { gated = ArenaLockdownManager.TryGetActiveArenaInRoom(out members); } } catch { } object obj2 = null; int count = list.Count; for (int i = 0; i < count; i++) { int num2 = (_desertAimRotation + 1 + i) % count; object obj3 = list[num2]; if (obj3 != null) { Object val = (Object)((obj3 is Object) ? obj3 : null); if ((val == null || !(val == (Object)null)) && (!flag2 || CousinArmPatches.IsTargetAttackable(obj3, gated, members))) { obj2 = obj3; _desertAimRotation = num2; break; } } } if (obj2 == null) { return; } object obj4 = BossReflect.GetMember(bossNpc, "AiAgent") ?? BossReflect.GetMember(bossNpc, "aiAgent"); FieldInfo fieldInfo = obj4?.GetType().GetField("target", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (obj4 != null && !(fieldInfo == null)) { fieldInfo.SetValue(obj4, obj2); float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup >= _desertAimLogNext) { _desertAimLogNext = realtimeSinceStartup + 1f; STLogger log = Plugin.Log; object obj5 = ((obj2 is Component) ? obj2 : null); log.Info($"[BossTargetRotate] pike burst aimed at {((obj5 != null) ? ((Object)obj5).name : null) ?? obj2.ToString()} (slot {_desertAimRotation + 1}/{count})"); } } } catch (Exception ex) { Plugin.Log.Warn("[BossTargetRotate] OnHostBossPikeActivateShooting failed: " + ex.GetType().Name + ": " + ex.Message); } } private static bool TryGetRegisteredBossOnCarrier(object carrier, out object bossNpc) { string key; string bossType; return TryGetRegisteredBossOnCarrier(carrier, out bossNpc, out key, out bossType); } private static bool TryGetRegisteredBossOnCarrier(object carrier, out object bossNpc, out string key, out string bossType) { bossNpc = null; key = ""; bossType = ""; try { if (!(carrier.GetType().GetField("attachedUnits", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(carrier) is IList { Count: not 0 } list)) { return false; } foreach (object item in list) { if (item == null) { continue; } object obj = ((item is Component) ? item : null); Transform val = ((obj != null) ? ((Component)obj).transform.root : null); if ((Object)(object)val == (Object)null) { continue; } lock (_lock) { foreach (KeyValuePair item2 in _registry) { Entry value = item2.Value; if (value.Adapter == null || value.Component == null) { continue; } try { if (!value.Adapter.RunsLocalIntroPresentation(value.Component)) { continue; } } catch { continue; } object? healthUnit = value.Adapter.GetHealthUnit(value.Component); Component val2 = (Component)((healthUnit is Component) ? healthUnit : null); if ((Object)(object)val2 != (Object)null && (Object)(object)val2.transform.root == (Object)(object)val) { bossNpc = item; key = item2.Key; bossType = value.Component.GetType().Name; return true; } } } } } catch { } return false; } public static void OnHostBossPikeJumpStarted(object carrier) { //IL_005f: 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_005b: 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_0066: 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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0093: 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_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: 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_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_0194: 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_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) try { if (!Enabled || NetGameplaySyncBridge.BossMode != NetMode.Host || carrier == null) { return; } string eventName = "PikeJump"; if (TryGetRegisteredBossOnCarrier(carrier, out object _, out string key, out string bossType) || TryGetDesertEnemyPikeJumpEvent(carrier, out eventName, out key, out bossType)) { Vector3 val = ((BossReflect.GetMember(carrier, "startPos") is Vector3 val2) ? val2 : Vector3.zero); Vector3 val3 = ((BossReflect.GetMember(carrier, "posToJumpTowards") is Vector3 val4) ? val4 : Vector3.zero); Vector3 val5 = ((BossReflect.GetMember(carrier, "finalTarget") is Vector3 val6) ? val6 : val3); float jumpAirTimer = ((BossReflect.GetMember(carrier, "jumpAirTimer") is float num) ? num : 1f); float jumpHeight = ((BossReflect.GetMember(carrier, "jumpHeight") is float num2) ? num2 : 1f); float jumpSpawnPct = ((BossReflect.GetMember(carrier, "jumpSpawnPercentage") is float num3) ? num3 : 0.5f); int num4; lock (_lock) { num4 = ++_pikeJumpSeq; } NetGameplaySyncBridge.BroadcastHostBossDiscreteEvent(new NetBossDiscreteEvent { EncounterKey = key, BossType = bossType, EventName = eventName, HasPos = true, Position = val3, Seq = num4, HasJump = true, JumpStart = val, JumpAirTimer = jumpAirTimer, JumpHeight = jumpHeight, JumpDepth = val3.y - val5.y, JumpSpawnPct = jumpSpawnPct }); if (LogOn) { Plugin.Log.Info($"[PikeJumpSync] host broadcast {eventName} seq={num4} start={val:F1} target={val3:F1}"); } } } catch (Exception ex) { Plugin.Log.Warn("[PikeJumpSync] OnHostBossPikeJumpStarted failed: " + ex.GetType().Name + ": " + ex.Message); } } private static bool TryGetDesertEnemyPikeJumpEvent(object carrier, out string eventName, out string key, out string bossType) { eventName = ""; key = ""; bossType = ""; object member = BossReflect.GetMember(carrier, "owner"); Object val = (Object)((member is Object) ? member : null); if (val == null || val == (Object)null) { return false; } if (!(BossReflect.ReadUnitId(member) ?? "").StartsWith("HellshrewPike", StringComparison.Ordinal)) { return false; } int spawnIndexForObject = NetGameplayProbeManager.GetSpawnIndexForObject(member); if (spawnIndexForObject <= 0) { return false; } object obj = null; lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; if (!(value.Adapter is BossFightHelperAdapter bossFightHelperAdapter) || value.Component == null) { continue; } try { if (bossFightHelperAdapter.RunsLocalIntroPresentation(value.Component) && SafeStarted(bossFightHelperAdapter, value.Component)) { obj = value.Component; break; } } catch { } } } if (obj == null || !TryGetEncounterKeyForBoss(obj, out key, out bossType)) { return false; } eventName = "PikeJump:" + spawnIndexForObject; return true; } public static bool ShouldBlockClientBossPikeJump(object carrier) { try { if (!Enabled || NetGameplaySyncBridge.BossMode != NetMode.Client || carrier == null) { return false; } if (InReentry) { return false; } if (!IsRegisteredBossPikeCarrier(carrier)) { object member = BossReflect.GetMember(carrier, "owner"); if (member == null || !NetGameplayProbeManager.IsRuntimeMirroredEntity(member)) { return false; } if (LogOn) { Plugin.Log.Info("[PikeJumpSync] client blocked local MIRRORED-pike jump (host-authoritative)"); } return true; } if (LogOn) { Plugin.Log.Info("[PikeJumpSync] client blocked local boss-pike jump (host-authoritative)"); } return true; } catch { return false; } } private static bool TryGetBossMissileBase(object missileBase, out string label, out object? component) { label = ""; component = null; if (!(missileBase is Object) || missileBase == null) { return false; } lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; if (!(value.Adapter is BossFightHelperAdapter) || value.Component == null) { continue; } try { if (SafeStarted(value.Adapter, value.Component)) { if (BossReflect.GetMember(value.Component, "sniperBase") == missileBase) { label = "Sniper"; component = value.Component; return true; } if (BossReflect.GetMember(value.Component, "terminatorBase") == missileBase) { label = "Terminator"; component = value.Component; return true; } } } catch { } } } return false; } public static bool ShouldBlockClientMissileStart(object missileBase) { try { if (!Enabled || NetGameplaySyncBridge.BossMode != NetMode.Client || missileBase == null) { return false; } if (InReentry) { return false; } if (!TryGetBossMissileBase(missileBase, out string _, out object _)) { return false; } if (LogOn) { Plugin.Log.Info("[DesertMissile] client blocked local missile start (host-authoritative)"); } return true; } catch { return false; } } public static void OnHostMissileStart(object missileBase) { try { if (Enabled && NetGameplaySyncBridge.BossMode == NetMode.Host && missileBase != null && TryGetBossMissileBase(missileBase, out string label, out object component) && component != null && TryGetEncounterKeyForBoss(component, out string key, out string bossType)) { NetGameplaySyncBridge.BroadcastHostBossDiscreteEvent(new NetBossDiscreteEvent { EncounterKey = key, BossType = bossType, EventName = "MissileStart:" + label }); if (LogOn) { Plugin.Log.Info("[DesertMissile] host broadcast MissileStart:" + label); } } } catch (Exception ex) { Plugin.Log.Warn("[DesertMissile] OnHostMissileStart failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void OnHostMissilePatternInitiated(object missileBase) { try { if (Enabled && NetGameplaySyncBridge.BossMode == NetMode.Host && missileBase != null && TryGetBossMissileBase(missileBase, out string label, out object component) && component != null) { MonoBehaviour val = (MonoBehaviour)((missileBase is MonoBehaviour) ? missileBase : null); if (val != null && !((Object)(object)val == (Object)null) && TryGetEncounterKeyForBoss(component, out string key, out string bossType)) { val.StartCoroutine(BroadcastMissilePatternNextFrame(missileBase, label, key, bossType)); } } } catch (Exception ex) { Plugin.Log.Warn("[DesertMissile] OnHostMissilePatternInitiated failed: " + ex.GetType().Name + ": " + ex.Message); } } private static IEnumerator BroadcastMissilePatternNextFrame(object missileBase, string label, string key, string bossType) { yield return null; Vector3 val = ((BossReflect.GetMember(missileBase, "patternDirection") is Vector3 val2) ? val2 : Vector3.zero); NetGameplaySyncBridge.BroadcastHostBossDiscreteEvent(new NetBossDiscreteEvent { EncounterKey = key, BossType = bossType, EventName = "MissilePattern:" + label, HasPos = true, Position = val }); Plugin.Log.Info($"[DesertMissile] host broadcast MissilePattern:{label} dir={val:F2}"); } public static void OnMissileLinePatternPre(object missileBase) { //IL_003b: 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_0043: 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_0074: 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_00c5: 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_0103: Unknown result type (might be due to invalid IL or missing references) try { if (Enabled && missileBase != null && TryGetBossMissileBase(missileBase, out string label, out object _)) { Vector3 val = ((BossReflect.GetMember(missileBase, "patternDirection") is Vector3 val2) ? val2 : Vector3.zero); int num = ((BossReflect.GetMember(missileBase, "patternFireCounter") is int num2) ? num2 : 0); int num3 = 17; num3 = num3 * 31 + Mathf.RoundToInt(val.x * 1000f); num3 = num3 * 31 + Mathf.RoundToInt(val.z * 1000f); num3 = num3 * 31 + num; num3 = num3 * 31 + ((label.Length > 0) ? label[0] : '\0'); _savedRngState = Random.state; _rngStateSaved = true; Random.InitState(num3); if (LogOn) { Plugin.Log.Info($"[DesertMissile] barrage volley reseeded base={label} seed={num3} (dir={val:F2} counter={num})"); } } } catch (Exception ex) { Plugin.Log.Warn("[DesertMissile] OnMissileLinePatternPre failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void OnMissileLinePatternPost() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (!_rngStateSaved) { return; } _rngStateSaved = false; try { Random.state = _savedRngState; } catch { } } public static void OnHostMissileStop(object missileBase) { try { if (Enabled && NetGameplaySyncBridge.BossMode == NetMode.Host && missileBase != null && TryGetBossMissileBase(missileBase, out string label, out object component) && component != null && TryGetEncounterKeyForBoss(component, out string key, out string bossType)) { NetGameplaySyncBridge.BroadcastHostBossDiscreteEvent(new NetBossDiscreteEvent { EncounterKey = key, BossType = bossType, EventName = "MissileStop:" + label }); if (LogOn) { Plugin.Log.Info("[DesertMissile] host broadcast MissileStop:" + label); } } } catch (Exception ex) { Plugin.Log.Warn("[DesertMissile] OnHostMissileStop failed: " + ex.GetType().Name + ": " + ex.Message); } } private static void ApplyClientMissileWindow(NetBossDiscreteEvent msg, object component) { //IL_0109: Unknown result type (might be due to invalid IL or missing references) try { string text = msg.EventName ?? ""; string text2 = (string.Equals(text.Substring(text.IndexOf(':') + 1), "Sniper", StringComparison.Ordinal) ? "sniperBase" : "terminatorBase"); object member = BossReflect.GetMember(component, text2); Object val = (Object)((member is Object) ? member : null); if (val == null || val == (Object)null) { if (LogOn) { Plugin.Log.Info("[DesertMissile] client has no " + text2 + " to apply " + text); } return; } if (text.StartsWith("MissileStart:", StringComparison.Ordinal)) { MethodInfo method = member.GetType().GetMethod("StartMissiles", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return; } BeginApply(); try { method.Invoke(member, new object[1] { 3f }); } finally { EndApply(); } } else if (text.StartsWith("MissilePattern:", StringComparison.Ordinal) && msg.HasPos) { member.GetType().GetField("patternDirection", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(member, msg.Position); MethodInfo method2 = member.GetType().GetMethod("InitiateFirePattern", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method2 == null) { return; } BeginApply(); try { method2.Invoke(member, null); } finally { EndApply(); } } else { MethodInfo method3 = member.GetType().GetMethod("StopRockets", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method3 == null) { return; } ParameterInfo[] parameters = method3.GetParameters(); method3.Invoke(member, (parameters.Length == 0) ? null : new object[1] { false }); } if (LogOn) { Plugin.Log.Info("[DesertMissile] client applied " + text); } } catch (Exception ex) { Plugin.Log.Warn("[DesertMissile] ApplyClientMissileWindow failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void RegisterGhostRocket(Object rocket, GameObject? marker) { if (rocket == (Object)null) { return; } lock (_lock) { int instanceID = rocket.GetInstanceID(); _ghostRocketIds.Add(instanceID); if ((Object)(object)marker != (Object)null) { _ghostRocketMarkers[instanceID] = marker; } } } public static bool IsGhostRocket(object rocket) { try { Object val = (Object)((rocket is Object) ? rocket : null); if (val == null || val == (Object)null) { return false; } lock (_lock) { return _ghostRocketIds.Contains(val.GetInstanceID()); } } catch { return false; } } public static void OnRocketDestroyed(object rocket) { try { Object val = (Object)((rocket is Object) ? rocket : null); if (val == null || val == (Object)null) { return; } GameObject val2 = null; lock (_lock) { int instanceID = val.GetInstanceID(); _ghostRocketIds.Remove(instanceID); if (_ghostRocketMarkers.TryGetValue(instanceID, out GameObject value)) { val2 = value; _ghostRocketMarkers.Remove(instanceID); } } if ((Object)(object)val2 != (Object)null) { BossFightHelperAdapter.ReleaseGhostMarker(val2); } } catch { } } public static void OnMissileRealRocketFired(object missileBase) { try { if (!Enabled || missileBase == null) { return; } BossFightHelperAdapter bossFightHelperAdapter = null; lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; if (!(value.Adapter is BossFightHelperAdapter bossFightHelperAdapter2) || value.Component == null) { continue; } try { if (!SafeStarted(bossFightHelperAdapter2, value.Component) || (BossReflect.GetMember(value.Component, "sniperBase") != missileBase && BossReflect.GetMember(value.Component, "terminatorBase") != missileBase)) { continue; } bossFightHelperAdapter = bossFightHelperAdapter2; break; } catch { } } } bossFightHelperAdapter?.SpawnGhostVisualRockets(missileBase); } catch (Exception ex) { Plugin.Log.Warn("[DesertMissile] OnMissileRealRocketFired failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void OnRuntimeMirrorSpawned(object unit) { try { if (unit == null || NetGameplaySyncBridge.BossMode != NetMode.Client) { return; } string text = BossReflect.ReadUnitId(unit) ?? ""; if (!text.StartsWith("HellshrewPike", StringComparison.Ordinal)) { return; } Component val = (Component)((unit is Component) ? unit : null); if (val == null || (Object)(object)val == (Object)null) { return; } if (_pikeCarrierType == null) { _pikeCarrierType = AccessTools.TypeByName("PerfectRandom.Sulfur.Gameplay.DesertPikeCarrier"); } Component val2 = ((_pikeCarrierType != null) ? val.GetComponent(_pikeCarrierType) : null); if (!((Object)(object)val2 == (Object)null)) { try { ((object)val2).GetType().GetField("isLevelSpawning", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(val2, false); } catch { } BossReflect.TryInvokeBool(unit, "SetPhysicsEnabled", arg: false, out string _); try { ((object)val2).GetType().GetMethod("SetMovement", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(val2, new object[2] { unit, true }); } catch { } if (LogOn) { Plugin.Log.Info("[DesertAdds] mirrored enemy pike tamed (physics/movement off, trigger-pounce off) unit=" + text); } } } catch (Exception ex) { Plugin.Log.Warn("[DesertAdds] OnRuntimeMirrorSpawned failed: " + ex.GetType().Name + ": " + ex.Message); } } public static bool ShouldBlockClientPerimeterJumping() { try { return Enabled && NetGameplaySyncBridge.BossMode == NetMode.Client; } catch { return false; } } private static bool IsRegisteredBossPikeCarrier(object carrier) { if (!(carrier is Object) || carrier == null) { return false; } lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; if (!(value.Adapter is BossFightHelperAdapter) || value.Component == null) { continue; } try { if (!SafeStarted(value.Adapter, value.Component)) { continue; } object? member = BossReflect.GetMember(value.Component, "spawnedBossPike"); Component val = (Component)((member is Component) ? member : null); if (val == null || (Object)(object)val == (Object)null) { continue; } if (_pikeCarrierType == null) { _pikeCarrierType = AccessTools.TypeByName("PerfectRandom.Sulfur.Gameplay.DesertPikeCarrier"); } Component val2 = ((_pikeCarrierType != null) ? val.GetComponent(_pikeCarrierType) : null); if (!((Object)(object)val2 != (Object)null)) { continue; } if (val2 == carrier) { goto IL_010b; } if (val2 != null) { Component val3 = val2; Component val4 = (Component)((carrier is Component) ? carrier : null); if (val4 != null && val3.gameObject == val4.gameObject) { goto IL_010b; } } goto end_IL_005c; IL_010b: return true; end_IL_005c:; } catch { } } } return false; } private static void ApplyClientBossPikeJump(NetBossDiscreteEvent msg, object component) { //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0149: 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_0213: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) try { string text = msg.EventName ?? "PikeJump"; int num = text.IndexOf(':'); object runtimeObject; if (num >= 0 && int.TryParse(text.Substring(num + 1), out var result)) { if (!NetGameplayProbeManager.TryGetMirroredRuntimeObject(result, out runtimeObject) || runtimeObject == null) { if (LogOn) { Plugin.Log.Info($"[PikeJumpSync] no mirrored pike for hostIdx={result} — jump skipped"); } return; } } else { runtimeObject = BossReflect.GetMember(component, "spawnedBossPike"); } Component val = (Component)((runtimeObject is Component) ? runtimeObject : null); if (val == null || (Object)(object)val == (Object)null) { if (LogOn) { Plugin.Log.Info("[PikeJumpSync] client has no spawnedBossPike yet — jump skipped"); } return; } object obj = null; Component[] components = val.GetComponents(); foreach (Component val2 in components) { if (!((Object)(object)val2 == (Object)null) && ((object)val2).GetType().GetField("attachedUnits", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) != null) { obj = val2; break; } } if (obj == null) { if (LogOn) { Plugin.Log.Info("[PikeJumpSync] client pike has no DesertPikeCarrier — jump skipped"); } return; } Transform transform = ((Component)obj).transform; object? obj2 = BossReflect.GetMember(runtimeObject, "Rigidbody") ?? BossReflect.GetMember(runtimeObject, "rigidbody"); Rigidbody val3 = (Rigidbody)((obj2 is Rigidbody) ? obj2 : null); if ((Object)(object)val3 != (Object)null) { val3.position = msg.JumpStart; } transform.position = msg.JumpStart; MethodInfo method = obj.GetType().GetMethod("JumpTowards", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { Plugin.Log.Warn("[PikeJumpSync] JumpTowards not found on carrier"); return; } method.Invoke(obj, new object[5] { msg.Position, msg.JumpAirTimer, msg.JumpHeight, msg.JumpDepth, msg.JumpSpawnPct }); if (LogOn) { Plugin.Log.Info($"[PikeJumpSync] client replayed {text} seq={msg.Seq} start={msg.JumpStart:F1} target={msg.Position:F1}"); } } catch (Exception ex) { Plugin.Log.Warn("[PikeJumpSync] ApplyClientBossPikeJump failed: " + ex.GetType().Name + ": " + ex.Message); } } public static bool IsDesertPikePuppetExempt() { RefreshDesertPuppetExemptions(); return _pikeExempt; } public static bool IsDesertBossBodyPuppetExempt() { RefreshDesertPuppetExemptions(); return _mountedExempt; } private static void RefreshDesertPuppetExemptions() { try { if (Time.frameCount == _puppetExemptFrame) { return; } _puppetExemptFrame = Time.frameCount; _pikeExempt = false; _mountedExempt = false; if (!Enabled || NetGameplaySyncBridge.BossMode != NetMode.Client) { return; } lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; if (value.Adapter == null || value.Component == null) { continue; } try { if (!value.Adapter.RunsLocalIntroPresentation(value.Component) || !SafeStarted(value.Adapter, value.Component)) { continue; } _pikeExempt = true; object? member = BossReflect.GetMember(value.Component, "bossNPC"); object? obj = ((member is Component) ? member : null); Transform val = ((obj != null) ? ((Component)obj).transform.parent : null); if ((Object)(object)val != (Object)null && ((Object)val).name.StartsWith("CarrySpot", StringComparison.Ordinal)) { _mountedExempt = true; } break; } catch { } } } } catch { } } public static void ProbeDesertVisibility() { try { if (!Enabled || !LogOn) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _desertVisProbeNext) { return; } _desertVisProbeNext = realtimeSinceStartup + 1.5f; NetMode bossMode = NetGameplaySyncBridge.BossMode; BossFightHelperAdapter bossFightHelperAdapter = null; object obj = null; lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; if (!(value.Adapter is BossFightHelperAdapter bossFightHelperAdapter2) || value.Component == null) { continue; } try { if (!bossFightHelperAdapter2.RunsLocalIntroPresentation(value.Component) || !SafeStarted(bossFightHelperAdapter2, value.Component)) { continue; } goto IL_00b2; } catch { } continue; IL_00b2: bossFightHelperAdapter = bossFightHelperAdapter2; obj = value.Component; break; } } if (bossFightHelperAdapter != null && obj != null) { bossFightHelperAdapter.LogDesertVisibility(obj, bossMode); } } catch { } } public static void OnHostBossPikeDismount(object component, bool jumping) { //IL_00a0: 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_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) try { if (!Enabled || NetGameplaySyncBridge.BossMode != NetMode.Host || component == null || !TryGetEncounterKeyForBoss(component, out string key, out string bossType)) { return; } string text = (jumping ? "BossJump" : "BossLand"); bool flag = false; Vector3 position = Vector3.zero; try { object? obj = ResolveAdapter(component)?.GetHealthUnit(component); Component val = (Component)((obj is Component) ? obj : null); if (val != null && (Object)(object)val != (Object)null) { position = val.transform.position; flag = true; } } catch { } NetGameplaySyncBridge.BroadcastHostBossDiscreteEvent(new NetBossDiscreteEvent { EncounterKey = key, BossType = bossType, EventName = text, HasPos = flag, Position = position }); Plugin.Log.Info("[BossPhaseAction] host broadcast " + text + " key=" + key + " pos=" + (flag ? ((Vector3)(ref position)).ToString("F1") : "?")); } catch (Exception ex) { Plugin.Log.Warn("[BossPhaseAction] OnHostBossPikeDismount failed: " + ex.GetType().Name + ": " + ex.Message); } } public static bool IsLocalIntroPresentationActive() { lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; if (value.Adapter == null || value.Component == null) { continue; } try { if (!value.Adapter.RunsLocalIntroPresentation(value.Component) || !_appliedStart.Contains(item.Key) || SafeStarted(value.Adapter, value.Component)) { continue; } return true; } catch { } } } return false; } private static string SafeDescribe(IBossEncounterAdapter adapter, object component) { try { return adapter.DescribeForLog(component); } catch (Exception ex) { return "describe-failed:" + ex.GetType().Name; } } private static void TouchContinuation(ContinuationWindow window) { window.LastActivityAt = Time.realtimeSinceStartup; if (window.StartObserved) { return; } try { if (window.Adapter.IsStarted(window.Component)) { window.StartObserved = true; window.StartObservedAt = Time.realtimeSinceStartup; } } catch { } } private static bool TryBuildContext(out BossEncounterContext ctx, out string runScope) { ctx = default(BossEncounterContext); runScope = ""; if (!NetRunStateBridge.TryGetLocalRunState(out NetRunState state) || !state.HasLevel) { return false; } ctx = new BossEncounterContext { ChapterName = state.ChapterName, LevelIndex = state.LevelIndex, HasSeed = state.HasLevelSeed, Seed = state.LevelSeed, GraphName = (state.LevelGenerator ?? ""), RunKey = string.Format("{0}:{1}:{2}", state.ChapterName, state.LevelIndex, state.HasLevelSeed ? state.LevelSeed.ToString() : "?") }; runScope = ctx.RunKey; return true; } private static IBossEncounterAdapter? ResolveAdapter(object component) { IBossEncounterAdapter[] adapters = _adapters; foreach (IBossEncounterAdapter bossEncounterAdapter in adapters) { try { if (bossEncounterAdapter.CanHandle(component)) { return bossEncounterAdapter; } } catch { } } return null; } public static string? DescribeComponent(object component) { IBossEncounterAdapter bossEncounterAdapter = ResolveAdapter(component); if (bossEncounterAdapter != null) { return SafeDescribe(bossEncounterAdapter, component); } return null; } public static bool TryGetEncounterKeyForBoss(object bossComponent, out string key, out string bossType) { key = ""; bossType = ""; try { if (!Enabled || bossComponent == null) { return false; } IBossEncounterAdapter bossEncounterAdapter = ResolveAdapter(bossComponent); if (bossEncounterAdapter == null) { return false; } if (!TryBuildContext(out BossEncounterContext ctx, out string runScope)) { return false; } OnLevelChanged(runScope); key = bossEncounterAdapter.BuildEncounterId(bossComponent, in ctx).Key; bossType = bossComponent.GetType().Name; Register(key, bossEncounterAdapter, bossComponent); return true; } catch { return false; } } private static bool TryResolveAndRegisterBossOwnDialog(object? npc, out string key, out IBossEncounterAdapter adapter, out object component) { //IL_006c: 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) key = ""; adapter = null; component = null; try { object obj = null; IBossEncounterAdapter bossEncounterAdapter = null; Component val = (Component)((npc is Component) ? npc : null); IEnumerable enumerable = ((val == null || !((Object)(object)val != (Object)null)) ? Resources.FindObjectsOfTypeAll() : ((Component)val.transform.root).GetComponentsInChildren(true)); foreach (MonoBehaviour item in enumerable) { if ((Object)(object)item == (Object)null) { continue; } if (npc == null) { GameObject gameObject = ((Component)item).gameObject; Scene scene = gameObject.scene; if (!((Scene)(ref scene)).IsValid() || !gameObject.activeInHierarchy) { continue; } } IBossEncounterAdapter bossEncounterAdapter2 = ResolveAdapter(item); if (bossEncounterAdapter2 == null) { continue; } bool flag = false; try { flag = bossEncounterAdapter2.BroadcastsBossOwnDialog(item); } catch { } if (!flag) { continue; } if (npc != null) { object obj3 = null; try { obj3 = bossEncounterAdapter2.GetHealthUnit(item); } catch { } if (obj3 == null || obj3 != npc) { continue; } } obj = item; bossEncounterAdapter = bossEncounterAdapter2; break; } if (obj == null || bossEncounterAdapter == null) { return false; } component = obj; adapter = bossEncounterAdapter; string bossType; return TryGetEncounterKeyForBoss(component, out key, out bossType); } catch { return false; } } private static void CloseBossOwnDialogOnFightStart(IBossEncounterAdapter adapter, object component, string key) { try { bool flag = false; try { flag = adapter.BroadcastsBossOwnDialog(component); } catch { } if (!flag) { return; } lock (_lock) { _bossOwnDialogLocallyOpen.Remove(key); if (_pendingMirrorKey == key) { _pendingMirrorKey = null; _pendingMirrorEvent = null; } } if (BossDialogReflect.IsDialogActive()) { string detail; bool flag2 = BossDialogReflect.TryFinalizeCurrentDialog(out detail); Plugin.Log.Info($"[BossDialogSync] closed opening dialog on fight start key={key} closed={flag2} ({detail})"); } } catch (Exception ex) { Plugin.Log.Warn("[BossDialogSync] CloseBossOwnDialogOnFightStart failed: " + ex.GetType().Name + ": " + ex.Message); } } public static bool TryGetEncounterKeyForDesertPerimeter(object owner, out string key, out string bossType) { key = ""; bossType = ""; try { if (Enabled) { Object val = (Object)((owner is Object) ? owner : null); if (val != null && !(val == (Object)null)) { object obj = null; lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; if (!(value.Adapter is BossFightHelperAdapter) || value.Component == null) { continue; } try { if (BossReflect.GetMember(value.Component, "desertClausePerimeter") == owner) { obj = value.Component; break; } } catch { } } } return obj != null && TryGetEncounterKeyForBoss(obj, out key, out bossType); } } return false; } catch { return false; } } public static bool TryGetRunContext(out string chapter, out int level, out bool hasSeed, out int seed) { chapter = ""; level = -1; hasSeed = false; seed = 0; if (!TryBuildContext(out BossEncounterContext ctx, out string _)) { return false; } chapter = ctx.ChapterName; level = ctx.LevelIndex; hasSeed = ctx.HasSeed; seed = ctx.Seed; return true; } public static bool OnLocalStartEntrypoint(object component, string source) { try { if (!Enabled || component == null) { return true; } if (InReentry) { return true; } IBossEncounterAdapter bossEncounterAdapter = ResolveAdapter(component); if (bossEncounterAdapter == null) { return true; } if (!TryBuildContext(out BossEncounterContext ctx, out string runScope)) { return true; } OnLevelChanged(runScope); NetBossEncounterId netBossEncounterId = bossEncounterAdapter.BuildEncounterId(component, in ctx); string key = netBossEncounterId.Key; Register(key, bossEncounterAdapter, component); NetMode bossMode = NetGameplaySyncBridge.BossMode; bool flag = false; try { flag = NetClientJoinFlow.SessionJoinedHost; } catch { } LogPreFight(source, key, bossMode, flag, in ctx); if (RoomMembershipActive && (bossMode == NetMode.Host || (bossMode == NetMode.Client && flag)) && bossEncounterAdapter.IsRoomEntrySource(source)) { ReportLocalRoomEntry(key, in ctx, source, bossMode); } bool flag2; lock (_lock) { flag2 = _terminalDead.Contains(key); } if (flag2 && bossMode == NetMode.Client && flag) { BossLocalStartBlocked++; if (LogOn) { Plugin.Log.Info("[BossEncounter] client blocked start on TERMINAL encounter source=" + source + " key=" + key); } return false; } if (GateFightActive && (bossMode == NetMode.Host || (bossMode == NetMode.Client && flag)) && source.EndsWith(".StartFight", StringComparison.Ordinal) && bossEncounterAdapter.GatesFightOnDialogClose(component)) { if (!SafeStarted(bossEncounterAdapter, component)) { BossLocalStartBlocked++; if (LogOn) { Plugin.Log.Info($"[BossFightGate] blocked behavior-tree StartFight source={source} key={key} mode={bossMode} (fight starts only on a dialog-close commit)"); } return false; } if (LogOn) { Plugin.Log.Info($"[BossFightGate] allowed trailing StartFight (already started — clears a late intro's lock) source={source} key={key} mode={bossMode}"); } return true; } if (bossMode == NetMode.Host) { if (bossEncounterAdapter.IsCombatEntrySource(component, source)) { if (SafeStarted(bossEncounterAdapter, component)) { BossDuplicateSuppressed++; if (LogOn) { Plugin.Log.Info("[BossEncounter] host suppressed duplicate combat-entry source=" + source + " key=" + key + " (already started)"); } return false; } BroadcastCombatEntryOnce(key, bossEncounterAdapter, component, in ctx, source); return true; } if (SafeStarted(bossEncounterAdapter, component) || _appliedStart.Contains(key)) { BossDuplicateSuppressed++; if (LogOn) { Plugin.Log.Info("[BossEncounter] host suppressed re-entry start source=" + source + " key=" + key + " (already started — initiator was not locally consumed)"); } return false; } BroadcastStartOnce(key, bossEncounterAdapter, component, in ctx, source); if (bossEncounterAdapter.IsDialogBoss(component) && bossEncounterAdapter.IsDialogCommitSource(source)) { BroadcastDialogCommitOnce(key, bossEncounterAdapter, component, in ctx, source); RemoveDialogInteractableOnce(key, bossEncounterAdapter, component, "host-initiated"); } return true; } if (bossMode == NetMode.Client && flag) { bool flag3 = false; try { flag3 = Plugin.Cfg.EnableFaithfulBossIntro.Value; } catch { } bool flag4 = false; try { flag4 = source.EndsWith(".TriggerFight", StringComparison.Ordinal) && bossEncounterAdapter.RunsLocalIntroPresentation(component) && !SafeStarted(bossEncounterAdapter, component); } catch { } if (flag3 && (source.EndsWith(".Introduction", StringComparison.Ordinal) || source.EndsWith(".StartFight", StringComparison.Ordinal) || flag4)) { BossContinuationAllowed++; if (LogOn) { Plugin.Log.Info($"[BossEncounter] faithful intro: allowed native chain source={source} key={key} started={SafeStarted(bossEncounterAdapter, component)}"); } return true; } if (bossEncounterAdapter.IsDialogBoss(component) && bossEncounterAdapter.ShouldSuppressDuplicateDialogEntry(component, source)) { BossDialogEntrySuppressed++; if (LogOn) { Plugin.Log.Info($"[BossEncounter] BossDialogCommit duplicate suppressed source={source} key={key} FightStarted={SafeStarted(bossEncounterAdapter, component)} speakable={BossDialogReflect.CurrentSpeakableName()}"); } return false; } if (IsContinuationActive(key, out ContinuationWindow window) && bossEncounterAdapter.IsContinuationSource(source)) { TouchContinuation(window); BossContinuationAllowed++; if (LogOn) { Plugin.Log.Info($"[BossEncounter] client allowed authorized continuation source={source} key={key} reason=HostBossEncounterStart started={SafeStarted(bossEncounterAdapter, component)}"); } return true; } if (_appliedStart.Contains(key) || SafeStarted(bossEncounterAdapter, component)) { BossDuplicateSuppressed++; if (LogOn) { Plugin.Log.Info("[BossEncounter] client suppressed duplicate local start source=" + source + " key=" + key + " (already host-authorized/started)"); } return false; } bool flag5 = true; try { flag5 = Plugin.Cfg.BossEncounterClientBlockLocalStart.Value; } catch { } if (bossEncounterAdapter.IsDialogBoss(component)) { RequestDialogCommitOnce(key, bossEncounterAdapter, component, in ctx, source); } else { RequestStartOnce(key, bossEncounterAdapter, component, in ctx, source); } if (flag5) { BossLocalStartBlocked++; BossContinuationBlocked++; if (LogOn) { Plugin.Log.Info("[BossEncounter] client blocked local start source=" + source + " " + netBossEncounterId.ToCompact() + " — requested host authority"); } return false; } return true; } return true; } catch (Exception ex) { Plugin.Log.Warn("[BossEncounter] OnLocalStartEntrypoint failed: " + ex.GetType().Name + ": " + ex.Message); return true; } } private static void LogPreFight(string source, string key, NetMode mode, bool joined, in BossEncounterContext ctx) { if (!PreFightLogOn) { return; } try { bool flag; lock (_lock) { flag = _terminalDead.Contains(key); } bool allConverged; string text = NetGameplaySyncBridge.FormatBossConvergence(out allConverged); string item = $"{key}|{source}|{allConverged}|{text.GetHashCode()}"; lock (_lock) { if (!_preFightLogged.Add(item)) { return; } } Plugin.Log.Info($"[BossPreFight] entry source={source} mode={mode} joined={joined} terminal={flag} key={key} allConverged={allConverged} | {text}"); } catch (Exception ex) { Plugin.Log.Warn("[BossPreFight] log failed: " + ex.GetType().Name + ": " + ex.Message); } } private static void Register(string key, IBossEncounterAdapter adapter, object component) { lock (_lock) { if (!_registry.ContainsKey(key)) { BossEncounterDiscovered++; if (LogOn) { Plugin.Log.Info("[BossEncounter] discovered " + adapter.DescribeForLog(component) + " key=" + key); } } _registry[key] = new Entry { Adapter = adapter, Component = component }; } } private static void BroadcastStartOnce(string key, IBossEncounterAdapter adapter, object component, in BossEncounterContext ctx, string source) { lock (_lock) { if (!_hostBroadcast.Add(key)) { return; } } NetBossEncounterState netBossEncounterState = BuildState(key, adapter, component, in ctx, source); BossStartBroadcast++; if (LogOn) { Plugin.Log.Info("[BossEncounter] host broadcasting BossEncounterStart " + netBossEncounterState.ToCompact()); } NetGameplaySyncBridge.BroadcastHostBossEncounterStart(netBossEncounterState); TryBeginSandstormArena(adapter, component, in ctx); CloseBossOwnDialogOnFightStart(adapter, component, key); } private static void BroadcastCombatEntryOnce(string key, IBossEncounterAdapter adapter, object component, in BossEncounterContext ctx, string source) { lock (_lock) { if (!_hostCombatEntryBroadcast.Add(key)) { return; } } NetBossEncounterState netBossEncounterState = BuildState(key, adapter, component, in ctx, source); BossStartBroadcast++; Plugin.Log.Info("[BossEncounter] host broadcasting combat-entry (TriggerFight) " + netBossEncounterState.ToCompact()); NetGameplaySyncBridge.BroadcastHostBossEncounterStart(netBossEncounterState); TryBeginSandstormArena(adapter, component, in ctx); } private static void TryBeginSandstormArena(IBossEncounterAdapter adapter, object component, in BossEncounterContext ctx) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) try { if (NetGameplaySyncBridge.IsHost && adapter.TryGetSandstormArenaSphere(component, out var center, out var _)) { ArenaLockdownManager.BeginSandstormArena(center, ctx.ChapterName, ctx.LevelIndex, ctx.HasSeed, ctx.Seed); } } catch (Exception ex) { Plugin.Log.Warn("[BossEncounter] TryBeginSandstormArena failed: " + ex.GetType().Name + ": " + ex.Message); } } public static bool TryGetSandstormArenaSphere(out Vector3 center, out float radius) { //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) center = Vector3.zero; radius = 0f; lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; if (value.Adapter == null || value.Component == null) { continue; } try { if (value.Adapter.TryGetSandstormArenaSphere(value.Component, out center, out radius)) { return true; } } catch { } } } return false; } public static bool TryGetActiveSandstormArenaSphere(out Vector3 center, out float radius) { //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) center = Vector3.zero; radius = 0f; lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; if (value.Adapter == null || value.Component == null) { continue; } try { if (!SafeStarted(value.Adapter, value.Component) || _terminalDead.Contains(item.Key) || !value.Adapter.TryGetSandstormArenaSphere(value.Component, out center, out radius)) { continue; } return true; } catch { } } } return false; } private static void RequestStartOnce(string key, IBossEncounterAdapter adapter, object component, in BossEncounterContext ctx, string source) { lock (_lock) { if (!_clientRequested.Add(key)) { return; } } NetClientBossStartRequest netClientBossStartRequest = new NetClientBossStartRequest { EncounterKey = key, BossType = component.GetType().Name, GraphName = ctx.GraphName, RootName = BossReflect.RootName(component), ChapterName = ctx.ChapterName, LevelIndex = ctx.LevelIndex, HasSeed = ctx.HasSeed, Seed = ctx.Seed, StartSource = source, SentAt = Time.realtimeSinceStartup }; BossStartRequestSent++; if (LogOn) { Plugin.Log.Info("[BossEncounter] client sending ClientBossStartRequest " + netClientBossStartRequest.ToCompact()); } NetGameplaySyncBridge.SendClientBossStartRequest(netClientBossStartRequest); } private static NetBossEncounterState BuildState(string key, IBossEncounterAdapter adapter, object component, in BossEncounterContext ctx, string source) { //IL_009d: Unknown result type (might be due to invalid IL or missing references) adapter.TryReadState(component, out var hasPhase, out var phaseIndex, out var hasPos, out var pos); return new NetBossEncounterState { EncounterKey = key, BossType = component.GetType().Name, GraphName = ctx.GraphName, RootName = BossReflect.RootName(component), ChapterName = ctx.ChapterName, LevelIndex = ctx.LevelIndex, HasSeed = ctx.HasSeed, Seed = ctx.Seed, Started = true, StartSource = source, HostRevision = 0, HostTimestamp = Time.realtimeSinceStartup, HasPosition = hasPos, Position = pos, HasPhaseIndex = hasPhase, PhaseIndex = phaseIndex }; } private static NetBossDialogCommit BuildDialogCommit(string key, object component, in BossEncounterContext ctx, string source) { return new NetBossDialogCommit { EncounterKey = key, BossType = component.GetType().Name, GraphName = ctx.GraphName, RootName = BossReflect.RootName(component), ChapterName = ctx.ChapterName, LevelIndex = ctx.LevelIndex, HasSeed = ctx.HasSeed, Seed = ctx.Seed, CommitSource = source, Revision = 0, Timestamp = Time.realtimeSinceStartup }; } private static void BroadcastDialogCommitOnce(string key, IBossEncounterAdapter adapter, object component, in BossEncounterContext ctx, string source) { lock (_lock) { if (!_dialogCommitBroadcast.Add(key)) { return; } } NetBossDialogCommit netBossDialogCommit = BuildDialogCommit(key, component, in ctx, source); MarkDialogSessionStarted(key, netBossDialogCommit, LocalInRoom(key)); BossDialogCommitBroadcast++; Plugin.Log.Info("[BossDialogCommit] host confirmed + broadcasting " + netBossDialogCommit.ToCompact() + " speakable=" + BossDialogReflect.CurrentSpeakableName()); NetGameplaySyncBridge.BroadcastHostBossDialogCommit(netBossDialogCommit); TryBeginSandstormArena(adapter, component, in ctx); } private static void RequestDialogCommitOnce(string key, IBossEncounterAdapter adapter, object component, in BossEncounterContext ctx, string source) { lock (_lock) { if (!_clientRequested.Add(key)) { return; } } NetBossDialogCommit netBossDialogCommit = BuildDialogCommit(key, component, in ctx, source); MarkDialogSessionStarted(key, netBossDialogCommit, LocalInRoom(key)); BossDialogCommitRequested++; Plugin.Log.Info("[BossDialogCommit] request " + netBossDialogCommit.ToCompact() + " speakable=" + BossDialogReflect.CurrentSpeakableName()); NetGameplaySyncBridge.SendClientBossDialogCommitRequest(netBossDialogCommit); } public static void HandleClientBossDialogCommitRequest(NetBossDialogCommit msg, string peerId) { try { if (!Enabled || msg == null || NetGameplaySyncBridge.BossMode != NetMode.Host) { return; } if (LogOn) { Plugin.Log.Info("[BossDialogCommit] host received request from " + peerId + ": " + msg.ToCompact()); } if (!TryBuildContext(out BossEncounterContext ctx, out string runScope) || !string.Equals(ctx.ChapterName, msg.ChapterName, StringComparison.Ordinal) || ctx.LevelIndex != msg.LevelIndex || (ctx.HasSeed && msg.HasSeed && ctx.Seed != msg.Seed)) { BossStartRejectedSession++; Plugin.Log.Warn($"[BossDialogCommit] reject request from {peerId}: run mismatch req={msg.ChapterName}:{msg.LevelIndex} host={ctx.ChapterName}:{ctx.LevelIndex}"); return; } if (string.Equals(msg.CommitSource, "open", StringComparison.Ordinal)) { if (!TryFindLocalEncounter(msg.EncounterKey, out IBossEncounterAdapter adapter, out object component) && !TryResolveAndRegisterBossOwnDialog(null, out runScope, out adapter, out component)) { BossEncounterNotFound++; Plugin.Log.Warn("[BossDialogSync] host has no encounter for client open-request key=" + msg.EncounterKey + "; candidates: " + DescribeCandidates()); return; } bool flag; lock (_lock) { flag = _desertDialogOpenKey == msg.EncounterKey; } if (flag && BossDialogReflect.IsDialogActive()) { if (LogOn) { Plugin.Log.Info("[BossDialogSync] host open-request no-op key=" + msg.EncounterKey + " (already open)"); } } else if (ArenaLockdownManager.IsLocalPlayerInActiveArena()) { string detail; bool flag2 = adapter.TryOpenBossOwnDialog(component, out detail); Plugin.Log.Info($"[BossDialogSync] host received client open from {peerId}: opened={flag2} ({detail}) key={msg.EncounterKey}"); } else { BroadcastHostBossOwnDialogOpen(msg.EncounterKey, adapter, component, hostHasItOpen: false); lock (_lock) { _pendingMirrorKey = msg.EncounterKey; _pendingMirrorEvent = "Dialog:boss-open"; } Plugin.Log.Info("[BossDialogSync] host received client open from " + peerId + ": broadcast-only (host out of arena; catch-up stashed) key=" + msg.EncounterKey); } return; } if (string.Equals(msg.CommitSource, "intro-finish", StringComparison.Ordinal)) { if (!TryFindLocalEncounter(msg.EncounterKey, out IBossEncounterAdapter adapter2, out object component2)) { BossEncounterNotFound++; Plugin.Log.Warn("[BossDialogSync] host has no encounter for client intro-finish key=" + msg.EncounterKey + "; candidates: " + DescribeCandidates()); } else { Plugin.Log.Info("[BossDialogSync] host received client intro-finish from " + peerId + ": " + msg.ToCompact()); CommitHostIntroDialog(msg.EncounterKey, adapter2, component2, "client-intro-finish:" + peerId); } return; } if (string.Equals(msg.CommitSource, "midfight-close", StringComparison.Ordinal)) { bool flag3; lock (_lock) { flag3 = _desertDialogOpenKey == msg.EncounterKey; } if (flag3 && BossDialogReflect.IsDialogActive() && TryFindLocalEncounter(msg.EncounterKey, out IBossEncounterAdapter _, out object component3)) { MonoBehaviour val = (MonoBehaviour)((component3 is MonoBehaviour) ? component3 : null); if (val != null && (Object)(object)val != (Object)null) { val.StartCoroutine(FastForwardHostMidFightDialog(msg.EncounterKey, peerId)); return; } } if (LogOn) { Plugin.Log.Info($"[BossDialogSync] host mid-fight-close no-op key={msg.EncounterKey} (open={flag3} active={BossDialogReflect.IsDialogActive()})"); } return; } if (msg.IsFightCommit) { if (!TryFindLocalEncounter(msg.EncounterKey, out IBossEncounterAdapter adapter4, out object component4)) { BossEncounterNotFound++; Plugin.Log.Warn("[BossFightGate] host has no encounter for client fight-commit key=" + msg.EncounterKey + "; candidates: " + DescribeCandidates()); } else { Plugin.Log.Info("[BossFightGate] host received client fight-commit from " + peerId + ": " + msg.ToCompact()); CommitFightStart(msg.EncounterKey, adapter4, component4, "client:" + msg.CommitSource); } return; } if (!TryFindLocalEncounter(msg.EncounterKey, out IBossEncounterAdapter adapter5, out object component5)) { BossEncounterNotFound++; Plugin.Log.Warn("[BossDialogCommit] host has no local encounter for key=" + msg.EncounterKey + "; candidates: " + DescribeCandidates()); return; } BeginApply(); try { ApplyIntroCutsceneGated(msg.EncounterKey, adapter5, component5, msg, out string detail2); if (LogOn) { Plugin.Log.Info("[BossDialogCommit] host applied own commit key=" + msg.EncounterKey + ": " + detail2); } } finally { EndApply(); } RemoveDialogInteractableOnce(msg.EncounterKey, adapter5, component5, "host-apply-client-commit"); BroadcastDialogCommitOnce(msg.EncounterKey, adapter5, component5, in ctx, "ClientRequest:" + msg.CommitSource); } catch (Exception ex) { Plugin.Log.Warn("[BossDialogCommit] HandleClientBossDialogCommitRequest failed: " + ex.GetType().Name + ": " + ex.Message); } } private static void RemoveDialogInteractableOnce(string key, IBossEncounterAdapter adapter, object component, string ctx) { try { if (!Plugin.Cfg.RemoveBossDialogInteractableOnStart.Value) { return; } lock (_lock) { if (!_dialogInteractableRemoved.Add(key)) { return; } } string detail; bool flag = adapter.TryRemoveDialogInteractable(component, out detail); Plugin.Log.Info($"[BossDialogFix] removed boss dialog interactable ({ctx}) key={key} ok={flag}: {detail}"); } catch (Exception ex) { Plugin.Log.Warn("[BossDialogFix] interactable removal failed key=" + key + ": " + ex.GetType().Name + ": " + ex.Message); } } private static bool LocalInRoom(string key) { lock (_lock) { return _roomEnterReported.Contains(key) || _localTeleportedIn.Contains(key); } } private static void MarkDialogSessionStarted(string key, NetBossDialogCommit msg, bool playedLocally) { if (!CutsceneGateActive || string.IsNullOrEmpty(key)) { return; } lock (_lock) { _dialogSessionActive.Add(key); if (msg != null) { _lastIntroCommit[key] = msg; } if (playedLocally) { _cutscenePlayed.Add(key); } } } private static bool ApplyIntroCutsceneGated(string key, IBossEncounterAdapter adapter, object component, NetBossDialogCommit msg, out string detail) { bool flag = false; try { flag = adapter.GatesFightOnDialogClose(component); } catch { } if (!flag) { return adapter.TryApplyDialogCommit(component, msg, out detail); } lock (_lock) { _dialogSessionActive.Add(key); if (msg != null) { _lastIntroCommit[key] = msg; } } if (CutsceneGateActive && !LocalInRoom(key)) { BeginApply(); try { BossReflect.TryInvoke(component, "Introduction", out detail); } finally { EndApply(); } detail = "out-of-room headless appear: " + detail; Plugin.Log.Info("[BossDialogCutscene] " + detail + " key=" + key); return true; } bool result = adapter.TryApplyDialogCommit(component, msg, out detail); lock (_lock) { _cutscenePlayed.Add(key); return result; } } private static void TryCatchUpCutscene(string key) { try { if (!CutsceneGateActive || !TryFindLocalEncounter(key, out IBossEncounterAdapter adapter, out object component)) { return; } try { if (!adapter.GatesFightOnDialogClose(component)) { return; } } catch { return; } bool flag; bool flag2; bool flag3; lock (_lock) { flag = _roomEnterReported.Contains(key) || _localTeleportedIn.Contains(key); flag2 = _cutscenePlayed.Contains(key); flag3 = _fightCommitted.Contains(key); } bool flag4 = false; try { flag4 = SafeStarted(adapter, component); } catch { } bool flag5 = false; try { flag5 = BossReflect.TryGetBool(component, "introPlayed", out var value) && value; } catch { } if (!flag || flag2 || flag3 || flag4 || !flag5) { if (LogOn) { Plugin.Log.Info($"[BossDialogCutscene] catch-up SKIP key={key} inRoom={flag} played={flag2} committed={flag3} started={flag4} appeared={flag5}"); } return; } lock (_lock) { _appliedStart.Add(key); _cutscenePlayed.Add(key); } TryOpenBossDialogLocally(key); Plugin.Log.Info("[BossDialogCutscene] catch-up dialog key=" + key + " (boss already appeared)"); try { adapter.OnClientPresentationStart(component); } catch { } } catch (Exception ex) { Plugin.Log.Warn("[BossDialogCutscene] catch-up failed key=" + key + ": " + ex.GetType().Name + ": " + ex.Message); } } public static void OnLocalTeleportedIntoArena() { try { if (!CutsceneGateActive) { return; } string[] array; lock (_lock) { array = _registry.Keys.ToArray(); } string[] array2 = array; foreach (string text in array2) { lock (_lock) { _localTeleportedIn.Add(text); } TryCatchUpCutscene(text); } } catch (Exception ex) { Plugin.Log.Warn("[BossDialogCutscene] OnLocalTeleportedIntoArena failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void SetSuppressBossCutscene(bool on) { _suppressBossCutscene = on; } public static bool IsLocalOutOfRoomForBoss(object component) { try { if (!CutsceneGateActive || component == null) { return false; } if (!TryGetEncounterKeyForBoss(component, out string key, out string _)) { return false; } return !LocalInRoom(key); } catch { return false; } } public static void OnBossDoneAppearing(object component) { try { if (Enabled && GateFightActive && TryGetEncounterKeyForBoss(component, out string key, out string _)) { bool flag; lock (_lock) { flag = _fightCommitted.Contains(key); } if (!flag && SetBossInvulnerable(component, invuln: true) && LogOn) { Plugin.Log.Info("[BossInvuln] kept boss invulnerable pre-fight key=" + key); } } } catch (Exception ex) { Plugin.Log.Warn("[BossInvuln] OnBossDoneAppearing failed: " + ex.GetType().Name + ": " + ex.Message); } } private static bool SetBossInvulnerable(object component, bool invuln) { try { object obj = null; try { obj = AccessTools.Field(component.GetType(), "owner")?.GetValue(component); } catch { } if (obj == null) { try { obj = ResolveAdapter(component)?.GetHealthUnit(component); } catch { } } if (obj == null) { return false; } MethodInfo methodInfo = AccessTools.Method(obj.GetType(), "SetInvulnerable", (Type[])null, (Type[])null); if (methodInfo == null) { return false; } methodInfo.Invoke(obj, new object[1] { invuln }); return true; } catch { return false; } } public static void MarkIntroRanOutOfRoom(object component) { try { if (TryGetEncounterKeyForBoss(component, out string key, out string _)) { lock (_lock) { _introRanWhileOutOfRoom.Add(key); return; } } } catch { } } private static void TryOpenBossDialogLocally(string key) { try { if (!TryFindLocalEncounter(key, out IBossEncounterAdapter adapter, out object component)) { return; } object obj = null; try { obj = adapter.GetHealthUnit(component); } catch { } if (obj == null) { return; } MethodInfo methodInfo = AccessTools.Method(obj.GetType(), "Interact", (Type[])null, (Type[])null); if (!(methodInfo == null)) { object[] parameters = ((methodInfo.GetParameters().Length != 1) ? Array.Empty() : new object[1] { ResolveLocalPlayerObject() }); BeginApply(); try { methodInfo.Invoke(obj, parameters); } finally { EndApply(); } Plugin.Log.Info("[BossDialogCutscene] catch-up opened boss dialog directly key=" + key); } } catch (Exception ex) { Plugin.Log.Warn("[BossDialogCutscene] direct dialog open failed key=" + key + ": " + ex.GetType().Name + ": " + ex.Message); } } private static object ResolveLocalPlayerObject() { try { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); object obj = ((type == null) ? null : AccessTools.Property(type, "Instance")?.GetValue(null, null)); if (obj == null) { return null; } return AccessTools.Property(type, "PlayerObject")?.GetValue(obj, null) ?? AccessTools.Field(type, "PlayerObject")?.GetValue(obj); } catch { return null; } } public static bool ShouldBlockBossAppearTrigger(object trigger) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) try { if (!Enabled || trigger == null) { goto IL_001d; } Component val = (Component)((trigger is Component) ? trigger : null); if (val == null || (Object)(object)val == (Object)null) { goto IL_001d; } if (!NetGameplaySyncBridge.IsSessionActive) { return false; } if (GateSyncManager.IsApplyingTriggerMirror) { return false; } if (!GateSyncManager.HasAppearAnimatorPersistent(trigger)) { return false; } Vector3 position = val.transform.position; lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; if (value.Adapter == null) { continue; } object component = value.Component; Object val2 = (Object)((component is Object) ? component : null); if (val2 == null || val2 == (Object)null || !SafeStarted(value.Adapter, value.Component)) { continue; } object? healthUnit = value.Adapter.GetHealthUnit(value.Component); Component val3 = (Component)((healthUnit is Component) ? healthUnit : null); if (val3 != null && !((Object)(object)val3 == (Object)null)) { Vector3 val4 = val3.transform.position - position; if (((Vector3)(ref val4)).sqrMagnitude <= 3600f) { Plugin.Log.Info($"[BossIntroSync] blocked mid-fight appear trigger name={((Object)val).name} pos={position:F1} key={item.Key}"); return true; } } } } goto end_IL_0000; IL_001d: return false; end_IL_0000:; } catch { } return false; } public static bool ShouldBlockOutOfRoomBossOwnDialog(object npc) { try { if (!Enabled || npc == null) { return false; } if (!NetGameplaySyncBridge.IsSessionActive) { return false; } lock (_lock) { if (_applyingHostDialogOpen) { return false; } } if (ArenaLockdownManager.IsLocalPlayerInActiveArena()) { return false; } if (!TryResolveAndRegisterBossOwnDialog(npc, out string key, out IBossEncounterAdapter adapter, out object component)) { return false; } if (SafeStarted(adapter, component)) { return false; } Plugin.Log.Info("[BossDialogSync] blocked out-of-arena opening dialog (local player not in-room) key=" + key); return true; } catch { return false; } } public static bool ShouldBlockStartedBossOwnDialog(object npc) { try { if (!Enabled || npc == null) { return false; } lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; object component = value.Component; Object val = (Object)((component is Object) ? component : null); if (val == null || val == (Object)null) { continue; } bool flag = false; try { flag = value.Adapter.BroadcastsBossOwnDialog(value.Component); } catch { } if (flag) { object obj2 = null; try { obj2 = value.Adapter.GetHealthUnit(value.Component); } catch { } if (obj2 != null && obj2 == npc) { return SafeStarted(value.Adapter, value.Component); } } } } } catch { } return false; } public static bool ShouldBlockBossDialogNpc(object npc) { try { if (!CutsceneGateActive || npc == null) { return false; } lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; object component = value.Component; Object val = (Object)((component is Object) ? component : null); if (val == null || val == (Object)null) { continue; } try { if (!value.Adapter.GatesFightOnDialogClose(value.Component)) { continue; } } catch { continue; } object obj2 = null; try { obj2 = value.Adapter.GetHealthUnit(value.Component); } catch { } if (obj2 != null && obj2 == npc) { return !_roomEnterReported.Contains(item.Key) && !_localTeleportedIn.Contains(item.Key); } } } } catch { } return false; } public static void OnLocalBossOwnDialogInteract(object npc) { try { if (!Enabled || npc == null || NetGameplaySyncBridge.BossMode != NetMode.Client) { return; } bool applyingHostDialogOpen; lock (_lock) { applyingHostDialogOpen = _applyingHostDialogOpen; } if (applyingHostDialogOpen) { return; } bool flag = false; try { flag = NetClientJoinFlow.SessionJoinedHost; } catch { } if (flag && TryResolveAndRegisterBossOwnDialog(npc, out string key, out IBossEncounterAdapter adapter, out object component) && !SafeStarted(adapter, component)) { bool flag2; lock (_lock) { flag2 = _bossOwnDialogLocallyOpen.Add(key); } if (flag2 && TryBuildContext(out BossEncounterContext ctx, out string _)) { NetBossDialogCommit netBossDialogCommit = BuildDialogCommit(key, component, in ctx, "open"); Plugin.Log.Info("[BossDialogSync] client opened boss dialog → request host mirror " + netBossDialogCommit.ToCompact()); NetGameplaySyncBridge.SendClientBossDialogCommitRequest(netBossDialogCommit); } } } catch (Exception ex) { Plugin.Log.Warn("[BossDialogSync] OnLocalBossOwnDialogInteract failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void NotifyBossDialogOpened(object npc) { try { if (!Enabled || !GateFightActive || npc == null) { return; } NetMode bossMode = NetGameplaySyncBridge.BossMode; if (bossMode != NetMode.Host && bossMode != NetMode.Client) { return; } string text = null; lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; object component = value.Component; Object val = (Object)((component is Object) ? component : null); if (val == null || val == (Object)null) { continue; } try { if (!value.Adapter.GatesFightOnDialogClose(value.Component)) { continue; } } catch { continue; } object obj2 = null; try { obj2 = value.Adapter.GetHealthUnit(value.Component); } catch { } if (obj2 != null && obj2 == npc) { text = item.Key; break; } } if (text != null) { _dialogOpenKey = text; } } if (text != null && LogOn) { Plugin.Log.Info("[BossFightGate] boss intro dialog opened key=" + text + " (awaiting dismissal to commit fight)"); } } catch (Exception ex) { Plugin.Log.Warn("[BossFightGate] NotifyBossDialogOpened failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void NotifyDialogClosed() { try { if (Enabled && GateFightActive) { string dialogOpenKey; lock (_lock) { dialogOpenKey = _dialogOpenKey; _dialogOpenKey = null; } if (dialogOpenKey != null) { OnLocalBossDialogClosed(dialogOpenKey); } } } catch (Exception ex) { Plugin.Log.Warn("[BossFightGate] NotifyDialogClosed failed: " + ex.GetType().Name + ": " + ex.Message); } } private static void OnLocalBossDialogClosed(string key) { lock (_lock) { if (_fightCommitted.Contains(key)) { return; } } if (!TryFindLocalEncounter(key, out IBossEncounterAdapter adapter, out object component)) { return; } try { if (!adapter.GatesFightOnDialogClose(component)) { return; } } catch { return; } if (SafeStarted(adapter, component)) { if (LogOn) { Plugin.Log.Info("[BossFightGate] dialog close ignored — fight already started key=" + key); } return; } switch (NetGameplaySyncBridge.BossMode) { case NetMode.Host: Plugin.Log.Info("[BossFightGate] host dialog dismissed → committing fight key=" + key); CommitFightStart(key, adapter, component, "host-dialog-close"); break; case NetMode.Client: { bool flag = false; try { flag = NetClientJoinFlow.SessionJoinedHost; } catch { } if (!flag) { break; } lock (_lock) { if (!_fightCommitRequested.Add(key)) { break; } } if (TryBuildContext(out BossEncounterContext ctx, out string _)) { NetBossDialogCommit netBossDialogCommit = BuildDialogCommit(key, component, in ctx, "client-dialog-close"); netBossDialogCommit.IsFightCommit = true; Plugin.Log.Info("[BossFightGate] client dialog dismissed → request fight commit " + netBossDialogCommit.ToCompact()); NetGameplaySyncBridge.SendClientBossDialogCommitRequest(netBossDialogCommit); } break; } } } private static void CommitFightStart(string key, IBossEncounterAdapter adapter, object component, string reason) { CommitFightStartLocal(key, adapter, component, reason); lock (_lock) { if (!_fightCommitBroadcast.Add(key)) { return; } } if (TryBuildContext(out BossEncounterContext ctx, out string _)) { NetBossDialogCommit netBossDialogCommit = BuildDialogCommit(key, component, in ctx, reason); netBossDialogCommit.IsFightCommit = true; Plugin.Log.Info("[BossFightGate] host broadcasting FIGHT commit " + netBossDialogCommit.ToCompact()); NetGameplaySyncBridge.BroadcastHostBossDialogCommit(netBossDialogCommit); } } private static bool CommitFightStartLocal(string key, IBossEncounterAdapter adapter, object component, string reason) { lock (_lock) { if (!_fightCommitted.Add(key)) { return false; } _dialogSessionActive.Remove(key); } string detail = "already-started"; BeginApply(); try { if (!SafeStarted(adapter, component)) { bool flag; lock (_lock) { flag = CutsceneGateActive && !_cutscenePlayed.Contains(key); } if (flag) { SetSuppressBossCutscene(on: true); try { BossReflect.TryInvoke(component, "Introduction", out string detail2); Plugin.Log.Info("[BossDialogCutscene] forced boss appearance (out-of-room) key=" + key + ": " + detail2); } finally { SetSuppressBossCutscene(on: false); } } BossReflect.TryInvoke(component, "StartFight", out detail); } } finally { EndApply(); } SetBossInvulnerable(component, invuln: false); string detail3; bool flag2 = BossDialogReflect.TryFinalizeCurrentDialog(out detail3); Plugin.Log.Info($"[BossFightGate] committed fight start key={key} reason={reason} start[{detail}] dialogClosed={flag2}({detail3})"); if (DeferIntroArmActive) { bool flag3 = false; try { flag3 = adapter.DefersIntroArmUntilCommit(component); } catch { } if (flag3) { bool flag4; lock (_lock) { flag4 = _introArmReplayed.Add(key); } if (flag4) { BeginApply(); bool flag5; string detail4; try { flag5 = adapter.TryReplayIntroArm(component, out detail4); } catch (Exception ex) { flag5 = false; detail4 = "ex " + ex.GetType().Name + ": " + ex.Message; } finally { EndApply(); } Plugin.Log.Info($"[BossArmDefer] replayed intro arm at commit key={key} ok={flag5} ({detail4})"); } } } return true; } private static void MarkCousinFightLoopStarted(string key) { if (string.IsNullOrEmpty(key)) { return; } lock (_lock) { _cousinFightLoopStarted.Add(key); } } public static bool OnLocalIntroArmSpawn(object component) { try { if (!Enabled || component == null || !DeferIntroArmActive) { return true; } if (InReentry) { return true; } IBossEncounterAdapter bossEncounterAdapter = ResolveAdapter(component); if (bossEncounterAdapter == null) { return true; } bool flag = false; try { flag = bossEncounterAdapter.DefersIntroArmUntilCommit(component); } catch { } if (!flag) { return true; } NetMode bossMode = NetGameplaySyncBridge.BossMode; bool flag2 = false; try { flag2 = NetClientJoinFlow.SessionJoinedHost; } catch { } if (bossMode != NetMode.Host && !(bossMode == NetMode.Client && flag2)) { return true; } if (!TryGetEncounterKeyForBoss(component, out string key, out string _)) { return true; } bool flag3; bool flag4; lock (_lock) { flag3 = _cousinFightLoopStarted.Contains(key); flag4 = _terminalDead.Contains(key); } if (flag3 || flag4) { return true; } if (LogOn) { Plugin.Log.Info($"[BossArmDefer] blocked intro arm (deferred to fight commit) key={key} mode={bossMode}"); } return false; } catch (Exception ex) { Plugin.Log.Warn("[BossArmDefer] OnLocalIntroArmSpawn failed: " + ex.GetType().Name + ": " + ex.Message); return true; } } private static void ReportLocalRoomEntry(string key, in BossEncounterContext ctx, string source, NetMode mode) { try { lock (_lock) { if (!_roomEnterReported.Add(key)) { return; } } TryCatchUpCutscene(key); if (mode == NetMode.Host) { MarkInRoom(key, "host", source); return; } NetClientRoomEnter netClientRoomEnter = new NetClientRoomEnter { EncounterKey = key, ChapterName = ctx.ChapterName, LevelIndex = ctx.LevelIndex, HasSeed = ctx.HasSeed, Seed = ctx.Seed, EntrySource = source, Timestamp = Time.realtimeSinceStartup }; Plugin.Log.Info("[RoomMembership] client reporting local room entry " + netClientRoomEnter.ToCompact()); NetGameplaySyncBridge.SendClientRoomEnter(netClientRoomEnter); } catch (Exception ex) { Plugin.Log.Warn("[RoomMembership] ReportLocalRoomEntry failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void HandleClientRoomEnter(NetClientRoomEnter msg, string peerId) { try { if (!Enabled || !RoomMembershipActive || msg == null || NetGameplaySyncBridge.BossMode != NetMode.Host) { return; } if (!TryBuildContext(out BossEncounterContext ctx, out string _) || !string.Equals(ctx.ChapterName, msg.ChapterName, StringComparison.Ordinal) || ctx.LevelIndex != msg.LevelIndex || (ctx.HasSeed && msg.HasSeed && ctx.Seed != msg.Seed)) { if (LogOn) { Plugin.Log.Warn($"[RoomMembership] host reject room-enter from {peerId}: run mismatch req={msg.ChapterName}:{msg.LevelIndex} host={ctx.ChapterName}:{ctx.LevelIndex}"); } } else { MarkInRoom(msg.EncounterKey, peerId, "client:" + msg.EntrySource); } } catch (Exception ex) { Plugin.Log.Warn("[RoomMembership] HandleClientRoomEnter failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void HandleHostRoomMembership(NetHostRoomMembership msg) { try { if (Enabled && RoomMembershipActive && msg != null && NetGameplaySyncBridge.BossMode == NetMode.Client) { lock (_lock) { _roomMembersClientView[msg.EncounterKey] = new HashSet(msg.PlayerIds ?? Array.Empty()); } if (LogOn) { Plugin.Log.Info("[RoomMembership] client received " + msg.ToCompact()); } } } catch (Exception ex) { Plugin.Log.Warn("[RoomMembership] HandleHostRoomMembership failed: " + ex.GetType().Name + ": " + ex.Message); } } private static void MarkInRoom(string key, string playerId, string source) { bool flag; string text; lock (_lock) { if (!_roomMembers.TryGetValue(key, out HashSet value)) { value = new HashSet(); _roomMembers[key] = value; } flag = value.Add(playerId); text = string.Join(",", value); } if (flag) { Plugin.Log.Info("[RoomMembership] host in-room += " + playerId + " (src=" + source + ") key=" + key + " members=[" + text + "]"); BroadcastRoomMembership(key); } } private static void BroadcastRoomMembership(string key) { if (NetGameplaySyncBridge.BossMode == NetMode.Host && TryBuildContext(out BossEncounterContext ctx, out string _)) { string[] playerIds; lock (_lock) { playerIds = (_roomMembers.TryGetValue(key, out HashSet value) ? value.ToArray() : Array.Empty()); } NetGameplaySyncBridge.BroadcastHostRoomMembership(new NetHostRoomMembership { EncounterKey = key, ChapterName = ctx.ChapterName, LevelIndex = ctx.LevelIndex, HasSeed = ctx.HasSeed, Seed = ctx.Seed, PlayerIds = playerIds, Timestamp = Time.realtimeSinceStartup }); } } public static bool IsPlayerInRoom(string key, string playerId) { lock (_lock) { HashSet value; return ((NetGameplaySyncBridge.BossMode == NetMode.Host) ? _roomMembers : _roomMembersClientView).TryGetValue(key, out value) && value.Contains(playerId); } } public static string[] GetRoomMembers(string key) { lock (_lock) { HashSet value; return ((NetGameplaySyncBridge.BossMode == NetMode.Host) ? _roomMembers : _roomMembersClientView).TryGetValue(key, out value) ? value.ToArray() : Array.Empty(); } } public static void HandleHostBossDialogCommit(NetBossDialogCommit msg) { try { if (!Enabled || msg == null || NetGameplaySyncBridge.BossMode != NetMode.Client) { return; } if (LogOn) { Plugin.Log.Info("[BossDialogCommit] client received " + msg.ToCompact()); } if (msg.IsFightCommit) { if (!TryFindLocalEncounter(msg.EncounterKey, out IBossEncounterAdapter adapter, out object component)) { BossEncounterNotFound++; Plugin.Log.Warn("[BossFightGate] client has no encounter for host fight-commit key=" + msg.EncounterKey + "; candidates: " + DescribeCandidates()); } else { Plugin.Log.Info("[BossFightGate] client received host fight-commit " + msg.ToCompact()); CommitFightStartLocal(msg.EncounterKey, adapter, component, "host-fight-commit"); } return; } lock (_lock) { if (!_dialogCommitApplied.Add(msg.EncounterKey)) { if (LogOn) { Plugin.Log.Info("[BossDialogCommit] already applied key=" + msg.EncounterKey); } return; } } if (!TryFindLocalEncounter(msg.EncounterKey, out IBossEncounterAdapter adapter2, out object component2)) { BossEncounterNotFound++; Plugin.Log.Warn("[BossDialogCommit] client has no local encounter for key=" + msg.EncounterKey + "; candidates: " + DescribeCandidates()); return; } bool flag; lock (_lock) { flag = !_appliedStart.Add(msg.EncounterKey); } if (!flag) { OpenContinuation(msg.EncounterKey, adapter2, component2, "HostBossDialogCommit:" + msg.CommitSource); BeginApply(); bool flag2; string detail; try { flag2 = ApplyIntroCutsceneGated(msg.EncounterKey, adapter2, component2, msg, out detail); } finally { EndApply(); } if (flag2) { BossDialogCommitApplied++; Plugin.Log.Info("[BossDialogCommit] applied key=" + msg.EncounterKey + ": " + detail + " after[" + SafeDescribe(adapter2, component2) + "]"); try { adapter2.OnClientPresentationStart(component2); } catch { } } else { Plugin.Log.Warn("[BossDialogCommit] client failed to apply commit key=" + msg.EncounterKey + ": " + detail); } } else if (LogOn) { Plugin.Log.Info("[BossDialogCommit] start already applied via host-start path key=" + msg.EncounterKey + "; skipping redundant intro apply"); } bool flag3 = false; try { flag3 = Plugin.Cfg.EnableFaithfulBossIntro.Value; } catch { } if (flag3) { return; } RemoveDialogInteractableOnce(msg.EncounterKey, adapter2, component2, "client-commit"); lock (_lock) { _pendingDialogFinalize.Add(new PendingDialogFinalize { Key = msg.EncounterKey, Until = Time.realtimeSinceStartup + 6f }); } } catch (Exception ex) { Plugin.Log.Warn("[BossDialogCommit] HandleHostBossDialogCommit failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void OnHostBossPhaseChanged(object component) { try { if (Enabled && component != null && NetGameplaySyncBridge.BossMode == NetMode.Host && !InReentry) { IBossEncounterAdapter bossEncounterAdapter = ResolveAdapter(component); if (bossEncounterAdapter != null && bossEncounterAdapter.ProvidesPhaseState && TryBuildContext(out BossEncounterContext ctx, out string runScope)) { OnLevelChanged(runScope); NetBossEncounterId netBossEncounterId = bossEncounterAdapter.BuildEncounterId(component, in ctx); Register(netBossEncounterId.Key, bossEncounterAdapter, component); BroadcastBossStateFor(netBossEncounterId.Key, bossEncounterAdapter, component, in ctx, verbose: true); } } } catch (Exception ex) { Plugin.Log.Warn("[BossState] OnHostBossPhaseChanged failed: " + ex.GetType().Name + ": " + ex.Message); } } private static bool ParticipatesInStateSync(IBossEncounterAdapter adapter, object component) { if (adapter.ProvidesPhaseState) { return true; } try { return adapter.GetHealthUnit(component) != null; } catch { return false; } } private static void BroadcastBossStateFor(string key, IBossEncounterAdapter adapter, object component, in BossEncounterContext ctx, bool verbose) { NetBossState netBossState = new NetBossState { EncounterKey = key, BossType = component.GetType().Name, ChapterName = ctx.ChapterName, LevelIndex = ctx.LevelIndex, HasSeed = ctx.HasSeed, Seed = ctx.Seed, Revision = ++_bossStateRevision, Timestamp = Time.realtimeSinceStartup }; adapter.FillBossState(component, netBossState); BossStateBroadcast++; if (LogOn && verbose) { string text = ((adapter is WitchBossControllerAdapter witchBossControllerAdapter) ? (" | " + witchBossControllerAdapter.DescribeAddManifest(component)) : ""); Plugin.Log.Info("[BossState] host broadcasting " + netBossState.ToCompact() + text); } NetGameplaySyncBridge.BroadcastHostBossState(netBossState); } private static void TickHostBossStateBroadcast() { if (NetGameplaySyncBridge.BossMode != NetMode.Host) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _nextBossStateBroadcast) { return; } _nextBossStateBroadcast = realtimeSinceStartup + 0.4f; if (!TryBuildContext(out BossEncounterContext ctx, out string _)) { return; } List> list; lock (_lock) { list = _registry.ToList(); } foreach (KeyValuePair item in list) { Entry value = item.Value; object component = value.Component; Object val = (Object)((component is Object) ? component : null); if (val == null || val == (Object)null) { continue; } try { lock (_lock) { if (_terminalDead.Contains(item.Key)) { continue; } } if (ParticipatesInStateSync(value.Adapter, value.Component) && SafeStarted(value.Adapter, value.Component)) { bool flag; lock (_lock) { flag = _bossUiAttached.Add(item.Key); } if (flag && value.Adapter.TryAttachBossBar(value.Component) && LogOn) { Plugin.Log.Info("[BossState] host attached boss bar key=" + item.Key); } BroadcastBossStateFor(item.Key, value.Adapter, value.Component, in ctx, verbose: false); } } catch { } } } public static void HandleHostBossState(NetBossState state) { try { if (!Enabled || state == null || NetGameplaySyncBridge.BossMode != NetMode.Client) { return; } if (LogOn) { Plugin.Log.Info("[BossState] client received " + state.ToCompact()); } if (!TryFindLocalEncounter(state.EncounterKey, out IBossEncounterAdapter adapter, out object component)) { BossEncounterNotFound++; Plugin.Log.Warn("[BossState] client has no local encounter for key=" + state.EncounterKey + "; candidates: " + DescribeCandidates()); } else { if (!ParticipatesInStateSync(adapter, component)) { return; } bool flag; lock (_lock) { flag = _bossUiAttached.Add(state.EncounterKey); } if (flag && adapter.TryAttachBossBar(component) && LogOn) { Plugin.Log.Info("[BossState] client attached boss bar key=" + state.EncounterKey); } BeginApply(); bool flag2; string detail; try { flag2 = adapter.TryApplyBossState(component, state, out detail); } finally { EndApply(); } if (flag2) { BossStateApplied++; bool flag3 = state.PhaseIndex != _lastLoggedClientPhase; _lastLoggedClientPhase = state.PhaseIndex; float realtimeSinceStartup = Time.realtimeSinceStartup; if (LogOn && (flag3 || realtimeSinceStartup >= _nextBossStateClientLog)) { _nextBossStateClientLog = realtimeSinceStartup + 1.5f; Plugin.Log.Info(string.Format("[BossState] client applied key={0} hp={1} phase={2}: {3}", state.EncounterKey, state.HasHealth ? $"{state.CurrentHealth:0}/{state.MaxHealth:0}" : "?", state.PhaseIndex, detail)); } } else { Plugin.Log.Warn("[BossState] client failed to apply state key=" + state.EncounterKey + ": " + detail); } } } catch (Exception ex) { Plugin.Log.Warn("[BossState] HandleHostBossState failed: " + ex.GetType().Name + ": " + ex.Message); } } public static bool TryClientBossHit(object hitUnit, float damage, int damageTypeInt) { try { if (!Enabled || hitUnit == null || damage <= 0f) { return false; } try { if (!Plugin.Cfg.EnableBossDamageAuthority.Value) { return false; } } catch { return false; } if (NetGameplaySyncBridge.BossMode != NetMode.Client) { return false; } bool flag = false; try { flag = NetClientJoinFlow.SessionJoinedHost; } catch { } if (!flag) { return false; } IBossEncounterAdapter bossEncounterAdapter = null; object obj3 = null; string text = null; string text2 = ""; lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; object component = value.Component; Object val = (Object)((component is Object) ? component : null); if (val != null && !(val == (Object)null)) { string text3 = null; try { text3 = value.Adapter.ResolveHitTargetRole(value.Component, hitUnit); } catch { } if (text3 != null) { bossEncounterAdapter = value.Adapter; obj3 = value.Component; text = text3; text2 = item.Key; break; } } } } if (bossEncounterAdapter == null || obj3 == null || text == null) { return false; } if (text == "body") { _bodySwallowCount++; float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup - _lastBodySwallowLogAt >= 5f) { _lastBodySwallowLogAt = realtimeSinceStartup; Plugin.Log.Info($"[BossDamage] client swallowed {_bodySwallowCount} outside-window body hit(s) key={text2} (eye window never open? check isTransitioning/fightStarted)"); } TerrorbaumMechanicSync.ClientNoteBodyHit(text2, obj3, damage, damageTypeInt); return true; } lock (_lock) { if (_terminalDead.Contains(text2)) { return true; } } if (!TryGetRunContext(out string chapter, out int level, out bool hasSeed, out int seed)) { return false; } NetClientBossHitRequest netClientBossHitRequest = new NetClientBossHitRequest { EncounterKey = text2, BossType = obj3.GetType().Name, RootName = BossReflect.RootName(obj3), ChapterName = chapter, LevelIndex = level, HasSeed = hasSeed, Seed = seed, TargetRole = text, Damage = damage, DamageTypeInt = damageTypeInt, RequestSeq = ++_bossHitSeq, SentAt = Time.realtimeSinceStartup }; BossHitClientSent++; if (LogOn) { Plugin.Log.Info("[BossDamage] client hit local target=" + BossReflect.RootName(hitUnit) + " -> route to host (reusedRoster=false) " + netClientBossHitRequest.ToCompact()); } NetGameplaySyncBridge.SendClientBossHitRequest(netClientBossHitRequest); return true; } catch (Exception ex) { Plugin.Log.Warn("[BossDamage] TryClientBossHit failed: " + ex.GetType().Name + ": " + ex.Message); return false; } } private static void FlushClientAbsorbBatch() { try { if (!TerrorbaumMechanicSync.TryFlushClientAbsorb(out string key, out object component, out string role, out float damage, out int dtype) || !Enabled || component == null) { return; } try { if (!Plugin.Cfg.EnableBossDamageAuthority.Value) { return; } } catch { return; } bool flag = false; try { flag = NetClientJoinFlow.SessionJoinedHost; } catch { } if (!flag) { return; } lock (_lock) { if (_terminalDead.Contains(key)) { return; } } if (TryGetRunContext(out string chapter, out int level, out bool hasSeed, out int seed)) { NetClientBossHitRequest netClientBossHitRequest = new NetClientBossHitRequest { EncounterKey = key, BossType = component.GetType().Name, RootName = BossReflect.RootName(component), ChapterName = chapter, LevelIndex = level, HasSeed = hasSeed, Seed = seed, TargetRole = role, Damage = damage, DamageTypeInt = dtype, RequestSeq = ++_bossHitSeq, SentAt = Time.realtimeSinceStartup }; BossHitClientSent++; if (LogOn) { Plugin.Log.Info("[BossDamage] client absorb batch -> host " + netClientBossHitRequest.ToCompact()); } NetGameplaySyncBridge.SendClientBossHitRequest(netClientBossHitRequest); } } catch (Exception ex) { Plugin.Log.Warn("[BossDamage] FlushClientAbsorbBatch failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void HandleClientBossHitRequest(NetClientBossHitRequest req, string peerId) { try { if (!Enabled || req == null || NetGameplaySyncBridge.BossMode != NetMode.Host) { return; } try { if (!Plugin.Cfg.EnableBossDamageAuthority.Value) { return; } } catch { return; } BossHitHostRecv++; if (!TryBuildContext(out BossEncounterContext ctx, out string _) || !string.Equals(ctx.ChapterName, req.ChapterName, StringComparison.Ordinal) || ctx.LevelIndex != req.LevelIndex || (ctx.HasSeed && req.HasSeed && ctx.Seed != req.Seed)) { BossHitHostRejected++; Plugin.Log.Warn($"[BossDamage] host REJECT hit from {peerId}: run mismatch req={req.ChapterName}:{req.LevelIndex} host={ctx.ChapterName}:{ctx.LevelIndex}"); return; } if (!TryFindLocalEncounter(req.EncounterKey, out IBossEncounterAdapter adapter, out object component)) { BossHitHostRejected++; Plugin.Log.Warn("[BossDamage] host REJECT hit from " + peerId + ": no encounter key=" + req.EncounterKey + " (missingEncounter)"); return; } if (req.TargetRole != null && req.TargetRole.StartsWith("absorb:", StringComparison.Ordinal)) { if (TerrorbaumMechanicSync.HostAbsorbFeed(component, req.TargetRole, req.Damage, req.DamageTypeInt, out string detail)) { BossHitHostApplied++; Plugin.Log.Info("[BossDamage] host ABSORB feed from " + peerId + " key=" + req.EncounterKey + " " + detail); } else { BossHitHostRejected++; Plugin.Log.Info("[BossDamage] host absorb feed skipped from " + peerId + " key=" + req.EncounterKey + " reason=" + detail); } return; } object obj2 = null; try { obj2 = adapter.ResolveHostTargetForRole(component, req.TargetRole); } catch { } if (obj2 == null) { BossHitHostRejected++; Plugin.Log.Warn("[BossDamage] host REJECT hit from " + peerId + ": role '" + req.TargetRole + "' resolved no target (wrongRole) key=" + req.EncounterKey + " hostState[" + SafeDescribe(adapter, component) + "]"); return; } NetGameplayProbeManager.TryReadBossUnitHealth(obj2, out var currentHp, out var maxHp); object obj4 = BossDamageReflect.ResolveHostPlayerUnit(); string text = obj4?.GetType().FullName ?? "null"; string text2 = BossReflect.ReadUnitId(obj2); object obj5 = null; try { obj5 = adapter.GetHealthUnit(component); } catch { } bool flag = obj5 != null && obj5 != obj2; float currentHp2 = 0f; float maxHp2 = 0f; if (flag) { NetGameplayProbeManager.TryReadBossUnitHealth(obj5, out currentHp2, out maxHp2); } bool vanillaResult; string detail2; bool flag2 = adapter.TryApplyHostBossHit(component, req.TargetRole, obj2, req.Damage, req.DamageTypeInt, obj4, out vanillaResult, out detail2); NetGameplayProbeManager.TryReadBossUnitHealth(obj2, out var currentHp3, out var maxHp3); string text3 = ""; float currentHp4 = 0f; if (flag) { NetGameplayProbeManager.TryReadBossUnitHealth(obj5, out currentHp4, out maxHp3); text3 = $" mainHp={currentHp2:0}->{currentHp4:0}/{maxHp2:0}"; } string text4 = string.Format("source=[{0} isIDamager={1}] target=[{2} unitId={3}]{4} overload={5}", text, BossDamageReflect.IsValidDamageSource(obj4), obj2.GetType().Name, string.IsNullOrEmpty(text2) ? "?" : text2, text3, BossDamageReflect.ReceiveDamageSignature); if (flag2 && vanillaResult) { BossHitHostApplied++; Plugin.Log.Info($"[BossDamage] host APPLY hit from {peerId} key={req.EncounterKey} role={req.TargetRole} dmg={req.Damage:0.0} hp={currentHp:0}->{currentHp3:0}/{maxHp:0} result=true {text4}"); float delta = (flag ? (currentHp2 - currentHp4) : (currentHp - currentHp3)); object entity = (flag ? obj5 : obj2); NetRunStatsManager.RecordDamageDealtDelta(peerId, NetRunStatsManager.EntityKey(entity), delta); bool flag3; lock (_lock) { _lastHitVisualAt.TryGetValue(req.EncounterKey, out var value); flag3 = Time.realtimeSinceStartup - value >= 0.1f; if (flag3) { _lastHitVisualAt[req.EncounterKey] = Time.realtimeSinceStartup; } } if (flag3) { BossHitVisualSent++; NetGameplaySyncBridge.BroadcastHostBossHitVisual(new NetHostBossHitVisual { EncounterKey = req.EncounterKey, BossType = req.BossType, TargetRole = req.TargetRole, TargetUnitId = text2, Seq = ++_bossHitVisualSeq }); } } else { BossHitHostRejected++; string text5 = (flag2 ? "vanilla-false(invuln/hitbox-invuln/parry)" : "apply-failed"); Plugin.Log.Warn($"[BossDamage] host hit NOT applied from {peerId} key={req.EncounterKey} role={req.TargetRole} result=false reason={text5} detail={detail2} hp={currentHp:0}/{maxHp:0} {text4}"); } } catch (Exception ex) { Plugin.Log.Warn("[BossDamage] HandleClientBossHitRequest failed: " + ex.GetType().Name + ": " + ex.Message); } } public static bool OnLocalBossDiscreteEvent_Pre(object component, string eventName) { try { if (!Enabled || component == null) { return true; } if (NetGameplaySyncBridge.BossMode != NetMode.Client) { return true; } if (InReentry) { return true; } bool flag = true; try { flag = Plugin.Cfg.EnableBossDiscreteEventAuthority.Value; } catch { } if (!flag) { return true; } BossDiscreteClientSelfFired++; if (LogOn) { Plugin.Log.Info("[CousinPool] client BLOCKED local '" + eventName + "' (host-authoritative; client must not pick its own pool)"); } return false; } catch { return true; } } public static void OnLocalBossDiscreteEvent(object component, string eventName) { //IL_00af: Unknown result type (might be due to invalid IL or missing references) try { if (!Enabled || component == null) { return; } IBossEncounterAdapter bossEncounterAdapter = ResolveAdapter(component); if (bossEncounterAdapter != null && NetGameplaySyncBridge.BossMode == NetMode.Host && TryGetEncounterKeyForBoss(component, out string key, out string bossType)) { if (!bossEncounterAdapter.IsTerminalEvent(eventName)) { MarkCousinFightLoopStarted(key); } if (!TryGetRunContext(out string chapter, out int level, out bool hasSeed, out int seed)) { chapter = ""; level = -1; } bossEncounterAdapter.BuildDiscreteEvent(component, eventName, out bool hasPos, out Vector3 pos, out string diag); if (LogOn && !string.IsNullOrEmpty(diag)) { Plugin.Log.Info(diag); } NetBossDiscreteEvent netBossDiscreteEvent = new NetBossDiscreteEvent { EncounterKey = key, BossType = bossType, EventName = eventName, HasPos = hasPos, Position = pos, ChapterName = chapter, LevelIndex = level, HasSeed = hasSeed, Seed = seed, Seq = ++_bossDiscreteSeq }; BossDiscreteSent++; if (LogOn) { Plugin.Log.Info("[CousinPool] host broadcasting discrete " + netBossDiscreteEvent.ToCompact()); } NetGameplaySyncBridge.BroadcastHostBossDiscreteEvent(netBossDiscreteEvent); } } catch (Exception ex) { Plugin.Log.Warn("[CousinPool] OnLocalBossDiscreteEvent failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void HandleHostBossDiscreteEvent(NetBossDiscreteEvent msg) { //IL_059e: 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_0344: Unknown result type (might be due to invalid IL or missing references) //IL_0349: Unknown result type (might be due to invalid IL or missing references) //IL_030d: Unknown result type (might be due to invalid IL or missing references) try { if (!Enabled || msg == null || NetGameplaySyncBridge.BossMode != NetMode.Client) { return; } if (!TryFindLocalEncounter(msg.EncounterKey, out IBossEncounterAdapter adapter, out object component) && (msg.EventName == null || !msg.EventName.StartsWith("Dialog:", StringComparison.Ordinal) || !TryResolveAndRegisterBossOwnDialog(null, out string _, out adapter, out component))) { if (LogOn) { Plugin.Log.Info("[CousinPool] client has no local encounter for " + msg.ToCompact()); } return; } if (msg.EventName != null && msg.EventName.StartsWith("PikeJump", StringComparison.Ordinal) && msg.HasJump) { BeginApply(); try { ApplyClientBossPikeJump(msg, component); return; } finally { EndApply(); } } if (msg.EventName != null && msg.EventName.StartsWith("Missile", StringComparison.Ordinal)) { ApplyClientMissileWindow(msg, component); return; } if (msg.EventName == "TerrorRootAngle" && msg.HasPos) { TerrorbaumMechanicSync.ApplyRootAngle(component, msg.Position.x); return; } if (msg.EventName == "TerrorRootStop") { TerrorbaumMechanicSync.ApplyRootStop(component); return; } if (msg.EventName != null && msg.EventName.StartsWith("TerrorRootLash:", StringComparison.Ordinal)) { if (int.TryParse(msg.EventName.Substring("TerrorRootLash:".Length), out var result)) { TerrorbaumMechanicSync.ApplyLash(component, result); } return; } if (msg.EventName == "TerrorAbsorb") { TerrorbaumMechanicSync.ApplyAbsorb(component); return; } if (msg.EventName != null && msg.EventName.StartsWith("TerrorVolley:", StringComparison.Ordinal)) { string[] array = msg.EventName.Split(new char[1] { ':' }); if (array.Length >= 9 && int.TryParse(array[1], out var result2) && float.TryParse(array[2], NumberStyles.Float, CultureInfo.InvariantCulture, out var result3) && float.TryParse(array[3], NumberStyles.Float, CultureInfo.InvariantCulture, out var result4) && int.TryParse(array[4], out var result5) && int.TryParse(array[5], out var result6) && int.TryParse(array[6], out var result7) && int.TryParse(array[7], out var result8) && int.TryParse(array[8], out var result9)) { TerrorbaumMechanicSync.ApplyVolley(component, result2, result3, result4, result5, result6, result7, result8, result9); } return; } if (msg.EventName != null && msg.EventName.StartsWith("TerrorSky:", StringComparison.Ordinal) && msg.HasPos) { string[] array2 = msg.EventName.Split(new char[1] { ':' }); if (array2.Length >= 7 && float.TryParse(array2[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result10) && int.TryParse(array2[2], out var result11) && int.TryParse(array2[3], out var result12) && int.TryParse(array2[4], out var result13) && int.TryParse(array2[5], out var result14) && int.TryParse(array2[6], out var result15)) { TerrorbaumMechanicSync.ApplySkySpike(component, msg.Position, result10, result11, result12, result13, result14, result15); } return; } if (string.Equals(msg.EventName, "ArenaPos", StringComparison.Ordinal) && msg.HasPos) { _desertArenaTarget = msg.Position; return; } bool flag = false; try { flag = adapter.IsTerminalEvent(msg.EventName); } catch { } if (!flag) { MarkCousinFightLoopStarted(msg.EncounterKey); } if (flag) { bool flag2; lock (_lock) { flag2 = _terminalDead.Add(msg.EncounterKey); } if (!flag2) { if (LogOn) { Plugin.Log.Info("[CousinDeath] client already terminal key=" + msg.EncounterKey); } return; } Plugin.Log.Info("[CousinDeath] client received death event " + msg.ToCompact() + "; local Cousin resolved"); } bool flag3 = string.Equals(msg.EventName, "DialogClose", StringComparison.Ordinal); if (flag3) { lock (_lock) { _applyingHostDialogClose = true; _bossOwnDialogLocallyOpen.Remove(msg.EncounterKey); if (_pendingMirrorKey == msg.EncounterKey) { _pendingMirrorKey = null; _pendingMirrorEvent = null; } } } bool flag4 = msg.EventName != null && msg.EventName.StartsWith("Dialog:", StringComparison.Ordinal); bool flag5 = false; try { flag5 = flag4 && adapter.BroadcastsBossOwnDialog(component); } catch { } if (flag5) { bool flag6; lock (_lock) { flag6 = _bossOwnDialogLocallyOpen.Contains(msg.EncounterKey); } if (flag6) { if (LogOn) { Plugin.Log.Info("[BossDialogSync] dialog open mirror skipped (already open locally) key=" + msg.EncounterKey); } return; } if (!ArenaLockdownManager.IsLocalPlayerInActiveArena()) { lock (_lock) { _pendingMirrorKey = msg.EncounterKey; _pendingMirrorEvent = msg.EventName; } if (LogOn) { Plugin.Log.Info("[BossDialogSync] dialog open mirror deferred (local player not in arena yet) key=" + msg.EncounterKey); } return; } lock (_lock) { _applyingHostDialogOpen = true; _pendingMirrorKey = null; } } BeginApply(); bool flag7; string detail; try { flag7 = adapter.TryApplyDiscreteEvent(component, msg.EventName, msg.HasPos, msg.Position, out detail); } finally { EndApply(); if (flag3) { lock (_lock) { _applyingHostDialogClose = false; } } if (flag5) { lock (_lock) { _applyingHostDialogOpen = false; } } } if (flag7 && flag5) { lock (_lock) { _bossOwnDialogLocallyOpen.Add(msg.EncounterKey); } } if (flag7) { BossDiscreteApplied++; } if (flag) { if (flag7) { BossDeathApplied++; } Plugin.Log.Info($"[CousinDeath] applied local death key={msg.EncounterKey} ok={flag7}: {detail}; further BossDamage suppressed"); } else if (LogOn) { Plugin.Log.Info($"[CousinPool] client applied discrete {msg.ToCompact()} ok={flag7}: {detail}"); } } catch (Exception ex) { Plugin.Log.Warn("[CousinPool] HandleHostBossDiscreteEvent failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void OnHostBossDeath(object component, string eventName) { try { if (!Enabled || component == null || NetGameplaySyncBridge.BossMode != NetMode.Host) { return; } try { if (!Plugin.Cfg.EnableBossDiscreteEventAuthority.Value) { return; } } catch { return; } if (InReentry || !TryGetEncounterKeyForBoss(component, out string key, out string bossType)) { return; } lock (_lock) { if (!_terminalDead.Add(key)) { return; } } if (!TryGetRunContext(out string chapter, out int level, out bool hasSeed, out int seed)) { chapter = ""; level = -1; } BossDeathBroadcast++; Plugin.Log.Info("[CousinDeath] host detected " + eventName + "; broadcast encounter death key=" + key); NetGameplaySyncBridge.BroadcastHostBossDiscreteEvent(new NetBossDiscreteEvent { EncounterKey = key, BossType = bossType, EventName = eventName, HasPos = false, ChapterName = chapter, LevelIndex = level, HasSeed = hasSeed, Seed = seed, Seq = ++_bossDiscreteSeq }); } catch (Exception ex) { Plugin.Log.Warn("[CousinDeath] OnHostBossDeath failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void HandleHostBossHitVisual(NetHostBossHitVisual msg) { try { if (!Enabled || msg == null || NetGameplaySyncBridge.BossMode != NetMode.Client || !TryFindLocalEncounter(msg.EncounterKey, out IBossEncounterAdapter adapter, out object component)) { return; } object obj = null; try { obj = adapter.ResolveHostTargetForRole(component, msg.TargetRole); } catch { } if (obj != null) { bool flag = NetGameplayProbeManager.TryPlayBossHitVisual(obj); if (flag) { BossHitVisualPlayed++; } if (LogOn) { Plugin.Log.Info($"[BossDamage] client hit-visual {msg.ToCompact()} localTarget={BossReflect.RootName(obj)} played={flag} (visual-only, no local damage)"); } } } catch (Exception ex) { Plugin.Log.Warn("[BossDamage] HandleHostBossHitVisual failed: " + ex.GetType().Name + ": " + ex.Message); } } public static bool OnLocalLuciaEyeDied(object component, object eyeUnit) { try { if (!Enabled || component == null || !LuciaEyeEnabled) { return true; } if (InReentry) { return true; } IBossEncounterAdapter bossEncounterAdapter = ResolveAdapter(component); if (bossEncounterAdapter == null || !bossEncounterAdapter.IsEyeBoss) { return true; } NetMode bossMode = NetGameplaySyncBridge.BossMode; if (bossMode == NetMode.Host) { return true; } bool flag = false; try { flag = NetClientJoinFlow.SessionJoinedHost; } catch { } if (bossMode != NetMode.Client || !flag) { return true; } if (!TryBuildContext(out BossEncounterContext ctx, out string runScope)) { return true; } OnLevelChanged(runScope); string key = bossEncounterAdapter.BuildEncounterId(component, in ctx).Key; Register(key, bossEncounterAdapter, component); bossEncounterAdapter.TryReadEyePhase(component, out var cycle, out var livingEyes); bool flag2 = bossEncounterAdapter.TryRemoveDeadEyeFromList(component, eyeUnit); bossEncounterAdapter.TryReadEyePhase(component, out livingEyes, out var livingEyes2); int reportSeq = ++_luciaEyeReportSeq; NetLuciaEyeReport netLuciaEyeReport = new NetLuciaEyeReport { EncounterKey = key, BossType = component.GetType().Name, ChapterName = ctx.ChapterName, LevelIndex = ctx.LevelIndex, HasSeed = ctx.HasSeed, Seed = ctx.Seed, Cycle = cycle, LocalRemaining = livingEyes2, ReportSeq = reportSeq, ClientPeerId = "", SentAt = Time.realtimeSinceStartup }; LuciaEyeReportSent++; Plugin.Log.Info($"[LuciaEye] local eye defeated key={key} encounter={component.GetType().Name} cycle={cycle} localRemaining={livingEyes2} removedFromList={flag2}"); Plugin.Log.Info("[LuciaEye] report sent " + netLuciaEyeReport.ToCompact()); NetGameplaySyncBridge.SendClientLuciaEyeReport(netLuciaEyeReport); return false; } catch (Exception ex) { Plugin.Log.Warn("[LuciaEye] OnLocalLuciaEyeDied failed: " + ex.GetType().Name + ": " + ex.Message); return true; } } public static void OnHostLuciaEyeDied(object component) { try { if (!Enabled || component == null || !LuciaEyeEnabled || NetGameplaySyncBridge.BossMode != NetMode.Host) { return; } IBossEncounterAdapter bossEncounterAdapter = ResolveAdapter(component); if (bossEncounterAdapter != null && bossEncounterAdapter.IsEyeBoss && TryBuildContext(out BossEncounterContext ctx, out string runScope)) { OnLevelChanged(runScope); NetBossEncounterId netBossEncounterId = bossEncounterAdapter.BuildEncounterId(component, in ctx); Register(netBossEncounterId.Key, bossEncounterAdapter, component); bossEncounterAdapter.TryReadEyePhase(component, out var cycle, out var livingEyes); if (livingEyes == 0) { Plugin.Log.Info($"[LuciaEye] host cycle complete (eyes→0, cycle={cycle}); vanilla RestartPhases runs host-side key={netBossEncounterId.Key}"); } BroadcastLuciaEyeState(netBossEncounterId.Key, bossEncounterAdapter, component, in ctx); } } catch (Exception ex) { Plugin.Log.Warn("[LuciaEye] OnHostLuciaEyeDied failed: " + ex.GetType().Name + ": " + ex.Message); } } private static void BroadcastLuciaEyeState(string key, IBossEncounterAdapter adapter, object component, in BossEncounterContext ctx) { adapter.TryReadEyePhase(component, out var cycle, out var livingEyes); NetLuciaEyeState netLuciaEyeState = new NetLuciaEyeState { EncounterKey = key, BossType = component.GetType().Name, ChapterName = ctx.ChapterName, LevelIndex = ctx.LevelIndex, HasSeed = ctx.HasSeed, Seed = ctx.Seed, Cycle = cycle, LivingEyes = livingEyes, Revision = ++_luciaEyeStateRevision, Timestamp = Time.realtimeSinceStartup }; LuciaEyeStateBroadcast++; if (LogOn) { Plugin.Log.Info("[LuciaEye] host broadcasting eye state " + netLuciaEyeState.ToCompact()); } NetGameplaySyncBridge.BroadcastHostLuciaEyeState(netLuciaEyeState); } public static void HandleClientLuciaEyeReport(NetLuciaEyeReport req, string peerId) { try { if (!Enabled || req == null || NetGameplaySyncBridge.BossMode != NetMode.Host || !LuciaEyeEnabled) { return; } LuciaEyeReportRecv++; Plugin.Log.Info("[LuciaEye] report received from " + peerId + ": " + req.ToCompact()); if (!TryBuildContext(out BossEncounterContext ctx, out string _) || !string.Equals(ctx.ChapterName, req.ChapterName, StringComparison.Ordinal) || ctx.LevelIndex != req.LevelIndex || (ctx.HasSeed && req.HasSeed && ctx.Seed != req.Seed)) { LuciaEyeRejected++; Plugin.Log.Warn($"[LuciaEye] rejected from {peerId}: run mismatch (req={req.ChapterName}:{req.LevelIndex} host={ctx.ChapterName}:{ctx.LevelIndex})"); return; } int remaining = req.ReportSeq; string item = peerId + ":" + remaining; lock (_lock) { if (!_luciaEyeConsumed.Add(item)) { LuciaEyeRejected++; Plugin.Log.Info($"[LuciaEye] rejected from {peerId}: duplicate report (seq={req.ReportSeq})"); return; } } if (!TryFindLocalEncounter(req.EncounterKey, out IBossEncounterAdapter adapter, out object component)) { LuciaEyeRejected++; Plugin.Log.Warn("[LuciaEye] rejected from " + peerId + ": missing host encounter key=" + req.EncounterKey + "; candidates: " + DescribeCandidates()); return; } if (!adapter.IsEyeBoss) { LuciaEyeRejected++; Plugin.Log.Warn("[LuciaEye] rejected from " + peerId + ": wrong encounter (not an eye boss) key=" + req.EncounterKey); return; } if (!adapter.TryReadEyePhase(component, out var cycle, out var livingEyes)) { LuciaEyeRejected++; Plugin.Log.Warn($"[LuciaEye] rejected from {peerId}: not in eye phase (hostCycle={cycle} livingEyes={livingEyes}) key={req.EncounterKey}"); return; } if (livingEyes <= 0) { LuciaEyeRejected++; Plugin.Log.Warn($"[LuciaEye] rejected from {peerId}: no living host eyes (hostCycle={cycle}) key={req.EncounterKey}"); return; } Plugin.Log.Info($"[LuciaEye] accepted from {peerId} key={req.EncounterKey} reportCycle={req.Cycle} hostCycle={cycle} hostLivingBefore={livingEyes}"); BeginApply(); bool flag; string detail; try { flag = adapter.TryConsumeOneEye(component, out remaining, out detail); } finally { EndApply(); } adapter.TryReadEyePhase(component, out remaining, out var livingEyes2); if (flag) { LuciaEyeAccepted++; Plugin.Log.Info($"[LuciaEye] consumed host eye key={req.EncounterKey} {detail} hostLivingBefore={livingEyes} hostLivingAfter={livingEyes2} EyeDiedObserved={livingEyes2 < livingEyes} RestartPhasesObserved(lastEye)={livingEyes2 == 0}"); } else { LuciaEyeRejected++; Plugin.Log.Warn($"[LuciaEye] consume FAILED key={req.EncounterKey}: {detail} (nativeDeathFailed); hostLivingBefore={livingEyes} hostLivingAfter={livingEyes2}"); } } catch (Exception ex) { Plugin.Log.Warn("[LuciaEye] HandleClientLuciaEyeReport failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void HandleHostLuciaEyeState(NetLuciaEyeState msg) { //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) try { if (!Enabled || msg == null || NetGameplaySyncBridge.BossMode != NetMode.Client || !LuciaEyeEnabled) { return; } if (!TryFindLocalEncounter(msg.EncounterKey, out IBossEncounterAdapter adapter, out object component)) { if (LogOn) { Plugin.Log.Info("[LuciaEye] client has no local encounter for " + msg.ToCompact()); } } else { if (!adapter.IsEyeBoss) { return; } LuciaEyeStateApplied++; Plugin.Log.Info("[LuciaEye] host remaining applied " + msg.ToCompact()); if (msg.LivingEyes > 0) { return; } string item = msg.EncounterKey + ":" + msg.Cycle; bool flag; lock (_lock) { flag = _luciaEyeCycleComplete.Add(item); } if (!flag) { return; } Plugin.Log.Info($"[LuciaEye] client cycle-complete received key={msg.EncounterKey} cycle={msg.Cycle}"); int phase; int restartCounter; bool invulnerable; Vector3 pos; bool flag2 = adapter.TryReadEyeCompletionDiag(component, out phase, out restartCounter, out invulnerable, out pos); BeginApply(); bool flag3; int cleared; string detail; try { flag3 = adapter.TryApplyEyePhaseComplete(component, out cleared, out detail); } finally { EndApply(); } if (flag3) { LuciaEyeResidualCleared += cleared; } Plugin.Log.Info("[LuciaEye] client RestartPhases " + (flag3 ? "invoked" : "FAILED") + " key=" + msg.EncounterKey + " (" + detail + ")"); Plugin.Log.Info($"[LuciaEye] local residual eyes cleared count={cleared}"); if (flag2) { Plugin.Log.Info($"[LuciaEye] before: currentPhase={phase} restartCounter={restartCounter} invulnerable={invulnerable} pos={pos:F1}"); lock (_lock) { _pendingEyeComplete.Add(new PendingEyeComplete { Key = msg.EncounterKey, Cycle = msg.Cycle, PhaseBefore = phase, RestartBefore = restartCounter, InvulnBefore = invulnerable, PosBefore = pos, DueAt = Time.realtimeSinceStartup + 11f }); return; } } } } catch (Exception ex) { Plugin.Log.Warn("[LuciaEye] HandleHostLuciaEyeState failed: " + ex.GetType().Name + ": " + ex.Message); } } public static bool OnLocalLuciaBossDead_Pre(object component, object unit) { try { if (!Enabled || component == null || !LuciaDeathEnabled) { return true; } if (InReentry) { return true; } NetMode bossMode = NetGameplaySyncBridge.BossMode; if (bossMode == NetMode.Host) { return true; } bool flag = false; try { flag = NetClientJoinFlow.SessionJoinedHost; } catch { } if (bossMode != NetMode.Client || !flag) { return true; } if (LogOn) { Plugin.Log.Info("[LuciaDeath] client OnBossDead loot/save body BLOCKED (isolated) root=" + BossReflect.RootName(component)); } return false; } catch { return true; } } public static void OnHostLuciaBossDead_Post(object component, object unit) { try { if (!Enabled || component == null || !LuciaDeathEnabled || NetGameplaySyncBridge.BossMode != NetMode.Host) { return; } IBossEncounterAdapter bossEncounterAdapter = ResolveAdapter(component); if (bossEncounterAdapter == null || !bossEncounterAdapter.IsEyeBoss || !TryGetEncounterKeyForBoss(component, out string key, out string bossType)) { return; } lock (_lock) { if (!_terminalDead.Add(key)) { return; } } Plugin.Log.Info("[LuciaDeath] host detected real death key=" + key); if (!TryGetRunContext(out string chapter, out int level, out bool hasSeed, out int seed)) { chapter = ""; level = -1; } LuciaDeathBroadcast++; NetLuciaDeath netLuciaDeath = new NetLuciaDeath { EncounterKey = key, BossType = bossType, ChapterName = chapter, LevelIndex = level, HasSeed = hasSeed, Seed = seed, Revision = ++_luciaDeathRevision, Timestamp = Time.realtimeSinceStartup }; Plugin.Log.Info("[LuciaDeath] host broadcast terminal event " + netLuciaDeath.ToCompact()); NetGameplaySyncBridge.BroadcastHostLuciaDeath(netLuciaDeath); } catch (Exception ex) { Plugin.Log.Warn("[LuciaDeath] OnHostLuciaBossDead_Post failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void HandleHostLuciaDeath(NetLuciaDeath msg) { try { if (!Enabled || msg == null || NetGameplaySyncBridge.BossMode != NetMode.Client || !LuciaDeathEnabled) { return; } Plugin.Log.Info("[LuciaDeath] client received " + msg.ToCompact()); bool flag; lock (_lock) { flag = _terminalDead.Add(msg.EncounterKey); } if (!flag) { Plugin.Log.Info("[LuciaDeath] client already terminal key=" + msg.EncounterKey); return; } if (!TryFindLocalEncounter(msg.EncounterKey, out IBossEncounterAdapter adapter, out object component)) { Plugin.Log.Warn("[LuciaDeath] client has no local encounter for key=" + msg.EncounterKey + "; candidates: " + DescribeCandidates()); return; } if (!adapter.IsEyeBoss) { Plugin.Log.Warn("[LuciaDeath] client encounter not Lucia key=" + msg.EncounterKey); return; } Plugin.Log.Info("[LuciaDeath] local Lucia resolved key=" + msg.EncounterKey + " root=" + BossReflect.RootName(component)); bool flag2; string detail; try { flag2 = adapter.TryApplyLuciaDeath(component, out detail); } catch (Exception ex) { flag2 = false; detail = "exception " + ex.GetType().Name + ": " + ex.Message; } if (flag2) { LuciaDeathApplied++; } Plugin.Log.Info($"[LuciaDeath] local death applied key={msg.EncounterKey} ok={flag2}: {detail}"); Plugin.Log.Info("[LuciaDeath] boss bar cleared key=" + msg.EncounterKey); Plugin.Log.Info("[LuciaDeath] terminal hit/state suppression enabled key=" + msg.EncounterKey); } catch (Exception ex2) { Plugin.Log.Warn("[LuciaDeath] HandleHostLuciaDeath failed: " + ex2.GetType().Name + ": " + ex2.Message); } } public static bool OnLocalWitchChangePhase_Pre(object component, int phaseInt) { try { if (!Enabled || component == null || !WitchPhaseAuthorityEnabled) { return true; } if (InReentry) { return true; } NetMode bossMode = NetGameplaySyncBridge.BossMode; if (bossMode == NetMode.Host) { return true; } bool flag = false; try { flag = NetClientJoinFlow.SessionJoinedHost; } catch { } if (bossMode != NetMode.Client || !flag) { return true; } WitchPhaseBlockedLocal++; if (LogOn) { Plugin.Log.Info($"[WitchPhase] client blocked local transition ->{phaseInt} reason=host-authority (local self-advance suppressed)"); } return false; } catch { return true; } } public static void OnHostWitchChangePhase_Post(object component, int phaseInt) { try { if (!Enabled || component == null || !WitchPhaseAuthorityEnabled || NetGameplaySyncBridge.BossMode != NetMode.Host || !(ResolveAdapter(component) is WitchBossControllerAdapter witchBossControllerAdapter)) { return; } int currentPhase = witchBossControllerAdapter.GetCurrentPhase(component); if (currentPhase != phaseInt || currentPhase == _lastHostWitchPhase) { return; } _lastHostWitchPhase = currentPhase; if (currentPhase != 3) { OnHostWitchPhase2Ended(); } if (TryGetEncounterKeyForBoss(component, out string key, out string bossType)) { if (!TryGetRunContext(out string chapter, out int level, out bool hasSeed, out int seed)) { chapter = ""; level = -1; } int num = ++_witchPhaseRevision; bool flag = witchBossControllerAdapter.IsStarted(component); Plugin.Log.Info($"[WitchPhase] host transition phase={currentPhase} revision={num} fightStarted={flag} key={key}"); NetGameplaySyncBridge.BroadcastHostWitchPhase(new NetWitchPhase { EncounterKey = key, BossType = bossType, ChapterName = chapter, LevelIndex = level, HasSeed = hasSeed, Seed = seed, PhaseIndex = currentPhase, PhaseRevision = num, FightStarted = flag, Timestamp = Time.realtimeSinceStartup }); WitchPhaseBroadcast++; } } catch (Exception ex) { Plugin.Log.Warn("[WitchPhase] OnHostWitchChangePhase_Post failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void HandleHostWitchPhase(NetWitchPhase msg) { try { if (!Enabled || msg == null || NetGameplaySyncBridge.BossMode != NetMode.Client || !WitchPhaseAuthorityEnabled) { return; } if (!TryFindLocalEncounter(msg.EncounterKey, out IBossEncounterAdapter adapter, out object component)) { if (LogOn) { Plugin.Log.Info("[WitchPhase] client has no local encounter for " + msg.ToCompact()); } return; } if (!(adapter is WitchBossControllerAdapter witchBossControllerAdapter)) { if (LogOn) { Plugin.Log.Info("[WitchPhase] client encounter not a Witch key=" + msg.EncounterKey); } return; } int value; lock (_lock) { _witchAppliedRevision.TryGetValue(msg.EncounterKey, out value); } int currentPhase = witchBossControllerAdapter.GetCurrentPhase(component); Plugin.Log.Info($"[WitchPhase] client received phase={msg.PhaseIndex} revision={msg.PhaseRevision} (localPhase={currentPhase} appliedRev={value})"); if (msg.PhaseRevision <= value) { WitchPhaseStaleIgnored++; if (LogOn) { Plugin.Log.Info(string.Format("[WitchPhase] client {0} revision ignored (msg={1} applied={2})", (msg.PhaseRevision == value) ? "duplicate" : "stale", msg.PhaseRevision, value)); } return; } witchBossControllerAdapter.EndCurrentWitchPhase(component, out string detail); BeginApply(); bool flag; string detail2; try { flag = witchBossControllerAdapter.ApplyHostPhase(component, msg.PhaseIndex, out detail2); } finally { EndApply(); } lock (_lock) { _witchAppliedRevision[msg.EncounterKey] = msg.PhaseRevision; } int currentPhase2 = witchBossControllerAdapter.GetCurrentPhase(component); if (flag) { WitchPhaseApplied++; } Plugin.Log.Info($"[WitchPhase] client apply local={currentPhase}/rev={value} -> host={msg.PhaseIndex}/rev={msg.PhaseRevision} changePhase={flag} (teardown: {detail}; {detail2}); nowPhase={currentPhase2} activeWitchUnit={witchBossControllerAdapter.ActivePhaseWitchExists(component)}"); } catch (Exception ex) { Plugin.Log.Warn("[WitchPhase] HandleHostWitchPhase failed: " + ex.GetType().Name + ": " + ex.Message); } } public static bool IsWitchPhase2Suppressed(object runtimeObject) { try { if (!Enabled || runtimeObject == null) { return false; } if (!WitchP2ManifestEnabled) { return false; } int num = BossReflect.InstanceId(runtimeObject); return num != 0 && WitchBossControllerAdapter.IsInstancePhase2Suppressed(num); } catch { return false; } } public static bool OnLocalWitchShowWitches_Pre(object witchPhase2) { try { if (!Enabled || witchPhase2 == null || !WitchP2ManifestEnabled) { return true; } if (InReentry) { return true; } NetMode bossMode = NetGameplaySyncBridge.BossMode; if (bossMode == NetMode.Host) { return true; } bool flag = false; try { flag = NetClientJoinFlow.SessionJoinedHost; } catch { } if (bossMode != NetMode.Client || !flag) { return true; } if (LogOn) { Plugin.Log.Info("[WitchP2] client blocked local ShowWitches (manifest drives dome layout)"); } return false; } catch { return true; } } public static void OnHostWitchShowWitches(object witchPhase2) { try { if (!Enabled || witchPhase2 == null || !WitchP2ManifestEnabled || NetGameplaySyncBridge.BossMode != NetMode.Host || !TryFindWitchEncounter(out WitchBossControllerAdapter adapter, out object controller, out string key)) { return; } int hostRealDomeIndex = adapter.GetHostRealDomeIndex(controller); int hostDomeCount = adapter.GetHostDomeCount(controller); if (hostRealDomeIndex < 0 || hostDomeCount <= 0) { Plugin.Log.Warn($"[WitchP2] host ShowWitches capture failed realDome={hostRealDomeIndex} domes={hostDomeCount}"); return; } if (!TryGetRunContext(out string chapter, out int level, out bool hasSeed, out int seed)) { chapter = ""; level = -1; } _witchP2Cycle++; _witchP2Active = true; _witchP2RealHitConsumed = false; _witchP2DefeatedDomes.Clear(); NetWitchP2Manifest netWitchP2Manifest = new NetWitchP2Manifest { EncounterKey = key, BossType = controller.GetType().Name, ChapterName = chapter, LevelIndex = level, HasSeed = hasSeed, Seed = seed, PhaseRevision = _witchP2Cycle, DomeCount = hostDomeCount, RealDomeIndex = hostRealDomeIndex, Timestamp = Time.realtimeSinceStartup }; WitchP2ManifestBroadcast++; Plugin.Log.Info($"[WitchP2] host ShowWitches captured + broadcasting {netWitchP2Manifest.ToCompact()} (cycle={_witchP2Cycle})"); NetGameplaySyncBridge.BroadcastHostWitchP2Manifest(netWitchP2Manifest); } catch (Exception ex) { Plugin.Log.Warn("[WitchP2] OnHostWitchShowWitches failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void OnHostWitchP2Hit(object witchPhase2, object hitUnit, bool realHit) { try { if (!Enabled || witchPhase2 == null || !WitchP2ManifestEnabled || NetGameplaySyncBridge.BossMode != NetMode.Host || !_witchP2Active || !TryFindWitchEncounter(out WitchBossControllerAdapter _, out object controller, out string key)) { return; } object member = BossReflect.GetMember(controller, "phase2"); int num = -1; byte kind; if (realHit) { if (_witchP2RealHitConsumed || !(BossReflect.TryGetBool(member, "illusionsDissappeared", out var value) && value)) { return; } _witchP2RealHitConsumed = true; kind = 1; } else { if (hitUnit == null) { return; } if (BossReflect.GetMember(member, "spawnedWitches") is IList list) { for (int i = 0; i < list.Count; i++) { if (list[i] == hitUnit) { num = i; break; } } } if (num < 0 || _witchP2DefeatedDomes.Contains(num)) { return; } bool flag = false; if (BossReflect.GetMember(member, "appearingWitches") is IDictionary dictionary && dictionary.Contains(hitUnit) && dictionary[hitUnit] is bool flag2) { flag = flag2; } if (!flag) { return; } _witchP2DefeatedDomes.Add(num); kind = 0; } NetWitchP2Result netWitchP2Result = new NetWitchP2Result { EncounterKey = key, PhaseRevision = _witchP2Cycle, DomeIndex = num, Kind = kind }; WitchP2ResultBroadcast++; Plugin.Log.Info($"[WitchP2] host result + broadcasting {netWitchP2Result.ToCompact()} (cycle={_witchP2Cycle})"); NetGameplaySyncBridge.BroadcastHostWitchP2Result(netWitchP2Result); } catch (Exception ex) { Plugin.Log.Warn("[WitchP2] OnHostWitchP2Hit failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void OnHostWitchPhase2Ended() { _witchP2Active = false; } private static void MarkWitchTerminal(object component, string ctx) { _witchP2Active = false; if (TryGetEncounterKeyForBoss(component, out string key, out string _)) { bool flag; lock (_lock) { flag = _terminalDead.Add(key); } if (flag) { WitchDeathTerminal++; Plugin.Log.Info("[WitchDeath] witch terminal key=" + key + " (" + ctx + ") — hits/state suppressed"); } } } public static void OnHostWitchDeath(object component) { try { if (!Enabled || component == null) { return; } try { if (!Plugin.Cfg.EnableWitchDeathFix.Value) { return; } } catch { return; } MarkWitchTerminal(component, "host"); } catch (Exception ex) { Plugin.Log.Warn("[WitchDeath] OnHostWitchDeath failed: " + ex.GetType().Name + ": " + ex.Message); } } public static bool OnLocalWitchDeath_Pre(object component) { try { if (!Enabled || component == null) { return true; } try { if (!Plugin.Cfg.EnableWitchDeathFix.Value) { return true; } } catch { return true; } if (InReentry) { return true; } NetMode bossMode = NetGameplaySyncBridge.BossMode; if (bossMode == NetMode.Host) { return true; } bool flag = false; try { flag = NetClientJoinFlow.SessionJoinedHost; } catch { } if (bossMode != NetMode.Client || !flag) { return true; } if (!(ResolveAdapter(component) is WitchBossControllerAdapter witchBossControllerAdapter)) { return true; } BeginApply(); bool flag2; string detail; try { flag2 = witchBossControllerAdapter.TryApplyWitchDeath(component, out detail); } finally { EndApply(); } if (flag2) { WitchDeathClientReplica++; } Plugin.Log.Info($"[WitchDeath] client safe death replica ok={flag2} ({detail})"); MarkWitchTerminal(component, "client-replica"); return false; } catch (Exception ex) { Plugin.Log.Warn("[WitchDeath] OnLocalWitchDeath_Pre failed: " + ex.GetType().Name + ": " + ex.Message); return true; } } public static void HandleHostWitchP2Manifest(NetWitchP2Manifest msg) { try { if (!Enabled || msg == null || NetGameplaySyncBridge.BossMode != NetMode.Client || !WitchP2ManifestEnabled) { return; } if (!TryFindLocalEncounter(msg.EncounterKey, out IBossEncounterAdapter adapter, out object component) || !(adapter is WitchBossControllerAdapter witchBossControllerAdapter)) { if (LogOn) { Plugin.Log.Info("[WitchP2] client has no Witch encounter for " + msg.ToCompact()); } } else if (!witchBossControllerAdapter.IsPhase2Ready(component, msg.DomeCount)) { lock (_lock) { _pendingP2Manifest = new PendingP2Manifest { Msg = msg, Until = Time.realtimeSinceStartup + 10f }; } Plugin.Log.Info("[WitchP2] client manifest received but local not ready → pending " + msg.ToCompact()); } else { ApplyP2ManifestNow(witchBossControllerAdapter, component, msg); } } catch (Exception ex) { Plugin.Log.Warn("[WitchP2] HandleHostWitchP2Manifest failed: " + ex.GetType().Name + ": " + ex.Message); } } private static void ApplyP2ManifestNow(WitchBossControllerAdapter adapter, object component, NetWitchP2Manifest msg) { BeginApply(); bool flag; string detail; try { flag = adapter.ApplyP2Manifest(component, msg.RealDomeIndex, msg.DomeCount, out int _, out detail); } finally { EndApply(); } if (flag) { WitchP2ManifestApplied++; _p2ClientAppliedCycle = msg.PhaseRevision; } Plugin.Log.Info($"[WitchP2] client manifest applied ok={flag} {msg.ToCompact()} ({detail})"); } public static void HandleHostWitchP2Result(NetWitchP2Result msg) { try { if (!Enabled || msg == null || NetGameplaySyncBridge.BossMode != NetMode.Client || !WitchP2ManifestEnabled) { return; } if (!TryFindLocalEncounter(msg.EncounterKey, out IBossEncounterAdapter adapter, out object component) || !(adapter is WitchBossControllerAdapter witchBossControllerAdapter)) { if (LogOn) { Plugin.Log.Info("[WitchP2] client has no Witch encounter for result " + msg.ToCompact()); } return; } if (msg.PhaseRevision != _p2ClientAppliedCycle) { if (LogOn) { Plugin.Log.Info($"[WitchP2] client dropped stale result {msg.ToCompact()} (appliedCycle={_p2ClientAppliedCycle})"); } return; } BeginApply(); bool flag; string detail; try { flag = witchBossControllerAdapter.ApplyP2Result(component, msg.DomeIndex, msg.Kind, out detail); } finally { EndApply(); } if (flag) { WitchP2ResultApplied++; } Plugin.Log.Info($"[WitchP2] client result applied ok={flag} {msg.ToCompact()} ({detail})"); } catch (Exception ex) { Plugin.Log.Warn("[WitchP2] HandleHostWitchP2Result failed: " + ex.GetType().Name + ": " + ex.Message); } } private static bool TryFindWitchEncounter(out WitchBossControllerAdapter adapter, out object controller, out string key) { adapter = null; controller = null; key = ""; lock (_lock) { foreach (KeyValuePair item in _registry) { if (item.Value.Adapter is WitchBossControllerAdapter witchBossControllerAdapter) { object component = item.Value.Component; Object val = (Object)((component is Object) ? component : null); if (val != null && val != (Object)null) { adapter = witchBossControllerAdapter; controller = item.Value.Component; key = item.Key; return true; } } } } if (TryBuildContext(out BossEncounterContext ctx, out string _)) { IBossEncounterAdapter[] adapters = _adapters; foreach (IBossEncounterAdapter bossEncounterAdapter in adapters) { if (!(bossEncounterAdapter is WitchBossControllerAdapter witchBossControllerAdapter2)) { continue; } Type type = bossEncounterAdapter.ResolveType(); if (type == null) { continue; } using IEnumerator enumerator2 = FindSceneComponents(type).GetEnumerator(); if (enumerator2.MoveNext()) { object current2 = enumerator2.Current; NetBossEncounterId netBossEncounterId = bossEncounterAdapter.BuildEncounterId(current2, in ctx); Register(netBossEncounterId.Key, bossEncounterAdapter, current2); adapter = witchBossControllerAdapter2; controller = current2; key = netBossEncounterId.Key; return true; } } } return false; } public static void HandleClientBossStartRequest(NetClientBossStartRequest req, string peerId) { try { if (!Enabled || req == null || NetGameplaySyncBridge.BossMode != NetMode.Host) { return; } BossStartRequestReceived++; if (LogOn) { Plugin.Log.Info("[BossEncounter] host received ClientBossStartRequest from " + peerId + ": " + req.ToCompact()); } if (!TryBuildContext(out BossEncounterContext ctx, out string _) || !string.Equals(ctx.ChapterName, req.ChapterName, StringComparison.Ordinal) || ctx.LevelIndex != req.LevelIndex || (ctx.HasSeed && req.HasSeed && ctx.Seed != req.Seed)) { BossStartRejectedSession++; Plugin.Log.Warn($"[BossEncounter] reject boss start request from {peerId}: run mismatch req={req.ChapterName}:{req.LevelIndex} host={ctx.ChapterName}:{ctx.LevelIndex}"); return; } if (!TryFindLocalEncounter(req.EncounterKey, out IBossEncounterAdapter adapter, out object component)) { BossEncounterNotFound++; Plugin.Log.Warn("[BossEncounter] host has no local encounter for key=" + req.EncounterKey + "; candidates: " + DescribeCandidates()); return; } if (!adapter.IsStarted(component)) { BeginApply(); bool flag; string detail; try { flag = adapter.TryApplyHostStart(component, BuildState(req.EncounterKey, adapter, component, in ctx, "ClientRequest:" + req.StartSource), out detail); } finally { EndApply(); } if (!flag) { BossStartApplyFailed++; Plugin.Log.Warn("[BossEncounter] host failed to start boss for client request key=" + req.EncounterKey + ": " + detail); return; } if (LogOn) { Plugin.Log.Info("[BossEncounter] host started boss on client request key=" + req.EncounterKey + ": " + detail); } } lock (_lock) { _hostBroadcast.Add(req.EncounterKey); } NetBossEncounterState netBossEncounterState = BuildState(req.EncounterKey, adapter, component, in ctx, "ClientRequest:" + req.StartSource); BossStartBroadcast++; if (LogOn) { Plugin.Log.Info("[BossEncounter] host broadcasting BossEncounterStart (from request) " + netBossEncounterState.ToCompact()); } NetGameplaySyncBridge.BroadcastHostBossEncounterStart(netBossEncounterState); TryBeginSandstormArena(adapter, component, in ctx); CloseBossOwnDialogOnFightStart(adapter, component, req.EncounterKey); } catch (Exception ex) { Plugin.Log.Warn("[BossEncounter] HandleClientBossStartRequest failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void HandleHostBossEncounterStart(NetBossEncounterState state) { try { if (!Enabled || state == null || NetGameplaySyncBridge.BossMode != NetMode.Client) { return; } if (LogOn) { Plugin.Log.Info("[BossEncounter] client received HostBossEncounterStart " + state.ToCompact()); } bool flag = state.StartSource != null && state.StartSource.EndsWith(".TriggerFight", StringComparison.Ordinal); bool flag2; lock (_lock) { flag2 = _appliedStart.Add(state.EncounterKey); } if (!flag2 && !flag) { if (LogOn) { Plugin.Log.Info("[BossEncounter] start already applied key=" + state.EncounterKey); } return; } if (!TryFindLocalEncounter(state.EncounterKey, out IBossEncounterAdapter adapter, out object component)) { BossEncounterNotFound++; Plugin.Log.Warn("[BossEncounter] client has no local encounter for key=" + state.EncounterKey + "; candidates: " + DescribeCandidates()); return; } CloseBossOwnDialogOnFightStart(adapter, component, state.EncounterKey); if (adapter.IsStarted(component)) { if (LogOn) { Plugin.Log.Info("[BossEncounter] client boss already started key=" + state.EncounterKey); } return; } if (flag && !flag2 && adapter.IsCombatEntrySource(component, state.StartSource)) { OpenContinuation(state.EncounterKey, adapter, component, "HostCombatEntry:" + state.StartSource); BeginApply(); bool flag3; string detail; try { flag3 = adapter.TryApplyCombatEntry(component, out detail); } finally { EndApply(); } if (flag3) { BossStartApplied++; Plugin.Log.Info("[BossEncounter] client applied combat-entry key=" + state.EncounterKey + ": " + detail + " after[" + SafeDescribe(adapter, component) + "]"); try { adapter.OnClientPresentationStart(component); return; } catch { return; } } BossStartApplyFailed++; Plugin.Log.Warn("[BossEncounter] client failed to apply combat-entry key=" + state.EncounterKey + ": " + detail); return; } OpenContinuation(state.EncounterKey, adapter, component, "HostBossEncounterStart:" + state.StartSource); string text = SafeDescribe(adapter, component); BeginApply(); bool flag4; string detail2; try { flag4 = adapter.TryApplyHostStart(component, state, out detail2); } finally { EndApply(); } if (flag4) { BossStartApplied++; Plugin.Log.Info("[BossEncounter] client applied start source=HostAuthority key=" + state.EncounterKey + " invoked=" + detail2 + " before[" + text + "] after[" + SafeDescribe(adapter, component) + "]"); try { adapter.OnClientPresentationStart(component); } catch { } lock (_lock) { _pendingVerify.Add(new PendingVerify { Key = state.EncounterKey, Source = state.StartSource, Invoked = detail2, Before = text, DueAt = Time.realtimeSinceStartup + 2f }); return; } } BossStartApplyFailed++; Plugin.Log.Warn("[BossEncounter] client failed to apply host start key=" + state.EncounterKey + ": " + detail2); } catch (Exception ex) { Plugin.Log.Warn("[BossEncounter] HandleHostBossEncounterStart failed: " + ex.GetType().Name + ": " + ex.Message); } } private static bool TryFindLocalEncounter(string key, out IBossEncounterAdapter adapter, out object component) { lock (_lock) { if (_registry.TryGetValue(key, out Entry value)) { object component2 = value.Component; Object val = (Object)((component2 is Object) ? component2 : null); if (val != null && val != (Object)null) { adapter = value.Adapter; component = value.Component; return true; } } } if (!TryBuildContext(out BossEncounterContext ctx, out string _)) { adapter = null; component = null; return false; } IBossEncounterAdapter[] adapters = _adapters; foreach (IBossEncounterAdapter bossEncounterAdapter in adapters) { Type type = bossEncounterAdapter.ResolveType(); if (type == null) { continue; } foreach (object item in FindSceneComponents(type)) { if (bossEncounterAdapter.BuildEncounterId(item, in ctx).Key == key) { Register(key, bossEncounterAdapter, item); adapter = bossEncounterAdapter; component = item; return true; } } } adapter = null; component = null; return false; } private static string DescribeCandidates() { if (!TryBuildContext(out BossEncounterContext ctx, out string _)) { return "no-run-state"; } List list = new List(); IBossEncounterAdapter[] adapters = _adapters; foreach (IBossEncounterAdapter bossEncounterAdapter in adapters) { Type type = bossEncounterAdapter.ResolveType(); if (type == null) { continue; } foreach (object item in FindSceneComponents(type)) { NetBossEncounterId netBossEncounterId = bossEncounterAdapter.BuildEncounterId(item, in ctx); Component val = (Component)((item is Component) ? item : null); string text = ((val == null || !((Object)(object)val != (Object)null)) ? "?" : (val.gameObject.activeInHierarchy ? "active" : "inactive")); list.Add(netBossEncounterId.Key + "[" + text + "]"); if (list.Count >= 8) { break; } } } if (list.Count != 0) { return string.Join(" ; ", list); } return "none"; } private static IEnumerable FindSceneComponents(Type t) { Object[] array; try { array = Resources.FindObjectsOfTypeAll(t); } catch { yield break; } Object[] array2 = array; foreach (Object val in array2) { if (val == (Object)null) { continue; } Component val2 = (Component)(object)((val is Component) ? val : null); if (val2 != null && !((Object)(object)val2 == (Object)null) && !((Object)(object)val2.gameObject == (Object)null)) { Scene scene = val2.gameObject.scene; if (((Scene)(ref scene)).IsValid() && ((Scene)(ref scene)).isLoaded) { yield return val; } } } } private static void TickHostPurePuppetBossTarget() { //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02a0: Unknown result type (might be due to invalid IL or missing references) try { float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _nextPurePuppetTargetCheck) { return; } _nextPurePuppetTargetCheck = realtimeSinceStartup + 0.25f; List> list = null; lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; if (value.Adapter == null) { continue; } object component = value.Component; Object val = (Object)((component is Object) ? component : null); if (val != null && !(val == (Object)null)) { bool flag = false; try { flag = value.Adapter.ClientBossIsPurePuppet(value.Component); } catch { } if (flag && !_terminalDead.Contains(item.Key)) { (list ?? (list = new List>())).Add(item); } } } } if (list == null) { return; } foreach (KeyValuePair item2 in list) { IBossEncounterAdapter adapter = item2.Value.Adapter; object component2 = item2.Value.Component; if (!SafeStarted(adapter, component2)) { continue; } object? healthUnit = adapter.GetHealthUnit(component2); Component val2 = (Component)((healthUnit is Component) ? healthUnit : null); if (val2 == null || (Object)(object)val2 == (Object)null) { continue; } object obj2 = BossReflect.GetMember(val2, "AiAgent") ?? BossReflect.GetMember(val2, "aiAgent"); if (obj2 == null) { continue; } FieldInfo field = obj2.GetType().GetField("target", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); FieldInfo field2 = obj2.GetType().GetField("lastTarget", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null || field2 == null) { continue; } bool filter = false; bool gated = false; HashSet members = null; try { filter = Plugin.Cfg.ExcludeOutOfRoomPlayersFromBossAttacks.Value; if (filter) { gated = ArenaLockdownManager.TryGetActiveArenaInRoom(out members); } } catch { } object value2 = field.GetValue(obj2); object value3 = field2.GetValue(obj2); if (IsValid(value2) && IsValid(value3)) { continue; } List list2 = CousinArmPatches.GatherPlayerUnits(); object obj4 = null; float num = float.MaxValue; Vector3 position = val2.transform.position; foreach (object item3 in list2) { if (!IsValid(item3)) { continue; } Component val3 = (Component)((item3 is Component) ? item3 : null); if (val3 != null && !((Object)(object)val3 == (Object)null)) { Vector3 val4 = val3.transform.position - position; float sqrMagnitude = ((Vector3)(ref val4)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; obj4 = item3; } } } if (obj4 != null) { if (!IsValid(value2)) { field.SetValue(obj2, obj4); } if (!IsValid(value3)) { field2.SetValue(obj2, obj4); } if (realtimeSinceStartup - _lastBossTargetLogAt >= 5f) { _lastBossTargetLogAt = realtimeSinceStartup; STLogger log = Plugin.Log; string[] obj5 = new string[7] { "[BossTarget] host aimed pure-puppet boss at ", null, null, null, null, null, null }; object obj6 = ((obj4 is Component) ? obj4 : null); obj5[1] = ((obj6 != null) ? ((Object)obj6).name : null) ?? obj4.ToString(); obj5[2] = " key="; obj5[3] = item2.Key; obj5[4] = " (target was "; obj5[5] = ((value2 == null) ? "null" : "unattackable"); obj5[6] = ")"; log.Info(string.Concat(obj5)); } } bool IsValid(object? u) { if (u != null) { Object val5 = (Object)((u is Object) ? u : null); if (val5 == null || !(val5 == (Object)null)) { if (filter) { return CousinArmPatches.IsTargetAttackable(u, gated, members); } return true; } } return false; } } } catch (Exception ex) { Plugin.Log.Warn("[BossTarget] TickHostPurePuppetBossTarget failed: " + ex.GetType().Name + ": " + ex.Message); } } private static void TickHostTerrorMechanics() { try { float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _nextTerrorMechTick) { return; } _nextTerrorMechTick = realtimeSinceStartup + 0.12f; List list = null; lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; if (value.Adapter == null) { continue; } object component = value.Component; Object val = (Object)((component is Object) ? component : null); if (val != null && !(val == (Object)null)) { bool flag = false; try { flag = value.Adapter.ClientBossIsPurePuppet(value.Component); } catch { } if (flag && !_terminalDead.Contains(item.Key) && SafeStarted(value.Adapter, value.Component)) { (list ?? (list = new List())).Add(value.Component); } } } } if (list == null) { return; } foreach (object item2 in list) { TerrorbaumMechanicSync.HostStreamRoot(item2); } } catch { } } private static void TickBossVisibilityProbe() { //IL_0316: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Unknown result type (might be due to invalid IL or missing references) //IL_02b7: Unknown result type (might be due to invalid IL or missing references) //IL_02bc: Unknown result type (might be due to invalid IL or missing references) try { float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _nextBossVisProbe) { return; } _nextBossVisProbe = realtimeSinceStartup + 5f; List> list = null; lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; if (value.Adapter == null) { continue; } object component = value.Component; Object val = (Object)((component is Object) ? component : null); if (val != null && !(val == (Object)null)) { bool flag = false; try { flag = value.Adapter.ClientBossIsPurePuppet(value.Component); } catch { } if (flag && !_terminalDead.Contains(item.Key)) { (list ?? (list = new List>())).Add(item); } } } } if (list == null) { return; } foreach (KeyValuePair item2 in list) { if (!SafeStarted(item2.Value.Adapter, item2.Value.Component)) { continue; } object? healthUnit = item2.Value.Adapter.GetHealthUnit(item2.Value.Component); Component val2 = (Component)((healthUnit is Component) ? healthUnit : null); if (val2 == null || (Object)(object)val2 == (Object)null) { continue; } GameObject gameObject = val2.gameObject; Renderer[] componentsInChildren = gameObject.GetComponentsInChildren(true); int num = componentsInChildren.Length; int num2 = 0; int num3 = 0; int num4 = 0; Renderer[] array = componentsInChildren; foreach (Renderer val3 in array) { if (!((Object)(object)val3 == (Object)null)) { if (val3.enabled) { num2++; } if (val3.forceRenderingOff) { num3++; } if (val3.isVisible) { num4++; } } } Animator componentInChildren = gameObject.GetComponentInChildren(true); string text = (((Object)(object)componentInChildren == (Object)null) ? "none" : $"enabled={((Behaviour)componentInChildren).enabled} culling={componentInChildren.cullingMode} speed={componentInChildren.speed:0.0}"); object? member = BossReflect.GetMember(val2, "currentRoom"); object? obj2 = ((member is Component) ? member : null); string text2 = ((obj2 != null) ? ((Object)obj2).name : null) ?? "?"; string text3 = "?"; try { object? obj3 = BossDamageReflect.ResolveHostPlayerUnit(); Component val4 = (Component)((obj3 is Component) ? obj3 : null); if ((Object)(object)val4 != (Object)null) { object? member2 = BossReflect.GetMember(val4, "currentRoom"); object? obj4 = ((member2 is Component) ? member2 : null); string arg = ((obj4 != null) ? ((Object)obj4).name : null) ?? "?"; object arg2 = val4.transform.position; Vector3 val5 = val4.transform.position - val2.transform.position; text3 = $"playerPos={arg2:F1} playerRoom={arg} dist={((Vector3)(ref val5)).magnitude:0.0}"; } } catch { } Plugin.Log.Info($"[BossVis] mode={NetGameplaySyncBridge.BossMode} boss={((Object)gameObject).name} active={gameObject.activeInHierarchy} pos={val2.transform.position:F1} renderers={num} enabled={num2} forceOff={num3} visible={num4} animator[{text}] bossRoom={text2} {text3} key={item2.Key}"); } } catch (Exception ex) { Plugin.Log.Warn("[BossVis] probe failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void OnHostTerrorStateEvent(object helper, string eventName, bool hasPos, Vector3 pos) { //IL_004a: 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) try { if (Enabled && helper != null && NetGameplaySyncBridge.BossMode == NetMode.Host && TryGetEncounterKeyForBoss(helper, out string key, out string bossType)) { NetGameplaySyncBridge.BroadcastHostBossDiscreteEvent(new NetBossDiscreteEvent { EncounterKey = key, BossType = bossType, EventName = eventName, HasPos = hasPos, Position = pos }); if (LogOn) { Plugin.Log.Info("[BossAnimSync] host broadcast " + eventName + (hasPos ? $" pos={pos:F1}" : "") + " key=" + key); } } } catch (Exception ex) { Plugin.Log.Warn("[BossAnimSync] OnHostTerrorStateEvent failed: " + ex.GetType().Name + ": " + ex.Message); } } public static bool ShouldBlockClientPurePuppetBossAnimDamage(object helper) { try { if (!Enabled || helper == null || NetGameplaySyncBridge.BossMode != NetMode.Client) { return false; } return ResolveAdapter(helper)?.ClientBossIsPurePuppet(helper) ?? false; } catch { return false; } } public static void Tick() { //IL_0678: Unknown result type (might be due to invalid IL or missing references) //IL_0682: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Unknown result type (might be due to invalid IL or missing references) try { if (!Enabled) { return; } TickHostBossStateBroadcast(); if (NetGameplaySyncBridge.BossMode == NetMode.Client) { List> list = null; lock (_lock) { foreach (KeyValuePair item in _registry) { Entry value = item.Value; if (value.Adapter == null) { continue; } object component = value.Component; Object val = (Object)((component is Object) ? component : null); if (val != null && !(val == (Object)null)) { bool flag = false; try { flag = value.Adapter.ClientBossIsPurePuppet(value.Component); } catch { } if (flag) { (list ?? (list = new List>())).Add(item); } } } } if (list != null) { foreach (KeyValuePair item2 in list) { if (item2.Value.Adapter.SuppressClientMechanics(item2.Value.Component, out string detail) > 0) { Plugin.Log.Info("[BossPuppet] suppressed client boss mechanics key=" + item2.Key + ": " + detail); } } } } if (NetGameplaySyncBridge.BossMode == NetMode.Host) { TickHostPurePuppetBossTarget(); } if (NetGameplaySyncBridge.BossMode == NetMode.Host) { TickHostTerrorMechanics(); } else { TerrorbaumMechanicSync.TickClient(); FlushClientAbsorbBatch(); } TickBossVisibilityProbe(); string pendingMirrorKey; string pendingMirrorEvent; lock (_lock) { pendingMirrorKey = _pendingMirrorKey; pendingMirrorEvent = _pendingMirrorEvent; } if (pendingMirrorKey != null) { bool flag2 = false; if (!TryFindLocalEncounter(pendingMirrorKey, out IBossEncounterAdapter adapter, out object component2)) { flag2 = true; } else if (SafeStarted(adapter, component2)) { flag2 = true; } else { lock (_lock) { if (_bossOwnDialogLocallyOpen.Contains(pendingMirrorKey)) { flag2 = true; } } } if (flag2) { lock (_lock) { if (_pendingMirrorKey == pendingMirrorKey) { _pendingMirrorKey = null; _pendingMirrorEvent = null; } } } else if (ArenaLockdownManager.IsLocalPlayerInActiveArena()) { lock (_lock) { _applyingHostDialogOpen = true; _pendingMirrorKey = null; _pendingMirrorEvent = null; } try { string detail2; bool flag3 = adapter.TryApplyDiscreteEvent(component2, pendingMirrorEvent, hasPos: false, Vector3.zero, out detail2); if (flag3) { lock (_lock) { _bossOwnDialogLocallyOpen.Add(pendingMirrorKey); } } Plugin.Log.Info($"[BossDialogSync] dialog open mirror catch-up (entered arena) key={pendingMirrorKey} ok={flag3}: {detail2}"); } finally { lock (_lock) { _applyingHostDialogOpen = false; } } } } string text = null; lock (_lock) { if (_pendingDialogFinalize.Count > 0) { float realtimeSinceStartup = Time.realtimeSinceStartup; for (int num = _pendingDialogFinalize.Count - 1; num >= 0; num--) { if (realtimeSinceStartup > _pendingDialogFinalize[num].Until) { _pendingDialogFinalize.RemoveAt(num); } } if (_pendingDialogFinalize.Count > 0) { text = _pendingDialogFinalize[0].Key; } } } if (text != null) { try { if (BossDialogReflect.IsDialogActive() && BossDialogReflect.TryFinalizeCurrentDialog(out string detail3)) { Plugin.Log.Info("[BossDialogCommit] deferred dialog finalized key=" + text + ": " + detail3); } } catch { } } PendingP2Manifest pendingP2Manifest = null; lock (_lock) { pendingP2Manifest = _pendingP2Manifest; } if (pendingP2Manifest != null) { IBossEncounterAdapter adapter2; object component3; if (Time.realtimeSinceStartup > pendingP2Manifest.Until) { lock (_lock) { if (_pendingP2Manifest == pendingP2Manifest) { _pendingP2Manifest = null; } } Plugin.Log.Warn("[WitchP2] pending manifest expired (local never ready) " + pendingP2Manifest.Msg.ToCompact()); } else if (TryFindLocalEncounter(pendingP2Manifest.Msg.EncounterKey, out adapter2, out component3) && adapter2 is WitchBossControllerAdapter witchBossControllerAdapter && witchBossControllerAdapter.IsPhase2Ready(component3, pendingP2Manifest.Msg.DomeCount)) { lock (_lock) { if (_pendingP2Manifest == pendingP2Manifest) { _pendingP2Manifest = null; } } Plugin.Log.Info("[WitchP2] client applying pending manifest (now ready) " + pendingP2Manifest.Msg.ToCompact()); ApplyP2ManifestNow(witchBossControllerAdapter, component3, pendingP2Manifest.Msg); } } List list2 = null; lock (_lock) { if (_pendingEyeComplete.Count > 0) { float realtimeSinceStartup2 = Time.realtimeSinceStartup; for (int num2 = _pendingEyeComplete.Count - 1; num2 >= 0; num2--) { if (_pendingEyeComplete[num2].DueAt <= realtimeSinceStartup2) { (list2 ?? (list2 = new List())).Add(_pendingEyeComplete[num2]); _pendingEyeComplete.RemoveAt(num2); } } } } if (list2 != null) { foreach (PendingEyeComplete item3 in list2) { if (!TryFindLocalEncounter(item3.Key, out IBossEncounterAdapter adapter3, out object component4) || !adapter3.TryReadEyeCompletionDiag(component4, out var phase, out var restartCounter, out var invulnerable, out var pos)) { Plugin.Log.Warn($"[LuciaEye] cycle-complete verify: encounter gone key={item3.Key} cycle={item3.Cycle}"); continue; } Plugin.Log.Info($"[LuciaEye] currentPhase {item3.PhaseBefore} -> {phase}"); Plugin.Log.Info($"[LuciaEye] restartCounter {item3.RestartBefore} -> {restartCounter}"); Plugin.Log.Info($"[LuciaEye] invulnerable {item3.InvulnBefore} -> {invulnerable}"); Plugin.Log.Info($"[LuciaEye] position {item3.PosBefore:F1} -> {pos:F1}"); bool num3 = phase != 5; bool flag4 = restartCounter > item3.RestartBefore; if (num3 || flag4) { Plugin.Log.Info($"[LuciaEye] restart routine / StartPhase observed (phase {item3.PhaseBefore}->{phase}, restartCounter {item3.RestartBefore}->{restartCounter}) key={item3.Key}"); } else { Plugin.Log.Warn(string.Format("[LuciaEye] STILL stuck after RestartPhases key={0}: phase remained {1}{2}{3} (return-to-center / StartPhase did not run)", item3.Key, phase, (phase == 5) ? " (phase remained 5)" : "", invulnerable ? " (invulnerable remained true)" : "")); } } } List list3 = null; lock (_lock) { if (_pendingVerify.Count > 0) { float realtimeSinceStartup3 = Time.realtimeSinceStartup; for (int num4 = _pendingVerify.Count - 1; num4 >= 0; num4--) { if (_pendingVerify[num4].DueAt <= realtimeSinceStartup3) { (list3 ?? (list3 = new List())).Add(_pendingVerify[num4]); _pendingVerify.RemoveAt(num4); } } } } if (list3 != null) { foreach (PendingVerify item4 in list3) { if (!TryFindLocalEncounter(item4.Key, out IBossEncounterAdapter adapter4, out object component5)) { continue; } bool num5 = SafeStarted(adapter4, component5); ContinuationWindow window; bool flag5 = IsContinuationActive(item4.Key, out window); if (num5) { if (LogOn) { Plugin.Log.Info($"[BossEncounter] host-authority start verified key={item4.Key} source={item4.Source} after[{SafeDescribe(adapter4, component5)}] continuationActive={flag5}"); } } else { BossApplyIncomplete++; Plugin.Log.Warn($"[BossEncounter] host-authority start incomplete key={item4.Key} source={item4.Source} invoked={item4.Invoked} after[{SafeDescribe(adapter4, component5)}] continuationActive={flag5} expected={DescribeExpectedChain(adapter4)}"); } } } lock (_lock) { if (_continuation.Count <= 0) { return; } float realtimeSinceStartup4 = Time.realtimeSinceStartup; List list4 = null; foreach (KeyValuePair item5 in _continuation) { ContinuationWindow value2 = item5.Value; if (value2.StartObserved && realtimeSinceStartup4 - value2.StartObservedAt > ContinuationGraceSeconds) { (list4 ?? (list4 = new List())).Add(item5.Key); } } if (list4 == null) { return; } foreach (string item6 in list4) { _continuation.Remove(item6); } } } catch (Exception ex) { Plugin.Log.Warn("[BossEncounter] Tick failed: " + ex.GetType().Name + ": " + ex.Message); } } private static string DescribeExpectedChain(IBossEncounterAdapter adapter) { try { return (adapter.StartChainMethods.Length == 0) ? "" : string.Join("->", adapter.StartChainMethods); } catch { return ""; } } public static string FormatCounters() { return $"discovered={BossEncounterDiscovered} broadcast={BossStartBroadcast} reqSent={BossStartRequestSent} reqRecv={BossStartRequestReceived} " + $"applied={BossStartApplied} applyFailed={BossStartApplyFailed} blocked={BossLocalStartBlocked} rejectSession={BossStartRejectedSession} notFound={BossEncounterNotFound} " + $"contAllowed={BossContinuationAllowed} contBlocked={BossContinuationBlocked} applyIncomplete={BossApplyIncomplete} dupSuppressed={BossDuplicateSuppressed} " + $"dlgReq={BossDialogCommitRequested} dlgBroadcast={BossDialogCommitBroadcast} dlgApplied={BossDialogCommitApplied} dlgEntrySuppressed={BossDialogEntrySuppressed} stateBroadcast={BossStateBroadcast} stateApplied={BossStateApplied} " + $"hitSent={BossHitClientSent} hitRecv={BossHitHostRecv} hitApplied={BossHitHostApplied} hitRejected={BossHitHostRejected} hitVisSent={BossHitVisualSent} hitVisPlayed={BossHitVisualPlayed} " + $"poolSent={BossDiscreteSent} poolApplied={BossDiscreteApplied} poolClientBlocked={BossDiscreteClientSelfFired} deathBroadcast={BossDeathBroadcast} deathApplied={BossDeathApplied} terminal={_terminalDead.Count} " + $"eyeReportSent={LuciaEyeReportSent} eyeReportRecv={LuciaEyeReportRecv} eyeAccepted={LuciaEyeAccepted} eyeRejected={LuciaEyeRejected} eyeStateBroadcast={LuciaEyeStateBroadcast} eyeStateApplied={LuciaEyeStateApplied} eyeResidualCleared={LuciaEyeResidualCleared} " + $"luciaDeathBroadcast={LuciaDeathBroadcast} luciaDeathApplied={LuciaDeathApplied} " + $"witchPhaseBroadcast={WitchPhaseBroadcast} witchPhaseApplied={WitchPhaseApplied} witchPhaseBlockedLocal={WitchPhaseBlockedLocal} witchPhaseStaleIgnored={WitchPhaseStaleIgnored} " + $"witchP2ManifestBroadcast={WitchP2ManifestBroadcast} witchP2ManifestApplied={WitchP2ManifestApplied} witchP2ResultBroadcast={WitchP2ResultBroadcast} witchP2ResultApplied={WitchP2ResultApplied} registry={_registry.Count}"; } } internal sealed class NetClientBossStartRequest { public string EncounterKey { get; set; } = ""; public string BossType { get; set; } = ""; public string GraphName { get; set; } = ""; public string RootName { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasSeed { get; set; } public int Seed { get; set; } public string ClientPeerId { get; set; } = ""; public string StartSource { get; set; } = ""; public float SentAt { get; set; } public string ToCompact() { return string.Format("key={0} type={1} graph={2} run={3}:{4} peer={5} src={6}", EncounterKey, BossType, string.IsNullOrEmpty(GraphName) ? "?" : GraphName, ChapterName, LevelIndex, ClientPeerId, StartSource); } } internal sealed class NetBossDialogCommit { public string EncounterKey { get; set; } = ""; public string BossType { get; set; } = ""; public string GraphName { get; set; } = ""; public string RootName { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasSeed { get; set; } public int Seed { get; set; } public string CommitSource { get; set; } = ""; public int Revision { get; set; } public float Timestamp { get; set; } public bool IsFightCommit { get; set; } public string ToCompact() { return string.Format("key={0} type={1} run={2}:{3} src={4} rev={5} phase={6}", EncounterKey, BossType, ChapterName, LevelIndex, CommitSource, Revision, IsFightCommit ? "FIGHT" : "intro"); } } internal sealed class NetBossState { public string EncounterKey { get; set; } = ""; public string BossType { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasSeed { get; set; } public int Seed { get; set; } public int PhaseIndex { get; set; } public bool FightStarted { get; set; } public bool IntroFinished { get; set; } public bool HasHealth { get; set; } public float CurrentHealth { get; set; } public float MaxHealth { get; set; } public int AliveAdds { get; set; } public int Revision { get; set; } public float Timestamp { get; set; } public string ToCompact() { return string.Format("key={0} type={1} phase={2} fightStarted={3} hp={4} adds={5} rev={6}", EncounterKey, BossType, PhaseIndex, FightStarted, HasHealth ? $"{CurrentHealth:0}/{MaxHealth:0}" : "?", AliveAdds, Revision); } } internal sealed class NetBossDynamicSpawn { public string EncounterKey { get; set; } = ""; public string OwnerBossType { get; set; } = ""; public string AddType { get; set; } = ""; public string AddUnitId { get; set; } = ""; public int SequenceIndex { get; set; } public Vector3 Position { get; set; } public int HostInstanceId { get; set; } public int UnitIdValue { get; set; } public int HostSpawnIndex { get; set; } public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasSeed { get; set; } public int Seed { get; set; } public int Revision { get; set; } public float Timestamp { get; set; } public string ToCompact() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) return string.Format("key={0} owner={1} add={2} seq={3} unitId={4} pos={5:F1} inst={6}", EncounterKey, OwnerBossType, AddType, SequenceIndex, string.IsNullOrEmpty(AddUnitId) ? "?" : AddUnitId, Position, HostInstanceId); } } internal sealed class NetClientBossHitRequest { public string EncounterKey { get; set; } = ""; public string BossType { get; set; } = ""; public string RootName { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasSeed { get; set; } public int Seed { get; set; } public string TargetRole { get; set; } = "main"; public float Damage { get; set; } public int DamageTypeInt { get; set; } public int RequestSeq { get; set; } public string ClientPeerId { get; set; } = ""; public float SentAt { get; set; } public string ToCompact() { return $"key={EncounterKey} type={BossType} role={TargetRole} dmg={Damage:0.0} dtype={DamageTypeInt} run={ChapterName}:{LevelIndex} seq={RequestSeq}"; } } internal sealed class NetHostBossHitVisual { public string EncounterKey { get; set; } = ""; public string BossType { get; set; } = ""; public string TargetRole { get; set; } = "main"; public string TargetUnitId { get; set; } = ""; public int Seq { get; set; } public string ToCompact() { return string.Format("key={0} role={1} unitId={2} seq={3}", EncounterKey, TargetRole, string.IsNullOrEmpty(TargetUnitId) ? "?" : TargetUnitId, Seq); } } internal sealed class NetBossDiscreteEvent { public string EncounterKey { get; set; } = ""; public string BossType { get; set; } = ""; public string EventName { get; set; } = ""; public bool HasPos { get; set; } public Vector3 Position { get; set; } public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasSeed { get; set; } public int Seed { get; set; } public int Seq { get; set; } public bool HasJump { get; set; } public Vector3 JumpStart { get; set; } public float JumpAirTimer { get; set; } public float JumpHeight { get; set; } public float JumpDepth { get; set; } public float JumpSpawnPct { get; set; } public string ToCompact() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) object[] obj = new object[7] { EncounterKey, EventName, null, null, null, null, null }; object obj2; if (!HasPos) { obj2 = "?"; } else { Vector3 position = Position; obj2 = ((Vector3)(ref position)).ToString("F1"); } obj[2] = obj2; obj[3] = ChapterName; obj[4] = LevelIndex; obj[5] = Seq; obj[6] = (HasJump ? $" jump[start={JumpStart:F1} air={JumpAirTimer:F2} h={JumpHeight:F1} d={JumpDepth:F1}]" : ""); return string.Format("key={0} event={1} pos={2} run={3}:{4} seq={5}{6}", obj); } } internal sealed class NetLuciaEyeReport { public string EncounterKey { get; set; } = ""; public string BossType { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasSeed { get; set; } public int Seed { get; set; } public int Cycle { get; set; } public int LocalRemaining { get; set; } = -1; public int ReportSeq { get; set; } public string ClientPeerId { get; set; } = ""; public float SentAt { get; set; } public string ToCompact() { return $"key={EncounterKey} run={ChapterName}:{LevelIndex} cycle={Cycle} localRemaining={LocalRemaining} seq={ReportSeq} peer={ClientPeerId}"; } } internal sealed class NetLuciaEyeState { public string EncounterKey { get; set; } = ""; public string BossType { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasSeed { get; set; } public int Seed { get; set; } public int Cycle { get; set; } public int LivingEyes { get; set; } public int Revision { get; set; } public float Timestamp { get; set; } public string ToCompact() { return $"key={EncounterKey} cycle={Cycle} livingEyes={LivingEyes} rev={Revision}"; } } internal sealed class NetLuciaDeath { public string EncounterKey { get; set; } = ""; public string BossType { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasSeed { get; set; } public int Seed { get; set; } public int Revision { get; set; } public float Timestamp { get; set; } public string ToCompact() { return $"key={EncounterKey} run={ChapterName}:{LevelIndex} rev={Revision}"; } } internal sealed class NetWitchPhase { public string EncounterKey { get; set; } = ""; public string BossType { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasSeed { get; set; } public int Seed { get; set; } public int PhaseIndex { get; set; } public int PhaseRevision { get; set; } public bool FightStarted { get; set; } public float Timestamp { get; set; } public string ToCompact() { return $"key={EncounterKey} phase={PhaseIndex} revision={PhaseRevision} fightStarted={FightStarted}"; } } internal sealed class NetWitchP2Manifest { public string EncounterKey { get; set; } = ""; public string BossType { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasSeed { get; set; } public int Seed { get; set; } public int PhaseRevision { get; set; } public int DomeCount { get; set; } public int RealDomeIndex { get; set; } public float Timestamp { get; set; } public string ToCompact() { return $"key={EncounterKey} rev={PhaseRevision} domes={DomeCount} realDome={RealDomeIndex}"; } } internal sealed class NetWitchP2Result { public const byte KindIllusionDefeated = 0; public const byte KindRealHit = 1; public string EncounterKey { get; set; } = ""; public int PhaseRevision { get; set; } public int DomeIndex { get; set; } public byte Kind { get; set; } public string ToCompact() { return string.Format("key={0} rev={1} dome={2} kind={3}", EncounterKey, PhaseRevision, DomeIndex, (Kind == 1) ? "realHit" : "illusionDefeated"); } } internal sealed class NetClientRoomEnter { public string EncounterKey { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasSeed { get; set; } public int Seed { get; set; } public string EntrySource { get; set; } = ""; public float Timestamp { get; set; } public string ToCompact() { return $"key={EncounterKey} run={ChapterName}:{LevelIndex} src={EntrySource}"; } } internal sealed class NetHostRoomMembership { public string EncounterKey { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasSeed { get; set; } public int Seed { get; set; } public string[] PlayerIds { get; set; } = Array.Empty(); public float Timestamp { get; set; } public string ToCompact() { return string.Format("key={0} run={1}:{2} members=[{3}]", EncounterKey, ChapterName, LevelIndex, string.Join(",", PlayerIds ?? Array.Empty())); } } internal static class NetBossEncounterCodec { private const byte RequestVersion = 1; private const byte StateVersion = 1; private const byte DialogCommitVersion = 2; private const byte BossStateVersion = 1; private const byte DynamicSpawnVersion = 1; private const byte BossHitVersion = 1; private const byte BossHitVisualVersion = 1; private const byte DiscreteEventVersion = 2; private const byte LuciaEyeReportVersion = 1; private const byte LuciaEyeStateVersion = 1; private const byte LuciaDeathVersion = 1; private const byte WitchPhaseVersion = 1; private const byte WitchP2ManifestVersion = 1; private const byte WitchP2ResultVersion = 1; private const byte RoomEnterVersion = 1; private const byte RoomMembershipVersion = 1; public static void WriteRoomEnter(NetDataWriter w, NetClientRoomEnter m) { w.Put((byte)1); w.Put(m.EncounterKey ?? ""); w.Put(m.ChapterName ?? ""); w.Put(m.LevelIndex); w.Put(m.HasSeed); if (m.HasSeed) { w.Put(m.Seed); } w.Put(m.EntrySource ?? ""); w.Put(m.Timestamp); } public static bool TryReadRoomEnter(NetDataReader r, out NetClientRoomEnter result) { result = null; try { if (r.GetByte() != 1) { return false; } NetClientRoomEnter netClientRoomEnter = new NetClientRoomEnter { EncounterKey = r.GetString(), ChapterName = r.GetString(), LevelIndex = r.GetInt(), HasSeed = r.GetBool() }; if (netClientRoomEnter.HasSeed) { netClientRoomEnter.Seed = r.GetInt(); } netClientRoomEnter.EntrySource = r.GetString(); netClientRoomEnter.Timestamp = r.GetFloat(); result = netClientRoomEnter; return true; } catch { return false; } } public static void WriteRoomMembership(NetDataWriter w, NetHostRoomMembership m) { w.Put((byte)1); w.Put(m.EncounterKey ?? ""); w.Put(m.ChapterName ?? ""); w.Put(m.LevelIndex); w.Put(m.HasSeed); if (m.HasSeed) { w.Put(m.Seed); } string[] array = m.PlayerIds ?? Array.Empty(); w.Put(array.Length); string[] array2 = array; foreach (string text in array2) { w.Put(text ?? ""); } w.Put(m.Timestamp); } public static bool TryReadRoomMembership(NetDataReader r, out NetHostRoomMembership result) { result = null; try { if (r.GetByte() != 1) { return false; } NetHostRoomMembership netHostRoomMembership = new NetHostRoomMembership { EncounterKey = r.GetString(), ChapterName = r.GetString(), LevelIndex = r.GetInt(), HasSeed = r.GetBool() }; if (netHostRoomMembership.HasSeed) { netHostRoomMembership.Seed = r.GetInt(); } int num = r.GetInt(); if (num < 0 || num > 64) { return false; } string[] array = new string[num]; for (int i = 0; i < num; i++) { array[i] = r.GetString(); } netHostRoomMembership.PlayerIds = array; netHostRoomMembership.Timestamp = r.GetFloat(); result = netHostRoomMembership; return true; } catch { return false; } } public static void WriteRequest(NetDataWriter w, NetClientBossStartRequest r) { w.Put((byte)1); w.Put(r.EncounterKey ?? ""); w.Put(r.BossType ?? ""); w.Put(r.GraphName ?? ""); w.Put(r.RootName ?? ""); w.Put(r.ChapterName ?? ""); w.Put(r.LevelIndex); w.Put(r.HasSeed); if (r.HasSeed) { w.Put(r.Seed); } w.Put(r.ClientPeerId ?? ""); w.Put(r.StartSource ?? ""); w.Put(r.SentAt); } public static bool TryReadRequest(NetDataReader r, out NetClientBossStartRequest result) { result = null; try { if (r.GetByte() != 1) { return false; } NetClientBossStartRequest netClientBossStartRequest = new NetClientBossStartRequest { EncounterKey = r.GetString(), BossType = r.GetString(), GraphName = r.GetString(), RootName = r.GetString(), ChapterName = r.GetString(), LevelIndex = r.GetInt(), HasSeed = r.GetBool() }; if (netClientBossStartRequest.HasSeed) { netClientBossStartRequest.Seed = r.GetInt(); } netClientBossStartRequest.ClientPeerId = r.GetString(); netClientBossStartRequest.StartSource = r.GetString(); netClientBossStartRequest.SentAt = r.GetFloat(); result = netClientBossStartRequest; return true; } catch { return false; } } public static void WriteState(NetDataWriter w, NetBossEncounterState s) { //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) w.Put((byte)1); w.Put(s.EncounterKey ?? ""); w.Put(s.BossType ?? ""); w.Put(s.GraphName ?? ""); w.Put(s.RootName ?? ""); w.Put(s.ChapterName ?? ""); w.Put(s.LevelIndex); w.Put(s.HasSeed); if (s.HasSeed) { w.Put(s.Seed); } w.Put(s.Started); w.Put(s.StartSource ?? ""); w.Put(s.HostRevision); w.Put(s.HostTimestamp); w.Put(s.HasPosition); if (s.HasPosition) { w.Put(s.Position.x); w.Put(s.Position.y); w.Put(s.Position.z); } w.Put(s.HasPhaseIndex); if (s.HasPhaseIndex) { w.Put(s.PhaseIndex); } } public static bool TryReadState(NetDataReader r, out NetBossEncounterState result) { //IL_00d8: Unknown result type (might be due to invalid IL or missing references) result = null; try { if (r.GetByte() != 1) { return false; } NetBossEncounterState netBossEncounterState = new NetBossEncounterState { EncounterKey = r.GetString(), BossType = r.GetString(), GraphName = r.GetString(), RootName = r.GetString(), ChapterName = r.GetString(), LevelIndex = r.GetInt(), HasSeed = r.GetBool() }; if (netBossEncounterState.HasSeed) { netBossEncounterState.Seed = r.GetInt(); } netBossEncounterState.Started = r.GetBool(); netBossEncounterState.StartSource = r.GetString(); netBossEncounterState.HostRevision = r.GetInt(); netBossEncounterState.HostTimestamp = r.GetFloat(); netBossEncounterState.HasPosition = r.GetBool(); if (netBossEncounterState.HasPosition) { netBossEncounterState.Position = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } netBossEncounterState.HasPhaseIndex = r.GetBool(); if (netBossEncounterState.HasPhaseIndex) { netBossEncounterState.PhaseIndex = r.GetInt(); } result = netBossEncounterState; return true; } catch { return false; } } public static void WriteDialogCommit(NetDataWriter w, NetBossDialogCommit m) { w.Put((byte)2); w.Put(m.EncounterKey ?? ""); w.Put(m.BossType ?? ""); w.Put(m.GraphName ?? ""); w.Put(m.RootName ?? ""); w.Put(m.ChapterName ?? ""); w.Put(m.LevelIndex); w.Put(m.HasSeed); if (m.HasSeed) { w.Put(m.Seed); } w.Put(m.CommitSource ?? ""); w.Put(m.Revision); w.Put(m.Timestamp); w.Put(m.IsFightCommit); } public static bool TryReadDialogCommit(NetDataReader r, out NetBossDialogCommit result) { result = null; try { if (r.GetByte() != 2) { return false; } NetBossDialogCommit netBossDialogCommit = new NetBossDialogCommit { EncounterKey = r.GetString(), BossType = r.GetString(), GraphName = r.GetString(), RootName = r.GetString(), ChapterName = r.GetString(), LevelIndex = r.GetInt(), HasSeed = r.GetBool() }; if (netBossDialogCommit.HasSeed) { netBossDialogCommit.Seed = r.GetInt(); } netBossDialogCommit.CommitSource = r.GetString(); netBossDialogCommit.Revision = r.GetInt(); netBossDialogCommit.Timestamp = r.GetFloat(); netBossDialogCommit.IsFightCommit = r.GetBool(); result = netBossDialogCommit; return true; } catch { return false; } } public static void WriteBossState(NetDataWriter w, NetBossState s) { w.Put((byte)1); w.Put(s.EncounterKey ?? ""); w.Put(s.BossType ?? ""); w.Put(s.ChapterName ?? ""); w.Put(s.LevelIndex); w.Put(s.HasSeed); if (s.HasSeed) { w.Put(s.Seed); } w.Put(s.PhaseIndex); w.Put(s.FightStarted); w.Put(s.IntroFinished); w.Put(s.HasHealth); if (s.HasHealth) { w.Put(s.CurrentHealth); w.Put(s.MaxHealth); } w.Put(s.AliveAdds); w.Put(s.Revision); w.Put(s.Timestamp); } public static bool TryReadBossState(NetDataReader r, out NetBossState result) { result = null; try { if (r.GetByte() != 1) { return false; } NetBossState netBossState = new NetBossState { EncounterKey = r.GetString(), BossType = r.GetString(), ChapterName = r.GetString(), LevelIndex = r.GetInt(), HasSeed = r.GetBool() }; if (netBossState.HasSeed) { netBossState.Seed = r.GetInt(); } netBossState.PhaseIndex = r.GetInt(); netBossState.FightStarted = r.GetBool(); netBossState.IntroFinished = r.GetBool(); netBossState.HasHealth = r.GetBool(); if (netBossState.HasHealth) { netBossState.CurrentHealth = r.GetFloat(); netBossState.MaxHealth = r.GetFloat(); } netBossState.AliveAdds = r.GetInt(); netBossState.Revision = r.GetInt(); netBossState.Timestamp = r.GetFloat(); result = netBossState; return true; } catch { return false; } } public static void WriteDynamicSpawn(NetDataWriter w, NetBossDynamicSpawn s) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) w.Put((byte)1); w.Put(s.EncounterKey ?? ""); w.Put(s.OwnerBossType ?? ""); w.Put(s.AddType ?? ""); w.Put(s.AddUnitId ?? ""); w.Put(s.SequenceIndex); w.Put(s.Position.x); w.Put(s.Position.y); w.Put(s.Position.z); w.Put(s.HostInstanceId); w.Put(s.ChapterName ?? ""); w.Put(s.LevelIndex); w.Put(s.HasSeed); if (s.HasSeed) { w.Put(s.Seed); } w.Put(s.Revision); w.Put(s.Timestamp); w.Put(s.UnitIdValue); w.Put(s.HostSpawnIndex); } public static bool TryReadDynamicSpawn(NetDataReader r, out NetBossDynamicSpawn result) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) result = null; try { if (r.GetByte() != 1) { return false; } NetBossDynamicSpawn netBossDynamicSpawn = new NetBossDynamicSpawn { EncounterKey = r.GetString(), OwnerBossType = r.GetString(), AddType = r.GetString(), AddUnitId = r.GetString(), SequenceIndex = r.GetInt(), Position = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()), HostInstanceId = r.GetInt(), ChapterName = r.GetString(), LevelIndex = r.GetInt(), HasSeed = r.GetBool() }; if (netBossDynamicSpawn.HasSeed) { netBossDynamicSpawn.Seed = r.GetInt(); } netBossDynamicSpawn.Revision = r.GetInt(); netBossDynamicSpawn.Timestamp = r.GetFloat(); netBossDynamicSpawn.UnitIdValue = r.GetInt(); netBossDynamicSpawn.HostSpawnIndex = r.GetInt(); result = netBossDynamicSpawn; return true; } catch { return false; } } public static void WriteBossHit(NetDataWriter w, NetClientBossHitRequest h) { w.Put((byte)1); w.Put(h.EncounterKey ?? ""); w.Put(h.BossType ?? ""); w.Put(h.RootName ?? ""); w.Put(h.ChapterName ?? ""); w.Put(h.LevelIndex); w.Put(h.HasSeed); if (h.HasSeed) { w.Put(h.Seed); } w.Put(h.TargetRole ?? "main"); w.Put(h.Damage); w.Put(h.DamageTypeInt); w.Put(h.RequestSeq); w.Put(h.ClientPeerId ?? ""); w.Put(h.SentAt); } public static bool TryReadBossHit(NetDataReader r, out NetClientBossHitRequest result) { result = null; try { if (r.GetByte() != 1) { return false; } NetClientBossHitRequest netClientBossHitRequest = new NetClientBossHitRequest { EncounterKey = r.GetString(), BossType = r.GetString(), RootName = r.GetString(), ChapterName = r.GetString(), LevelIndex = r.GetInt(), HasSeed = r.GetBool() }; if (netClientBossHitRequest.HasSeed) { netClientBossHitRequest.Seed = r.GetInt(); } netClientBossHitRequest.TargetRole = r.GetString(); netClientBossHitRequest.Damage = r.GetFloat(); netClientBossHitRequest.DamageTypeInt = r.GetInt(); netClientBossHitRequest.RequestSeq = r.GetInt(); netClientBossHitRequest.ClientPeerId = r.GetString(); netClientBossHitRequest.SentAt = r.GetFloat(); result = netClientBossHitRequest; return true; } catch { return false; } } public static void WriteBossHitVisual(NetDataWriter w, NetHostBossHitVisual v) { w.Put((byte)1); w.Put(v.EncounterKey ?? ""); w.Put(v.BossType ?? ""); w.Put(v.TargetRole ?? "main"); w.Put(v.TargetUnitId ?? ""); w.Put(v.Seq); } public static bool TryReadBossHitVisual(NetDataReader r, out NetHostBossHitVisual result) { result = null; try { if (r.GetByte() != 1) { return false; } result = new NetHostBossHitVisual { EncounterKey = r.GetString(), BossType = r.GetString(), TargetRole = r.GetString(), TargetUnitId = r.GetString(), Seq = r.GetInt() }; return true; } catch { return false; } } public static void WriteDiscreteEvent(NetDataWriter w, NetBossDiscreteEvent e) { //IL_005c: 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_007e: 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_0101: 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) w.Put((byte)2); w.Put(e.EncounterKey ?? ""); w.Put(e.BossType ?? ""); w.Put(e.EventName ?? ""); w.Put(e.HasPos); if (e.HasPos) { w.Put(e.Position.x); w.Put(e.Position.y); w.Put(e.Position.z); } w.Put(e.ChapterName ?? ""); w.Put(e.LevelIndex); w.Put(e.HasSeed); if (e.HasSeed) { w.Put(e.Seed); } w.Put(e.Seq); w.Put(e.HasJump); if (e.HasJump) { w.Put(e.JumpStart.x); w.Put(e.JumpStart.y); w.Put(e.JumpStart.z); w.Put(e.JumpAirTimer); w.Put(e.JumpHeight); w.Put(e.JumpDepth); w.Put(e.JumpSpawnPct); } } public static bool TryReadDiscreteEvent(NetDataReader r, out NetBossDiscreteEvent result) { //IL_0064: 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) result = null; try { if (r.GetByte() != 2) { return false; } NetBossDiscreteEvent netBossDiscreteEvent = new NetBossDiscreteEvent { EncounterKey = r.GetString(), BossType = r.GetString(), EventName = r.GetString(), HasPos = r.GetBool() }; if (netBossDiscreteEvent.HasPos) { netBossDiscreteEvent.Position = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); } netBossDiscreteEvent.ChapterName = r.GetString(); netBossDiscreteEvent.LevelIndex = r.GetInt(); netBossDiscreteEvent.HasSeed = r.GetBool(); if (netBossDiscreteEvent.HasSeed) { netBossDiscreteEvent.Seed = r.GetInt(); } netBossDiscreteEvent.Seq = r.GetInt(); netBossDiscreteEvent.HasJump = r.GetBool(); if (netBossDiscreteEvent.HasJump) { netBossDiscreteEvent.JumpStart = new Vector3(r.GetFloat(), r.GetFloat(), r.GetFloat()); netBossDiscreteEvent.JumpAirTimer = r.GetFloat(); netBossDiscreteEvent.JumpHeight = r.GetFloat(); netBossDiscreteEvent.JumpDepth = r.GetFloat(); netBossDiscreteEvent.JumpSpawnPct = r.GetFloat(); } result = netBossDiscreteEvent; return true; } catch { return false; } } public static void WriteLuciaEyeReport(NetDataWriter w, NetLuciaEyeReport m) { w.Put((byte)1); w.Put(m.EncounterKey ?? ""); w.Put(m.BossType ?? ""); w.Put(m.ChapterName ?? ""); w.Put(m.LevelIndex); w.Put(m.HasSeed); if (m.HasSeed) { w.Put(m.Seed); } w.Put(m.Cycle); w.Put(m.LocalRemaining); w.Put(m.ReportSeq); w.Put(m.ClientPeerId ?? ""); w.Put(m.SentAt); } public static bool TryReadLuciaEyeReport(NetDataReader r, out NetLuciaEyeReport result) { result = null; try { if (r.GetByte() != 1) { return false; } NetLuciaEyeReport netLuciaEyeReport = new NetLuciaEyeReport { EncounterKey = r.GetString(), BossType = r.GetString(), ChapterName = r.GetString(), LevelIndex = r.GetInt(), HasSeed = r.GetBool() }; if (netLuciaEyeReport.HasSeed) { netLuciaEyeReport.Seed = r.GetInt(); } netLuciaEyeReport.Cycle = r.GetInt(); netLuciaEyeReport.LocalRemaining = r.GetInt(); netLuciaEyeReport.ReportSeq = r.GetInt(); netLuciaEyeReport.ClientPeerId = r.GetString(); netLuciaEyeReport.SentAt = r.GetFloat(); result = netLuciaEyeReport; return true; } catch { return false; } } public static void WriteLuciaEyeState(NetDataWriter w, NetLuciaEyeState s) { w.Put((byte)1); w.Put(s.EncounterKey ?? ""); w.Put(s.BossType ?? ""); w.Put(s.ChapterName ?? ""); w.Put(s.LevelIndex); w.Put(s.HasSeed); if (s.HasSeed) { w.Put(s.Seed); } w.Put(s.Cycle); w.Put(s.LivingEyes); w.Put(s.Revision); w.Put(s.Timestamp); } public static bool TryReadLuciaEyeState(NetDataReader r, out NetLuciaEyeState result) { result = null; try { if (r.GetByte() != 1) { return false; } NetLuciaEyeState netLuciaEyeState = new NetLuciaEyeState { EncounterKey = r.GetString(), BossType = r.GetString(), ChapterName = r.GetString(), LevelIndex = r.GetInt(), HasSeed = r.GetBool() }; if (netLuciaEyeState.HasSeed) { netLuciaEyeState.Seed = r.GetInt(); } netLuciaEyeState.Cycle = r.GetInt(); netLuciaEyeState.LivingEyes = r.GetInt(); netLuciaEyeState.Revision = r.GetInt(); netLuciaEyeState.Timestamp = r.GetFloat(); result = netLuciaEyeState; return true; } catch { return false; } } public static void WriteLuciaDeath(NetDataWriter w, NetLuciaDeath m) { w.Put((byte)1); w.Put(m.EncounterKey ?? ""); w.Put(m.BossType ?? ""); w.Put(m.ChapterName ?? ""); w.Put(m.LevelIndex); w.Put(m.HasSeed); if (m.HasSeed) { w.Put(m.Seed); } w.Put(m.Revision); w.Put(m.Timestamp); } public static bool TryReadLuciaDeath(NetDataReader r, out NetLuciaDeath result) { result = null; try { if (r.GetByte() != 1) { return false; } NetLuciaDeath netLuciaDeath = new NetLuciaDeath { EncounterKey = r.GetString(), BossType = r.GetString(), ChapterName = r.GetString(), LevelIndex = r.GetInt(), HasSeed = r.GetBool() }; if (netLuciaDeath.HasSeed) { netLuciaDeath.Seed = r.GetInt(); } netLuciaDeath.Revision = r.GetInt(); netLuciaDeath.Timestamp = r.GetFloat(); result = netLuciaDeath; return true; } catch { return false; } } public static void WriteWitchPhase(NetDataWriter w, NetWitchPhase m) { w.Put((byte)1); w.Put(m.EncounterKey ?? ""); w.Put(m.BossType ?? ""); w.Put(m.ChapterName ?? ""); w.Put(m.LevelIndex); w.Put(m.HasSeed); if (m.HasSeed) { w.Put(m.Seed); } w.Put(m.PhaseIndex); w.Put(m.PhaseRevision); w.Put(m.FightStarted); w.Put(m.Timestamp); } public static bool TryReadWitchPhase(NetDataReader r, out NetWitchPhase result) { result = null; try { if (r.GetByte() != 1) { return false; } NetWitchPhase netWitchPhase = new NetWitchPhase { EncounterKey = r.GetString(), BossType = r.GetString(), ChapterName = r.GetString(), LevelIndex = r.GetInt(), HasSeed = r.GetBool() }; if (netWitchPhase.HasSeed) { netWitchPhase.Seed = r.GetInt(); } netWitchPhase.PhaseIndex = r.GetInt(); netWitchPhase.PhaseRevision = r.GetInt(); netWitchPhase.FightStarted = r.GetBool(); netWitchPhase.Timestamp = r.GetFloat(); result = netWitchPhase; return true; } catch { return false; } } public static void WriteWitchP2Manifest(NetDataWriter w, NetWitchP2Manifest m) { w.Put((byte)1); w.Put(m.EncounterKey ?? ""); w.Put(m.BossType ?? ""); w.Put(m.ChapterName ?? ""); w.Put(m.LevelIndex); w.Put(m.HasSeed); if (m.HasSeed) { w.Put(m.Seed); } w.Put(m.PhaseRevision); w.Put(m.DomeCount); w.Put(m.RealDomeIndex); w.Put(m.Timestamp); } public static bool TryReadWitchP2Manifest(NetDataReader r, out NetWitchP2Manifest result) { result = null; try { if (r.GetByte() != 1) { return false; } NetWitchP2Manifest netWitchP2Manifest = new NetWitchP2Manifest { EncounterKey = r.GetString(), BossType = r.GetString(), ChapterName = r.GetString(), LevelIndex = r.GetInt(), HasSeed = r.GetBool() }; if (netWitchP2Manifest.HasSeed) { netWitchP2Manifest.Seed = r.GetInt(); } netWitchP2Manifest.PhaseRevision = r.GetInt(); netWitchP2Manifest.DomeCount = r.GetInt(); netWitchP2Manifest.RealDomeIndex = r.GetInt(); netWitchP2Manifest.Timestamp = r.GetFloat(); result = netWitchP2Manifest; return true; } catch { return false; } } public static void WriteWitchP2Result(NetDataWriter w, NetWitchP2Result m) { w.Put((byte)1); w.Put(m.EncounterKey ?? ""); w.Put(m.PhaseRevision); w.Put(m.DomeIndex); w.Put(m.Kind); } public static bool TryReadWitchP2Result(NetDataReader r, out NetWitchP2Result result) { result = null; try { if (r.GetByte() != 1) { return false; } result = new NetWitchP2Result { EncounterKey = r.GetString(), PhaseRevision = r.GetInt(), DomeIndex = r.GetInt(), Kind = r.GetByte() }; return true; } catch { return false; } } } internal sealed class NetBossEncounterState { public string EncounterKey { get; set; } = ""; public string BossType { get; set; } = ""; public string GraphName { get; set; } = ""; public string RootName { get; set; } = ""; public string ChapterName { get; set; } = ""; public int LevelIndex { get; set; } = -1; public bool HasSeed { get; set; } public int Seed { get; set; } public bool Started { get; set; } public string StartSource { get; set; } = ""; public int HostRevision { get; set; } public float HostTimestamp { get; set; } public bool HasPosition { get; set; } public Vector3 Position { get; set; } public bool HasPhaseIndex { get; set; } public int PhaseIndex { get; set; } public string RunKey => string.Format("{0}:{1}:{2}", string.IsNullOrEmpty(ChapterName) ? "" : ChapterName, LevelIndex, HasSeed ? Seed.ToString() : "?"); public string ToCompact() { return string.Format("key={0} type={1} graph={2} run={3} started={4} src={5} phase={6} rev={7}", EncounterKey, BossType, string.IsNullOrEmpty(GraphName) ? "?" : GraphName, RunKey, Started, StartSource, HasPhaseIndex ? PhaseIndex.ToString() : "?", HostRevision); } } internal static class NetEmperorSpiderSync { private static Vector3 _pos; private static Vector3 _prevPos; private static float _rotY; private static float _prevRotY; private static float _recvAt; private static float _prevRecvAt; private static int _wp; private static int _tgt; private static float _hp = -1f; private static int _seq = -1; private static bool _hasSample; private const float SendIntervalSeconds = 0.05f; private static int _sendSeq; private static float _lastSendAt = -999f; private static float _lastSendLogAt = -999f; private static float _lastRecvLogAt = -999f; private static Component _hostSpiderRef; private static Component _clientSpiderRef; private static bool _reflectTried; private static FieldInfo _rbField; private static FieldInfo _npcField; private static FieldInfo _curWpField; private static FieldInfo _tgtWpField; private static FieldInfo _isDeadField; private static FieldInfo _launcherField; private static FieldInfo _playerTransformField; private static FieldInfo _isDefendingField; private static MethodInfo _initializeMi; private static MethodInfo _triggerDefendMi; private static MethodInfo _onNpcDeathMi; private static MethodInfo _activateRapidFireMi; private static MethodInfo _doWhiteFlashMi; private static Type _npcType; private static PropertyInfo _npcDialogProp; private static float _lastWrittenHp = -1f; private static Transform _targetAnchor; private static Transform _hostLocalPlayer; private static int _fightStartCommittedId; private static int _fightStartRequestedId; private static bool _hostFightStartPending; private static bool _inFightStartCommit; private static string _p2StarterPeerId; private static bool _p2LatePulled; private static string _pendingStarterPeerId; private static int _hitSendSeq; private static float _lastHitLogAt = -999f; public const int EventDefend = 1; public const int EventRapidFire = 2; public const int EventDeath = 3; private static int _eventSendSeq; private static bool _clientDeathApplied; private static bool _defendAnnounced; private static bool _rapidFireAnnounced; private static bool _deathAnnounced; public static bool IsSpiderActive => (Object)(object)(_hostSpiderRef ?? _clientSpiderRef) != (Object)null; private static void EnsureReflect(object spider) { if (!_reflectTried) { _reflectTried = true; Type type = spider.GetType(); _rbField = AccessTools.Field(type, "rb"); _npcField = AccessTools.Field(type, "npc"); _curWpField = AccessTools.Field(type, "currentWaypointIndex"); _tgtWpField = AccessTools.Field(type, "targetWaypointIndex"); _isDeadField = AccessTools.Field(type, "isDead"); _isDefendingField = AccessTools.Field(type, "isDefending"); _launcherField = AccessTools.Field(type, "emperorBossSpiderRocketLauncher"); _playerTransformField = AccessTools.Field(type, "playerTransform"); _initializeMi = AccessTools.Method(type, "Initialize", (Type[])null, (Type[])null); _triggerDefendMi = AccessTools.Method(type, "TriggerDefendPhase", (Type[])null, (Type[])null); _onNpcDeathMi = AccessTools.Method(type, "OnNpcDeath", (Type[])null, (Type[])null); Type type2 = AccessTools.TypeByName("PerfectRandom.Sulfur.Gameplay.EmperorBossSpiderRocketLauncher"); if (type2 != null) { _activateRapidFireMi = AccessTools.Method(type2, "ActivateRapidFire", (Type[])null, (Type[])null); } _npcType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Units.Npc"); if (_npcType != null) { _npcDialogProp = _npcType.GetProperty("dialog", BindingFlags.Instance | BindingFlags.Public); _doWhiteFlashMi = AccessTools.Method(_npcType, "DoWhiteFlash", (Type[])null, (Type[])null); } } } private static bool ReadIsDead(object spider) { object obj = _isDeadField?.GetValue(spider); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } public static void HostCapture(object spider) { //IL_00b7: 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_00c3: 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_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) Component val = (Component)((spider is Component) ? spider : null); if (val == null || (Object)(object)val == (Object)null) { return; } if ((Object)(object)_hostSpiderRef == (Object)null || ((Object)_hostSpiderRef).GetInstanceID() != ((Object)val).GetInstanceID()) { _defendAnnounced = (_rapidFireAnnounced = (_deathAnnounced = false)); _hostLocalPlayer = null; Plugin.Log.Info("[EmperorSpider] host tracking a new spider instance — per-encounter broadcast flags re-armed."); } _hostSpiderRef = val; EnsureReflect(spider); UpdateHostTarget(val); if (_hostFightStartPending && _fightStartCommittedId != ((Object)val).GetInstanceID()) { _hostFightStartPending = false; HostCommitFightStart(val, "deferred client request", _pendingStarterPeerId); } float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup - _lastSendAt < 0.05f)) { _lastSendAt = realtimeSinceStartup; Vector3 position = val.transform.position; float y = val.transform.eulerAngles.y; int num = ((_curWpField?.GetValue(spider) is int num2) ? num2 : 0); int num3 = ((_tgtWpField?.GetValue(spider) is int num4) ? num4 : 0); float value = -1f; object obj = _npcField?.GetValue(spider); if (obj != null) { BossReflect.TryCallFloat(obj, "GetCurrentHealth", out value); } _sendSeq++; NetGameplaySyncBridge.BroadcastEmperorSpiderTransform(position.x, position.y, position.z, y, num, num3, value, _sendSeq); if (realtimeSinceStartup - _lastSendLogAt > 1f) { _lastSendLogAt = realtimeSinceStartup; Plugin.Log.Info($"[EmperorSpider] host sent transform seq={_sendSeq} pos={position:F1} wp={num}->{num3} hp={value:F0}"); } } } public static void OnTransformReceived(Vector3 pos, float rotY, int wp, int tgt, float hp, int seq) { //IL_0022: 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_0027: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005c: 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) if (_seq == -1 || seq > _seq) { float realtimeSinceStartup = Time.realtimeSinceStartup; _prevPos = (_hasSample ? _pos : pos); _prevRotY = (_hasSample ? _rotY : rotY); _prevRecvAt = (_hasSample ? _recvAt : realtimeSinceStartup); _seq = seq; _pos = pos; _rotY = rotY; _wp = wp; _tgt = tgt; _hp = hp; _recvAt = realtimeSinceStartup; _hasSample = true; if (realtimeSinceStartup - _lastRecvLogAt > 1f) { _lastRecvLogAt = realtimeSinceStartup; Plugin.Log.Info($"[EmperorSpider] client recv transform seq={seq} pos={pos:F1} wp={wp}->{tgt}"); } } } public static void DriveClientSpider(object spider) { //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_0100: 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_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) Component val = (Component)((spider is Component) ? spider : null); if (val == null || (Object)(object)val == (Object)null) { return; } EnsureReflect(spider); if ((Object)(object)_clientSpiderRef == (Object)null || ((Object)_clientSpiderRef).GetInstanceID() != ((Object)val).GetInstanceID()) { _hasSample = false; _seq = -1; _recvAt = 0f; _prevRecvAt = 0f; _hp = -1f; _lastWrittenHp = -1f; _clientDeathApplied = false; Plugin.Log.Info("[EmperorSpider] client tracking a new spider instance — prior stream/death state reset."); } _clientSpiderRef = val; object? obj = _rbField?.GetValue(spider); Rigidbody val2 = (Rigidbody)((obj is Rigidbody) ? obj : null); if (val2 != null && (Object)(object)val2 != (Object)null && !val2.isKinematic) { val2.isKinematic = true; } if (ReadIsDead(spider) || !_hasSample) { return; } float num = Mathf.Max(0.0001f, _recvAt - _prevRecvAt); float num2 = Mathf.Clamp01((Time.realtimeSinceStartup - _recvAt) / num); Vector3 val3 = Vector3.Lerp(_prevPos, _pos, num2); Quaternion val4 = Quaternion.Euler(0f, Mathf.LerpAngle(_prevRotY, _rotY, num2), 0f); val.transform.SetPositionAndRotation(val3, val4); _curWpField?.SetValue(spider, _wp); _tgtWpField?.SetValue(spider, _tgt); if (_hp >= 0f && Mathf.Abs(_hp - _lastWrittenHp) > 0.5f) { object obj2 = _npcField?.GetValue(spider); if (obj2 != null && NetGameplayProbeManager.TryWriteUnitHealth(obj2, _hp, raiseEvent: true)) { _lastWrittenHp = _hp; } } } private static void UpdateHostTarget(Component spider) { //IL_006f: 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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008b: 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_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown try { if ((Object)(object)_hostLocalPlayer == (Object)null) { object? obj = _playerTransformField?.GetValue(spider); _hostLocalPlayer = (Transform)((obj is Transform) ? obj : null); if ((Object)(object)_hostLocalPlayer == (Object)null) { return; } } if ((Object)(object)_targetAnchor == (Object)null) { GameObject val = new GameObject("EmperorSpiderTargetAnchor"); Object.DontDestroyOnLoad((Object)val); _targetAnchor = val.transform; } Vector3 sp = spider.transform.position; Vector3 best = _hostLocalPlayer.position; Vector3 val2 = best - sp; float bestSq = ((Vector3)(ref val2)).sqrMagnitude; NetGameplaySyncBridge.ForEachRemotePlayerPosition(delegate(Vector3 p) { //IL_0000: 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_000c: 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) Vector3 val3 = p - sp; float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude; if (sqrMagnitude < bestSq) { bestSq = sqrMagnitude; best = p; } }); _targetAnchor.position = best; _playerTransformField?.SetValue(spider, _targetAnchor); } catch { } } private static void FinalizeLocalDialog(string origin) { if (BossDialogReflect.TryFinalizeCurrentDialog(out string detail)) { Plugin.Log.Info("[EmperorSpider] fight-start (" + origin + ") closed local phase-2 dialog — " + detail); } } private static void DisableSpiderDialog(Component spider) { try { EnsureReflect(spider); object? obj = _npcField?.GetValue(spider); Component val = (Component)((obj is Component) ? obj : null); if (!((Object)(object)val == (Object)null) && !(_npcDialogProp == null) && _npcDialogProp.CanWrite && _npcDialogProp.GetValue(val) != null) { _npcDialogProp.SetValue(val, null); Plugin.Log.Info("[EmperorSpider] fight-start disabled P2 spider dialog (can no longer be re-triggered / re-Initialized during combat)"); } } catch (Exception ex) { Plugin.Log.Warn("[EmperorSpider] DisableSpiderDialog failed: " + ex.Message); } } public static bool TryTeleportLateP2Player(object npc) { //IL_0103: 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_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0109: 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_013d: 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) try { if (NetGameplaySyncBridge.BossMode == NetMode.Off || !NetGameplaySyncBridge.IsSessionActive) { return false; } if (_fightStartCommittedId == 0 || string.IsNullOrEmpty(_p2StarterPeerId)) { return false; } if (_p2LatePulled) { return false; } if (_p2StarterPeerId == NetGameplaySyncBridge.LocalPeerId) { return false; } Component val = _hostSpiderRef ?? _clientSpiderRef ?? FindLocalSpider(); if ((Object)(object)val == (Object)null) { return false; } EnsureReflect(val); object? obj = _npcField?.GetValue(val); Component val2 = (Component)((obj is Component) ? obj : null); if (!((Object)(object)val2 == (Object)null)) { Component val3 = (Component)((npc is Component) ? npc : null); if (val3 != null && !((Object)(object)val3 == (Object)null)) { if (((Object)val3.gameObject).GetInstanceID() != ((Object)val2.gameObject).GetInstanceID()) { return false; } Vector3 val4 = (Vector3)(((??)ResolveStarterPos()) ?? val.transform.position); TeleportLocalPlayerTo(val4); _p2LatePulled = true; Plugin.Log.Info($"[EmperorSpider] P2 late arrival pulled to fight-starter '{_p2StarterPeerId}' at ({val4.x:0.0},{val4.y:0.0},{val4.z:0.0})"); return true; } } return false; } catch { return false; } } private static Vector3? ResolveStarterPos() { Vector3? found = null; try { NetGameplaySyncBridge.ForEachRemotePlayerPositionWithPeer(delegate(string peerId, Vector3 pos) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (!found.HasValue && peerId == _p2StarterPeerId) { found = pos; } }); } catch { } return found; } private static void TeleportLocalPlayerTo(Vector3 dest) { //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_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_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) try { Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.GameManager"); object obj = ((type == null) ? null : AccessTools.Property(type, "Instance")?.GetValue(null, null)); object obj2 = ((obj == null) ? null : AccessTools.Property(type, "PlayerUnit")?.GetValue(obj, null)); Object val = (Object)((obj2 is Object) ? obj2 : null); if (val != null && val == (Object)null) { obj2 = null; } if (obj2 == null) { Plugin.Log.Warn("[EmperorSpider] P2 late teleport: local player unit missing"); return; } Vector3 val2 = dest + Vector3.up * 0.5f; MethodInfo methodInfo = AccessTools.Method(obj2.GetType(), "TeleportTo", new Type[1] { typeof(Vector3) }, (Type[])null) ?? AccessTools.Method(obj2.GetType(), "TeleportTo", (Type[])null, (Type[])null); if (methodInfo != null) { methodInfo.Invoke(obj2, new object[1] { val2 }); return; } Component val3 = (Component)((obj2 is Component) ? obj2 : null); if (val3 != null && (Object)(object)val3 != (Object)null) { val3.transform.position = val2; } } catch (Exception ex) { Plugin.Log.Warn("[EmperorSpider] P2 late teleport failed: " + ex.Message); } } public static bool SuppressClientMaintainDistance() { if (_inFightStartCommit) { return true; } if (NetGameplaySyncBridge.BossMode == NetMode.Client) { return !NetLinkState.ClientLinked; } return true; } public static bool TryGateFightStart(object spider) { try { if (_inFightStartCommit) { return true; } Component val = (Component)((spider is Component) ? spider : null); if (val == null || (Object)(object)val == (Object)null) { return true; } NetMode bossMode = NetGameplaySyncBridge.BossMode; bool flag = bossMode == NetMode.Client && NetLinkState.ClientLinked; if ((bossMode == NetMode.Host || flag) && _fightStartCommittedId == ((Object)val).GetInstanceID()) { return false; } if (bossMode == NetMode.Host) { _fightStartCommittedId = ((Object)val).GetInstanceID(); _hostSpiderRef = val; _p2StarterPeerId = NetGameplaySyncBridge.LocalPeerId; _p2LatePulled = false; NetGameplaySyncBridge.BroadcastEmperorSpiderFightStart(_p2StarterPeerId); DisableSpiderDialog(val); Plugin.Log.Info($"[EmperorSpider] host fight-start (local dialog) spider={((Object)val).GetInstanceID()} -> broadcast commit"); return true; } if (flag) { if (_fightStartRequestedId != ((Object)val).GetInstanceID()) { _fightStartRequestedId = ((Object)val).GetInstanceID(); NetGameplaySyncBridge.SendClientEmperorSpiderFightStart(); Plugin.Log.Info($"[EmperorSpider] client fight-start (local dialog) spider={((Object)val).GetInstanceID()} -> requested host commit (local start blocked)"); } return false; } return true; } catch { return true; } } public static void HostOnClientFightStartRequest(string requesterPeerId) { _pendingStarterPeerId = requesterPeerId; Component hostSpiderRef = _hostSpiderRef; if (hostSpiderRef != null) { Component val = hostSpiderRef; if (!((Object)(object)val == (Object)null)) { if (_fightStartCommittedId != ((Object)val).GetInstanceID()) { HostCommitFightStart(val, "client request", requesterPeerId); } return; } } _hostFightStartPending = true; Plugin.Log.Info("[EmperorSpider] client fight-start request arrived before host spider is live — deferred."); } private static void HostCommitFightStart(Component spider, string origin, string starterPeerId) { _fightStartCommittedId = ((Object)spider).GetInstanceID(); _p2StarterPeerId = starterPeerId; _p2LatePulled = false; NetGameplaySyncBridge.BroadcastEmperorSpiderFightStart(starterPeerId); InvokeInitialize(spider, origin); FinalizeLocalDialog(origin); DisableSpiderDialog(spider); Plugin.Log.Info($"[EmperorSpider] host fight-start ({origin}) spider={((Object)spider).GetInstanceID()} starter={starterPeerId} -> committed + broadcast"); } public static void OnFightStartCommitReceived(string starterPeerId) { if (!string.IsNullOrEmpty(starterPeerId)) { _p2StarterPeerId = starterPeerId; _p2LatePulled = false; } Component val = _clientSpiderRef ?? FindLocalSpider(); if (val != null) { Component val2 = val; if (!((Object)(object)val2 == (Object)null)) { if (_fightStartCommittedId == ((Object)val2).GetInstanceID()) { Plugin.Log.Info($"[EmperorSpider] fight-start commit ignored — spider={((Object)val2).GetInstanceID()} already started."); return; } _fightStartCommittedId = ((Object)val2).GetInstanceID(); InvokeInitialize(val2, "host commit"); FinalizeLocalDialog("host commit"); DisableSpiderDialog(val2); Plugin.Log.Info($"[EmperorSpider] client mirrored fight-start (host commit) spider={((Object)val2).GetInstanceID()}"); return; } } Plugin.Log.Warn("[EmperorSpider] fight-start commit received before any local spider exists — dropped."); } private static void InvokeInitialize(Component spider, string origin) { EnsureReflect(spider); if (_initializeMi == null) { Plugin.Log.Warn("[EmperorSpider] fight-start (" + origin + "): Initialize not resolved."); return; } _inFightStartCommit = true; try { _initializeMi.Invoke(spider, null); } catch (Exception ex) { Plugin.Log.Warn("[EmperorSpider] fight-start (" + origin + ") Initialize invoke failed: " + ex.Message); } finally { _inFightStartCommit = false; } } private static Component FindLocalSpider() { try { object? obj = AccessTools.TypeByName("PerfectRandom.Sulfur.Gameplay.EmperorBossSpider")?.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); return (Component)((obj is Component) ? obj : null); } catch { return null; } } public static bool TryClientSpiderHit(object npc, float damage, int damageTypeInt) { try { if (npc == null) { return false; } if (NetGameplaySyncBridge.BossMode != NetMode.Client || !NetLinkState.ClientLinked) { return false; } Component clientSpiderRef = _clientSpiderRef; if ((Object)(object)clientSpiderRef == (Object)null) { return false; } if (_clientDeathApplied) { return true; } EnsureReflect(clientSpiderRef); object? obj = _npcField?.GetValue(clientSpiderRef); Component val = (Component)((obj is Component) ? obj : null); if ((Object)(object)val == (Object)null) { return false; } Component val2 = (Component)((npc is Component) ? npc : null); if (val2 == null || (Object)(object)val2 == (Object)null) { return false; } if (((Object)val2.gameObject).GetInstanceID() != ((Object)val.gameObject).GetInstanceID()) { return false; } _hitSendSeq++; NetGameplaySyncBridge.SendClientEmperorSpiderHit(damage, damageTypeInt, _hitSendSeq); object obj2 = _isDefendingField?.GetValue(clientSpiderRef); bool flag = default(bool); int num; if (obj2 is bool) { flag = (bool)obj2; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) == 0 && !ReadIsDead(clientSpiderRef) && _doWhiteFlashMi != null) { try { _doWhiteFlashMi.Invoke(val, null); } catch { } } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup - _lastHitLogAt > 1f) { _lastHitLogAt = realtimeSinceStartup; Plugin.Log.Info($"[EmperorSpider] client -> host spider hit dmg={damage:F1} dtype={damageTypeInt} seq={_hitSendSeq} (local suppressed)"); } return true; } catch { return false; } } public static void HostApplyClientSpiderHit(float damage, int damageTypeInt, int seq) { try { Component hostSpiderRef = _hostSpiderRef; if ((Object)(object)hostSpiderRef == (Object)null) { Plugin.Log.Warn($"[EmperorSpider] client hit seq={seq} but no host spider is active — dropped."); return; } EnsureReflect(hostSpiderRef); object obj = _npcField?.GetValue(hostSpiderRef); if (obj == null) { Plugin.Log.Warn($"[EmperorSpider] client hit seq={seq}: host spider npc is null — dropped."); return; } object sourcePlayerUnit = BossDamageReflect.ResolveHostPlayerUnit(); bool vanillaResult; string detail; bool flag = BossDamageReflect.TryApplyRealDamage(obj, damage, damageTypeInt, sourcePlayerUnit, out vanillaResult, out detail); float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup - _lastHitLogAt > 1f) { _lastHitLogAt = realtimeSinceStartup; Plugin.Log.Info($"[EmperorSpider] host applied client spider hit seq={seq} dmg={damage:F1} ok={flag} vanilla={vanillaResult} ({detail})"); } } catch (Exception ex) { Plugin.Log.Warn($"[EmperorSpider] HostApplyClientSpiderHit seq={seq} failed: {ex.Message}"); } } public static void HostAnnounceDefend(object spider) { if (NetGameplaySyncBridge.BossMode == NetMode.Host) { _hostSpiderRef = (Component)((spider is Component) ? spider : null); if (!_defendAnnounced) { _defendAnnounced = true; _eventSendSeq++; NetGameplaySyncBridge.BroadcastEmperorSpiderEvent(1, _eventSendSeq); Plugin.Log.Info($"[EmperorSpider] host defend-phase -> broadcast seq={_eventSendSeq}"); } } } public static void HostAnnounceRapidFire() { if (NetGameplaySyncBridge.BossMode == NetMode.Host && !_rapidFireAnnounced) { _rapidFireAnnounced = true; _eventSendSeq++; NetGameplaySyncBridge.BroadcastEmperorSpiderEvent(2, _eventSendSeq); Plugin.Log.Info($"[EmperorSpider] host rapid-fire -> broadcast seq={_eventSendSeq}"); } } public static void HostAnnounceDeath(object spider) { if (NetGameplaySyncBridge.BossMode == NetMode.Host) { _hostSpiderRef = (Component)((spider is Component) ? spider : null); if (!_deathAnnounced) { _deathAnnounced = true; _eventSendSeq++; NetGameplaySyncBridge.BroadcastEmperorSpiderEvent(3, _eventSendSeq); Plugin.Log.Info($"[EmperorSpider] host death (OnNpcDeath) -> broadcast seq={_eventSendSeq}"); } } } public static void OnEventReceived(int eventCode, int seq) { try { Component val = _clientSpiderRef ?? FindLocalSpider(); if (val != null) { Component val2 = val; if (!((Object)(object)val2 == (Object)null)) { EnsureReflect(val); switch (eventCode) { case 1: _triggerDefendMi?.Invoke(val, null); Plugin.Log.Info($"[EmperorSpider] client mirrored defend-phase seq={seq}"); break; case 2: { object obj = _launcherField?.GetValue(val); if (obj != null && _activateRapidFireMi != null) { _activateRapidFireMi.Invoke(obj, null); } Plugin.Log.Info($"[EmperorSpider] client mirrored rapid-fire seq={seq}"); break; } case 3: if (_clientDeathApplied) { Plugin.Log.Info($"[EmperorSpider] death seq={seq} ignored — already applied."); break; } _clientDeathApplied = true; _onNpcDeathMi?.Invoke(val, new object[1]); Plugin.Log.Info($"[EmperorSpider] client mirrored death (OnNpcDeath) seq={seq}"); break; default: Plugin.Log.Warn($"[EmperorSpider] unknown event code={eventCode} seq={seq}"); break; } return; } } Plugin.Log.Warn($"[EmperorSpider] event code={eventCode} seq={seq} but no local spider — dropped."); } catch (Exception ex) { Plugin.Log.Warn($"[EmperorSpider] OnEventReceived code={eventCode} seq={seq} failed: {ex.Message}"); } } public static void ResetClient() { _hasSample = false; _seq = -1; _recvAt = 0f; _prevRecvAt = 0f; _hp = -1f; _lastWrittenHp = -1f; _clientSpiderRef = null; _hostSpiderRef = null; _hostLocalPlayer = null; _clientDeathApplied = false; _sendSeq = 0; _eventSendSeq = 0; _hitSendSeq = 0; _fightStartCommittedId = 0; _fightStartRequestedId = 0; _hostFightStartPending = false; _inFightStartCommit = false; _p2StarterPeerId = null; _p2LatePulled = false; _pendingStarterPeerId = null; _defendAnnounced = (_rapidFireAnnounced = (_deathAnnounced = false)); } } internal static class NetEmperorWormSync { private static Vector3 _headPos; private static float _headRotY; private static float _headRecvAt; private static Vector3 _prevHeadPos; private static float _prevHeadRotY; private static float _prevRecvAt; private static int _headSeq = -1; private static bool _hasHead; private static float _headTailHp = -1f; private static float _lastWrittenTailHp = -1f; private const float SendIntervalSeconds = 0.05f; private static int _sendSeq; private static float _lastSendAt = -999f; private static bool _reflectTried; private static FieldInfo _rootField; private static FieldInfo _rbField; private static MethodInfo _updateSectionsMi; private static MethodInfo _destroySectionMi; private static MethodInfo _moveVulnerableUpMi; private static MethodInfo _deathAnimationMi; private static FieldInfo _lastSectionNpcField; private static MethodInfo _startMovementMi; private static FieldInfo _playerField; private static Component _clientWormRef; private static Component _hostWormRef; private static float _lastSendLogAt = -999f; private static float _lastRecvLogAt = -999f; private static Transform _wormAnchor; private static Transform _wormHostPlayerTf; private static int _wormAnchorWormId; private static readonly List _wormRotation = new List(); private static int _wormRotIndex; private static string _wormTargetId; private static float _lastWormTargetLogAt = -999f; private static readonly HashSet _headCollidersDisabled = new HashSet(); private static readonly Dictionary _sectionColliders = new Dictionary(); private static FieldInfo _wormNpcsField; private static int _wormHitSendSeq; private static float _lastWormHitLogAt = -999f; private static int _fightStartCommittedWormId; private static int _fightStartRequestedWormId; private static bool _hostFightStartPending; private static bool _inFightStartCommit; private static Type _npcType; private static PropertyInfo _npcDialogProp; private static int _sectionDestroySendSeq; private static FieldInfo _lastActiveIndexField; private static int _deathSendSeq; private static bool _clientDeathApplied; public static bool IsWormActive => (Object)(object)(_hostWormRef ?? _clientWormRef) != (Object)null; private static void EnsureReflect(object worm) { if (!_reflectTried) { _reflectTried = true; Type type = worm.GetType(); _rootField = AccessTools.Field(type, "root"); _rbField = AccessTools.Field(type, "rb"); _updateSectionsMi = AccessTools.Method(type, "UpdateWormSections", (Type[])null, (Type[])null); _destroySectionMi = AccessTools.Method(type, "DestroySection", (Type[])null, (Type[])null); _moveVulnerableUpMi = AccessTools.Method(type, "MoveVulnerableSectionUp", (Type[])null, (Type[])null); _deathAnimationMi = AccessTools.Method(type, "DeathAnimation", (Type[])null, (Type[])null); _lastSectionNpcField = AccessTools.Field(type, "lastSectionNpc"); _startMovementMi = AccessTools.Method(type, "StartMovement", (Type[])null, (Type[])null); _playerField = AccessTools.Field(type, "player"); } } public static void HostCapture(object worm) { //IL_006c: 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_0078: 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_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) //IL_0104: Unknown result type (might be due to invalid IL or missing references) Component val = (Component)((worm is Component) ? worm : null); if (val == null || (Object)(object)val == (Object)null) { return; } _hostWormRef = val; EnsureReflect(worm); HostUpdateWormTarget(val); if (_hostFightStartPending && _fightStartCommittedWormId != ((Object)val).GetInstanceID()) { _hostFightStartPending = false; HostCommitFightStart(val, "deferred client request"); } float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup - _lastSendAt < 0.05f)) { _lastSendAt = realtimeSinceStartup; Vector3 position = val.transform.position; float y = val.transform.eulerAngles.y; float value = -1f; object obj = _lastSectionNpcField?.GetValue(worm); if (obj != null) { BossReflect.TryCallFloat(obj, "GetCurrentHealth", out value); } _sendSeq++; NetGameplaySyncBridge.BroadcastEmperorWormHead(position.x, position.y, position.z, y, value, _sendSeq); if (realtimeSinceStartup - _lastSendLogAt > 1f) { _lastSendLogAt = realtimeSinceStartup; Plugin.Log.Info($"[EmperorWormHead] host sent seq={_sendSeq} pos={position:F1} tailHp={value:F0}"); } } } private static void HostUpdateWormTarget(Component worm) { try { if (NetGameplaySyncBridge.BossMode != NetMode.Host) { return; } EnsureReflect(worm); if (_playerField == null) { return; } if (_wormAnchorWormId != ((Object)worm).GetInstanceID()) { _wormAnchorWormId = ((Object)worm).GetInstanceID(); _wormHostPlayerTf = null; _wormTargetId = null; _wormRotation.Clear(); _wormRotIndex = 0; } if ((Object)(object)_wormHostPlayerTf == (Object)null) { object? value = _playerField.GetValue(worm); _wormHostPlayerTf = (Transform)((value is Transform) ? value : null); if ((Object)(object)_wormHostPlayerTf == (Object)null) { return; } } if (_wormTargetId == null) { RebuildRotationAndAdvance(); } PositionAnchorOnTarget(worm); } catch { } } public static void HostAdvanceWormTarget(object worm) { try { if (NetGameplaySyncBridge.BossMode != NetMode.Host) { return; } Component val = (Component)((worm is Component) ? worm : null); if (val != null && !((Object)(object)val == (Object)null)) { EnsureReflect(worm); if (!(_playerField == null) && !((Object)(object)_wormHostPlayerTf == (Object)null)) { RebuildRotationAndAdvance(); PositionAnchorOnTarget(val); } } } catch { } } private static void PositionAnchorOnTarget(Component worm) { //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_0045: 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_0036: Expected O, but got Unknown if (!TryResolvePlayerPos(_wormTargetId, out var pos)) { pos = _wormHostPlayerTf.position; } if ((Object)(object)_wormAnchor == (Object)null) { GameObject val = new GameObject("EmperorWormTargetAnchor"); Object.DontDestroyOnLoad((Object)val); _wormAnchor = val.transform; } _wormAnchor.position = pos; _playerField.SetValue(worm, _wormAnchor); } private static void RebuildRotationAndAdvance() { HashSet inRoom = null; try { ArenaLockdownManager.TryGetActiveArenaInRoom(out inRoom); } catch { } List next = new List(); if (inRoom == null || inRoom.Contains("host")) { next.Add("host"); } try { NetGameplaySyncBridge.ForEachRemotePlayerPositionWithPeer(delegate(string peerId, Vector3 _) { if (!string.IsNullOrEmpty(peerId) && (inRoom == null || inRoom.Contains(peerId)) && !next.Contains(peerId)) { next.Add(peerId); } }); } catch { } if (next.Count == 0) { next.Add("host"); } _wormRotation.Clear(); _wormRotation.AddRange(next); int num = ((_wormTargetId != null) ? _wormRotation.IndexOf(_wormTargetId) : (-1)); _wormRotIndex = ((num >= 0) ? ((num + 1) % _wormRotation.Count) : 0); string text = _wormRotation[_wormRotIndex]; if (text != _wormTargetId) { _wormTargetId = text; float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup - _lastWormTargetLogAt > 0.5f) { _lastWormTargetLogAt = realtimeSinceStartup; Plugin.Log.Info($"[EmperorWorm] worm now targeting in-room player '{_wormTargetId}' (rotation {_wormRotIndex + 1}/{_wormRotation.Count})"); } } else { _wormTargetId = text; } } private static bool TryResolvePlayerPos(string id, out Vector3 pos) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_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_005b: 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_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) pos = Vector3.zero; if (string.IsNullOrEmpty(id)) { return false; } if (id == "host") { if ((Object)(object)_wormHostPlayerTf == (Object)null) { return false; } pos = _wormHostPlayerTf.position; return true; } Vector3 found = Vector3.zero; bool ok = false; try { NetGameplaySyncBridge.ForEachRemotePlayerPositionWithPeer(delegate(string peerId, Vector3 p) { //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) if (!ok && peerId == id) { found = p; ok = true; } }); } catch { } pos = found; return ok; } public static void OnHeadReceived(Vector3 pos, float rotY, float tailHp, int seq) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) if (_headSeq == -1 || seq > _headSeq) { float realtimeSinceStartup = Time.realtimeSinceStartup; _prevHeadPos = (_hasHead ? _headPos : pos); _prevHeadRotY = (_hasHead ? _headRotY : rotY); _prevRecvAt = (_hasHead ? _headRecvAt : realtimeSinceStartup); _headSeq = seq; _headPos = pos; _headRotY = rotY; _headTailHp = tailHp; _headRecvAt = realtimeSinceStartup; _hasHead = true; if (realtimeSinceStartup - _lastRecvLogAt > 1f) { _lastRecvLogAt = realtimeSinceStartup; Plugin.Log.Info($"[EmperorWormHead] client recv seq={seq} pos={pos:F1}"); } } } public static void DriveClientWorm(object worm) { //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0136: 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_0168: Unknown result type (might be due to invalid IL or missing references) Component val = (Component)((worm is Component) ? worm : null); if (val == null || (Object)(object)val == (Object)null) { return; } EnsureReflect(worm); if ((Object)(object)_clientWormRef == (Object)null || ((Object)_clientWormRef).GetInstanceID() != ((Object)val).GetInstanceID()) { _hasHead = false; _headSeq = -1; _headRecvAt = 0f; _prevRecvAt = 0f; _headTailHp = -1f; _lastWrittenTailHp = -1f; _clientDeathApplied = false; Plugin.Log.Info("[EmperorWorm] client tracking a new worm instance — prior worm's stream/death state reset."); } _clientWormRef = val; if (_clientDeathApplied) { return; } object? obj = _rbField?.GetValue(worm); Rigidbody val2 = (Rigidbody)((obj is Rigidbody) ? obj : null); if (val2 != null && (Object)(object)val2 != (Object)null && !val2.isKinematic) { val2.isKinematic = true; } EnsureWormVisualOnly(worm, val); if (!_hasHead) { return; } float num = Mathf.Max(0.0001f, _headRecvAt - _prevRecvAt); float num2 = Mathf.Clamp01((Time.realtimeSinceStartup - _headRecvAt) / num); Vector3 val3 = Vector3.Lerp(_prevHeadPos, _headPos, num2); Quaternion val4 = Quaternion.Euler(0f, Mathf.LerpAngle(_prevHeadRotY, _headRotY, num2), 0f); val.transform.SetPositionAndRotation(val3, val4); object? obj2 = _rootField?.GetValue(worm); Transform val5 = (Transform)((obj2 is Transform) ? obj2 : null); if (val5 != null && (Object)(object)val5 != (Object)null) { val5.SetPositionAndRotation(val3, val4); } _updateSectionsMi?.Invoke(worm, null); if (_headTailHp >= 0f && Mathf.Abs(_headTailHp - _lastWrittenTailHp) > 0.5f) { object obj3 = _lastSectionNpcField?.GetValue(worm); if (obj3 != null && NetGameplayProbeManager.TryWriteUnitHealth(obj3, _headTailHp, raiseEvent: true)) { _lastWrittenTailHp = _headTailHp; } } } private static void EnsureWormVisualOnly(object worm, Component c) { try { int instanceID = ((Object)c).GetInstanceID(); if (_headCollidersDisabled.Add(instanceID)) { Collider[] componentsInChildren = c.GetComponentsInChildren(true); Collider[] array = componentsInChildren; foreach (Collider val in array) { if ((Object)(object)val != (Object)null) { val.enabled = false; } } Plugin.Log.Info($"[EmperorWormHead] client disabled {componentsInChildren.Length} head collider(s) (visual-only worm)."); } if (_wormNpcsField == null) { _wormNpcsField = AccessTools.Field(worm.GetType(), "wormNpcs"); } if (!(_wormNpcsField?.GetValue(worm) is IList { Count: not 0 } list)) { return; } object? obj = _lastSectionNpcField?.GetValue(worm); Component val2 = (Component)((obj is Component) ? obj : null); for (int j = 0; j < list.Count; j++) { object? obj2 = list[j]; Component val3 = (Component)((obj2 is Component) ? obj2 : null); if (val3 == null || (Object)(object)val3 == (Object)null) { continue; } int instanceID2 = ((Object)val3).GetInstanceID(); if (!_sectionColliders.TryGetValue(instanceID2, out Collider[] value)) { value = val3.GetComponentsInChildren(true); _sectionColliders[instanceID2] = value; } bool flag = val3 == val2; Collider[] array = value; foreach (Collider val4 in array) { if ((Object)(object)val4 != (Object)null && val4.enabled != flag) { val4.enabled = flag; } } } } catch (Exception ex) { Plugin.Log.Warn("[EmperorWormHead] EnsureWormVisualOnly failed: " + ex.Message); } } public static bool TryClientWormHit(object npc, float damage, int damageTypeInt) { try { if (npc == null) { return false; } if (NetGameplaySyncBridge.BossMode != NetMode.Client || !NetLinkState.ClientLinked) { return false; } Component clientWormRef = _clientWormRef; if ((Object)(object)clientWormRef == (Object)null) { return false; } if (_clientDeathApplied) { return true; } object? obj = _lastSectionNpcField?.GetValue(clientWormRef); Component val = (Component)((obj is Component) ? obj : null); if ((Object)(object)val == (Object)null) { return false; } Component val2 = (Component)((npc is Component) ? npc : null); if (val2 == null || (Object)(object)val2 == (Object)null) { return false; } if (((Object)val2.gameObject).GetInstanceID() != ((Object)val.gameObject).GetInstanceID()) { return false; } _wormHitSendSeq++; NetGameplaySyncBridge.SendClientEmperorWormHit(damage, damageTypeInt, _wormHitSendSeq); float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup - _lastWormHitLogAt > 1f) { _lastWormHitLogAt = realtimeSinceStartup; Plugin.Log.Info($"[EmperorWorm] client -> host worm hit dmg={damage:F1} dtype={damageTypeInt} seq={_wormHitSendSeq} (local damage suppressed)"); } return true; } catch { return false; } } public static void HostApplyClientWormHit(float damage, int damageTypeInt, int seq) { try { Component hostWormRef = _hostWormRef; if ((Object)(object)hostWormRef == (Object)null) { Plugin.Log.Warn($"[EmperorWorm] client worm hit seq={seq} but no host worm is active — dropped."); return; } object obj = _lastSectionNpcField?.GetValue(hostWormRef); if (obj == null) { Plugin.Log.Warn($"[EmperorWorm] client worm hit seq={seq}: host lastSectionNpc is null — dropped."); return; } object sourcePlayerUnit = BossDamageReflect.ResolveHostPlayerUnit(); bool vanillaResult; string detail; bool flag = BossDamageReflect.TryApplyRealDamage(obj, damage, damageTypeInt, sourcePlayerUnit, out vanillaResult, out detail); float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup - _lastWormHitLogAt > 1f) { _lastWormHitLogAt = realtimeSinceStartup; Plugin.Log.Info($"[EmperorWorm] host applied client worm hit seq={seq} dmg={damage:F1} ok={flag} vanilla={vanillaResult} ({detail})"); } } catch (Exception ex) { Plugin.Log.Warn($"[EmperorWorm] HostApplyClientWormHit seq={seq} failed: {ex.Message}"); } } public static bool TryGateFightStart(object worm) { try { if (_inFightStartCommit) { return true; } Component val = (Component)((worm is Component) ? worm : null); if (val == null || (Object)(object)val == (Object)null) { return true; } switch (NetGameplaySyncBridge.BossMode) { case NetMode.Host: if (_fightStartCommittedWormId != ((Object)val).GetInstanceID()) { _fightStartCommittedWormId = ((Object)val).GetInstanceID(); NetGameplaySyncBridge.BroadcastEmperorFightStart(); DisableBossDialogOnFightStart(); Plugin.Log.Info($"[EmperorWorm] host fight-start (local dialog) worm={((Object)val).GetInstanceID()} -> broadcast commit"); } return true; case NetMode.Client: if (NetLinkState.ClientLinked) { if (_fightStartCommittedWormId == ((Object)val).GetInstanceID()) { return false; } if (_fightStartRequestedWormId != ((Object)val).GetInstanceID()) { _fightStartRequestedWormId = ((Object)val).GetInstanceID(); NetGameplaySyncBridge.SendClientEmperorFightStart(); Plugin.Log.Info($"[EmperorWorm] client fight-start (local dialog) worm={((Object)val).GetInstanceID()} -> requested host commit (local start blocked)"); } return false; } break; } return true; } catch { return true; } } public static void HostOnClientFightStartRequest() { Component hostWormRef = _hostWormRef; if (hostWormRef != null) { Component val = hostWormRef; if (!((Object)(object)val == (Object)null)) { if (_fightStartCommittedWormId != ((Object)val).GetInstanceID()) { HostCommitFightStart(val, "client request"); } return; } } _hostFightStartPending = true; Plugin.Log.Info("[EmperorWorm] client fight-start request arrived before host worm is live — deferred."); } private static void HostCommitFightStart(Component worm, string origin) { _fightStartCommittedWormId = ((Object)worm).GetInstanceID(); NetGameplaySyncBridge.BroadcastEmperorFightStart(); InvokeStartMovement(worm, origin); FinalizeLocalDialog(origin); DisableBossDialogOnFightStart(); Plugin.Log.Info($"[EmperorWorm] host fight-start ({origin}) worm={((Object)worm).GetInstanceID()} -> committed + broadcast"); } public static void OnFightStartCommitReceived() { Component clientWormRef = _clientWormRef; if (clientWormRef != null) { Component val = clientWormRef; if (!((Object)(object)val == (Object)null)) { if (_fightStartCommittedWormId == ((Object)val).GetInstanceID()) { Plugin.Log.Info($"[EmperorWorm] fight-start commit ignored — worm={((Object)val).GetInstanceID()} already started."); return; } _fightStartCommittedWormId = ((Object)val).GetInstanceID(); InvokeStartMovement(val, "host commit"); FinalizeLocalDialog("host commit"); DisableBossDialogOnFightStart(); Plugin.Log.Info($"[EmperorWorm] client mirrored fight-start (host commit) worm={((Object)val).GetInstanceID()}"); return; } } Plugin.Log.Warn("[EmperorWorm] fight-start commit received before any client worm is being driven — dropped."); } private static void FinalizeLocalDialog(string origin) { if (BossDialogReflect.TryFinalizeCurrentDialog(out string detail)) { Plugin.Log.Info("[EmperorWorm] fight-start (" + origin + ") closed local pre-fight dialog — " + detail); } } public static void DisableBossDialogOnFightStart() { try { Component val = _hostWormRef ?? _clientWormRef; if ((Object)(object)val == (Object)null) { return; } if (_npcType == null) { _npcType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.Units.Npc"); } if (_npcType == null) { return; } Component val2 = val.GetComponent(_npcType) ?? val.GetComponentInChildren(_npcType); if ((Object)(object)val2 == (Object)null) { Plugin.Log.Warn("[EmperorWorm] fight-start: worm boss Npc not found (dialog not disabled)"); return; } if (_npcDialogProp == null) { _npcDialogProp = _npcType.GetProperty("dialog", BindingFlags.Instance | BindingFlags.Public); } if (_npcDialogProp != null && _npcDialogProp.CanWrite && _npcDialogProp.GetValue(val2) != null) { _npcDialogProp.SetValue(val2, null); Plugin.Log.Info("[EmperorWorm] fight-start disabled P1 boss dialog (worm can no longer be re-talked during combat)"); } } catch (Exception ex) { Plugin.Log.Warn("[EmperorWorm] DisableBossDialogOnFightStart failed: " + ex.Message); } } private static void InvokeStartMovement(Component worm, string origin) { EnsureReflect(worm); if (_startMovementMi == null) { Plugin.Log.Warn("[EmperorWorm] fight-start (" + origin + "): StartMovement not resolved."); return; } _inFightStartCommit = true; try { _startMovementMi.Invoke(worm, null); } catch (Exception ex) { Plugin.Log.Warn("[EmperorWorm] fight-start (" + origin + ") invoke failed: " + ex.Message); } finally { _inFightStartCommit = false; } } public static void HostAnnounceSectionDestroy(object worm, int index) { _sectionDestroySendSeq++; NetGameplaySyncBridge.BroadcastEmperorWormSectionDestroy(_sectionDestroySendSeq); Plugin.Log.Info($"[EmperorWorm] host DestroySection index={index} -> broadcast seq={_sectionDestroySendSeq}"); } public static void OnSectionDestroyReceived(int seq) { try { Component clientWormRef = _clientWormRef; if ((Object)(object)clientWormRef == (Object)null) { Plugin.Log.Warn($"[EmperorWorm] section-destroy seq={seq} received before any client worm is being driven — dropped."); return; } EnsureReflect(clientWormRef); if (_destroySectionMi == null || _moveVulnerableUpMi == null) { Plugin.Log.Warn("[EmperorWorm] section-destroy: DestroySection/MoveVulnerableSectionUp not resolved."); return; } int num = ReadLastActiveIndex(clientWormRef); int num2 = num - 1; if (num2 < 0) { Plugin.Log.Warn($"[EmperorWorm] section-destroy seq={seq}: lastActiveIndex={num}, nothing left to destroy — dropped."); return; } _destroySectionMi.Invoke(clientWormRef, new object[1] { num2 }); _moveVulnerableUpMi.Invoke(clientWormRef, null); Plugin.Log.Info($"[EmperorWorm] client mirrored DestroySection seq={seq} index={num2}"); } catch (Exception ex) { Plugin.Log.Warn($"[EmperorWorm] OnSectionDestroyReceived seq={seq} failed: {ex.Message}"); } } private static int ReadLastActiveIndex(object worm) { if (_lastActiveIndexField == null) { _lastActiveIndexField = AccessTools.Field(worm.GetType(), "lastActiveIndex"); } object obj = _lastActiveIndexField?.GetValue(worm); if (obj is int) { return (int)obj; } return -1; } public static void HostAnnounceDeath(object worm) { _deathSendSeq++; NetGameplaySyncBridge.BroadcastEmperorWormDeath(_deathSendSeq); Plugin.Log.Info($"[EmperorWorm] host DeathAnimation started -> broadcast seq={_deathSendSeq}"); } public static void OnDeathReceived(int seq) { try { if (_clientDeathApplied) { Plugin.Log.Info($"[EmperorWorm] death seq={seq} ignored — already applied for this worm."); return; } Component clientWormRef = _clientWormRef; MonoBehaviour val = (MonoBehaviour)(object)((clientWormRef is MonoBehaviour) ? clientWormRef : null); if (val == null || (Object)(object)val == (Object)null) { Plugin.Log.Warn($"[EmperorWorm] death seq={seq} received before any client worm is being driven — dropped."); return; } EnsureReflect(clientWormRef); if (_deathAnimationMi == null) { Plugin.Log.Warn("[EmperorWorm] death: DeathAnimation not resolved."); return; } _clientDeathApplied = true; if (_deathAnimationMi.Invoke(clientWormRef, null) is IEnumerator enumerator) { val.StartCoroutine(enumerator); } Plugin.Log.Info($"[EmperorWorm] client mirrored DeathAnimation seq={seq}"); } catch (Exception ex) { Plugin.Log.Warn($"[EmperorWorm] OnDeathReceived seq={seq} failed: {ex.Message}"); } } public static void ResetClient() { _hasHead = false; _headSeq = -1; _headRecvAt = 0f; _prevRecvAt = 0f; _headTailHp = -1f; _lastWrittenTailHp = -1f; _headCollidersDisabled.Clear(); _sectionColliders.Clear(); _clientWormRef = null; _hostWormRef = null; _clientDeathApplied = false; _sectionDestroySendSeq = 0; _deathSendSeq = 0; _wormHitSendSeq = 0; _fightStartCommittedWormId = 0; _fightStartRequestedWormId = 0; _hostFightStartPending = false; _inFightStartCommit = false; } } internal static class TerrorbaumMechanicSync { internal sealed class SkyShotState { public HashSet MarkerKeys; public float Speed; public int Type; public int Caliber; public int Effect; public int Vfx; public int DamageType; public bool Valid; } private sealed class VolleyState { public Component Helper; public int Remaining; public float Delay; public float Speed; public float NextShotAt; public float EndAt; public int Type; public int Caliber; public int Effect; public int Vfx; public int DamageType; } private const BindingFlags BF = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static bool _resolved; private static FieldInfo _fIsDoingRoot; private static FieldInfo _fRootPoints; private static FieldInfo _fAttackRootTimer; private static FieldInfo _fAttackRootHitTimer; private static FieldInfo _fRootAtkMin; private static FieldInfo _fRootAtkMax; private static FieldInfo _fSmallRoots; private static FieldInfo _fSkyQueue; private static FieldInfo _fMarkerRefs; private static FieldInfo _fMarkerPrefab; private static FieldInfo _fStartHeight; private static FieldInfo _fAbsorbQueue; private static FieldInfo _fTreeShake; private static FieldInfo _fCasing; private static FieldInfo _fCasingRate; private static FieldInfo _fAoeLoopSound; private static FieldInfo _fBulletSpawn; private static FieldInfo _fSpawnDelay; private static FieldInfo _fMaxCollect; private static MethodInfo _mAbsorbBullet; private static float _lastSentAngle = float.MinValue; private static bool _wasDoingRoot; private static float _lastAbsorbSentAt; private const float AbsorbBatchWindow = 0.3f; private static readonly object _absorbLock = new object(); private static string _abKey; private static object _abComponent; private static int _abCount; private static float _abDamage; private static int _abDtype; private static float _abSpeed; private static int _abType; private static int _abCaliber; private static int _abEffect; private static int _abVfx; private static float _abStartedAt; private static Component _rootHelper; private static float _rootTargetAngle; private static float _rootLastPacketAt; private static VolleyState _volley; private static readonly List<(Component marker, float releaseAt)> _markers = new List<(Component, float)>(); private static bool _poolResolved; private static Type _autoPoolType; private static MethodInfo _poolGet; private static MethodInfo _poolRelease; private static Object _autoPool; private static bool LogOn { get { try { return Plugin.Cfg.LogBossEncounter.Value; } catch { return false; } } } private static bool EnsureResolved(object helper) { if (_resolved) { return _fRootPoints != null; } _resolved = true; try { Type type = helper.GetType(); _fIsDoingRoot = type.GetField("isDoingRoot", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fRootPoints = type.GetField("rootPoints", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fAttackRootTimer = type.GetField("attackRootTimer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fAttackRootHitTimer = type.GetField("attackRootHitTimer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fRootAtkMin = type.GetField("randomRootAttackMinTimer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fRootAtkMax = type.GetField("randomRootAttackMaxTimer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fSmallRoots = type.GetField("smallRootsAnimators", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fSkyQueue = type.GetField("skyProjectiles", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fMarkerRefs = type.GetField("projectileMarkerRefs", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fMarkerPrefab = type.GetField("bulletDetectMarker", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fStartHeight = type.GetField("randomBulletStartHeight", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fAbsorbQueue = type.GetField("absorbedProjectiles", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fTreeShake = type.GetField("treeShakeObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fCasing = type.GetField("casingParticle", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fCasingRate = type.GetField("casingRateOverTime", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fAoeLoopSound = type.GetField("aoeBulletLoopSoundEvent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fBulletSpawn = type.GetField("bulletSpawnpoint", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fSpawnDelay = type.GetField("projectileDelayEachSpawn", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _fMaxCollect = type.GetField("projectileMaxCollect", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _mAbsorbBullet = type.GetMethod("AbsorbPlayerBullet", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } catch { } return _fRootPoints != null; } public static void HostStreamRoot(object helper) { //IL_0094: 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) try { Component val = (Component)((helper is Component) ? helper : null); if (val == null || (Object)(object)val == (Object)null || !EnsureResolved(helper)) { return; } object obj = _fIsDoingRoot?.GetValue(helper); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) == 0) { _wasDoingRoot = false; _lastSentAngle = float.MinValue; return; } _wasDoingRoot = true; object? obj2 = _fRootPoints?.GetValue(helper); GameObject val2 = (GameObject)((obj2 is GameObject) ? obj2 : null); if (val2 != null && !((Object)(object)val2 == (Object)null)) { float y = val2.transform.localEulerAngles.y; if (_lastSentAngle == float.MinValue || !(Mathf.Abs(Mathf.DeltaAngle(_lastSentAngle, y)) < 0.5f)) { _lastSentAngle = y; NetBossEncounterManager.OnHostTerrorStateEvent(helper, "TerrorRootAngle", hasPos: true, new Vector3(y, 0f, 0f)); } } } catch { } } public static bool TryHostLash(object helper) { //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) try { if (NetGameplaySyncBridge.BossMode != NetMode.Host || !NetGameplaySyncBridge.IsSessionActive) { return false; } if (helper == null || !EnsureResolved(helper) || _fAttackRootTimer == null || _fAttackRootHitTimer == null || _fRootAtkMin == null || _fRootAtkMax == null || _fSmallRoots == null) { return false; } if (!(_fSmallRoots.GetValue(helper) is Animator[] array) || array.Length == 0) { return false; } float num = ((_fAttackRootTimer.GetValue(helper) is float num2) ? num2 : 0f); float num3 = ((_fAttackRootHitTimer.GetValue(helper) is float num4) ? num4 : 0f); num += Time.deltaTime; if (num > num3) { num = 0f; float num5 = ((_fRootAtkMin.GetValue(helper) is float num6) ? num6 : 5f); float num7 = ((_fRootAtkMax.GetValue(helper) is float num8) ? num8 : 12f); _fAttackRootHitTimer.SetValue(helper, Random.Range(num5, num7)); int num9 = Random.Range(0, array.Length); if ((Object)(object)array[num9] != (Object)null) { array[num9].SetTrigger("Attack"); } NetBossEncounterManager.OnHostTerrorStateEvent(helper, "TerrorRootLash:" + num9, hasPos: false, default(Vector3)); } _fAttackRootTimer.SetValue(helper, num); return true; } catch { return false; } } public static SkyShotState CaptureSkyShotPre(object helper) { //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0117: 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_011e: 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_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_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_014a: 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_016d: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Expected I4, but got Unknown //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Expected I4, but got Unknown //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Expected I4, but got Unknown //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Expected I4, but got Unknown //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Expected I4, but got Unknown try { if (NetGameplaySyncBridge.BossMode != NetMode.Host || !NetGameplaySyncBridge.IsSessionActive) { return null; } if (helper == null || !EnsureResolved(helper) || _fSkyQueue == null || _fMarkerRefs == null) { return null; } SkyShotState skyShotState = new SkyShotState { MarkerKeys = new HashSet() }; if (_fMarkerRefs.GetValue(helper) is IDictionary dictionary) { foreach (object key in dictionary.Keys) { skyShotState.MarkerKeys.Add(key); } } if (!(_fSkyQueue.GetValue(helper) is IEnumerable enumerable)) { return skyShotState; } IEnumerator enumerator = enumerable.GetEnumerator(); try { if (enumerator.MoveNext() && enumerator.Current is ITuple { Length: >=2 } tuple && tuple[1] is FullProjectileDescription val) { Vector3 val2 = new Vector3(val.ray.velocity.x, val.ray.velocity.y, val.ray.velocity.z); skyShotState.Speed = Mathf.Clamp(((Vector3)(ref val2)).magnitude, 5f, 30f); skyShotState.Type = (int)val.ray.type; skyShotState.Caliber = (int)val.data.caliber; skyShotState.Effect = (int)val.ray.effect; skyShotState.Vfx = (int)val.ray.vfxAsset; skyShotState.DamageType = (int)val.data.damageType; skyShotState.Valid = true; } } finally { IDisposable disposable = enumerator as IDisposable; if (disposable != null) { disposable.Dispose(); } } return skyShotState; } catch { return null; } } public static void BroadcastSkyShotPost(object helper, SkyShotState st) { //IL_00fb: Unknown result type (might be due to invalid IL or missing references) try { if (st == null || !st.Valid || helper == null || _fMarkerRefs == null || !(_fMarkerRefs.GetValue(helper) is IDictionary dictionary)) { return; } foreach (DictionaryEntry item in dictionary) { if (!st.MarkerKeys.Contains(item.Key)) { object? value = item.Value; Component val = (Component)((value is Component) ? value : null); if (val != null && !((Object)(object)val == (Object)null)) { string eventName = string.Format(CultureInfo.InvariantCulture, "TerrorSky:{0:F1}:{1}:{2}:{3}:{4}:{5}", st.Speed, st.Type, st.Caliber, st.Effect, st.Vfx, st.DamageType); NetBossEncounterManager.OnHostTerrorStateEvent(helper, eventName, hasPos: true, val.transform.position); } break; } } } catch { } } public static void HostAbsorbed(object helper) { //IL_0036: 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) try { if (NetGameplaySyncBridge.BossMode == NetMode.Host && NetGameplaySyncBridge.IsSessionActive) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup - _lastAbsorbSentAt < 0.2f)) { _lastAbsorbSentAt = realtimeSinceStartup; NetBossEncounterManager.OnHostTerrorStateEvent(helper, "TerrorAbsorb", hasPos: false, default(Vector3)); } } } catch { } } public static void HostVolleyStarted(object helper) { //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011c: 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_0128: Expected I4, but got Unknown //IL_0128: 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_0136: Expected I4, but got Unknown //IL_0136: 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_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Expected I4, but got Unknown //IL_0144: 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_0152: Expected I4, but got Unknown //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0154: 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) //IL_0160: Expected I4, but got Unknown //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) try { if (NetGameplaySyncBridge.BossMode != NetMode.Host || !NetGameplaySyncBridge.IsSessionActive || helper == null || !EnsureResolved(helper) || _fAbsorbQueue == null || !(_fAbsorbQueue.GetValue(helper) is ICollection { Count: >0 } collection)) { return; } float num = ((_fSpawnDelay?.GetValue(helper) is float num2 && num2 > 0.01f) ? num2 : 0.2f); float num3 = 30f; int num4 = 0; int num5 = 0; int num6 = 0; int num7 = 0; int num8 = 7; IEnumerator enumerator = collection.GetEnumerator(); Vector3 pos; try { if (enumerator.MoveNext() && enumerator.Current is FullProjectileDescription val) { pos = new Vector3(val.ray.velocity.x, val.ray.velocity.y, val.ray.velocity.z); num3 = Mathf.Clamp(((Vector3)(ref pos)).magnitude, 20f, 40f); num4 = (int)val.ray.type; num5 = (int)val.data.caliber; num6 = (int)val.ray.effect; num7 = (int)val.ray.vfxAsset; num8 = (int)val.data.damageType; } } finally { IDisposable disposable = enumerator as IDisposable; if (disposable != null) { disposable.Dispose(); } } string eventName = string.Format(CultureInfo.InvariantCulture, "TerrorVolley:{0}:{1:F2}:{2:F1}:{3}:{4}:{5}:{6}:{7}", Mathf.Min(collection.Count, 600), num, num3, num4, num5, num6, num7, num8); pos = default(Vector3); NetBossEncounterManager.OnHostTerrorStateEvent(helper, eventName, hasPos: false, pos); } catch { } } public static void ClientNoteBodyHit(string key, object component, float damage, int dtype) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Invalid comparison between Unknown and I4 //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0045: 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_0037: Invalid comparison between Unknown and I4 //IL_004d: 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_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: 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_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Expected I4, but got Unknown //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Expected I4, but got Unknown //IL_00f6: 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_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Expected I4, but got Unknown //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Expected I4, but got Unknown try { GameManager instance = StaticInstance.Instance; EquipmentManager val = (((Object)(object)instance != (Object)null) ? instance.EquipmentManager : null); if ((Object)(object)val == (Object)null || ((int)val.weaponCurrentSlot != 4 && (int)val.weaponCurrentSlot != 5)) { return; } FullProjectileDescription lastFiredProjectile = val.lastFiredProjectile; if (lastFiredProjectile.isRaygun || (int)lastFiredProjectile.ray.type == 0) { return; } lock (_absorbLock) { if (_abCount > 0 && !string.Equals(_abKey, key, StringComparison.Ordinal)) { _abCount = 0; } if (_abCount == 0) { _abKey = key; _abComponent = component; _abStartedAt = Time.realtimeSinceStartup; } _abCount++; _abDamage = damage; _abDtype = dtype; _abSpeed = math.length(lastFiredProjectile.ray.velocity); _abType = (int)lastFiredProjectile.ray.type; _abCaliber = (int)lastFiredProjectile.data.caliber; _abEffect = (int)lastFiredProjectile.ray.effect; _abVfx = (int)lastFiredProjectile.ray.vfxAsset; } } catch { } } public static bool TryFlushClientAbsorb(out string key, out object component, out string role, out float damage, out int dtype) { key = null; component = null; role = null; damage = 0f; dtype = 0; lock (_absorbLock) { if (_abCount <= 0 || Time.realtimeSinceStartup - _abStartedAt < 0.3f) { return false; } key = _abKey; component = _abComponent; damage = _abDamage; dtype = _abDtype; role = string.Format(CultureInfo.InvariantCulture, "absorb:{0}:{1:F1}:{2}:{3}:{4}:{5}", Mathf.Min(_abCount, 200), _abSpeed, _abType, _abCaliber, _abEffect, _abVfx); _abCount = 0; _abKey = null; _abComponent = null; return true; } } public static bool HostAbsorbFeed(object helper, string role, float damage, int dtype, out string detail) { //IL_01d3: 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_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Unknown result type (might be due to invalid IL or missing references) //IL_02c6: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02da: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Unknown result type (might be due to invalid IL or missing references) //IL_02fe: Unknown result type (might be due to invalid IL or missing references) //IL_0300: Unknown result type (might be due to invalid IL or missing references) //IL_031b: Unknown result type (might be due to invalid IL or missing references) detail = ""; try { Component val = (Component)((helper is Component) ? helper : null); if (val == null || (Object)(object)val == (Object)null || !EnsureResolved(helper)) { detail = "unresolved"; return false; } CultureInfo invariantCulture = CultureInfo.InvariantCulture; string[] array = role.Split(new char[1] { ':' }); if (array.Length < 7) { detail = "malformed role"; return false; } int num = int.Parse(array[1], invariantCulture); float num2 = float.Parse(array[2], invariantCulture); int num3 = int.Parse(array[3], invariantCulture); int num4 = int.Parse(array[4], invariantCulture); int num5 = int.Parse(array[5], invariantCulture); int num6 = int.Parse(array[6], invariantCulture); if (num <= 0) { detail = "count<=0"; return false; } object member = BossReflect.GetMember(helper, "fightStarted"); bool flag = default(bool); int num7; if (member is bool) { flag = (bool)member; num7 = 1; } else { num7 = 0; } if (((uint)num7 & (flag ? 1u : 0u)) == 0) { detail = "fight not started"; return false; } member = BossReflect.GetMember(helper, "isDoingAoe"); bool flag2 = default(bool); int num8; if (member is bool) { flag2 = (bool)member; num8 = 1; } else { num8 = 0; } if (((uint)num8 & (flag2 ? 1u : 0u)) != 0) { detail = "mid-volley (isDoingAoe)"; return false; } int num9 = ((_fMaxCollect?.GetValue(helper) is int num10 && num10 > 0) ? num10 : 150); int num11 = ((_fAbsorbQueue?.GetValue(helper) is ICollection collection) ? collection.Count : 0); if (num11 >= num9) { detail = $"queue full {num11}/{num9}"; return false; } if (_mAbsorbBullet == null) { detail = "AbsorbPlayerBullet not found"; return false; } float3 val2 = default(float3); ((float3)(ref val2))..ctor(val.transform.position.x, val.transform.position.y, val.transform.position.z); ProjectileRay ray = default(ProjectileRay); ((ProjectileRay)(ref ray))..ctor(val2, (ProjectileTypes)num3); ray.velocity = new float3(0f, Mathf.Max(1f, num2), 0f); ray.radius = 0.05f; ray.timeScale = 1f; ray.lifeTime = 20f; ray.effect = (ProjectileEffect)num5; ray.vfxAsset = (VFX_Persistent)(byte)num6; ray.drawDefaultBullet = true; ray.playImpactSounds = true; ray.createBulletHoles = true; ray.shotOrLastBounceFrom = val2; ray.barrelPosition = val2; PlayerWeaponFireManager.ApplyNativeCaliberPresentation(ref ray, num4); ref FixedList32Bytes damageComps = ref ray.damageComps; ProjectileDamage val3 = new ProjectileDamage(damage, (DamageTypes)(byte)dtype); damageComps.Add(ref val3); FullProjectileDescription val4 = new FullProjectileDescription { ray = ray, data = new ProjectileData { damageType = (DamageTypes)(byte)dtype, caliber = (CaliberTypes)(byte)num4, isPlayer = true }, visuals = null, isRaygun = false }; int num12 = Mathf.Max(1, (int)((float)num * 0.5f)); object[] parameters = new object[1] { val4 }; for (int i = 0; i < num12; i++) { _mAbsorbBullet.Invoke(helper, parameters); } detail = $"hits={num} absorbs={num12} dmg={damage:0.0} dtype={dtype} speed={num2:0.0} queue={num11}->{num11 + num12 * 3}"; return true; } catch (Exception ex) { detail = ex.GetType().Name + ": " + ex.Message; return false; } } public static void ApplyRootAngle(object component, float angleY) { Component val = (Component)((component is Component) ? component : null); if (val != null && (Object)(object)val != (Object)null) { _rootHelper = val; _rootTargetAngle = angleY; _rootLastPacketAt = Time.realtimeSinceStartup; } } public static void ApplyRootStop(object component) { _rootHelper = null; if (BossReflect.TryInvokeArg(component, "StopRoot", false, out string detail) && LogOn) { Plugin.Log.Info("[BossMech] client StopRoot mirrored (" + detail + ")"); } } public static void ApplyLash(object component, int idx) { try { if (component != null && EnsureResolved(component) && _fSmallRoots?.GetValue(component) is Animator[] array && array.Length != 0 && idx >= 0 && idx < array.Length && !((Object)(object)array[idx] == (Object)null)) { array[idx].SetTrigger("Attack"); } } catch { } } public static void ApplyAbsorb(object component) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) try { if (component == null || !EnsureResolved(component)) { return; } object? obj = _fTreeShake?.GetValue(component); Component val = (Component)((obj is Component) ? obj : null); if (val != null && !((Object)(object)val == (Object)null)) { VFXPlayManager instance = StaticInstance.Instance; if (instance != null) { instance.PlayOneShotEffect((VFX_OneShot)1, val.transform.position); } } } catch { } } public static void ApplyVolley(object component, int count, float delay, float speed, int type, int caliber, int effect, int vfx, int damageType) { try { Component val = (Component)((component is Component) ? component : null); if (val != null && !((Object)(object)val == (Object)null) && EnsureResolved(component) && count > 0) { float realtimeSinceStartup = Time.realtimeSinceStartup; _volley = new VolleyState { Helper = val, Remaining = count, Delay = delay, Speed = speed, NextShotAt = realtimeSinceStartup, EndAt = realtimeSinceStartup + (float)count * delay + 1f, Type = type, Caliber = caliber, Effect = effect, Vfx = vfx, DamageType = damageType }; SetVolleyPresentation(component, on: true); } } catch { } } private static void SetVolleyPresentation(object helper, bool on) { try { float num = ((on && _fCasingRate?.GetValue(helper) is float num2) ? num2 : 0f); if (_fCasing?.GetValue(helper) is IEnumerable enumerable) { foreach (object item in enumerable) { Object val = (Object)((item is Object) ? item : null); if (val == null || val == (Object)null) { continue; } try { object obj = item.GetType().GetProperty("emission", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(item); PropertyInfo propertyInfo = obj?.GetType().GetProperty("rateOverTime"); if (obj != null && !(propertyInfo == null)) { object value = Activator.CreateInstance(propertyInfo.PropertyType, num); propertyInfo.SetValue(obj, value); } } catch { } } } object? member = BossReflect.GetMember(helper, "bossAnimator"); Animator val2 = (Animator)((member is Animator) ? member : null); if (val2 != null && (Object)(object)val2 != (Object)null) { val2.SetBool("ShootingProjectiles", on); } object obj3 = _fAoeLoopSound?.GetValue(helper); if (obj3 != null) { Component val3 = (Component)((helper is Component) ? helper : null); if (val3 != null && (Object)(object)val3 != (Object)null) { obj3.GetType().GetMethod(on ? "Play" : "Stop", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(Transform) }, null)?.Invoke(obj3, new object[1] { val3.transform }); } } } catch { } } public static void ApplySkySpike(object component, Vector3 ground, float speed, int type, int caliber, int effect, int vfx, int damageType) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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_006f: 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_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) try { Component val = (Component)((component is Component) ? component : null); if (val == null || (Object)(object)val == (Object)null || !EnsureResolved(component)) { return; } float num = ((_fStartHeight?.GetValue(component) is float num2) ? num2 : (ground.y + 40f)); Vector3 origin = new Vector3(ground.x, num, ground.z); float num3 = Mathf.Max(0.2f, (num - ground.y) / Mathf.Max(1f, speed)); PlayerWeaponFireManager.FireVisualStraight(origin, Vector3.down * speed, type, caliber, effect, vfx, damageType); object? obj = _fMarkerPrefab?.GetValue(component); GameObject val2 = (GameObject)((obj is GameObject) ? obj : null); if (val2 != null && (Object)(object)val2 != (Object)null) { Component val3 = AutoPoolGetInstance(val2); if ((Object)(object)val3 != (Object)null) { val3.transform.position = ground; _markers.Add((val3, Time.realtimeSinceStartup + num3 + 0.75f)); } } } catch (Exception ex) { Plugin.Log.Warn("[BossMech] ApplySkySpike failed: " + ex.GetType().Name + ": " + ex.Message); } } public static void TickClient() { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)_rootHelper != (Object)null && Object.op_Implicit((Object)(object)_rootHelper)) { if (Time.realtimeSinceStartup - _rootLastPacketAt > 2f) { _rootHelper = null; } else if (EnsureResolved(_rootHelper)) { object? obj = _fRootPoints?.GetValue(_rootHelper); GameObject val = (GameObject)((obj is GameObject) ? obj : null); if (val != null && (Object)(object)val != (Object)null) { Vector3 localEulerAngles = val.transform.localEulerAngles; localEulerAngles.y = Mathf.LerpAngle(localEulerAngles.y, _rootTargetAngle, Mathf.Clamp01(12f * Time.deltaTime)); val.transform.localEulerAngles = localEulerAngles; } } } for (int num = _markers.Count - 1; num >= 0; num--) { if (!(Time.realtimeSinceStartup < _markers[num].releaseAt)) { Component item = _markers[num].marker; _markers.RemoveAt(num); if ((Object)(object)item != (Object)null && Object.op_Implicit((Object)(object)item)) { AutoPoolRelease(item); } } } VolleyState volley = _volley; if (volley == null) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if ((Object)(object)volley.Helper == (Object)null || !Object.op_Implicit((Object)(object)volley.Helper) || realtimeSinceStartup >= volley.EndAt) { if ((Object)(object)volley.Helper != (Object)null && Object.op_Implicit((Object)(object)volley.Helper)) { SetVolleyPresentation(volley.Helper, on: false); } _volley = null; } else if (volley.Remaining > 0 && realtimeSinceStartup >= volley.NextShotAt) { volley.NextShotAt = realtimeSinceStartup + volley.Delay; volley.Remaining--; object? obj2 = _fBulletSpawn?.GetValue(volley.Helper); Transform val2 = (Transform)((obj2 is Transform) ? obj2 : null); if (val2 != null && (Object)(object)val2 != (Object)null) { Vector3 origin = val2.TransformPoint(new Vector3(Random.Range(0f, 0.5f), 0f, 0f)); Vector3 randomDirectionInCode = Helpers.GetRandomDirectionInCode(15f, Vector3.up); PlayerWeaponFireManager.FireVisualStraight(origin, randomDirectionInCode * volley.Speed, volley.Type, volley.Caliber, volley.Effect, volley.Vfx, volley.DamageType); } if (volley.Remaining <= 0) { volley.EndAt = realtimeSinceStartup + 0.4f; } } } catch { } } public static void OnLevelChanged() { _rootHelper = null; _lastSentAngle = float.MinValue; _wasDoingRoot = false; _markers.Clear(); _volley = null; _lastAbsorbSentAt = 0f; lock (_absorbLock) { _abCount = 0; _abKey = null; _abComponent = null; } } private static Component AutoPoolGetInstance(GameObject prefab) { try { if (!_poolResolved) { _poolResolved = true; _autoPoolType = AccessTools.TypeByName("PerfectRandom.Sulfur.Core.AutoPool"); if (_autoPoolType != null) { MethodInfo[] methods = _autoPoolType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (methodInfo.Name == "GetInstance" && !methodInfo.IsGenericMethodDefinition && parameters.Length == 1 && parameters[0].ParameterType == typeof(GameObject)) { _poolGet = methodInfo; } if (methodInfo.Name == "ReleaseInstance" && parameters.Length == 1 && typeof(Component).IsAssignableFrom(parameters[0].ParameterType)) { _poolRelease = methodInfo; } } } } if (_autoPoolType == null || _poolGet == null) { return null; } if (_autoPool == (Object)null || !Object.op_Implicit(_autoPool)) { _autoPool = Object.FindAnyObjectByType(_autoPoolType); } if (_autoPool == (Object)null) { return null; } object? obj = _poolGet.Invoke(_autoPool, new object[1] { prefab }); return (Component)((obj is Component) ? obj : null); } catch { return null; } } private static void AutoPoolRelease(Component pooled) { try { if (_poolRelease == null || _autoPool == (Object)null || !Object.op_Implicit(_autoPool)) { if ((Object)(object)pooled != (Object)null) { pooled.gameObject.SetActive(false); } } else { _poolRelease.Invoke(_autoPool, new object[1] { pooled }); } } catch { try { if ((Object)(object)pooled != (Object)null) { pooled.gameObject.SetActive(false); } } catch { } } } } internal sealed class WitchBossControllerAdapter : BossAdapterBase { private static readonly string[] PhaseWitchControllerFields = new string[5] { "phase1", "phase3", "phase4", "phase5", "phase6" }; private const string P2DomeRole = "p2dome:"; private static readonly Dictionary _p2DomeUnit = new Dictionary(); private static readonly Dictionary _p2DomeByInstance = new Dictionary(); private static readonly HashSet _p2Suppressed = new HashSet(); private static int _p2RealDome = -1; public override string AdapterName => "WitchBossController"; protected override string TypeShortName => "WitchBossController"; protected override string[] TypeFullNames => new string[2] { "PerfectRandom.Sulfur.Gameplay.WitchBossController", "PerfectRandom.Sulfur.Core.WitchBossController" }; public override string[] StartChainMethods => new string[2] { "EventStarted", "StartFight" }; public override bool ProvidesPhaseState => true; private static bool WitchPhaseDamageEnabled { get { try { return Plugin.Cfg.EnableWitchPhaseDamageAuthority.Value; } catch { return false; } } } private static bool WitchP2ManifestEnabled { get { try { return Plugin.Cfg.EnableWitchPhase2Manifest.Value; } catch { return false; } } } public override bool IsStarted(object component) { bool value; return BossReflect.TryGetBool(component, "fightStarted", out value) && value; } public override void TryReadState(object component, out bool hasPhase, out int phaseIndex, out bool hasPos, out Vector3 pos) { hasPhase = BossReflect.TryGetInt(component, "currentPhase", out phaseIndex); hasPos = BossReflect.TryGetPosition(component, out pos); } public override object? GetHealthUnit(object component) { return BossReflect.GetMember(component, "witchMainUnit"); } public override void FillBossState(object component, NetBossState state) { base.FillBossState(component, state); BossReflect.TryGetInt(component, "currentPhase", out var value); state.PhaseIndex = value; state.FightStarted = BossReflect.TryGetBool(component, "fightStarted", out var value2) && value2; state.IntroFinished = BossReflect.TryGetBool(component, "isIntroFinished", out var value3) && value3; if (BossReflect.GetMember(component, "aliveAdds") is ICollection collection) { state.AliveAdds = collection.Count; } } public override bool TryApplyBossState(object component, NetBossState state, out string detail) { detail = ApplyHealth(component, state) + "; phase via WitchPhase-revision"; return true; } public int GetCurrentPhase(object component) { if (!BossReflect.TryGetInt(component, "currentPhase", out var value)) { return -1; } return value; } private static string? PhaseEnumToControllerField(int phase) { return phase switch { 2 => "phase1", 3 => "phase2", 4 => "phase3", 5 => "phase4", 6 => "phase5", 7 => "phase6", _ => null, }; } public bool EndCurrentWitchPhase(object component, out string detail) { int currentPhase = GetCurrentPhase(component); string text = PhaseEnumToControllerField(currentPhase); if (text == null) { detail = $"no controller for phase {currentPhase} (skip teardown)"; return false; } object member = BossReflect.GetMember(component, text); if (member == null) { detail = text + " controller null"; return false; } string text2 = ""; if (currentPhase == 3) { int num = HidePhase2Witches(component); ClearPhase2State(); text2 = $"; hidPhase2Witches={num} p2StateCleared"; } if (!(BossReflect.TryGetBool(member, "phaseActive", out var value) && value)) { detail = text + " already inactive" + text2; return text2.Length > 0; } string detail2; bool flag = BossReflect.TryInvoke(member, "EndPhase", out detail2); detail = $"EndPhase({text})={flag} {detail2}{text2}"; return flag; } public int HidePhase2Witches(object component) { int num = 0; try { object member = BossReflect.GetMember(component, "phase2"); if (member == null) { return 0; } if (!(BossReflect.GetMember(member, "spawnedWitches") is IList list)) { return 0; } object obj = TryGetAliveNpcs(); foreach (object item in list) { Object val = (Object)((item is Object) ? item : null); if (val == null || val == (Object)null) { continue; } object? member2 = BossReflect.GetMember(item, "hitmeshCollider"); Collider val2 = (Collider)((member2 is Collider) ? member2 : null); if (val2 != null && (Object)(object)val2 != (Object)null) { try { val2.enabled = false; } catch { } } object? member3 = BossReflect.GetMember(item, "animator"); Animator val3 = (Animator)((member3 is Animator) ? member3 : null); if (val3 != null && (Object)(object)val3 != (Object)null) { try { val3.SetBool("IsDisappeared", true); val3.SetTrigger("Disappear"); val3.ResetTrigger("Appear"); } catch { } } if (obj is IList list2) { try { list2.Remove(item); } catch { } } num++; } } catch { } return num; } private static object? TryGetAliveNpcs() { try { object obj = (BossReflect.FindType("GameManager", "PerfectRandom.Sulfur.Core.GameManager", "PerfectRandom.Sulfur.Gameplay.GameManager")?.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy))?.GetValue(null); return (obj == null) ? null : BossReflect.GetMember(obj, "aliveNpcs"); } catch { return null; } } public bool ApplyHostPhase(object component, int phaseInt, out string detail) { return BossReflect.TryInvokeWithEnumInt(component, "ChangePhase", phaseInt, out detail); } public bool ActivePhaseWitchExists(object component) { string text = PhaseEnumToControllerField(GetCurrentPhase(component)); if (text == null) { return false; } return GetPhaseWitchUnit(component, text) != null; } private static object? GetPhaseWitchUnit(object component, string controllerField) { object member = BossReflect.GetMember(component, controllerField); if (member != null) { return BossReflect.GetMember(member, "witchUnit"); } return null; } public override string? ResolveHitTargetRole(object component, object hitUnit) { if (hitUnit == null || !WitchPhaseDamageEnabled) { return null; } if (_p2DomeByInstance.TryGetValue(BossReflect.InstanceId(hitUnit), out var value)) { return "p2dome:" + value; } object healthUnit = GetHealthUnit(component); if (healthUnit != null && healthUnit == hitUnit) { return "main"; } string[] phaseWitchControllerFields = PhaseWitchControllerFields; foreach (string text in phaseWitchControllerFields) { object phaseWitchUnit = GetPhaseWitchUnit(component, text); if (phaseWitchUnit != null && phaseWitchUnit == hitUnit) { return text; } } return null; } public override object? ResolveHostTargetForRole(object component, string role) { if (string.IsNullOrEmpty(role)) { return null; } if (role == "main") { return base.ResolveHostTargetForRole(component, role); } if (!WitchPhaseDamageEnabled) { return null; } if (role.StartsWith("p2dome:", StringComparison.Ordinal) && int.TryParse(role.Substring("p2dome:".Length), out var result)) { return GetHostDomeWitch(component, result); } string[] phaseWitchControllerFields = PhaseWitchControllerFields; foreach (string text in phaseWitchControllerFields) { if (role == text) { return GetPhaseWitchUnit(component, text); } } return null; } public static bool IsInstancePhase2Suppressed(int instanceId) { return _p2Suppressed.Contains(instanceId); } public static void ClearPhase2State() { _p2DomeUnit.Clear(); _p2DomeByInstance.Clear(); _p2Suppressed.Clear(); _p2RealDome = -1; } public int GetHostRealDomeIndex(object component) { object member = BossReflect.GetMember(component, "phase2"); object member2 = BossReflect.GetMember(member, "realWitchUnit"); if (member2 != null && BossReflect.GetMember(member, "spawnedWitches") is IList list) { for (int i = 0; i < list.Count; i++) { if (list[i] == member2) { return i; } } } return -1; } public int GetHostDomeCount(object component) { if (!(BossReflect.GetMember(BossReflect.GetMember(component, "phase2"), "spawnedWitches") is ICollection collection)) { return -1; } return collection.Count; } private object? GetHostDomeWitch(object component, int dome) { if (BossReflect.GetMember(BossReflect.GetMember(component, "phase2"), "spawnedWitches") is IList list && dome >= 0 && dome < list.Count) { return list[dome]; } return null; } public bool IsPhase2Ready(object component, int domeCount) { object member = BossReflect.GetMember(component, "phase2"); if (member == null) { return false; } if (!(BossReflect.TryGetBool(member, "witchesCreated", out var value) && value)) { return false; } int num = ((BossReflect.GetMember(member, "spawnedWitches") is ICollection collection) ? collection.Count : (-1)); int num2 = ((BossReflect.GetMember(member, "domePositions") is ICollection collection2) ? collection2.Count : (-1)); if (num == domeCount && num2 == domeCount) { return BossReflect.GetMember(member, "realWitchUnit") != null; } return false; } public bool ApplyP2Manifest(object component, int realDomeIndex, int domeCount, out int placed, out string detail) { placed = 0; detail = ""; object member = BossReflect.GetMember(component, "phase2"); if (member == null) { detail = "no phase2 controller"; return false; } object member2 = BossReflect.GetMember(member, "realWitchUnit"); if (!(BossReflect.GetMember(member, "spawnedIllusions") is IList list) || !(BossReflect.GetMember(member, "domePositions") is IList list2) || member2 == null) { detail = "local witches not ready"; return false; } if (list2.Count < domeCount) { detail = $"domePositions {list2.Count} < {domeCount}"; return false; } ClearPhase2State(); _p2RealDome = realDomeIndex; object aliveNpcs = TryGetAliveNpcs(); if (realDomeIndex >= 0 && realDomeIndex < list2.Count) { if (PlaceAndShowDome(member, member2, list2[realDomeIndex], aliveNpcs)) { placed++; } RecordDome(realDomeIndex, member2); } int num = 0; for (int i = 0; i < domeCount; i++) { if (i == realDomeIndex) { continue; } if (num >= list.Count) { break; } object obj = list[num++]; if (obj != null) { if (PlaceAndShowDome(member, obj, list2[i], aliveNpcs)) { placed++; } RecordDome(i, obj); } } detail = $"placed={placed} realDome={realDomeIndex} domes={domeCount} suppressed={_p2Suppressed.Count}"; return true; } private void RecordDome(int dome, object unit) { _p2DomeUnit[dome] = unit; int num = BossReflect.InstanceId(unit); _p2DomeByInstance[num] = dome; _p2Suppressed.Add(num); } private bool PlaceAndShowDome(object p2, object npc, object domeTransform, object? aliveNpcs) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) try { Component val = (Component)((npc is Component) ? npc : null); if (val != null && (Object)(object)val != (Object)null) { Transform val2 = (Transform)((domeTransform is Transform) ? domeTransform : null); if (val2 != null && (Object)(object)val2 != (Object)null) { val.transform.position = val2.position; } } InvokeEnableWitchUnit(p2, npc, enable: true); if (aliveNpcs is IList list && !list.Contains(npc)) { try { list.Add(npc); } catch { } } object? member = BossReflect.GetMember(npc, "hitmeshCollider"); Collider val3 = (Collider)((member is Collider) ? member : null); if (val3 != null && (Object)(object)val3 != (Object)null) { try { val3.enabled = true; } catch { } } object? member2 = BossReflect.GetMember(npc, "animator"); Animator val4 = (Animator)((member2 is Animator) ? member2 : null); if (val4 != null && (Object)(object)val4 != (Object)null) { try { val4.SetBool("IsDisappeared", false); } catch { } } return true; } catch { return false; } } public bool ApplyP2Result(object component, int domeIndex, byte kind, out string detail) { BossReflect.GetMember(component, "phase2"); object aliveNpcs = TryGetAliveNpcs(); if (kind == 1) { int num = 0; foreach (KeyValuePair item in _p2DomeUnit) { if (item.Key != _p2RealDome && HideDomeUnit(item.Value, aliveNpcs)) { num++; } } detail = $"realHit hideAllIllusions hidden={num} (realDome={_p2RealDome} kept)"; return true; } if (_p2DomeUnit.TryGetValue(domeIndex, out object value)) { bool flag = HideDomeUnit(value, aliveNpcs); detail = $"illusionDefeated dome={domeIndex} hidden={flag}"; return flag; } detail = $"illusionDefeated dome={domeIndex} not in assignment"; return false; } private bool HideDomeUnit(object npc, object? aliveNpcs) { try { Object val = (Object)((npc is Object) ? npc : null); if (val == null || val == (Object)null) { return false; } object? member = BossReflect.GetMember(npc, "hitmeshCollider"); Collider val2 = (Collider)((member is Collider) ? member : null); if (val2 != null && (Object)(object)val2 != (Object)null) { try { val2.enabled = false; } catch { } } object? member2 = BossReflect.GetMember(npc, "animator"); Animator val3 = (Animator)((member2 is Animator) ? member2 : null); if (val3 != null && (Object)(object)val3 != (Object)null) { try { val3.SetBool("IsDisappeared", true); val3.SetTrigger("Disappear"); val3.ResetTrigger("Appear"); } catch { } } if (aliveNpcs is IList list) { try { list.Remove(npc); } catch { } } return true; } catch { return false; } } public bool TryApplyWitchDeath(object component, out string detail) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("teleportBack=").Append(TryStartPrivateCoroutine(component, "TeleportDeathDelay")); object member = BossReflect.GetMember(component, "witchMainUnit"); stringBuilder.Append(" barOff=").Append(BossReflect.TryInvokeBool(member, "AttachToBossUI", arg: false, out string detail2)); stringBuilder.Append(" churchFire=").Append(BossReflect.TryInvoke(BossReflect.GetMember(component, "fireSequence"), "StartChurchFire", out detail2)); TrySetAnimatorTrigger(BossReflect.GetMember(component, "outsideFireAnimator"), "StartEnd"); stringBuilder.Append(" musicStop=").Append(TryInvokeSingleFloat(BossReflect.GetMember(component, "musicTrigger"), "StopMusic", 2f)); TrySetBoolField(component, "fightStarted", value: false); TrySetActiveGO(BossReflect.GetMember(component, "blockadesToActivateAfterFight"), active: true); TrySetActiveGO(BossReflect.GetMember(component, "fireEffectRoot"), active: true); BossReflect.TryInvokeBool(BossReflect.GetMember(component, "followRain"), "ShouldEnableRain", arg: true, out detail2); BossReflect.TryInvoke(BossReflect.GetMember(component, "lightningSpawner"), "StartLightning", out detail2); BossReflect.TryInvoke(BossReflect.GetMember(component, "witchAnimationControl"), "EnableChurchBlockade", out detail2); stringBuilder.Append(" adds=").Append(BossReflect.TryInvoke(component, "DeactivateAllAdds", out detail2)); detail = stringBuilder.ToString(); return true; } private static bool TryStartPrivateCoroutine(object component, string method) { try { MonoBehaviour val = (MonoBehaviour)((component is MonoBehaviour) ? component : null); if (val == null || (Object)(object)val == (Object)null) { return false; } Type type = component.GetType(); while (type != null) { MethodInfo method2 = type.GetMethod(method, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method2 != null && typeof(IEnumerator).IsAssignableFrom(method2.ReturnType) && method2.Invoke(component, null) is IEnumerator enumerator) { val.StartCoroutine(enumerator); return true; } type = type.BaseType; } } catch { } return false; } private static void TrySetAnimatorTrigger(object? animator, string trigger) { try { Animator val = (Animator)((animator is Animator) ? animator : null); if (val != null && (Object)(object)val != (Object)null) { val.SetTrigger(trigger); } } catch { } } private static bool TryInvokeSingleFloat(object? obj, string method, float arg) { if (obj == null) { return false; } try { Type type = obj.GetType(); while (type != null) { MethodInfo method2 = type.GetMethod(method, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(float) }, null); if (method2 != null) { method2.Invoke(obj, new object[1] { arg }); return true; } type = type.BaseType; } } catch { } return false; } private static void TrySetActiveGO(object? go, bool active) { try { GameObject val = (GameObject)((go is GameObject) ? go : null); if (val != null && (Object)(object)val != (Object)null) { val.SetActive(active); return; } Component val2 = (Component)((go is Component) ? go : null); if (val2 != null && (Object)(object)val2 != (Object)null) { val2.gameObject.SetActive(active); } } catch { } } private static void TrySetBoolField(object obj, string name, bool value) { try { Type type = obj.GetType(); while (type != null) { FieldInfo field = type.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(bool)) { field.SetValue(obj, value); break; } type = type.BaseType; } } catch { } } private static void InvokeEnableWitchUnit(object controller, object npc, bool enable) { try { Type type = controller.GetType(); while (type != null) { MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "EnableWitchUnit")) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 3 && parameters[0].ParameterType.IsInstanceOfType(npc) && parameters[1].ParameterType == typeof(bool) && parameters[2].ParameterType == typeof(bool)) { methodInfo.Invoke(controller, new object[3] { npc, enable, false }); return; } } } type = type.BaseType; } } catch { } } public string DescribeAddManifest(object component) { List list = new List(); string[] array = new string[6] { "phase1", "phase2", "phase3", "phase4", "phase5", "phase6" }; foreach (string text in array) { object? member = BossReflect.GetMember(component, text); Component val = (Component)((member is Component) ? member : null); if (val != null && (Object)(object)val != (Object)null) { list.Add(text + "=" + (val.gameObject.activeInHierarchy ? "active" : "inactive")); } } object? member2 = BossReflect.GetMember(component, "witchMainUnit"); Component val2 = (Component)((member2 is Component) ? member2 : null); string arg = ((val2 != null && (Object)(object)val2 != (Object)null) ? ((Object)val2.gameObject).name : "?"); int num = ((BossReflect.GetMember(component, "aliveAdds") is ICollection collection) ? collection.Count : (-1)); return string.Format("witchMainUnit(real-damageable)={0} aliveAdds={1} {2}", arg, num, string.Join(" ", list)); } public override string DescribeForLog(object component) { bool flag = IsStarted(component); int value; bool flag2 = BossReflect.TryGetInt(component, "currentPhase", out value); BossReflect.TryGetBool(component, "isIntroFinished", out var value2); object member = BossReflect.GetMember(component, "witchMainUnit"); return string.Format("adapter={0} type={1} root={2} fightStarted={3} currentPhase={4} introFinished={5} witchMainUnit={6}", AdapterName, component.GetType().Name, BossReflect.RootName(component), flag, flag2 ? value.ToString() : "?", value2, (member != null) ? "yes" : "no"); } } internal static class WitchPhase2Probe { private static bool Enabled { get { try { return Plugin.Cfg.LogWitchPhase2Probe.Value; } catch { return false; } } } private static string Side() { try { return (NetGameplaySyncBridge.BossMode == NetMode.Host) ? "HOST" : ((NetGameplaySyncBridge.BossMode == NetMode.Client) ? "CLIENT" : "SP"); } catch { return "?"; } } public static void OnInitPhase(object witchPhase2) { if (!Enabled || witchPhase2 == null) { return; } try { Plugin.Log.Info("[WitchP2Probe] InitPhase entered (" + Side() + ") " + Snapshot(witchPhase2)); } catch { } } public static void OnShowWitchesEnter(object witchPhase2) { if (!Enabled || witchPhase2 == null) { return; } try { Plugin.Log.Info("[WitchP2Probe] ShowWitches ENTER (" + Side() + ") " + Snapshot(witchPhase2)); } catch { } } public static void OnShowWitchesExit(object witchPhase2) { if (!Enabled || witchPhase2 == null) { return; } try { Plugin.Log.Info($"[WitchP2Probe] ShowWitches EXIT ({Side()}) {Snapshot(witchPhase2)} realDomeIndex={ResolveRealDomeIndex(witchPhase2)}"); } catch { } } private static string Snapshot(object p2) { bool value; bool flag = BossReflect.TryGetBool(p2, "witchesCreated", out value) && value; int num = CountOf(p2, "spawnedWitches"); int num2 = CountOf(p2, "spawnedIllusions"); int num3 = CountOf(p2, "domePositions"); bool value2; bool flag2 = BossReflect.TryGetBool(p2, "hasInitPhase", out value2) && value2; bool value3; bool flag3 = BossReflect.TryGetBool(p2, "phaseActive", out value3) && value3; float value4; string text = (BossReflect.TryGetFloat(p2, "delayTimer", out value4) ? value4.ToString("0.00") : "?"); float time = Time.time; return string.Format("witchesCreated={0} spawnedWitches={1} spawnedIllusions={2} domePositions={3} hasInitPhase={4} phaseActive={5} delayTimer={6} time={7:0.00} (delayElapsed={8})", flag, num, num2, num3, flag2, flag3, text, time, (text != "?") ? (time >= value4).ToString() : "?"); } private static int ResolveRealDomeIndex(object p2) { try { object member = BossReflect.GetMember(p2, "realWitchUnit"); if (member == null) { return -1; } if (BossReflect.GetMember(p2, "spawnedWitches") is IList list) { for (int i = 0; i < list.Count; i++) { if (list[i] == member) { return i; } } } } catch { } return -1; } private static int CountOf(object obj, string member) { if (!(BossReflect.GetMember(obj, member) is ICollection collection)) { return -1; } return collection.Count; } } } namespace SULFURTogether.Logging { public class STLogger { private const string Prefix = "[SULFUR Together] "; private readonly ManualLogSource _src; private readonly CoopConfig _cfg; public STLogger(ManualLogSource src, CoopConfig cfg) { _src = src; _cfg = cfg; } public void Info(string msg) { _src.LogInfo((object)("[SULFUR Together] " + msg)); } public void Warn(string msg) { _src.LogWarning((object)("[SULFUR Together] " + msg)); } public void Error(string msg) { _src.LogError((object)("[SULFUR Together] " + msg)); } public void Debug(string msg) { if (_cfg.EnableDebugLog.Value) { _src.LogDebug((object)("[SULFUR Together] " + msg)); } } } } namespace SULFURTogether.Config { public class CoopConfig { public ConfigEntry EnableDebugLog { get; } public ConfigEntry EnableReverseProbe { get; } public ConfigEntry EnablePlayerProbe { get; } public ConfigEntry EnableUnitProbe { get; } public ConfigEntry EnableNpcProbe { get; } public ConfigEntry LogUnitReceiveDamage { get; } public ConfigEntry LogTeleportDiag { get; } public ConfigEntry EnableLootProbe { get; } public ConfigEntry EnablePickupProbe { get; } public ConfigEntry EnableLevelProbe { get; } public ConfigEntry EnableVerboseUnitSpawnProbe { get; } public ConfigEntry EnableBreakableSpawnProbe { get; } public ConfigEntry EnableVerboseInventoryProbe { get; } public ConfigEntry EnableNpcMeleeProbe { get; } public ConfigEntry EnableAiTargetProbe { get; } public ConfigEntry EnableAiUpdateTargetProbe { get; } public ConfigEntry EnableAiSetDestinationProbe { get; } public ConfigEntry EnableAiNavMeshStateProbe { get; } public Fixed LogAiTargetingReverseDump { get; } public ConfigEntry EnableAiCanMoveProbe { get; } public ConfigEntry EnableNpcUpdateProbe { get; } public ConfigEntry EnableInventorySerializationProbe { get; } public ConfigEntry EnableInventoryDestroyProbe { get; } public ConfigEntry EnableInventoryTransferProbe { get; } public ConfigEntry EnableInventoryDropProbe { get; } public ConfigEntry EnablePickupSpawnProbe { get; } public ConfigEntry EnablePickupExecuteProbe { get; } public ConfigEntry EnableLootRegisterProbe { get; } public ConfigEntry EnableLootSpawnProbe { get; } public ConfigEntry EnableVerbosePickupProbe { get; } public ConfigEntry EnableVerboseLootProbe { get; } public ConfigEntry CompactPickupLogs { get; } public ConfigEntry CompactLootLogs { get; } public ConfigEntry PickupBurstSummaryIntervalSeconds { get; } public ConfigEntry LootBurstSummaryIntervalSeconds { get; } public ConfigEntry ProbeThrottleSeconds { get; } public ConfigEntry MaxRepeatedLogPerKey { get; } public ConfigEntry EnableProbeSummary { get; } public ConfigEntry ProbeSummaryIntervalSeconds { get; } public ConfigEntry SuppressEmptyProbeSummary { get; } public Setting HostAddress { get; } public Setting HostPort { get; } public Setting PlayerName { get; } public Setting MaxPlayers { get; } public Setting ConnectionKey { get; } public Setting RequireSameModVersion { get; } public Setting LastSteamIdToJoin { get; } public ConfigEntry SendPingIntervalSeconds { get; } public CoopSettingsStore CoopSettings { get; } public Fixed EnableRunStateNegotiation { get; } public Fixed RunStateBroadcastIntervalSeconds { get; } public ConfigEntry WarnOnRunStateMismatch { get; } public Fixed EnableHostSceneAuthority { get; } public ConfigEntry WarnOnClientSceneDrift { get; } public Fixed EnableHostSceneRequestProtocol { get; } public Fixed AutoSendHostSceneRequestOnDrift { get; } public Fixed HostSceneRequestIntervalSeconds { get; } public Fixed EnableManualClientSceneFollow { get; } public ConfigEntry ManualClientSceneFollowKey { get; } public Fixed ManualClientSceneFollowRequiresHostRequest { get; } public Fixed EnableLevelSeedAuthority { get; } public Fixed RequireSameLevelSeedForSceneMatch { get; } public Fixed ApplyHostLevelSeedOnManualFollow { get; } public Fixed HideRemoteVisualWhenLevelSeedMismatch { get; } public Fixed SyncHostUsedSetsOnManualFollow { get; } public ConfigEntry LogUsedSetsTrace { get; } public Fixed ClientWaitHostGenerationInputBeforeFirstLoad { get; } public Fixed ClientLoadGateTimeoutSeconds { get; } public Fixed ClientLoadGateAllowFallbackAfterTimeout { get; } public Fixed ClientLoadGateRequestIntervalSeconds { get; } public Fixed ClientGateDeathRespawnUntilHostHub { get; } public Fixed ClientGateDeathRespawnTimeoutSeconds { get; } public Fixed EnableClientTransitionRelay { get; } public Fixed AllowClientInitiatedLevelLoad { get; } public Fixed ClientInitiatedLoadTimeoutSeconds { get; } public Fixed EnableClientReloadInPlaceRelay { get; } public ConfigEntry ClientLinkedByDefault { get; } public Fixed HostLinkedByDefault { get; } public ConfigEntry ClientUnlinkKey { get; } public ConfigEntry HostLinkToggleKey { get; } public ConfigEntry ClientJoinMode { get; } public Fixed EnableBossEncounterSync { get; } public Fixed BossEncounterClientBlockLocalStart { get; } public ConfigEntry LogBossEncounter { get; } public Fixed BossContinuationGraceSeconds { get; } public Fixed EnableBossLifecycleProbe { get; } public ConfigEntry LogBossLifecycle { get; } public ConfigEntry LogBossPreFight { get; } public Fixed RemoveBossDialogInteractableOnStart { get; } public Fixed EnableFaithfulBossIntro { get; } public Fixed GateBossFightOnDialogClose { get; } public Fixed DeferBossIntroArm { get; } public Fixed EnableBossRoomMembership { get; } public Fixed GateBossDialogToInRoom { get; } public Fixed ExcludeOutOfRoomPlayersFromBossAttacks { get; } public ConfigEntry EnableEmperorWormDiagnostics { get; } public ConfigEntry EnableEmperorSpiderDiagnostics { get; } public ConfigEntry LogEmperorWormPerf { get; } public ConfigEntry EmperorWormPerfThresholdMs { get; } public ConfigEntry LogBossTransitionDiagnostics { get; } public Fixed EnableBossDynamicSpawnManifest { get; } public ConfigEntry LogBossDynamicSpawn { get; } public Fixed EnableCousinArmSync { get; } public Fixed EnableBossDamageAuthority { get; } public Fixed EnableBossClientPresentation { get; } public Fixed EnableBossDiscreteEventAuthority { get; } public Fixed EnableLuciaEyeAuthority { get; } public Fixed EnableLuciaDeathAuthority { get; } public Fixed EnableWitchPhaseDamageAuthority { get; } public Fixed EnableWitchPhaseAuthority { get; } public ConfigEntry LogWitchPhase2Probe { get; } public Fixed EnableWitchPhase2Manifest { get; } public Fixed EnableWitchDeathFix { get; } public Fixed EnableRuntimeSpawnSync { get; } public ConfigEntry LogRuntimeSpawnSync { get; } public Fixed EnableRuntimeSpawnSnapOnBind { get; } public Fixed EnableRuntimeSpawnInertUntilBound { get; } public Fixed EnableDeathSpawnSync { get; } public Fixed EnableMinionSpawnSync { get; } public Fixed EnablePlayerWeaponSync { get; } public ConfigEntry LogPlayerWeaponSync { get; } public Fixed PlayerWeaponSyncMaxProjectilesPerShot { get; } public Fixed EnableBreakableSync { get; } public ConfigEntry LogBreakableSync { get; } public Fixed EnableGateSync { get; } public ConfigEntry LogGateSync { get; } public Fixed EnableTriggerDoorSync { get; } public ConfigEntry LogTriggerDoorSync { get; } public Fixed EnableArenaLockdown { get; } public ConfigEntry LogArenaLockdown { get; } public ConfigEntry ArenaEnterConfirmKey { get; } public Fixed EnableArenaGracePeriod { get; } public Setting EnableCoopToasts { get; } public Setting FriendlyFire { get; } public ConfigEntry LogFriendlyFire { get; } public Fixed EnableWorldItemDropSync { get; } public ConfigEntry LogWorldItemDropSync { get; } public ConfigEntry ShareAllLoot { get; } public Fixed EnableRemoteWeaponModel { get; } public ConfigEntry LogRemoteWeaponModel { get; } public ConfigEntry LogPlayerVisualDiscovery { get; } public ConfigEntry LogPlayerSpriteAssetScan { get; } public Fixed EnableRemotePlayerSpriteBody { get; } public Fixed EnableRemotePlayerNpcBody { get; } public Fixed RemotePlayerBodyUnitKeyword { get; } public ConfigEntry LogRemotePlayerBody { get; } public Fixed RemoteBodyScale { get; } public Fixed RemoteBodyFeetYOffset { get; } public Fixed RemoteWeaponScale { get; } public Fixed RemoteWeaponHipHeight { get; } public Fixed RemoteWeaponForward { get; } public Fixed RemoteWeaponRight { get; } public Fixed RemoteBodyPitchLimit { get; } public Fixed RemoteBodyDepthBias { get; } public Fixed RemoteNameSize { get; } public Fixed RemoteNameHeight { get; } public Fixed EnableAutoFollowHostSceneRequest { get; } public Fixed EnableLoadBarrier { get; } public Fixed LoadBarrierTimeoutSeconds { get; } public Fixed LoadBarrierBlockHostAdvance { get; } public Fixed LoadBarrierLogOnlyMode { get; } public Fixed EnableRemotePlayerVisualProxy { get; } public Fixed RemotePlayerTransformSendRateHz { get; } public Fixed RemotePlayerVisualTimeoutSeconds { get; } public Fixed RemotePlayerVisualInterpolationSpeed { get; } public Fixed RemotePlayerVisualSnapDistance { get; } public Fixed EnableRemotePlayerProxyCollision { get; } public Fixed RemotePlayerCollisionSoft { get; } public Fixed RemotePlayerSoftCollisionRadius { get; } public Fixed RemotePlayerSoftCollisionPushSpeed { get; } public ConfigEntry EnableGameplayEntityProbe { get; } public ConfigEntry GameplayEntityProbeSummaryIntervalSeconds { get; } public ConfigEntry LogGameplayEntitySpawn { get; } public ConfigEntry LogGameplayEntityDamage { get; } public ConfigEntry LogGameplayEntityDeath { get; } public ConfigEntry RequireStableSceneAndSeedForGameplayProbe { get; } public Fixed EnableHostEnemyDeathEventMirror { get; } public ConfigEntry LogReceivedEnemyDeathEvents { get; } public Fixed ApplyReceivedEnemyDeathEvents { get; } public Fixed EnemyDeathMirrorPositionTolerance { get; } public Fixed EnemyDeathMirrorUseHorizontalPositionTolerance { get; } public Fixed EnableClientEnemyDeathClaim { get; } public ConfigEntry LogReceivedClientEnemyDeathClaims { get; } public Fixed ApplyReceivedClientEnemyDeathClaimsOnHost { get; } public Fixed EnableCoopPlayerDownedRevive { get; } public ConfigEntry LogPlayerLifeSync { get; } public Fixed PlayerDownedRescueTimeoutSeconds { get; } public Fixed PlayerReviveHoldSeconds { get; } public Fixed PlayerReviveDistance { get; } public Fixed PlayerReviveHealthRatio { get; } public Fixed PlayerReviveInvulnerabilitySeconds { get; } public Fixed PlayerDownedHealthFloor { get; } public ConfigEntry PlayerReviveHoldKey { get; } public Fixed RequireReviveDistanceValidationOnHost { get; } public Fixed EnableHostEnemyStateSnapshotMirror { get; } public Fixed EnemyStateSnapshotSendRateHz { get; } public Fixed EnemyStateSnapshotMaxEnemiesPerPacket { get; } public Fixed OnlySendAliveEnemyStateSnapshots { get; } public ConfigEntry LogReceivedEnemyStateSnapshots { get; } public Fixed ApplyReceivedEnemyStateSnapshots { get; } public Fixed EnemyStateSnapshotPositionTolerance { get; } public Fixed EnemyStateSnapshotInterpolationSpeed { get; } public Fixed EnemyStateSnapshotPlaybackDurationMultiplier { get; } public Fixed EnemyStateSnapshotSnapDistance { get; } public Fixed EnemyStateSnapshotApplyRotationY { get; } public Fixed EnableClientEnemyAiSuppressionExperiment { get; } public Fixed SuppressClientEnemyAiWhenStateMirrorEnabled { get; } public Fixed EnableClientEnemyPuppetMode { get; } public ConfigEntry LogClientEnemyPuppetMode { get; } public ConfigEntry LogClientFrameHitch { get; } public Fixed ClientEnemyPuppetStaleReleaseSeconds { get; } public Fixed EnableHostEnemyAnimationMirror { get; } public Fixed ApplyReceivedEnemyAnimationMirror { get; } public ConfigEntry LogEnemyAnimationMirror { get; } public Fixed EnemyAnimationMirrorCrossFadeSeconds { get; } public Fixed EnemyAnimationMirrorNormalizedTimeTolerance { get; } public Fixed EnemyAnimationMirrorApplyAnimatorStatePlayback { get; } public Fixed EnemyAnimationMirrorApplyHostCombatStatePlayback { get; } public Fixed EnemyAnimationMirrorReplayHostCombatMethods { get; } public Fixed EnemyAnimationMirrorApplyCombatAnimatorFallback { get; } public Fixed EnemyAnimationMirrorHostCombatActionHoldSeconds { get; } public Fixed EnemyProjectileVisualMirrorEnabled { get; } public Fixed EnemyProjectileVisualMirrorUseNativeShootReplay { get; } public Fixed EnemyProjectileVisualMirrorSpeed { get; } public Fixed EnemyProjectileVisualMirrorLifetime { get; } public Fixed EnableGenericHostCombatAnimatorStateMirror { get; } public Fixed EnableHostAuthoritativeEnemyRangedDamage { get; } public Fixed EnableSyntheticRangedDamageFallback { get; } public Fixed EnableClientEnemyIntentDrivenMotion { get; } public ConfigEntry LogEnemyAiIntentMirror { get; } public Fixed EnemyIntentCorrectionDistance { get; } public Fixed EnemyIntentHardSnapDistance { get; } public Fixed EnemyIntentReplayMinIntervalSeconds { get; } public Fixed EnemyHostProjectileHitRadius { get; } public Fixed EnemyHostProjectileVerticalTolerance { get; } public Fixed EnemyHostProjectileMaxDistance { get; } public Fixed EnemyHostProjectileDamage { get; } public Fixed EnemyHostProjectileDamageCooldownSeconds { get; } public Fixed EnemyDamageDefaultType { get; } public Fixed EnableEnemyElementalStatusEffect { get; } public Fixed EnemyElementalStatusAmount { get; } public ConfigEntry LogEnemyHostDamageAuthority { get; } public Fixed EnableHostOnlyEnemyTargetAuthority { get; } public ConfigEntry LogEnemyTargetAuthority { get; } public Fixed EnemyTargetAuthorityProbeIntervalSeconds { get; } public Fixed EnableEnemyCombatProbe { get; } public ConfigEntry LogEnemyCombatProbe { get; } public Fixed EnableHostAuthorizedIntentExecution { get; } public Fixed HostAuthorizedIntentWindowSeconds { get; } public ConfigEntry LogHostAuthorizedIntentExecution { get; } public Fixed EnableClientEnemyNativeDamageSuppression { get; } public Fixed EnableClientPuppetAimOverride { get; } public Fixed EnableEnemyStateSnapshotDeltaCompression { get; } public Fixed EnemyStateSnapshotHeartbeatSeconds { get; } public Fixed EnemyStateSnapshotPositionDeltaThreshold { get; } public Fixed EnemyStateSnapshotRotationDeltaThresholdDegrees { get; } public Fixed EnemyStateSnapshotAnimationTimeDeltaThreshold { get; } public Fixed EnableHostEnemyDamageEventSync { get; } public Fixed EnableHostEnemyHealthStateSync { get; } public Fixed ApplyReceivedHostEnemyHealthState { get; } public ConfigEntry LogHostEnemyDamageEvents { get; } public ConfigEntry LogHostEnemyHealthState { get; } public Fixed AllowRosterBoundDeathDespitePositionDrift { get; } public Fixed HostDeathSnapBeforeApply { get; } public Fixed AllowDeathLateRebind { get; } public Fixed DisableClientEnemyDeathClaimWhenHostDamageSyncEnabled { get; } public Fixed EnableClientHitRequest { get; } public ConfigEntry LogClientHitRequests { get; } public Fixed FilterNonPlayerPuppetDamage { get; } public Fixed MakeClientPuppetsKinematic { get; } public Fixed StableWorldRosterBinding { get; } public Fixed ReleasePuppetOnHostDeath { get; } public Fixed EvictStaleHostBindings { get; } public Fixed SkipDeadHostIdxRebind { get; } public Fixed EnableRetroactiveEnemyBinding { get; } public Fixed EnableDestroyedUnitListSweep { get; } public Fixed ClientHitRequestMaxRangeMeters { get; } public Fixed ClientHitRequestRateLimitSeconds { get; } public Fixed EnableClientTerminalDeadLatch { get; } public ConfigEntry LogClientTerminalDead { get; } public Fixed EnableClientHitFlash { get; } public ConfigEntry LogClientHitFlash { get; } public Fixed ClientHitFlashDurationSeconds { get; } public Fixed EnableClientPendingDeadState { get; } public Fixed EnableClientDeathVisualFallback { get; } public Fixed ClientDeathVisualFallbackDelaySeconds { get; } public ConfigEntry LogClientPendingDead { get; } public Fixed EnableHostLevelManifest { get; } public ConfigEntry LogLevelManifest { get; } public ConfigEntry LogLevelManifestDiff { get; } public Fixed QuarantineClientOnlyManifestEnemies { get; } public Fixed EnableClientHitVisual { get; } public ConfigEntry EnableLevelGenTrace { get; } public ConfigEntry LogLevelGenTrace { get; } public Fixed EnableHostDrivenEnemyProxy { get; } public Fixed SuppressAllClientPuppetDamage { get; } public ConfigEntry LogClientPuppetDamageSuppression { get; } public Fixed EnableHostAttackPhaseEvents { get; } public ConfigEntry LogHostAttackPhaseEvents { get; } public Fixed EnableClientAttackPhaseAnimatorDrive { get; } public Fixed ClientAttackPhaseCrossFadeSeconds { get; } public Fixed EnableHostProjectileVisualSpawnEvent { get; } public Fixed EnableEnemyInterestManagement { get; } public Fixed IncludeRemotePlayersInInterest { get; } public Fixed FullRateForEngagedEnemies { get; } public Fixed ClientEngagedEnemyFullRateSeconds { get; } public Fixed ThrottleOnlyWithKnownRemotePositions { get; } public ConfigEntry LogEnemyInterestDiag { get; } public Fixed SendAllEnemySnapshotsToClients { get; } public Fixed DisablePauseInMultiplayer { get; } public ConfigEntry LogPauseSuppression { get; } public ConfigEntry LogDamageApplyHitch { get; } public ConfigEntry DamageApplyHitchThresholdMs { get; } public Fixed EnableRemotePlayerTargetProxy { get; } public Fixed RemotePlayerTargetProxySetIsPlayer { get; } public Fixed RemotePlayerTargetProxyForceAggro { get; } public Fixed RemotePlayerTargetProxyAggroRange { get; } public Fixed RemotePlayerTargetProxyOnlyWhenCloser { get; } public Fixed RemotePlayerTargetProxyHitboxLayer { get; } public Fixed RemotePlayerTargetProxyBodyBlocker { get; } public Fixed RemoveTargetProxyWhenPeerDowned { get; } public Fixed HideDownedLocalPlayerFromEnemies { get; } public Fixed BalanceCoopEnemyTargeting { get; } public ConfigEntry LogPooledObjectDestroyDiag { get; } public Fixed ApplyHostPlayerDamageViaReceiveDamage { get; } public Fixed EnemyNearCombatDistance { get; } public Fixed EnemyFarDistance { get; } public Fixed EnemyFarSnapshotHz { get; } public Fixed EnableCombatEventCoalescing { get; } public Fixed EnemyToClientDamageCoalesceSeconds { get; } public Fixed EnemyDamageEventMinIntervalSeconds { get; } public Fixed AttackPhaseEventMinIntervalSeconds { get; } public Fixed EnableMultiPlayerNpcActivation { get; } public Fixed MultiPlayerNpcActivationDistance { get; } public Fixed MultiPlayerNpcActivationsPerFrame { get; } public Fixed EnableRemotePlayerInPlayersList { get; } public Fixed EnableGhostPlayerHitbox { get; } public ConfigEntry LogRemotePlayerRegistry { get; } public Fixed SuppressGhostsWhileLoading { get; } public CoopConfig(ConfigFile cfg) { //IL_06a5: Unknown result type (might be due to invalid IL or missing references) //IL_07e3: Unknown result type (might be due to invalid IL or missing references) //IL_080d: Unknown result type (might be due to invalid IL or missing references) //IL_0b8c: Unknown result type (might be due to invalid IL or missing references) //IL_0d7a: Unknown result type (might be due to invalid IL or missing references) //IL_0d84: Expected O, but got Unknown //IL_1018: Unknown result type (might be due to invalid IL or missing references) //IL_188e: Unknown result type (might be due to invalid IL or missing references) //IL_1898: Expected O, but got Unknown CoopSettings = new CoopSettingsStore(cfg); CoopSettingsStore store = CoopSettings; PlayerName = new Setting(() => store.Values.playerName, delegate(string v) { store.Values.playerName = v; store.Save(); }); HostAddress = new Setting(() => store.Values.hostAddress, delegate(string v) { store.Values.hostAddress = v; store.Save(); }); HostPort = new Setting(() => store.Values.hostPort, delegate(int v) { store.Values.hostPort = v; store.Save(); }); ConnectionKey = new Setting(() => store.Values.connectionKey, delegate(string v) { store.Values.connectionKey = v ?? ""; store.Save(); }); MaxPlayers = new Setting(() => store.Values.maxPlayers, delegate(int v) { store.Values.maxPlayers = Mathf.Clamp(v, 2, 4); store.Save(); }); RequireSameModVersion = new Setting(() => store.Values.requireSameModVersion, delegate(bool v) { store.Values.requireSameModVersion = v; store.Save(); }); EnableCoopToasts = new Setting(() => store.Values.enableCoopToasts, delegate(bool v) { store.Values.enableCoopToasts = v; store.Save(); }); LastSteamIdToJoin = new Setting(() => store.Values.lastSteamIdToJoin, delegate(string v) { store.Values.lastSteamIdToJoin = v ?? ""; store.Save(); }); FriendlyFire = new Setting(() => store.Values.friendlyFire, delegate(bool v) { store.Values.friendlyFire = v; store.Save(); }); EnableDebugLog = cfg.Bind("Debug", "EnableDebugLog", false, "Verbose debug output."); EnableReverseProbe = cfg.Bind("Debug", "EnableReverseProbe", true, "Master switch for all reverse probes."); EnablePlayerProbe = cfg.Bind("Probe", "EnablePlayerProbe", true, "Log GameManager player/state events."); EnableUnitProbe = cfg.Bind("Probe", "EnableUnitProbe", true, "Log Unit / UnitManager lifecycle."); EnableNpcProbe = cfg.Bind("Probe", "EnableNpcProbe", true, "Log Npc events."); LogUnitReceiveDamage = cfg.Bind("Probe", "LogUnitReceiveDamage", false, "Log a line per Unit/Npc ReceiveDamage call (high-frequency in combat — default OFF). NOTE: this is a PURE LOG. The damage patches themselves (downed/client damage suppression, client→host hit forwarding, host Npc health-sync) are functional and always on, independent of this switch — see ApplyDamageForwardPatches. The old EnableDamageProbe/EnableNpcProbe gates conflated the two and could break combat when toggled off as a 'log'."); LogTeleportDiag = cfg.Bind("Probe", "LogTeleportDiag", false, "Diag (default OFF — high volume; also drives the per-enemy [PosDiag] line): TeleportPlayer.DoTeleport trigger + stack, local-player TeleportTo from/to, full DamageSourceData on suppressed puppet hits. Enable to debug teleport/position."); LogFriendlyFire = cfg.Bind("Probe", "LogFriendlyFire", false, "Diag (default OFF): FF-1 friendly-fire lines — per-proxy-hit source classification (sampled), FF hit request send/receive, session-settings broadcast/apply. PURE LOG; the friendly-fire feature itself is controlled by the connect-page toggle, not this switch."); EnableLootProbe = cfg.Bind("Probe", "EnableLootProbe", true, "Log LootManager and InventoryItem events."); EnablePickupProbe = cfg.Bind("Probe", "EnablePickupProbe", true, "Log InteractionManager pickup events."); EnableLevelProbe = cfg.Bind("Probe", "EnableLevelProbe", true, "Log level generation and transition events."); EnableVerboseUnitSpawnProbe = cfg.Bind("ProbeNoise", "EnableVerboseUnitSpawnProbe", false, "Log every Unit.Spawn (default: Player/Npc only)."); EnableBreakableSpawnProbe = cfg.Bind("ProbeNoise", "EnableBreakableSpawnProbe", false, "Log Breakable unit spawns (very noisy)."); EnableVerboseInventoryProbe = cfg.Bind("ProbeNoise", "EnableVerboseInventoryProbe", false, "Log every InventoryItem.Setup individually (default: summary only)."); EnableNpcMeleeProbe = cfg.Bind("ProbeNoise", "EnableNpcMeleeProbe", false, "Log Npc.HandleMeleeHit (very noisy in merchant areas)."); EnableAiTargetProbe = cfg.Bind("ProbeNoise", "EnableAiTargetProbe", false, "Log AiAgent.GetTarget (fires only on target change)."); EnableAiUpdateTargetProbe = cfg.Bind("ProbeNoise", "EnableAiUpdateTargetProbe", false, "Log AiAgent.UpdateTarget per-agent (throttled per instance)."); LogAiTargetingReverseDump = new Fixed(value: false); EnableAiSetDestinationProbe = cfg.Bind("ProbeNoise", "EnableAiSetDestinationProbe", false, "Log AiAgent.SetDestination per-agent (throttled per instance)."); EnableAiNavMeshStateProbe = cfg.Bind("ProbeNoise", "EnableAiNavMeshStateProbe", false, "Log AiAgent.SetNavMeshAgentState."); EnableAiCanMoveProbe = cfg.Bind("ProbeNoise", "EnableAiCanMoveProbe", false, "Log AiAgent.SetCanMove."); EnableNpcUpdateProbe = cfg.Bind("ProbeNoise", "EnableNpcUpdateProbe", false, "Log Npc.Update per-NPC (throttled, extremely noisy)."); EnableInventorySerializationProbe = cfg.Bind("ProbeNoise", "EnableInventorySerializationProbe", false, "Log InventoryItem.GetSerialized (fires on every save/serialize — very noisy)."); EnableInventoryDestroyProbe = cfg.Bind("ProbeNoise", "EnableInventoryDestroyProbe", false, "Log InventoryItem.DestroyFromInventory calls."); EnableInventoryTransferProbe = cfg.Bind("ProbeNoise", "EnableInventoryTransferProbe", false, "Log InventoryItem.TransferOwnership and TryMoveToPlayerInventory."); EnableInventoryDropProbe = cfg.Bind("ProbeNoise", "EnableInventoryDropProbe", false, "Log InventoryItem.DropFromPlayer."); EnablePickupSpawnProbe = cfg.Bind("Probe", "EnablePickupSpawnProbe", true, "Count and burst-summarize InteractionManager.SpawnPickup / RemovePickup events."); EnablePickupExecuteProbe = cfg.Bind("Probe", "EnablePickupExecuteProbe", true, "Count and burst-summarize InteractionManager.ExecutePickup events."); EnableLootRegisterProbe = cfg.Bind("Probe", "EnableLootRegisterProbe", true, "Count and burst-summarize LootManager.RegisterLootDropped events."); EnableLootSpawnProbe = cfg.Bind("Probe", "EnableLootSpawnProbe", true, "Count and burst-summarize LootManager.SpawnGlobalLoot / SpawnLootFrom events."); EnableVerbosePickupProbe = cfg.Bind("ProbeNoise", "EnableVerbosePickupProbe", false, "Log every pickup spawn/execute/remove individually. Very noisy during loot explosions."); EnableVerboseLootProbe = cfg.Bind("ProbeNoise", "EnableVerboseLootProbe", false, "Log every loot drop/spawn individually."); CompactPickupLogs = cfg.Bind("ProbeSummary", "CompactPickupLogs", true, "Replace per-pickup log lines with a burst summary every PickupBurstSummaryIntervalSeconds."); CompactLootLogs = cfg.Bind("ProbeSummary", "CompactLootLogs", true, "Replace per-loot log lines with a burst summary every LootBurstSummaryIntervalSeconds."); PickupBurstSummaryIntervalSeconds = cfg.Bind("ProbeSummary", "PickupBurstSummaryIntervalSeconds", 5f, "How often (seconds) to flush the pickup burst summary."); LootBurstSummaryIntervalSeconds = cfg.Bind("ProbeSummary", "LootBurstSummaryIntervalSeconds", 5f, "How often (seconds) to flush the loot burst summary."); ProbeThrottleSeconds = cfg.Bind("ProbeNoise", "ProbeThrottleSeconds", 10f, "Minimum seconds between repeated logs for the same throttle key."); MaxRepeatedLogPerKey = cfg.Bind("ProbeNoise", "MaxRepeatedLogPerKey", 3, "Max times a key with maxPerWindow can log within one throttle window."); EnableProbeSummary = cfg.Bind("ProbeSummary", "EnableProbeSummary", true, "Emit a periodic counter summary to the log."); ProbeSummaryIntervalSeconds = cfg.Bind("ProbeSummary", "ProbeSummaryIntervalSeconds", 30f, "How often (seconds) to flush the probe summary."); SuppressEmptyProbeSummary = cfg.Bind("ProbeSummary", "SuppressEmptyProbeSummary", true, "Skip the summary output if all delta counters are zero."); SendPingIntervalSeconds = cfg.Bind("Network", "SendPingIntervalSeconds", 2f, "How often (seconds) to send a Ping to peers."); EnableRunStateNegotiation = new Fixed(value: true); RunStateBroadcastIntervalSeconds = new Fixed(2f); WarnOnRunStateMismatch = cfg.Bind("NetworkRunState", "WarnOnRunStateMismatch", true, "Log a warning when local and remote chapter/level metadata differ. Loading vs Running on the same scene is ignored."); EnableHostSceneAuthority = new Fixed(value: true); WarnOnClientSceneDrift = cfg.Bind("NetworkSceneAuthority", "WarnOnClientSceneDrift", true, "Warn when a Client is not in the Host scene. Warning-only; no correction is performed."); EnableHostSceneRequestProtocol = new Fixed(value: true); AutoSendHostSceneRequestOnDrift = new Fixed(value: true); HostSceneRequestIntervalSeconds = new Fixed(10f); EnableManualClientSceneFollow = new Fixed(value: true); ManualClientSceneFollowKey = cfg.Bind("NetworkSceneAuthority", "ManualClientSceneFollowKey", new KeyboardShortcut((KeyCode)281, Array.Empty()), "Client-only manual follow key. Press this after receiving HostSceneRequest to attempt local GoToLevel. Avoid F1-F12 because SULFUR's DevTools/F-key bindings may toggle invulnerability or other debug states."); ManualClientSceneFollowRequiresHostRequest = new Fixed(value: true); EnableLevelSeedAuthority = new Fixed(value: true); RequireSameLevelSeedForSceneMatch = new Fixed(value: true); ApplyHostLevelSeedOnManualFollow = new Fixed(value: true); HideRemoteVisualWhenLevelSeedMismatch = new Fixed(value: true); SyncHostUsedSetsOnManualFollow = new Fixed(value: true); LogUsedSetsTrace = cfg.Bind("NetworkLevelSeed", "LogUsedSetsTrace", true, "Log [UsedSetsTrace]/[FollowPrep] details (GameManager used sets on level entry, and before/after applying Host used sets on follow)."); ClientWaitHostGenerationInputBeforeFirstLoad = new Fixed(value: true); ClientLoadGateTimeoutSeconds = new Fixed(30f); ClientLoadGateAllowFallbackAfterTimeout = new Fixed(value: false); ClientLoadGateRequestIntervalSeconds = new Fixed(2f); ClientGateDeathRespawnUntilHostHub = new Fixed(value: true); ClientGateDeathRespawnTimeoutSeconds = new Fixed(12f); EnableClientTransitionRelay = new Fixed(value: true); AllowClientInitiatedLevelLoad = new Fixed(value: true); ClientInitiatedLoadTimeoutSeconds = new Fixed(15f); EnableClientReloadInPlaceRelay = new Fixed(value: true); ClientLinkedByDefault = cfg.Bind("NetworkSceneAuthority", "ClientLinkedByDefault", false, "联机状态: whether the CLIENT starts LINKED (joining/following the host). Default false so an in-progress solo run is never hijacked — the player presses ManualClientSceneFollowKey (PageDown) to link and ClientUnlinkKey to unlink."); HostLinkedByDefault = new Fixed(value: true); ClientUnlinkKey = cfg.Bind("NetworkSceneAuthority", "ClientUnlinkKey", new KeyboardShortcut((KeyCode)280, Array.Empty()), "Client only: key to LEAVE 联机状态 (stop following/relaying and play the local run independently). PageDown links, this unlinks."); HostLinkToggleKey = cfg.Bind("NetworkSceneAuthority", "HostLinkToggleKey", new KeyboardShortcut((KeyCode)281, Array.Empty()), "Host only: key to TOGGLE 联机状态 (mod multiplayer on/off)."); EnableBossEncounterSync = new Fixed(value: true); BossEncounterClientBlockLocalStart = new Fixed(value: true); LogBossEncounter = cfg.Bind("NetworkBoss", "LogBossEncounter", true, "Log boss encounter discovery / start request / broadcast / apply for debugging."); BossContinuationGraceSeconds = new Fixed(5f); EnableBossLifecycleProbe = new Fixed(value: true); LogBossLifecycle = cfg.Bind("NetworkBoss", "LogBossLifecycle", true, "Phase 5.4-E2: log the boss lifecycle probe state-change lines. Compact and state-change-gated to avoid spam."); LogBossPreFight = cfg.Bind("NetworkBoss", "LogBossPreFight", true, "Phase PF-0: read-only diagnostic. When a boss pre-fight start entrypoint fires, log local+remote scene/seed convergence (did the client race ahead into a divergent boss instance?) and the room-seal/teleport timing. No gameplay change."); RemoveBossDialogInteractableOnStart = new Fixed(value: false); EnableFaithfulBossIntro = new Fixed(value: true); GateBossFightOnDialogClose = new Fixed(value: true); DeferBossIntroArm = new Fixed(value: true); EnableBossRoomMembership = new Fixed(value: true); GateBossDialogToInRoom = new Fixed(value: true); ExcludeOutOfRoomPlayersFromBossAttacks = new Fixed(value: true); EnableEmperorWormDiagnostics = cfg.Bind("NetworkBoss", "EnableEmperorWormDiagnostics", true, "Phase 5.4-E3: probe + log Emperor worm controller state (identify the double-worm source). Diagnostic only; does not change gameplay."); EnableEmperorSpiderDiagnostics = cfg.Bind("NetworkBoss", "EnableEmperorSpiderDiagnostics", true, "EMP-6a: observe-only probe for the Emperor phase-2 spider (lifecycle/startup-caller/position-divergence/defend/death). Diagnostic only; does not change gameplay."); LogEmperorWormPerf = cfg.Bind("NetworkBoss", "LogEmperorWormPerf", false, "EMP-1b (default OFF): stopwatch the Emperor worm's FixedUpdate + ground/underground native calls; log only frames slower than EmperorWormPerfThresholdMs to localize the ground-slam hitch."); EmperorWormPerfThresholdMs = cfg.Bind("NetworkBoss", "EmperorWormPerfThresholdMs", 6f, "EMP-1b: only log a worm perf sample when the measured call exceeds this many milliseconds."); LogBossTransitionDiagnostics = cfg.Bind("NetworkBoss", "LogBossTransitionDiagnostics", true, "Phase 5.4-E3 (P2): log extra context when a Client receives a HostSceneRequest while already loading/transitioning. Diagnostic only — does not change transition behavior."); EnableBossDynamicSpawnManifest = new Fixed(value: true); LogBossDynamicSpawn = cfg.Bind("NetworkBoss", "LogBossDynamicSpawn", true, "Phase 5.4-E4: log each boss dynamic spawn + binding result (bound / host-only / client-extra)."); EnableCousinArmSync = new Fixed(value: true); EnableBossDamageAuthority = new Fixed(value: true); EnableBossClientPresentation = new Fixed(value: false); EnableBossDiscreteEventAuthority = new Fixed(value: true); EnableLuciaEyeAuthority = new Fixed(value: true); EnableLuciaDeathAuthority = new Fixed(value: true); EnableWitchPhaseDamageAuthority = new Fixed(value: true); EnableWitchPhaseAuthority = new Fixed(value: true); LogWitchPhase2Probe = cfg.Bind("NetworkBoss", "LogWitchPhase2Probe", true, "Phase 5.4-G4: log WitchPhase2 InitPhase/ShowWitches timing (witchesCreated, spawn/dome counts, delayTimer, final real dome index) on both ends. Diagnostic-only."); EnableWitchPhase2Manifest = new Fixed(value: true); EnableWitchDeathFix = new Fixed(value: true); EnableRuntimeSpawnSync = new Fixed(value: true); LogRuntimeSpawnSync = cfg.Bind("NetworkEnemy", "LogRuntimeSpawnSync", true, "Phase 5.5-RT1: verbose log for runtime spawn sync (broadcast / mirror / bind)."); EnableRuntimeSpawnSnapOnBind = new Fixed(value: true); EnableRuntimeSpawnInertUntilBound = new Fixed(value: true); EnableDeathSpawnSync = new Fixed(value: true); EnableMinionSpawnSync = new Fixed(value: true); EnablePlayerWeaponSync = new Fixed(value: true); LogPlayerWeaponSync = cfg.Bind("PlayerWeapon", "LogPlayerWeaponSync", true, "Phase 5.6-WS: verbose log for player weapon sync (capture / broadcast / replay)."); PlayerWeaponSyncMaxProjectilesPerShot = new Fixed(256); EnableBreakableSync = new Fixed(value: true); LogBreakableSync = cfg.Bind("Destructibles", "LogBreakableSync", true, "Phase 5.7-BR: verbose log for destructible sync (capture / broadcast / mirror match)."); EnableGateSync = new Fixed(value: true); LogGateSync = cfg.Bind("Destructibles", "LogGateSync", true, "Phase LD-1: verbose log for gate sync (capture / broadcast / mirror match)."); EnableTriggerDoorSync = new Fixed(value: true); LogTriggerDoorSync = cfg.Bind("Destructibles", "LogTriggerDoorSync", true, "Phase LD-1b: verbose log for trigger-door sync (capture / broadcast / mirror match)."); EnableArenaLockdown = new Fixed(value: true); LogArenaLockdown = cfg.Bind("NetworkBoss", "LogArenaLockdown", true, "Phase LD-2: verbose log for arena lockdown (local crossings, in-room set, seal/popup/release/teleport)."); ArenaEnterConfirmKey = cfg.Bind("NetworkBoss", "ArenaEnterConfirmKey", new KeyboardShortcut((KeyCode)13, Array.Empty()), "Phase LD-2c: key an out-of-room player presses to confirm teleporting into a locked-down arena (the confirm prompt). Default Enter."); EnableArenaGracePeriod = new Fixed(value: true); EnableWorldItemDropSync = new Fixed(value: true); LogWorldItemDropSync = cfg.Bind("WorldItems", "LogWorldItemDropSync", true, "Verbose log for world item-drop sync (capture / mirror / take request / host grant / removal)."); ShareAllLoot = cfg.Bind("WorldItems", "ShareAllLoot", false, "FUTURE host room-setting: when true, ALL world pickups (loot included) are synced and shared (first picker takes it, it vanishes for everyone). When false (default), loot stays per-peer and only player-thrown items/guns are synced. NOTE: full shared-loot also needs host-authoritative loot rolling (suppress client rolls) — not yet implemented; flipping this now only widens the sync filter."); EnableRemoteWeaponModel = new Fixed(value: true); LogRemoteWeaponModel = cfg.Bind("PlayerWeapon", "LogRemoteWeaponModel", true, "Phase 5.6-WS-2: verbose log for remote weapon model sync (broadcast / build / attach)."); LogPlayerVisualDiscovery = cfg.Bind("PlayerWeapon", "LogPlayerVisualDiscovery", false, "Phase 5.6-WS-3: dump the local player's visual hierarchy once (find the directional billboard sprites). Diagnostic only; default off."); LogPlayerSpriteAssetScan = cfg.Bind("PlayerWeapon", "LogPlayerSpriteAssetScan", false, "Phase 5.6-WS-3b: one-shot scan of all loaded Sprites/Textures/Prefabs/Animators + Addressables keys for player/character art. Diagnostic only; default off."); EnableRemotePlayerSpriteBody = new Fixed(value: true); EnableRemotePlayerNpcBody = new Fixed(value: false); RemotePlayerBodyUnitKeyword = new Fixed("civilian,grocer,scholar,arthur,telia,citizen,man,woman"); LogRemotePlayerBody = cfg.Bind("PlayerWeapon", "LogRemotePlayerBody", true, "Phase 5.6-WS-3: verbose log for the remote player NPC billboard body (resolve / load / build / attach)."); RemoteBodyScale = new Fixed(1.2f); RemoteBodyFeetYOffset = new Fixed(0f); RemoteWeaponScale = new Fixed(1.4f); RemoteWeaponHipHeight = new Fixed(1f); RemoteWeaponForward = new Fixed(0.3f); RemoteWeaponRight = new Fixed(0.4f); RemoteBodyPitchLimit = new Fixed(25f); RemoteBodyDepthBias = new Fixed(0f); RemoteNameSize = new Fixed(0.03f); RemoteNameHeight = new Fixed(0.45f); ClientJoinMode = cfg.Bind("NetworkSceneAuthority", "ClientJoinMode", "AutoJoinFromHubOnly", new ConfigDescription("Client join policy. ManualOnly: never auto-join (use the manual follow key). AutoJoinFromHubOnly: auto-join/auto-follow only when the Client is in Hub/Menu/SafeZone; preserve any in-progress local combat/transition run. AskBeforeLeavingLocalRun: like AutoJoinFromHubOnly but logs a manual-confirm hint when not in a hub. ForceAutoJoin: always auto-join (test only; warns). Once joined, combat auto-follow to the Host's next level always continues regardless of this mode.", (AcceptableValueBase)(object)new AcceptableValueList(new string[4] { "ManualOnly", "AutoJoinFromHubOnly", "AskBeforeLeavingLocalRun", "ForceAutoJoin" }), Array.Empty())); EnableAutoFollowHostSceneRequest = new Fixed(value: true); EnableLoadBarrier = new Fixed(value: true); LoadBarrierTimeoutSeconds = new Fixed(30f); LoadBarrierBlockHostAdvance = new Fixed(value: false); LoadBarrierLogOnlyMode = new Fixed(value: true); EnableRemotePlayerVisualProxy = new Fixed(value: true); RemotePlayerTransformSendRateHz = new Fixed(10f); RemotePlayerVisualTimeoutSeconds = new Fixed(3f); RemotePlayerVisualInterpolationSpeed = new Fixed(12f); RemotePlayerVisualSnapDistance = new Fixed(8f); EnableRemotePlayerProxyCollision = new Fixed(value: true); RemotePlayerCollisionSoft = new Fixed(value: true); RemotePlayerSoftCollisionRadius = new Fixed(0.8f); RemotePlayerSoftCollisionPushSpeed = new Fixed(0.5f); EnableGameplayEntityProbe = cfg.Bind("NetworkGameplayProbe", "EnableGameplayEntityProbe", true, "Enable local-only structured gameplay entity probe logs. This never syncs or changes gameplay."); GameplayEntityProbeSummaryIntervalSeconds = cfg.Bind("NetworkGameplayProbe", "GameplayEntityProbeSummaryIntervalSeconds", 10f, "How often (seconds) to emit gameplay probe summaries."); LogGameplayEntitySpawn = cfg.Bind("NetworkGameplayProbe", "LogGameplayEntitySpawn", true, "Log first observed spawn/registration of each tracked gameplay entity."); LogGameplayEntityDamage = cfg.Bind("NetworkGameplayProbe", "LogGameplayEntityDamage", false, "Log individual damage events for tracked gameplay entities. Very noisy; keep false for first tests."); LogGameplayEntityDeath = cfg.Bind("NetworkGameplayProbe", "LogGameplayEntityDeath", true, "Log death events for tracked gameplay entities."); RequireStableSceneAndSeedForGameplayProbe = cfg.Bind("NetworkGameplayProbe", "RequireStableSceneAndSeedForGameplayProbe", true, "Delay per-entity gameplay probe logs until local chapter/level and levelSeed are known. Events are observed locally only and late-logged when context becomes available."); EnableHostEnemyDeathEventMirror = new Fixed(value: true); LogReceivedEnemyDeathEvents = cfg.Bind("NetworkGameplaySyncExperimental", "LogReceivedEnemyDeathEvents", true, "Log Host enemy death events received by a Client and the local entity match result."); ApplyReceivedEnemyDeathEvents = new Fixed(value: true); EnemyDeathMirrorPositionTolerance = new Fixed(2.5f); EnemyDeathMirrorUseHorizontalPositionTolerance = new Fixed(value: true); EnableClientEnemyDeathClaim = new Fixed(value: true); LogReceivedClientEnemyDeathClaims = cfg.Bind("NetworkGameplaySyncExperimental", "LogReceivedClientEnemyDeathClaims", true, "Log Client enemy death claims received by the Host and the Host-side local entity match/apply result."); ApplyReceivedClientEnemyDeathClaimsOnHost = new Fixed(value: true); EnableCoopPlayerDownedRevive = new Fixed(value: true); LogPlayerLifeSync = cfg.Bind("NetworkPlayerLifeExperimental", "LogPlayerLifeSync", true, "Log player downed/revive/native-death lifecycle packets and local decisions."); PlayerDownedRescueTimeoutSeconds = new Fixed(0f); PlayerReviveHoldSeconds = new Fixed(5f); PlayerReviveDistance = new Fixed(2.5f); PlayerReviveHealthRatio = new Fixed(0.35f); PlayerReviveInvulnerabilitySeconds = new Fixed(2f); PlayerDownedHealthFloor = new Fixed(1f); PlayerReviveHoldKey = cfg.Bind("NetworkPlayerLifeExperimental", "PlayerReviveHoldKey", new KeyboardShortcut((KeyCode)101, Array.Empty()), "Temporary revive key used by Phase 4.3.0-A. Default E matches normal interact/pickup on many setups; can be changed if your binding differs."); RequireReviveDistanceValidationOnHost = new Fixed(value: true); EnableHostEnemyStateSnapshotMirror = new Fixed(value: true); EnemyStateSnapshotSendRateHz = new Fixed(6f); EnemyStateSnapshotMaxEnemiesPerPacket = new Fixed(4); OnlySendAliveEnemyStateSnapshots = new Fixed(value: true); LogReceivedEnemyStateSnapshots = cfg.Bind("NetworkEnemyStateExperimental", "LogReceivedEnemyStateSnapshots", false, "Log Client-side matching and position-drift summaries for received Host enemy state snapshot batches. Default false to avoid log overhead during active testing."); ApplyReceivedEnemyStateSnapshots = new Fixed(value: true); EnemyStateSnapshotPositionTolerance = new Fixed(5f); EnemyStateSnapshotInterpolationSpeed = new Fixed(18f); EnemyStateSnapshotPlaybackDurationMultiplier = new Fixed(1.1f); EnemyStateSnapshotSnapDistance = new Fixed(10f); EnemyStateSnapshotApplyRotationY = new Fixed(value: true); EnableClientEnemyAiSuppressionExperiment = new Fixed(value: false); SuppressClientEnemyAiWhenStateMirrorEnabled = new Fixed(value: false); EnableClientEnemyPuppetMode = new Fixed(value: true); LogClientEnemyPuppetMode = cfg.Bind("NetworkEnemyStateExperimental", "LogClientEnemyPuppetMode", true, "Log one-line begin/end events when a Client NPC enters or leaves Host-mirrored puppet mode."); LogClientFrameHitch = cfg.Bind("NetworkEnemyStateExperimental", "LogClientFrameHitch", true, "Diagnostic: on a slow client frame during combat, log the frame time + active enemy-puppet / craw count, to attribute the Terrorbaum craw hitch. Only logs frames under ~20 fps (silent during smooth play)."); ClientEnemyPuppetStaleReleaseSeconds = new Fixed(3f); EnableHostEnemyAnimationMirror = new Fixed(value: true); ApplyReceivedEnemyAnimationMirror = new Fixed(value: true); LogEnemyAnimationMirror = cfg.Bind("NetworkEnemyStateExperimental", "LogEnemyAnimationMirror", false, "Log throttled Host/Client enemy animation mirror state changes. Keep false unless diagnosing sliding or missing attack animations."); EnemyAnimationMirrorCrossFadeSeconds = new Fixed(0.06f); EnemyAnimationMirrorNormalizedTimeTolerance = new Fixed(0.3f); EnemyAnimationMirrorApplyAnimatorStatePlayback = new Fixed(value: false); EnemyAnimationMirrorApplyHostCombatStatePlayback = new Fixed(value: true); EnemyAnimationMirrorReplayHostCombatMethods = new Fixed(value: true); EnemyAnimationMirrorApplyCombatAnimatorFallback = new Fixed(value: false); EnemyAnimationMirrorHostCombatActionHoldSeconds = new Fixed(0.8f); EnemyProjectileVisualMirrorEnabled = new Fixed(value: false); EnemyProjectileVisualMirrorUseNativeShootReplay = new Fixed(value: false); EnemyProjectileVisualMirrorSpeed = new Fixed(26f); EnemyProjectileVisualMirrorLifetime = new Fixed(2f); EnableGenericHostCombatAnimatorStateMirror = new Fixed(value: true); EnableHostAuthoritativeEnemyRangedDamage = new Fixed(value: false); EnableSyntheticRangedDamageFallback = new Fixed(value: false); EnableClientEnemyIntentDrivenMotion = new Fixed(value: false); LogEnemyAiIntentMirror = cfg.Bind("NetworkEnemyIntentExperimental", "LogEnemyAiIntentMirror", true, "Log low-frequency Host AI intent capture and Client intent replay/correction summaries."); EnemyIntentCorrectionDistance = new Fixed(2.5f); EnemyIntentHardSnapDistance = new Fixed(9f); EnemyIntentReplayMinIntervalSeconds = new Fixed(0.18f); EnemyHostProjectileHitRadius = new Fixed(0.75f); EnemyHostProjectileVerticalTolerance = new Fixed(1.5f); EnemyHostProjectileMaxDistance = new Fixed(28f); EnemyHostProjectileDamage = new Fixed(10f); EnemyHostProjectileDamageCooldownSeconds = new Fixed(0.45f); EnemyDamageDefaultType = new Fixed(7); EnableEnemyElementalStatusEffect = new Fixed(value: true); EnemyElementalStatusAmount = new Fixed(25f); LogEnemyHostDamageAuthority = cfg.Bind("NetworkEnemyTargetExperimental", "LogEnemyHostDamageAuthority", false, "Log Host authoritative enemy ranged damage checks and client-side damage applications. Default OFF: this fires once PER HIT, so on a busy fight it is synchronous per-hit disk I/O that stutters the client (confirmed LogOutput108). Turn on only to debug damage routing."); EnableHostOnlyEnemyTargetAuthority = new Fixed(value: true); LogEnemyTargetAuthority = cfg.Bind("NetworkEnemyTargetExperimental", "LogEnemyTargetAuthority", true, "Log low-frequency HostOnly enemy target authority/probe lines for Client puppet enemies."); EnemyTargetAuthorityProbeIntervalSeconds = new Fixed(2f); EnableEnemyCombatProbe = new Fixed(value: true); LogEnemyCombatProbe = cfg.Bind("NetworkEnemyTargetExperimental", "LogEnemyCombatProbe", false, "Default OFF — high volume: gates the per-shot [Npc] TriggerShoot/SetShooting/SetAimTarget lines + [EnemyCombatProbe]. Floods when many enemies are active (e.g. a ranged group infighting). EnableEnemyCombatProbe (functional puppet-combat blocking) stays independent. Enable to debug enemy combat."); EnableHostAuthorizedIntentExecution = new Fixed(value: true); HostAuthorizedIntentWindowSeconds = new Fixed(1f); LogHostAuthorizedIntentExecution = cfg.Bind("NetworkEnemyIntentExperimental", "LogHostAuthorizedIntentExecution", true, "Log when Client puppet combat methods pass through Host-authorized intent windows."); EnableClientEnemyNativeDamageSuppression = new Fixed(value: true); EnableClientPuppetAimOverride = new Fixed(value: true); EnableEnemyStateSnapshotDeltaCompression = new Fixed(value: true); EnemyStateSnapshotHeartbeatSeconds = new Fixed(0.75f); EnemyStateSnapshotPositionDeltaThreshold = new Fixed(0.04f); EnemyStateSnapshotRotationDeltaThresholdDegrees = new Fixed(3f); EnemyStateSnapshotAnimationTimeDeltaThreshold = new Fixed(0.1f); EnableHostEnemyDamageEventSync = new Fixed(value: true); EnableHostEnemyHealthStateSync = new Fixed(value: true); ApplyReceivedHostEnemyHealthState = new Fixed(value: true); LogHostEnemyDamageEvents = cfg.Bind("HostDrivenProxy", "LogHostEnemyDamageEvents", true, "Log HostEnemyDamageEvent send (Host) and receive/apply (Client) for debugging."); LogHostEnemyHealthState = cfg.Bind("HostDrivenProxy", "LogHostEnemyHealthState", false, "Log HostEnemyHealthState send and receive. Verbose — disable once stable."); AllowRosterBoundDeathDespitePositionDrift = new Fixed(value: true); HostDeathSnapBeforeApply = new Fixed(value: true); AllowDeathLateRebind = new Fixed(value: true); DisableClientEnemyDeathClaimWhenHostDamageSyncEnabled = new Fixed(value: true); EnableClientHitRequest = new Fixed(value: true); LogClientHitRequests = cfg.Bind("HostDrivenProxy", "LogClientHitRequests", true, "Log ClientHitRequest send (Client) and receive/apply/reject (Host) for debugging."); FilterNonPlayerPuppetDamage = new Fixed(value: true); MakeClientPuppetsKinematic = new Fixed(value: true); StableWorldRosterBinding = new Fixed(value: true); ReleasePuppetOnHostDeath = new Fixed(value: true); EvictStaleHostBindings = new Fixed(value: true); SkipDeadHostIdxRebind = new Fixed(value: true); EnableRetroactiveEnemyBinding = new Fixed(value: true); EnableDestroyedUnitListSweep = new Fixed(value: true); ClientHitRequestMaxRangeMeters = new Fixed(30f); ClientHitRequestRateLimitSeconds = new Fixed(0.08f); EnableClientTerminalDeadLatch = new Fixed(value: true); LogClientTerminalDead = cfg.Bind("HostDrivenProxy", "LogClientTerminalDead", true, "Log when a host enemy is latched terminal-dead and when overrides are blocked because of it."); EnableClientHitFlash = new Fixed(value: true); LogClientHitFlash = cfg.Bind("HostDrivenProxy", "LogClientHitFlash", true, "Log visual hit-flash play/skip events for debugging."); ClientHitFlashDurationSeconds = new Fixed(0.08f); EnableClientPendingDeadState = new Fixed(value: true); EnableClientDeathVisualFallback = new Fixed(value: true); ClientDeathVisualFallbackDelaySeconds = new Fixed(0.25f); LogClientPendingDead = cfg.Bind("HostDrivenProxy", "LogClientPendingDead", true, "Log PendingDead mark / host-death-applied / visual-fallback events."); EnableHostLevelManifest = new Fixed(value: true); LogLevelManifest = cfg.Bind("LevelManifest", "LogLevelManifest", true, "Log manifest build summaries (Host built / Client built) and reconcile completion."); LogLevelManifestDiff = cfg.Bind("LevelManifest", "LogLevelManifestDiff", true, "Log detailed per-room / per-unit / per-special diff lines. Verbose — disable once the divergence cause is understood."); QuarantineClientOnlyManifestEnemies = new Fixed(value: true); EnableClientHitVisual = new Fixed(value: true); EnableLevelGenTrace = cfg.Bind("LevelManifest", "EnableLevelGenTrace", true, "Phase 5.3-F: Hook LevelGeneration nodes (FinalizeConnection / Connector.FinalizeSpawn / AddExtraRoomsNode) to trace the real generation flow and locate the first host/client divergence. Discovery-first: logs real method signatures, never guesses."); LogLevelGenTrace = cfg.Bind("LevelManifest", "LogLevelGenTrace", true, "Log per-connector / per-room LevelGeneration trace lines. Verbose during generation — disable once the divergence cause is understood."); EnableHostDrivenEnemyProxy = new Fixed(value: true); SuppressAllClientPuppetDamage = new Fixed(value: true); LogClientPuppetDamageSuppression = cfg.Bind("HostDrivenProxy", "LogClientPuppetDamageSuppression", true, "Log when client puppet enemy damage is suppressed by the host-driven proxy gate."); EnableHostAttackPhaseEvents = new Fixed(value: true); LogHostAttackPhaseEvents = cfg.Bind("HostDrivenProxy", "LogHostAttackPhaseEvents", false, "Default OFF — high volume (one line per enemy attack action): logs HostAttackPhaseEvent send (Host) / receive (Client). The network broadcast itself is gated by EnableHostAttackPhaseEvents (stays on). Enable to debug attack-animation sync."); EnableClientAttackPhaseAnimatorDrive = new Fixed(value: true); ClientAttackPhaseCrossFadeSeconds = new Fixed(0.05f); EnableHostProjectileVisualSpawnEvent = new Fixed(value: false); IncludeRemotePlayersInInterest = new Fixed(value: true); EnableRemotePlayerTargetProxy = new Fixed(value: false); RemotePlayerTargetProxySetIsPlayer = new Fixed(value: false); RemotePlayerTargetProxyForceAggro = new Fixed(value: true); RemotePlayerTargetProxyAggroRange = new Fixed(30f); RemotePlayerTargetProxyOnlyWhenCloser = new Fixed(value: false); RemotePlayerTargetProxyHitboxLayer = new Fixed(6); RemotePlayerTargetProxyBodyBlocker = new Fixed(value: false); RemoveTargetProxyWhenPeerDowned = new Fixed(value: true); HideDownedLocalPlayerFromEnemies = new Fixed(value: true); BalanceCoopEnemyTargeting = new Fixed(value: true); LogPooledObjectDestroyDiag = cfg.Bind("HostDrivenProxy", "LogPooledObjectDestroyDiag", false, "DIAGNOSTIC (default OFF; culprit found = AutoPool.ResetPools on level switch). Logs a stack trace whenever something destroys a still-pooled AutoPooledObject. Has a perf cost (inspects every Object.Destroy). Only enable to re-investigate pool corruption."); ApplyHostPlayerDamageViaReceiveDamage = new Fixed(value: true); EnableEnemyInterestManagement = new Fixed(value: true); FullRateForEngagedEnemies = new Fixed(value: true); ClientEngagedEnemyFullRateSeconds = new Fixed(8f); ThrottleOnlyWithKnownRemotePositions = new Fixed(value: true); LogEnemyInterestDiag = cfg.Bind("Debug", "LogEnemyInterestDiag", false, "Diagnostic: log per-enemy interest decisions (distHost / distRemoteMin / remoteCount / engaged) and the remote-interest feed (collected positions vs host position). Throttled. Default OFF."); SendAllEnemySnapshotsToClients = new Fixed(value: true); DisablePauseInMultiplayer = new Fixed(value: true); LogPauseSuppression = cfg.Bind("Debug", "LogPauseSuppression", false, "Diagnostic: log when a world-pause (inventory/ESC/F3/dialog/focus) is suppressed in multiplayer. Default OFF."); LogDamageApplyHitch = cfg.Bind("Debug", "LogDamageApplyHitch", false, "Diagnostic: time the client's per-hit host-damage apply (includes the native ReceiveDamage feedback) and log only when it exceeds DamageApplyHitchThresholdMs. Locates the 'hitch on every hit'. Default OFF."); DamageApplyHitchThresholdMs = cfg.Bind("Debug", "DamageApplyHitchThresholdMs", 3f, new ConfigDescription("Threshold (ms) above which a single client damage-apply is logged as a hitch.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 50f), Array.Empty())); EnemyNearCombatDistance = new Fixed(20f); EnemyFarDistance = new Fixed(40f); EnemyFarSnapshotHz = new Fixed(2f); EnableCombatEventCoalescing = new Fixed(value: true); EnemyToClientDamageCoalesceSeconds = new Fixed(0.1f); EnemyDamageEventMinIntervalSeconds = new Fixed(0.07f); AttackPhaseEventMinIntervalSeconds = new Fixed(0.08f); EnableMultiPlayerNpcActivation = new Fixed(value: true); MultiPlayerNpcActivationDistance = new Fixed(60f); MultiPlayerNpcActivationsPerFrame = new Fixed(8); EnableRemotePlayerInPlayersList = new Fixed(value: true); EnableGhostPlayerHitbox = new Fixed(value: true); LogRemotePlayerRegistry = cfg.Bind("PlayerRegistry", "LogRemotePlayerRegistry", true, "Plan B: verbose log for the headless Player registry + activation pass (create/update/destroy/register/activate)."); SuppressGhostsWhileLoading = new Fixed(value: true); CoopSettingsStore.PruneRetiredCfgKeys(cfg); } } public sealed class CoopSettingsStore { [Serializable] public sealed class Data { public string playerName = "Player"; public string hostAddress = "127.0.0.1"; public int hostPort = 9050; public string connectionKey = "SULFUR_TOGETHER_DEV"; public int maxPlayers = 4; public bool requireSameModVersion = true; public bool enableCoopToasts = true; public string lastSteamIdToJoin = ""; public bool friendlyFire; } private static readonly string[] RetiredCfgKeys = new string[233] { "EnableNetworking", "NetworkMode", "PlayerName", "HostAddress", "HostPort", "ConnectionKey", "MaxPlayers", "RequireSameModVersion", "EnableCoopToasts", "EnableBreakableSync", "EnableGateSync", "EnableTriggerDoorSync", "EnableRunStateNegotiation", "RunStateBroadcastIntervalSeconds", "EnableLevelSeedAuthority", "RequireSameLevelSeedForSceneMatch", "ApplyHostLevelSeedOnManualFollow", "HideRemoteVisualWhenLevelSeedMismatch", "SyncHostUsedSetsOnManualFollow", "EnableRemotePlayerVisualProxy", "RemotePlayerTransformSendRateHz", "RemotePlayerVisualTimeoutSeconds", "RemotePlayerVisualInterpolationSpeed", "RemotePlayerVisualSnapDistance", "EnableRemotePlayerProxyCollision", "RemotePlayerCollisionSoft", "RemotePlayerSoftCollisionRadius", "RemotePlayerSoftCollisionPushSpeed", "EnableRuntimeSpawnSync", "EnableRuntimeSpawnSnapOnBind", "EnableRuntimeSpawnInertUntilBound", "EnableDeathSpawnSync", "EnableMinionSpawnSync", "EnableHostEnemyDeathEventMirror", "ApplyReceivedEnemyDeathEvents", "EnemyDeathMirrorPositionTolerance", "EnemyDeathMirrorUseHorizontalPositionTolerance", "EnableClientEnemyDeathClaim", "ApplyReceivedClientEnemyDeathClaimsOnHost", "EnableCoopPlayerDownedRevive", "PlayerDownedRescueTimeoutSeconds", "PlayerReviveHoldSeconds", "PlayerReviveDistance", "PlayerReviveHealthRatio", "PlayerReviveInvulnerabilitySeconds", "PlayerDownedHealthFloor", "RequireReviveDistanceValidationOnHost", "EnablePlayerWeaponSync", "PlayerWeaponSyncMaxProjectilesPerShot", "EnableRemoteWeaponModel", "EnableRemotePlayerSpriteBody", "EnableRemotePlayerNpcBody", "RemotePlayerBodyUnitKeyword", "RemoteBodyScale", "RemoteBodyFeetYOffset", "RemoteWeaponScale", "RemoteWeaponHipHeight", "RemoteWeaponForward", "RemoteWeaponRight", "RemoteBodyPitchLimit", "RemoteBodyDepthBias", "RemoteNameSize", "RemoteNameHeight", "EnableClientEnemyIntentDrivenMotion", "EnemyIntentCorrectionDistance", "EnemyIntentHardSnapDistance", "EnemyIntentReplayMinIntervalSeconds", "EnableHostAuthorizedIntentExecution", "HostAuthorizedIntentWindowSeconds", "EnableClientEnemyNativeDamageSuppression", "EnableClientPuppetAimOverride", "EnableHostEnemyStateSnapshotMirror", "EnemyStateSnapshotSendRateHz", "EnemyStateSnapshotMaxEnemiesPerPacket", "OnlySendAliveEnemyStateSnapshots", "ApplyReceivedEnemyStateSnapshots", "EnemyStateSnapshotPositionTolerance", "EnemyStateSnapshotInterpolationSpeed", "EnemyStateSnapshotPlaybackDurationMultiplier", "EnemyStateSnapshotSnapDistance", "EnemyStateSnapshotApplyRotationY", "EnableClientEnemyAiSuppressionExperiment", "SuppressClientEnemyAiWhenStateMirrorEnabled", "EnableClientEnemyPuppetMode", "ClientEnemyPuppetStaleReleaseSeconds", "EnableHostEnemyAnimationMirror", "ApplyReceivedEnemyAnimationMirror", "EnemyAnimationMirrorCrossFadeSeconds", "EnemyAnimationMirrorNormalizedTimeTolerance", "EnemyAnimationMirrorApplyAnimatorStatePlayback", "EnemyAnimationMirrorApplyHostCombatStatePlayback", "EnemyAnimationMirrorReplayHostCombatMethods", "EnemyAnimationMirrorApplyCombatAnimatorFallback", "EnemyAnimationMirrorHostCombatActionHoldSeconds", "EnableEnemyStateSnapshotDeltaCompression", "EnemyStateSnapshotHeartbeatSeconds", "EnemyStateSnapshotPositionDeltaThreshold", "EnemyStateSnapshotRotationDeltaThresholdDegrees", "EnemyStateSnapshotAnimationTimeDeltaThreshold", "EnemyProjectileVisualMirrorEnabled", "EnemyProjectileVisualMirrorUseNativeShootReplay", "EnemyProjectileVisualMirrorSpeed", "EnemyProjectileVisualMirrorLifetime", "EnableGenericHostCombatAnimatorStateMirror", "EnableHostAuthoritativeEnemyRangedDamage", "EnableSyntheticRangedDamageFallback", "EnemyHostProjectileHitRadius", "EnemyHostProjectileVerticalTolerance", "EnemyHostProjectileMaxDistance", "EnemyHostProjectileDamage", "EnemyHostProjectileDamageCooldownSeconds", "EnemyDamageDefaultType", "EnableEnemyElementalStatusEffect", "EnemyElementalStatusAmount", "EnableHostOnlyEnemyTargetAuthority", "EnemyTargetAuthorityProbeIntervalSeconds", "EnableEnemyCombatProbe", "EnableHostEnemyDamageEventSync", "EnableHostEnemyHealthStateSync", "ApplyReceivedHostEnemyHealthState", "AllowRosterBoundDeathDespitePositionDrift", "HostDeathSnapBeforeApply", "AllowDeathLateRebind", "DisableClientEnemyDeathClaimWhenHostDamageSyncEnabled", "EnableClientHitRequest", "FilterNonPlayerPuppetDamage", "MakeClientPuppetsKinematic", "StableWorldRosterBinding", "ReleasePuppetOnHostDeath", "EvictStaleHostBindings", "SkipDeadHostIdxRebind", "EnableRetroactiveEnemyBinding", "EnableDestroyedUnitListSweep", "ClientHitRequestMaxRangeMeters", "ClientHitRequestRateLimitSeconds", "EnableClientTerminalDeadLatch", "EnableClientHitFlash", "ClientHitFlashDurationSeconds", "EnableClientPendingDeadState", "EnableClientDeathVisualFallback", "ClientDeathVisualFallbackDelaySeconds", "EnableClientHitVisual", "EnableHostDrivenEnemyProxy", "SuppressAllClientPuppetDamage", "EnableHostAttackPhaseEvents", "EnableClientAttackPhaseAnimatorDrive", "ClientAttackPhaseCrossFadeSeconds", "EnableHostProjectileVisualSpawnEvent", "IncludeRemotePlayersInInterest", "EnableRemotePlayerTargetProxy", "RemotePlayerTargetProxySetIsPlayer", "RemotePlayerTargetProxyForceAggro", "RemotePlayerTargetProxyAggroRange", "RemotePlayerTargetProxyOnlyWhenCloser", "RemotePlayerTargetProxyHitboxLayer", "RemotePlayerTargetProxyBodyBlocker", "RemoveTargetProxyWhenPeerDowned", "HideDownedLocalPlayerFromEnemies", "ApplyHostPlayerDamageViaReceiveDamage", "EnableEnemyInterestManagement", "FullRateForEngagedEnemies", "ClientEngagedEnemyFullRateSeconds", "ThrottleOnlyWithKnownRemotePositions", "SendAllEnemySnapshotsToClients", "DisablePauseInMultiplayer", "EnemyNearCombatDistance", "EnemyFarDistance", "EnemyFarSnapshotHz", "EnableCombatEventCoalescing", "EnemyToClientDamageCoalesceSeconds", "EnemyDamageEventMinIntervalSeconds", "AttackPhaseEventMinIntervalSeconds", "EnableHostSceneAuthority", "EnableHostSceneRequestProtocol", "AutoSendHostSceneRequestOnDrift", "HostSceneRequestIntervalSeconds", "EnableManualClientSceneFollow", "ManualClientSceneFollowRequiresHostRequest", "ClientWaitHostGenerationInputBeforeFirstLoad", "ClientLoadGateTimeoutSeconds", "ClientLoadGateAllowFallbackAfterTimeout", "ClientLoadGateRequestIntervalSeconds", "ClientGateDeathRespawnUntilHostHub", "ClientGateDeathRespawnTimeoutSeconds", "EnableClientTransitionRelay", "AllowClientInitiatedLevelLoad", "ClientInitiatedLoadTimeoutSeconds", "EnableClientReloadInPlaceRelay", "EnableAutoFollowHostSceneRequest", "EnableLoadBarrier", "LoadBarrierTimeoutSeconds", "LoadBarrierBlockHostAdvance", "LoadBarrierLogOnlyMode", "EnableHostLevelManifest", "QuarantineClientOnlyManifestEnemies", "EnableWorldItemDropSync", "EnableMultiPlayerNpcActivation", "MultiPlayerNpcActivationDistance", "MultiPlayerNpcActivationsPerFrame", "EnableRemotePlayerInPlayersList", "EnableGhostPlayerHitbox", "SuppressGhostsWhileLoading", "EnableBossEncounterSync", "BossEncounterClientBlockLocalStart", "BossContinuationGraceSeconds", "EnableBossLifecycleProbe", "RemoveBossDialogInteractableOnStart", "EnableFaithfulBossIntro", "GateBossFightOnDialogClose", "DeferBossIntroArm", "EnableBossRoomMembership", "GateBossDialogToInRoom", "ExcludeOutOfRoomPlayersFromBossAttacks", "EnableBossDynamicSpawnManifest", "EnableCousinArmSync", "EnableBossDamageAuthority", "EnableBossClientPresentation", "EnableBossDiscreteEventAuthority", "EnableLuciaEyeAuthority", "EnableLuciaDeathAuthority", "EnableWitchPhaseDamageAuthority", "EnableWitchPhaseAuthority", "EnableWitchPhase2Manifest", "EnableWitchDeathFix", "EnableArenaLockdown", "EnableArenaGracePeriod", "LogAiTargetingReverseDump", "LogSuppressedClientEnemyAi", "LogHostProjectileVisualSpawn", "LogRemotePlayerTargetProxy", "EnableDamageProbe", "EnableEmperorClientWormSuppression", "HostLinkedByDefault" }; private readonly string _jsonPath; public Data Values { get; private set; } = new Data(); public CoopSettingsStore(ConfigFile cfg) { _jsonPath = DeriveJsonPath(cfg.ConfigFilePath); if (!TryLoadJson()) { MigrateFromCfg(cfg); Save(); } } private static string DeriveJsonPath(string cfgFilePath) { return Path.Combine(Path.GetDirectoryName(cfgFilePath) ?? ".", "com.ryuka.sulfur.together.coop.json"); } private bool TryLoadJson() { try { if (!File.Exists(_jsonPath)) { return false; } Data data = JsonUtility.FromJson(File.ReadAllText(_jsonPath)); if (data != null) { Values = data; } return true; } catch (Exception ex) { Plugin.Log?.Warn("[CoopSettings] load failed (" + ex.GetType().Name + "), using defaults: " + ex.Message); return false; } } public void Save() { try { File.WriteAllText(_jsonPath, JsonUtility.ToJson((object)Values, true)); } catch (Exception ex) { Plugin.Log?.Warn("[CoopSettings] save failed: " + ex.Message); } } private void MigrateFromCfg(ConfigFile cfg) { IDictionary orphans = GetOrphans(cfg); if (orphans != null) { if (TryGetOrphan(orphans, "PlayerName", out string value)) { Values.playerName = value; } if (TryGetOrphan(orphans, "HostAddress", out value)) { Values.hostAddress = value; } if (TryGetOrphan(orphans, "HostPort", out value) && int.TryParse(value, out var result)) { Values.hostPort = result; } if (TryGetOrphan(orphans, "ConnectionKey", out value)) { Values.connectionKey = value; } if (TryGetOrphan(orphans, "MaxPlayers", out value) && int.TryParse(value, out var result2)) { Values.maxPlayers = Mathf.Clamp(result2, 2, 4); } if (TryGetOrphan(orphans, "RequireSameModVersion", out value) && bool.TryParse(value, out var result3)) { Values.requireSameModVersion = result3; } if (TryGetOrphan(orphans, "EnableCoopToasts", out value) && bool.TryParse(value, out var result4)) { Values.enableCoopToasts = result4; } } } public static void PruneRetiredCfgKeys(ConfigFile cfg) { try { IDictionary orphans = GetOrphans(cfg); if (orphans == null) { return; } List list = new List(); foreach (DictionaryEntry item in orphans) { if (Array.IndexOf(RetiredCfgKeys, KeyOf(item.Key)) >= 0) { list.Add(item.Key); } } if (list.Count == 0) { return; } foreach (object item2 in list) { orphans.Remove(item2); } cfg.Save(); Plugin.Log?.Info(string.Format("[CoopSettings] pruned {0} retired co-op key(s) from the .cfg (moved to {1}.coop.json).", list.Count, "com.ryuka.sulfur.together")); } catch (Exception ex) { Plugin.Log?.Warn("[CoopSettings] prune failed: " + ex.Message); } } private static IDictionary GetOrphans(ConfigFile cfg) { return typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(cfg) as IDictionary; } private static bool TryGetOrphan(IDictionary orphans, string key, out string value) { foreach (DictionaryEntry orphan in orphans) { if (string.Equals(KeyOf(orphan.Key), key, StringComparison.Ordinal)) { value = (orphan.Value as string) ?? ""; return !string.IsNullOrEmpty(value); } } value = null; return false; } private static string KeyOf(object configDefinition) { return (configDefinition?.GetType().GetProperty("Key")?.GetValue(configDefinition) as string) ?? ""; } } public readonly struct Fixed { public T Value { get; } public Fixed(T value) { Value = value; } public override string ToString() { T value = Value; return ((value != null) ? value.ToString() : null) ?? string.Empty; } } public sealed class Setting { private readonly Func _get; private readonly Action _set; public T Value { get { return _get(); } set { _set(value); } } public Setting(Func get, Action set) { _get = get; _set = set; } public override string ToString() { T value = Value; return ((value != null) ? value.ToString() : null) ?? string.Empty; } } }