Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of RoleShuffle v4.4.7
RoleShuffle.dll
Decompiled 5 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Serialization.Json; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; 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.TextCore; using UnityEngine.TextCore.LowLevel; 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.4.7.0")] [assembly: AssemblyInformationalVersion("4.4.7+9269e61b2c38bc59797b55ed8c9f2ec1d30b0e55")] [assembly: AssemblyProduct("RoleShuffle")] [assembly: AssemblyTitle("RoleShuffle")] [assembly: AssemblyVersion("4.4.7.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.4.7")] [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.4.7"; public const string StageFluxGuid = "REPOJP.StagePhysicsEvents"; public const string RepoConfigGuid = "nickklmao.repoconfig"; public const string MenuLibGuid = "nickklmao.menulib"; private Harmony? _harmony; private GameObject? _controllerObject; private object? _guidePublishedRoom; private string _publishedBaseUpgradeSignature = string.Empty; private float _nextBaseUpgradePublishAt; private bool _settingsPublishPending; private float _settingsPublishAt; private float _nextPrivacyRefresh; internal RoleBugReport BugReport { get; private set; } internal static StageRolesPlugin Instance { get; private set; } internal static ManualLogSource ModLogger { get; private set; } internal StageRolesConfig Settings { get; private set; } internal RoleSelectionSettings RoleSettings { get; private set; } internal BaseUpgradeSelectionSettings BaseUpgradeSettings { get; private set; } internal StageRoleController Controller { get; private set; } internal BaseUpgradeDrawRuntime BaseUpgradeDraw { get; private set; } internal void SaveLocalSettings() { ((BaseUnityPlugin)this).Config.Save(); } internal void CreateBugReport() { BugReport.Create(((BaseUnityPlugin)this).Config); } private void Awake() { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Expected O, but got Unknown Instance = this; ModLogger = ((BaseUnityPlugin)this).Logger; BugReport = new RoleBugReport(); Logger.Listeners.Add((ILogListener)(object)BugReport); Settings = new StageRolesConfig(((BaseUnityPlugin)this).Config); RoleSettings = new RoleSelectionSettings(Settings, ((BaseUnityPlugin)this).Config); BaseUpgradeSettings = new BaseUpgradeSelectionSettings(Settings, ((BaseUnityPlugin)this).Config); ((BaseUnityPlugin)this).Config.SettingChanged += ConfigSettingChanged; ((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<StageRoleController>(); Controller.Initialize(Settings); ((Component)this).gameObject.AddComponent<RoleHud>().Initialize(Settings); ((Component)this).gameObject.AddComponent<RoleMenu>().Initialize(Settings); ((Component)this).gameObject.AddComponent<RoleSyncStatus>().Initialize(Settings); BaseUpgradeDraw = ((Component)this).gameObject.AddComponent<BaseUpgradeDrawRuntime>(); BaseUpgradeDraw.Initialize(Settings); ((Component)this).gameObject.AddComponent<RoleTestCommandService>().Initialize(Settings, Controller); _harmony = new Harmony("REPOJP.RoleShuffle"); try { _harmony.PatchAll(typeof(LifecyclePatches)); _harmony.PatchAll(typeof(RoleExpressionPatches)); _harmony.PatchAll(typeof(NinjaVisionPatch)); _harmony.PatchAll(typeof(NinjaVoiceInvestigationPatch)); _harmony.PatchAll(typeof(NinjaLandingInvestigationPatch)); _harmony.PatchAll(typeof(NinjaFootstepInvestigationPatch)); _harmony.PatchAll(typeof(InfluencerFootstepInvestigationPatch)); _harmony.PatchAll(typeof(InfluencerLandingInvestigationPatch)); _harmony.PatchAll(typeof(InfluencerVoiceInvestigationPatch)); _harmony.PatchAll(typeof(InfluencerInvestigationRadiusPatch)); _harmony.PatchAll(typeof(EngineerTrapUpdatePatches)); _harmony.PatchAll(typeof(EngineerEffectActivationPatches)); _harmony.PatchAll(typeof(ShopPatches)); _harmony.PatchAll(typeof(NotificationEnemyReactionPatches)); _harmony.PatchAll(typeof(HunterBatteryPatches)); _harmony.PatchAll(typeof(MageStaffProjectilePatch)); _harmony.PatchAll(typeof(MageStaffImpactPatch)); _harmony.PatchAll(typeof(MageStaffVoidDurationPatch)); _harmony.PatchAll(typeof(MageStaffBeamDurationPatch)); _harmony.PatchAll(typeof(RoleGuideScrollPatch)); SceneManager.activeSceneChanged += ActiveSceneChanged; ((BaseUnityPlugin)this).Logger.LogInfo((object)"RoleShuffle 4.4.7 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) { RoleHudTestPreview.Clear(); RoleHudEditor.Instance?.Close(save: false); BaseUpgradeHistory.ClearDisplayPreview(); Controller?.StageEnding(); } private void Update() { RoleGuideFont.InstallNativeNameFallback(); if (Time.unscaledTime >= _nextPrivacyRefresh) { _nextPrivacyRefresh = Time.unscaledTime + 1f; BugReport.RememberPlayers(); } if ((Object)(object)GameManager.instance == (Object)null) { return; } if (_settingsPublishPending && Time.unscaledTime >= _settingsPublishAt) { _settingsPublishPending = false; RoleGuideSync.Publish(Settings); BaseUpgradeSync.Publish(Settings); _publishedBaseUpgradeSignature = BaseUpgradeSync.LocalPublishSignature(Settings); } object currentRoom = PhotonNetwork.CurrentRoom; if (!SemiFunc.IsMultiplayer() || !PhotonNetwork.IsMasterClient || currentRoom == null) { _guidePublishedRoom = null; _publishedBaseUpgradeSignature = string.Empty; return; } if (_guidePublishedRoom != currentRoom) { RoleGuideSync.Publish(Settings); BaseUpgradeSync.Publish(Settings); _guidePublishedRoom = currentRoom; _publishedBaseUpgradeSignature = BaseUpgradeSync.LocalPublishSignature(Settings); _nextBaseUpgradePublishAt = Time.unscaledTime + 0.5f; } if (!(Time.unscaledTime < _nextBaseUpgradePublishAt)) { _nextBaseUpgradePublishAt = Time.unscaledTime + 0.5f; string text = BaseUpgradeSync.LocalPublishSignature(Settings); if (!string.Equals(text, _publishedBaseUpgradeSignature, StringComparison.Ordinal)) { BaseUpgradeSync.Publish(Settings); _publishedBaseUpgradeSignature = text; } } } private void ConfigSettingChanged(object sender, SettingChangedEventArgs args) { if (!Settings.SetRoleCommandEnabled.Value) { BaseUpgradeHistory.ClearDisplayPreview(); RoleHudTestPreview.Clear(); } string section = args.ChangedSetting.Definition.Section; if ((!(section == "HUD") && !(section == "UI")) || 1 == 0) { RoleGuideSync.Invalidate(); _settingsPublishPending = true; _settingsPublishAt = Time.unscaledTime + 0.15f; } } private void OnDestroy() { RoleHudTestPreview.Clear(); RoleHudEditor.Instance?.Close(save: false); BaseUpgradeHistory.ClearDisplayPreview(); Logger.Listeners.Remove((ILogListener)(object)BugReport); BugReport?.Dispose(); ((BaseUnityPlugin)this).Config.SettingChanged -= ConfigSettingChanged; SceneManager.activeSceneChanged -= ActiveSceneChanged; Controller?.Shutdown(); BaseUpgradeDraw?.Shutdown(); Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } StageFluxCompatibility.ClearNotifications(); RoleGuideSync.Invalidate(); RoleAssignmentSync.ResetCache(); RoleEmblems.Shutdown(); if ((Object)(object)_controllerObject != (Object)null) { Object.Destroy((Object)(object)_controllerObject); } } } internal sealed class StageRolesConfig { internal const int MaximumSupportedPlayers = 30; private readonly Dictionary<string, ConfigEntry<int>> _truckUpgradeDrawWeights = new Dictionary<string, ConfigEntry<int>>(StringComparer.Ordinal); private readonly Dictionary<string, ConfigEntry<bool>> _baseUpgradeDrawEnabled = new Dictionary<string, ConfigEntry<bool>>(StringComparer.Ordinal); internal ConfigEntry<bool> Enabled { get; } internal ConfigEntry<bool> UniqueRoles { get; } internal ConfigEntry<int> ShopUpgradeItemCount { get; } internal ConfigEntry<bool> EnhancedEnemyRewardsEnabled { get; } internal ConfigEntry<bool> ShowcaseGuaranteesEnabled { get; } internal ConfigEntry<string> ShowcaseMinimumsByPlayerCount { get; } internal ConfigEntry<bool> SupportGuaranteesEnabled { get; } internal ConfigEntry<string> SupportMinimumsByPlayerCount { get; } internal ConfigEntry<bool> RiskCombinationLimitsEnabled { get; } internal ConfigEntry<string> DangerRoleLimit { get; } internal ConfigEntry<string> HardshipRoleLimit { get; } internal ConfigEntry<int> SmallPartyCombinedRiskPlayerCount { get; } internal ConfigEntry<int> SmallPartyCombinedRiskRoleLimit { get; } internal ConfigEntry<bool> PreventConsecutiveSameRole { get; } internal ConfigEntry<int> HardshipPersonalCooldownStages { get; } internal ConfigEntry<bool> ExcludeUnavailableContextRoles { get; } internal ConfigEntry<bool> AnnouncementsEnabled { get; } internal ConfigEntry<float> AnnouncementDelaySeconds { get; } internal ConfigEntry<float> StageFluxAnnouncementDelaySeconds { get; } internal ConfigEntry<string> GuideLanguage { get; } internal ConfigEntry<int> HudFontSize { get; } internal ConfigEntry<bool> HudEnabled { get; } internal ConfigEntry<string> HudRoleDisplay { get; } internal ConfigEntry<int> HudIconSize { get; } internal ConfigEntry<string> HudAnchor { get; } internal ConfigEntry<string> HudAlignment { get; } internal ConfigEntry<int> HudOffsetX { get; } internal ConfigEntry<int> HudOffsetY { get; } internal ConfigEntry<int> HudScalePercent { get; } internal ConfigEntry<int> HudPlayersPerPage { get; } internal ConfigEntry<float> HudPageIntervalSeconds { get; } internal ConfigEntry<float> HudTransitionDurationSeconds { get; } internal ConfigEntry<bool> HudPinLocalPlayer { get; } internal ConfigEntry<bool> SetRoleCommandEnabled { get; } internal ConfigEntry<bool> BaseUpgradeManualAdjustmentEnabled { get; } internal ConfigEntry<string> BaseHealthLevels { get; } internal ConfigEntry<string> BaseStaminaLevels { get; } internal ConfigEntry<string> BaseExtraJumpLevels { get; } internal ConfigEntry<string> BaseSpeedLevels { get; } internal ConfigEntry<string> BaseStrengthLevels { get; } internal ConfigEntry<string> BaseRangeLevels { get; } internal ConfigEntry<string> BaseLaunchLevels { get; } internal ConfigEntry<string> BaseTumbleClimbLevels { get; } internal ConfigEntry<string> BaseTumbleWingsLevels { get; } internal ConfigEntry<string> BaseCrouchRestLevels { get; } internal ConfigEntry<string> BaseMapPlayerCountLevels { get; } internal ConfigEntry<string> BaseDeathHeadBatteryLevels { get; } internal ConfigEntry<bool> TruckUpgradeDrawEnabled { get; } internal ConfigEntry<int> TruckUpgradeDrawMaximumLevel { get; } internal ConfigEntry<float> TruckUpgradeDrawCappedWeightMultiplier { get; } internal ConfigEntry<float> TruckUpgradeDrawWeightFalloffExponent { get; } internal ConfigEntry<string> TruckUpgradeDrawDeltaWeights { get; } internal ConfigEntry<bool> TankEnabled { get; } internal ConfigEntry<int> TankWeight { get; } internal ConfigEntry<int> TankHealthLevels { get; } internal ConfigEntry<bool> RunnerEnabled { get; } internal ConfigEntry<int> RunnerWeight { get; } internal ConfigEntry<int> RunnerSpeedLevels { get; } internal ConfigEntry<int> RunnerStaminaLevels { get; } internal ConfigEntry<bool> JumperEnabled { get; } internal ConfigEntry<int> JumperWeight { get; } internal ConfigEntry<int> JumperExtraJumpLevels { get; } internal ConfigEntry<bool> LifterEnabled { get; } internal ConfigEntry<int> LifterWeight { get; } internal ConfigEntry<int> LifterStrengthLevels { get; } internal ConfigEntry<bool> LauncherEnabled { get; } internal ConfigEntry<int> LauncherWeight { get; } internal ConfigEntry<int> LauncherLaunchLevels { get; } internal ConfigEntry<bool> ClimberEnabled { get; } internal ConfigEntry<int> ClimberWeight { get; } internal ConfigEntry<int> ClimberClimbLevels { get; } internal ConfigEntry<int> ClimberRangeLevels { get; } internal ConfigEntry<bool> FlyerEnabled { get; } internal ConfigEntry<int> FlyerWeight { get; } internal ConfigEntry<int> FlyerWingsLevels { get; } internal ConfigEntry<bool> TrackerEnabled { get; } internal ConfigEntry<int> TrackerWeight { get; } internal ConfigEntry<int> TrackerHealthLevels { get; } internal ConfigEntry<bool> GhostEnabled { get; } internal ConfigEntry<int> GhostWeight { get; } internal ConfigEntry<int> GhostDeathHeadBatteryLevels { get; } internal ConfigEntry<bool> BomberEnabled { get; } internal ConfigEntry<int> BomberWeight { get; } internal ConfigEntry<float> BomberDistance { get; } internal ConfigEntry<int> BomberMaximumActiveGrenades { get; } internal ConfigEntry<bool> BomberAllowTruckSpawns { get; } internal ConfigEntry<bool> BomberExplosiveEnabled { get; } internal ConfigEntry<bool> BomberStunEnabled { get; } internal ConfigEntry<bool> BomberShockwaveEnabled { get; } internal ConfigEntry<bool> BomberDuctTapedEnabled { get; } internal ConfigEntry<bool> MedicEnabled { get; } internal ConfigEntry<int> MedicWeight { get; } internal ConfigEntry<int> MedicHealAmount { get; } internal ConfigEntry<float> MedicHealIntervalSeconds { get; } internal ConfigEntry<float> MedicHealRadius { get; } internal ConfigEntry<int> MedicTotalHealingLimit { get; } internal ConfigEntry<bool> PhoenixEnabled { get; } internal ConfigEntry<int> PhoenixWeight { get; } internal ConfigEntry<int> PhoenixRevivalHealth { get; } internal ConfigEntry<float> PhoenixReviveDelaySeconds { get; } internal ConfigEntry<float> PhoenixFailureGraceSeconds { get; } internal ConfigEntry<bool> JoblessEnabled { get; } internal ConfigEntry<int> JoblessWeight { get; } internal ConfigEntry<int> JoblessDamage { get; } internal ConfigEntry<float> JoblessDamageIntervalSeconds { get; } internal ConfigEntry<bool> RescuerEnabled { get; } internal ConfigEntry<int> RescuerWeight { get; } internal ConfigEntry<int> RescuerRevivalHealth { get; } internal ConfigEntry<float> RescuerReviveDelaySeconds { get; } internal ConfigEntry<float> RescuerRadius { get; } internal ConfigEntry<int> RescuerMaximumRevives { get; } internal ConfigEntry<bool> VampireEnabled { get; } internal ConfigEntry<int> VampireWeight { get; } internal ConfigEntry<int> VampireTier1HealAmount { get; } internal ConfigEntry<int> VampireTier2HealAmount { get; } internal ConfigEntry<int> VampireTier3HealAmount { get; } internal ConfigEntry<float> VampireRadius { get; } internal ConfigEntry<bool> KingEnabled { get; } internal ConfigEntry<int> KingWeight { get; } internal ConfigEntry<bool> TunaEnabled { get; } internal ConfigEntry<int> TunaWeight { get; } internal ConfigEntry<float> TunaStationaryDelaySeconds { get; } internal ConfigEntry<int> TunaDamage { get; } internal ConfigEntry<float> TunaDamageIntervalSeconds { get; } internal ConfigEntry<bool> MusicianEnabled { get; } internal ConfigEntry<int> MusicianWeight { get; } internal ConfigEntry<int> MusicianHealAmount { get; } internal ConfigEntry<float> MusicianHealRadius { get; } internal ConfigEntry<bool> MageEnabled { get; } internal ConfigEntry<int> MageWeight { get; } internal ConfigEntry<float> MageCastIntervalSeconds { get; } internal ConfigEntry<string> MageStarExpression { get; } internal ConfigEntry<string> MageRollExpression { get; } internal ConfigEntry<string> MageGravityExpression { get; } internal ConfigEntry<string> MageVoidExpression { get; } internal ConfigEntry<string> MageLaserExpression { get; } internal ConfigEntry<bool> MageAutoRecoveryEnabled { get; } internal ConfigEntry<float> MageAutoRecoveryDelaySeconds { get; } internal ConfigEntry<float> MageAutoRecoveryIntervalSeconds { get; } internal ConfigEntry<int> MageAutoRecoveryAmount { get; } internal ConfigEntry<int> MageAutoRecoveryTotalHealingLimit { get; } internal ConfigEntry<int> MageStarHealthCost { get; } internal ConfigEntry<int> MageGravityHealthCost { get; } internal ConfigEntry<int> MageRollHealthCost { get; } internal ConfigEntry<int> MageVoidHealthCost { get; } internal ConfigEntry<int> MageLaserHealthCost { get; } internal ConfigEntry<bool> GamblerEnabled { get; } internal ConfigEntry<int> GamblerWeight { get; } internal ConfigEntry<int> GamblerWinChancePercent { get; } internal ConfigEntry<float> GamblerWinValueMultiplier { get; } internal ConfigEntry<int> GamblerGambitGreenHealAmount { get; } internal ConfigEntry<int> GamblerGambitRedDamage { get; } internal ConfigEntry<int> GamblerGambitWhiteHealthUpgradeLevels { get; } internal ConfigEntry<bool> HunterEnabled { get; } internal ConfigEntry<int> HunterWeight { get; } internal ConfigEntry<int> HunterBatteryConsumptionPercent { get; } internal ConfigEntry<float> HunterDoubleOrbChancePercent { get; } internal ConfigEntry<float> HunterJackpotOrbChancePercent { get; } internal ConfigEntry<int> HunterJackpotOrbCount { get; } internal ConfigEntry<bool> StinkerEnabled { get; } internal ConfigEntry<int> StinkerWeight { get; } internal ConfigEntry<float> StinkerDistance { get; } internal ConfigEntry<float> StinkerSafetyDistance { get; } internal ConfigEntry<bool> StinkerAllowTruckSpawns { get; } internal ConfigEntry<bool> EngineerEnabled { get; } internal ConfigEntry<int> EngineerWeight { get; } internal ConfigEntry<bool> TricksterEnabled { get; } internal ConfigEntry<int> TricksterWeight { get; } internal ConfigEntry<string> TricksterExpression { get; } internal ConfigEntry<float> TricksterActiveSeconds { get; } internal ConfigEntry<float> TricksterCooldownSeconds { get; } internal ConfigEntry<float> TricksterNoTargetCooldownSeconds { get; } internal ConfigEntry<float> TricksterInvestigateRadius { get; } internal ConfigEntry<float> TricksterPulseIntervalSeconds { get; } internal ConfigEntry<float> TricksterPlacementDistance { get; } internal ConfigEntry<bool> MechanicEnabled { get; } internal ConfigEntry<int> MechanicWeight { get; } internal ConfigEntry<float> MechanicRepairPercentPerSecond { get; } internal ConfigEntry<float> MechanicMaximumRepairPercentPerStage { get; } internal ConfigEntry<bool> ElectricianEnabled { get; } internal ConfigEntry<int> ElectricianWeight { get; } internal ConfigEntry<float> ElectricianChargePercentPerSecond { get; } internal ConfigEntry<float> ElectricianMaximumChargePercentPerStage { get; } internal ConfigEntry<bool> WardenEnabled { get; } internal ConfigEntry<int> WardenWeight { get; } internal ConfigEntry<float> WardenAdditionalStunSeconds { get; } internal ConfigEntry<bool> NinjaEnabled { get; } internal ConfigEntry<int> NinjaWeight { get; } internal ConfigEntry<float> NinjaVisionRecognitionMultiplier { get; } internal ConfigEntry<bool> ExecutionerEnabled { get; } internal ConfigEntry<int> ExecutionerWeight { get; } internal ConfigEntry<float> ExecutionerStunnedDamageMultiplier { get; } internal ConfigEntry<bool> RiderEnabled { get; } internal ConfigEntry<int> RiderWeight { get; } internal ConfigEntry<float> RiderEnemyDamageMultiplier { get; } internal ConfigEntry<float> RiderPlayerKnockbackMultiplier { get; } internal ConfigEntry<bool> InfluencerEnabled { get; } internal ConfigEntry<int> InfluencerWeight { get; } internal ConfigEntry<float> InfluencerRadius { get; } internal ConfigEntry<float> InfluencerPlayerCheckIntervalSeconds { get; } internal ConfigEntry<float> InfluencerNoiseRadiusMultiplier { get; } internal ConfigEntry<float> InfluencerTtsInvestigateRadius { get; } internal ConfigEntry<float> InfluencerMinimumTtsIntervalSeconds { get; } internal ConfigEntry<float> InfluencerMaximumTtsIntervalSeconds { get; } internal ConfigEntry<float> InfluencerTtsQuietPeriodSeconds { get; } internal IReadOnlyDictionary<string, ConfigEntry<string>> InfluencerUpgradeScaling { get; } internal ConfigEntry<bool> WerewolfEnabled { get; } internal ConfigEntry<int> WerewolfWeight { get; } internal ConfigEntry<float> WerewolfPlayerDamageMultiplier { get; } internal ConfigEntry<bool> BerserkerEnabled { get; } internal ConfigEntry<int> BerserkerWeight { get; } internal IReadOnlyDictionary<string, ConfigEntry<string>> BerserkerUpgradeScaling { get; } internal ConfigEntry<bool> BodyguardEnabled { get; } internal ConfigEntry<int> BodyguardWeight { get; } internal ConfigEntry<int> BodyguardHealthLevels { get; } internal ConfigEntry<float> BodyguardRadius { get; } internal ConfigEntry<float> BodyguardDamageSharePercent { get; } internal ConfigEntry<bool> RammerEnabled { get; } internal ConfigEntry<int> RammerWeight { get; } internal ConfigEntry<int> RammerTumbleDamage { get; } internal ConfigEntry<int> RammerSelfDamage { get; } internal ConfigEntry<bool> DiverEnabled { get; } internal ConfigEntry<int> DiverWeight { get; } internal ConfigEntry<float> DiverUnderfloorDurationSeconds { get; } internal ConfigEntry<float> DiverMovementForce { get; } internal ConfigEntry<bool> SniperEnabled { get; } internal ConfigEntry<int> SniperWeight { get; } internal ConfigEntry<float> SniperReferenceDistance { get; } internal ConfigEntry<float> SniperMinimumDamageMultiplier { get; } internal ConfigEntry<float> SniperMaximumDamageMultiplier { get; } internal ConfigEntry<float> SniperMaximumMultiplierDistance { get; } internal ConfigEntry<bool> ImitatorEnabled { get; } internal ConfigEntry<int> ImitatorWeight { get; } internal ConfigEntry<bool> AvengerEnabled { get; } internal ConfigEntry<int> AvengerWeight { get; } internal ConfigEntry<float> AvengerDamageMultiplier { get; } internal ConfigEntry<float> AvengerDurationSeconds { get; } internal ConfigEntry<float> AvengerTriggerRadius { get; } internal ConfigEntry<bool> BrawlerEnabled { get; } internal ConfigEntry<int> BrawlerWeight { get; } internal ConfigEntry<float> BrawlerMeleeDamageMultiplier { get; } internal ConfigEntry<float> BrawlerRangedDamageMultiplier { get; } internal ConfigEntry<bool> SuperbotEnabled { get; } internal ConfigEntry<bool> DisasterEnabled { 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 ConfigEntry<bool>? BaseUpgradeDrawEnabledEntry(string name) { if (!_baseUpgradeDrawEnabled.TryGetValue(name, out ConfigEntry<bool> value)) { return null; } return value; } internal bool BaseUpgradeDrawIsEnabled(string name) { return BaseUpgradeDrawEnabledEntry(name)?.Value ?? false; } internal ConfigEntry<string>? BaseUpgradeLevelsEntry(string name) { return (ConfigEntry<string>?)(name switch { "Health" => BaseHealthLevels, "Stamina" => BaseStaminaLevels, "ExtraJump" => BaseExtraJumpLevels, "Speed" => BaseSpeedLevels, "Strength" => BaseStrengthLevels, "Range" => BaseRangeLevels, "Launch" => BaseLaunchLevels, "TumbleClimb" => BaseTumbleClimbLevels, "TumbleWings" => BaseTumbleWingsLevels, "CrouchRest" => BaseCrouchRestLevels, "MapPlayerCount" => BaseMapPlayerCountLevels, "DeathHeadBattery" => BaseDeathHeadBatteryLevels, _ => null, }); } internal StageRolesConfig(ConfigFile config) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected O, but got Unknown //IL_0322: Unknown result type (might be due to invalid IL or missing references) //IL_032c: Expected O, but got Unknown //IL_03c4: Unknown result type (might be due to invalid IL or missing references) //IL_03ce: Expected O, but got Unknown //IL_0411: Unknown result type (might be due to invalid IL or missing references) //IL_041b: Expected O, but got Unknown RoleConfigMigration.Apply(config); 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."); GuideLanguage = config.Bind<string>("UI", "GuideLanguage", "English", new ConfigDescription("Local language for the role guide and utility pages. Default: English. Language names are shown in their native spelling. Also changes the in-menu language toggle; both controls save the same setting.", (AcceptableValueBase)(object)new RoleLanguageValues(), Array.Empty<object>())); EnhancedEnemyRewardsEnabled = BindBool(config, "Compatibility", "EnhancedEnemyRewardsEnabled", defaultValue: true, "Treats Elite Enemy Variants Enhanced enemies as one tier higher for Vampire healing and Hunter bonus-orb quality."); ShowcaseGuaranteesEnabled = BindBool(config, "Role Balance - Guarantees", "ShowcaseEnabled", defaultValue: true, "Guarantees event-focused roles according to the configured party-size tiers."); ShowcaseMinimumsByPlayerCount = PartySizeScaling(config, "Role Balance - Guarantees", "ShowcaseMinimums", "3:1,7:2,14:3,24:4", "Minimum Showcase roles by party size."); SupportGuaranteesEnabled = BindBool(config, "Role Balance - Guarantees", "SupportEnabled", defaultValue: true, "Guarantees teamwork-focused roles according to the configured party-size tiers."); SupportMinimumsByPlayerCount = PartySizeScaling(config, "Role Balance - Guarantees", "SupportMinimums", "4:1,8:2,14:3,24:4", "Minimum Support roles by party size."); RiskCombinationLimitsEnabled = BindBool(config, "Role Balance - Limits", "Enabled", defaultValue: true, "Limits simultaneous Danger and Hardship roles during normal assignment."); DangerRoleLimit = PartySizeScaling(config, "Role Balance - Limits", "DangerMaximums", "1:1,8:2,16:3", "Maximum simultaneous Bomber, Stinker, and Werewolf roles by party size."); HardshipRoleLimit = PartySizeScaling(config, "Role Balance - Limits", "HardshipMaximums", "1:1,12:2", "Maximum simultaneous Jobless and Tuna roles by party size."); SmallPartyCombinedRiskPlayerCount = BindInt(config, "Role Balance - Limits", "SmallPartyMaximumPlayers", 4, 1, 30, "Largest party size that uses the combined Danger and Hardship limit."); SmallPartyCombinedRiskRoleLimit = BindInt(config, "Role Balance - Limits", "SmallPartyCombinedMaximum", 1, 1, 30, "Maximum combined Danger and Hardship roles in a small party."); PreventConsecutiveSameRole = BindBool(config, "Role Balance - Variety", "PreventSameRole", defaultValue: true, "Prevents a player from receiving the same role in consecutive stages when another role is available."); HardshipPersonalCooldownStages = BindInt(config, "Role Balance - Variety", "HardshipCooldownStages", 2, 0, 10, "Stages after Jobless or Tuna during which that player cannot normally receive either Hardship role."); ExcludeUnavailableContextRoles = BindBool(config, "Role Balance - Variety", "ExcludeUnavailableRoles", defaultValue: true, "Excludes context-dependent roles when their required player, enemy, weapon, or vanilla object is not available."); AnnouncementsEnabled = BindBool(config, "Notifications", "Enabled", defaultValue: true, "Enables role-name and role-effect chat and TTS notifications."); AnnouncementDelaySeconds = BindFloat(config, "Notifications", "DelaySeconds", 2.5f, 0f, 30f, "Delay before role announcements."); StageFluxAnnouncementDelaySeconds = BindFloat(config, "Notifications", "StageFluxDelaySeconds", 5f, 0f, 30f, "Delay used when Stage Flux is installed."); HudFontSize = BindInt(config, "HUD", "FontSize", 28, 16, 48, "HUD text size before overall scaling."); HudEnabled = BindBool(config, "HUD", "Enabled", defaultValue: true, "Shows every player's assigned role during a stage."); HudRoleDisplay = config.Bind<string>("HUD", "RoleDisplay", "NameOnly", new ConfigDescription("Role display style. Player names remain visible in every mode.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "IconAndName", "NameOnly", "IconOnly" }), Array.Empty<object>())); HudIconSize = BindInt(config, "HUD", "IconSize", 64, 32, 128, "Role icon size before HUD scaling. Rows and page capacity adjust to keep icons from overlapping."); HudAnchor = config.Bind<string>("HUD", "Anchor", "BottomLeft", new ConfigDescription("Anchor point used to position the role HUD.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[9] { "TopLeft", "TopCenter", "TopRight", "MiddleLeft", "MiddleCenter", "MiddleRight", "BottomLeft", "BottomCenter", "BottomRight" }), Array.Empty<object>())); HudAlignment = config.Bind<string>("HUD", "Alignment", "Left", new ConfigDescription("Horizontal alignment of the role HUD text.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "Left", "Center", "Right" }), Array.Empty<object>())); HudOffsetX = BindInt(config, "HUD", "OffsetX", 0, -3840, 3840, "Horizontal offset from the anchor in pixels."); HudOffsetY = BindInt(config, "HUD", "OffsetY", 80, -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. The HUD reserves room for six players when this is at least 6, including taller fallback fonts and icons."); 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", "Enabled", defaultValue: false, "Enables the testing commands."); BaseUpgradeManualAdjustmentEnabled = BindBool(config, "Base Upgrades", "ManualAdjustmentEnabled", defaultValue: false, "Allows the host to use the minus/plus buttons on BASE UPGRADES. Default: off. Adjustments are available in the lobby, truck, or shop, not during stages. Turning this off hides the buttons and excludes saved manual adjustments from base targets. Saved amounts are kept and apply again when enabled. Configured base levels and truck draws are unaffected."); BaseHealthLevels = BaseUpgradeScaling(config, "HealthUpgradeLevels", "1:1", 200); BaseStaminaLevels = BaseUpgradeScaling(config, "StaminaUpgradeLevels", string.Empty, 200); BaseExtraJumpLevels = BaseUpgradeScaling(config, "ExtraJumpUpgradeLevels", string.Empty, 200); BaseSpeedLevels = BaseUpgradeScaling(config, "SpeedUpgradeLevels", string.Empty, 200); BaseStrengthLevels = BaseUpgradeScaling(config, "StrengthUpgradeLevels", string.Empty, 200); BaseRangeLevels = BaseUpgradeScaling(config, "RangeUpgradeLevels", string.Empty, 200); BaseLaunchLevels = BaseUpgradeScaling(config, "LaunchUpgradeLevels", string.Empty, 200); BaseTumbleClimbLevels = BaseUpgradeScaling(config, "TumbleClimbUpgradeLevels", string.Empty, 200); BaseTumbleWingsLevels = BaseUpgradeScaling(config, "TumbleWingsUpgradeLevels", string.Empty, 200); BaseCrouchRestLevels = BaseUpgradeScaling(config, "CrouchRestUpgradeLevels", string.Empty, 200); BaseMapPlayerCountLevels = BaseUpgradeScaling(config, "MapPlayerCountUpgradeLevels", string.Empty, 1); BaseDeathHeadBatteryLevels = BaseUpgradeScaling(config, "DeathHeadBatteryUpgradeLevels", string.Empty, 200); TruckUpgradeDrawEnabled = BindBool(config, "Base Upgrade Draw", "Enabled", defaultValue: true, "After leaving the shop, runs a weighted shared Base Upgrade draw during the truck preparation phase."); TruckUpgradeDrawMaximumLevel = BindInt(config, "Base Upgrade Draw", "MaximumLevel", 200, 0, 200, "Maximum Base Upgrade target reachable through positive truck draws. Map Player Count remains limited to 1."); TruckUpgradeDrawCappedWeightMultiplier = BindFloat(config, "Base Upgrade Draw", "CappedUpgradeWeightMultiplier", 0.25f, 0f, 1f, "Minimum selection weight multiplier reached at the truck draw maximum, including configured levels and truck bonuses. Weight decreases gradually toward this value. 0 excludes capped upgrades; 1 disables the decrease. Does not allow increases beyond the maximum or change All Upgrades weight."); TruckUpgradeDrawWeightFalloffExponent = BindFloat(config, "Base Upgrade Draw", "WeightFalloffExponent", 2f, 0.1f, 10f, "Controls how upgrade selection weight decreases toward the draw maximum. 1 decreases evenly; larger values preserve more weight until near the maximum. All Upgrades weight is unchanged."); TruckUpgradeDrawDeltaWeights = config.Bind<string>("Base Upgrade Draw", "ChangeAmountWeights", "-1:10,0:15,1:60,2:15", "Comma-separated delta:weight pairs used by the truck Base Upgrade draw. Delta range: -200 to 200. Weight range: 0 to 1000. Out-of-range numeric values are clamped, malformed pairs are ignored, and duplicate deltas use the last value."); BindTruckUpgradeDrawWeight(config, "Health"); BindTruckUpgradeDrawWeight(config, "Stamina"); BindTruckUpgradeDrawWeight(config, "ExtraJump"); BindTruckUpgradeDrawWeight(config, "Speed"); BindTruckUpgradeDrawWeight(config, "Strength"); BindTruckUpgradeDrawWeight(config, "Range"); BindTruckUpgradeDrawWeight(config, "Launch"); BindTruckUpgradeDrawWeight(config, "TumbleClimb"); BindTruckUpgradeDrawWeight(config, "TumbleWings"); BindTruckUpgradeDrawWeight(config, "CrouchRest"); BindTruckUpgradeDrawWeight(config, "MapPlayerCount"); BindTruckUpgradeDrawWeight(config, "DeathHeadBattery"); BindTruckUpgradeDrawWeight(config, "AllUpgrades"); 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"); TrackerHealthLevels = UpgradeLevel(config, "Tracker", "HealthUpgradeLevels", 3); GhostEnabled = RoleEnabled(config, "Ghost"); GhostWeight = RoleWeight(config, "Ghost", 80); GhostDeathHeadBatteryLevels = UpgradeLevel(config, "Ghost", "DeathHeadBatteryUpgradeLevels", 50); BomberEnabled = RoleEnabled(config, "Bomber"); BomberWeight = RoleWeight(config, "Bomber", 80); BomberDistance = BindFloat(config, "Bomber", "DistancePerGrenade", 8f, 1f, 100f, "Travel distance required per grenade."); BomberMaximumActiveGrenades = BindInt(config, "Bomber", "MaximumActiveGrenades", 30, 1, 30, "Maximum retained generated grenades per Bomber. Placing another removes the oldest grenade."); 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", 150, 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", 20); 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", 80); 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", 50); 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", 60); 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", 3f, 0.1f, 30f, "Minimum seconds between spell activations."); MageStarExpression = ExpressionName(config, "Mage", "StarExpression", "Angry", "Star Wand"); MageRollExpression = ExpressionName(config, "Mage", "RollExpression", "Sad", "Roll Staff"); MageGravityExpression = ExpressionName(config, "Mage", "GravityExpression", "Suspicious", "Zero Gravity Staff"); MageVoidExpression = ExpressionName(config, "Mage", "VoidExpression", "EyesClosed", "Void Staff"); MageLaserExpression = ExpressionName(config, "Mage", "LaserExpression", "Scared", "Wizard Staff beam"); MageAutoRecoveryEnabled = BindBool(config, "Mage", "AutoRecoveryEnabled", defaultValue: true, "Enables Mage health recovery after avoiding damage."); MageAutoRecoveryDelaySeconds = BindFloat(config, "Mage", "AutoRecoveryDelaySeconds", 10f, 0f, 300f, "Seconds without taking damage before automatic health recovery starts."); MageAutoRecoveryIntervalSeconds = BindFloat(config, "Mage", "AutoRecoveryIntervalSeconds", 2f, 0.1f, 60f, "Seconds between automatic Mage health recovery ticks."); MageAutoRecoveryAmount = BindInt(config, "Mage", "AutoRecoveryAmount", 1, 0, 100, "Health restored by each automatic Mage recovery tick."); MageAutoRecoveryTotalHealingLimit = BindInt(config, "Mage", "AutoRecoveryTotalHealingLimit", 120, 1, 10000, "Maximum total health restored by each player's Mage automatic recovery per stage. Other healing is not counted."); 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."); GamblerGambitGreenHealAmount = BindInt(config, "Gambler", "GambitGreenHealAmount", 50, 25, 1000, "Total health restored when Gambit lands on green for a Gambler."); GamblerGambitRedDamage = BindInt(config, "Gambler", "GambitRedDamage", 100, 50, 1000, "Total damage dealt when Gambit lands on red for a Gambler."); GamblerGambitWhiteHealthUpgradeLevels = BindInt(config, "Gambler", "GambitWhiteHealthUpgradeLevels", 5, 0, 200, "Temporary Health upgrade levels added when Gambit lands on white for a Gambler."); HunterEnabled = RoleEnabled(config, "Hunter"); HunterWeight = RoleWeight(config, "Hunter", 80); 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.5f, 0f, 100f, "Chance for an enemy defeated by Hunter to drop the configured jackpot orb count."); HunterJackpotOrbCount = BindInt(config, "Hunter", "JackpotOrbCount", 10, 1, 30, "Orb count used when Hunter's jackpot roll succeeds."); StinkerEnabled = RoleEnabled(config, "Stinker"); StinkerWeight = RoleWeight(config, "Stinker", 80); StinkerDistance = BindFloat(config, "Stinker", "DistancePerCloud", 2f, 1f, 100f, "Travel distance required per uranium cloud."); StinkerSafetyDistance = BindFloat(config, "Stinker", "MinimumSafetyDistance", 2f, 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", 70); TricksterEnabled = RoleEnabled(config, "Trickster"); TricksterWeight = RoleWeight(config, "Trickster"); TricksterExpression = ExpressionName(config, "Trickster", "DecoyExpression", "Happy", "decoy placement"); TricksterActiveSeconds = BindFloat(config, "Trickster", "ActiveSeconds", 25f, 1f, 120f, "Seconds before the active decoy is removed."); TricksterCooldownSeconds = BindFloat(config, "Trickster", "CooldownSeconds", 45f, 1f, 300f, "Seconds after an effective decoy ends before another can be placed."); TricksterNoTargetCooldownSeconds = BindFloat(config, "Trickster", "NoTargetCooldownSeconds", 10f, 0f, 300f, "Seconds after a decoy that attracted no enemies ends before another can be placed."); TricksterInvestigateRadius = BindFloat(config, "Trickster", "InvestigateRadius", 40f, 1f, 100f, "Enemy investigation radius around the decoy."); TricksterPulseIntervalSeconds = BindFloat(config, "Trickster", "PulseIntervalSeconds", 1f, 0.25f, 10f, "Seconds between enemy investigation pulses."); TricksterPlacementDistance = BindFloat(config, "Trickster", "PlacementDistance", 2f, 0.5f, 10f, "Distance in front of Trickster used for decoy placement."); MechanicEnabled = RoleEnabled(config, "Mechanic"); MechanicWeight = RoleWeight(config, "Mechanic"); MechanicRepairPercentPerSecond = BindFloat(config, "Mechanic", "RepairPercentPerSecond", 2f, 0f, 100f, "Percentage of a held valuable's original value repaired per second."); MechanicMaximumRepairPercentPerStage = BindFloat(config, "Mechanic", "MaximumRepairPercentPerStage", 50f, 0f, 100f, "Maximum cumulative original-value percentage repaired by each Mechanic per stage."); ElectricianEnabled = RoleEnabled(config, "Electrician"); ElectricianWeight = RoleWeight(config, "Electrician", 80); ElectricianChargePercentPerSecond = BindFloat(config, "Electrician", "ChargePercentPerSecond", 10f, 0f, 100f, "Battery percentage restored per second to held inactive rechargeable items."); ElectricianMaximumChargePercentPerStage = BindFloat(config, "Electrician", "MaximumChargePercentPerStage", 100f, 0f, 1000f, "Maximum cumulative battery percentage restored by each Electrician per stage."); WardenEnabled = RoleEnabled(config, "Warden"); WardenWeight = RoleWeight(config, "Warden"); WardenAdditionalStunSeconds = BindFloat(config, "Warden", "AdditionalStunSeconds", 3f, 0f, 30f, "Seconds added to enemy stuns caused by Warden."); NinjaEnabled = RoleEnabled(config, "Ninja"); NinjaWeight = RoleWeight(config, "Ninja"); NinjaVisionRecognitionMultiplier = BindFloat(config, "Ninja", "VisionRecognitionMultiplier", 2f, 1f, 10f, "Multiplier applied to the time enemies need to visually recognize Ninja. Player-generated investigation sounds remain suppressed."); ExecutionerEnabled = RoleEnabled(config, "Executioner"); ExecutionerWeight = RoleWeight(config, "Executioner"); ExecutionerStunnedDamageMultiplier = BindFloat(config, "Executioner", "StunnedDamageMultiplier", 2f, 1f, 10f, "Damage multiplier for direct attacks by Executioner against enemies that were already stunned."); RiderEnabled = RoleEnabled(config, "Rider"); RiderWeight = RoleWeight(config, "Rider", 60); RiderEnemyDamageMultiplier = BindFloat(config, "Rider", "EnemyDamageMultiplier", 3f, 1f, 10f, "Multiplier for vanilla vehicle impact damage against enemies while Rider is driving."); RiderPlayerKnockbackMultiplier = BindFloat(config, "Rider", "PlayerKnockbackMultiplier", 2f, 1f, 10f, "Multiplier for existing vanilla vehicle Tumble knockback against players while Rider is driving."); InfluencerEnabled = RoleEnabled(config, "Influencer"); InfluencerWeight = RoleWeight(config, "Influencer"); InfluencerRadius = BindFloat(config, "Influencer", "PlayerRadius", 20f, 1f, 100f, "Radius used to count nearby living players."); InfluencerPlayerCheckIntervalSeconds = BindFloat(config, "Influencer", "PlayerCheckIntervalSeconds", 0.5f, 0.1f, 10f, "Seconds between nearby-player and upgrade checks."); InfluencerNoiseRadiusMultiplier = BindFloat(config, "Influencer", "NoiseRadiusMultiplier", 2f, 1f, 10f, "Multiplier applied to vanilla footstep, landing, voice, and chat TTS investigation radii."); InfluencerTtsInvestigateRadius = BindFloat(config, "Influencer", "TTSInvestigateRadius", 20f, 1f, 100f, "Enemy investigation radius produced by periodic Influencer TTS."); InfluencerMinimumTtsIntervalSeconds = BindFloat(config, "Influencer", "MinimumTTSIntervalSeconds", 20f, 1f, 300f, "Minimum randomly selected interval between periodic TTS messages."); InfluencerMaximumTtsIntervalSeconds = BindFloat(config, "Influencer", "MaximumTTSIntervalSeconds", 40f, 1f, 300f, "Maximum randomly selected interval between periodic TTS messages."); InfluencerTtsQuietPeriodSeconds = BindFloat(config, "Influencer", "TTSQuietPeriodSeconds", 1.5f, 0f, 10f, "Required quiet period after any TTS before Influencer may speak."); InfluencerUpgradeScaling = UpgradeScalingEntries(config, "Influencer", useHealthPercent: false, includeHealth: true); WerewolfEnabled = RoleEnabled(config, "Werewolf"); WerewolfWeight = RoleWeight(config, "Werewolf", 40); WerewolfPlayerDamageMultiplier = BindFloat(config, "Werewolf", "PlayerDamageMultiplier", 2f, 1f, 10f, "Multiplier for identifiable player-origin damage dealt by Werewolf to another player."); BerserkerEnabled = RoleEnabled(config, "Berserker"); BerserkerWeight = RoleWeight(config, "Berserker"); BerserkerUpgradeScaling = UpgradeScalingEntries(config, "Berserker", useHealthPercent: true, includeHealth: false); BodyguardEnabled = RoleEnabled(config, "Bodyguard"); BodyguardWeight = RoleWeight(config, "Bodyguard", 80); BodyguardHealthLevels = UpgradeLevel(config, "Bodyguard", "HealthUpgradeLevels", 11); BodyguardRadius = BindFloat(config, "Bodyguard", "ProtectionRadius", 15f, 1f, 100f, "Maximum distance from a player for Bodyguard to absorb enemy damage."); BodyguardDamageSharePercent = BindFloat(config, "Bodyguard", "DamageSharePercent", 50f, 0f, 100f, "Percentage of enemy damage transferred to Bodyguard, without reducing Bodyguard below 1 HP."); RammerEnabled = RoleEnabled(config, "Rammer"); RammerWeight = RoleWeight(config, "Rammer"); RammerTumbleDamage = BindInt(config, "Rammer", "TumbleAttackDamage", 100, 0, 100000, "Enemy damage dealt by Rammer's Tumble Attack."); RammerSelfDamage = BindInt(config, "Rammer", "SelfDamage", 15, 0, 100000, "Damage Rammer takes after its Tumble Attack hits an enemy."); DiverEnabled = RoleEnabled(config, "Diver"); DiverWeight = RoleWeight(config, "Diver"); DiverUnderfloorDurationSeconds = BindFloat(config, "Diver", "UnderfloorDurationSeconds", 10f, 1f, 120f, "Maximum seconds Diver can remain below a floor before dying."); DiverMovementForce = BindFloat(config, "Diver", "MovementForce", 8f, 1f, 30f, "Force used for free movement while Diver is below a floor."); SniperEnabled = RoleEnabled(config, "Sniper"); SniperWeight = RoleWeight(config, "Sniper"); SniperReferenceDistance = BindFloat(config, "Sniper", "ReferenceDistance", 8f, 1f, 50f, "Distance in meters where Sniper deals normal damage."); SniperMinimumDamageMultiplier = BindFloat(config, "Sniper", "MinimumDamageMultiplier", 0.5f, 0f, 1f, "Damage multiplier at zero distance."); SniperMaximumDamageMultiplier = BindFloat(config, "Sniper", "MaximumDamageMultiplier", 2f, 1f, 10f, "Maximum long-range damage multiplier."); SniperMaximumMultiplierDistance = BindFloat(config, "Sniper", "MaximumMultiplierDistance", 24f, 1f, 100f, "Distance in meters where Sniper reaches the maximum damage multiplier."); ImitatorEnabled = RoleEnabled(config, "Imitator"); ImitatorWeight = RoleWeight(config, "Imitator"); AvengerEnabled = RoleEnabled(config, "Avenger"); AvengerWeight = RoleWeight(config, "Avenger"); AvengerDamageMultiplier = BindFloat(config, "Avenger", "DamageMultiplier", 1.5f, 1f, 10f, "Enemy damage multiplier after another player dies."); AvengerDurationSeconds = BindFloat(config, "Avenger", "DurationSeconds", 20f, 1f, 120f, "Duration of Avenger's enemy damage bonus after another player dies."); AvengerTriggerRadius = BindFloat(config, "Avenger", "TriggerRadius", 30f, 1f, 100f, "Maximum distance in meters from a dying player that activates Avenger."); BrawlerEnabled = RoleEnabled(config, "Brawler"); BrawlerWeight = RoleWeight(config, "Brawler"); BrawlerMeleeDamageMultiplier = BindFloat(config, "Brawler", "MeleeDamageMultiplier", 1.25f, 0f, 10f, "Damage multiplier for identifiable melee weapon attacks."); BrawlerRangedDamageMultiplier = BindFloat(config, "Brawler", "RangedDamageMultiplier", 0.75f, 0f, 10f, "Damage multiplier for identifiable ranged weapon attacks."); SuperbotEnabled = BindBool(config, "???1", "Enabled", defaultValue: true, "???"); DisasterEnabled = BindBool(config, "???2", "Enabled", defaultValue: true, "???"); } internal int TruckUpgradeDrawWeight(string commandName) { if (!_truckUpgradeDrawWeights.TryGetValue(commandName, out ConfigEntry<int> value)) { return 0; } return Math.Clamp(value.Value, 0, 1000); } internal bool RoleIsEnabled(StageRole role) { return RoleEnabledEntry(role)?.Value ?? false; } internal ConfigEntry<bool>? RoleEnabledEntry(StageRole role) { return (ConfigEntry<bool>?)(role switch { StageRole.Tank => TankEnabled, StageRole.Runner => RunnerEnabled, StageRole.Jumper => JumperEnabled, StageRole.Lifter => LifterEnabled, StageRole.Launcher => LauncherEnabled, StageRole.Climber => ClimberEnabled, StageRole.Flyer => FlyerEnabled, StageRole.Tracker => TrackerEnabled, StageRole.Ghost => GhostEnabled, StageRole.Bomber => BomberEnabled, StageRole.Medic => MedicEnabled, StageRole.Phoenix => PhoenixEnabled, StageRole.Jobless => JoblessEnabled, StageRole.Rescuer => RescuerEnabled, StageRole.Vampire => VampireEnabled, StageRole.King => KingEnabled, StageRole.Tuna => TunaEnabled, StageRole.Musician => MusicianEnabled, StageRole.Mage => MageEnabled, StageRole.Gambler => GamblerEnabled, StageRole.Hunter => HunterEnabled, StageRole.Stinker => StinkerEnabled, StageRole.Engineer => EngineerEnabled, StageRole.Trickster => TricksterEnabled, StageRole.Mechanic => MechanicEnabled, StageRole.Electrician => ElectricianEnabled, StageRole.Warden => WardenEnabled, StageRole.Ninja => NinjaEnabled, StageRole.Executioner => ExecutionerEnabled, StageRole.Rider => RiderEnabled, StageRole.Influencer => InfluencerEnabled, StageRole.Werewolf => WerewolfEnabled, StageRole.Berserker => BerserkerEnabled, StageRole.Bodyguard => BodyguardEnabled, StageRole.Rammer => RammerEnabled, StageRole.Diver => DiverEnabled, StageRole.Sniper => SniperEnabled, StageRole.Imitator => ImitatorEnabled, StageRole.Avenger => AvengerEnabled, StageRole.Brawler => BrawlerEnabled, StageRole.Superbot => SuperbotEnabled, StageRole.Disaster => DisasterEnabled, _ => null, }); } 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, StageRole.Trickster => TricksterWeight.Value, StageRole.Mechanic => MechanicWeight.Value, StageRole.Electrician => ElectricianWeight.Value, StageRole.Warden => WardenWeight.Value, StageRole.Ninja => NinjaWeight.Value, StageRole.Executioner => ExecutionerWeight.Value, StageRole.Rider => RiderWeight.Value, StageRole.Influencer => InfluencerWeight.Value, StageRole.Werewolf => WerewolfWeight.Value, StageRole.Berserker => BerserkerWeight.Value, StageRole.Bodyguard => BodyguardWeight.Value, StageRole.Rammer => RammerWeight.Value, StageRole.Diver => DiverWeight.Value, StageRole.Sniper => SniperWeight.Value, StageRole.Imitator => ImitatorWeight.Value, StageRole.Avenger => AvengerWeight.Value, StageRole.Brawler => BrawlerWeight.Value, StageRole.Superbot => 1, StageRole.Disaster => 1, _ => 0, }; } internal int ShowcaseMinimumForPlayerCount(int playerCount) { if (!ShowcaseGuaranteesEnabled.Value) { return 0; } return Mathf.Min(Mathf.Max(0, playerCount), PartySizeScalingValue(ShowcaseMinimumsByPlayerCount.Value, playerCount, allowZero: true)); } internal int SupportMinimumForPlayerCount(int playerCount) { if (!SupportGuaranteesEnabled.Value) { return 0; } return Mathf.Min(Mathf.Max(0, playerCount), PartySizeScalingValue(SupportMinimumsByPlayerCount.Value, playerCount, allowZero: true)); } internal int DangerRoleLimitForPlayerCount(int playerCount) { return PartySizeScalingValue(DangerRoleLimit.Value, playerCount); } internal int HardshipRoleLimitForPlayerCount(int playerCount) { return PartySizeScalingValue(HardshipRoleLimit.Value, playerCount); } private static int PartySizeScalingValue(string expression, int playerCount, bool allowZero = false) { RoleUpgradeScaling.TryParse(expression, 30, 30, out IReadOnlyList<UpgradeScalingRule> rules, out string _); int num = 0; foreach (UpgradeScalingRule item in rules) { if (playerCount >= item.Condition) { num = item.Level; } } return Mathf.Clamp(num, (!allowZero) ? 1 : 0, 30); } private static ConfigEntry<bool> RoleEnabled(ConfigFile config, string section) { return BindBool(config, section, "Enabled", defaultValue: true, "Includes this role in stage assignment."); } private static ConfigEntry<int> RoleWeight(ConfigFile config, string section, int defaultValue = 100) { return BindInt(config, section, "Weight", defaultValue, 0, 1000, "Relative assignment weight."); } private static ConfigEntry<string> PartySizeScaling(ConfigFile config, string section, string key, string defaultValue, string description) { return config.Bind<string>(section, key, defaultValue, description + " Use comma-separated party size:limit pairs."); } private static ConfigEntry<string> ExpressionName(ConfigFile config, string section, string key, string defaultValue, string action) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown return config.Bind<string>(section, key, defaultValue, new ConfigDescription("Facial expression that triggers " + action + ".", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[7] { "Disabled", "Angry", "Sad", "Suspicious", "EyesClosed", "Scared", "Happy" }), Array.Empty<object>())); } private static ConfigEntry<int> UpgradeLevel(ConfigFile config, string section, string key, int defaultValue) { return BindInt(config, section, key, defaultValue, 0, 200, "Vanilla upgrade target level while this role is assigned."); } private static IReadOnlyDictionary<string, ConfigEntry<string>> UpgradeScalingEntries(ConfigFile config, string section, bool useHealthPercent, bool includeHealth) { Dictionary<string, ConfigEntry<string>> dictionary = new Dictionary<string, ConfigEntry<string>>(StringComparer.Ordinal); foreach (DynamicUpgradeDefinition definition in RoleUpgradeScaling.Definitions) { if (includeHealth || !(definition.Name == "Health")) { string text = (useHealthPercent ? RoleUpgradeScaling.BerserkerDefault(definition.Name) : RoleUpgradeScaling.InfluencerDefault(definition.Name)); string text2 = (useHealthPercent ? "remaining HP percent at or below which the value applies" : "nearby-player count at or above which the value applies"); dictionary[definition.Name] = config.Bind<string>(section, definition.Name + "UpgradeScaling", text, "Comma-separated condition:level pairs (" + text2 + "). " + $"Level range: 0-{definition.MaximumLevel}. Blank and conditions not yet reached use target level 0."); } } return dictionary; } private static ConfigEntry<string> BaseUpgradeScaling(ConfigFile config, string key, string defaultValue, int maximumLevel) { return config.Bind<string>("Base Upgrades", key, defaultValue, "Comma-separated level:value pairs. The last entry at or below the current run level sets the base vanilla upgrade target. " + $"Run level range: 1-999999. Value range: 0-{maximumLevel}; out-of-range numeric values are clamped. " + "Blank and levels not yet reached use target level 0."); } private void BindTruckUpgradeDrawWeight(ConfigFile config, string upgradeName) { _baseUpgradeDrawEnabled[upgradeName] = BindBool(config, "Base Upgrade Draw Selection", upgradeName, defaultValue: true, "Includes this upgrade in future truck draws, including All Upgrades results. Existing levels and configured base targets are kept. For AllUpgrades, controls the combined draw result only; individual upgrade switches still apply."); _truckUpgradeDrawWeights[upgradeName] = BindInt(config, "Base Upgrade Draw Weights", upgradeName, TruckDrawWeight.DefaultWeight(upgradeName), 0, 1000, "Relative selection weight for " + upgradeName + " in the truck Base Upgrade draw. " + ((upgradeName == "AllUpgrades") ? "All Upgrades uses this weight directly. " : "Weight gradually decreases toward CappedUpgradeWeightMultiplier as the upgrade approaches its draw maximum. Defaults reflect vanilla shop stock. ") + "Zero excludes it from direct selection."); } private static ConfigEntry<bool> BindBool(ConfigFile config, string section, string key, bool defaultValue, string description) { return config.Bind<bool>(section, key, defaultValue, description); } private static ConfigEntry<int> 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<int>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<int>(minimum, maximum), Array.Empty<object>())); } private static ConfigEntry<float> 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<float>(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(minimum, maximum), Array.Empty<object>())); } } internal enum StageRole { Tank = 0, Runner = 1, Jumper = 2, Lifter = 3, Launcher = 4, Climber = 5, Flyer = 6, Tracker = 7, Ghost = 8, Bomber = 9, Medic = 10, Phoenix = 11, Jobless = 12, Rescuer = 13, Vampire = 14, King = 15, Tuna = 16, Musician = 17, Mage = 18, Gambler = 19, Hunter = 20, Stinker = 21, Engineer = 22, Trickster = 23, Mechanic = 24, Electrician = 25, Warden = 26, Ninja = 27, Executioner = 28, Rider = 29, Influencer = 30, Werewolf = 31, Berserker = 32, Bodyguard = 33, Rammer = 34, Diver = 35, Sniper = 36, Imitator = 37, Avenger = 38, Brawler = 39, Superbot = 1001, Disaster = 1002 } internal enum RolePreset { Standard, Beginner, Cooperative, Chaos, Challenge } internal sealed class RolePresetDefinition { private readonly HashSet<StageRole> _roles = new HashSet<StageRole>(roles); internal RolePreset Id { get; } internal string Name { get; } internal string Description { get; } internal int Count => _roles.Count; public RolePresetDefinition(RolePreset id, string name, string description, params StageRole[] roles) { Id = id; Name = name; Description = description; base..ctor(); } internal bool Includes(StageRole role) { return _roles.Contains(role); } } internal static class RolePresets { internal static readonly IReadOnlyList<RolePresetDefinition> All = new RolePresetDefinition[5] { new RolePresetDefinition(RolePreset.Standard, "Standard", "All roles, including secrets.", (StageRole[])Enum.GetValues(typeof(StageRole))), new RolePresetDefinition(RolePreset.Beginner, "Beginner", "Simple upgrades and recovery.", StageRole.Tank, StageRole.Runner, StageRole.Jumper, StageRole.Lifter, StageRole.Launcher, StageRole.Climber, StageRole.Flyer, StageRole.Tracker, StageRole.Ghost, StageRole.Medic, StageRole.Phoenix, StageRole.Rescuer, StageRole.Engineer, StageRole.Mechanic, StageRole.Electrician, StageRole.Warden, StageRole.Ninja, StageRole.Influencer, StageRole.Bodyguard), new RolePresetDefinition(RolePreset.Cooperative, "Cooperative", "Healing, repairs and team support.", StageRole.Tank, StageRole.Runner, StageRole.Lifter, StageRole.Tracker, StageRole.Ghost, StageRole.Medic, StageRole.Phoenix, StageRole.Rescuer, StageRole.Musician, StageRole.Engineer, StageRole.Trickster, StageRole.Mechanic, StageRole.Electrician, StageRole.Warden, StageRole.Influencer, StageRole.Bodyguard), new RolePresetDefinition(RolePreset.Chaos, "Chaos", "Explosions, magic and risky roles.", StageRole.Bomber, StageRole.King, StageRole.Tuna, StageRole.Mage, StageRole.Gambler, StageRole.Stinker, StageRole.Trickster, StageRole.Rider, StageRole.Werewolf, StageRole.Berserker, StageRole.Rammer, StageRole.Diver, StageRole.Imitator, StageRole.Disaster), new RolePresetDefinition(RolePreset.Challenge, "Challenge", "High risk; no dedicated healers or revivers.", StageRole.Jobless, StageRole.Tuna, StageRole.Vampire, StageRole.Mage, StageRole.Gambler, StageRole.Hunter, StageRole.Executioner, StageRole.Werewolf, StageRole.Berserker, StageRole.Rammer, StageRole.Diver, StageRole.Sniper, StageRole.Brawler, StageRole.Avenger) }; internal static RolePresetDefinition? Find(RolePreset id) { foreach (RolePresetDefinition item in All) { if (item.Id == id) { return item; } } return null; } internal static string MatchingName(Func<StageRole, bool> enabled) { foreach (RolePresetDefinition item in All) { bool flag = true; foreach (StageRole value in Enum.GetValues(typeof(StageRole))) { if (enabled(value) != item.Includes(value)) { flag = false; break; } } if (flag) { return item.Name; } } return "Custom"; } } internal sealed class RoleSelectionSettings(StageRolesConfig settings, ConfigFile file) { internal static bool CanEdit { get { if ((Object)(object)GameManager.instance != (Object)null) { if (SemiFunc.IsMultiplayer()) { return PhotonNetwork.IsMasterClient; } return true; } return false; } } internal bool TrySetEnabled(StageRole role, bool enabled) { if (!CanEdit || settings.RoleEnabledEntry(role) == null) { return false; } Save(new Dictionary<StageRole, bool> { [role] = enabled }); return true; } internal bool TryApplyPreset(RolePreset id) { if (!CanEdit) { return false; } RolePresetDefinition rolePresetDefinition = RolePresets.Find(id); if (rolePresetDefinition == null) { return false; } Dictionary<StageRole, bool> dictionary = new Dictionary<StageRole, bool>(); foreach (StageRole value in Enum.GetValues(typeof(StageRole))) { dictionary[value] = rolePresetDefinition.Includes(value); } Save(dictionary); return true; } private void Save(IReadOnlyDictionary<StageRole, bool> values) { Dictionary<ConfigEntry<bool>, bool> dictionary = new Dictionary<ConfigEntry<bool>, bool>(); foreach (KeyValuePair<StageRole, bool> value in values) { dictionary[settings.RoleEnabledEntry(value.Key)] = value.Value; } SaveEntries(file, dictionary); } internal static void SaveEntries<T>(ConfigFile file, IReadOnlyDictionary<ConfigEntry<T>, T> values) { bool saveOnConfigSet = file.SaveOnConfigSet; Dictionary<ConfigEntry<T>, T> dictionary = new Dictionary<ConfigEntry<T>, T>(); file.SaveOnConfigSet = false; try { foreach (KeyValuePair<ConfigEntry<T>, T> value in values) { ConfigEntry<T> key = value.Key; dictionary[key] = key.Value; key.Value = value.Value; } file.Save(); } catch { foreach (KeyValuePair<ConfigEntry<T>, T> item in dictionary) { item.Key.Value = item.Value; } throw; } finally { file.SaveOnConfigSet = saveOnConfigSet; } } } internal static class BaseUpgradeDrawSelection { internal static readonly IReadOnlyList<string> UpgradeNames = Array.AsReadOnly(new string[13] { "Health", "Stamina", "ExtraJump", "Speed", "Strength", "Range", "Launch", "TumbleClimb", "TumbleWings", "CrouchRest", "MapPlayerCount", "DeathHeadBattery", "AllUpgrades" }); } internal sealed class BaseUpgradeSelectionSettings(StageRolesConfig settings, ConfigFile file) { internal static int CurrentRunLevel => (int)Math.Max(1L, Math.Min(999999L, ((Object)(object)RunManager.instance != (Object)null) ? ((long)RunManager.instance.levelsCompleted + 1L) : 1)); internal static bool CanAdjustInCurrentScene { get { if ((Object)(object)RunManager.instance != (Object)null && (Object)(object)RunManager.instance.levelCurrent != (Object)null) { if (!SemiFunc.RunIsLobbyMenu() && !SemiFunc.RunIsLobby()) { return SemiFunc.RunIsShop(); } return true; } return false; } } internal bool CanAdjustLevel(string name, int delta, string saveIdentity) { string dictionaryName; int adjustment; if (RoleSelectionSettings.CanEdit) { return TryBuildLevelAdjustment(name, delta, saveIdentity, out dictionaryName, out adjustment); } return false; } internal bool TryAdjustLevel(string name, int delta, string saveIdentity) { if (!RoleSelectionSettings.CanEdit || !TryBuildLevelAdjustment(name, delta, saveIdentity, out string dictionaryName, out int adjustment)) { return false; } return BaseUpgradeManualStore.TrySave(dictionaryName, adjustment, saveIdentity); } private bool TryBuildLevelAdjustment(string name, int delta, string saveIdentity, out string dictionaryName, out int adjustment) { dictionaryName = string.Empty; adjustment = 0; ConfigEntry<string> val = settings.BaseUpgradeLevelsEntry(name); bool flag = !settings.BaseUpgradeManualAdjustmentEnabled.Value || !CanAdjustInCurrentScene || val == null; if (!flag) { bool flag2 = ((delta == -1 || delta == 1) ? true : false); flag = !flag2; } if (flag || string.IsNullOrEmpty(saveIdentity) || saveIdentity != BaseUpgradeManualStore.SaveIdentity) { return false; } int num = ((name == "MapPlayerCount") ? 1 : 200); if (!RoleUpgradeScaling.TryParse(val.Value, 1, 999999, num, clampOutOfRange: true, out IReadOnlyList<UpgradeScalingRule> rules, out string _)) { return false; } int num2 = 0; foreach (UpgradeScalingRule item in rules) { if (item.Condition <= CurrentRunLevel) { num2 = item.Level; } } dictionaryName = "playerUpgrade" + name; int num3 = BaseUpgradeManualStore.EffectiveLevel(dictionaryName, num2, num, settings.BaseUpgradeManualAdjustmentEnabled.Value) + delta; if (num3 < 0 || num3 > num) { return false; } long num4 = (long)num3 - (long)num2 - BaseUpgradeBonusStore.Get(dictionaryName); if (num4 < int.MinValue || num4 > int.MaxValue) { return false; } adjustment = (int)num4; return true; } internal bool TrySetEnabled(string name, bool enabled) { if (RoleSelectionSettings.CanEdit) { ConfigEntry<bool> val = settings.BaseUpgradeDrawEnabledEntry(name); if (val != null) { RoleSelectionSettings.SaveEntries(file, new Dictionary<ConfigEntry<bool>, bool> { [val] = enabled }); return true; } } return false; } internal bool TrySetDrawEnabled(bool enabled) { if (!RoleSelectionSettings.CanEdit) { return false; } RoleSelectionSettings.SaveEntries(file, new Dictionary<ConfigEntry<bool>, bool> { [settings.TruckUpgradeDrawEnabled] = enabled }); return true; } } internal static class BaseUpgradeManualStore { private const string KeyPrefix = "RoleShuffle.BaseUpgradeManual."; internal static string SaveIdentity { get { if (!GameSaveState.CanSave) { return GameSaveState.PendingNewRunIdentity; } return GameSaveState.CurrentName; } } internal static int Get(string dictionaryName) { if (!((Object)(object)StatsManager.instance == (Object)null)) { return StatsManager.instance.runStats.GetValueOrDefault("RoleShuffle.BaseUpgradeManual." + dictionaryName, 0); } return 0; } internal static bool TrySave(string dictionaryName, int value, string saveIdentity) { if (string.IsNullOrEmpty(saveIdentity) || SaveIdentity != saveIdentity) { return false; } if (!GameSaveState.CanSave && !GameSaveState.PrepareNewRun(saveIdentity)) { return false; } StatsManager instance = StatsManager.instance; string key = "RoleShuffle.BaseUpgradeManual." + dictionaryName; int value2; bool flag = instance.runStats.TryGetValue(key, out value2); instance.runStats[key] = value; try { instance.SaveFileSave(); } catch { if (flag) { instance.runStats[key] = value2; } else { instance.runStats.Remove(key); } throw; } return true; } internal static int Applied(string dictionaryName, bool enabled) { if (!enabled) { return 0; } return Get(dictionaryName); } internal static int EffectiveLevel(string dictionaryName, int configured, int maximum, bool manualEnabled) { return (int)Math.Max(0L, Math.Min(maximum, (long)configured + (long)Applied(dictionaryName, manualEnabled) + BaseUpgradeBonusStore.Get(dictionaryName))); } } internal static class BaseUpgradeBonusStore { private const string KeyPrefix = "RoleShuffle.BaseUpgradeBonus."; internal static int Get(string dictionaryName) { if (!((Object)(object)StatsManager.instance == (Object)null) && !string.IsNullOrEmpty(dictionaryName)) { return StatsManager.instance.runStats.GetValueOrDefault("RoleShuffle.BaseUpgradeBonus." + dictionaryName, 0); } return 0; } internal static bool ApplyEffectiveDelta(string dictionaryName, int currentLevel, int configuredLevel, int delta, int maximumLevel, bool manualEnabled) { if ((Object)(object)StatsManager.instance == (Object)null || string.IsNullOrEmpty(dictionaryName) || delta == 0) { return false; } int num = (int)Math.Max(0L, Math.Min(maximumLevel, (long)currentLevel + (long)delta)); if (num == currentLevel) { return false; } long num2 = (long)num - (long)configuredLevel - BaseUpgradeManualStore.Applied(dictionaryName, manualEnabled); if (num2 < int.MinValue || num2 > int.MaxValue) { return false; } StatsManager.instance.runStats["RoleShuffle.BaseUpgradeBonus." + dictionaryName] = (int)num2; return true; } } internal static class GameSaveState { private const BindingFlags Fields = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly FieldInfo? CurrentFile = typeof(StatsManager).GetField("saveFileCurrent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo? FileReady = typeof(StatsManager).GetField("saveFileReady", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo? LobbyType = typeof(GameManager).GetField("lobbyType", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static StatsManager? _resetStats; private static string _resetIdentity = string.Empty; internal static string CurrentName { get { if (!((Object)(object)StatsManager.instance != (Object)null)) { return string.Empty; } return (CurrentFile?.GetValue(StatsManager.instance) as string) ?? string.Empty; } } private static bool CanWrite { get { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)StatsManager.instance != (Object)null && (Object)(object)GameManager.instance != (Object)null && (Object)(object)GameDirector.instance != (Object)null && LobbyType?.GetValue(GameManager.instance) is LobbyTypes item) { return StatsManager.instance.savedLobbyTypes.Contains(item); } return false; } } internal static bool CanSave { get { if (CanWrite) { object obj = FileReady?.GetValue(StatsManager.instance); if (obj is bool) { return (bool)obj; } return false; } return false; } } internal static string PendingNewRunIdentity { get { if (!CanWrite || CanSave || _resetStats != StatsManager.instance || !((Object)(object)RunManager.instance != (Object)null) || !((Object)(object)RunManager.instance.levelCurrent != (Object)null) || RunManager.instance.levelsCompleted != 0 || !SemiFunc.RunIsLobbyMenu()) { return string.Empty; } return _resetIdentity; } } internal static void RecordRunReset(StatsManager stats) { _resetStats = stats; _resetIdentity = "new-run:" + Guid.NewGuid().ToString("N"); } internal static void RecordSaveReady(StatsManager stats) { if (_resetStats == stats) { object obj = FileReady?.GetValue(stats); if (obj is bool && (bool)obj) { _resetStats = null; _resetIdentity = string.Empty; } } } internal static bool PrepareNewRun(string identity) { if (!RoleSelectionSettings.CanEdit || string.IsNullOrEmpty(identity) || identity != PendingNewRunIdentity) { return false; } StatsManager.instance.SaveFileCreate("", false); if (CanSave) { return CurrentName.Length > 0; } return false; } } internal sealed class BaseUpgradeSettingsSnapshot { internal bool ModEnabled { get; } internal bool DrawEnabled { get; } public BaseUpgradeSettingsSnapshot(bool modEnabled, bool drawEnabled, string flags) { <flags>P = flags; ModEnabled = modEnabled; DrawEnabled = drawEnabled; base..ctor(); } internal bool IsEnabled(string name) { for (int i = 0; i < BaseUpgradeDrawSelection.UpgradeNames.Count; i++) { if (BaseUpgradeDrawSelection.UpgradeNames[i] == name) { return <flags>P[i] == '1'; } } return false; } } internal static class BaseUpgradeSettingsSync { internal const string PropertyKey = "RS.BaseUpgradeSettingsV1"; internal static string LocalSignature(StageRolesConfig config) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(config.Enabled.Value ? '1' : '0').Append(config.TruckUpgradeDrawEnabled.Value ? '1' : '0'); foreach (string upgradeName in BaseUpgradeDrawSelection.UpgradeNames) { stringBuilder.Append(config.BaseUpgradeDrawIsEnabled(upgradeName) ? '1' : '0'); } return stringBuilder.ToString(); } internal static string CurrentSignature(StageRolesConfig config) { if (!SemiFunc.IsMultiplayer() || PhotonNetwork.IsMasterClient) { return LocalSignature(config); } if (PhotonNetwork.CurrentRoom == null || !((Dictionary<object, object>)(object)((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties).TryGetValue((object)"RS.BaseUpgradeSettingsV1", out object value) || !(value is string result)) { return "unavailable"; } return result; } internal static BaseUpgradeSettingsSnapshot? Read(StageRolesConfig config) { string text = CurrentSignature(config); if (text.Length != BaseUpgradeDrawSelection.UpgradeNames.Count + 2) { return null; } string text2 = text; foreach (char c in text2) { if ((c != '0' && c != '1') || 1 == 0) { return null; } } return new BaseUpgradeSettingsSnapshot(text[0] == '1', text[1] == '1', text.Substring(2)); } } internal sealed class DynamicUpgradeDefinition { internal string Name { get; } internal string CommandName { get; } internal string DictionaryName { get; } internal int MaximumLevel { get; } internal DynamicUpgradeDefinition(string name, string commandName, string dictionaryName, int maximumLevel) { Name = name; CommandName = commandName; DictionaryName = dictionaryName; MaximumLevel = maximumLevel; } } internal readonly struct UpgradeScalingRule { internal int Condition { get; } internal int Level { get; } internal UpgradeScalingRule(int condition, int level) { Condition = condition; Level = level; } } internal static class RoleUpgradeScaling { internal const int MaximumUpgradeLevel = 200; internal static readonly IReadOnlyList<DynamicUpgradeDefinition> Definitions = new DynamicUpgradeDefinition[12] { new DynamicUpgradeDefinition("Health", "Health", "playerUpgradeHealth", 200), new DynamicUpgradeDefinition("Stamina", "Stamina", "playerUpgradeStamina", 200), new DynamicUpgradeDefinition("ExtraJump", "ExtraJump", "playerUpgradeExtraJump", 200), new DynamicUpgradeDefinition("Speed", "Speed", "playerUpgradeSpeed", 200), new DynamicUpgradeDefinition("Strength", "Strength", "playerUpgradeStrength", 200), new DynamicUpgradeDefinition("Range", "Range", "playerUpgradeRange", 200), new DynamicUpgradeDefinition("Launch", "Launch", "playerUpgradeLaunch", 200), new DynamicUpgradeDefinition("TumbleClimb", "TumbleClimb", "playerUpgradeTumbleClimb", 200), new DynamicUpgradeDefinition("TumbleWings", "TumbleWings", "playerUpgradeTumbleWings", 200), new DynamicUpgradeDefinition("CrouchRest", "CrouchRest", "playerUpgradeCrouchRest", 200), new DynamicUpgradeDefinition("MapPlayerCount", "MapPlayerCount", "playerUpgradeMapPlayerCount", 1), new DynamicUpgradeDefinition("DeathHeadBattery", "DeathHeadBattery", "playerUpgradeDeathHeadBattery", 200) }; internal static string InfluencerDefault(string upgradeName) { return upgradeName switch { "Strength" => "1:1,2:4,3:9,4:13,5:20", "Speed" => "1:1,2:2,3:3,4:3,5:3", "Stamina" => "1:3,2:6,3:10,4:12,5:15", "ExtraJump" => "2:1,3:2,4:4,5:5", "TumbleWings" => "1:1,2:2,3:3,4:4,5:5", "Health" => "4:6,5:11", _ => string.Empty, }; } internal static string BerserkerDefault(string upgradeName) { return upgradeName switch { "Strength" => "80:1,60:4,40:9,20:13,10:20", "Speed" => "80:1,60:2,40:3,20:3,10:4", "Stamina" => "80:3,60:6,40:10,20:12,10:20", "ExtraJump" => "60:1,40:2,20:4,10:5", "TumbleWings" => string.Empty, _ => string.Empty, }; } internal static bool TryParse(string expression, int maximumCondition, int maximumLevel, out IReadOnlyList<UpgradeScalingRule> rules, out string error) { return TryParse(expression, 0, maximumCondition, maximumLevel, clampOutOfRange: false, out rules, out error); } internal static bool TryParse(string expression, int minimumCondition, int maximumCondition, int maximumLevel, bool clampOutOfRange, out IReadOnlyList<UpgradeScalingRule> rules, out string error) { List<UpgradeScalingRule> list = new List<UpgradeScalingRule>(); List<string> list2 = new List<string>(); Dictionary<int, int> dictionary = new Dictionary<int, int>(); string[] array = (expression ?? string.Empty).Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); int num = text.IndexOf(':'); if (num <= 0 || num == text.Length - 1 || !int.TryParse(text.Substring(0, num).Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || !int.TryParse(text.Substring(num + 1).Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2)) { list2.Add(text); continue; } if (result < minimumCondition || result > maximumCondition || result2 < 0 || result2 > maximumLevel) { if (!clampOutOfRange) { list2.Add(text); continue; } result = Math.Max(minimumCondition, Math.Min(maximumCondition, result)); result2 = Math.Max(0, Math.Min(maximumLevel, result2)); } dictionary[result] = result2; } foreach (KeyValuePair<int, int> item in dictionary) { list.Add(new UpgradeScalingRule(item.Key, item.Value)); } list.Sort((UpgradeScalingRule left, UpgradeScalingRule right) => left.Condition.CompareTo(right.Condition)); rules = list; error = ((list2.Count == 0) ? string.Empty : string.Join(", ", list2)); return list2.Count == 0; } } internal static class RoleConfigMigration { private const int CurrentSchemaVersion = 32; private static readonly ConfigDefinition SchemaDefinition = new ConfigDefinition("Migration", "ConfigVersion"); private static readonly string[] RemovedSections = new string[19] { "Camper", "Commands", "Decoy", "Longarm", "Librarian", "Magician", "Magnet", "Marathoner", "Mimic", "Musicain", "Navigator", "Physician", "Sapper", "SpeedStar", "Sprinter", "Striker", "Tamer", "Thrower", "Unemployed" }; internal static void Apply(ConfigFile config) { //IL_0bbb: Unknown result type (might be due to invalid IL or missing references) //IL_0bc7: Expected O, but got Unknown string configFilePath = config.ConfigFilePath; string text = configFilePath + ".roleshuffle.tmp"; try { Dictionary<ConfigDefinition, string> dictionary = ReadValues(configFilePath); int num = ReadSchemaVersion(dictionary); if (num > 32) { StageRolesPlugin.ModLogger.LogWarning((object)($"RoleShuffle config schema {num} is newer than " + $"the supported schema {32}; migration was skipped.")); return; } int migrated = 0; int removed = 0; MoveKeys(dictionary, "???", "???1", ref migrated, ref removed, "Enabled"); migrated += MigrateKeys(dictionary, "SpeedStar", "Runner", "Enabled", "Weight", "SpeedUpgradeLevels", "StaminaUpgradeLevels"); migrated += MigrateKeys(dictionary, "Sprinter", "Runner", "Enabled", "Weight", "SpeedUpgradeLevels"); migrated += MigrateKeys(dictionary, "Marathoner", "Runner", "Enabled", "Weight", "StaminaUpgradeLevels"); migrated += MigrateKeys(dictionary, "Longarm", "Climber", "RangeUpgradeLevels"); migrated += MigrateKeys(dictionary, "Unemployed", "Jobless", "Enabled", "Weight", "Damage", "DamageIntervalSeconds"); migrated += MigrateKeys(dictionary, "Musicain", "Musician", "Enabled", "Weight", "HealAmount", "HealRadius"); migrated += MigrateKeys(dictionary, "Magician", "Mage", "Enabled", "Weight", "CastIntervalSeconds"); migrated += MigrateKeys(dictionary, "Physician", "Medic", "Enabled", "Weight", "HealAmount", "HealIntervalSeconds", "HealRadius", "TotalHealingLimit"); migrated += MigrateKeys(dictionary, "Navigator", "Medic", "Enabled", "Weight", "HealAmount", "HealIntervalSeconds", "HealRadius", "TotalHealingLimit"); migrated += MigrateKeys(dictionary, "Librarian", "Ninja", "Enabled", "Weight"); migrated += MigrateKeys(dictionary, "Striker", "Rammer", "Enabled", "Weight", "TumbleAttackDamage"); migrated += MigrateKeys(dictionary, "Mimic", "Imitator", "Enabled", "Weight"); if (num < 5) { migrated += ReplaceValueIfEqual(dictionary, "Jobless", "Weight", "100", "20"); migrated += ReplaceValueIfEqual(dictionary, "Medic", "TotalHealingLimit", "200", "150"); } if (num < 6) { migrated += ReplaceValueIfEqual(dictionary, "Hunter", "JackpotOrbChancePercent", "0.01", "0.5"); migrated += ReplaceValueIfEqual(dictionary, "Mechanic", "RepairPercentPerSecond", "1", "2"); migrated += ReplaceValueIfEqual(dictionary, "Electrician", "ChargePercentPerSecond", "5", "10"); } if (num < 7) { migrated += ReplaceValueIfEqual(dictionary, "Stinker", "DistancePerCloud", "1", "2"); migrated += ReplaceValueIfEqual(dictionary, "Stinker", "MinimumSafetyDistance", "1", "2"); } if (num == 7) { migrated += ReplaceValueIfEqual(dictionary, "Stinker", "DistancePerCloud", "3", "2"); migrated += ReplaceValueIfEqual(dictionary, "Stinker", "MinimumSafetyDistance", "3", "2"); } if (num < 10) { migrated += MigrateDynamicUpgradeScaling(dictionary); } if (num < 11) { migrated += ReplaceValueIfEqual(dictionary, "Influencer", "ExtraJumpUpgradeScaling", "1:0,2:1,3:2,4:4,5:5", "2:1,3:2,4:4,5:5"); migrated += ReplaceValueIfEqual(dictionary, "Influencer", "HealthUpgradeScaling", "1:0,2:0,3:0,4:6,5:11", "4:6,5:11"); migrated += ReplaceValueIfEqual(dictionary, "Berserker", "ExtraJumpUpgradeScaling", "80:0,60:1,40:2,20:4,10:5", "60:1,40:2,20:4,10:5"); } if (num < 12) { migrated += ReplaceValueIfEqual(dictionary, "Berserker", "SpeedUpgradeScaling", "80:1,60:2,40:3,20:3,10:3", "80:1,60:2,40:3,20:3,10:4"); migrated += ReplaceValueIfEqual(dictionary, "Berserker", "StaminaUpgradeScaling", "80:3,60:6,40:10,20:12,10:15", "80:3,60:6,40:10,20:12,10:20"); migrated += ReplaceValueIfEqual(dictionary, "Berserker", "TumbleWingsUpgradeScaling", "80:1,60:2,40:3,20:4,10:5", string.Empty); } if (num < 13) { migrated += ReplaceValueIfEqual(dictionary, "Rider", "EnemyDamageMultiplier", "2", "3"); migrated += ReplaceValueIfEqual(dictionary, "Rider", "EnemyDamageMultiplier", "2.0", "3"); } if (num < 14) { migrated += MigrateBaseUpgradeScaling(dictionary); } if (num < 15) { migrated += MigratePartySizeLimit(dictionary, "DangerRoleLimit", "1:1,8:2,16:3"); migrated += MigratePartySizeLimit(dictionary, "HardshipRoleLimit", "1:1,12:2"); migrated += ReplaceValueIfEqual(dictionary, "Role Balance", "HardshipPersonalCooldownStages", "1", "2"); migrated += UpdateDefaultRoleWeights(dictionary); } if (num < 17) { MigrateGuaranteeScaling(dictionary, "Showcase", new int[4] { 3, 7, 14, 24 }, new int[4] { 1, 2, 3, 4 }, ref migrated, ref removed); MigrateGuaranteeScaling(dictionary, "Support", new int[4] { 4, 8, 14, 24 }, new int[4] { 1, 2, 3, 4 }, ref migrated, ref removed); MoveKeys(dictionary, "Role Balance", "Role Balance - Guarantees", ref migrated, ref removed, "ShowcaseGuaranteesEnabled", "SupportGuaranteesEnabled"); MoveKeys(dictionary, "Role Balance", "Role Balance - Limits", ref migrated, ref removed, "RiskCombinationLimitsEnabled", "DangerRoleLimit", "HardshipRoleLimit", "SmallPartyCombinedRiskPlayerCount", "SmallPartyCombinedRiskRoleLimit"); MoveKeys(dictionary, "Role Balance", "Role Balance - Variety", ref migrated, ref removed, "PreventConsecutiveSameRole", "HardshipPersonalCooldownStages", "ExcludeUnavailableContextRoles"); MoveKeys(dictionary, "Base Upgrades", "Base Upgrade Draw", ref migrated, ref removed, "TruckUpgradeDrawEnabled", "TruckUpgradeDrawMaximumLevel", "TruckUpgradeDrawDeltaWeights"); } if (num < 18) { migrated += ReplaceValueIfEqual(dictionary, "Trickster", "ActiveSeconds", "15", "25"); migrated += ReplaceValueIfEqual(dictionary, "Trickster", "InvestigateRadius", "30", "40"); migrated += ReplaceValueIfEqual(dictionary, "Trickster", "PulseIntervalSeconds", "2", "1"); } if (num < 20) { migrated += ReplaceValueIfEqual(dictionary, "Diver", "UnderfloorDurationSeconds", "30", "15"); migrated += ReplaceValueIfEqual(dictionary, "Diver", "UnderfloorDurationSeconds", "30.0", "15"); } if (num < 21) { migrated += ReplaceValueIfEqual(dictionary, "Rammer", "TumbleAttackDamage", "150", "100"); migrated += ReplaceValueIfEqual(dictionary, "Mage", "CastIntervalSeconds", "5", "3"); migrated += ReplaceValueIfEqual(dictionary, "Mage", "CastIntervalSeconds", "5.0", "3"); } if (num < 22) { migrated += ReplaceValueIfEqual(dictionary, "Diver", "UnderfloorDurationSeconds", "15", "10"); migrated += ReplaceValueIfEqual(dictionary, "Diver", "UnderfloorDurationSeconds", "15.0", "10"); } if (num < 23) { MoveKey(dictionary, "Role Balance - Guarantees", "ShowcaseGuaranteesEnabled", "Role Balance - Guarantees", "ShowcaseEnabled", ref migrated, ref removed); MoveKey(dictionary, "Role Balance - Guarantees", "ShowcaseMinimumsByPlayerCount", "Role Balance - Guarantees", "ShowcaseMinimums", ref migrated, ref removed); MoveKey(dictionary, "Role Balance - Guarantees", "SupportGuaranteesEnabled", "Role Balance - Guarantees", "SupportEnabled", ref migrated, ref removed); MoveKey(dictionary, "Role Balance - Guarantees", "SupportMinimumsByPlayerCount", "Role Balance - Guarantees", "SupportMinimums", ref migrated, ref removed); MoveKey(dictionary, "Role Balance - Limits", "RiskCombinationLimitsEnabled", "Role Balance - Limits", "Enabled", ref migrated, ref removed); MoveKey(dictionary, "Role Balance - Limits", "DangerRoleLimit", "Role Balance - Limits", "DangerMaximums", ref migrated, ref removed); MoveKey(dictionary, "Role Balance - Limits", "HardshipRoleLimit", "Role Balance - Limits", "HardshipMaximums", ref migrated, ref removed); MoveKey(dictionary, "Role Balance - Limits", "SmallPartyCombinedRiskPlayerCount", "Role Balance - Limits", "SmallPartyMaximumPlayers", ref migrated, ref removed); MoveKey(dictionary, "Role Balance - Limits", "SmallPartyCombinedRiskRoleLimit", "Role Balance - Limits", "SmallPartyCombinedMaximum", ref migrated, ref removed); MoveKey(dictionary, "Role Balance - Variety", "PreventConsecutiveSameRole", "Role Balance - Variety", "PreventSameRole", ref migrated, ref removed); MoveKey(dictionary, "Role Balance - Variety", "HardshipPersonalCooldownStages", "Role Balance - Variety", "HardshipCooldownStages", ref migrated, ref removed); MoveKey(dictionary, "Role Balance - Variety", "ExcludeUnavailableContextRoles", "Role Balance - Variety", "ExcludeUnavailableRoles", ref migrated, ref removed); MoveKey(dictionary, "Notifications", "AnnouncementDelaySeconds", "Notifications", "DelaySeconds", ref migrated, ref removed); MoveKey(dictionary, "Notifications", "StageFluxAnnouncementDelaySeconds", "Notifications", "StageFluxDelaySeconds", ref migrated, ref removed); MoveKey(dictionary, "Testing", "SetRoleCommandEnabled", "Testing", "Enabled", ref migrated, ref removed); MoveKey(dictionary, "Base Upgrade Draw", "TruckUpgradeDrawEnabled", "Base Upgrade Draw", "Enabled", ref migrated, ref removed); MoveKey(dictionary, "Base Upgrade Draw", "TruckUpgradeDrawMaximumLevel", "Base Upgrade Draw", "MaximumLevel", ref migrated, ref removed); MoveKey(dictionary, "Base Upgrade Draw", "TruckUpgradeDrawDeltaWeights", "Base Upgrade Draw", "ChangeAmountWeights", ref migrated, ref removed); } if (num < 24) { migrated += ReplaceValueIfEqual(dictionary, "Brawler", "MeleeDamageMultiplier", "2", "1.25"); migrated += ReplaceValueIfEqual(dictionary, "Brawler", "MeleeDamageMultiplier", "2.0", "1.25"); migrated += ReplaceValueIfEqual(dictionary, "Brawler", "RangedDamageMultiplier", "0.5", "0.75"); } if (num < 27) { migrated += Rep