using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using MenuLib; using MenuLib.MonoBehaviors; using MenuLib.Structs; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Codex")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("4.1.1.0")] [assembly: AssemblyInformationalVersion("4.1.1")] [assembly: AssemblyProduct("RoleShuffle")] [assembly: AssemblyTitle("RoleShuffle")] [assembly: AssemblyVersion("4.1.1.0")] [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 REPOJP.StageRoles { [BepInPlugin("REPOJP.RoleShuffle", "RoleShuffle", "4.1.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class StageRolesPlugin : BaseUnityPlugin { public const string PluginGuid = "REPOJP.RoleShuffle"; public const string PluginName = "RoleShuffle"; public const string PluginVersion = "4.1.1"; public const string StageFluxGuid = "REPOJP.StagePhysicsEvents"; public const string RepoConfigGuid = "nickklmao.repoconfig"; public const string MenuLibGuid = "nickklmao.menulib"; private Harmony? _harmony; private GameObject? _controllerObject; internal static StageRolesPlugin Instance { get; private set; } internal static ManualLogSource ModLogger { get; private set; } internal StageRolesConfig Settings { get; private set; } internal StageRoleController Controller { get; private set; } private void Awake() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Expected O, but got Unknown Instance = this; ModLogger = ((BaseUnityPlugin)this).Logger; Settings = new StageRolesConfig(((BaseUnityPlugin)this).Config); ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); _controllerObject = new GameObject("RoleShuffle_Controller"); ((Object)_controllerObject).hideFlags = (HideFlags)61; _controllerObject.transform.SetParent(((Component)this).transform, false); Controller = _controllerObject.AddComponent(); Controller.Initialize(Settings); ((Component)this).gameObject.AddComponent().Initialize(Settings); ((Component)this).gameObject.AddComponent().Initialize(); ((Component)this).gameObject.AddComponent().Initialize(Settings, Controller); _harmony = new Harmony("REPOJP.RoleShuffle"); try { _harmony.PatchAll(typeof(LifecyclePatches)); _harmony.PatchAll(typeof(EngineerTrapUpdatePatches)); _harmony.PatchAll(typeof(EngineerEffectActivationPatches)); _harmony.PatchAll(typeof(ShopPatches)); _harmony.PatchAll(typeof(NotificationEnemyReactionPatches)); _harmony.PatchAll(typeof(HunterBatteryPatches)); SceneManager.activeSceneChanged += ActiveSceneChanged; ((BaseUnityPlugin)this).Logger.LogInfo((object)"RoleShuffle 4.1.1 loaded."); } catch (Exception arg) { ((BaseUnityPlugin)this).Logger.LogError((object)$"Failed to apply Harmony patches: {arg}"); _harmony.UnpatchSelf(); ((Behaviour)Controller).enabled = false; } } private void ActiveSceneChanged(Scene previous, Scene current) { Controller?.StageEnding(); } private void OnDestroy() { SceneManager.activeSceneChanged -= ActiveSceneChanged; Controller?.Shutdown(); Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } NotificationEnemyReactionGuard.Clear(); if ((Object)(object)_controllerObject != (Object)null) { Object.Destroy((Object)(object)_controllerObject); } } } internal sealed class StageRolesConfig { internal ConfigEntry Enabled { get; } internal ConfigEntry UniqueRoles { get; } internal ConfigEntry ShopUpgradeItemCount { get; } internal ConfigEntry AnnouncementsEnabled { get; } internal ConfigEntry AnnouncementDelaySeconds { get; } internal ConfigEntry StageFluxAnnouncementDelaySeconds { get; } internal ConfigEntry HudEnabled { get; } internal ConfigEntry HudAnchor { get; } internal ConfigEntry HudAlignment { get; } internal ConfigEntry HudOffsetX { get; } internal ConfigEntry HudOffsetY { get; } internal ConfigEntry HudScalePercent { get; } internal ConfigEntry HudPlayersPerPage { get; } internal ConfigEntry HudPageIntervalSeconds { get; } internal ConfigEntry HudTransitionDurationSeconds { get; } internal ConfigEntry HudPinLocalPlayer { get; } internal ConfigEntry SetRoleCommandEnabled { get; } internal ConfigEntry BaseHealthLevels { get; } internal ConfigEntry BaseStaminaLevels { get; } internal ConfigEntry BaseExtraJumpLevels { get; } internal ConfigEntry BaseSpeedLevels { get; } internal ConfigEntry BaseStrengthLevels { get; } internal ConfigEntry BaseRangeLevels { get; } internal ConfigEntry BaseLaunchLevels { get; } internal ConfigEntry BaseTumbleClimbLevels { get; } internal ConfigEntry BaseTumbleWingsLevels { get; } internal ConfigEntry BaseCrouchRestLevels { get; } internal ConfigEntry BaseMapPlayerCountLevels { get; } internal ConfigEntry BaseDeathHeadBatteryLevels { get; } internal ConfigEntry TankEnabled { get; } internal ConfigEntry TankWeight { get; } internal ConfigEntry TankHealthLevels { get; } internal ConfigEntry RunnerEnabled { get; } internal ConfigEntry RunnerWeight { get; } internal ConfigEntry RunnerSpeedLevels { get; } internal ConfigEntry RunnerStaminaLevels { get; } internal ConfigEntry JumperEnabled { get; } internal ConfigEntry JumperWeight { get; } internal ConfigEntry JumperExtraJumpLevels { get; } internal ConfigEntry LifterEnabled { get; } internal ConfigEntry LifterWeight { get; } internal ConfigEntry LifterStrengthLevels { get; } internal ConfigEntry LauncherEnabled { get; } internal ConfigEntry LauncherWeight { get; } internal ConfigEntry LauncherLaunchLevels { get; } internal ConfigEntry ClimberEnabled { get; } internal ConfigEntry ClimberWeight { get; } internal ConfigEntry ClimberClimbLevels { get; } internal ConfigEntry ClimberRangeLevels { get; } internal ConfigEntry FlyerEnabled { get; } internal ConfigEntry FlyerWeight { get; } internal ConfigEntry FlyerWingsLevels { get; } internal ConfigEntry TrackerEnabled { get; } internal ConfigEntry TrackerWeight { get; } internal ConfigEntry GhostEnabled { get; } internal ConfigEntry GhostWeight { get; } internal ConfigEntry GhostDeathHeadBatteryLevels { get; } internal ConfigEntry BomberEnabled { get; } internal ConfigEntry BomberWeight { get; } internal ConfigEntry BomberDistance { get; } internal ConfigEntry BomberMaximumActiveGrenades { get; } internal ConfigEntry BomberAllowTruckSpawns { get; } internal ConfigEntry BomberExplosiveEnabled { get; } internal ConfigEntry BomberStunEnabled { get; } internal ConfigEntry BomberShockwaveEnabled { get; } internal ConfigEntry BomberDuctTapedEnabled { get; } internal ConfigEntry MedicEnabled { get; } internal ConfigEntry MedicWeight { get; } internal ConfigEntry MedicHealAmount { get; } internal ConfigEntry MedicHealIntervalSeconds { get; } internal ConfigEntry MedicHealRadius { get; } internal ConfigEntry MedicTotalHealingLimit { get; } internal ConfigEntry PhoenixEnabled { get; } internal ConfigEntry PhoenixWeight { get; } internal ConfigEntry PhoenixRevivalHealth { get; } internal ConfigEntry PhoenixReviveDelaySeconds { get; } internal ConfigEntry PhoenixFailureGraceSeconds { get; } internal ConfigEntry JoblessEnabled { get; } internal ConfigEntry JoblessWeight { get; } internal ConfigEntry JoblessDamage { get; } internal ConfigEntry JoblessDamageIntervalSeconds { get; } internal ConfigEntry RescuerEnabled { get; } internal ConfigEntry RescuerWeight { get; } internal ConfigEntry RescuerRevivalHealth { get; } internal ConfigEntry RescuerReviveDelaySeconds { get; } internal ConfigEntry RescuerRadius { get; } internal ConfigEntry RescuerMaximumRevives { get; } internal ConfigEntry VampireEnabled { get; } internal ConfigEntry VampireWeight { get; } internal ConfigEntry VampireTier1HealAmount { get; } internal ConfigEntry VampireTier2HealAmount { get; } internal ConfigEntry VampireTier3HealAmount { get; } internal ConfigEntry VampireRadius { get; } internal ConfigEntry KingEnabled { get; } internal ConfigEntry KingWeight { get; } internal ConfigEntry TunaEnabled { get; } internal ConfigEntry TunaWeight { get; } internal ConfigEntry TunaStationaryDelaySeconds { get; } internal ConfigEntry TunaDamage { get; } internal ConfigEntry TunaDamageIntervalSeconds { get; } internal ConfigEntry MusicianEnabled { get; } internal ConfigEntry MusicianWeight { get; } internal ConfigEntry MusicianHealAmount { get; } internal ConfigEntry MusicianHealRadius { get; } internal ConfigEntry MageEnabled { get; } internal ConfigEntry MageWeight { get; } internal ConfigEntry MageCastIntervalSeconds { get; } internal ConfigEntry MageStarHealthCost { get; } internal ConfigEntry MageGravityHealthCost { get; } internal ConfigEntry MageRollHealthCost { get; } internal ConfigEntry MageVoidHealthCost { get; } internal ConfigEntry MageLaserHealthCost { get; } internal ConfigEntry GamblerEnabled { get; } internal ConfigEntry GamblerWeight { get; } internal ConfigEntry GamblerWinChancePercent { get; } internal ConfigEntry GamblerWinValueMultiplier { get; } internal ConfigEntry HunterEnabled { get; } internal ConfigEntry HunterWeight { get; } internal ConfigEntry HunterBatteryConsumptionPercent { get; } internal ConfigEntry HunterDoubleOrbChancePercent { get; } internal ConfigEntry HunterJackpotOrbChancePercent { get; } internal ConfigEntry HunterJackpotOrbCount { get; } internal ConfigEntry StinkerEnabled { get; } internal ConfigEntry StinkerWeight { get; } internal ConfigEntry StinkerDistance { get; } internal ConfigEntry StinkerSafetyDistance { get; } internal ConfigEntry StinkerAllowTruckSpawns { get; } internal ConfigEntry EngineerEnabled { get; } internal ConfigEntry EngineerWeight { get; } internal float ClampedBomberDistance => Mathf.Clamp(BomberDistance.Value, 1f, 100f); internal int ClampedBomberMaximum => Mathf.Clamp(BomberMaximumActiveGrenades.Value, 1, 30); internal float ClampedHunterBatteryConsumption => (float)Mathf.Clamp(HunterBatteryConsumptionPercent.Value, 0, 100) / 100f; internal float ClampedStinkerDistance => Mathf.Clamp(StinkerDistance.Value, 1f, 100f); internal float ClampedStinkerSafetyDistance => Mathf.Clamp(StinkerSafetyDistance.Value, 1f, 30f); internal float ClampedJoblessInterval => Mathf.Clamp(JoblessDamageIntervalSeconds.Value, 0.05f, 10f); internal float ClampedTunaDamageInterval => Mathf.Clamp(TunaDamageIntervalSeconds.Value, 0.05f, 10f); internal StageRolesConfig(ConfigFile config) { //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Expected O, but got Unknown //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Expected O, but got Unknown //IL_0c73: Unknown result type (might be due to invalid IL or missing references) //IL_0c7d: Expected O, but got Unknown //IL_0c89: Unknown result type (might be due to invalid IL or missing references) //IL_0c93: Expected O, but got Unknown //IL_0c9f: Unknown result type (might be due to invalid IL or missing references) //IL_0ca9: Expected O, but got Unknown Enabled = BindBool(config, "General", "Enabled", defaultValue: true, "Enables stage role assignment."); UniqueRoles = BindBool(config, "General", "UniqueRoles", defaultValue: true, "Avoids duplicate roles until every enabled role has been assigned once."); ShopUpgradeItemCount = BindInt(config, "General", "ShopUpgradeItemCount", 0, 0, 30, "Number of upgrade items requested for each shop."); AnnouncementsEnabled = BindBool(config, "Notifications", "Enabled", defaultValue: true, "Enables role-name and role-effect chat and TTS notifications."); AnnouncementDelaySeconds = BindFloat(config, "Notifications", "AnnouncementDelaySeconds", 2.5f, 0f, 30f, "Delay before role announcements."); StageFluxAnnouncementDelaySeconds = BindFloat(config, "Notifications", "StageFluxAnnouncementDelaySeconds", 5f, 0f, 30f, "Delay used when Stage Flux is installed."); HudEnabled = BindBool(config, "HUD", "Enabled", defaultValue: true, "Shows every player's assigned role during a stage."); HudAnchor = config.Bind("HUD", "Anchor", "BottomLeft", new ConfigDescription("Anchor point used to position the role HUD.", (AcceptableValueBase)(object)new AcceptableValueList(new string[9] { "TopLeft", "TopCenter", "TopRight", "MiddleLeft", "MiddleCenter", "MiddleRight", "BottomLeft", "BottomCenter", "BottomRight" }), Array.Empty())); HudAlignment = config.Bind("HUD", "Alignment", "Left", new ConfigDescription("Horizontal alignment of the role HUD text.", (AcceptableValueBase)(object)new AcceptableValueList(new string[3] { "Left", "Center", "Right" }), Array.Empty())); HudOffsetX = BindInt(config, "HUD", "OffsetX", 0, -3840, 3840, "Horizontal offset from the anchor in pixels."); HudOffsetY = BindInt(config, "HUD", "OffsetY", 0, -2160, 2160, "Vertical offset from the anchor in pixels."); HudScalePercent = BindInt(config, "HUD", "ScalePercent", 70, 50, 200, "Role HUD scale percentage."); HudPlayersPerPage = BindInt(config, "HUD", "PlayersPerPage", 8, 2, 20, "Maximum players shown at once, including the pinned local player."); HudPageIntervalSeconds = BindFloat(config, "HUD", "PageIntervalSeconds", 5f, 1f, 30f, "Seconds each role page remains visible."); HudTransitionDurationSeconds = BindFloat(config, "HUD", "TransitionDurationSeconds", 0.2f, 0f, 1f, "Duration of the fade between role pages."); HudPinLocalPlayer = BindBool(config, "HUD", "PinLocalPlayer", defaultValue: true, "Keeps the local player's role visible on every page."); SetRoleCommandEnabled = BindBool(config, "Testing", "SetRoleCommandEnabled", defaultValue: true, "Enables the /setrole, /sr, /hudplayers, and /hp testing commands."); BaseHealthLevels = BaseUpgradeLevel(config, "HealthUpgradeLevels", 1); BaseStaminaLevels = BaseUpgradeLevel(config, "StaminaUpgradeLevels", 0); BaseExtraJumpLevels = BaseUpgradeLevel(config, "ExtraJumpUpgradeLevels", 0); BaseSpeedLevels = BaseUpgradeLevel(config, "SpeedUpgradeLevels", 0); BaseStrengthLevels = BaseUpgradeLevel(config, "StrengthUpgradeLevels", 0); BaseRangeLevels = BaseUpgradeLevel(config, "RangeUpgradeLevels", 0); BaseLaunchLevels = BaseUpgradeLevel(config, "LaunchUpgradeLevels", 0); BaseTumbleClimbLevels = BaseUpgradeLevel(config, "TumbleClimbUpgradeLevels", 0); BaseTumbleWingsLevels = BaseUpgradeLevel(config, "TumbleWingsUpgradeLevels", 0); BaseCrouchRestLevels = BaseUpgradeLevel(config, "CrouchRestUpgradeLevels", 0); BaseMapPlayerCountLevels = BindInt(config, "Base Upgrades", "MapPlayerCountUpgradeLevels", 0, 0, 1, "Base vanilla Map Player Count level kept when no role overrides it."); BaseDeathHeadBatteryLevels = BaseUpgradeLevel(config, "DeathHeadBatteryUpgradeLevels", 0); TankEnabled = RoleEnabled(config, "Tank"); TankWeight = RoleWeight(config, "Tank"); TankHealthLevels = UpgradeLevel(config, "Tank", "HealthUpgradeLevels", 21); RunnerEnabled = RoleEnabled(config, "Runner"); RunnerWeight = RoleWeight(config, "Runner"); RunnerSpeedLevels = UpgradeLevel(config, "Runner", "SpeedUpgradeLevels", 6); RunnerStaminaLevels = UpgradeLevel(config, "Runner", "StaminaUpgradeLevels", 46); JumperEnabled = RoleEnabled(config, "Jumper"); JumperWeight = RoleWeight(config, "Jumper"); JumperExtraJumpLevels = UpgradeLevel(config, "Jumper", "ExtraJumpUpgradeLevels", 10); LifterEnabled = RoleEnabled(config, "Lifter"); LifterWeight = RoleWeight(config, "Lifter"); LifterStrengthLevels = UpgradeLevel(config, "Lifter", "StrengthUpgradeLevels", 25); LauncherEnabled = RoleEnabled(config, "Launcher"); LauncherWeight = RoleWeight(config, "Launcher"); LauncherLaunchLevels = UpgradeLevel(config, "Launcher", "LaunchUpgradeLevels", 10); ClimberEnabled = RoleEnabled(config, "Climber"); ClimberWeight = RoleWeight(config, "Climber"); ClimberClimbLevels = UpgradeLevel(config, "Climber", "ClimbUpgradeLevels", 50); ClimberRangeLevels = UpgradeLevel(config, "Climber", "RangeUpgradeLevels", 20); FlyerEnabled = RoleEnabled(config, "Flyer"); FlyerWeight = RoleWeight(config, "Flyer"); FlyerWingsLevels = UpgradeLevel(config, "Flyer", "WingsUpgradeLevels", 10); TrackerEnabled = RoleEnabled(config, "Tracker"); TrackerWeight = RoleWeight(config, "Tracker"); GhostEnabled = RoleEnabled(config, "Ghost"); GhostWeight = RoleWeight(config, "Ghost"); GhostDeathHeadBatteryLevels = UpgradeLevel(config, "Ghost", "DeathHeadBatteryUpgradeLevels", 50); BomberEnabled = RoleEnabled(config, "Bomber"); BomberWeight = RoleWeight(config, "Bomber"); BomberDistance = BindFloat(config, "Bomber", "DistancePerGrenade", 8f, 1f, 100f, "Travel distance required per grenade."); BomberMaximumActiveGrenades = BindInt(config, "Bomber", "MaximumActiveGrenades", 30, 1, 30, "Maximum tracked grenades per player."); BomberAllowTruckSpawns = BindBool(config, "Bomber", "AllowTruckSpawns", defaultValue: false, "Allows grenade placement inside the truck."); BomberExplosiveEnabled = BindBool(config, "Bomber", "ExplosiveGrenadesEnabled", defaultValue: true, "Includes the vanilla explosive grenade."); BomberStunEnabled = BindBool(config, "Bomber", "StunGrenadesEnabled", defaultValue: true, "Includes the vanilla stun grenade."); BomberShockwaveEnabled = BindBool(config, "Bomber", "ShockwaveGrenadesEnabled", defaultValue: true, "Includes the vanilla shockwave grenade."); BomberDuctTapedEnabled = BindBool(config, "Bomber", "DuctTapedGrenadesEnabled", defaultValue: true, "Includes the vanilla duct-taped grenade."); MedicEnabled = RoleEnabled(config, "Medic"); MedicWeight = RoleWeight(config, "Medic"); MedicHealAmount = BindInt(config, "Medic", "HealAmount", 5, 1, 100, "Health restored to each nearby player per tick."); MedicHealIntervalSeconds = BindFloat(config, "Medic", "HealIntervalSeconds", 2f, 0.1f, 30f, "Seconds between Medic healing ticks."); MedicHealRadius = BindFloat(config, "Medic", "HealRadius", 5f, 1f, 30f, "Maximum distance from the Medic for healing."); MedicTotalHealingLimit = BindInt(config, "Medic", "TotalHealingLimit", 200, 1, 10000, "Maximum total health restored by each Medic per stage."); PhoenixEnabled = RoleEnabled(config, "Phoenix"); PhoenixWeight = RoleWeight(config, "Phoenix"); PhoenixRevivalHealth = BindInt(config, "Phoenix", "RevivalHealth", 25, 1, 1000, "Health restored after Phoenix revival, capped at the player's maximum health."); PhoenixReviveDelaySeconds = BindFloat(config, "Phoenix", "ReviveDelaySeconds", 2f, 2f, 10f, "Delay before revival. Phoenix always waits at least two seconds."); PhoenixFailureGraceSeconds = BindFloat(config, "Phoenix", "FailureGraceSeconds", 5f, 1f, 15f, "Maximum time to hold a failed-stage transition while revival initializes."); JoblessEnabled = RoleEnabled(config, "Jobless"); JoblessWeight = RoleWeight(config, "Jobless"); JoblessDamage = BindInt(config, "Jobless", "Damage", 1, 1, 100, "Damage applied per tick outside the truck."); JoblessDamageIntervalSeconds = BindFloat(config, "Jobless", "DamageIntervalSeconds", 0.1f, 0.05f, 10f, "Seconds between damage ticks outside the truck."); RescuerEnabled = RoleEnabled(config, "Rescuer"); RescuerWeight = RoleWeight(config, "Rescuer"); RescuerRevivalHealth = BindInt(config, "Rescuer", "RevivalHealth", 25, 1, 1000, "Health restored to a player revived by Rescuer, capped at the player's maximum health."); RescuerReviveDelaySeconds = BindFloat(config, "Rescuer", "ReviveDelaySeconds", 2f, 0f, 10f, "Delay before reviving another player."); RescuerRadius = BindFloat(config, "Rescuer", "Radius", 3f, 1f, 50f, "Maximum distance to a dead player."); RescuerMaximumRevives = BindInt(config, "Rescuer", "MaximumRevives", 2, 1, 10, "Maximum revivals per stage."); VampireEnabled = RoleEnabled(config, "Vampire"); VampireWeight = RoleWeight(config, "Vampire"); VampireTier1HealAmount = BindInt(config, "Vampire", "Tier1HealAmount", 5, 1, 100, "Health restored by a nearby Tier 1 enemy death."); VampireTier2HealAmount = BindInt(config, "Vampire", "Tier2HealAmount", 10, 1, 100, "Health restored by a nearby Tier 2 enemy death."); VampireTier3HealAmount = BindInt(config, "Vampire", "Tier3HealAmount", 50, 1, 100, "Health restored by a nearby Tier 3 enemy death."); VampireRadius = BindFloat(config, "Vampire", "Radius", 10f, 1f, 50f, "Maximum distance from a dying enemy."); KingEnabled = RoleEnabled(config, "King"); KingWeight = RoleWeight(config, "King"); TunaEnabled = RoleEnabled(config, "Tuna"); TunaWeight = RoleWeight(config, "Tuna"); TunaStationaryDelaySeconds = BindFloat(config, "Tuna", "StationaryDelaySeconds", 3f, 0.1f, 30f, "Seconds without movement before damage starts."); TunaDamage = BindInt(config, "Tuna", "Damage", 1, 1, 100, "Damage applied per stationary tick."); TunaDamageIntervalSeconds = BindFloat(config, "Tuna", "DamageIntervalSeconds", 0.1f, 0.05f, 10f, "Seconds between stationary damage ticks."); MusicianEnabled = RoleEnabled(config, "Musician"); MusicianWeight = RoleWeight(config, "Musician"); MusicianHealAmount = BindInt(config, "Musician", "HealAmount", 5, 1, 100, "Health restored to the Musician and each nearby player when an instrument note is played."); MusicianHealRadius = BindFloat(config, "Musician", "HealRadius", 10f, 1f, 50f, "Maximum healing distance from the Musician when an instrument note is played."); MageEnabled = RoleEnabled(config, "Mage"); MageWeight = RoleWeight(config, "Mage"); MageCastIntervalSeconds = BindFloat(config, "Mage", "CastIntervalSeconds", 5f, 0.1f, 30f, "Minimum seconds between chat-triggered casts."); MageStarHealthCost = BindInt(config, "Mage", "StarHealthCost", 10, 0, 100, "Health consumed after a successful Star Wand cast."); MageGravityHealthCost = BindInt(config, "Mage", "GravityHealthCost", 10, 0, 100, "Health consumed after a successful Zero Gravity Staff cast."); MageRollHealthCost = BindInt(config, "Mage", "RollHealthCost", 15, 0, 100, "Health consumed after a successful Roll Staff cast."); MageVoidHealthCost = BindInt(config, "Mage", "VoidHealthCost", 30, 0, 100, "Health consumed after a successful Void Staff cast."); MageLaserHealthCost = BindInt(config, "Mage", "LaserHealthCost", 50, 0, 100, "Health consumed after a successful Wizard Staff beam cast."); GamblerEnabled = RoleEnabled(config, "Gambler"); GamblerWeight = RoleWeight(config, "Gambler"); GamblerWinChancePercent = BindInt(config, "Gambler", "WinChancePercent", 50, 0, 100, "Chance for a wagered valuable to use the win multiplier."); GamblerWinValueMultiplier = BindFloat(config, "Gambler", "WinValueMultiplier", 2f, 0f, 10f, "Value multiplier applied when a wager wins."); config.Remove(new ConfigDefinition("Gambler", "MaximumWagersPerStage")); config.Remove(new ConfigDefinition("Gambler", "HealAmountPerExtraction")); config.Remove(new ConfigDefinition("Gambler", "LoseValueMultiplier")); HunterEnabled = RoleEnabled(config, "Hunter"); HunterWeight = RoleWeight(config, "Hunter"); HunterBatteryConsumptionPercent = BindInt(config, "Hunter", "WeaponBatteryConsumptionPercent", 75, 0, 100, "Percentage of normal weapon battery consumption used by Hunter."); HunterDoubleOrbChancePercent = BindFloat(config, "Hunter", "DoubleOrbChancePercent", 10f, 0f, 100f, "Chance for an enemy defeated by Hunter to drop twice its normal orb count."); HunterJackpotOrbChancePercent = BindFloat(config, "Hunter", "JackpotOrbChancePercent", 0.01f, 0f, 100f, "Chance for an enemy defeated by Hunter to drop the configured jackpot orb count."); HunterJackpotOrbCount = BindInt(config, "Hunter", "JackpotOrbCount", 10, 1, 100, "Orb count used when Hunter's jackpot roll succeeds."); StinkerEnabled = RoleEnabled(config, "Stinker"); StinkerWeight = RoleWeight(config, "Stinker"); StinkerDistance = BindFloat(config, "Stinker", "DistancePerCloud", 1f, 1f, 100f, "Travel distance required per uranium cloud."); StinkerSafetyDistance = BindFloat(config, "Stinker", "MinimumSafetyDistance", 1f, 1f, 30f, "Minimum distance from the newest pending cloud when it is created."); StinkerAllowTruckSpawns = BindBool(config, "Stinker", "AllowTruckSpawns", defaultValue: false, "Allows uranium cloud creation inside the truck."); EngineerEnabled = RoleEnabled(config, "Engineer"); EngineerWeight = RoleWeight(config, "Engineer"); } internal bool RoleIsEnabled(StageRole role) { return role switch { StageRole.Tank => TankEnabled.Value, StageRole.Runner => RunnerEnabled.Value, StageRole.Jumper => JumperEnabled.Value, StageRole.Lifter => LifterEnabled.Value, StageRole.Launcher => LauncherEnabled.Value, StageRole.Climber => ClimberEnabled.Value, StageRole.Flyer => FlyerEnabled.Value, StageRole.Tracker => TrackerEnabled.Value, StageRole.Ghost => GhostEnabled.Value, StageRole.Bomber => BomberEnabled.Value, StageRole.Medic => MedicEnabled.Value, StageRole.Phoenix => PhoenixEnabled.Value, StageRole.Jobless => JoblessEnabled.Value, StageRole.Rescuer => RescuerEnabled.Value, StageRole.Vampire => VampireEnabled.Value, StageRole.King => KingEnabled.Value, StageRole.Tuna => TunaEnabled.Value, StageRole.Musician => MusicianEnabled.Value, StageRole.Mage => MageEnabled.Value, StageRole.Gambler => GamblerEnabled.Value, StageRole.Hunter => HunterEnabled.Value, StageRole.Stinker => StinkerEnabled.Value, StageRole.Engineer => EngineerEnabled.Value, _ => false, }; } internal int RoleWeightValue(StageRole role) { return role switch { StageRole.Tank => TankWeight.Value, StageRole.Runner => RunnerWeight.Value, StageRole.Jumper => JumperWeight.Value, StageRole.Lifter => LifterWeight.Value, StageRole.Launcher => LauncherWeight.Value, StageRole.Climber => ClimberWeight.Value, StageRole.Flyer => FlyerWeight.Value, StageRole.Tracker => TrackerWeight.Value, StageRole.Ghost => GhostWeight.Value, StageRole.Bomber => BomberWeight.Value, StageRole.Medic => MedicWeight.Value, StageRole.Phoenix => PhoenixWeight.Value, StageRole.Jobless => JoblessWeight.Value, StageRole.Rescuer => RescuerWeight.Value, StageRole.Vampire => VampireWeight.Value, StageRole.King => KingWeight.Value, StageRole.Tuna => TunaWeight.Value, StageRole.Musician => MusicianWeight.Value, StageRole.Mage => MageWeight.Value, StageRole.Gambler => GamblerWeight.Value, StageRole.Hunter => HunterWeight.Value, StageRole.Stinker => StinkerWeight.Value, StageRole.Engineer => EngineerWeight.Value, _ => 0, }; } private static ConfigEntry RoleEnabled(ConfigFile config, string section) { return BindBool(config, section, "Enabled", defaultValue: true, "Includes this role in stage assignment."); } private static ConfigEntry RoleWeight(ConfigFile config, string section) { return BindInt(config, section, "Weight", 100, 0, 1000, "Relative assignment weight."); } private static ConfigEntry UpgradeLevel(ConfigFile config, string section, string key, int defaultValue) { return BindInt(config, section, key, defaultValue, 0, 100, "Vanilla upgrade target level while this role is assigned."); } private static ConfigEntry BaseUpgradeLevel(ConfigFile config, string key, int defaultValue) { return BindInt(config, "Base Upgrades", key, defaultValue, 0, 100, "Base vanilla upgrade level kept when no role overrides it."); } private static ConfigEntry BindBool(ConfigFile config, string section, string key, bool defaultValue, string description) { return config.Bind(section, key, defaultValue, description); } private static ConfigEntry BindInt(ConfigFile config, string section, string key, int defaultValue, int minimum, int maximum, string description) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown return config.Bind(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange(minimum, maximum), Array.Empty())); } private static ConfigEntry BindFloat(ConfigFile config, string section, string key, float defaultValue, float minimum, float maximum, string description) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown return config.Bind(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange(minimum, maximum), Array.Empty())); } } internal enum StageRole { Tank, Runner, Jumper, Lifter, Launcher, Climber, Flyer, Tracker, Ghost, Bomber, Medic, Phoenix, Jobless, Rescuer, Vampire, King, Tuna, Musician, Mage, Gambler, Hunter, Stinker, Engineer } internal sealed class RoleAssignment { internal string SteamId { get; } internal PlayerAvatar Player { get; set; } internal StageRole Role { get; set; } internal Vector3 PreviousPosition { get; set; } internal Vector3 StinkerTrailAnchor { get; set; } internal float TravelDistance { get; set; } internal float JoblessDamageTimer { get; set; } internal float TunaStationaryTimer { get; set; } internal float TunaDamageTimer { get; set; } internal float MageNextCastAt { get; set; } internal bool WasAlive { get; set; } internal bool PhoenixUsed { get; set; } internal bool PhoenixRevivePending { get; set; } internal float PhoenixReadyAt { get; set; } internal int RescuerRevivesUsed { get; set; } internal int GamblerWagersUsed { get; set; } internal RoleAssignment(string steamId, PlayerAvatar player, StageRole role) { //IL_0022: 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) SteamId = steamId; Player = player; Role = role; PreviousPosition = ((Component)player).transform.position; StinkerTrailAnchor = ((Component)player).transform.position; WasAlive = true; } } internal readonly struct UpgradeGrant { internal string CommandName { get; } internal string DictionaryName { get; } internal int Level { get; } internal UpgradeGrant(string commandName, string dictionaryName, int level) { CommandName = commandName; DictionaryName = dictionaryName; Level = level; } } internal static class RoleCatalog { internal static readonly IReadOnlyList AllRoles = (StageRole[])Enum.GetValues(typeof(StageRole)); internal static IReadOnlyList BaseUpgrades(StageRolesConfig config) { return new UpgradeGrant[12] { new UpgradeGrant("Health", "playerUpgradeHealth", config.BaseHealthLevels.Value), new UpgradeGrant("Stamina", "playerUpgradeStamina", config.BaseStaminaLevels.Value), new UpgradeGrant("ExtraJump", "playerUpgradeExtraJump", config.BaseExtraJumpLevels.Value), new UpgradeGrant("Speed", "playerUpgradeSpeed", config.BaseSpeedLevels.Value), new UpgradeGrant("Strength", "playerUpgradeStrength", config.BaseStrengthLevels.Value), new UpgradeGrant("Range", "playerUpgradeRange", config.BaseRangeLevels.Value), new UpgradeGrant("Launch", "playerUpgradeLaunch", config.BaseLaunchLevels.Value), new UpgradeGrant("TumbleClimb", "playerUpgradeTumbleClimb", config.BaseTumbleClimbLevels.Value), new UpgradeGrant("TumbleWings", "playerUpgradeTumbleWings", config.BaseTumbleWingsLevels.Value), new UpgradeGrant("CrouchRest", "playerUpgradeCrouchRest", config.BaseCrouchRestLevels.Value), new UpgradeGrant("MapPlayerCount", "playerUpgradeMapPlayerCount", config.BaseMapPlayerCountLevels.Value), new UpgradeGrant("DeathHeadBattery", "playerUpgradeDeathHeadBattery", config.BaseDeathHeadBatteryLevels.Value) }; } internal static IReadOnlyList TargetUpgrades(StageRole role, StageRolesConfig config) { List list = new List(BaseUpgrades(config)); foreach (UpgradeGrant item in RoleUpgrades(role, config)) { for (int i = 0; i < list.Count; i++) { if (string.Equals(list[i].DictionaryName, item.DictionaryName, StringComparison.Ordinal)) { list[i] = item; break; } } } return list; } private static IReadOnlyList RoleUpgrades(StageRole role, StageRolesConfig config) { return role switch { StageRole.Tank => Grant("Health", "playerUpgradeHealth", config.TankHealthLevels.Value), StageRole.Runner => new UpgradeGrant[2] { new UpgradeGrant("Speed", "playerUpgradeSpeed", config.RunnerSpeedLevels.Value), new UpgradeGrant("Stamina", "playerUpgradeStamina", config.RunnerStaminaLevels.Value) }, StageRole.Jumper => Grant("ExtraJump", "playerUpgradeExtraJump", config.JumperExtraJumpLevels.Value), StageRole.Lifter => Grant("Strength", "playerUpgradeStrength", config.LifterStrengthLevels.Value), StageRole.Launcher => Grant("Launch", "playerUpgradeLaunch", config.LauncherLaunchLevels.Value), StageRole.Climber => new UpgradeGrant[2] { new UpgradeGrant("TumbleClimb", "playerUpgradeTumbleClimb", config.ClimberClimbLevels.Value), new UpgradeGrant("Range", "playerUpgradeRange", config.ClimberRangeLevels.Value) }, StageRole.Flyer => Grant("TumbleWings", "playerUpgradeTumbleWings", config.FlyerWingsLevels.Value), StageRole.Tracker => Grant("MapPlayerCount", "playerUpgradeMapPlayerCount", 1), StageRole.Ghost => Grant("DeathHeadBattery", "playerUpgradeDeathHeadBattery", config.GhostDeathHeadBatteryLevels.Value), _ => Array.Empty(), }; } private static IReadOnlyList Grant(string commandName, string dictionaryName, int level) { return new UpgradeGrant[1] { new UpgradeGrant(commandName, dictionaryName, level) }; } } internal readonly struct RoleSnapshot { internal string SteamId { get; } internal string PlayerName { get; } internal StageRole Role { get; } internal RoleSnapshot(string steamId, string playerName, StageRole role) { SteamId = steamId; PlayerName = playerName; Role = role; } } internal static class RoleAssignmentSync { private const string RoleMapKey = "RS.RoleMap"; private static string _localRoleMap = string.Empty; private static int _previewPlayerCount; internal static int PreviewPlayerCount => _previewPlayerCount; internal static void SetPreviewPlayerCount(int count) { _previewPlayerCount = Math.Max(1, Math.Min(30, count)); } internal static void ClearPreview() { _previewPlayerCount = 0; } internal static void Publish(IReadOnlyList assignments) { //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown List list = new List(); foreach (RoleAssignment assignment in assignments) { if (!string.IsNullOrEmpty(assignment.SteamId)) { string text = PlayerIdentity.Name(assignment.Player); string arg = Convert.ToBase64String(Encoding.UTF8.GetBytes(text ?? string.Empty)); list.Add($"{assignment.SteamId},{(int)assignment.Role},{arg}"); } } _localRoleMap = string.Join(";", list); if (SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && PhotonNetwork.CurrentRoom != null) { PhotonNetwork.CurrentRoom.SetCustomProperties(new Hashtable { [(object)"RS.RoleMap"] = _localRoleMap }, (Hashtable)null, (WebFlags)null); } } internal static void Clear() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown _localRoleMap = string.Empty; if (SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && PhotonNetwork.CurrentRoom != null) { PhotonNetwork.CurrentRoom.SetCustomProperties(new Hashtable { [(object)"RS.RoleMap"] = null }, (Hashtable)null, (WebFlags)null); } } internal static IReadOnlyList Read() { string text = _localRoleMap; if (SemiFunc.IsMultiplayer() && PhotonNetwork.CurrentRoom != null && ((Dictionary)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"RS.RoleMap", out object value) && value is string text2) { text = text2; } List list = new List(); if (string.IsNullOrEmpty(text)) { return ApplyPreview(list); } string[] array = text.Split(';'); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split(','); if (array2.Length == 3 && !string.IsNullOrEmpty(array2[0]) && int.TryParse(array2[1], out var result) && Enum.IsDefined(typeof(StageRole), result)) { string playerName; try { playerName = Encoding.UTF8.GetString(Convert.FromBase64String(array2[2])); } catch { playerName = string.Empty; } list.Add(new RoleSnapshot(array2[0], playerName, (StageRole)result)); } } return ApplyPreview(list); } private static IReadOnlyList ApplyPreview(List actual) { if (_previewPlayerCount <= 0) { return actual; } int num = Math.Max(1, Math.Min(30, _previewPlayerCount)); List list = new List(num); string text = PlayerIdentity.SteamId(SemiFunc.PlayerGetLocal()); int num2 = -1; for (int i = 0; i < actual.Count; i++) { if (!string.IsNullOrEmpty(text) && string.Equals(actual[i].SteamId, text, StringComparison.Ordinal)) { num2 = i; break; } } if (num2 >= 0) { list.Add(actual[num2]); } else if (actual.Count == 0 && !string.IsNullOrEmpty(text)) { string text2 = PlayerIdentity.Name(SemiFunc.PlayerGetLocal()); list.Add(new RoleSnapshot(text, string.IsNullOrWhiteSpace(text2) ? "Local Player" : text2, StageRole.Tank)); } for (int j = 0; j < actual.Count; j++) { if (list.Count >= num) { break; } if (j != num2) { list.Add(actual[j]); } } int num3 = 1; while (list.Count < num) { StageRole role = RoleCatalog.AllRoles[(list.Count - actual.Count + RoleCatalog.AllRoles.Count) % RoleCatalog.AllRoles.Count]; list.Add(new RoleSnapshot($"preview:{num3}", $"Preview Player {num3:00}", role)); num3++; } return list; } } internal sealed class RoleHud : MonoBehaviour { private const float RefreshIntervalSeconds = 0.25f; private const int MaximumNameCharacters = 20; private static readonly Vector2 LayoutSize = new Vector2(620f, 340f); private readonly List _fontTargets = new List(); private readonly List _assignments = new List(); private StageRolesConfig _config; private GameObject? _root; private RectTransform? _canvasRect; private RectTransform? _contentRect; private CanvasGroup? _canvasGroup; private TextMeshProUGUI? _text; private string _lastText = string.Empty; private string _lastLayout = string.Empty; private string _assignmentSignature = string.Empty; private float _nextRefreshAt; private float _nextFontProbeAt; private float _nextPageAt; private float _transitionStartedAt; private int _pageIndex; private bool _transitioning; private bool _pageSwitched; private bool _hasAssignments; private float ClampedPageInterval => Mathf.Clamp(_config.HudPageIntervalSeconds.Value, 1f, 30f); internal void Initialize(StageRolesConfig config) { _config = config; } private void Update() { EnsureCreated(); if (!((Object)(object)_root == (Object)null)) { if (Time.unscaledTime >= _nextRefreshAt) { _nextRefreshAt = Time.unscaledTime + 0.25f; RefreshAssignments(); } bool flag = _config.HudEnabled.Value && _config.Enabled.Value && _hasAssignments && IsPlayableStageContext(); if (_root.activeSelf != flag) { _root.SetActive(flag); } if (flag) { UpdatePageTransition(); ApplyLayout(); TryApplyRepoUiFont(); } } } private void EnsureCreated() { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown //IL_009f: 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_00bf: 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_00d5: 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_014f: Expected O, but got Unknown //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Expected O, but got Unknown //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_024f: 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_0278: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_root != (Object)null) { return; } object obj; if (!((Object)(object)HealthUI.instance != (Object)null)) { HUDCanvas instance = HUDCanvas.instance; obj = ((instance != null) ? ((Component)instance).transform : null); } else { obj = ((Component)HealthUI.instance).transform.parent; } Transform val = (Transform)obj; if (!((Object)(object)val == (Object)null)) { _root = new GameObject("RoleShuffle_HUD", new Type[1] { typeof(RectTransform) }); ((Object)_root).hideFlags = (HideFlags)61; _root.transform.SetParent(val, false); RectTransform val2 = (_canvasRect = _root.GetComponent()); val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.pivot = new Vector2(0.5f, 0.5f); val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; if ((Object)(object)HealthUI.instance != (Object)null && (Object)(object)((Component)HealthUI.instance).transform.parent == (Object)(object)val) { _root.transform.SetSiblingIndex(((Component)HealthUI.instance).transform.GetSiblingIndex() + 1); } GameObject val3 = new GameObject("RotatingRoles", new Type[2] { typeof(RectTransform), typeof(CanvasGroup) }); val3.transform.SetParent(_root.transform, false); _contentRect = val3.GetComponent(); _contentRect.sizeDelta = LayoutSize; _canvasGroup = val3.GetComponent(); _canvasGroup.interactable = false; _canvasGroup.blocksRaycasts = false; GameObject val4 = new GameObject("RoleText", new Type[2] { typeof(RectTransform), typeof(TextMeshProUGUI) }); val4.transform.SetParent(val3.transform, false); _text = val4.GetComponent(); RectTransform rectTransform = ((TMP_Text)_text).rectTransform; rectTransform.anchorMin = Vector2.zero; rectTransform.anchorMax = Vector2.one; rectTransform.offsetMin = Vector2.zero; rectTransform.offsetMax = Vector2.zero; ((TMP_Text)_text).fontSize = 28f; ((Graphic)_text).color = new Color(0.9f, 0.91f, 0.87f, 1f); ((TMP_Text)_text).outlineColor = Color32.op_Implicit(new Color(0f, 0f, 0f, 0.95f)); ((TMP_Text)_text).outlineWidth = 0.15f; ((Graphic)_text).raycastTarget = false; ((TMP_Text)_text).enableWordWrapping = false; ((TMP_Text)_text).overflowMode = (TextOverflowModes)3; ((TMP_Text)_text).alignment = (TextAlignmentOptions)513; _fontTargets.Add((TMP_Text)(object)_text); _lastLayout = string.Empty; _root.SetActive(false); } } private void RefreshAssignments() { IReadOnlyList readOnlyList = RoleAssignmentSync.Read(); _hasAssignments = readOnlyList.Count > 0; string text = BuildAssignmentSignature(readOnlyList) + "|" + $"{_config.HudPlayersPerPage.Value}|" + $"{_config.HudPinLocalPlayer.Value}"; if (string.Equals(text, _assignmentSignature, StringComparison.Ordinal)) { return; } _assignmentSignature = text; _assignments.Clear(); foreach (RoleSnapshot item in readOnlyList) { _assignments.Add(item); } _pageIndex = 0; _transitioning = false; _pageSwitched = false; _nextPageAt = Time.unscaledTime + ClampedPageInterval; if ((Object)(object)_canvasGroup != (Object)null) { _canvasGroup.alpha = 1f; } UpdateDisplayedText(); } private void UpdatePageTransition() { if ((Object)(object)_canvasGroup == (Object)null) { return; } int pageCount = GetPageCount(); if (pageCount <= 1) { _pageIndex = 0; _transitioning = false; _canvasGroup.alpha = 1f; return; } float unscaledTime = Time.unscaledTime; if (!_transitioning) { if (unscaledTime < _nextPageAt || RoleMenu.IsOpen) { return; } _transitioning = true; _pageSwitched = false; _transitionStartedAt = unscaledTime; } float num = Mathf.Clamp(_config.HudTransitionDurationSeconds.Value, 0f, 1f); if (num <= 0.001f) { AdvancePage(pageCount); FinishTransition(unscaledTime); return; } float num2 = num * 0.5f; float num3 = unscaledTime - _transitionStartedAt; if (num3 < num2) { _canvasGroup.alpha = 1f - Mathf.Clamp01(num3 / num2); return; } if (!_pageSwitched) { AdvancePage(pageCount); _pageSwitched = true; } if (num3 < num) { _canvasGroup.alpha = Mathf.Clamp01((num3 - num2) / num2); } else { FinishTransition(unscaledTime); } } private void AdvancePage(int pageCount) { _pageIndex = (_pageIndex + 1) % pageCount; UpdateDisplayedText(); } private void FinishTransition(float now) { _transitioning = false; _pageSwitched = false; if ((Object)(object)_canvasGroup != (Object)null) { _canvasGroup.alpha = 1f; } _nextPageAt = now + ClampedPageInterval; } private void UpdateDisplayedText() { string text = BuildPageText(); if ((Object)(object)_text != (Object)null && !string.Equals(_lastText, text, StringComparison.Ordinal)) { _lastText = text; ((TMP_Text)_text).text = text; } } private string BuildPageText() { if (_assignments.Count == 0) { return string.Empty; } RoleSnapshot? roleSnapshot = FindLocalSnapshot(); List list = new List(_assignments.Count); foreach (RoleSnapshot assignment in _assignments) { if (!roleSnapshot.HasValue || !string.Equals(assignment.SteamId, roleSnapshot.Value.SteamId, StringComparison.Ordinal)) { list.Add(assignment); } } int num = Mathf.Clamp(_config.HudPlayersPerPage.Value, 2, 20); int num2 = Mathf.Max(1, num - (roleSnapshot.HasValue ? 1 : 0)); int num3 = Mathf.Max(1, Mathf.CeilToInt((float)list.Count / (float)num2)); _pageIndex = Mathf.Clamp(_pageIndex, 0, num3 - 1); List list2 = new List(num + 1) { (num3 > 1) ? $"ROLES {_pageIndex + 1} / {num3}" : "ROLES" }; if (roleSnapshot.HasValue) { list2.Add($"YOU: {roleSnapshot.Value.Role}"); } int num4 = _pageIndex * num2; int num5 = Mathf.Min(num4 + num2, list.Count); for (int i = num4; i < num5; i++) { RoleSnapshot roleSnapshot2 = list[i]; string text = $"Player {i + 1}"; string arg = Ellipsize(string.IsNullOrWhiteSpace(roleSnapshot2.PlayerName) ? text : roleSnapshot2.PlayerName.Trim(), 20); list2.Add($"{arg}: {roleSnapshot2.Role}"); } return string.Join("\n", list2); } private RoleSnapshot? FindLocalSnapshot() { if (!_config.HudPinLocalPlayer.Value) { return null; } string text = PlayerIdentity.SteamId(SemiFunc.PlayerGetLocal()); if (string.IsNullOrEmpty(text)) { return null; } foreach (RoleSnapshot assignment in _assignments) { if (string.Equals(assignment.SteamId, text, StringComparison.Ordinal)) { return assignment; } } return null; } private int GetPageCount() { if (_assignments.Count == 0) { return 1; } bool hasValue = FindLocalSnapshot().HasValue; int num = Mathf.Clamp(_config.HudPlayersPerPage.Value, 2, 20); int num2 = Mathf.Max(1, num - (hasValue ? 1 : 0)); int num3 = Mathf.Max(0, _assignments.Count - (hasValue ? 1 : 0)); return Mathf.Max(1, Mathf.CeilToInt((float)num3 / (float)num2)); } private static string BuildAssignmentSignature(IReadOnlyList assignments) { List list = new List(assignments.Count); foreach (RoleSnapshot assignment in assignments) { list.Add($"{assignment.SteamId}\u001f{assignment.PlayerName}\u001f{assignment.Role}"); } return string.Join("\u001e", list); } private static string Ellipsize(string value, int maximumCharacters) { if (value.Length <= maximumCharacters) { return value; } return value.Substring(0, Mathf.Max(1, maximumCharacters - 1)) + "…"; } private void ApplyLayout() { //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_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_0169: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: 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_01f3: 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_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_0267: 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_0263: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_contentRect == (Object)null) && !((Object)(object)_text == (Object)null)) { int num; Rect rect; if (!((Object)(object)_canvasRect != (Object)null)) { num = Screen.width; } else { rect = _canvasRect.rect; num = Mathf.RoundToInt(((Rect)(ref rect)).width); } int num2 = num; int num3; if (!((Object)(object)_canvasRect != (Object)null)) { num3 = Screen.height; } else { rect = _canvasRect.rect; num3 = Mathf.RoundToInt(((Rect)(ref rect)).height); } int num4 = num3; string text = _config.HudAnchor.Value + ":" + _config.HudAlignment.Value + ":" + $"{_config.HudOffsetX.Value}:{_config.HudOffsetY.Value}:" + $"{_config.HudScalePercent.Value}:{num2}x{num4}:" + $"{Screen.width}x{Screen.height}"; if (!(_lastLayout == text)) { _lastLayout = text; Vector2 val = ResolveAnchor(_config.HudAnchor.Value); float num5 = ResolveResolutionScale(); float userScale = (float)_config.HudScalePercent.Value / 100f; float num6 = CalculateResponsiveScale(LayoutSize, userScale, num5); _contentRect.anchorMin = val; _contentRect.anchorMax = val; _contentRect.pivot = val; _contentRect.anchoredPosition = new Vector2((float)_config.HudOffsetX.Value * num5, (float)_config.HudOffsetY.Value * num5); ((Transform)_contentRect).localScale = Vector3.one * num6; TextMeshProUGUI text2 = _text; string value = _config.HudAlignment.Value; TextAlignmentOptions alignment = ((value == "Center") ? ((TextAlignmentOptions)514) : ((!(value == "Right")) ? ((TextAlignmentOptions)513) : ((TextAlignmentOptions)516))); ((TMP_Text)text2).alignment = alignment; } } } private float ResolveResolutionScale() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_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) float num; if ((Object)(object)_canvasRect != (Object)null) { Rect rect = _canvasRect.rect; if (((Rect)(ref rect)).height > 1f) { rect = _canvasRect.rect; num = ((Rect)(ref rect)).height; goto IL_0043; } } num = Screen.height; goto IL_0043; IL_0043: float num2 = num; return Mathf.Max(0.1f, num2 / 540f); } private float CalculateResponsiveScale(Vector2 layoutSize, float userScale, float resolutionScale) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_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_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_00a9: 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) float num = Mathf.Max(0.1f, resolutionScale * userScale); if (!((Object)(object)_canvasRect == (Object)null)) { Rect rect = _canvasRect.rect; if (!(((Rect)(ref rect)).width <= 1f)) { rect = _canvasRect.rect; if (!(((Rect)(ref rect)).height <= 1f)) { float num2 = 8f * resolutionScale; rect = _canvasRect.rect; float num3 = Mathf.Max(1f, ((Rect)(ref rect)).width - num2 * 2f); rect = _canvasRect.rect; float num4 = Mathf.Max(1f, ((Rect)(ref rect)).height - num2 * 2f); float num5 = Mathf.Min(num3 / layoutSize.x, num4 / layoutSize.y); return Mathf.Max(0.1f, Mathf.Min(num, num5)); } } } return num; } private void TryApplyRepoUiFont() { if (Time.unscaledTime < _nextFontProbeAt) { return; } _nextFontProbeAt = Time.unscaledTime + 2f; TMP_Text val = (TMP_Text)(object)(((Object)(object)HealthUI.instance != (Object)null) ? ((Component)HealthUI.instance).GetComponent() : null); if (val == null) { val = (TMP_Text)(object)ChatUI.instance?.chatText; } if ((Object)(object)val == (Object)null && (Object)(object)HUD.instance != (Object)null) { TMP_Text[] componentsInChildren = ((Component)HUD.instance).GetComponentsInChildren(true); foreach (TMP_Text val2 in componentsInChildren) { if ((Object)(object)val2 != (Object)null && !_fontTargets.Contains(val2) && (Object)(object)val2.font != (Object)null) { val = val2; break; } } } if ((Object)(object)((val != null) ? val.font : null) == (Object)null) { return; } foreach (TMP_Text fontTarget in _fontTargets) { if ((Object)(object)fontTarget != (Object)null && (Object)(object)fontTarget.font != (Object)(object)val.font) { fontTarget.font = val.font; } } } private static bool IsPlayableStageContext() { try { return SemiFunc.RunIsLevel() && !SemiFunc.RunIsLobby() && !SemiFunc.RunIsShop() && !SemiFunc.RunIsArena() && !SemiFunc.RunIsTutorial(); } catch { return false; } } private static Vector2 ResolveAnchor(string anchor) { //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: 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_013f: 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_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) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_0163: 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) return (Vector2)(anchor switch { "TopLeft" => new Vector2(0f, 1f), "TopCenter" => new Vector2(0.5f, 1f), "TopRight" => new Vector2(1f, 1f), "MiddleLeft" => new Vector2(0f, 0.5f), "MiddleCenter" => new Vector2(0.5f, 0.5f), "MiddleRight" => new Vector2(1f, 0.5f), "BottomCenter" => new Vector2(0.5f, 0f), "BottomRight" => new Vector2(1f, 0f), _ => new Vector2(0f, 0f), }); } private void OnDestroy() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } } } internal sealed class RoleMenu : MonoBehaviour { private sealed class RoleMenuRow(REPOLabel label, REPOScrollViewElement element) { internal REPOLabel Label { get; } = label; internal REPOScrollViewElement Element { get; } = element; } [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static BuilderDelegate <>9__10_0; public static ShouldCloseMenuDelegate <>9__12_2; internal void b__10_0(Transform parent) { //IL_0021: 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_0050: 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) RectTransform rectTransform = ((REPOElement)MenuAPI.CreateREPOButton("ROLES", (Action)OpenRolePage, parent, Vector2.zero)).rectTransform; rectTransform.anchorMin = new Vector2(1f, 0f); rectTransform.anchorMax = new Vector2(1f, 0f); rectTransform.pivot = new Vector2(1f, 0f); rectTransform.anchoredPosition = new Vector2(-36f, 36f); } internal bool b__12_2() { ClearOpenPageTracking(); return true; } } private const float RefreshIntervalSeconds = 0.25f; private static bool _registered; private static readonly List _roleRows = new List(); private static REPOPopupPage? _openPage; private static string _openSignature = string.Empty; private float _nextRefreshAt; internal static bool IsOpen { get; private set; } internal void Initialize() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown if (_registered) { return; } _registered = true; object obj = <>c.<>9__10_0; if (obj == null) { BuilderDelegate val = delegate(Transform parent) { //IL_0021: 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_0050: 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) RectTransform rectTransform = ((REPOElement)MenuAPI.CreateREPOButton("ROLES", (Action)OpenRolePage, parent, Vector2.zero)).rectTransform; rectTransform.anchorMin = new Vector2(1f, 0f); rectTransform.anchorMax = new Vector2(1f, 0f); rectTransform.pivot = new Vector2(1f, 0f); rectTransform.anchoredPosition = new Vector2(-36f, 36f); }; <>c.<>9__10_0 = val; obj = (object)val; } MenuAPI.AddElementToEscapeMenu((BuilderDelegate)obj); } private void Update() { if (!IsOpen) { return; } if ((Object)(object)_openPage == (Object)null) { ClearOpenPageTracking(); } else if (!(Time.unscaledTime < _nextRefreshAt)) { _nextRefreshAt = Time.unscaledTime + 0.25f; IReadOnlyList assignments = RoleAssignmentSync.Read(); string text = BuildSignature(assignments); if (!string.Equals(text, _openSignature, StringComparison.Ordinal)) { RefreshRoleRows(_openPage, assignments, text); } } } private static void OpenRolePage() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0068: 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_00ac: Expected O, but got Unknown //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Expected O, but got Unknown //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Expected O, but got Unknown IReadOnlyList assignments = RoleAssignmentSync.Read(); REPOPopupPage page = MenuAPI.CreateREPOPopupPage("Roles", (PresetSide)1, false, true, 1.5f); page.scrollView.scrollSpeed = 3f; Padding maskPadding = page.maskPadding; maskPadding.top = 24f; maskPadding.bottom = 12f; page.maskPadding = maskPadding; _openPage = page; _roleRows.Clear(); RefreshRoleRows(page, assignments, BuildSignature(assignments)); page.AddElement((BuilderDelegate)delegate(Transform parent) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) MenuAPI.CreateREPOButton("Back", (Action)delegate { ClearOpenPageTracking(); page.ClosePage(true); }, parent, new Vector2(66f, 18f)); }); REPOPopupPage obj = page; ShouldCloseMenuDelegate onEscapePressed = obj.onEscapePressed; object obj2 = <>c.<>9__12_2; if (obj2 == null) { ShouldCloseMenuDelegate val = delegate { ClearOpenPageTracking(); return true; }; <>c.<>9__12_2 = val; obj2 = (object)val; } obj.onEscapePressed = (ShouldCloseMenuDelegate)Delegate.Combine((Delegate?)(object)onEscapePressed, (Delegate?)obj2); IsOpen = true; page.OpenPage(false); } private static void RefreshRoleRows(REPOPopupPage page, IReadOnlyList assignments, string signature) { List<(string, bool)> list = new List<(string, bool)>(); string text = PlayerIdentity.SteamId(SemiFunc.PlayerGetLocal()); if (assignments.Count == 0) { list.Add(("No roles are currently assigned.", true)); } else { int num = 1; foreach (RoleSnapshot assignment in assignments) { string arg = (string.IsNullOrWhiteSpace(assignment.PlayerName) ? $"Player {num}" : assignment.PlayerName.Trim()); bool flag = !string.IsNullOrEmpty(text) && string.Equals(assignment.SteamId, text, StringComparison.Ordinal); list.Add((flag ? $"YOU - {assignment.Role}" : $"{arg} - {assignment.Role}", flag)); num++; } } while (_roleRows.Count < list.Count) { _roleRows.Add(CreateRoleRow(page)); } for (int i = 0; i < _roleRows.Count; i++) { RoleMenuRow roleMenuRow = _roleRows[i]; bool flag2 = i < list.Count; roleMenuRow.Element.visibility = flag2; if (flag2) { (string, bool) tuple = list[i]; string item = tuple.Item1; bool item2 = tuple.Item2; TextMeshProUGUI labelTMP = roleMenuRow.Label.labelTMP; ((TMP_Text)labelTMP).text = item; ((TMP_Text)labelTMP).alignment = (TextAlignmentOptions)513; ((TMP_Text)labelTMP).fontSize = (item2 ? 24f : 21f); ((TMP_Text)labelTMP).fontStyle = (FontStyles)(item2 ? 1 : 0); } } page.scrollView.UpdateElements(); _openSignature = signature; } private static RoleMenuRow CreateRoleRow(REPOPopupPage page) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown REPOLabel createdLabel = null; page.AddElementToScrollView((ScrollViewBuilderDelegate)delegate(Transform parent) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) createdLabel = MenuAPI.CreateREPOLabel(string.Empty, parent, Vector2.zero); return ((REPOElement)createdLabel).rectTransform; }, 3f, 3f); if ((Object)(object)createdLabel == (Object)null) { throw new InvalidOperationException("Failed to create a role menu row."); } REPOScrollViewElement component = ((Component)((REPOElement)createdLabel).rectTransform).GetComponent(); return new RoleMenuRow(createdLabel, component); } private static string BuildSignature(IReadOnlyList assignments) { StringBuilder stringBuilder = new StringBuilder(); foreach (RoleSnapshot assignment in assignments) { stringBuilder.Append(assignment.SteamId).Append('\u001f').Append(assignment.PlayerName) .Append('\u001f') .Append((int)assignment.Role) .Append('\u001e'); } return stringBuilder.ToString(); } private static void ClearOpenPageTracking() { IsOpen = false; _openPage = null; _roleRows.Clear(); _openSignature = string.Empty; } } internal sealed class RoleTestCommandService : MonoBehaviour { private const string FullCommand = "setrole"; private const string ShortCommand = "sr"; private const string HudPlayersCommand = "hudplayers"; private const string HudPlayersShortCommand = "hp"; private static readonly FieldInfo? DebugConsoleInstanceField = AccessTools.Field(typeof(DebugConsoleUI), "instance"); private static readonly MethodInfo? SetResponseTextMethod = AccessTools.Method(typeof(DebugConsoleUI), "SetResponseText", new Type[3] { typeof(string), typeof(Color), typeof(float) }, (Type[])null); private StageRolesConfig _config; private StageRoleController _controller; private DebugCommandHandler? _registeredHandler; internal void Initialize(StageRolesConfig config, StageRoleController controller) { _config = config; _controller = controller; } private void Update() { DebugCommandHandler instance = DebugCommandHandler.instance; if (!((Object)(object)instance == (Object)null) && instance != _registeredHandler) { _registeredHandler = instance; bool flag = TryRegister(instance, "setrole"); bool flag2 = TryRegister(instance, "sr"); bool flag3 = TryRegisterHudPlayers(instance, "hudplayers"); bool flag4 = TryRegisterHudPlayers(instance, "hp"); if (flag || flag2 || flag3 || flag4) { StageRolesPlugin.ModLogger.LogInfo((object)("Registered role testing commands: " + $"/setrole={flag}, /sr={flag2}, " + $"/hudplayers={flag3}, " + $"/hp={flag4}.")); } } } private bool TryRegisterHudPlayers(DebugCommandHandler handler, string commandName) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown try { handler.Register(new ChatCommand(commandName, "Overrides the local role HUD player count for testing.", (Action)ExecuteHudPlayers, (Func>)SuggestHudPlayers, (Func)(() => _config.SetRoleCommandEnabled.Value), false)); return true; } catch (Exception ex) { StageRolesPlugin.ModLogger.LogWarning((object)("Could not register /" + commandName + ": " + ex.Message)); return false; } } private static void ExecuteHudPlayers(bool isDebugConsole, string[] args) { int result; if (args.Length != 1) { Respond("Usage: /hudplayers <1-30|reset>", success: false); } else if (string.Equals(args[0], "reset", StringComparison.OrdinalIgnoreCase)) { RoleAssignmentSync.ClearPreview(); Respond("HUD player preview reset.", success: true); } else if (!int.TryParse(args[0], out result) || result < 1 || result > 30) { Respond("HUD player count must be from 1 to 30.", success: false); } else { RoleAssignmentSync.SetPreviewPlayerCount(result); Respond($"Local HUD preview player count: {result}.", success: true); } } private static List SuggestHudPlayers(bool isDebugConsole, string partial, string[] args) { List list = new List(); if (args.Length > 1) { return list; } string filter = partial ?? string.Empty; AddSuggestion(list, "1", filter); AddSuggestion(list, "8", filter); AddSuggestion(list, "20", filter); AddSuggestion(list, "30", filter); AddSuggestion(list, "reset", filter); return list; } private bool TryRegister(DebugCommandHandler handler, string commandName) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown try { handler.Register(new ChatCommand(commandName, "Changes a player's stage role for testing.", (Action)Execute, (Func>)Suggest, (Func)(() => _config.SetRoleCommandEnabled.Value), false)); return true; } catch (Exception ex) { StageRolesPlugin.ModLogger.LogWarning((object)("Could not register /" + commandName + ": " + ex.Message)); return false; } } private void Execute(bool isDebugConsole, string[] args) { if (args.Length == 0) { Respond($"Usage: /setrole " + "[player name or Steam ID]", success: false); return; } if (string.Equals(args[0], "list", StringComparison.OrdinalIgnoreCase)) { Respond(BuildRoleList(), success: true, 8f); return; } if (!TryParseRole(args[0], out var role)) { Respond("Unknown role: " + args[0] + ". Use /setrole list.", success: false); return; } string targetIdentifier = ((args.Length > 1) ? string.Join(" ", args, 1, args.Length - 1) : string.Empty); string response; bool success = _controller.TrySetRole(targetIdentifier, role, out response); Respond(response, success); } private static List Suggest(bool isDebugConsole, string partial, string[] args) { List list = new List(); if (args.Length > 1) { return list; } string filter = partial ?? string.Empty; AddSuggestion(list, "list", filter); for (int i = 0; i < RoleCatalog.AllRoles.Count; i++) { AddSuggestion(list, RoleCatalog.AllRoles[i].ToString(), filter); AddSuggestion(list, (i + 1).ToString(), filter); } return list; } private static void AddSuggestion(ICollection suggestions, string candidate, string filter) { if (candidate.StartsWith(filter, StringComparison.OrdinalIgnoreCase)) { suggestions.Add(candidate); } } private static bool TryParseRole(string value, out StageRole role) { if (int.TryParse(value, out var result) && result >= 1 && result <= RoleCatalog.AllRoles.Count) { role = RoleCatalog.AllRoles[result - 1]; return true; } if (Enum.TryParse(value, ignoreCase: true, out role)) { return Enum.IsDefined(typeof(StageRole), role); } return false; } private static string BuildRoleList() { List list = new List(RoleCatalog.AllRoles.Count); for (int i = 0; i < RoleCatalog.AllRoles.Count; i++) { list.Add($"{i + 1}={RoleCatalog.AllRoles[i]}"); } return string.Join(", ", list); } private static void Respond(string text, bool success, float duration = 4f) { //IL_005b: 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) if (success) { StageRolesPlugin.ModLogger.LogInfo((object)text); } else { StageRolesPlugin.ModLogger.LogWarning((object)text); } try { object obj = DebugConsoleInstanceField?.GetValue(null); if (obj != null && SetResponseTextMethod != null) { SetResponseTextMethod.Invoke(obj, new object[3] { text, success ? Color.green : Color.red, duration }); } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Could not show command response: " + ex.Message)); } } } internal static class PlayerState { private static readonly FieldInfo? DisabledField = AccessTools.Field(typeof(PlayerAvatar), "isDisabled"); private static readonly FieldInfo? DeadField = AccessTools.Field(typeof(PlayerAvatar), "deadSet"); private static readonly FieldInfo? SpectatingField = AccessTools.Field(typeof(PlayerAvatar), "spectating"); private static readonly FieldInfo? HealthField = AccessTools.Field(typeof(PlayerHealth), "health"); private static readonly FieldInfo? MaxHealthField = AccessTools.Field(typeof(PlayerHealth), "maxHealth"); private static readonly FieldInfo? DeathHeadField = AccessTools.Field(typeof(PlayerAvatar), "playerDeathHead"); private static readonly FieldInfo? InTruckField = AccessTools.Field(typeof(RoomVolumeCheck), "inTruck"); internal static bool IsLiving(PlayerAvatar? player) { if ((Object)(object)player == (Object)null || !((Component)player).gameObject.activeInHierarchy || ReadBool(DisabledField, player) || ReadBool(DeadField, player) || ReadBool(SpectatingField, player)) { return false; } if (!((Object)(object)player.playerHealth == (Object)null) && HealthField?.GetValue(player.playerHealth) is int num) { return num > 0; } return true; } internal static bool IsInTruck(PlayerAvatar? player) { if ((Object)(object)player?.RoomVolumeCheck != (Object)null) { return ReadBool(InTruckField, player.RoomVolumeCheck); } return false; } internal static bool HasDeathHead(PlayerAvatar? player) { if ((Object)(object)player != (Object)null) { object? obj = DeathHeadField?.GetValue(player); PlayerDeathHead val = (PlayerDeathHead)((obj is PlayerDeathHead) ? obj : null); if (val != null) { return (Object)(object)val != (Object)null; } } return false; } internal static bool TryGetCurrentHealth(PlayerAvatar? player, out int health) { health = 0; if ((Object)(object)player?.playerHealth != (Object)null && HealthField?.GetValue(player.playerHealth) is int num) { return (health = num) >= 0; } return false; } internal static bool SetHealthSynchronized(PlayerAvatar? player, int health) { //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) PlayerHealth val = player?.playerHealth; if ((Object)(object)val == (Object)null) { return false; } int num = ((MaxHealthField?.GetValue(val) is int num2 && num2 > 0) ? num2 : Math.Max(health, 1)); int num3 = Math.Clamp(health, 1, Math.Max(1, num)); if (SemiFunc.IsMultiplayer()) { PhotonView val2 = ((Component)val).GetComponent() ?? ((player != null) ? ((Component)player).GetComponent() : null); if ((Object)(object)val2 == (Object)null) { return false; } val2.RPC("UpdateHealthRPC", (RpcTarget)0, new object[4] { num3, num, false, false }); return true; } val.UpdateHealthRPC(num3, num, false, false, default(PhotonMessageInfo)); return true; } private static bool ReadBool(FieldInfo? field, object instance) { object obj = field?.GetValue(instance); 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; } } internal static class PlayerIdentity { internal static string SteamId(PlayerAvatar? player) { if ((Object)(object)player == (Object)null) { return string.Empty; } try { string text = SemiFunc.PlayerGetSteamID(player); if (!string.IsNullOrEmpty(text)) { return text; } } catch { } try { PhotonView photonView = player.photonView; object obj2; if (photonView == null) { obj2 = null; } else { Player owner = photonView.Owner; obj2 = ((owner != null) ? owner.UserId : null); } if (obj2 == null) { obj2 = string.Empty; } return (string)obj2; } catch { return string.Empty; } } internal static string Name(PlayerAvatar? player) { if ((Object)(object)player == (Object)null) { return string.Empty; } try { return SemiFunc.PlayerGetName(player) ?? string.Empty; } catch { try { PhotonView photonView = player.photonView; object obj2; if (photonView == null) { obj2 = null; } else { Player owner = photonView.Owner; obj2 = ((owner != null) ? owner.NickName : null); } if (obj2 == null) { obj2 = string.Empty; } return (string)obj2; } catch { return string.Empty; } } } } internal static class StageFluxCompatibility { private const string CarrierTypeName = "REPOJP.StagePhysicsEvents.StagePhysicsEffectCarrierMarker"; private static Type? _carrierType; private static bool _resolved; internal static void MarkInternalCarrier(GameObject instance) { if ((Object)(object)instance == (Object)null) { return; } Type type = ResolveCarrierType(); if (type == null || (Object)(object)instance.GetComponent(type) != (Object)null) { return; } try { instance.AddComponent(type); } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Stage Flux carrier marker was not added: " + ex.Message)); } } private static Type? ResolveCarrierType() { if (_resolved) { return _carrierType; } _resolved = true; if (!Chainloader.PluginInfos.TryGetValue("REPOJP.StagePhysicsEvents", out var value)) { return null; } _carrierType = ((object)value.Instance).GetType().Assembly.GetType("REPOJP.StagePhysicsEvents.StagePhysicsEffectCarrierMarker", throwOnError: false); if (_carrierType != null && !typeof(Component).IsAssignableFrom(_carrierType)) { _carrierType = null; } return _carrierType; } } internal readonly struct ResolvedRolePrefab { internal GameObject Prefab { get; } internal string ResourcePath { get; } internal VanillaGrenadeKind GrenadeKind { get; } internal ResolvedRolePrefab(GameObject prefab, string resourcePath, VanillaGrenadeKind grenadeKind = VanillaGrenadeKind.None) { Prefab = prefab; ResourcePath = resourcePath; GrenadeKind = grenadeKind; } } internal enum VanillaGrenadeKind { None, Explosive, Stun, Shockwave, DuctTaped } internal sealed class VanillaRolePrefabResolver { private const int MaximumResolveAttempts = 5; private readonly List _grenades = new List(); private ResolvedRolePrefab _healOrb; private ResolvedRolePrefab _starProjectile; private ResolvedRolePrefab _rollProjectile; private ResolvedRolePrefab _zeroGravityProjectile; private ResolvedRolePrefab _voidProjectile; private PrefabRef? _voidImpactPrefab; private ResolvedRolePrefab _wizardStaff; private readonly List _uraniumValuables = new List(); private bool _grenadesResolved; private int _healOrbResolveAttempts; private bool _healOrbResolveFailed; private int _starProjectileResolveAttempts; private bool _starProjectileResolveFailed; private int _rollProjectileResolveAttempts; private bool _rollProjectileResolveFailed; private int _zeroGravityProjectileResolveAttempts; private bool _zeroGravityProjectileResolveFailed; private int _voidProjectileResolveAttempts; private bool _voidProjectileResolveFailed; private int _wizardStaffResolveAttempts; private bool _wizardStaffResolveFailed; private int _uraniumResolveAttempts; private bool _uraniumResolveFailed; internal bool TryGetRandomUraniumValuable(out ResolvedRolePrefab resolved) { if (_uraniumValuables.Count == 0 && !_uraniumResolveFailed) { ResolveUraniumValuables(); } if (_uraniumValuables.Count > 0) { resolved = _uraniumValuables[Random.Range(0, _uraniumValuables.Count)]; return true; } _uraniumResolveAttempts++; if (_uraniumResolveAttempts >= 5) { _uraniumResolveFailed = true; StageRolesPlugin.ModLogger.LogWarning((object)"Vanilla uranium valuable could not be resolved; Stinker is unavailable."); } resolved = default(ResolvedRolePrefab); return false; } internal bool TryGetRandomGrenade(StageRolesConfig config, out ResolvedRolePrefab resolved) { ResolveGrenades(); List list = new List(); foreach (ResolvedRolePrefab grenade in _grenades) { if (grenade.GrenadeKind switch { VanillaGrenadeKind.Explosive => config.BomberExplosiveEnabled.Value, VanillaGrenadeKind.Stun => config.BomberStunEnabled.Value, VanillaGrenadeKind.Shockwave => config.BomberShockwaveEnabled.Value, VanillaGrenadeKind.DuctTaped => config.BomberDuctTapedEnabled.Value, _ => false, }) { list.Add(grenade); } } if (list.Count == 0) { resolved = default(ResolvedRolePrefab); return false; } resolved = list[Random.Range(0, list.Count)]; return true; } internal bool TryGetHealOrb(out ResolvedRolePrefab resolved) { if ((Object)(object)_healOrb.Prefab != (Object)null && !string.IsNullOrEmpty(_healOrb.ResourcePath)) { resolved = _healOrb; return true; } if (_healOrbResolveFailed) { resolved = default(ResolvedRolePrefab); return false; } Item val = Resources.Load("Items/Item Orb Heal") ?? Resources.Load("Items/Removed Items/Item Orb Heal"); if ((Object)(object)val != (Object)null && TryGetVanillaPrefab(val, out var resolved2) && IsHealOrb(resolved2.Prefab)) { CacheHealOrb(resolved2); } else { foreach (Item item in EnumerateItems()) { if (TryGetVanillaPrefab(item, out var resolved3) && IsHealOrb(resolved3.Prefab)) { CacheHealOrb(resolved3); break; } } } resolved = _healOrb; if ((Object)(object)resolved.Prefab != (Object)null && !string.IsNullOrEmpty(resolved.ResourcePath)) { return true; } _healOrbResolveAttempts++; if (_healOrbResolveAttempts >= 5) { _healOrbResolveFailed = true; StageRolesPlugin.ModLogger.LogWarning((object)"Vanilla Heal Orb could not be resolved; Medic is unavailable."); } return false; } internal bool TryGetStarProjectile(out ResolvedRolePrefab resolved) { if (IsResolved(_starProjectile)) { resolved = _starProjectile; return true; } if (_starProjectileResolveFailed) { resolved = default(ResolvedRolePrefab); return false; } LevelValuables[] array = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < array.Length; i++) { foreach (PrefabRef item in EnumerateValuableRefs(array[i])) { if (TryGetVanillaPrefab(item, out var resolved2)) { ValuableStarWand componentInChildren = resolved2.Prefab.GetComponentInChildren(true); if (!((Object)(object)componentInChildren == (Object)null) && TryGetVanillaPrefab(componentInChildren.bulletPrefab, out var resolved3) && !((Object)(object)resolved3.Prefab.GetComponentInChildren(true) == (Object)null)) { _starProjectile = resolved3; StageRolesPlugin.ModLogger.LogInfo((object)("Resolved vanilla Star Wand projectile prefab: " + resolved3.ResourcePath)); break; } } } if (IsResolved(_starProjectile)) { break; } } resolved = _starProjectile; if (IsResolved(resolved)) { return true; } if (++_starProjectileResolveAttempts >= 5) { _starProjectileResolveFailed = true; StageRolesPlugin.ModLogger.LogWarning((object)"Vanilla Star Wand projectile could not be resolved; Mage star casts are unavailable."); } return false; } internal bool TryGetWizardStaff(out ResolvedRolePrefab resolved) { if (IsResolved(_wizardStaff)) { resolved = _wizardStaff; return true; } if (_wizardStaffResolveFailed) { resolved = default(ResolvedRolePrefab); return false; } LevelValuables[] array = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < array.Length; i++) { foreach (PrefabRef item in EnumerateValuableRefs(array[i])) { if (TryGetVanillaPrefab(item, out var resolved2)) { ValuableWizardStaff componentInChildren = resolved2.Prefab.GetComponentInChildren(true); if (!((Object)(object)componentInChildren?.semiLaser == (Object)null) && !((Object)(object)componentInChildren.laserTransform == (Object)null)) { _wizardStaff = resolved2; StageRolesPlugin.ModLogger.LogInfo((object)("Resolved vanilla Wizard Staff prefab: " + resolved2.ResourcePath)); break; } } } if (IsResolved(_wizardStaff)) { break; } } resolved = _wizardStaff; if (IsResolved(resolved)) { return true; } if (++_wizardStaffResolveAttempts >= 5) { _wizardStaffResolveFailed = true; StageRolesPlugin.ModLogger.LogWarning((object)"Vanilla Wizard Staff could not be resolved; Mage beam casts are unavailable."); } return false; } internal bool TryGetRollProjectile(out ResolvedRolePrefab resolved) { return TryGetStaffProjectile(typeof(ItemStaffTorque), "Roll Staff", ref _rollProjectile, ref _rollProjectileResolveAttempts, ref _rollProjectileResolveFailed, out resolved); } internal bool TryGetZeroGravityProjectile(out ResolvedRolePrefab resolved) { return TryGetStaffProjectile(typeof(ItemStaffZeroGravity), "Zero Gravity Staff", ref _zeroGravityProjectile, ref _zeroGravityProjectileResolveAttempts, ref _zeroGravityProjectileResolveFailed, out resolved); } internal bool TryGetVoidProjectile(out ResolvedRolePrefab resolved) { return TryGetStaffProjectile(typeof(ItemStaffVoid), "Void Staff", ref _voidProjectile, ref _voidProjectileResolveAttempts, ref _voidProjectileResolveFailed, out resolved); } internal bool TryGetVoidImpactPrefab(out PrefabRef impactPrefab) { TryGetVoidProjectile(out var _); if (_voidImpactPrefab != null && ((PrefabRef)(object)_voidImpactPrefab).IsValid() && !string.IsNullOrEmpty(((PrefabRef)(object)_voidImpactPrefab).ResourcePath)) { impactPrefab = _voidImpactPrefab; return true; } impactPrefab = null; return false; } private bool TryGetStaffProjectile(Type staffType, string displayName, ref ResolvedRolePrefab cached, ref int resolveAttempts, ref bool resolveFailed, out ResolvedRolePrefab resolved) { if (IsResolved(cached)) { resolved = cached; return true; } if (resolveFailed) { resolved = default(ResolvedRolePrefab); return false; } foreach (Item item in EnumerateItems()) { if (TryGetVanillaPrefab(item, out var resolved2)) { Component componentInChildren = resolved2.Prefab.GetComponentInChildren(staffType, true); ItemStaffTorque val = (ItemStaffTorque)(object)((componentInChildren is ItemStaffTorque) ? componentInChildren : null); PrefabRef val3; if (val == null) { ItemStaffZeroGravity val2 = (ItemStaffZeroGravity)(object)((componentInChildren is ItemStaffZeroGravity) ? componentInChildren : null); val3 = ((val2 != null) ? val2.projectilePrefab : ((ItemStaffVoid)(((componentInChildren is ItemStaffVoid) ? componentInChildren : null)?)).projectilePrefab); } else { val3 = val.projectilePrefab; } PrefabRef val4 = val3; ItemStaffVoid val5 = (ItemStaffVoid)(object)((componentInChildren is ItemStaffVoid) ? componentInChildren : null); if (val5 != null && val5.prefabToInstantiateOnHit != null && ((PrefabRef)(object)val5.prefabToInstantiateOnHit).IsValid()) { _voidImpactPrefab = val5.prefabToInstantiateOnHit; } if (val4 != null && TryGetVanillaPrefab(val4, out var resolved3) && !((Object)(object)resolved3.Prefab.GetComponentInChildren(true) == (Object)null)) { cached = resolved3; StageRolesPlugin.ModLogger.LogInfo((object)("Resolved vanilla " + displayName + " projectile prefab: " + resolved3.ResourcePath)); break; } } } resolved = cached; if (IsResolved(resolved)) { return true; } if (++resolveAttempts >= 5) { resolveFailed = true; StageRolesPlugin.ModLogger.LogWarning((object)("Vanilla " + displayName + " projectile could not be resolved; Mage " + displayName + " casts are unavailable.")); } return false; } private void CacheHealOrb(ResolvedRolePrefab candidate) { _healOrb = candidate; StageRolesPlugin.ModLogger.LogInfo((object)("Resolved vanilla Heal Orb prefab: " + candidate.ResourcePath)); } private static bool IsHealOrb(GameObject prefab) { if ((Object)(object)prefab != (Object)null && (Object)(object)prefab.GetComponentInChildren(true) != (Object)null && (Object)(object)prefab.GetComponentInChildren(true) != (Object)null) { return (Object)(object)prefab.GetComponentInChildren(true) != (Object)null; } return false; } private void ResolveGrenades() { if (_grenadesResolved) { return; } _grenadesResolved = true; (Type, VanillaGrenadeKind)[] obj = new(Type, VanillaGrenadeKind)[4] { (typeof(ItemGrenadeExplosive), VanillaGrenadeKind.Explosive), (typeof(ItemGrenadeStun), VanillaGrenadeKind.Stun), (typeof(ItemGrenadeShockwave), VanillaGrenadeKind.Shockwave), (typeof(ItemGrenadeDuctTaped), VanillaGrenadeKind.DuctTaped) }; HashSet hashSet = new HashSet(StringComparer.Ordinal); (Type, VanillaGrenadeKind)[] array = obj; for (int i = 0; i < array.Length; i++) { var (type, grenadeKind) = array[i]; foreach (Item item in EnumerateItems()) { if (TryGetVanillaPrefab(item, out var resolved) && !((Object)(object)resolved.Prefab.GetComponentInChildren(type, true) == (Object)null) && !((Object)(object)resolved.Prefab.GetComponentInChildren(true) == (Object)null) && !((Object)(object)resolved.Prefab.GetComponentInChildren(true) == (Object)null) && hashSet.Add(resolved.ResourcePath)) { _grenades.Add(new ResolvedRolePrefab(resolved.Prefab, resolved.ResourcePath, grenadeKind)); break; } } } StageRolesPlugin.ModLogger.LogInfo((object)$"Resolved {_grenades.Count}/4 vanilla Bomber grenade prefabs."); } private void ResolveUraniumValuables() { HashSet hashSet = new HashSet(StringComparer.Ordinal); LevelValuables[] array = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < array.Length; i++) { foreach (PrefabRef item in EnumerateValuableRefs(array[i])) { if (TryGetVanillaPrefab(item, out var resolved) && hashSet.Add(resolved.ResourcePath) && !((Object)(object)resolved.Prefab.GetComponentInChildren(true) == (Object)null) && !((Object)(object)resolved.Prefab.GetComponentInChildren(true) == (Object)null) && !((Object)(object)resolved.Prefab.GetComponentInChildren(true) == (Object)null)) { _uraniumValuables.Add(resolved); } } } if (_uraniumValuables.Count > 0) { StageRolesPlugin.ModLogger.LogInfo((object)$"Resolved {_uraniumValuables.Count} vanilla uranium valuable prefab(s)."); } } private static IEnumerable EnumerateItems() { HashSet seen = new HashSet(); if ((Object)(object)StatsManager.instance != (Object)null) { foreach (Item value in StatsManager.instance.itemDictionary.Values) { if ((Object)(object)value != (Object)null && seen.Add(value)) { yield return value; } } } Item[] array = Resources.FindObjectsOfTypeAll(); foreach (Item val in array) { if ((Object)(object)val != (Object)null && seen.Add(val)) { yield return val; } } } private static IEnumerable EnumerateValuableRefs(LevelValuables preset) { List[] array = new List[7] { preset.tiny, preset.small, preset.medium, preset.big, preset.wide, preset.tall, preset.veryTall }; List[] array2 = array; foreach (List list in array2) { if (list == null) { continue; } foreach (PrefabRef item in list) { if (item != null) { yield return item; } } } } private static bool TryGetVanillaPrefab(Item item, out ResolvedRolePrefab resolved) { try { PrefabRef prefab = item.prefab; if (prefab == null || (Object)(object)((PrefabRef)(object)prefab).Bundle != (Object)null || !((PrefabRef)(object)prefab).IsValid() || string.IsNullOrEmpty(((PrefabRef)(object)prefab).ResourcePath) || (Object)(object)((PrefabRef)(object)prefab).Prefab == (Object)null) { resolved = default(ResolvedRolePrefab); return false; } resolved = new ResolvedRolePrefab(((PrefabRef)(object)prefab).Prefab, ((PrefabRef)(object)prefab).ResourcePath); return true; } catch { resolved = default(ResolvedRolePrefab); return false; } } private static bool TryGetVanillaPrefab(PrefabRef prefabRef, out ResolvedRolePrefab resolved) { try { if (prefabRef == null || (Object)(object)((PrefabRef)(object)prefabRef).Bundle != (Object)null || !((PrefabRef)(object)prefabRef).IsValid() || string.IsNullOrEmpty(((PrefabRef)(object)prefabRef).ResourcePath) || (Object)(object)((PrefabRef)(object)prefabRef).Prefab == (Object)null) { resolved = default(ResolvedRolePrefab); return false; } resolved = new ResolvedRolePrefab(((PrefabRef)(object)prefabRef).Prefab, ((PrefabRef)(object)prefabRef).ResourcePath); return true; } catch { resolved = default(ResolvedRolePrefab); return false; } } private static bool IsResolved(ResolvedRolePrefab resolved) { if ((Object)(object)resolved.Prefab != (Object)null) { return !string.IsNullOrEmpty(resolved.ResourcePath); } return false; } } internal static class UpgradeService { internal static bool Ready { get { if ((Object)(object)PunManager.instance != (Object)null) { return (Object)(object)StatsManager.instance != (Object)null; } return false; } } internal static void SetLevels(string steamId, IReadOnlyList targets) { if (!Ready || string.IsNullOrEmpty(steamId)) { return; } Dictionary dictionary; try { dictionary = StatsManager.instance.FetchPlayerUpgrades(steamId); } catch (Exception ex) { StageRolesPlugin.ModLogger.LogWarning((object)("Could not read upgrade levels for player " + steamId + ": " + ex.Message)); return; } foreach (UpgradeGrant target in targets) { int num = Math.Max(0, target.Level); int valueOrDefault = dictionary.GetValueOrDefault(target.DictionaryName, 0); int num2 = num - valueOrDefault; if (num2 != 0) { try { SendUpgradeDelta(steamId, target.CommandName, num2); } catch (Exception ex2) { StageRolesPlugin.ModLogger.LogWarning((object)($"Could not set {target.CommandName} to {num} for player " + steamId + ": " + ex2.Message)); } } } } private static void SendUpgradeDelta(string steamId, string commandName, int delta) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) if (SemiFunc.IsMultiplayer()) { PhotonView component = ((Component)PunManager.instance).GetComponent(); if ((Object)(object)component == (Object)null) { throw new InvalidOperationException("PunManager has no PhotonView."); } component.RPC("TesterUpgradeCommandRPC", (RpcTarget)0, new object[3] { steamId, commandName, delta }); } else { PunManager.instance.TesterUpgradeCommandRPC(steamId, commandName, delta, default(PhotonMessageInfo)); } } } internal sealed class BomberRoleRuntime { private readonly struct GrenadeOwner { internal PhysGrabObject Grenade { get; } internal string SteamId { get; } internal int PlayerViewId { get; } internal GrenadeOwner(PhysGrabObject grenade, string steamId, int playerViewId) { Grenade = grenade; SteamId = steamId; PlayerViewId = playerViewId; } } private readonly MonoBehaviour _coroutineOwner; private readonly StageRolesConfig _config; private readonly VanillaRolePrefabResolver _resolver; private readonly Dictionary> _instances = new Dictionary>(StringComparer.Ordinal); private readonly Dictionary _grenadeOwners = new Dictionary(); private readonly Dictionary<(int GrenadeId, int PlayerViewId), float> _deniedReleaseUntil = new Dictionary<(int, int), float>(); private int _generation; private bool _active; internal BomberRoleRuntime(MonoBehaviour coroutineOwner, StageRolesConfig config, VanillaRolePrefabResolver resolver) { _coroutineOwner = coroutineOwner; _config = config; _resolver = resolver; } internal void Begin() { Stop(); _active = true; _generation++; } internal void Tick(RoleAssignment assignment) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002d: 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_0039: 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_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_00ec: Unknown result type (might be due to invalid IL or missing references) PlayerAvatar player = assignment.Player; if (!_active || (Object)(object)player == (Object)null || !PlayerState.IsLiving(player)) { return; } Vector3 position = ((Component)player).transform.position; Vector3 val = position - assignment.PreviousPosition; assignment.PreviousPosition = position; val.y = 0f; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude > 4f) { return; } assignment.TravelDistance += magnitude; if (!(assignment.TravelDistance < _config.ClampedBomberDistance) && (_config.BomberAllowTruckSpawns.Value || !PlayerState.IsInTruck(player))) { assignment.TravelDistance -= _config.ClampedBomberDistance; if (HasCapacity(assignment.SteamId) && _resolver.TryGetRandomGrenade(_config, out var resolved)) { Vector3 position2 = FindFloorPosition(position); _coroutineOwner.StartCoroutine(SpawnAndArm(assignment.SteamId, player, position2, resolved, _generation)); } } } internal void Stop() { _active = false; _generation++; foreach (List value in _instances.Values) { foreach (GameObject item in value) { DestroyNetworkObject(item); } } _instances.Clear(); _grenadeOwners.Clear(); _deniedReleaseUntil.Clear(); } internal void RemovePlayer(string steamId) { if (!_instances.Remove(steamId, out List value)) { return; } HashSet hashSet = new HashSet(); foreach (GameObject item in value) { if ((Object)(object)item != (Object)null) { PhysGrabObject val = item.GetComponent() ?? item.GetComponentInChildren(true); if ((Object)(object)val != (Object)null) { hashSet.Add(((Object)val).GetInstanceID()); _grenadeOwners.Remove(((Object)val).GetInstanceID()); } } DestroyNetworkObject(item); } if (hashSet.Count <= 0) { return; } List<(int, int)> list = new List<(int, int)>(); foreach (var key in _deniedReleaseUntil.Keys) { if (hashSet.Contains(key.GrenadeId)) { list.Add(key); } } foreach (var item2 in list) { _deniedReleaseUntil.Remove(item2); } } internal bool AllowGrabStart(PhysGrabObject grenade, int playerViewId, PhotonMessageInfo messageInfo, bool requesterIsBomber) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) if (!_active || (Object)(object)grenade == (Object)null || !_grenadeOwners.TryGetValue(((Object)grenade).GetInstanceID(), out var value) || value.Grenade != grenade) { return true; } PhysGrabber val = ResolveGrabber(playerViewId); if ((Object)(object)val?.photonView == (Object)null || !SemiFunc.OwnerOnlyRPC(messageInfo, val.photonView)) { return true; } string text = PlayerIdentity.SteamId(val.playerAvatar); if (playerViewId == value.PlayerViewId || (!string.IsNullOrEmpty(value.SteamId) && string.Equals(text, value.SteamId, StringComparison.Ordinal)) || requesterIsBomber) { return true; } PruneDeniedReleases(); _deniedReleaseUntil[(((Object)grenade).GetInstanceID(), playerViewId)] = Time.time + 2f; try { PhotonView val2 = ((Component)grenade).GetComponent() ?? ((Component)grenade).GetComponentInParent() ?? ((Component)grenade).GetComponentInChildren(true); int num = (((Object)(object)val2 != (Object)null) ? val2.ViewID : (-1)); val.OverrideGrabRelease(num, 0.5f); StageRolesPlugin.ModLogger.LogDebug((object)("Rejected a non-owner grab of Bomber grenade by " + text + ".")); } catch (Exception ex) { StageRolesPlugin.ModLogger.LogWarning((object)("Could not release a non-owner Bomber grenade grab: " + ex.Message)); } return false; } internal bool AllowGrabEnd(PhysGrabObject grenade, int playerViewId, PhotonMessageInfo messageInfo) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)grenade == (Object)null) { return true; } (int, int) key = (((Object)grenade).GetInstanceID(), playerViewId); if (!_deniedReleaseUntil.ContainsKey(key)) { return true; } PhysGrabber val = ResolveGrabber(playerViewId); if ((Object)(object)val?.photonView == (Object)null || !SemiFunc.OwnerOnlyRPC(messageInfo, val.photonView)) { return true; } _deniedReleaseUntil.Remove(key); return false; } private bool HasCapacity(string steamId) { if (!_instances.TryGetValue(steamId, out List value)) { return true; } value.RemoveAll((GameObject instance) => (Object)(object)instance == (Object)null); return value.Count < _config.ClampedBomberMaximum; } private IEnumerator SpawnAndArm(string steamId, PlayerAvatar player, Vector3 position, ResolvedRolePrefab resolved, int generation) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) GameObject instance; try { instance = (SemiFunc.IsMultiplayer() ? PhotonNetwork.Instantiate(resolved.ResourcePath, position, Quaternion.identity, (byte)0, (object[])null) : Object.Instantiate(resolved.Prefab, position, Quaternion.identity)); StageFluxCompatibility.MarkInternalCarrier(instance); if (!_instances.TryGetValue(steamId, out List value)) { value = new List(); _instances[steamId] = value; } value.Add(instance); PhysGrabObject val = instance.GetComponent() ?? instance.GetComponentInChildren(true); if ((Object)(object)val != (Object)null) { Dictionary grenadeOwners = _grenadeOwners; int instanceID = ((Object)val).GetInstanceID(); PhotonView photonView = player.photonView; grenadeOwners[instanceID] = new GrenadeOwner(val, steamId, (photonView != null) ? photonView.ViewID : 0); } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogWarning((object)("Could not place a Bomber grenade: " + ex.Message)); yield break; } yield return null; if (!_active || generation != _generation || (Object)(object)instance == (Object)null || (Object)(object)player == (Object)null) { DestroyNetworkObject(instance); yield break; } try { ItemGrenade val2 = instance.GetComponent() ?? instance.GetComponentInChildren(true); ItemToggle val3 = instance.GetComponent() ?? instance.GetComponentInChildren(true); if ((Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null) { DestroyNetworkObject(instance); yield break; } val2.isSpawnedGrenade = true; val2.SetActivator(player); PhotonView photonView2 = player.photonView; val3.ToggleItem(true, (photonView2 != null) ? photonView2.ViewID : 0); StageRolesPlugin.ModLogger.LogDebug((object)("Bomber placed an armed grenade for player " + steamId + ".")); } catch (Exception ex2) { StageRolesPlugin.ModLogger.LogWarning((object)("Could not arm a Bomber grenade: " + ex2.Message)); DestroyNetworkObject(instance); } } private static PhysGrabber? ResolveGrabber(int playerViewId) { try { PhotonView val = PhotonView.Find(playerViewId); return ((Object)(object)val != (Object)null) ? ((Component)val).GetComponent() : null; } catch { return null; } } private void PruneDeniedReleases() { if (_deniedReleaseUntil.Count == 0) { return; } List<(int, int)> list = new List<(int, int)>(); foreach (KeyValuePair<(int, int), float> item in _deniedReleaseUntil) { if (Time.time > item.Value) { list.Add(item.Key); } } foreach (var item2 in list) { _deniedReleaseUntil.Remove(item2); } } private static Vector3 FindFloorPosition(Vector3 playerPosition) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_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) RaycastHit val = default(RaycastHit); if (!Physics.Raycast(playerPosition + Vector3.up * 1.5f, Vector3.down, ref val, 5f, -1, (QueryTriggerInteraction)1)) { return playerPosition + Vector3.up * 0.08f; } return ((RaycastHit)(ref val)).point + Vector3.up * 0.08f; } private static void DestroyNetworkObject(GameObject? instance) { if ((Object)(object)instance == (Object)null) { return; } try { PhotonView val = instance.GetComponent() ?? instance.GetComponentInChildren(true); if (SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && (Object)(object)val != (Object)null && val.ViewID != 0) { PhotonNetwork.Destroy(((Component)val).gameObject); } else { Object.Destroy((Object)(object)instance); } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Bomber grenade cleanup deferred to scene unload: " + ex.Message)); } } } internal static class OrbCarrierSafety { internal static Vector3 HiddenPosition => new Vector3(0f, -1000f, 0f); internal static void DisablePhysicalInteraction(GameObject carrier) { //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) Collider[] componentsInChildren = carrier.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { componentsInChildren[i].enabled = false; } Rigidbody[] componentsInChildren2 = carrier.GetComponentsInChildren(true); foreach (Rigidbody val in componentsInChildren2) { if (!val.isKinematic) { val.velocity = Vector3.zero; val.angularVelocity = Vector3.zero; } val.isKinematic = true; val.detectCollisions = false; } } internal static void HidePresentation(GameObject carrier) { Renderer[] componentsInChildren = carrier.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { componentsInChildren[i].enabled = false; } Light[] componentsInChildren2 = carrier.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren2.Length; i++) { ((Behaviour)componentsInChildren2[i]).enabled = false; } ParticleSystem[] componentsInChildren3 = carrier.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren3.Length; i++) { componentsInChildren3[i].Stop(true, (ParticleSystemStopBehavior)0); } AudioSource[] componentsInChildren4 = carrier.GetComponentsInChildren(true); foreach (AudioSource obj in componentsInChildren4) { obj.Stop(); ((Behaviour)obj).enabled = false; } } } internal sealed class HiddenOrbCarrierPresentation : MonoBehaviour { private void LateUpdate() { OrbCarrierSafety.HidePresentation(((Component)this).gameObject); } } internal sealed class MedicRoleRuntime { private sealed class FollowOrb { internal string OwnerSteamId { get; } internal PlayerAvatar Player { get; } internal GameObject Orb { get; } internal float NextHealAt { get; set; } internal FollowOrb(string ownerSteamId, PlayerAvatar player, GameObject orb, float nextHealAt) { OwnerSteamId = ownerSteamId; Player = player; Orb = orb; NextHealAt = nextHealAt; } } private static readonly FieldInfo? HealthField = AccessTools.Field(typeof(PlayerHealth), "health"); private static readonly FieldInfo? MaxHealthField = AccessTools.Field(typeof(PlayerHealth), "maxHealth"); private readonly MonoBehaviour _coroutineOwner; private readonly StageRolesConfig _config; private readonly VanillaRolePrefabResolver _resolver; private readonly Dictionary _orbs = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _healingUsed = new Dictionary(StringComparer.Ordinal); private readonly HashSet _exhaustionLogged = new HashSet(StringComparer.Ordinal); private int _generation; private bool _active; internal MedicRoleRuntime(MonoBehaviour coroutineOwner, StageRolesConfig config, VanillaRolePrefabResolver resolver) { _coroutineOwner = coroutineOwner; _config = config; _resolver = resolver; } internal void Begin(IEnumerable assignments) { StopOrbs(); _active = true; _generation++; HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (RoleAssignment assignment in assignments) { if (assignment.Role == StageRole.Medic) { hashSet.Add(assignment.SteamId); if (!_healingUsed.ContainsKey(assignment.SteamId)) { _healingUsed[assignment.SteamId] = 0; } EnsureOrb(assignment); } } List list = new List(); foreach (string key in _healingUsed.Keys) { if (!hashSet.Contains(key)) { list.Add(key); } } foreach (string item in list) { _healingUsed.Remove(item); _exhaustionLogged.Remove(item); } } internal void Tick(IEnumerable assignments) { //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_0125: 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 (!_active) { return; } IReadOnlyList readOnlyList = (assignments as IReadOnlyList) ?? new List(assignments); foreach (RoleAssignment item in readOnlyList) { if (item.Role == StageRole.Medic) { EnsureOrb(item); } } List list = new List(); foreach (KeyValuePair orb in _orbs) { FollowOrb value = orb.Value; if ((Object)(object)value.Player == (Object)null || (Object)(object)value.Orb == (Object)null) { DestroyOrb(value.Orb); list.Add(orb.Key); continue; } Vector3 hiddenPosition = OrbCarrierSafety.HiddenPosition; PhysGrabObject val = value.Orb.GetComponent() ?? value.Orb.GetComponentInChildren(true); if ((Object)(object)val?.rb != (Object)null) { val.rb.isKinematic = true; val.rb.detectCollisions = false; val.rb.position = hiddenPosition; } value.Orb.transform.position = hiddenPosition; OrbCarrierSafety.DisablePhysicalInteraction(value.Orb); OrbCarrierSafety.HidePresentation(value.Orb); HealNearbyPlayers(value, readOnlyList); ItemBattery val2 = value.Orb.GetComponent() ?? value.Orb.GetComponentInChildren(true); if ((Object)(object)val2 != (Object)null) { val2.autoDrain = false; val2.batteryLife = 100f; } } foreach (string item2 in list) { _orbs.Remove(item2); } } internal void Stop() { _active = false; _generation++; StopOrbs(); _healingUsed.Clear(); _exhaustionLogged.Clear(); } internal void RemovePlayer(string steamId) { if (_orbs.Remove(steamId, out FollowOrb value)) { DestroyOrb(value.Orb); } _healingUsed.Remove(steamId); _exhaustionLogged.Remove(steamId); } private void StopOrbs() { foreach (FollowOrb value in _orbs.Values) { DestroyOrb(value.Orb); } _orbs.Clear(); } private void EnsureOrb(RoleAssignment assignment) { //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_0050: 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 ((_orbs.TryGetValue(assignment.SteamId, out FollowOrb value) && (Object)(object)value.Orb != (Object)null) || (Object)(object)assignment.Player == (Object)null || !_resolver.TryGetHealOrb(out var resolved)) { return; } try { Vector3 hiddenPosition = OrbCarrierSafety.HiddenPosition; GameObject val = Object.Instantiate(resolved.Prefab, hiddenPosition, Quaternion.identity); ((Object)val).name = "RoleShuffle_MedicHealOrb"; ((Object)val).hideFlags = (HideFlags)61; StageFluxCompatibility.MarkInternalCarrier(val); val.AddComponent(); _orbs[assignment.SteamId] = new FollowOrb(assignment.SteamId, assignment.Player, val, Time.time + Mathf.Clamp(_config.MedicHealIntervalSeconds.Value, 0.1f, 30f)); OrbCarrierSafety.DisablePhysicalInteraction(val); OrbCarrierSafety.HidePresentation(val); _coroutineOwner.StartCoroutine(InitializeAfterStart(val, assignment.Player, _generation)); } catch (Exception ex) { StageRolesPlugin.ModLogger.LogWarning((object)("Could not create a Medic Heal Orb: " + ex.Message)); } } private IEnumerator InitializeAfterStart(GameObject orb, PlayerAvatar player, int generation) { yield return null; if (!_active || generation != _generation || (Object)(object)orb == (Object)null || (Object)(object)player == (Object)null) { DestroyOrb(orb); yield break; } try { ItemOrb val = orb.GetComponent() ?? orb.GetComponentInChildren(true); ItemToggle val2 = orb.GetComponent() ?? orb.GetComponentInChildren(true); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null) { DestroyOrb(orb); yield break; } val.targetEnemies = false; val.targetNonValuables = false; val.targetValuables = false; val.targetPlayers = false; ItemOrbHeal val3 = orb.GetComponent() ?? orb.GetComponentInChildren(true); if ((Object)(object)val3 != (Object)null) { ((Behaviour)val3).enabled = false; } ItemBattery val4 = orb.GetComponent() ?? orb.GetComponentInChildren(true); if ((Object)(object)val4 != (Object)null) { val4.autoDrain = false; val4.batteryLife = 100f; } val2.ToggleItem(true, 0); OrbCarrierSafety.DisablePhysicalInteraction(orb); OrbCarrierSafety.HidePresentation(orb); PhysGrabObject obj = orb.GetComponent() ?? orb.GetComponentInChildren(true); if (obj != null) { obj.PhysRidingDisabledSet(true); } StageRolesPlugin.ModLogger.LogDebug((object)("Medic Heal Orb activated for player " + PlayerIdentity.SteamId(player) + ".")); } catch (Exception ex) { StageRolesPlugin.ModLogger.LogWarning((object)("Could not activate a Medic Heal Orb: " + ex.Message)); DestroyOrb(orb); } } private void HealNearbyPlayers(FollowOrb follow, IReadOnlyList assignments) { //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_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_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) if (Time.time < follow.NextHealAt) { return; } follow.NextHealAt = Time.time + Mathf.Clamp(_config.MedicHealIntervalSeconds.Value, 0.1f, 30f); if (!PlayerState.IsLiving(follow.Player)) { return; } float num = Mathf.Clamp(_config.MedicHealRadius.Value, 1f, 30f); float num2 = num * num; int num3 = Mathf.Clamp(_config.MedicHealAmount.Value, 1, 100); int num4 = Mathf.Clamp(_config.MedicTotalHealingLimit.Value, 1, 10000); _healingUsed.TryGetValue(follow.OwnerSteamId, out var value); int num5 = Mathf.Max(0, num4 - value); if (num5 <= 0) { LogExhaustion(follow.OwnerSteamId, num4); return; } _exhaustionLogged.Remove(follow.OwnerSteamId); Vector3 position = ((Component)follow.Player).transform.position; foreach (RoleAssignment assignment in assignments) { if (string.Equals(assignment.SteamId, follow.OwnerSteamId, StringComparison.Ordinal)) { continue; } PlayerAvatar player = assignment.Player; if (!PlayerState.IsLiving(player) || (Object)(object)player.playerHealth == (Object)null) { continue; } Vector3 val = ((Component)player).transform.position - position; if (((Vector3)(ref val)).sqrMagnitude > num2) { continue; } try { int num6 = MissingHealth(player.playerHealth); int num7 = Mathf.Min(num3, Mathf.Min(num5, num6)); if (num7 > 0) { player.playerHealth.HealOther(num7, true); value += num7; num5 -= num7; _healingUsed[follow.OwnerSteamId] = value; if (num5 <= 0) { LogExhaustion(follow.OwnerSteamId, num4); break; } } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Medic healing skipped for " + assignment.SteamId + ": " + ex.Message)); } } } private static int MissingHealth(PlayerHealth playerHealth) { try { if (HealthField?.GetValue(playerHealth) is int num && MaxHealthField?.GetValue(playerHealth) is int num2) { return Mathf.Max(0, num2 - num); } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Medic health read failed: " + ex.Message)); } return int.MaxValue; } private void LogExhaustion(string ownerSteamId, int healingLimit) { if (_exhaustionLogged.Add(ownerSteamId)) { StageRolesPlugin.ModLogger.LogInfo((object)$"Medic {ownerSteamId} exhausted its {healingLimit} HP stage healing limit."); } } private static void DestroyOrb(GameObject? orb) { if ((Object)(object)orb == (Object)null) { return; } try { ItemToggle obj = orb.GetComponent() ?? orb.GetComponentInChildren(true); if (obj != null) { obj.ToggleItem(false, 0); } PhotonView val = orb.GetComponent() ?? orb.GetComponentInChildren(true); if (SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && (Object)(object)val != (Object)null && val.ViewID != 0) { PhotonNetwork.Destroy(((Component)val).gameObject); } else { Object.Destroy((Object)(object)orb); } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Medic Heal Orb cleanup deferred to scene unload: " + ex.Message)); } } } internal sealed class MageRoleRuntime { private delegate bool TryResolveProjectile(out ResolvedRolePrefab resolved); private const float BeamCarrierLifetimeSeconds = 4.25f; private readonly MonoBehaviour _coroutineOwner; private readonly StageRolesConfig _config; private readonly VanillaRolePrefabResolver _resolver; private readonly List _spawnedObjects = new List(); private int _generation; private float ClampedInterval => Mathf.Clamp(_config.MageCastIntervalSeconds.Value, 0.1f, 30f); internal MageRoleRuntime(MonoBehaviour coroutineOwner, StageRolesConfig config, VanillaRolePrefabResolver resolver) { _coroutineOwner = coroutineOwner; _config = config; _resolver = resolver; } internal void Begin(IReadOnlyList assignments) { Stop(); foreach (RoleAssignment assignment in assignments) { assignment.MageNextCastAt = 0f; } _resolver.TryGetStarProjectile(out var resolved); _resolver.TryGetRollProjectile(out resolved); _resolver.TryGetZeroGravityProjectile(out resolved); _resolver.TryGetVoidProjectile(out resolved); _resolver.TryGetWizardStaff(out resolved); } internal void Tick(RoleAssignment assignment) { _spawnedObjects.RemoveAll((GameObject instance) => (Object)(object)instance == (Object)null); if (!PlayerState.IsLiving(assignment.Player)) { assignment.MageNextCastAt = 0f; } } internal bool TryCast(RoleAssignment assignment, string spell) { //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_00b7: 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_0106: 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_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) if (!PlayerState.IsLiving(assignment.Player) || Time.time < assignment.MageNextCastAt) { return false; } int num = HealthCost(spell); if (num > 0 && (!PlayerState.TryGetCurrentHealth(assignment.Player, out var health) || health <= num)) { StageRolesPlugin.ModLogger.LogDebug((object)$"Mage {spell} cast was rejected because its {num} health cost would be fatal."); return false; } Vector3 forwardDirection = GetForwardDirection(assignment.Player); int num2 = spell switch { "star" => TryCastProjectile(assignment.Player, forwardDirection, "Star", _resolver.TryGetStarProjectile) ? 1 : 0, "roll" => TryCastProjectile(assignment.Player, forwardDirection, "Roll", _resolver.TryGetRollProjectile) ? 1 : 0, "gravity" => TryCastProjectile(assignment.Player, forwardDirection, "Gravity", _resolver.TryGetZeroGravityProjectile) ? 1 : 0, "void" => TryCastProjectile(assignment.Player, forwardDirection, "Void", _resolver.TryGetVoidProjectile) ? 1 : 0, "laser" => TryCastBeam(GetCastOrigin(assignment.Player, forwardDirection, 3f), forwardDirection) ? 1 : 0, _ => 0, }; if (num2 != 0) { ConsumeHealth(assignment.Player, num, spell); assignment.MageNextCastAt = Time.time + ClampedInterval; } return (byte)num2 != 0; } internal void Stop() { _generation++; foreach (GameObject spawnedObject in _spawnedObjects) { DestroyNetworkObject(spawnedObject); } _spawnedObjects.Clear(); } private int HealthCost(string spell) { return spell switch { "star" => Mathf.Clamp(_config.MageStarHealthCost.Value, 0, 100), "gravity" => Mathf.Clamp(_config.MageGravityHealthCost.Value, 0, 100), "roll" => Mathf.Clamp(_config.MageRollHealthCost.Value, 0, 100), "void" => Mathf.Clamp(_config.MageVoidHealthCost.Value, 0, 100), "laser" => Mathf.Clamp(_config.MageLaserHealthCost.Value, 0, 100), _ => 0, }; } private static void ConsumeHealth(PlayerAvatar player, int healthCost, string spell) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (healthCost <= 0) { return; } try { PlayerHealth playerHealth = player.playerHealth; if (playerHealth != null) { playerHealth.HurtOther(healthCost, Vector3.zero, false, -1, false); } StageRolesPlugin.ModLogger.LogDebug((object)$"Mage consumed {healthCost} health for the {spell} spell."); } catch (Exception ex) { StageRolesPlugin.ModLogger.LogWarning((object)("Mage " + spell + " health cost failed: " + ex.Message)); } } private bool TryCastProjectile(PlayerAvatar player, Vector3 direction, string spellName, TryResolveProjectile resolver) { //IL_0010: 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_001c: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) if (!resolver(out var resolved)) { return false; } try { GameObject val = SpawnNetworkObject(resolved, GetCastOrigin(player, direction, 0.8f), Quaternion.LookRotation(direction)); ((Object)val).name = "RoleShuffle_Mage" + spellName; StageFluxCompatibility.MarkInternalCarrier(val); SlowProjectile val2 = val.GetComponent() ?? val.GetComponentInChildren(true); if ((Object)(object)val2 == (Object)null) { DestroyNetworkObject(val); return false; } bool flag = string.Equals(spellName, "Void", StringComparison.Ordinal); if (flag && !_resolver.TryGetVoidImpactPrefab(out PrefabRef _)) { DestroyNetworkObject(val); StageRolesPlugin.ModLogger.LogWarning((object)"Mage Void cast was cancelled because the vanilla Void impact prefab was unavailable."); return false; } if (flag) { _resolver.TryGetVoidImpactPrefab(out PrefabRef impactPrefab2); val2.prefabToInstantiateOnHit = impactPrefab2; } if (flag || string.Equals(spellName, "Roll", StringComparison.Ordinal) || string.Equals(spellName, "Gravity", StringComparison.Ordinal)) { val2.investigateRadiusOnHit = 24f; } val2.SetSpawner(((Component)player).gameObject); val2.Launch(direction); _spawnedObjects.Add(val); StageRolesPlugin.ModLogger.LogDebug((object)("Mage fired the " + spellName + " spell.")); return true; } catch (Exception ex) { StageRolesPlugin.ModLogger.LogWarning((object)("Mage " + spellName + " cast failed: " + ex.Message)); return false; } } private bool TryCastBeam(Vector3 origin, Vector3 direction) { //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_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_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_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_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_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_0084: 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) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) if (!_resolver.TryGetWizardStaff(out var resolved)) { return false; } ValuableWizardStaff componentInChildren = resolved.Prefab.GetComponentInChildren(true); if ((Object)(object)componentInChildren?.laserTransform == (Object)null) { return false; } try { Transform transform = resolved.Prefab.transform; Quaternion val = Quaternion.Inverse(transform.rotation) * componentInChildren.laserTransform.rotation; Quaternion val2 = Quaternion.LookRotation(direction) * Quaternion.Inverse(val); Vector3 val3 = transform.InverseTransformPoint(componentInChildren.laserTransform.position); Vector3 position = origin - val2 * val3; GameObject val4 = SpawnNetworkObject(resolved, position, val2); ((Object)val4).name = "RoleShuffle_MageBeam"; StageFluxCompatibility.MarkInternalCarrier(val4); _spawnedObjects.Add(val4); int generation = _generation; _coroutineOwner.StartCoroutine(ActivateBeam(val4, generation)); return true; } catch (Exception ex) { StageRolesPlugin.ModLogger.LogWarning((object)("Mage beam cast failed: " + ex.Message)); return false; } } private IEnumerator ActivateBeam(GameObject carrier, int generation) { yield return null; if (!((Object)(object)carrier == (Object)null) && generation == _generation) { DisableBeamCarrierPhysics(carrier); ValuableWizardStaff val = carrier.GetComponent() ?? carrier.GetComponentInChildren(true); if ((Object)(object)val == (Object)null) { _spawnedObjects.Remove(carrier); DestroyNetworkObject(carrier); yield break; } val.StaffLaser(); StageRolesPlugin.ModLogger.LogDebug((object)"Mage fired a Wizard Staff beam."); yield return (object)new WaitForSeconds(4.25f); _spawnedObjects.Remove(carrier); DestroyNetworkObject(carrier); } } private static GameObject SpawnNetworkObject(ResolvedRolePrefab resolved, Vector3 position, Quaternion rotation) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (!SemiFunc.IsMultiplayer()) { return Object.Instantiate(resolved.Prefab, position, rotation); } return PhotonNetwork.Instantiate(resolved.ResourcePath, position, rotation, (byte)0, (object[])null); } private static void DisableBeamCarrierPhysics(GameObject carrier) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) Collider[] componentsInChildren = carrier.GetComponentsInChildren(true); foreach (Collider val in componentsInChildren) { if ((Object)(object)((Component)val).GetComponentInParent() == (Object)null) { val.enabled = false; } } Rigidbody[] componentsInChildren2 = carrier.GetComponentsInChildren(true); foreach (Rigidbody val2 in componentsInChildren2) { if (!val2.isKinematic) { val2.velocity = Vector3.zero; val2.angularVelocity = Vector3.zero; } val2.isKinematic = true; } } private static Vector3 GetCastOrigin(PlayerAvatar player, Vector3 direction, float forwardOffset) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) Transform val = player.playerAvatarVisuals?.headLookAtTransform; return (((Object)(object)val != (Object)null) ? val.position : (((Component)player).transform.position + Vector3.up)) + direction * forwardOffset; } private static Vector3 GetForwardDirection(PlayerAvatar player) { //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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_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_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: 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_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_00da: 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_00e3: 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) try { PlayerAvatar val = SemiFunc.PlayerGetLocal(); if (player == val && (Object)(object)player.localCamera != (Object)null) { Transform overrideTransform = player.localCamera.GetOverrideTransform(); if ((Object)(object)overrideTransform != (Object)null) { Vector3 result = overrideTransform.forward; if (((Vector3)(ref result)).sqrMagnitude > 0.001f) { result = overrideTransform.forward; result = ((Vector3)(ref result)).normalized; return result; } } } } catch { } Transform val2 = player.playerAvatarVisuals?.headLookAtTransform; Vector3 forward; if ((Object)(object)val2 != (Object)null) { forward = val2.forward; if (((Vector3)(ref forward)).sqrMagnitude > 0.001f) { forward = val2.forward; return ((Vector3)(ref forward)).normalized; } } Transform val3 = (((Object)(object)player.playerTransform != (Object)null) ? player.playerTransform : ((Component)player).transform); forward = val3.forward; if (!(((Vector3)(ref forward)).sqrMagnitude > 0.001f)) { return Vector3.forward; } forward = val3.forward; return ((Vector3)(ref forward)).normalized; } private static void DestroyNetworkObject(GameObject? instance) { if ((Object)(object)instance == (Object)null) { return; } try { PhotonView val = instance.GetComponent() ?? instance.GetComponentInChildren(true); if (SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && (Object)(object)val != (Object)null && val.ViewID != 0) { PhotonNetwork.Destroy(((Component)val).gameObject); } else { Object.Destroy((Object)(object)instance); } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Mage object cleanup was deferred: " + ex.Message)); } } } internal sealed class GamblerRoleRuntime { private static readonly FieldInfo? DollarValueCurrentField = AccessTools.Field(typeof(ValuableObject), "dollarValueCurrent"); private static readonly FieldInfo? DollarValueSetField = AccessTools.Field(typeof(ValuableObject), "dollarValueSet"); private static readonly FieldInfo? ExtractionPointsCompletedField = AccessTools.Field(typeof(RoundDirector), "extractionPointsCompleted"); private readonly StageRolesConfig _config; private readonly HashSet _wageredValuables = new HashSet(StringComparer.Ordinal); private bool _active; private int _lastDirectorId; private int _lastCompletionCount = -1; internal GamblerRoleRuntime(StageRolesConfig config) { _config = config; } internal void Begin() { _wageredValuables.Clear(); _lastDirectorId = 0; _lastCompletionCount = -1; _active = true; } internal void Stop() { _active = false; _wageredValuables.Clear(); _lastDirectorId = 0; _lastCompletionCount = -1; } internal void Tick(RoleAssignment assignment, RoleNotifier notifier) { if (!_active || !PlayerState.IsLiving(assignment.Player) || assignment.GamblerWagersUsed > 0 || (Object)(object)ValuableDirector.instance == (Object)null) { return; } foreach (ValuableObject valuable in ValuableDirector.instance.valuableList) { if (!((Object)(object)valuable == (Object)null) && ValueIsReady(valuable) && IsGrabbedBy(valuable, assignment)) { string text = ValuableId(valuable); if (!_wageredValuables.Contains(text) && TryApplyWager(valuable, assignment, text, notifier)) { break; } } } } internal void ExtractionCompleted(RoundDirector? director, IReadOnlyList assignments, RoleNotifier notifier) { if (!_active || (Object)(object)director == (Object)null) { return; } int instanceID = ((Object)director).GetInstanceID(); int num = ReadExtractionCompletionCount(director); if (instanceID == _lastDirectorId && num == _lastCompletionCount) { return; } _lastDirectorId = instanceID; _lastCompletionCount = num; int num2 = 0; foreach (RoleAssignment assignment in assignments) { if (assignment.Role == StageRole.Gambler && assignment.GamblerWagersUsed > 0) { assignment.GamblerWagersUsed = 0; notifier.Notify(assignment.Player, "GamblerReady"); num2++; } } StageRolesPlugin.ModLogger.LogInfo((object)($"Extraction #{num} restored the Gambler effect for " + $"{num2} player(s).")); } private bool TryApplyWager(ValuableObject valuable, RoleAssignment assignment, string valuableId, RoleNotifier notifier) { float num = ReadFloat(DollarValueCurrentField, valuable); if (num < 0f || float.IsNaN(num) || float.IsInfinity(num)) { return false; } bool flag = Random.Range(0, 100) < Mathf.Clamp(_config.GamblerWinChancePercent.Value, 0, 100); float num2 = (flag ? Mathf.Max(0f, num * Mathf.Clamp(_config.GamblerWinValueMultiplier.Value, 0f, 10f)) : 0f); try { if (flag) { if (!TrySetValue(valuable, num2)) { return false; } } else if (!TryDestroyValuable(valuable)) { return false; } _wageredValuables.Add(valuableId); assignment.GamblerWagersUsed = 1; notifier.Notify(assignment.Player, flag ? "Jackpot!" : "Bust!"); StageRolesPlugin.ModLogger.LogInfo((object)("Gambler effect used by player " + assignment.SteamId + ": " + (flag ? $"{num:0} -> {num2:0} (doubled)." : $"{num:0} -> destroyed."))); return true; } catch (Exception ex) { StageRolesPlugin.ModLogger.LogWarning((object)("Gambler effect failed for player " + assignment.SteamId + ": " + ex.Message)); return false; } } private static bool TrySetValue(ValuableObject valuable, float newValue) { //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) if (SemiFunc.IsMultiplayer()) { PhotonView val = ((Component)valuable).GetComponent() ?? ((Component)valuable).GetComponentInParent(); if ((Object)(object)val == (Object)null || val.ViewID == 0) { return false; } val.RPC("DollarValueSetRPC", (RpcTarget)0, new object[1] { newValue }); return true; } valuable.DollarValueSetRPC(newValue, default(PhotonMessageInfo)); return true; } private static bool TryDestroyValuable(ValuableObject valuable) { PhysGrabObjectImpactDetector val = ((Component)valuable).GetComponent() ?? ((Component)valuable).GetComponentInParent() ?? ((Component)valuable).GetComponentInChildren(true); if ((Object)(object)val == (Object)null) { return false; } if (SemiFunc.IsMultiplayer()) { PhotonView val2 = ((Component)val).GetComponent() ?? ((Component)val).GetComponentInParent(); if ((Object)(object)val2 == (Object)null || val2.ViewID == 0) { return false; } val2.RPC("DestroyObjectRPC", (RpcTarget)5, new object[1] { true }); return true; } val.DestroyObject(true); return true; } private static bool IsGrabbedBy(ValuableObject valuable, RoleAssignment assignment) { PhysGrabObject val = ((Component)valuable).GetComponent() ?? ((Component)valuable).GetComponentInParent(); if (val?.playerGrabbing == null) { return false; } foreach (PhysGrabber item in val.playerGrabbing) { PlayerAvatar val2 = item?.playerAvatar; if (!((Object)(object)val2 == (Object)null) && (val2 == assignment.Player || string.Equals(PlayerIdentity.SteamId(val2), assignment.SteamId, StringComparison.Ordinal))) { return true; } } return false; } private static string ValuableId(ValuableObject valuable) { PhotonView val = ((Component)valuable).GetComponent() ?? ((Component)valuable).GetComponentInParent(); if (!((Object)(object)val != (Object)null) || val.ViewID == 0) { return $"object:{((Object)valuable).GetInstanceID()}"; } return $"view:{val.ViewID}"; } private static bool ValueIsReady(ValuableObject valuable) { object obj = DollarValueSetField?.GetValue(valuable); 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 float ReadFloat(FieldInfo? field, object instance) { object obj = field?.GetValue(instance); if (obj is float) { return (float)obj; } return -1f; } private int ReadExtractionCompletionCount(RoundDirector director) { try { object obj = ExtractionPointsCompletedField?.GetValue(director); if (obj is int) { return (int)obj; } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Gambler could not read the extraction count: " + ex.Message)); } return _lastCompletionCount + 1; } } internal readonly struct HunterBatteryState { internal ItemBattery? Battery { get; } internal float BatteryLife { get; } internal bool Active { get; } internal HunterBatteryState(ItemBattery? battery, float batteryLife, bool active) { Battery = battery; BatteryLife = batteryLife; Active = active; } } internal static class HunterBatteryRuntime { private static readonly Dictionary FullBarSavings = new Dictionary(); private static readonly FieldInfo? LastPlayerGrabbingField = AccessTools.Field(typeof(PhysGrabObject), "lastPlayerGrabbing"); private static bool _failureLogged; private static float ConsumptionFactor => StageRolesPlugin.Instance?.Settings?.ClampedHunterBatteryConsumption ?? 1f; internal static void Clear() { FullBarSavings.Clear(); _failureLogged = false; } internal static bool TryScaleDrain(ItemBattery battery, ref float amount) { try { if (!IsHunterWeapon(battery)) { return false; } amount *= ConsumptionFactor; return true; } catch (Exception exception) { LogFailure("battery drain", exception); return false; } } internal static void ScaleFullBarDrain(ItemBattery battery, ref int bars) { try { if (IsHunterWeapon(battery) && bars > 0) { int instanceID = ((Object)battery).GetInstanceID(); float num = FullBarSavings.GetValueOrDefault(instanceID, 0f) + (float)bars * (1f - ConsumptionFactor); int num2 = Mathf.FloorToInt(num + 0.0001f); if (num2 > 0) { bars = Mathf.Max(0, bars - num2); num -= (float)num2; } FullBarSavings[instanceID] = Mathf.Max(0f, num); } } catch (Exception exception) { LogFailure("full-bar drain", exception); } } internal static HunterBatteryState Capture(Component source) { try { ItemBattery val = FindBattery(source); return ((Object)(object)val != (Object)null && IsHunterWeapon(val)) ? new HunterBatteryState(val, val.batteryLife, active: true) : default(HunterBatteryState); } catch (Exception exception) { LogFailure("consumption capture", exception); return default(HunterBatteryState); } } internal static void Refund(HunterBatteryState state) { try { ItemBattery battery = state.Battery; if (state.Active && !((Object)(object)battery == (Object)null)) { float num = state.BatteryLife - battery.batteryLife; if (!(num <= 0f)) { battery.batteryLife = Mathf.Clamp(battery.batteryLife + num * (1f - ConsumptionFactor), 0f, 100f); } } } catch (Exception exception) { LogFailure("consumption refund", exception); } } internal static bool IsHunterWeapon(ItemBattery? battery) { if ((Object)(object)battery == (Object)null || !SemiFunc.IsMasterClientOrSingleplayer() || !IsWeapon(battery)) { return false; } PlayerAvatar val = ResolveOwner(battery); if ((Object)(object)val != (Object)null) { StageRolesPlugin instance = StageRolesPlugin.Instance; if (instance == null) { return false; } return instance.Controller?.PlayerHasRole(val, StageRole.Hunter) == true; } return false; } private static ItemBattery? FindBattery(Component source) { return source.GetComponent() ?? source.GetComponentInChildren() ?? source.GetComponentInParent(); } private static PlayerAvatar? ResolveOwner(ItemBattery battery) { ItemEquippable componentInParent = ((Component)battery).GetComponentInParent(); PlayerAvatar val = ((componentInParent != null) ? componentInParent.GetOwnerPlayerAvatar() : null); if ((Object)(object)val != (Object)null) { return val; } PhysGrabObject componentInParent2 = ((Component)battery).GetComponentInParent(); if (!((Object)(object)componentInParent2 != (Object)null)) { return null; } object? obj = LastPlayerGrabbingField?.GetValue(componentInParent2); return (PlayerAvatar?)((obj is PlayerAvatar) ? obj : null); } private static void LogFailure(string operation, Exception exception) { if (!_failureLogged) { _failureLogged = true; StageRolesPlugin.ModLogger.LogWarning((object)("Hunter " + operation + " handling was disabled for this attempt so vanilla weapon behavior could continue: " + exception.Message)); } } private static bool IsWeapon(ItemBattery battery) { if (!((Object)(object)((Component)battery).GetComponentInParent() != (Object)null) && !((Object)(object)((Component)battery).GetComponentInParent() != (Object)null) && !((Object)(object)((Component)battery).GetComponentInParent() != (Object)null) && !((Object)(object)((Component)battery).GetComponentInParent() != (Object)null) && !((Object)(object)((Component)battery).GetComponentInParent() != (Object)null) && !((Object)(object)((Component)battery).GetComponentInParent() != (Object)null) && !((Object)(object)((Component)battery).GetComponentInParent() != (Object)null)) { return (Object)(object)((Component)battery).GetComponentInParent() != (Object)null; } return true; } } [HarmonyPatch] internal static class HunterBatteryPatches { [HarmonyPrefix] [HarmonyPatch(typeof(ItemBattery), "Drain")] private static void ItemBatteryDrainPrefix(ItemBattery __instance, ref float __0) { HunterBatteryRuntime.TryScaleDrain(__instance, ref __0); } [HarmonyPrefix] [HarmonyPatch(typeof(ItemBattery), "RemoveFullBar")] private static void ItemBatteryRemoveFullBarPrefix(ItemBattery __instance, ref int __0) { HunterBatteryRuntime.ScaleFullBarDrain(__instance, ref __0); } [HarmonyPrefix] [HarmonyPatch(typeof(ItemGun), "ShootRPC")] private static void ItemGunShootRpcPrefix(ItemGun __instance, out HunterBatteryState __state) { __state = (__instance.batteryDrainFullBar ? default(HunterBatteryState) : HunterBatteryRuntime.Capture((Component)(object)__instance)); } [HarmonyPostfix] [HarmonyPatch(typeof(ItemGun), "ShootRPC")] private static void ItemGunShootRpcPostfix(HunterBatteryState __state) { HunterBatteryRuntime.Refund(__state); } [HarmonyPrefix] [HarmonyPatch(typeof(ItemMelee), "EnemyOrPVPSwingHitRPC")] private static void ItemMeleeEnemyHitPrefix(ItemMelee __instance, out HunterBatteryState __state) { __state = HunterBatteryRuntime.Capture((Component)(object)__instance); } [HarmonyPostfix] [HarmonyPatch(typeof(ItemMelee), "EnemyOrPVPSwingHitRPC")] private static void ItemMeleeEnemyHitPostfix(HunterBatteryState __state) { HunterBatteryRuntime.Refund(__state); } [HarmonyPrefix] [HarmonyPatch(typeof(ItemMelee), "SwingHitRPC")] private static void ItemMeleeSwingHitPrefix(ItemMelee __instance, out HunterBatteryState __state) { __state = HunterBatteryRuntime.Capture((Component)(object)__instance); } [HarmonyPostfix] [HarmonyPatch(typeof(ItemMelee), "SwingHitRPC")] private static void ItemMeleeSwingHitPostfix(HunterBatteryState __state) { HunterBatteryRuntime.Refund(__state); } [HarmonyPrefix] [HarmonyPatch(typeof(ItemLeafBlower), "BatteryDrain")] private static void ItemLeafBlowerBatteryDrainPrefix(ItemLeafBlower __instance, ref float __0) { ItemBattery val = ((Component)__instance).GetComponent() ?? ((Component)__instance).GetComponentInChildren() ?? ((Component)__instance).GetComponentInParent(); if ((Object)(object)val != (Object)null) { HunterBatteryRuntime.TryScaleDrain(val, ref __0); } } } internal sealed class StinkerRoleRuntime { private const float TrailEmissionIntervalSeconds = 0.2f; private readonly MonoBehaviour _coroutineOwner; private readonly StageRolesConfig _config; private readonly VanillaRolePrefabResolver _resolver; private readonly List _carriers = new List(); private readonly Dictionary> _pendingTrailPoints = new Dictionary>(StringComparer.Ordinal); private readonly Dictionary _nextTrailEmissionAt = new Dictionary(StringComparer.Ordinal); private int _generation; private bool _active; internal StinkerRoleRuntime(MonoBehaviour coroutineOwner, StageRolesConfig config, VanillaRolePrefabResolver resolver) { _coroutineOwner = coroutineOwner; _config = config; _resolver = resolver; } internal void Begin(IReadOnlyList assignments) { //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_0047: 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) Stop(); _active = true; _generation++; foreach (RoleAssignment assignment in assignments) { if (assignment.Role == StageRole.Stinker) { Vector3 stinkerTrailAnchor = (assignment.PreviousPosition = ((Component)assignment.Player).transform.position); assignment.StinkerTrailAnchor = stinkerTrailAnchor; assignment.TravelDistance = 0f; _pendingTrailPoints[assignment.SteamId] = new List(); _nextTrailEmissionAt[assignment.SteamId] = 0f; } } _resolver.TryGetRandomUraniumValuable(out var _); } internal void Tick(RoleAssignment assignment) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: 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_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0129: 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_0222: 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_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0190: 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_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) PlayerAvatar player = assignment.Player; if (!_active || (Object)(object)player == (Object)null || !PlayerState.IsLiving(player)) { return; } Vector3 previousPosition = assignment.PreviousPosition; Vector3 position = ((Component)player).transform.position; Vector3 val = position - previousPosition; assignment.PreviousPosition = position; val.y = 0f; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude > 4f) { assignment.StinkerTrailAnchor = position; assignment.TravelDistance = 0f; PendingPoints(assignment.SteamId).Clear(); _nextTrailEmissionAt[assignment.SteamId] = 0f; return; } if (!_config.StinkerAllowTruckSpawns.Value && PlayerState.IsInTruck(player)) { assignment.TravelDistance = 0f; return; } assignment.TravelDistance += magnitude; float clampedStinkerDistance = _config.ClampedStinkerDistance; List list = PendingPoints(assignment.SteamId); Vector3 val2 = ((magnitude > 0.001f) ? (val / magnitude) : Vector3.zero); while (assignment.TravelDistance >= clampedStinkerDistance) { assignment.TravelDistance -= clampedStinkerDistance; Vector3 item = position - val2 * assignment.TravelDistance; list.Add(item); if (list.Count > 64) { list.RemoveAt(0); } } _nextTrailEmissionAt.TryGetValue(assignment.SteamId, out var value); if (list.Count > 0 && Time.time >= value) { Vector3 val3 = list[0]; Vector3 val4 = position - val3; val4.y = 0f; bool num = list.Count == 1; bool flag = ((Vector3)(ref val4)).magnitude >= _config.ClampedStinkerSafetyDistance; if ((!num || flag) && _resolver.TryGetRandomUraniumValuable(out var resolved)) { list.RemoveAt(0); Vector3 position2 = FindFloorPosition(val3); _coroutineOwner.StartCoroutine(SpawnAndBreak(position2, resolved, _generation)); _nextTrailEmissionAt[assignment.SteamId] = Time.time + 0.2f; } } assignment.StinkerTrailAnchor = position; } internal void Stop() { _active = false; _generation++; foreach (GameObject carrier in _carriers) { DestroyNetworkObject(carrier); } _carriers.Clear(); _pendingTrailPoints.Clear(); _nextTrailEmissionAt.Clear(); } internal void RemovePlayer(string steamId) { _pendingTrailPoints.Remove(steamId); _nextTrailEmissionAt.Remove(steamId); } private List PendingPoints(string steamId) { if (!_pendingTrailPoints.TryGetValue(steamId, out List value)) { value = new List(); _pendingTrailPoints[steamId] = value; } return value; } private IEnumerator SpawnAndBreak(Vector3 position, ResolvedRolePrefab resolved, int generation) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) GameObject carrier; try { carrier = (SemiFunc.IsMultiplayer() ? PhotonNetwork.InstantiateRoomObject(resolved.ResourcePath, position, Quaternion.identity, (byte)0, (object[])null) : Object.Instantiate(resolved.Prefab, position, Quaternion.identity)); StageFluxCompatibility.MarkInternalCarrier(carrier); _carriers.Add(carrier); Rigidbody[] componentsInChildren = carrier.GetComponentsInChildren(true); foreach (Rigidbody obj in componentsInChildren) { obj.velocity = Vector3.zero; obj.angularVelocity = Vector3.zero; obj.isKinematic = true; } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogWarning((object)("Stinker uranium carrier could not be created: " + ex.Message)); yield break; } yield return null; if (!_active || generation != _generation || (Object)(object)carrier == (Object)null) { DestroyNetworkObject(carrier); yield break; } try { UraniumScript obj2 = carrier.GetComponent() ?? carrier.GetComponentInChildren(true); PhysGrabObjectImpactDetector val = carrier.GetComponent() ?? carrier.GetComponentInChildren(true); if ((Object)(object)obj2 == (Object)null || (Object)(object)val == (Object)null) { DestroyNetworkObject(carrier); yield break; } val.DestroyObject(true); StageRolesPlugin.ModLogger.LogDebug((object)"Stinker created a vanilla uranium break cloud."); } catch (Exception ex2) { StageRolesPlugin.ModLogger.LogWarning((object)("Stinker uranium carrier could not be broken: " + ex2.Message)); DestroyNetworkObject(carrier); } finally { _carriers.Remove(carrier); } } private static Vector3 FindFloorPosition(Vector3 position) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_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) RaycastHit val = default(RaycastHit); if (!Physics.Raycast(position + Vector3.up * 1.5f, Vector3.down, ref val, 5f, -1, (QueryTriggerInteraction)1)) { return position + Vector3.up * 0.08f; } return ((RaycastHit)(ref val)).point + Vector3.up * 0.08f; } private static void DestroyNetworkObject(GameObject? instance) { if ((Object)(object)instance == (Object)null) { return; } try { PhotonView val = instance.GetComponent() ?? instance.GetComponentInChildren(true); if (SemiFunc.IsMultiplayer() && PhotonNetwork.IsMasterClient && (Object)(object)val != (Object)null && val.ViewID != 0) { PhotonNetwork.Destroy(((Component)val).gameObject); } else { Object.Destroy((Object)(object)instance); } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Stinker carrier cleanup was deferred: " + ex.Message)); } } } internal sealed class RoleNotifier { private const float NotificationReadyTimeoutSeconds = 10f; private const float VoiceWaitTimeoutSeconds = 15f; private const float StageFluxQuietPeriodSeconds = 1.5f; private const float PollIntervalSeconds = 0.1f; private static readonly FieldInfo? VoiceChatField = AccessTools.Field(typeof(PlayerAvatar), "voiceChat"); private readonly MonoBehaviour _coroutineOwner; private readonly StageRolesConfig _config; private int _generation; internal RoleNotifier(MonoBehaviour coroutineOwner, StageRolesConfig config) { _coroutineOwner = coroutineOwner; _config = config; } internal void Begin(IReadOnlyList assignments) { End(); if (_config.AnnouncementsEnabled.Value) { int generation = _generation; _coroutineOwner.StartCoroutine(Announce(assignments, generation)); } } internal void End() { _generation++; NotificationEnemyReactionGuard.Clear(); } internal void Notify(PlayerAvatar player, string message) { if (!_config.AnnouncementsEnabled.Value || !CanNotify() || (Object)(object)player == (Object)null || !((Component)player).gameObject.activeInHierarchy || (Object)(object)player.photonView == (Object)null || string.IsNullOrEmpty(message)) { return; } try { NotificationEnemyReactionGuard.Expect(player, message); player.ChatMessageSend(message); } catch (Exception ex) { NotificationEnemyReactionGuard.CancelExpected(player); StageRolesPlugin.ModLogger.LogWarning((object)("Role effect notification failed for player view " + $"{player.photonView.ViewID}: {ex.Message}")); } } private IEnumerator Announce(IReadOnlyList assignments, int generation) { bool stageFluxInstalled = Chainloader.PluginInfos.ContainsKey("REPOJP.StagePhysicsEvents"); float num = (stageFluxInstalled ? _config.StageFluxAnnouncementDelaySeconds.Value : _config.AnnouncementDelaySeconds.Value); yield return (object)new WaitForSeconds(Mathf.Clamp(num, 0f, 30f)); float readyDeadline = Time.time + 10f; while (generation == _generation && !CanNotify() && Time.time < readyDeadline) { yield return (object)new WaitForSeconds(0.1f); } if (generation != _generation || !CanNotify()) { yield break; } float requiredQuietPeriod = (stageFluxInstalled ? 1.5f : 0f); float voiceDeadline = Time.time + 15f; float quietSince = -1f; bool quietPeriodReached = false; while (generation == _generation && Time.time < voiceDeadline) { if (AnyTtsPlaying()) { quietSince = -1f; } else { if (quietSince < 0f) { quietSince = Time.time; } if (Time.time - quietSince >= requiredQuietPeriod) { quietPeriodReached = true; break; } } yield return (object)new WaitForSeconds(0.1f); } if (generation != _generation || !CanNotify() || !quietPeriodReached) { yield break; } int num2 = 0; foreach (RoleAssignment assignment in assignments) { PlayerAvatar player = assignment.Player; if (!((Object)(object)player == (Object)null) && ((Component)player).gameObject.activeInHierarchy && !((Object)(object)player.photonView == (Object)null)) { string text = assignment.Role.ToString(); try { NotificationEnemyReactionGuard.Expect(player, text); player.ChatMessageSend(text); num2++; } catch (Exception ex) { NotificationEnemyReactionGuard.CancelExpected(player); StageRolesPlugin.ModLogger.LogWarning((object)($"Role announcement failed for player view {player.photonView.ViewID}: " + ex.Message)); } } } StageRolesPlugin.ModLogger.LogInfo((object)$"Announced {num2}/{assignments.Count} assigned roles through player chat."); } private static bool CanNotify() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 if (SemiFunc.IsMasterClientOrSingleplayer() && (Object)(object)GameDirector.instance != (Object)null) { return (int)GameDirector.instance.currentState == 2; } return false; } private static bool AnyTtsPlaying() { List list = SemiFunc.PlayerGetList(); if (list == null) { return false; } foreach (PlayerAvatar item in list) { object? obj = VoiceChatField?.GetValue(item); PlayerVoiceChat val = (PlayerVoiceChat)((obj is PlayerVoiceChat) ? obj : null); if (val != null && (Object)(object)val.ttsAudioSource != (Object)null && val.ttsAudioSource.isPlaying) { return true; } } return false; } } internal static class NotificationEnemyReactionGuard { private sealed class ActiveNotification { internal PlayerVoiceChat VoiceChat { get; } internal float AwaitPlaybackUntil { get; } internal float HardExpiresAt { get; } internal bool PlaybackObserved { get; set; } internal float LastPlaybackAt { get; set; } internal float NextMicrophoneInvestigateAt { get; set; } internal int NoTtsCrawlingCounter { get; set; } internal ActiveNotification(PlayerVoiceChat voiceChat, string message) { VoiceChat = voiceChat; AwaitPlaybackUntil = Time.time + 10f; HardExpiresAt = Time.time + Mathf.Clamp(12f + (float)message.Length * 0.25f, 12f, 45f); NextMicrophoneInvestigateAt = Time.time + Mathf.Max(0f, ReadFloat(InvestigateTimerField, voiceChat)); NoTtsCrawlingCounter = ((ReadFloat(ClipLoudnessNoTtsField, voiceChat) > 0.05f) ? ReadInt(ClipLoudnessCrawlingCounterField, voiceChat) : 0); } } private const float PlaybackStartGraceSeconds = 10f; private const float PlaybackSilenceGraceSeconds = 1f; private const float InvestigateCooldownSeconds = 1f; private static readonly FieldInfo? PlayerVoiceChatField = AccessTools.Field(typeof(PlayerAvatar), "voiceChat"); private static readonly FieldInfo? VoiceChatPlayerAvatarField = AccessTools.Field(typeof(PlayerVoiceChat), "playerAvatar"); private static readonly FieldInfo? InvestigateTimerField = AccessTools.Field(typeof(PlayerVoiceChat), "investigateTimer"); private static readonly FieldInfo? ClipLoudnessNoTtsField = AccessTools.Field(typeof(PlayerVoiceChat), "clipLoudnessNoTTS"); private static readonly FieldInfo? ClipLoudnessCrawlingCounterField = AccessTools.Field(typeof(PlayerVoiceChat), "clipLoudnessCrawlingCounter"); private static readonly FieldInfo? PlayerDeathHeadField = AccessTools.Field(typeof(PlayerAvatar), "playerDeathHead"); private static readonly FieldInfo? PlayerDisabledField = AccessTools.Field(typeof(PlayerAvatar), "isDisabled"); private static readonly FieldInfo? PlayerCrawlingField = AccessTools.Field(typeof(PlayerAvatar), "isCrawling"); private static readonly FieldInfo? PlayerCrouchingField = AccessTools.Field(typeof(PlayerAvatar), "isCrouching"); private static readonly FieldInfo? DeathHeadSpectatedField = AccessTools.Field(typeof(PlayerDeathHead), "spectated"); private static readonly FieldInfo? DeathHeadPhysGrabObjectField = AccessTools.Field(typeof(PlayerDeathHead), "physGrabObject"); private static readonly Dictionary ActiveByVoiceChat = new Dictionary(); internal static void Expect(PlayerAvatar player, string message) { PlayerVoiceChat voiceChat = GetVoiceChat(player); if (!((Object)(object)player?.photonView == (Object)null) && !string.IsNullOrEmpty(message) && !((Object)(object)voiceChat == (Object)null)) { ActiveByVoiceChat[((Object)voiceChat).GetInstanceID()] = new ActiveNotification(voiceChat, message); } } internal static void CancelExpected(PlayerAvatar player) { PlayerVoiceChat voiceChat = GetVoiceChat(player); if ((Object)(object)voiceChat != (Object)null) { ActiveByVoiceChat.Remove(((Object)voiceChat).GetInstanceID()); } } internal static bool PrepareVoiceUpdate(PlayerVoiceChat voiceChat) { if (!SuppressionEnabled() || !TryGetActive(voiceChat, out ActiveNotification _)) { return false; } voiceChat.OverrideDisableEnemyInvestigate(0.25f); return true; } internal static void RestoreMicrophoneInvestigation(PlayerVoiceChat voiceChat) { //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) //IL_016e: 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) //IL_0156: Unknown result type (might be due to invalid IL or missing references) if (!SemiFunc.IsMultiplayer() || !SemiFunc.IsMasterClient() || (Object)(object)EnemyDirector.instance == (Object)null || !TryGetActive(voiceChat, out ActiveNotification active)) { return; } PlayerAvatar val = ReadReference(VoiceChatPlayerAvatarField, voiceChat); if ((Object)(object)val == (Object)null) { return; } PlayerDeathHead val2 = ReadReference(PlayerDeathHeadField, val); bool flag = (Object)(object)val2 != (Object)null && ReadBool(DeathHeadSpectatedField, val2); if (ReadBool(PlayerDisabledField, val) && !flag) { active.NoTtsCrawlingCounter = 0; return; } bool flag2; if (flag) { active.NoTtsCrawlingCounter = 0; flag2 = ReadFloat(ClipLoudnessNoTtsField, voiceChat) > 0.05f; } else if (ReadBool(PlayerCrawlingField, val)) { active.NoTtsCrawlingCounter = ((ReadFloat(ClipLoudnessNoTtsField, voiceChat) > 0.05f) ? (active.NoTtsCrawlingCounter + 1) : 0); flag2 = active.NoTtsCrawlingCounter > 10; } else { active.NoTtsCrawlingCounter = 0; flag2 = ReadFloat(ClipLoudnessNoTtsField, voiceChat) > (ReadBool(PlayerCrouchingField, val) ? 0.05f : 0.025f); } if (flag2 && !(Time.time < active.NextMicrophoneInvestigateAt)) { Vector3 val3 = val.PlayerVisionTarget.VisionTransform.position; PhysGrabObject val4 = (((Object)(object)val2 != (Object)null) ? ReadReference(DeathHeadPhysGrabObjectField, val2) : null); if (flag && (Object)(object)val4 != (Object)null) { val3 = val4.centerPoint; } active.NextMicrophoneInvestigateAt = Time.time + 1f; EnemyDirector.instance.SetInvestigate(val3, 5f, false); } } internal static void Clear() { ActiveByVoiceChat.Clear(); } private static bool TryGetActive(PlayerVoiceChat voiceChat, out ActiveNotification active) { int instanceID = ((Object)voiceChat).GetInstanceID(); if (!ActiveByVoiceChat.TryGetValue(instanceID, out active) || (Object)(object)active.VoiceChat == (Object)null) { ActiveByVoiceChat.Remove(instanceID); active = null; return false; } float time = Time.time; if (time > active.HardExpiresAt) { ActiveByVoiceChat.Remove(instanceID); active = null; return false; } if ((Object)(object)voiceChat.ttsAudioSource != (Object)null && voiceChat.ttsAudioSource.isPlaying) { active.PlaybackObserved = true; active.LastPlaybackAt = time; return true; } if (!active.PlaybackObserved && time <= active.AwaitPlaybackUntil) { return true; } if (active.PlaybackObserved && time - active.LastPlaybackAt <= 1f) { return true; } ActiveByVoiceChat.Remove(instanceID); active = null; return false; } private static bool SuppressionEnabled() { if ((Object)(object)StageRolesPlugin.Instance != (Object)null) { return StageRolesPlugin.Instance.Settings.Enabled.Value; } return false; } private static PlayerVoiceChat? GetVoiceChat(PlayerAvatar? player) { if (!((Object)(object)player == (Object)null)) { return ReadReference(PlayerVoiceChatField, player); } return null; } private static T? ReadReference(FieldInfo? field, object instance) where T : class { return field?.GetValue(instance) as T; } private static float ReadFloat(FieldInfo? field, object instance) { object obj = field?.GetValue(instance); if (obj is float) { return (float)obj; } return 0f; } private static int ReadInt(FieldInfo? field, object instance) { object obj = field?.GetValue(instance); if (obj is int) { return (int)obj; } return 0; } private static bool ReadBool(FieldInfo? field, object instance) { object obj = field?.GetValue(instance); 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; } } [HarmonyAfter(new string[] { "REPOJP.StagePhysicsEvents" })] internal static class NotificationEnemyReactionPatches { [HarmonyPrefix] [HarmonyPatch(typeof(PlayerVoiceChat), "Update")] private static void PlayerVoiceChatUpdatePrefix(PlayerVoiceChat __instance, out bool __state) { __state = NotificationEnemyReactionGuard.PrepareVoiceUpdate(__instance); } [HarmonyPostfix] [HarmonyPatch(typeof(PlayerVoiceChat), "Update")] private static void PlayerVoiceChatUpdatePostfix(PlayerVoiceChat __instance, bool __state) { if (__state) { NotificationEnemyReactionGuard.RestoreMicrophoneInvestigation(__instance); } } } internal sealed class StageRoleController : MonoBehaviour { private static readonly FieldInfo? EnemyHealthEnemyField = AccessTools.Field(typeof(EnemyHealth), "enemy"); private static readonly FieldInfo? EnemyParentField = AccessTools.Field(typeof(Enemy), "EnemyParent"); private static readonly FieldInfo? EnemyDifficultyField = AccessTools.Field(typeof(EnemyParent), "difficulty"); private static readonly FieldInfo? EnemySpawnValuableCurrentField = AccessTools.Field(typeof(EnemyHealth), "spawnValuableCurrent"); private const float PlayerRefreshIntervalSeconds = 0.5f; private const float TunaMovementThresholdSquared = 2.5E-05f; private const float TunaStageStartGraceSeconds = 5f; private const float RoleQueryCooldownSeconds = 2f; private readonly List _assignments = new List(); private readonly Dictionary _rescueDeadSince = new Dictionary(StringComparer.Ordinal); private readonly HashSet _rescueRevivePending = new HashSet(StringComparer.Ordinal); private readonly Dictionary _roleQueryAllowedAt = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _enemyLastAttacker = new Dictionary(); private readonly Dictionary> _grabbersByObjectId = new Dictionary>(); private readonly HashSet _engineerSuppressedTrapIds = new HashSet(); private readonly Dictionary _engineerSuppressionGraceUntil = new Dictionary(); private StageRolesConfig _config; private VanillaRolePrefabResolver _resolver; private BomberRoleRuntime _bomber; private MedicRoleRuntime _medic; private MageRoleRuntime _mage; private GamblerRoleRuntime _gambler; private StinkerRoleRuntime _stinker; private RoleNotifier _notifier; private bool _stageReady; private int _stageGeneration; private int _levelGeneratorInstanceId; private float _nextPlayerRefreshAt; private float _phoenixFailureGraceUntil; private float _tunaStageGraceUntil; private string _previousCrownedSteamId = string.Empty; private bool _kingCrownApplied; internal void Initialize(StageRolesConfig config) { _config = config; _resolver = new VanillaRolePrefabResolver(); _bomber = new BomberRoleRuntime((MonoBehaviour)(object)this, config, _resolver); _medic = new MedicRoleRuntime((MonoBehaviour)(object)this, config, _resolver); _mage = new MageRoleRuntime((MonoBehaviour)(object)this, config, _resolver); _gambler = new GamblerRoleRuntime(config); _stinker = new StinkerRoleRuntime((MonoBehaviour)(object)this, config, _resolver); _notifier = new RoleNotifier((MonoBehaviour)(object)this, config); ((Component)this).gameObject.SetActive(false); } internal void StageReady(LevelGenerator generator) { if (!_config.Enabled.Value || !IsAuthority() || !IsPlayableStageContext()) { StageEndingInternal(restoreBaseUpgrades: true, deactivate: true); return; } int num = (((Object)(object)generator != (Object)null) ? ((Object)generator).GetInstanceID() : 0); if (!_stageReady || _levelGeneratorInstanceId != num) { if (!((Component)this).gameObject.activeSelf) { ((Component)this).gameObject.SetActive(true); } StageEndingInternal(restoreBaseUpgrades: true, deactivate: false); _stageReady = true; _tunaStageGraceUntil = Time.time + 5f; _levelGeneratorInstanceId = num; ((MonoBehaviour)this).StartCoroutine(PrepareStage(++_stageGeneration)); } } internal void StageEnding() { StageEndingInternal(restoreBaseUpgrades: true, deactivate: true); } internal void ExtractionCompleted(RoundDirector? director) { if (_stageReady && IsAuthority()) { _gambler.ExtractionCompleted(director, _assignments, _notifier); } } internal bool AllowBomberGrenadeGrabStart(PhysGrabObject grenade, int playerViewId, PhotonMessageInfo messageInfo) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (_stageReady && IsAuthority()) { return _bomber.AllowGrabStart(grenade, playerViewId, messageInfo, IsAssignedBomber(playerViewId)); } return true; } internal bool AllowBomberGrenadeGrabEnd(PhysGrabObject grenade, int playerViewId, PhotonMessageInfo messageInfo) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (_stageReady && IsAuthority()) { return _bomber.AllowGrabEnd(grenade, playerViewId, messageInfo); } return true; } internal bool ShouldSuppressEngineerTrap(Trap trap) { return ShouldSuppressEngineerEffect((Component)(object)trap); } internal bool ShouldSuppressEngineerEffect(Component effect) { if (!_stageReady || !IsAuthority() || (Object)(object)effect == (Object)null) { return false; } try { PhysGrabObject val = effect.GetComponent() ?? effect.GetComponentInParent() ?? effect.GetComponentInChildren(true); if ((Object)(object)val == (Object)null) { return false; } int instanceID = ((Object)val).GetInstanceID(); if (_engineerSuppressionGraceUntil.TryGetValue(instanceID, out var value)) { if (Time.time < value) { ResetEngineerEffectState(effect); return true; } _engineerSuppressionGraceUntil.Remove(instanceID); } foreach (PhysGrabber item in val.playerGrabbing) { if ((Object)(object)item?.playerAvatar != (Object)null && PlayerHasRole(item.playerAvatar, StageRole.Engineer)) { ResetEngineerEffectState(effect); LogEngineerSuppression(val); return true; } } if (_grabbersByObjectId.TryGetValue(((Object)val).GetInstanceID(), out HashSet value2)) { foreach (int item2 in value2) { PhotonView val2 = PhotonView.Find(item2); PlayerAvatar player = (((Object)(object)val2 != (Object)null) ? (((Component)val2).GetComponent() ?? ((Component)val2).GetComponent()?.playerAvatar) : null); if (PlayerHasRole(player, StageRole.Engineer)) { ResetEngineerEffectState(effect); LogEngineerSuppression(val); return true; } } } _engineerSuppressedTrapIds.Remove(((Object)val).GetInstanceID()); } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Engineer trap ownership could not be resolved: " + ex.Message)); } return false; } internal bool TryRegisterEngineerHostOnlyGrab(PhysGrabObject grabbedObject, int playerViewId, PhotonMessageInfo messageInfo) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) if (!_stageReady || !IsAuthority() || (Object)(object)grabbedObject == (Object)null || playerViewId <= 0 || !EngineerEffectCatalog.IsEffectValuable(grabbedObject)) { return false; } try { PhotonView val = PhotonView.Find(playerViewId); PhysGrabber val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent() : null); PhotonView val3 = (((Object)(object)val2 != (Object)null) ? ((Component)val2).GetComponent() : null); if ((Object)(object)val2?.playerAvatar == (Object)null || (Object)(object)val3 == (Object)null || !PlayerHasRole(val2.playerAvatar, StageRole.Engineer) || !SemiFunc.OwnerOnlyRPC(messageInfo, val3)) { return false; } if (!grabbedObject.playerGrabbing.Contains(val2)) { grabbedObject.playerGrabbing.Add(val2); } RecordGrabStarted(grabbedObject, playerViewId); _engineerSuppressionGraceUntil.Remove(((Object)grabbedObject).GetInstanceID()); StageRolesPlugin.ModLogger.LogDebug((object)"Engineer effect valuable grab registered on the host without broadcasting the vanilla activation condition."); return true; } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Engineer host-only grab registration fell back to vanilla: " + ex.Message)); return false; } } internal void RecordGrabStarted(PhysGrabObject grabbedObject, int playerViewId) { if (_stageReady && IsAuthority() && !((Object)(object)grabbedObject == (Object)null) && playerViewId > 0) { int instanceID = ((Object)grabbedObject).GetInstanceID(); if (!_grabbersByObjectId.TryGetValue(instanceID, out HashSet value)) { value = new HashSet(); _grabbersByObjectId[instanceID] = value; } value.Add(playerViewId); } } internal void RecordGrabEnded(PhysGrabObject grabbedObject, int playerViewId) { if ((Object)(object)grabbedObject == (Object)null) { return; } int instanceID = ((Object)grabbedObject).GetInstanceID(); if (_engineerSuppressedTrapIds.Contains(instanceID) || (EngineerEffectCatalog.IsEffectValuable(grabbedObject) && IsAssignedRole(playerViewId, StageRole.Engineer))) { _engineerSuppressionGraceUntil[instanceID] = Time.time + 0.25f; } if (_grabbersByObjectId.TryGetValue(instanceID, out HashSet value)) { value.Remove(playerViewId); if (value.Count == 0) { _grabbersByObjectId.Remove(instanceID); _engineerSuppressedTrapIds.Remove(instanceID); } } } private static void ResetEngineerEffectState(Component effect) { Trap val = (Trap)(object)((effect is Trap) ? effect : null); if (val != null) { val.trapStart = false; val.trapActive = false; val.trapTriggered = false; val.enemyInvestigate = false; } } private void LogEngineerSuppression(PhysGrabObject valuable) { if (_engineerSuppressedTrapIds.Add(((Object)valuable).GetInstanceID())) { StageRolesPlugin.ModLogger.LogDebug((object)"Engineer prevented a held valuable effect from updating or activating."); } } private bool IsAssignedBomber(int playerViewId) { return IsAssignedRole(playerViewId, StageRole.Bomber); } private bool IsAssignedRole(int playerViewId, StageRole role) { try { PhotonView val = PhotonView.Find(playerViewId); PlayerAvatar val2 = (((Object)(object)val != (Object)null) ? (((Component)val).GetComponent() ?? ((Component)val).GetComponent()?.playerAvatar) : null); if ((Object)(object)val2 == (Object)null) { return false; } string text = PlayerIdentity.SteamId(val2); foreach (RoleAssignment assignment in _assignments) { if (assignment.Role == role && (assignment.Player == val2 || (!string.IsNullOrEmpty(text) && string.Equals(assignment.SteamId, text, StringComparison.Ordinal)))) { return true; } } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Grabber role could not be resolved: " + ex.Message)); } return false; } internal bool TryPreventPhoenixRetake() { if (!_stageReady || !IsAuthority()) { return false; } bool flag = false; foreach (RoleAssignment assignment in _assignments) { if (assignment.Role == StageRole.Phoenix && !assignment.PhoenixUsed && !((Object)(object)assignment.Player == (Object)null)) { if (PlayerState.IsLiving(assignment.Player)) { CompletePhoenixRevival(assignment); assignment.WasAlive = true; assignment.PhoenixReadyAt = 0f; } else if (assignment.PhoenixRevivePending) { flag = true; } else if (assignment.WasAlive || !(assignment.PhoenixReadyAt <= 0f)) { flag = true; SchedulePhoenix(assignment); TryRevivePhoenix(assignment); } } } if (!flag) { _phoenixFailureGraceUntil = 0f; return false; } if (_phoenixFailureGraceUntil <= 0f) { _phoenixFailureGraceUntil = Time.time + Mathf.Clamp(_config.PhoenixFailureGraceSeconds.Value, 1f, 15f); } bool flag2 = false; foreach (RoleAssignment assignment2 in _assignments) { if (assignment2.Role == StageRole.Phoenix && assignment2.PhoenixUsed && PlayerState.IsLiving(assignment2.Player)) { flag2 = true; } } if (flag2) { _phoenixFailureGraceUntil = 0f; return true; } return Time.time < _phoenixFailureGraceUntil; } internal void Shutdown() { StageEndingInternal(restoreBaseUpgrades: true, deactivate: true); } internal bool TrySetRole(string targetIdentifier, StageRole role, out string response) { //IL_00a9: 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_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) response = string.Empty; if (!_config.SetRoleCommandEnabled.Value) { response = "The set-role command is disabled."; return false; } if (!_stageReady || !IsAuthority()) { response = "Roles can only be changed by the host during a stage."; return false; } RoleAssignment roleAssignment = ResolveAssignment(targetIdentifier); if (roleAssignment == null) { response = (string.IsNullOrWhiteSpace(targetIdentifier) ? "The local player has no stage role." : ("Player not found: " + targetIdentifier)); return false; } StageRole role2 = roleAssignment.Role; RestoreKingCrown(); _bomber.Stop(); _mage.Stop(); _stinker.Stop(); HunterBatteryRuntime.Clear(); roleAssignment.Role = role; roleAssignment.PreviousPosition = ((Component)roleAssignment.Player).transform.position; roleAssignment.StinkerTrailAnchor = ((Component)roleAssignment.Player).transform.position; roleAssignment.TravelDistance = 0f; roleAssignment.JoblessDamageTimer = 0f; roleAssignment.TunaStationaryTimer = 0f; roleAssignment.TunaDamageTimer = 0f; roleAssignment.MageNextCastAt = 0f; roleAssignment.WasAlive = PlayerState.IsLiving(roleAssignment.Player); roleAssignment.PhoenixUsed = false; roleAssignment.PhoenixRevivePending = false; roleAssignment.PhoenixReadyAt = 0f; roleAssignment.RescuerRevivesUsed = 0; roleAssignment.GamblerWagersUsed = 0; _rescueDeadSince.Remove(roleAssignment.SteamId); _rescueRevivePending.Remove(roleAssignment.SteamId); UpgradeService.SetLevels(roleAssignment.SteamId, RoleCatalog.TargetUpgrades(role, _config)); foreach (RoleAssignment assignment in _assignments) { if (assignment.Role == StageRole.Bomber || assignment.Role == StageRole.Stinker) { assignment.PreviousPosition = ((Component)assignment.Player).transform.position; assignment.StinkerTrailAnchor = ((Component)assignment.Player).transform.position; assignment.TravelDistance = 0f; } } _bomber.Begin(); _medic.Begin(_assignments); _mage.Begin(_assignments); _gambler.Begin(); _stinker.Begin(_assignments); ApplyKingCrown((role == StageRole.King) ? roleAssignment.SteamId : string.Empty); RoleAssignmentSync.Publish(_assignments); string text = PlayerIdentity.Name(roleAssignment.Player); if (string.IsNullOrEmpty(text)) { text = roleAssignment.SteamId; } response = $"{text}: {role2} -> {role}"; StageRolesPlugin.ModLogger.LogInfo((object)("Test role changed: " + response + ".")); return true; } internal bool IsRoleQueryRequest(string message) { if (_config.Enabled.Value && IsAuthority()) { return string.Equals(message?.Trim(), "/roles", StringComparison.OrdinalIgnoreCase); } return false; } internal void TryHandleMageCast(PlayerAvatar requester, string message) { if ((Object)(object)requester == (Object)null || !_stageReady || !_config.Enabled.Value || !IsAuthority()) { return; } string text = message?.Trim().ToLowerInvariant() ?? string.Empty; if (text != "star" && text != "roll" && text != "gravity" && text != "void" && text != "laser") { return; } string text2 = PlayerIdentity.SteamId(requester); foreach (RoleAssignment assignment in _assignments) { if (assignment.Role == StageRole.Mage && (assignment.Player == requester || (!string.IsNullOrEmpty(text2) && string.Equals(assignment.SteamId, text2, StringComparison.Ordinal)))) { _mage.TryCast(assignment, text); break; } } } internal void RespondToRoleQuery(PlayerAvatar requester) { if ((Object)(object)requester == (Object)null || !_config.Enabled.Value || !IsAuthority()) { return; } string text = PlayerIdentity.SteamId(requester); if (string.IsNullOrEmpty(text)) { text = (((Object)(object)requester.photonView != (Object)null) ? $"view:{requester.photonView.ViewID}" : $"object:{((Object)requester).GetInstanceID()}"); } if (_roleQueryAllowedAt.TryGetValue(text, out var value) && Time.time < value) { return; } _roleQueryAllowedAt[text] = Time.time + 2f; RoleAssignment roleAssignment = null; if (_stageReady) { foreach (RoleAssignment assignment in _assignments) { if (assignment.Player == requester || string.Equals(assignment.SteamId, text, StringComparison.Ordinal)) { roleAssignment = assignment; break; } } } string text2 = ((roleAssignment != null) ? $"YourRole:{roleAssignment.Role}" : "YourRole:Unavailable"); try { NotificationEnemyReactionGuard.Expect(requester, text2); requester.ChatMessageSend(text2); StageRolesPlugin.ModLogger.LogInfo((object)("Answered a role query from player " + text + ".")); } catch (Exception ex) { NotificationEnemyReactionGuard.CancelExpected(requester); StageRolesPlugin.ModLogger.LogWarning((object)("Role query response failed for player " + text + ": " + ex.Message)); } } private IEnumerator PrepareStage(int generation) { yield return (object)new WaitForSeconds(0.75f); float deadline = Time.time + 10f; while (generation == _stageGeneration && (!UpgradeService.Ready || (Object)(object)GameDirector.instance == (Object)null) && Time.time < deadline) { yield return (object)new WaitForSeconds(0.25f); } if (generation != _stageGeneration || !_stageReady || !IsAuthority() || !IsPlayableStageContext()) { yield break; } List list = CollectPlayers(); while (generation == _stageGeneration && (list.Count == 0 || !AllPlayerIdentitiesReady(list)) && Time.time < deadline) { yield return (object)new WaitForSeconds(0.25f); list = CollectPlayers(); } if (list.Count == 0) { StageRolesPlugin.ModLogger.LogWarning((object)"No players were available for stage role assignment."); yield break; } IReadOnlyList targets = RoleCatalog.BaseUpgrades(_config); List list2 = EnabledRoles(); if (!SemiFunc.IsMultiplayer() || list.Count <= 1) { list2.Remove(StageRole.Jobless); StageRolesPlugin.ModLogger.LogDebug((object)"Jobless was excluded from the single-player assignment pool."); } if (list2.Count == 0) { StageRolesPlugin.ModLogger.LogWarning((object)"No roles are enabled with a positive assignment weight."); { foreach (PlayerAvatar item2 in list) { string text = PlayerIdentity.SteamId(item2); if (!string.IsNullOrEmpty(text)) { UpgradeService.SetLevels(text, targets); } } yield break; } } List list3 = new List(list2); bool flag = false; _assignments.Clear(); foreach (PlayerAvatar item3 in list) { string text2 = PlayerIdentity.SteamId(item3); if (string.IsNullOrEmpty(text2)) { continue; } if (list3.Count == 0) { foreach (StageRole item4 in list2) { if (item4 != StageRole.King || !flag) { list3.Add(item4); } } if (list3.Count == 0) { StageRolesPlugin.ModLogger.LogWarning((object)"No repeatable roles remain for additional players."); UpgradeService.SetLevels(text2, targets); continue; } } StageRole stageRole = PickWeightedRole(list3); if (_config.UniqueRoles.Value || stageRole == StageRole.King) { list3.Remove(stageRole); } if (stageRole == StageRole.King) { flag = true; } RoleAssignment item = new RoleAssignment(text2, item3, stageRole); _assignments.Add(item); UpgradeService.SetLevels(text2, RoleCatalog.TargetUpgrades(stageRole, _config)); StageRolesPlugin.ModLogger.LogInfo((object)$"Assigned {stageRole} to player {text2}."); } _bomber.Begin(); _medic.Begin(_assignments); _mage.Begin(_assignments); _gambler.Begin(); _stinker.Begin(_assignments); ApplyKingCrown(); RoleAssignmentSync.Publish(_assignments); _notifier.Begin(_assignments); _nextPlayerRefreshAt = Time.time; } private void Update() { if (!_stageReady || !IsAuthority()) { return; } if (Time.time >= _nextPlayerRefreshAt) { RefreshPlayerReferences(); _nextPlayerRefreshAt = Time.time + 0.5f; } RefreshRescueDeaths(); foreach (RoleAssignment assignment in _assignments) { switch (assignment.Role) { case StageRole.Bomber: _bomber.Tick(assignment); break; case StageRole.Jobless: TickJobless(assignment); break; case StageRole.Phoenix: TickPhoenix(assignment); break; case StageRole.Rescuer: TickRescuer(assignment); break; case StageRole.Tuna: TickTuna(assignment); break; case StageRole.Mage: _mage.Tick(assignment); break; case StageRole.Gambler: _gambler.Tick(assignment, _notifier); break; case StageRole.Stinker: _stinker.Tick(assignment); break; } } _medic.Tick(_assignments); } private void TickJobless(RoleAssignment assignment) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) PlayerAvatar player = assignment.Player; if (!PlayerState.IsLiving(player) || PlayerState.IsInTruck(player)) { assignment.JoblessDamageTimer = 0f; return; } assignment.JoblessDamageTimer += Time.deltaTime; int num = 0; float clampedJoblessInterval = _config.ClampedJoblessInterval; while (assignment.JoblessDamageTimer >= clampedJoblessInterval && num < 20 && PlayerState.IsLiving(player) && !PlayerState.IsInTruck(player)) { assignment.JoblessDamageTimer -= clampedJoblessInterval; num++; try { PlayerHealth playerHealth = player.playerHealth; if (playerHealth != null) { playerHealth.HurtOther(Mathf.Clamp(_config.JoblessDamage.Value, 1, 100), Vector3.zero, false, -1, false); } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Jobless damage was skipped: " + ex.Message)); break; } } } private void TickTuna(RoleAssignment assignment) { //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_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_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_0021: 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) PlayerAvatar player = assignment.Player; Vector3 position = ((Component)player).transform.position; Vector3 val = position - assignment.PreviousPosition; assignment.PreviousPosition = position; if (!PlayerState.IsLiving(player)) { assignment.TunaStationaryTimer = 0f; assignment.TunaDamageTimer = 0f; return; } if (Time.time < _tunaStageGraceUntil) { assignment.TunaStationaryTimer = 0f; assignment.TunaDamageTimer = 0f; return; } if (((Vector3)(ref val)).sqrMagnitude > 2.5E-05f) { assignment.TunaStationaryTimer = 0f; assignment.TunaDamageTimer = 0f; return; } assignment.TunaStationaryTimer += Time.deltaTime; float num = Mathf.Clamp(_config.TunaStationaryDelaySeconds.Value, 0.1f, 30f); if (assignment.TunaStationaryTimer < num) { assignment.TunaDamageTimer = 0f; return; } assignment.TunaDamageTimer += Time.deltaTime; int num2 = 0; float clampedTunaDamageInterval = _config.ClampedTunaDamageInterval; while (assignment.TunaDamageTimer >= clampedTunaDamageInterval && num2 < 20 && PlayerState.IsLiving(player)) { assignment.TunaDamageTimer -= clampedTunaDamageInterval; num2++; try { PlayerHealth playerHealth = player.playerHealth; if (playerHealth != null) { playerHealth.HurtOther(Mathf.Clamp(_config.TunaDamage.Value, 1, 100), Vector3.zero, false, -1, false); } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Tuna damage was skipped: " + ex.Message)); break; } } } private void TickPhoenix(RoleAssignment assignment) { bool flag = PlayerState.IsLiving(assignment.Player); if (assignment.PhoenixUsed) { assignment.WasAlive = flag; } else if (flag) { CompletePhoenixRevival(assignment); assignment.WasAlive = true; assignment.PhoenixReadyAt = 0f; } else if (!assignment.PhoenixRevivePending) { if (assignment.WasAlive) { SchedulePhoenix(assignment); } TryRevivePhoenix(assignment); } } private void TickRescuer(RoleAssignment rescuer) { //IL_0103: 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) int num = Mathf.Clamp(_config.RescuerMaximumRevives.Value, 1, 10); if (rescuer.RescuerRevivesUsed >= num || !PlayerState.IsLiving(rescuer.Player)) { return; } float num2 = Mathf.Clamp(_config.RescuerReviveDelaySeconds.Value, 0f, 10f); float num3 = Mathf.Clamp(_config.RescuerRadius.Value, 1f, 50f); float num4 = num3 * num3; RoleAssignment roleAssignment = null; foreach (RoleAssignment assignment in _assignments) { if (!(assignment.SteamId == rescuer.SteamId) && !_rescueRevivePending.Contains(assignment.SteamId) && _rescueDeadSince.TryGetValue(assignment.SteamId, out var value) && !(Time.time < value + num2) && !PlayerState.IsLiving(assignment.Player) && PlayerState.HasDeathHead(assignment.Player)) { Vector3 val = ((Component)assignment.Player).transform.position - ((Component)rescuer.Player).transform.position; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (!(sqrMagnitude > num4)) { num4 = sqrMagnitude; roleAssignment = assignment; } } } if (roleAssignment == null) { return; } try { roleAssignment.Player.Revive(false); rescuer.RescuerRevivesUsed++; _rescueDeadSince.Remove(roleAssignment.SteamId); _rescueRevivePending.Add(roleAssignment.SteamId); StageRolesPlugin.ModLogger.LogInfo((object)("Rescuer " + rescuer.SteamId + " requested one revival for nearest player " + roleAssignment.SteamId + " " + $"({rescuer.RescuerRevivesUsed}/{num}).")); } catch (Exception ex) { StageRolesPlugin.ModLogger.LogWarning((object)("Rescuer revival failed: " + ex.Message)); } } private void RefreshRescueDeaths() { bool flag = false; foreach (RoleAssignment assignment in _assignments) { if (assignment.Role == StageRole.Rescuer && assignment.RescuerRevivesUsed < Mathf.Clamp(_config.RescuerMaximumRevives.Value, 1, 10)) { flag = true; break; } } if (!flag) { _rescueDeadSince.Clear(); return; } foreach (RoleAssignment assignment2 in _assignments) { if (PlayerState.IsLiving(assignment2.Player)) { _rescueDeadSince.Remove(assignment2.SteamId); if (_rescueRevivePending.Remove(assignment2.SteamId)) { SetRevivalHealth(assignment2, "Rescuer", _config.RescuerRevivalHealth.Value); } } else if (!_rescueRevivePending.Contains(assignment2.SteamId) && !_rescueDeadSince.ContainsKey(assignment2.SteamId)) { _rescueDeadSince[assignment2.SteamId] = Time.time; } } } internal bool PlayerHasRole(PlayerAvatar? player, StageRole role) { if (!_stageReady || (Object)(object)player == (Object)null) { return false; } string text = PlayerIdentity.SteamId(player); foreach (RoleAssignment assignment in _assignments) { if (assignment.Role == role && (assignment.Player == player || (!string.IsNullOrEmpty(text) && string.Equals(assignment.SteamId, text, StringComparison.Ordinal)))) { return true; } } return false; } internal void RecordEnemyAttacker(Enemy? enemy, PlayerAvatar? attacker) { if (!_stageReady || !IsAuthority() || (Object)(object)enemy == (Object)null) { return; } EnemyHealth component = ((Component)enemy).GetComponent(); if (!((Object)(object)component == (Object)null)) { int instanceID = ((Object)component).GetInstanceID(); string value = PlayerIdentity.SteamId(attacker); if (string.IsNullOrEmpty(value)) { _enemyLastAttacker.Remove(instanceID); } else { _enemyLastAttacker[instanceID] = value; } } } internal void EnemyDied(EnemyHealth enemyHealth) { //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_00db: 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_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) if (!_stageReady || !IsAuthority() || (Object)(object)enemyHealth == (Object)null) { return; } Vector3 position = ((Component)enemyHealth).transform.position; float num = Mathf.Clamp(_config.VampireRadius.Value, 1f, 50f); float num2 = num * num; int num3 = Mathf.Clamp(ResolveEnemyTier(enemyHealth) switch { 3 => _config.VampireTier3HealAmount.Value, 2 => _config.VampireTier2HealAmount.Value, _ => _config.VampireTier1HealAmount.Value, }, 1, 100); foreach (RoleAssignment assignment in _assignments) { if (assignment.Role != StageRole.Vampire || !PlayerState.IsLiving(assignment.Player)) { continue; } Vector3 val = ((Component)assignment.Player).transform.position - position; if (((Vector3)(ref val)).sqrMagnitude > num2) { continue; } try { PlayerHealth playerHealth = assignment.Player.playerHealth; if (playerHealth != null) { playerHealth.HealOther(num3, true); } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Vampire healing was skipped: " + ex.Message)); } } } internal int CaptureEnemyOrbSpawnCount(EnemyParent enemyParent) { if (!_stageReady || !IsAuthority() || (Object)(object)enemyParent == (Object)null) { return -1; } return ReadEnemyOrbSpawnCount(((Component)enemyParent).GetComponentInChildren(true)); } internal void EnemyDespawned(EnemyParent enemyParent, int previousSpawnCount) { if (!_stageReady || !IsAuthority() || (Object)(object)enemyParent == (Object)null || previousSpawnCount < 0) { return; } EnemyHealth componentInChildren = ((Component)enemyParent).GetComponentInChildren(true); int num = ReadEnemyOrbSpawnCount(componentInChildren) - previousSpawnCount; if ((Object)(object)componentInChildren == (Object)null || num <= 0) { return; } int instanceID = ((Object)componentInChildren).GetInstanceID(); if (!_enemyLastAttacker.Remove(instanceID, out string value) || !RoleAssignedTo(value, StageRole.Hunter)) { return; } float num2 = Mathf.Clamp(_config.HunterJackpotOrbChancePercent.Value, 0f, 100f) / 100f; float num3 = Mathf.Clamp(_config.HunterDoubleOrbChancePercent.Value, 0f, 100f) / 100f; int num4; string arg; if (Random.value < num2) { num4 = Mathf.Clamp(_config.HunterJackpotOrbCount.Value, 1, 100); arg = "jackpot"; } else { if (!(Random.value < num3)) { return; } num4 = Mathf.Clamp(num * 2, 1, 100); arg = "double"; } int extraCount = Mathf.Max(0, num4 - num); int num5 = SpawnHunterBonusOrbs(componentInChildren, extraCount); int num6 = num + num5; StageRolesPlugin.ModLogger.LogInfo((object)($"Hunter orb roll {arg}: {num} -> " + $"{num6} for player {value}.")); } private static int ReadEnemyOrbSpawnCount(EnemyHealth? enemyHealth) { try { return ((Object)(object)enemyHealth != (Object)null && EnemySpawnValuableCurrentField?.GetValue(enemyHealth) is int num) ? num : (-1); } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Hunter orb count could not be read: " + ex.Message)); return -1; } } private static int SpawnHunterBonusOrbs(EnemyHealth enemyHealth, int extraCount) { //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_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_007e: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) if (extraCount <= 0 || (Object)(object)AssetManager.instance == (Object)null) { return 0; } GameObject val = (GameObject)(ResolveEnemyTier(enemyHealth) switch { 3 => AssetManager.instance.enemyValuableBig, 2 => AssetManager.instance.enemyValuableMedium, _ => AssetManager.instance.enemyValuableSmall, }); if ((Object)(object)val == (Object)null) { return 0; } int num = 0; Vector3 val2 = ResolveEnemyOrbSpawnPosition(enemyHealth); for (int i = 0; i < extraCount; i++) { try { if (SemiFunc.IsMultiplayer()) { PhotonNetwork.InstantiateRoomObject("Valuables/" + ((Object)val).name, val2, Quaternion.identity, (byte)0, (object[])null); } else { Object.Instantiate(val, val2, Quaternion.identity); } num++; } catch (Exception ex) { StageRolesPlugin.ModLogger.LogWarning((object)$"Hunter bonus orb spawning stopped after {num}/{extraCount}: {ex.Message}"); break; } } return num; } private static Vector3 ResolveEnemyOrbSpawnPosition(EnemyHealth enemyHealth) { //IL_0078: 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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) try { object? obj = EnemyHealthEnemyField?.GetValue(enemyHealth); Enemy val = (Enemy)((obj is Enemy) ? obj : null); if (val != null) { if ((Object)(object)val.CustomValuableSpawnTransform != (Object)null) { return val.CustomValuableSpawnTransform.position; } if ((Object)(object)val.CenterTransform != (Object)null) { return val.CenterTransform.position; } } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Hunter orb spawn position fell back to enemy health: " + ex.Message)); } return ((Component)enemyHealth).transform.position; } private bool RoleAssignedTo(string steamId, StageRole role) { foreach (RoleAssignment assignment in _assignments) { if (assignment.Role == role && string.Equals(assignment.SteamId, steamId, StringComparison.Ordinal)) { return true; } } return false; } internal void InstrumentNotePlayed(int grabberViewId) { //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0129: 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) if (!_stageReady || !IsAuthority()) { return; } PlayerAvatar val = ResolveInstrumentPlayer(grabberViewId); if ((Object)(object)val == (Object)null) { return; } string text = PlayerIdentity.SteamId(val); bool flag = false; foreach (RoleAssignment assignment in _assignments) { if (assignment.Role == StageRole.Musician && (assignment.Player == val || (!string.IsNullOrEmpty(text) && string.Equals(assignment.SteamId, text, StringComparison.Ordinal)))) { flag = true; val = assignment.Player; break; } } if (!flag || !PlayerState.IsLiving(val)) { return; } float num = Mathf.Clamp(_config.MusicianHealRadius.Value, 1f, 50f); float num2 = num * num; int num3 = Mathf.Clamp(_config.MusicianHealAmount.Value, 1, 100); int num4 = 0; if ((Object)(object)GameDirector.instance == (Object)null) { return; } foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if (!PlayerState.IsLiving(player)) { continue; } Vector3 val2 = ((Component)player).transform.position - ((Component)val).transform.position; if (((Vector3)(ref val2)).sqrMagnitude > num2) { continue; } try { PlayerHealth playerHealth = player.playerHealth; if (playerHealth != null) { playerHealth.HealOther(num3, true); } num4++; } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Musician healing was skipped: " + ex.Message)); } } StageRolesPlugin.ModLogger.LogDebug((object)$"Musician note healed {num4} player(s) for {num3} HP."); } private static PlayerAvatar? ResolveInstrumentPlayer(int grabberViewId) { try { PhysGrabber val = null; if (SemiFunc.IsMultiplayer() && grabberViewId >= 0) { PhotonView val2 = PhotonView.Find(grabberViewId); val = (((Object)(object)val2 != (Object)null) ? ((Component)val2).GetComponent() : null); } else { val = PhysGrabber.instance; } return val?.playerAvatar; } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Musician instrument player could not be resolved: " + ex.Message)); return null; } } private static int ResolveEnemyTier(EnemyHealth enemyHealth) { try { object? obj = EnemyHealthEnemyField?.GetValue(enemyHealth); Enemy val = (Enemy)((obj is Enemy) ? obj : null); if (val != null) { object? obj2 = EnemyParentField?.GetValue(val); EnemyParent val2 = (EnemyParent)((obj2 is EnemyParent) ? obj2 : null); if (val2 != null) { object obj3 = EnemyDifficultyField?.GetValue(val2); if (obj3 != null) { int num = Convert.ToInt32(obj3) + 1; if (num >= 1 && num <= 3) { return num; } } } } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogDebug((object)("Vampire enemy tier resolution failed: " + ex.Message)); } return 1; } private void SchedulePhoenix(RoleAssignment assignment) { if (assignment.PhoenixReadyAt <= 0f) { assignment.PhoenixReadyAt = Time.time + Mathf.Clamp(_config.PhoenixReviveDelaySeconds.Value, 2f, 10f); StageRolesPlugin.ModLogger.LogDebug((object)("Phoenix revival scheduled for player " + assignment.SteamId + ".")); } assignment.WasAlive = true; } private void TryRevivePhoenix(RoleAssignment assignment) { if (assignment.PhoenixUsed || assignment.PhoenixRevivePending || assignment.PhoenixReadyAt <= 0f || Time.time < assignment.PhoenixReadyAt || !PlayerState.HasDeathHead(assignment.Player)) { return; } try { assignment.PhoenixRevivePending = true; assignment.PhoenixReadyAt = 0f; assignment.Player.Revive(false); StageRolesPlugin.ModLogger.LogInfo((object)("Phoenix requested one revival for player " + assignment.SteamId + ".")); if (PlayerState.IsLiving(assignment.Player)) { CompletePhoenixRevival(assignment); } } catch (Exception ex) { assignment.PhoenixRevivePending = false; StageRolesPlugin.ModLogger.LogWarning((object)("Phoenix revival failed: " + ex.Message)); } } private void CompletePhoenixRevival(RoleAssignment assignment) { if (assignment.PhoenixRevivePending) { assignment.PhoenixRevivePending = false; assignment.PhoenixUsed = true; assignment.PhoenixReadyAt = 0f; assignment.WasAlive = true; SetRevivalHealth(assignment, "Phoenix", _config.PhoenixRevivalHealth.Value); StageRolesPlugin.ModLogger.LogInfo((object)("Phoenix revival synchronized for player " + assignment.SteamId + ".")); } } private static void SetRevivalHealth(RoleAssignment assignment, string source, int configuredHealth) { int num = Mathf.Clamp(configuredHealth, 1, 1000); try { if (!PlayerState.SetHealthSynchronized(assignment.Player, num)) { StageRolesPlugin.ModLogger.LogWarning((object)(source + " could not set revival health for player " + assignment.SteamId + ".")); } else { StageRolesPlugin.ModLogger.LogInfo((object)$"{source} set revival health to {num} for player {assignment.SteamId}."); } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogWarning((object)(source + " revival health update failed for player " + assignment.SteamId + ": " + ex.Message)); } } private void RefreshPlayerReferences() { List list = CollectPlayers(); Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (PlayerAvatar item in list) { string text = PlayerIdentity.SteamId(item); if (!string.IsNullOrEmpty(text)) { dictionary[text] = item; } } HashSet hashSet = ConnectedSteamIds(dictionary); if (hashSet.Count == 0) { return; } List list2 = new List(); for (int num = _assignments.Count - 1; num >= 0; num--) { RoleAssignment roleAssignment = _assignments[num]; PlayerAvatar value; if (!hashSet.Contains(roleAssignment.SteamId)) { list2.Add(roleAssignment); _assignments.RemoveAt(num); } else if (dictionary.TryGetValue(roleAssignment.SteamId, out value)) { roleAssignment.Player = value; } } if (list2.Count == 0) { return; } foreach (RoleAssignment item2 in list2) { _bomber.RemovePlayer(item2.SteamId); _medic.RemovePlayer(item2.SteamId); _stinker.RemovePlayer(item2.SteamId); _rescueDeadSince.Remove(item2.SteamId); _rescueRevivePending.Remove(item2.SteamId); _roleQueryAllowedAt.Remove(item2.SteamId); if (item2.Role == StageRole.King) { RestoreKingCrown(); } StageRolesPlugin.ModLogger.LogInfo((object)$"Removed departed player {item2.SteamId} ({item2.Role}) from the active role list."); } RoleAssignmentSync.Publish(_assignments); } private static HashSet ConnectedSteamIds(IReadOnlyDictionary playersBySteamId) { HashSet hashSet = new HashSet(StringComparer.Ordinal); if (SemiFunc.IsMultiplayer() && PhotonNetwork.InRoom) { Player[] playerList = PhotonNetwork.PlayerList; foreach (Player val in playerList) { if (!string.IsNullOrEmpty((val != null) ? val.UserId : null)) { hashSet.Add(val.UserId); } } if (hashSet.Count > 0) { return hashSet; } } foreach (string key in playersBySteamId.Keys) { hashSet.Add(key); } return hashSet; } private void StageEndingInternal(bool restoreBaseUpgrades, bool deactivate) { _stageReady = false; _stageGeneration++; ((MonoBehaviour)this).StopAllCoroutines(); _notifier?.End(); _bomber?.Stop(); _medic?.Stop(); _mage?.Stop(); _gambler?.Stop(); _stinker?.Stop(); HunterBatteryRuntime.Clear(); RestoreKingCrown(); RoleAssignmentSync.ClearPreview(); if (IsAuthority()) { RoleAssignmentSync.Clear(); } if (restoreBaseUpgrades && IsAuthority()) { HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (RoleAssignment assignment in _assignments) { hashSet.Add(assignment.SteamId); } IReadOnlyList targets = RoleCatalog.BaseUpgrades(_config); foreach (string item in hashSet) { UpgradeService.SetLevels(item, targets); } } _assignments.Clear(); _rescueDeadSince.Clear(); _rescueRevivePending.Clear(); _roleQueryAllowedAt.Clear(); _enemyLastAttacker.Clear(); _grabbersByObjectId.Clear(); _engineerSuppressedTrapIds.Clear(); _engineerSuppressionGraceUntil.Clear(); _levelGeneratorInstanceId = 0; _nextPlayerRefreshAt = 0f; _phoenixFailureGraceUntil = 0f; _tunaStageGraceUntil = 0f; if (deactivate && ((Component)this).gameObject.activeSelf) { ((Component)this).gameObject.SetActive(false); } } private void ApplyKingCrown(string preferredSteamId = "") { if (!string.IsNullOrEmpty(preferredSteamId)) { foreach (RoleAssignment assignment in _assignments) { if (assignment.Role == StageRole.King && string.Equals(assignment.SteamId, preferredSteamId, StringComparison.Ordinal)) { ApplyKingCrownTo(assignment); return; } } } foreach (RoleAssignment assignment2 in _assignments) { if (assignment2.Role == StageRole.King && !((Object)(object)PunManager.instance == (Object)null) && !((Object)(object)SessionManager.instance == (Object)null)) { ApplyKingCrownTo(assignment2); break; } } } private void ApplyKingCrownTo(RoleAssignment assignment) { if ((Object)(object)PunManager.instance == (Object)null || (Object)(object)SessionManager.instance == (Object)null) { return; } try { _previousCrownedSteamId = SessionManager.instance.crownedPlayerSteamID ?? string.Empty; PunManager.instance.CrownPlayerSync(assignment.SteamId); _kingCrownApplied = true; StageRolesPlugin.ModLogger.LogInfo((object)("King crown assigned to player " + assignment.SteamId + ".")); } catch (Exception ex) { StageRolesPlugin.ModLogger.LogWarning((object)("King crown assignment failed: " + ex.Message)); } } private void RestoreKingCrown() { if (!_kingCrownApplied) { return; } try { if ((Object)(object)PunManager.instance != (Object)null) { PunManager.instance.CrownPlayerSync(_previousCrownedSteamId); } else if ((Object)(object)SessionManager.instance != (Object)null) { SessionManager.instance.crownedPlayerSteamID = _previousCrownedSteamId; } } catch (Exception ex) { StageRolesPlugin.ModLogger.LogWarning((object)("King crown restoration failed: " + ex.Message)); } _kingCrownApplied = false; _previousCrownedSteamId = string.Empty; } private static List CollectPlayers() { List list = new List(); if ((Object)(object)GameDirector.instance == (Object)null) { return list; } foreach (PlayerAvatar player in GameDirector.instance.PlayerList) { if ((Object)(object)player != (Object)null && ((Component)player).gameObject.activeInHierarchy) { list.Add(player); } } return list; } private RoleAssignment? ResolveAssignment(string targetIdentifier) { if (string.IsNullOrWhiteSpace(targetIdentifier)) { string b = PlayerIdentity.SteamId(SemiFunc.PlayerGetLocal()); foreach (RoleAssignment assignment in _assignments) { if (string.Equals(assignment.SteamId, b, StringComparison.Ordinal)) { return assignment; } } return null; } string text = targetIdentifier.Trim(); foreach (RoleAssignment assignment2 in _assignments) { if (string.Equals(assignment2.SteamId, text, StringComparison.OrdinalIgnoreCase) || string.Equals(PlayerIdentity.Name(assignment2.Player), text, StringComparison.OrdinalIgnoreCase)) { return assignment2; } } RoleAssignment roleAssignment = null; foreach (RoleAssignment assignment3 in _assignments) { if (PlayerIdentity.Name(assignment3.Player).IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { if (roleAssignment != null) { return null; } roleAssignment = assignment3; } } return roleAssignment; } private static bool AllPlayerIdentitiesReady(IEnumerable players) { foreach (PlayerAvatar player in players) { if (string.IsNullOrEmpty(PlayerIdentity.SteamId(player))) { return false; } } return true; } private List EnabledRoles() { List list = new List(); foreach (StageRole allRole in RoleCatalog.AllRoles) { if (_config.RoleIsEnabled(allRole) && _config.RoleWeightValue(allRole) > 0) { list.Add(allRole); } } return list; } private StageRole PickWeightedRole(IReadOnlyList roles) { int num = 0; foreach (StageRole role in roles) { num += Mathf.Max(0, _config.RoleWeightValue(role)); } int num2 = Random.Range(0, Mathf.Max(1, num)); foreach (StageRole role2 in roles) { num2 -= Mathf.Max(0, _config.RoleWeightValue(role2)); if (num2 < 0) { return role2; } } return roles[roles.Count - 1]; } private static bool IsAuthority() { try { return (Object)(object)GameManager.instance != (Object)null && SemiFunc.IsMasterClientOrSingleplayer(); } catch { return false; } } private static bool IsPlayableStageContext() { try { return SemiFunc.RunIsLevel() && !SemiFunc.RunIsLobby() && !SemiFunc.RunIsShop() && !SemiFunc.RunIsArena() && !SemiFunc.RunIsTutorial(); } catch { return false; } } } [HarmonyAfter(new string[] { "REPOJP.StagePhysicsEvents" })] internal static class LifecyclePatches { private static readonly FieldInfo? HurtColliderPlayerField = AccessTools.Field(typeof(HurtCollider), "playerCausingHurt"); [HarmonyPostfix] [HarmonyPatch(typeof(LevelGenerator), "GenerateDone")] private static void LevelGeneratorGenerateDonePostfix(LevelGenerator __instance) { StageRolesPlugin.Instance?.Controller?.StageReady(__instance); } [HarmonyPostfix] [HarmonyPatch(typeof(RoundDirector), "ExtractionCompleted")] private static void RoundDirectorExtractionCompletedPostfix(RoundDirector __instance) { try { StageRolesPlugin.Instance?.Controller?.ExtractionCompleted(__instance); } catch (Exception arg) { StageRolesPlugin.ModLogger.LogError((object)("Gambler extraction recovery was skipped without interrupting " + $"the vanilla extraction: {arg}")); } } [HarmonyPrefix] [HarmonyPatch(typeof(RunManager), "ChangeLevel")] private static bool RunManagerChangeLevelPrefix(RunManager __instance, bool _levelFailed) { StageRoleController stageRoleController = StageRolesPlugin.Instance?.Controller; if (_levelFailed && (Object)(object)stageRoleController != (Object)null && stageRoleController.TryPreventPhoenixRetake()) { __instance.AllPlayersDeadSet(false); StageRolesPlugin.ModLogger.LogInfo((object)"Phoenix prevented a failed-stage retake."); return false; } return true; } [HarmonyPostfix] [HarmonyPatch(typeof(RunManager), "ChangeLevel")] private static void RunManagerChangeLevelPostfix(bool __runOriginal) { if (__runOriginal) { StageRolesPlugin.Instance?.Controller?.StageEnding(); } } [HarmonyPostfix] [HarmonyPatch(typeof(EnemyHealth), "DeathImpulseRPC")] private static void EnemyHealthDeathImpulseRpcPostfix(EnemyHealth __instance) { StageRolesPlugin.Instance?.Controller?.EnemyDied(__instance); } [HarmonyPrefix] [HarmonyPatch(typeof(EnemyParent), "Despawn")] private static void EnemyParentDespawnPrefix(EnemyParent __instance, out int __state) { __state = StageRolesPlugin.Instance?.Controller?.CaptureEnemyOrbSpawnCount(__instance) ?? (-1); } [HarmonyPostfix] [HarmonyPatch(typeof(EnemyParent), "Despawn")] private static void EnemyParentDespawnPostfix(EnemyParent __instance, int __state) { StageRolesPlugin.Instance?.Controller?.EnemyDespawned(__instance, __state); } [HarmonyPostfix] [HarmonyPatch(typeof(HurtCollider), "EnemyHurt")] private static void HurtColliderEnemyHurtPostfix(HurtCollider __instance, Enemy __0, bool __result) { if (__result) { object? obj = HurtColliderPlayerField?.GetValue(__instance); PlayerAvatar attacker = (PlayerAvatar)((obj is PlayerAvatar) ? obj : null); StageRolesPlugin.Instance?.Controller?.RecordEnemyAttacker(__0, attacker); } } [HarmonyPostfix] [HarmonyPatch(typeof(MusicalValuableLogic), "MusicKeyPressedRPC")] private static void MusicalValuableLogicMusicKeyPressedRpcPostfix(int grabberID) { StageRolesPlugin.Instance?.Controller?.InstrumentNotePlayed(grabberID); } [HarmonyPrefix] [HarmonyPatch(typeof(Trap), "TrapActivateSync")] private static bool TrapActivateSyncPrefix(Trap __instance) { StageRolesPlugin instance = StageRolesPlugin.Instance; if (instance == null) { return true; } return instance.Controller?.ShouldSuppressEngineerTrap(__instance) != true; } [HarmonyPrefix] [HarmonyPatch(typeof(Trap), "TrapStart")] private static bool TrapStartPrefix(Trap __instance) { StageRolesPlugin instance = StageRolesPlugin.Instance; if (instance == null) { return true; } return instance.Controller?.ShouldSuppressEngineerTrap(__instance) != true; } [HarmonyPrefix] [HarmonyPatch(typeof(PlayerAvatar), "ChatMessageSendRPC")] private static void PlayerAvatarChatMessageSendRpcPrefix(PlayerAvatar __instance, string _message, out bool __state) { StageRoleController stageRoleController = StageRolesPlugin.Instance?.Controller; __state = (Object)(object)stageRoleController != (Object)null && stageRoleController.IsRoleQueryRequest(_message); if (__state) { NotificationEnemyReactionGuard.Expect(__instance, _message); } } [HarmonyPostfix] [HarmonyPatch(typeof(PlayerAvatar), "ChatMessageSendRPC")] private static void PlayerAvatarChatMessageSendRpcPostfix(PlayerAvatar __instance, string _message, bool __state) { if (__state) { StageRolesPlugin.Instance?.Controller?.RespondToRoleQuery(__instance); } StageRolesPlugin.Instance?.Controller?.TryHandleMageCast(__instance, _message); } [HarmonyPrefix] [HarmonyPatch(typeof(PhysGrabObject), "GrabStartedRPC")] private static bool PhysGrabObjectGrabStartedRpcPrefix(PhysGrabObject __instance, int playerPhotonID, PhotonMessageInfo _info) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) StageRoleController stageRoleController = StageRolesPlugin.Instance?.Controller; if (!((Object)(object)stageRoleController == (Object)null) && !stageRoleController.AllowBomberGrenadeGrabStart(__instance, playerPhotonID, _info)) { return false; } if (stageRoleController != null && stageRoleController.TryRegisterEngineerHostOnlyGrab(__instance, playerPhotonID, _info)) { return false; } stageRoleController?.RecordGrabStarted(__instance, playerPhotonID); return true; } [HarmonyPrefix] [HarmonyPatch(typeof(PhysGrabObject), "GrabEndedRPC")] private static bool PhysGrabObjectGrabEndedRpcPrefix(PhysGrabObject __instance, int playerPhotonID, PhotonMessageInfo _info) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) StageRoleController stageRoleController = StageRolesPlugin.Instance?.Controller; stageRoleController?.RecordGrabEnded(__instance, playerPhotonID); if (!((Object)(object)stageRoleController == (Object)null)) { return stageRoleController.AllowBomberGrenadeGrabEnd(__instance, playerPhotonID, _info); } return true; } } [HarmonyPatch] internal static class EngineerTrapUpdatePatches { private static readonly string[] FrameMethodNames = new string[3] { "Update", "FixedUpdate", "LateUpdate" }; private static IEnumerable TargetMethods() { HashSet hashSet = new HashSet(); Type[] types = typeof(Trap).Assembly.GetTypes(); foreach (Type type in types) { if (typeof(Trap).IsAssignableFrom(type)) { AddFrameMethods(type, hashSet); } } string[] frameEffectTypeNames = EngineerEffectCatalog.FrameEffectTypeNames; for (int i = 0; i < frameEffectTypeNames.Length; i++) { Type type2 = AccessTools.TypeByName(frameEffectTypeNames[i]); if (type2 != null) { AddFrameMethods(type2, hashSet); } } foreach (MethodBase item in hashSet) { yield return item; } } private static void AddFrameMethods(Type type, HashSet targets) { string[] frameMethodNames = FrameMethodNames; foreach (string text in frameMethodNames) { MethodInfo methodInfo = AccessTools.DeclaredMethod(type, text, Type.EmptyTypes, (Type[])null); if (methodInfo != null && !methodInfo.IsStatic && methodInfo.ReturnType == typeof(void)) { targets.Add(methodInfo); } } } [HarmonyPrefix] private static bool EffectFramePrefix(Component __instance) { StageRolesPlugin instance = StageRolesPlugin.Instance; if (instance == null) { return true; } return instance.Controller?.ShouldSuppressEngineerEffect(__instance) != true; } } [HarmonyPatch] internal static class EngineerEffectActivationPatches { private static readonly (string TypeName, string MethodName)[] Targets = new(string, string)[10] { ("FireExtinguisherValuable", "GrabTrigger"), ("FlamethrowerValuable", "GrabTrigger"), ("MusicalValuableLogic", "MusicKeyPressed"), ("PhoneValuable", "PickUp"), ("PowerCrystalValuable", "Explode"), ("ValuableCamera", "Explosion"), ("ValuableCauldronBox", "TrapActivate"), ("ValuableLevitationPotion", "ActivateSphere"), ("ValuableStarWand", "CastSpell"), ("ValuableWizardStaff", "StaffLaser") }; private static IEnumerable TargetMethods() { HashSet hashSet = new HashSet(); Type[] types = typeof(Trap).Assembly.GetTypes(); foreach (Type type in types) { if (typeof(Trap).IsAssignableFrom(type)) { MethodInfo methodInfo = AccessTools.DeclaredMethod(type, "TrapActivate", Type.EmptyTypes, (Type[])null); if (methodInfo != null && !methodInfo.IsStatic) { hashSet.Add(methodInfo); } } } (string, string)[] targets = Targets; for (int i = 0; i < targets.Length; i++) { (string, string) tuple = targets[i]; string item = tuple.Item1; string item2 = tuple.Item2; Type type2 = AccessTools.TypeByName(item); MethodInfo methodInfo2 = ((type2 != null) ? AccessTools.DeclaredMethod(type2, item2, (Type[])null, (Type[])null) : null); if (methodInfo2 != null && !methodInfo2.IsStatic) { hashSet.Add(methodInfo2); } } foreach (MethodBase item3 in hashSet) { yield return item3; } } [HarmonyPrefix] private static bool EffectActivationPrefix(Component __instance) { StageRolesPlugin instance = StageRolesPlugin.Instance; if (instance == null) { return true; } return instance.Controller?.ShouldSuppressEngineerEffect(__instance) != true; } } internal static class EngineerEffectCatalog { internal static readonly string[] FrameEffectTypeNames = new string[25] { "CrystalBallValuable", "FireExtinguisherValuable", "FlamethrowerValuable", "JackhammerValuable", "MapValuable", "MusicalValuableLogic", "PhoneValuable", "PowerCrystalValuable", "ScreamDollValuable", "UraniumScript", "ValuableArcticSnowBike", "ValuableCamera", "ValuableCauldronBox", "ValuableEyeOfOrpigox", "ValuableFlashlight", "ValuableLevitationPotion", "ValuableLovePotion", "ValuablePills", "ValuableSawBlade", "ValuableScale", "ValuableSpiderPotion", "ValuableStarWand", "ValuableTeethBot", "ValuableWizardStaff", "ValuableWizardTimeGlass" }; private static readonly HashSet EffectTypeNames = new HashSet(FrameEffectTypeNames, StringComparer.Ordinal); internal static bool IsEffectValuable(PhysGrabObject valuable) { if ((Object)(object)((Component)valuable).GetComponent() != (Object)null || (Object)(object)((Component)valuable).GetComponentInChildren(true) != (Object)null) { return true; } Component[] componentsInChildren = ((Component)valuable).GetComponentsInChildren(true); foreach (Component val in componentsInChildren) { if ((Object)(object)val != (Object)null && EffectTypeNames.Contains(((object)val).GetType().Name)) { return true; } } return false; } } [HarmonyAfter(new string[] { "REPOJP.StagePhysicsEvents" })] internal static class ShopPatches { [HarmonyPostfix] [HarmonyPatch(typeof(ShopManager), "GetAllItemsFromStatsManager")] private static void ShopManagerGetAllItemsFromStatsManagerPostfix(ShopManager __instance) { StageRolesPlugin instance = StageRolesPlugin.Instance; if (instance != null && instance.Settings.Enabled.Value && SemiFunc.IsMasterClientOrSingleplayer()) { int num = (__instance.itemUpgradesAmount = Mathf.Clamp(StageRolesPlugin.Instance.Settings.ShopUpgradeItemCount.Value, 0, 30)); if (num == 0) { __instance.potentialItemUpgrades?.Clear(); } StageRolesPlugin.ModLogger.LogDebug((object)$"Shop upgrade item count set to {num}."); } } } }