using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HG.BlendableTypes; using Microsoft.CodeAnalysis; using On.RoR2; using On.RoR2.Projectile; using On.RoR2.UI; using On.RoR2.UI.MainMenu; using RiskOfOptions; using RiskOfOptions.OptionConfigs; using RiskOfOptions.Options; using RoR2; using RoR2.Projectile; using RoR2.UI; using RoR2.UI.MainMenu; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.Rendering.PostProcessing; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("PetrichorProtocol")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.16.0.0")] [assembly: AssemblyInformationalVersion("2.16.0+615702a4273eef4ba1c49317a3516c3ccfedacf9")] [assembly: AssemblyProduct("PetrichorProtocol")] [assembly: AssemblyTitle("PetrichorProtocol")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.16.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace PetrichorProtocol { internal class ChallengeContractsModule : ModuleBase { public enum Contract { None, KillTheTitan, NoHealing, SpeedClear, EliteHunt, StayInside, PacifistStart } public ConfigEntry OfferChance; public ConfigEntry RewardGoldMultiplier; public ConfigEntry SpeedClearSeconds; public ConfigEntry EliteHuntCount; public ConfigEntry NoHealingCap; private Contract active; private bool resolved; private float stageStartTime; private float healedThisStage; private int elitesKilledThisStage; private bool titanKilled; private bool pacifistBroken; private CharacterMaster titanMaster; private static readonly Random rng = new Random(); public override string Name => "Challenge Contracts"; public override string StandaloneGuid => null; public override int Stars => 2; public override bool UsesStageStart => true; public override bool UsesRunStart => true; public override bool UsesDeath => true; public override bool UsesHeal => true; public override bool UsesReward => true; public override bool UsesFixedUpdate => true; public override void Bind(ConfigFile cfg) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Expected O, but got Unknown Enabled = cfg.Bind("Contracts", "Enabled", false, "Each stage offers one optional objective (a contract). Complete it for a bonus reward; ignoring it costs nothing. A goals-and-rewards layer - off by default."); OfferChance = cfg.Bind("Contracts", "OfferChancePerStage", 100f, new ConfigDescription("Chance (percent) that a stage offers a contract at all.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); RewardGoldMultiplier = cfg.Bind("Contracts", "RewardGoldMultiplier", 0.5f, new ConfigDescription("Bonus gold on completing a contract, as a fraction of your current gold (0.5 = +50%).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 3f), Array.Empty())); SpeedClearSeconds = cfg.Bind("Contracts", "SpeedClearSeconds", 120f, new ConfigDescription("For the Speed Clear contract: seconds allowed to activate the teleporter.", (AcceptableValueBase)(object)new AcceptableValueRange(30f, 600f), Array.Empty())); EliteHuntCount = cfg.Bind("Contracts", "EliteHuntCount", 3, new ConfigDescription("For the Elite Hunt contract: how many elites to kill this stage.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 15), Array.Empty())); NoHealingCap = cfg.Bind("Contracts", "NoHealingCap", 50f, new ConfigDescription("For the No Healing contract: total healing allowed this stage before it fails.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 500f), Array.Empty())); } public override void OnRunStart() { active = Contract.None; } public override void OnStageStart(Stage stage) { active = Contract.None; resolved = false; healedThisStage = 0f; elitesKilledThisStage = 0; titanKilled = false; pacifistBroken = false; titanMaster = null; stageStartTime = Time.time; if (!NetworkServer.active) { return; } if (rng.NextDouble() * 100.0 > (double)OfferChance.Value) { DebugLog($"No contract offered this stage (OfferChance={OfferChance.Value}%)"); return; } active = (Contract)(1 + rng.Next(6)); DebugLog($"Rolled contract: {active}"); if (active == Contract.KillTheTitan && !TrySpawnTitan()) { DebugLog("KillTheTitan could not find a spawn candidate - falling back to EliteHunt"); active = Contract.EliteHunt; } DebugLog($"Offered {active} contract - {Describe(active)}"); if (NotificationsModule.Gate) { Chat.AddMessage("CONTRACT: " + Describe(active)); } } private string Describe(Contract c) { return c switch { Contract.KillTheTitan => "Kill the Titan before the teleporter finishes charging.", Contract.NoHealing => $"Heal no more than {NoHealingCap.Value:0} this stage.", Contract.SpeedClear => $"Activate the teleporter within {SpeedClearSeconds.Value:0} seconds.", Contract.EliteHunt => $"Kill {EliteHuntCount.Value} elites this stage.", Contract.StayInside => "Charge the teleporter without leaving the zone.", Contract.PacifistStart => "Do not kill anything for the first 60 seconds.", _ => "", }; } public override float ModifyHeal(HealthComponent target, float amount) { if (active == Contract.NoHealing && !resolved && Object.op_Implicit((Object)(object)target.body) && ModuleBase.IsPlayer(target.body)) { healedThisStage += amount; if (healedThisStage > NoHealingCap.Value) { DebugLog($"NoHealing contract broken - healed {healedThisStage:0.0}/{NoHealingCap.Value:0} this stage"); Fail(); } } return amount; } public override void OnDeath(DamageReport report) { if (active == Contract.None || resolved || report == null) { return; } if (active == Contract.PacifistStart && Object.op_Implicit((Object)(object)report.attackerBody) && ModuleBase.IsPlayer(report.attackerBody) && Time.time - stageStartTime < 60f) { DebugLog($"PacifistStart contract broken - kill at {Time.time - stageStartTime:0.0}s into stage (limit 60s)"); pacifistBroken = true; Fail(); return; } if (active == Contract.EliteHunt && Object.op_Implicit((Object)(object)report.victimBody) && report.victimBody.isElite && Object.op_Implicit((Object)(object)report.attackerBody) && ModuleBase.IsPlayer(report.attackerBody)) { elitesKilledThisStage++; DebugLog($"EliteHunt progress: {elitesKilledThisStage}/{EliteHuntCount.Value} elites killed"); if (elitesKilledThisStage >= EliteHuntCount.Value) { Succeed(); } } if (active == Contract.KillTheTitan && (Object)(object)titanMaster != (Object)null && Object.op_Implicit((Object)(object)report.victimBody) && (Object)(object)report.victimBody.master == (Object)(object)titanMaster) { DebugLog("KillTheTitan contract target killed"); titanKilled = true; Succeed(); } } public override void OnFixedUpdateServer() { if (active == Contract.None || resolved) { return; } TeleporterInteraction instance = TeleporterInteraction.instance; if (active == Contract.SpeedClear) { if (Object.op_Implicit((Object)(object)instance) && instance.isCharged) { DebugLog($"SpeedClear contract succeeded - teleporter charged at {Time.time - stageStartTime:0.0}s (limit {SpeedClearSeconds.Value:0}s)"); Succeed(); } else if (Time.time - stageStartTime > SpeedClearSeconds.Value) { DebugLog($"SpeedClear contract failed - {SpeedClearSeconds.Value:0}s elapsed without charging teleporter"); Fail(); } } if (active == Contract.StayInside && Object.op_Implicit((Object)(object)instance) && Object.op_Implicit((Object)(object)instance.holdoutZoneController) && ((Behaviour)instance.holdoutZoneController).isActiveAndEnabled && instance.holdoutZoneController.charge > 0f && instance.holdoutZoneController.charge < 1f) { bool flag = false; foreach (PlayerCharacterMasterController instance2 in PlayerCharacterMasterController.instances) { CharacterBody val = ((Object.op_Implicit((Object)(object)instance2) && Object.op_Implicit((Object)(object)instance2.master)) ? instance2.master.GetBody() : null); if (Object.op_Implicit((Object)(object)val) && instance.holdoutZoneController.IsBodyInChargingRadius(val)) { flag = true; break; } } if (!flag) { DebugLog($"StayInside contract failed - no player inside charging radius at {instance.holdoutZoneController.charge * 100f:0}% charge"); Fail(); } else if (instance.isCharged) { DebugLog("StayInside contract succeeded - teleporter fully charged with a player inside the zone throughout"); Succeed(); } } if (active == Contract.PacifistStart && !pacifistBroken && Time.time - stageStartTime >= 60f) { DebugLog("PacifistStart contract succeeded - 60s elapsed with no kills"); Succeed(); } if (active == Contract.KillTheTitan && !titanKilled && Object.op_Implicit((Object)(object)instance) && instance.isCharged) { DebugLog("KillTheTitan contract failed - teleporter charged before the titan was killed"); Fail(); } } private void Succeed() { if (!resolved) { resolved = true; DebugLog($"{active} contract resolved: SUCCESS - granting reward (RewardGoldMultiplier={RewardGoldMultiplier.Value})"); GrantReward(); if (NotificationsModule.Gate) { Chat.AddMessage("CONTRACT COMPLETE! Reward granted."); } } } private void Fail() { if (!resolved) { resolved = true; DebugLog($"{active} contract resolved: FAILED - no penalty applied"); if (NotificationsModule.Gate) { Chat.AddMessage("Contract failed. No penalty - better luck next stage."); } } } private void GrantReward() { foreach (PlayerCharacterMasterController instance in PlayerCharacterMasterController.instances) { if (Object.op_Implicit((Object)(object)instance) && Object.op_Implicit((Object)(object)instance.master)) { uint money = instance.master.money; uint num = (uint)Mathf.Max(25f, (float)instance.master.money * RewardGoldMultiplier.Value); instance.master.GiveMoney(num); DebugLog($"Granted {num} bonus gold to {((Object)instance.master).name} (had {money}, now {instance.master.money})"); } } } public override float RewardMultiplier(DamageReport report) { if (active == Contract.None || resolved) { return 1f; } return 1.1f; } private bool TrySpawnTitan() { //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) List list = new List(); foreach (CharacterBody readOnlyInstances in CharacterBody.readOnlyInstancesList) { if (Object.op_Implicit((Object)(object)readOnlyInstances) && ModuleBase.IsHostileEnemy(readOnlyInstances) && !readOnlyInstances.isBoss && Object.op_Implicit((Object)(object)readOnlyInstances.healthComponent) && readOnlyInstances.healthComponent.alive) { list.Add(readOnlyInstances); } } if (list.Count == 0) { DebugLog("TrySpawnTitan found no eligible non-boss hostile enemy on the stage"); return false; } list.Sort((CharacterBody a, CharacterBody b) => b.maxHealth.CompareTo(a.maxHealth)); CharacterBody val = list[rng.Next(Mathf.Min(3, list.Count))]; titanMaster = val.master; DebugLog($"TrySpawnTitan chose {((Object)val).name} (base maxHealth {val.maxHealth:0}) from a pool of {list.Count} candidates"); ModelLocator modelLocator = val.modelLocator; if (Object.op_Implicit((Object)(object)modelLocator) && Object.op_Implicit((Object)(object)modelLocator.modelTransform)) { Transform transform = ((Component)modelLocator.modelTransform).gameObject.transform; transform.localScale *= 3f; } if ((Object)(object)val.inventory != (Object)null) { EliteDef fire = Elites.Fire; if (Object.op_Implicit((Object)(object)fire) && Object.op_Implicit((Object)(object)fire.eliteEquipmentDef)) { val.inventory.SetEquipmentIndex(fire.eliteEquipmentDef.equipmentIndex, false); } val.inventory.GiveItemPermanent(Items.BoostHp, 120); val.inventory.GiveItemPermanent(Items.BoostDamage, 30); } val.RecalculateStats(); if (Object.op_Implicit((Object)(object)val.healthComponent)) { val.healthComponent.HealFraction(1f, default(ProcChainMask)); } DebugLog($"Titan ready: {((Object)val).name} scaled 3x, Fire elite affix, +120 BoostHp, +30 BoostDamage, final maxHealth {val.maxHealth:0}"); return (Object)(object)titanMaster != (Object)null; } } [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("dileppy.petrichorprotocol", "PetrichorProtocol", "2.16.0")] public class PetrichorProtocolPlugin : BaseUnityPlugin { public const string PluginGUID = "dileppy.petrichorprotocol"; public const string PluginName = "PetrichorProtocol"; public const string PluginVersion = "2.16.0"; internal static PetrichorProtocolPlugin Instance; internal static ManualLogSource Log; internal static readonly List Modules = new List(); private static readonly List hookBodyStart = new List(); private static readonly List hookRecalc = new List(); private static readonly List hookProjectile = new List(); private static readonly List hookBullet = new List(); private static readonly List hookSpawnEffect = new List(); private static readonly List hookReward = new List(); private static readonly List hookDeath = new List(); private static readonly List hookRunStart = new List(); private static readonly List hookStageStart = new List(); private static readonly List hookHeal = new List(); private static readonly List hookFixed = new List(); private static readonly List<(string name, int stars)> externalRegistered = new List<(string, int)>(); public static ConfigEntry EnableRewardScaling; public static ConfigEntry MaxRewardMultiplier; public static ConfigEntry ShowRunSummary; public static ConfigEntry DebugLogging; private static int riskScore; private static float protocolRewardMult = 1f; private static bool spawnEffectHooked; private static void BuildHookLists() { hookBodyStart.Clear(); hookRecalc.Clear(); hookProjectile.Clear(); hookBullet.Clear(); hookSpawnEffect.Clear(); hookReward.Clear(); hookDeath.Clear(); hookRunStart.Clear(); hookStageStart.Clear(); hookHeal.Clear(); hookFixed.Clear(); foreach (ModuleBase module in Modules) { if (module.Active) { if (module.UsesBodyStart) { hookBodyStart.Add(module); } if (module.UsesRecalcStats) { hookRecalc.Add(module); } if (module.UsesProjectile) { hookProjectile.Add(module); } if (module.UsesBulletFire) { hookBullet.Add(module); } if (module.UsesSpawnEffect) { hookSpawnEffect.Add(module); } if (module.UsesReward) { hookReward.Add(module); } if (module.UsesDeath) { hookDeath.Add(module); } if (module.UsesRunStart) { hookRunStart.Add(module); } if (module.UsesStageStart) { hookStageStart.Add(module); } if (module.UsesHeal) { hookHeal.Add(module); } if (module.UsesFixedUpdate) { hookFixed.Add(module); } } } } public static void RegisterModifier(string displayName, int stars) { externalRegistered.Add((displayName, Mathf.Clamp(stars, 1, 5))); } private void Awake() { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_02a7: Unknown result type (might be due to invalid IL or missing references) //IL_02b1: Expected O, but got Unknown //IL_02b8: Unknown result type (might be due to invalid IL or missing references) //IL_02c2: Expected O, but got Unknown //IL_02c9: Unknown result type (might be due to invalid IL or missing references) //IL_02d3: Expected O, but got Unknown //IL_02da: Unknown result type (might be due to invalid IL or missing references) //IL_02e4: Expected O, but got Unknown //IL_02eb: Unknown result type (might be due to invalid IL or missing references) //IL_02f5: Expected O, but got Unknown //IL_032f: Unknown result type (might be due to invalid IL or missing references) //IL_0339: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; EnableRewardScaling = ((BaseUnityPlugin)this).Config.Bind("Protocol", "EnableRewardScaling", true, "Scale gold and XP up based on the total Risk Score of active features."); MaxRewardMultiplier = ((BaseUnityPlugin)this).Config.Bind("Protocol", "MaxRewardMultiplier", 3f, new ConfigDescription("The highest the gold/XP reward multiplier can reach, no matter how high your Risk Score climbs.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 5f), Array.Empty())); ShowRunSummary = ((BaseUnityPlugin)this).Config.Bind("Protocol", "ShowRunSummary", true, "Announce the Risk Score, tier, and active features in chat at run start."); DebugLogging = ((BaseUnityPlugin)this).Config.Bind("Protocol", "DebugLogging", false, "Write extra Protocol diagnostics to the console log. Only useful for troubleshooting; leave off normally."); Modules.Add(new ShrunkenSurvivorModule()); Modules.Add(new EnlargedSurvivorModule()); Modules.Add(new ShrunkenEnemiesModule()); Modules.Add(new EnlargedEnemiesModule()); Modules.Add(new BiggerBulletsModule()); Modules.Add(new EnemyMutationsModule()); Modules.Add(new BossMutationsModule()); Modules.Add(new PresetsModule()); Modules.Add(new CombosModule()); Modules.Add(new CurseDeckModule()); Modules.Add(new HazardsModule()); Modules.Add(new WorldEventsModule()); Modules.Add(new StagePersonalitiesModule()); Modules.Add(new TeleporterRulesModule()); Modules.Add(new EnemySwarmModule()); Modules.Add(new ItemDietModule()); Modules.Add(new LootMutationsModule()); Modules.Add(new SizeRouletteModule()); Modules.Add(new ChallengeContractsModule()); Modules.Add(new RandomizerModule()); Modules.Add(new NotificationsModule()); Modules.Add(new RunTitlesModule()); Modules.Add(new HudModule()); Modules.Add(new TitleBrandingModule()); foreach (ModuleBase module in Modules) { module.Bind(((BaseUnityPlugin)this).Config); } foreach (ModuleBase module2 in Modules) { if (module2.Enabled != null) { module2.Enabled.SettingChanged += delegate { BuildHookLists(); RefreshDynamicHooks(); RefreshRiskLine(); }; } } BuildHookLists(); CharacterBody.Start += new hook_Start(CharacterBody_Start); CharacterBody.RecalculateStats += new hook_RecalculateStats(CharacterBody_RecalculateStats); ProjectileController.Start += new hook_Start(ProjectileController_Start); BulletAttack.Fire += new hook_Fire(BulletAttack_Fire); DeathRewards.OnKilledServer += new hook_OnKilledServer(DeathRewards_OnKilledServer); GlobalEventManager.onCharacterDeathGlobal += OnCharacterDeath; Run.onRunStartGlobal += OnRunStart; Stage.onStageStartGlobal += OnStageStart; HealthComponent.Heal += new hook_Heal(HealthComponent_Heal); RefreshDynamicHooks(); ((Component)this).gameObject.AddComponent(); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("The Petrichor Protocol v{0} online. {1} modules loaded.", "2.16.0", Modules.Count)); } private void Start() { foreach (ModuleBase module in Modules) { module.StandaloneAlsoInstalled = module.StandaloneGuid != null && Chainloader.PluginInfos.ContainsKey(module.StandaloneGuid); if (module.StandaloneAlsoInstalled) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Protocol: standalone " + module.Name + " is also installed. Protocol settings take precedence; the standalone mod is dormant.")); } } RiskOfOptionsBridge.TryRegisterAll(((BaseUnityPlugin)this).Config); } internal static void RefreshDynamicHooks() { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown if ((Object)(object)Instance == (Object)null) { return; } bool flag = false; for (int i = 0; i < Modules.Count; i++) { if (Modules[i].WantsSpawnEffectHook) { flag = true; break; } } if (flag == spawnEffectHooked) { return; } try { if (flag) { EffectManager.SpawnEffect_GameObject_EffectData_bool += new hook_SpawnEffect_GameObject_EffectData_bool(Instance.EffectManager_SpawnEffect); } else { EffectManager.SpawnEffect_GameObject_EffectData_bool -= new hook_SpawnEffect_GameObject_EffectData_bool(Instance.EffectManager_SpawnEffect); } spawnEffectHooked = flag; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)("Protocol: SpawnEffect hook " + (flag ? "attached (tracer scaling on)" : "detached (not needed - zero cost)") + ".")); } } catch (Exception arg) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogError((object)$"Protocol: SpawnEffect hook toggle failed: {arg}"); } } } private void OnDestroy() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Expected O, but got Unknown CharacterBody.Start -= new hook_Start(CharacterBody_Start); CharacterBody.RecalculateStats -= new hook_RecalculateStats(CharacterBody_RecalculateStats); ProjectileController.Start -= new hook_Start(ProjectileController_Start); BulletAttack.Fire -= new hook_Fire(BulletAttack_Fire); DeathRewards.OnKilledServer -= new hook_OnKilledServer(DeathRewards_OnKilledServer); GlobalEventManager.onCharacterDeathGlobal -= OnCharacterDeath; Run.onRunStartGlobal -= OnRunStart; Stage.onStageStartGlobal -= OnStageStart; HealthComponent.Heal -= new hook_Heal(HealthComponent_Heal); if (spawnEffectHooked) { EffectManager.SpawnEffect_GameObject_EffectData_bool -= new hook_SpawnEffect_GameObject_EffectData_bool(EffectManager_SpawnEffect); spawnEffectHooked = false; } foreach (ModuleBase module in Modules) { try { module.Unhook(); } catch (Exception arg) { Log.LogError((object)$"{module.Name}.Unhook: {arg}"); } } } private void CharacterBody_Start(orig_Start orig, CharacterBody self) { orig.Invoke(self); for (int i = 0; i < hookBodyStart.Count; i++) { ModuleBase moduleBase = hookBodyStart[i]; if (moduleBase.Active) { try { moduleBase.OnBodyStart(self); } catch (Exception arg) { Log.LogError((object)$"{moduleBase.Name}.OnBodyStart: {arg}"); } } } } private void CharacterBody_RecalculateStats(orig_RecalculateStats orig, CharacterBody self) { orig.Invoke(self); for (int i = 0; i < hookRecalc.Count; i++) { ModuleBase moduleBase = hookRecalc[i]; if (moduleBase.Active) { try { moduleBase.OnRecalcStats(self); } catch (Exception arg) { Log.LogError((object)$"{moduleBase.Name}.OnRecalcStats: {arg}"); } } } } private void ProjectileController_Start(orig_Start orig, ProjectileController self) { orig.Invoke(self); for (int i = 0; i < hookProjectile.Count; i++) { ModuleBase moduleBase = hookProjectile[i]; if (moduleBase.Active) { try { moduleBase.OnProjectileStart(self); } catch (Exception arg) { Log.LogError((object)$"{moduleBase.Name}.OnProjectileStart: {arg}"); } } } } private void BulletAttack_Fire(orig_Fire orig, BulletAttack self) { bool flag = false; for (int i = 0; i < hookBullet.Count; i++) { if (flag) { break; } ModuleBase moduleBase = hookBullet[i]; if (moduleBase.Active) { try { flag = moduleBase.OnBulletFire(self, orig); } catch (Exception arg) { Log.LogError((object)$"{moduleBase.Name}.OnBulletFire: {arg}"); } } } if (!flag) { orig.Invoke(self); } } private void EffectManager_SpawnEffect(orig_SpawnEffect_GameObject_EffectData_bool orig, GameObject effectPrefab, EffectData effectData, bool transmit) { for (int i = 0; i < hookSpawnEffect.Count; i++) { ModuleBase moduleBase = hookSpawnEffect[i]; if (moduleBase.Active) { try { moduleBase.OnSpawnEffect(effectPrefab, effectData); } catch (Exception arg) { Log.LogError((object)$"{moduleBase.Name}.OnSpawnEffect: {arg}"); } } } orig.Invoke(effectPrefab, effectData, transmit); } private void DeathRewards_OnKilledServer(orig_OnKilledServer orig, DeathRewards self, DamageReport report) { float num = 1f; for (int i = 0; i < hookReward.Count; i++) { ModuleBase moduleBase = hookReward[i]; if (moduleBase.Active) { try { num *= moduleBase.RewardMultiplier(report); } catch (Exception arg) { Log.LogError((object)$"{moduleBase.Name}.RewardMultiplier: {arg}"); } } } if (EnableRewardScaling.Value) { num *= protocolRewardMult; } if (float.IsNaN(num) || float.IsInfinity(num)) { num = 1f; } num = Mathf.Clamp(num, 0f, 1000f); if (num != 1f) { self.goldReward = (uint)((float)self.goldReward * num); self.expReward = (uint)((float)self.expReward * num); } orig.Invoke(self, report); } private void OnCharacterDeath(DamageReport report) { for (int i = 0; i < hookDeath.Count; i++) { ModuleBase moduleBase = hookDeath[i]; if (moduleBase.Active) { try { moduleBase.OnDeath(report); } catch (Exception arg) { Log.LogError((object)$"{moduleBase.Name}.OnDeath: {arg}"); } } } } private void OnStageStart(Stage stage) { for (int i = 0; i < hookStageStart.Count; i++) { ModuleBase moduleBase = hookStageStart[i]; if (moduleBase.Active) { try { moduleBase.OnStageStart(stage); } catch (Exception arg) { Log.LogError((object)$"{moduleBase.Name}.OnStageStart: {arg}"); } } } } private void FixedUpdate() { if (hookFixed.Count == 0 || !NetworkServer.active || (Object)(object)Run.instance == (Object)null) { return; } for (int i = 0; i < hookFixed.Count; i++) { ModuleBase moduleBase = hookFixed[i]; if (moduleBase.Active) { try { moduleBase.OnFixedUpdateServer(); } catch (Exception arg) { Log.LogError((object)$"{moduleBase.Name}.OnFixedUpdateServer: {arg}"); } } } } private float HealthComponent_Heal(orig_Heal orig, HealthComponent self, float amount, ProcChainMask procChainMask, bool nonRegen) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < hookHeal.Count; i++) { ModuleBase moduleBase = hookHeal[i]; if (moduleBase.Active) { try { amount = moduleBase.ModifyHeal(self, amount); } catch (Exception arg) { Log.LogError((object)$"{moduleBase.Name}.ModifyHeal: {arg}"); } } } return orig.Invoke(self, amount, procChainMask, nonRegen); } internal static string TierName(int score) { if (score <= 0) { return "Vanilla"; } if (score <= 5) { return "Casual Chaos"; } if (score <= 10) { return "Risky"; } if (score <= 15) { return "Brutal"; } if (score <= 20) { return "Nightmare"; } return "Apocalypse"; } private void OnRunStart(Run run) { foreach (ModuleBase module in Modules) { if (module.Active) { try { module.OnRunStart(); } catch (Exception arg) { Log.LogError((object)$"{module.Name}.OnRunStart: {arg}"); } } } BuildHookLists(); RefreshDynamicHooks(); List<(string, int)> list = RefreshRiskLine(); if (!ShowRunSummary.Value || list.Count == 0 || !NotificationsModule.Gate) { return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("THE PETRICHOR PROTOCOL - Risk Score ").Append(riskScore).Append(""); stringBuilder.Append(" - Tier: ").Append(TierName(riskScore)).Append(""); if (EnableRewardScaling.Value) { stringBuilder.Append(" - Rewards x").Append(protocolRewardMult.ToString("F2")).Append(""); } Chat.AddMessage(stringBuilder.ToString()); StringBuilder stringBuilder2 = new StringBuilder("Active: "); for (int i = 0; i < list.Count; i++) { if (i > 0) { stringBuilder2.Append(", "); } stringBuilder2.Append(list[i].Item1).Append(" (").Append(new string('*', list[i].Item2)) .Append(")"); } Chat.AddMessage(stringBuilder2.ToString()); } private List<(string, int)> RefreshRiskLine() { riskScore = 0; List<(string, int)> list = new List<(string, int)>(); foreach (ModuleBase module in Modules) { if (module.Stars > 0 && module.FeatureActive) { riskScore += module.Stars; list.Add((module.Name + (module.StandaloneAlsoInstalled ? " (protocol-controlled)" : ""), module.Stars)); } } foreach (var (item, num) in externalRegistered) { riskScore += num; list.Add((item, num)); } protocolRewardMult = ((riskScore <= 5) ? (1f + (float)riskScore * 0.02f) : (1f + 0.004f * (float)riskScore * (float)riskScore)); protocolRewardMult = Mathf.Min(protocolRewardMult, MaxRewardMultiplier.Value); HudModule.RiskLine = $"Risk {riskScore} - {TierName(riskScore)}"; return list; } } internal abstract class ModuleBase { internal class SizeMarker : MonoBehaviour { public Vector3 baseScale; } internal class AimOriginMarker : MonoBehaviour { public Vector3 baseAimLocalPos; public Vector3 modelPivotToBodyRoot; } public ConfigEntry Enabled; public bool StandaloneAlsoInstalled; public abstract string Name { get; } public abstract string StandaloneGuid { get; } public abstract int Stars { get; } public bool Active { get { if (Enabled != null) { return Enabled.Value; } return false; } } public bool FeatureActive => Active; public virtual bool UsesBodyStart => false; public virtual bool UsesRecalcStats => false; public virtual bool UsesProjectile => false; public virtual bool UsesBulletFire => false; public virtual bool UsesSpawnEffect => false; public virtual bool UsesReward => false; public virtual bool UsesDeath => false; public virtual bool UsesRunStart => false; public virtual bool UsesStageStart => false; public virtual bool UsesHeal => false; public virtual bool UsesFixedUpdate => false; public virtual bool WantsSpawnEffectHook => false; public abstract void Bind(ConfigFile cfg); protected void DebugLog(string message) { if (PetrichorProtocolPlugin.DebugLogging != null && PetrichorProtocolPlugin.DebugLogging.Value) { PetrichorProtocolPlugin.Log.LogInfo((object)("[" + Name + "] " + message)); } } public virtual void OnBodyStart(CharacterBody body) { } public virtual void OnRecalcStats(CharacterBody body) { } public virtual void OnProjectileStart(ProjectileController proj) { } public virtual bool OnBulletFire(BulletAttack attack, orig_Fire orig) { return false; } public virtual void OnSpawnEffect(GameObject prefab, EffectData data) { } public virtual float RewardMultiplier(DamageReport report) { return 1f; } public virtual void OnDeath(DamageReport report) { } public virtual void OnRunStart() { } public virtual void OnStageStart(Stage stage) { } public virtual float ModifyHeal(HealthComponent target, float amount) { return amount; } public virtual void OnFixedUpdateServer() { } public virtual void Unhook() { } protected static bool IsPlayer(CharacterBody body) { if (Object.op_Implicit((Object)(object)body) && Object.op_Implicit((Object)(object)body.master)) { return Object.op_Implicit((Object)(object)body.master.playerCharacterMasterController); } return false; } protected static bool IsHostileEnemy(CharacterBody body) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Invalid comparison between Unknown and I4 //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Invalid comparison between Unknown and I4 //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 if (!Object.op_Implicit((Object)(object)body) || !Object.op_Implicit((Object)(object)body.teamComponent)) { return false; } TeamIndex teamIndex = body.teamComponent.teamIndex; if ((int)teamIndex != 2 && (int)teamIndex != 3) { return (int)teamIndex == 4; } return true; } protected static void ApplyAbsoluteScale(GameObject go, float factor) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)go)) { return; } Scene scene = go.scene; if (((Scene)(ref scene)).IsValid()) { SizeMarker sizeMarker = go.GetComponent(); if (!Object.op_Implicit((Object)(object)sizeMarker)) { sizeMarker = go.AddComponent(); sizeMarker.baseScale = go.transform.localScale; } go.transform.localScale = sizeMarker.baseScale * factor; } } private static void ApplyAimOriginScale(CharacterBody body, Transform modelTransform, float factor) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)body) && Object.op_Implicit((Object)(object)body.aimOriginTransform) && Object.op_Implicit((Object)(object)modelTransform)) { AimOriginMarker aimOriginMarker = ((Component)body).gameObject.GetComponent(); if (!Object.op_Implicit((Object)(object)aimOriginMarker)) { aimOriginMarker = ((Component)body).gameObject.AddComponent(); aimOriginMarker.baseAimLocalPos = body.aimOriginTransform.localPosition; aimOriginMarker.modelPivotToBodyRoot = body.transform.position - modelTransform.position; } Vector3 val = (aimOriginMarker.modelPivotToBodyRoot + aimOriginMarker.baseAimLocalPos) * factor; body.aimOriginTransform.localPosition = val - aimOriginMarker.modelPivotToBodyRoot; } } protected static void ApplyModelScale(CharacterBody body, float factor) { ModelLocator modelLocator = body.modelLocator; if (Object.op_Implicit((Object)(object)modelLocator) && Object.op_Implicit((Object)(object)modelLocator.modelTransform)) { ApplyAbsoluteScale(((Component)modelLocator.modelTransform).gameObject, factor); if (IsPlayer(body)) { ApplyAimOriginScale(body, modelLocator.modelTransform, factor); } } } } internal static class RiskOfOptionsBridge { private static readonly HashSet KeepEnabledSections = new HashSet { "HUD", "Notifications", "RunTitles", "Protocol" }; private static string CategoryFor(string section) { switch (section) { case "Presets": return "0. Presets (Start Here)"; case "EnlargedSurvivor": case "ShrunkenSurvivor": case "EnlargedEnemies": case "ShrunkenEnemies": case "BiggerBullets": case "SizeRoulette": return "1. Size & Scale"; case "BossMutations": case "EnemyMutations": return "2. Mutations"; case "Hazards": case "TeleporterRules": case "CurseDeck": case "EnemySwarm": case "WorldEvents": case "StagePersonalities": return "3. Environment & Events"; case "LootMutations": case "ItemDiet": return "4. Items & Loot"; case "Contracts": case "Randomizer": case "Combos": return "5. Randomizer, Combos & Contracts"; case "TitleBranding": case "Notifications": case "RunTitles": case "HUD": return "6. Interface & Feedback"; default: return "7. Protocol Core"; } } [MethodImpl(MethodImplOptions.NoInlining)] public static void TryRegisterAll(ConfigFile cfg) { if (!Chainloader.PluginInfos.ContainsKey("com.rune580.riskofoptions")) { return; } try { RegisterAll(cfg); } catch (Exception ex) { PetrichorProtocolPlugin.Log.LogWarning((object)("Protocol: Risk of Options registration failed (" + ex.Message + "). Config remains file-based.")); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void RegisterAll(ConfigFile cfg) { int num = 0; List list = new List(((IDictionary)cfg).Values); list.Sort(delegate(ConfigEntryBase a, ConfigEntryBase b) { int num2 = string.CompareOrdinal(CategoryFor(a.Definition.Section), CategoryFor(b.Definition.Section)); if (num2 != 0) { return num2; } int num3 = string.CompareOrdinal(a.Definition.Section, b.Definition.Section); if (num3 != 0) { return num3; } bool flag = a.Definition.Key == "Enabled"; bool flag2 = b.Definition.Key == "Enabled"; return (flag != flag2) ? ((!flag) ? 1 : (-1)) : string.CompareOrdinal(a.Definition.Key, b.Definition.Key); }); foreach (ConfigEntryBase item in list) { if (!(item.Definition.Section == "DebugMenu") && TryAddOption(item)) { num++; } } TryAddResetButton(cfg); TryAddPresetButtons(); PetrichorProtocolPlugin.Log.LogInfo((object)$"Protocol: {num} settings registered with Risk of Options, bucketed into categories."); } [MethodImpl(MethodImplOptions.NoInlining)] private static void TryAddPresetButtons() { //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Expected O, but got Unknown //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Expected O, but got Unknown PresetsModule presets = null; foreach (ModuleBase module in PetrichorProtocolPlugin.Modules) { if (module is PresetsModule presetsModule) { presets = presetsModule; break; } } if (presets == null) { return; } foreach (PresetsModule.Preset value in Enum.GetValues(typeof(PresetsModule.Preset))) { if (value == PresetsModule.Preset.None) { continue; } PresetsModule.Preset captured = value; try { ModSettingsManager.AddOption((BaseOption)new GenericButtonOption(PresetsModule.Prettify(captured), "0. Presets (Start Here)", PresetsModule.Flavor(captured), "Apply", (UnityAction)delegate { presets.ApplyNow(captured); })); } catch (Exception ex) { PetrichorProtocolPlugin.Log.LogWarning((object)$"Protocol: could not add preset button for {captured} ({ex.Message})."); } } } [MethodImpl(MethodImplOptions.NoInlining)] private static void TryAddResetButton(ConfigFile cfg) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown try { ModSettingsManager.AddOption((BaseOption)new GenericButtonOption("Reset all settings to default", "7. Protocol Core", "Turn every gameplay feature off and restore all sliders and options to their original values, for a genuine clean slate. The HUD and notifications stay on. Use this to clear a complex setup and rebuild from scratch, enabling just the features you want.", "Reset", (UnityAction)delegate { ResetAllToDefault(cfg); })); } catch (Exception ex) { PetrichorProtocolPlugin.Log.LogWarning((object)("Protocol: could not add reset button (" + ex.Message + ").")); } } internal static void ResetAllToDefault(ConfigFile cfg) { int num = 0; int num2 = 0; foreach (ConfigEntryBase item in new List(((IDictionary)cfg).Values)) { try { item.BoxedValue = item.DefaultValue; num++; if (item.Definition.Key == "Enabled" && item.SettingType == typeof(bool) && !KeepEnabledSections.Contains(item.Definition.Section)) { item.BoxedValue = false; num2++; } } catch (Exception ex) { PetrichorProtocolPlugin.Log.LogWarning((object)("Protocol reset: could not reset " + item.Definition.Section + "/" + item.Definition.Key + " (" + ex.Message + ").")); } } cfg.Save(); PetrichorProtocolPlugin.Log.LogInfo((object)$"Protocol: reset {num} settings to default; {num2} gameplay features turned off."); try { Chat.AddMessage("THE PETRICHOR PROTOCOL - reset complete. All gameplay features are now off - pick the ones you want."); } catch { } } private static string FriendlySection(string section) { switch (section) { case "HUD": return "HUD"; case "Protocol": return "Protocol Core"; case "Hazards": return "Environmental Hazards"; default: { StringBuilder stringBuilder = new StringBuilder(section.Length + 4); for (int i = 0; i < section.Length; i++) { char c = section[i]; if (i > 0 && char.IsUpper(c) && !char.IsUpper(section[i - 1])) { stringBuilder.Append(' '); } stringBuilder.Append(c); } return stringBuilder.ToString(); } } } private static string FriendlyName(string section, string key) { string text = FriendlySection(section); if (key == "Enabled") { return text; } string text2 = SpaceCamel(key); return text + ": " + text2; } private static string SpaceCamel(string s) { StringBuilder stringBuilder = new StringBuilder(s.Length + 4); for (int i = 0; i < s.Length; i++) { char c = s[i]; if (i > 0 && char.IsUpper(c) && !char.IsUpper(s[i - 1])) { stringBuilder.Append(' '); } stringBuilder.Append(c); } return stringBuilder.ToString(); } [MethodImpl(MethodImplOptions.NoInlining)] private static bool TryAddOption(ConfigEntryBase entry) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Expected O, but got Unknown //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Expected O, but got Unknown try { string section = entry.Definition.Section; string category = CategoryFor(section); string name = FriendlyName(section, entry.Definition.Key); Type settingType = entry.SettingType; if (settingType == typeof(bool)) { ModSettingsManager.AddOption((BaseOption)new CheckBoxOption((ConfigEntry)(object)entry, new CheckBoxConfig { category = category, name = name })); } else if (settingType == typeof(float)) { ModSettingsManager.AddOption((BaseOption)new SliderOption((ConfigEntry)(object)entry, new SliderConfig { category = category, name = name })); } else if (settingType == typeof(int)) { ModSettingsManager.AddOption((BaseOption)new IntSliderOption((ConfigEntry)(object)entry, new IntSliderConfig { category = category, name = name })); } else { if (!settingType.IsEnum) { return false; } ModSettingsManager.AddOption((BaseOption)new ChoiceOption(entry, new ChoiceConfig { category = category, name = name })); } return true; } catch { return false; } } } internal class DebugMenuController : MonoBehaviour { internal ConfigEntry ToggleKey; internal bool WindowOpen; private Rect windowRect = new Rect(0f, 0f, 640f, 480f); private bool openedFromPauseMenu; private GUISkin scaledSkin; private GUIStyle scaledToolbarStyle; private GUIStyle scaledWrapLabelStyle; private float scaledSkinFor = -1f; private RectTransform pauseMenuMainPanelToRestore; private int selectedTab; private static readonly string[] TabNames = new string[7] { "Presets", "Size & Scale", "Mutations", "Environment & Events", "Items & Loot", "Randomizer/Combos/Contracts", "Interface & Feedback" }; private Vector2 scrollPos; private bool awaitingRebind; private static readonly Dictionary EnumNameCache = new Dictionary(); private static readonly HashSet WarnedNoRange = new HashSet(); private static readonly Dictionary> SectionCache = new Dictionary>(); private float UiScale => Mathf.Clamp((float)Screen.height / 1080f, 0.8f, 2.5f); private void EnsureScaledSkin() { //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected O, but got Unknown //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Expected O, but got Unknown float uiScale = UiScale; if (!((Object)(object)scaledSkin != (Object)null) || !Mathf.Approximately(uiScale, scaledSkinFor)) { scaledSkin = Object.Instantiate(GUI.skin); int fontSize = Mathf.RoundToInt(15f * uiScale); int fontSize2 = Mathf.RoundToInt(18f * uiScale); scaledSkin.label.fontSize = fontSize; scaledSkin.button.fontSize = fontSize; scaledSkin.toggle.fontSize = fontSize; scaledSkin.window.fontSize = fontSize2; scaledSkin.horizontalSlider.fixedHeight = 16f * uiScale; scaledSkin.horizontalSliderThumb.fixedHeight = 16f * uiScale; scaledToolbarStyle = new GUIStyle(scaledSkin.FindStyle("toolbarbutton") ?? scaledSkin.button) { fontSize = fontSize }; scaledWrapLabelStyle = new GUIStyle(scaledSkin.label) { wordWrap = true }; scaledSkinFor = uiScale; } } private void ResetWindowSizeAndCenter() { ((Rect)(ref windowRect)).width = (float)Screen.width * 0.5f * 0.83f; ((Rect)(ref windowRect)).height = (float)Screen.height * 0.5f; ((Rect)(ref windowRect)).x = (float)Screen.width * 0.03f; ((Rect)(ref windowRect)).y = ((float)Screen.height - ((Rect)(ref windowRect)).height) / 2f; } private static string[] EnumNamesFor(Type t) { if (!EnumNameCache.TryGetValue(t, out var value)) { value = Enum.GetNames(t); EnumNameCache[t] = value; } return value; } private static void RenderEntry(ConfigEntryBase entry) { string text = SpaceCamel(entry.Definition.Key); Type settingType = entry.SettingType; if (settingType == typeof(bool)) { ConfigEntry obj = (ConfigEntry)(object)entry; obj.Value = GUILayout.Toggle(obj.Value, text, Array.Empty()); } else if (settingType == typeof(float)) { ConfigEntry val = (ConfigEntry)(object)entry; float num = 0f; float num2 = 1f; ConfigDescription description = entry.Description; if (((description != null) ? description.AcceptableValues : null) is AcceptableValueRange val2) { num = val2.MinValue; num2 = val2.MaxValue; } else { WarnNoRange(entry); } GUILayout.Label($"{text}: {val.Value:0.00}", Array.Empty()); val.Value = GUILayout.HorizontalSlider(val.Value, num, num2, Array.Empty()); } else if (settingType == typeof(int)) { ConfigEntry val3 = (ConfigEntry)(object)entry; int num3 = 0; int num4 = 100; ConfigDescription description2 = entry.Description; if (((description2 != null) ? description2.AcceptableValues : null) is AcceptableValueRange val4) { num3 = val4.MinValue; num4 = val4.MaxValue; } else { WarnNoRange(entry); } GUILayout.Label($"{text}: {val3.Value}", Array.Empty()); val3.Value = Mathf.RoundToInt(GUILayout.HorizontalSlider((float)val3.Value, (float)num3, (float)num4, Array.Empty())); } else if (settingType.IsEnum) { string[] array = EnumNamesFor(settingType); int num5 = Array.IndexOf(array, entry.BoxedValue.ToString()); if (num5 < 0) { num5 = 0; } GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(text + ": " + array[num5], Array.Empty()); if (GUILayout.Button("<", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(24f) })) { entry.BoxedValue = Enum.Parse(settingType, array[(num5 - 1 + array.Length) % array.Length]); } if (GUILayout.Button(">", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(24f) })) { entry.BoxedValue = Enum.Parse(settingType, array[(num5 + 1) % array.Length]); } GUILayout.EndHorizontal(); } } private static void WarnNoRange(ConfigEntryBase entry) { string item = entry.Definition.Section + "/" + entry.Definition.Key; if (!WarnedNoRange.Contains(item)) { WarnedNoRange.Add(item); PetrichorProtocolPlugin.Log.LogWarning((object)("DebugMenu: " + entry.Definition.Section + "/" + entry.Definition.Key + " has no AcceptableValueRange - using a default 0-1/0-100 slider range that may not fit its real values.")); } } private static string SpaceCamel(string s) { StringBuilder stringBuilder = new StringBuilder(s.Length + 4); for (int i = 0; i < s.Length; i++) { char c = s[i]; if (i > 0 && char.IsUpper(c) && !char.IsUpper(s[i - 1])) { stringBuilder.Append(' '); } stringBuilder.Append(c); } return stringBuilder.ToString(); } private static string FriendlySection(string section) { return section switch { "HUD" => "HUD", "Protocol" => "Protocol Core", "Hazards" => "Environmental Hazards", _ => SpaceCamel(section), }; } private static List EntriesForSections(ConfigFile cfg, params string[] sections) { string key = string.Join("|", sections); if (SectionCache.TryGetValue(key, out var value)) { return value; } HashSet hashSet = new HashSet(sections); List list = new List(); foreach (KeyValuePair item in (IEnumerable>)cfg) { if (hashSet.Contains(item.Key.Section)) { list.Add(item.Value); } } list.Sort(delegate(ConfigEntryBase a, ConfigEntryBase b) { int num = string.CompareOrdinal(a.Definition.Section, b.Definition.Section); if (num != 0) { return num; } bool flag = a.Definition.Key == "Enabled"; bool flag2 = b.Definition.Key == "Enabled"; return (flag != flag2) ? ((!flag) ? 1 : (-1)) : string.CompareOrdinal(a.Definition.Key, b.Definition.Key); }); SectionCache[key] = list; return list; } private void Awake() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown ToggleKey = ((BaseUnityPlugin)PetrichorProtocolPlugin.Instance).Config.Bind("DebugMenu", "ToggleKey", (KeyCode)284, "Opens/closes the PetrichorProtocol debug menu. Default F3 (not F2 - SpawnBox, a separate Dileppy mod, already uses F2)."); PauseScreenController.Awake += new hook_Awake(PauseScreenController_Awake); } private void Update() { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Invalid comparison between Unknown and I4 //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) if (openedFromPauseMenu && PauseScreenController.instancesList.Count == 0) { WindowOpen = false; openedFromPauseMenu = false; pauseMenuMainPanelToRestore = null; } if (awaitingRebind) { if (Input.GetKeyDown((KeyCode)27)) { awaitingRebind = false; } else { if (!Input.anyKeyDown) { return; } foreach (KeyCode value in Enum.GetValues(typeof(KeyCode))) { if ((int)value < 323 && Input.GetKeyDown(value)) { ToggleKey.Value = value; awaitingRebind = false; break; } } } } else if (Input.GetKeyDown(ToggleKey.Value)) { bool windowOpen = WindowOpen; WindowOpen = !WindowOpen; if (WindowOpen && !windowOpen) { ResetWindowSizeAndCenter(); } if (!WindowOpen) { RestorePauseMenuIfNeeded(); } } } private void OnGUI() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (WindowOpen) { EnsureScaledSkin(); GUISkin skin = GUI.skin; GUI.skin = scaledSkin; GUILayout.Window(((Object)this).GetInstanceID(), windowRect, new WindowFunction(DrawWindow), "PetrichorProtocol", Array.Empty()); GUI.skin = skin; } } private void DrawPresetsTab() { ConfigFile config = ((BaseUnityPlugin)PetrichorProtocolPlugin.Instance).Config; PresetsModule presetsModule = null; foreach (ModuleBase module in PetrichorProtocolPlugin.Modules) { if (module is PresetsModule presetsModule2) { presetsModule = presetsModule2; break; } } if (presetsModule == null) { GUILayout.Label("Presets module not found.", Array.Empty()); return; } foreach (ConfigEntryBase item in EntriesForSections(config, "Presets")) { if (item.Definition.Key == "SelectedPreset") { GUILayout.Label("Currently selected: " + PresetsModule.Prettify(presetsModule.Selected.Value), Array.Empty()); } else { RenderEntry(item); } } GUILayout.Space(8f); GUILayout.Label("Apply a preset:", Array.Empty()); float num = Mathf.Max(200f, ((Rect)(ref windowRect)).width - 60f); foreach (PresetsModule.Preset value in Enum.GetValues(typeof(PresetsModule.Preset))) { if (value == PresetsModule.Preset.None) { continue; } PresetsModule.Preset p = value; GUILayout.Label(PresetsModule.Prettify(p) + " - " + PresetsModule.Flavor(p), scaledWrapLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(num) }); if (GUILayout.Button("Apply", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(160f) })) { try { presetsModule.ApplyNow(p); } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"DebugMenu: preset apply failed - {arg}"); } } GUILayout.Space(6f); } GUILayout.Space(8f); if (GUILayout.Button("Reset all settings to default", Array.Empty())) { RiskOfOptionsBridge.ResetAllToDefault(config); } } private static void DrawSectionedTab(params string[] sections) { foreach (ConfigEntryBase item in EntriesForSections(((BaseUnityPlugin)PetrichorProtocolPlugin.Instance).Config, sections)) { if (item.Definition.Key == "Enabled") { GUILayout.Space(6f); GUILayout.Label(FriendlySection(item.Definition.Section), Array.Empty()); } RenderEntry(item); } } private void DrawInterfaceFeedbackTab() { //IL_00f3: Unknown result type (might be due to invalid IL or missing references) DrawSectionedTab("RunTitles", "HUD", "Notifications", "TitleBranding"); GUILayout.Space(10f); GUILayout.Label("— Protocol Core —", Array.Empty()); foreach (ConfigEntryBase item in EntriesForSections(((BaseUnityPlugin)PetrichorProtocolPlugin.Instance).Config, "Protocol", "DebugMenu")) { if (!(item.Definition.Section == "DebugMenu") || !(item.Definition.Key == "ToggleKey")) { RenderEntry(item); } } GUILayout.Space(6f); if (awaitingRebind) { GUILayout.Label("Press any key (Esc to cancel)...", Array.Empty()); return; } GUILayout.Label($"Toggle Key: {ToggleKey.Value}", Array.Empty()); if (GUILayout.Button("Rebind", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) })) { awaitingRebind = true; } } private void DrawWindow(int id) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) selectedTab = GUILayout.Toolbar(selectedTab, TabNames, scaledToolbarStyle, Array.Empty()); scrollPos = GUILayout.BeginScrollView(scrollPos, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(((Rect)(ref windowRect)).height - 100f) }); try { switch (selectedTab) { case 0: DrawPresetsTab(); break; case 1: DrawSectionedTab("ShrunkenSurvivor", "EnlargedSurvivor", "ShrunkenEnemies", "EnlargedEnemies", "BiggerBullets", "SizeRoulette"); break; case 2: DrawSectionedTab("EnemyMutations", "BossMutations"); break; case 3: DrawSectionedTab("CurseDeck", "Hazards", "WorldEvents", "StagePersonalities", "TeleporterRules", "EnemySwarm"); break; case 4: DrawSectionedTab("ItemDiet", "LootMutations"); break; case 5: DrawSectionedTab("Combos", "Randomizer", "Contracts"); break; case 6: DrawInterfaceFeedbackTab(); break; default: GUILayout.Label(TabNames[selectedTab] + " - added in a later task", Array.Empty()); break; } } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"DebugMenu: tab '{TabNames[selectedTab]}' failed to render - {arg}"); GUILayout.Label("This tab failed to render. See the log (" + TabNames[selectedTab] + ").", Array.Empty()); } GUILayout.EndScrollView(); if (GUILayout.Button("Close", Array.Empty())) { WindowOpen = false; RestorePauseMenuIfNeeded(); } GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref windowRect)).width, 20f)); } private void PauseScreenController_Awake(orig_Awake orig, PauseScreenController self) { orig.Invoke(self); try { AddPauseMenuButton(self); } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"DebugMenu: failed to add pause menu button - {arg}"); } } private void AddPauseMenuButton(PauseScreenController controller) { //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Expected O, but got Unknown if ((Object)(object)controller.exitGameButton == (Object)null) { PetrichorProtocolPlugin.Log.LogWarning((object)"DebugMenu: PauseScreenController.exitGameButton was null, cannot add pause menu button."); return; } GameObject obj = Object.Instantiate(controller.exitGameButton, controller.exitGameButton.transform.parent); ((Object)obj).name = "PetrichorProtocolPauseButton"; LanguageTextMeshController component = obj.GetComponent(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } HGTextMeshProUGUI componentInChildren = obj.GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null) { ((TMP_Text)componentInChildren).text = "PetrichorProtocol"; } HGButton component2 = obj.GetComponent(); if ((Object)(object)component2 == (Object)null) { PetrichorProtocolPlugin.Log.LogWarning((object)"DebugMenu: cloned pause menu button has no HGButton component, cannot wire click."); return; } for (int i = 0; i < ((UnityEventBase)((Button)component2).onClick).GetPersistentEventCount(); i++) { ((UnityEventBase)((Button)component2).onClick).SetPersistentListenerState(i, (UnityEventCallState)0); } ((UnityEventBase)((Button)component2).onClick).RemoveAllListeners(); ((UnityEvent)((Button)component2).onClick).AddListener((UnityAction)delegate { OnPauseMenuButtonClicked(controller); }); } private void OnPauseMenuButtonClicked(PauseScreenController controller) { try { ((Component)controller.mainPanel).gameObject.SetActive(false); pauseMenuMainPanelToRestore = controller.mainPanel; openedFromPauseMenu = true; WindowOpen = true; ResetWindowSizeAndCenter(); } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"DebugMenu: pause menu button click failed - {arg}"); } } private void LateUpdate() { if (openedFromPauseMenu && (Object)(object)pauseMenuMainPanelToRestore != (Object)null) { ((Component)pauseMenuMainPanelToRestore).gameObject.SetActive(false); } } private void RestorePauseMenuIfNeeded() { if (openedFromPauseMenu) { openedFromPauseMenu = false; if ((Object)(object)pauseMenuMainPanelToRestore != (Object)null) { ((Component)pauseMenuMainPanelToRestore).gameObject.SetActive(true); } pauseMenuMainPanelToRestore = null; } } } internal class TeleporterRulesModule : ModuleBase { public enum TeleRule { None, ShrinkingZone, BloodZone, EnemyFavoredZone, Overcharged } public ConfigEntry RuleChance; private TeleRule active; private HoldoutZoneController zone; private float baseRadiusCache = -1f; private static readonly Random rng = new Random(); public override string Name => "Teleporter Rules"; public override string StandaloneGuid => null; public override int Stars => 2; public override bool UsesStageStart => true; public override bool UsesFixedUpdate => true; public override bool UsesRecalcStats => true; public override bool UsesHeal => true; public override void Bind(ConfigFile cfg) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown Enabled = cfg.Bind("TeleporterRules", "Enabled", false, "Each teleporter event can roll a rule modifier: Shrinking Zone, Blood Zone, Enemy-Favored Zone, Overcharged."); RuleChance = cfg.Bind("TeleporterRules", "TeleporterRuleChance", 50f, new ConfigDescription("Chance (percent) that starting a teleporter event applies one random rule modifier to it.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); HoldoutZoneController.OnEnable += new hook_OnEnable(Zone_OnEnable); } public override void Unhook() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown HoldoutZoneController.OnEnable -= new hook_OnEnable(Zone_OnEnable); } public override void OnStageStart(Stage stage) { active = TeleRule.None; zone = null; baseRadiusCache = -1f; } private void Zone_OnEnable(orig_OnEnable orig, HoldoutZoneController self) { orig.Invoke(self); if (!base.Active || !NetworkServer.active || !Object.op_Implicit((Object)(object)TeleporterInteraction.instance) || (Object)(object)((Component)self).gameObject != (Object)(object)((Component)TeleporterInteraction.instance.holdoutZoneController).gameObject) { return; } double num = rng.NextDouble() * 100.0; if (num > (double)RuleChance.Value) { DebugLog($"Teleporter rule rolled {num:0.0}% vs {RuleChance.Value:0.0}% chance - no rule applied this event"); active = TeleRule.None; return; } active = (TeleRule)(1 + rng.Next(4)); zone = self; baseRadiusCache = self.baseRadius; DebugLog($"Teleporter rule rolled {num:0.0}% vs {RuleChance.Value:0.0}% chance - applying {active} (baseRadius={baseRadiusCache:0.0})"); if (NotificationsModule.Gate) { string arg = active switch { TeleRule.ShrinkingZone => "The zone shrinks as it charges. It gets personal at the end.", TeleRule.BloodZone => "Inside the zone: +25% damage dealt, healing cut to 60%.", TeleRule.EnemyFavoredZone => "Enemies inside the zone gain 40 armor. Kite them out or fight uphill.", TeleRule.Overcharged => "Charges 50% faster - but the boss is much stronger.", _ => "", }; Chat.AddMessage($"TELEPORTER RULE: {active} - {arg}"); } } public override void OnFixedUpdateServer() { if (active == TeleRule.None || !Object.op_Implicit((Object)(object)zone) || !((Behaviour)zone).isActiveAndEnabled) { return; } try { if (active == TeleRule.ShrinkingZone && baseRadiusCache > 0f) { zone.baseRadius = Mathf.Lerp(baseRadiusCache, baseRadiusCache * 0.35f, zone.charge); } else if (active == TeleRule.Overcharged) { zone.charge = Mathf.Min(1f, zone.charge + 0.5f * Time.fixedDeltaTime * ChargeRatePerSecond(zone)); } } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"{Name}: zone tick failed: {arg}"); } } private static float ChargeRatePerSecond(HoldoutZoneController z) { if (!(z.baseChargeDuration > 0f)) { return 0f; } return 1f / z.baseChargeDuration; } public bool InZone(CharacterBody body) { if (active != TeleRule.None && Object.op_Implicit((Object)(object)zone) && ((Behaviour)zone).isActiveAndEnabled) { return zone.IsBodyInChargingRadius(body); } return false; } public override void OnRecalcStats(CharacterBody self) { if (active == TeleRule.None || !Object.op_Implicit((Object)(object)zone)) { return; } switch (active) { case TeleRule.BloodZone: if (ModuleBase.IsPlayer(self) && InZone(self)) { self.damage *= 1.25f; } break; case TeleRule.EnemyFavoredZone: if (ModuleBase.IsHostileEnemy(self) && InZone(self)) { self.armor += 40f; } break; case TeleRule.Overcharged: if (self.isBoss) { self.maxHealth *= 1.5f; self.damage *= 1.4f; } break; } } public override float ModifyHeal(HealthComponent target, float amount) { if (active != TeleRule.BloodZone || !Object.op_Implicit((Object)(object)target.body) || !ModuleBase.IsPlayer(target.body) || !InZone(target.body)) { return amount; } return amount * 0.6f; } } internal class StagePersonalitiesModule : ModuleBase { public enum Personality { None, LowGravity, TreasureStorm, MonsterMigration, BossTerritory } public ConfigEntry PersonalityChance; public ConfigEntry RewardBonus; private Personality active; private Personality previous; private Vector3 baseGravity; private bool gravityStored; private float nextMigrationPulse; private static readonly Random rng = new Random(); public override string Name => "Stage Personalities"; public override string StandaloneGuid => null; public override int Stars => 2; public override bool UsesStageStart => true; public override bool UsesRunStart => true; public override bool UsesRecalcStats => true; public override bool UsesFixedUpdate => true; public override bool UsesReward => true; public override void OnRunStart() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (gravityStored) { Physics.gravity = baseGravity; gravityStored = false; } active = Personality.None; previous = Personality.None; } public override void Bind(ConfigFile cfg) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Expected O, but got Unknown Enabled = cfg.Bind("StagePersonalities", "Enabled", false, "Every stage can roll a temporary identity: Low Gravity, Treasure Storm, Monster Migration, Boss Territory."); PersonalityChance = cfg.Bind("StagePersonalities", "PersonalityChance", 60f, new ConfigDescription("Chance (percent) that a stage takes on one random personality when you arrive.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); RewardBonus = cfg.Bind("StagePersonalities", "RewardMultiplier", 0.2f, new ConfigDescription("Extra gold/XP granted while a stage personality is active, as a fraction (0.2 = +20%).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 0.5f), Array.Empty())); SceneDirector.Start += new hook_Start(SceneDirector_Start); } public override void Unhook() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown SceneDirector.Start -= new hook_Start(SceneDirector_Start); } public override void OnStageStart(Stage stage) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02a0: Unknown result type (might be due to invalid IL or missing references) //IL_02a5: Unknown result type (might be due to invalid IL or missing references) //IL_02b1: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_0310: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Unknown result type (might be due to invalid IL or missing references) if (gravityStored) { Physics.gravity = baseGravity; gravityStored = false; } active = Personality.None; if (!NetworkServer.active) { return; } double num = rng.NextDouble() * 100.0; if (num > (double)PersonalityChance.Value) { DebugLog($"Stage personality rolled {num:0.0}% vs {PersonalityChance.Value:0.0}% chance - stage stays plain"); return; } Personality personality; do { personality = (Personality)(1 + rng.Next(4)); } while (personality == previous); active = personality; previous = personality; nextMigrationPulse = Time.time + 25f; DebugLog($"Stage personality rolled {num:0.0}% vs {PersonalityChance.Value:0.0}% chance - applying {active} (reward bonus +{RewardBonus.Value:P0})"); if (active == Personality.LowGravity) { if (!gravityStored) { baseGravity = Physics.gravity; gravityStored = true; } Physics.gravity = baseGravity * 0.55f; DebugLog($"Low Gravity applied - gravity set to {Physics.gravity} (base {baseGravity} x0.55)"); } else if (active == Personality.BossTerritory) { List list = new List(); foreach (CharacterMaster allAiMaster in MasterCatalog.allAiMasters) { if (Object.op_Implicit((Object)(object)allAiMaster) && Object.op_Implicit((Object)(object)allAiMaster.bodyPrefab)) { CharacterBody component = allAiMaster.bodyPrefab.GetComponent(); if (Object.op_Implicit((Object)(object)component) && component.isChampion) { list.Add(allAiMaster.masterIndex); } } } if (list.Count > 0) { GameObject masterPrefab = MasterCatalog.GetMasterPrefab(list[rng.Next(list.Count)]); ReadOnlyCollection instances = PlayerCharacterMasterController.instances; Vector3 val = Vector3.zero; if (instances != null && instances.Count > 0 && Object.op_Implicit((Object)(object)instances[0]) && Object.op_Implicit((Object)(object)instances[0].master)) { CharacterBody body = instances[0].master.GetBody(); if (Object.op_Implicit((Object)(object)body)) { val = body.corePosition + new Vector3(60f, 10f, 60f); } } CharacterMaster val2 = new MasterSummon { masterPrefab = masterPrefab, position = val, rotation = Quaternion.identity, teamIndexOverride = (TeamIndex)2, ignoreTeamMemberLimit = true }.Perform(); if ((Object)(object)val2 == (Object)null) { PetrichorProtocolPlugin.Log.LogWarning((object)(Name + ": BossTerritory summon failed this stage.")); } else { CharacterBody body2 = val2.GetBody(); DebugLog(string.Format("Boss Territory spawned {0} at {1} ({2} champion candidates available)", Object.op_Implicit((Object)(object)body2) ? Util.GetBestBodyName(((Component)body2).gameObject) : "unknown body", val, list.Count)); } } else { DebugLog("Boss Territory rolled but no champion-class master candidates were found - nothing spawned"); } } if (NotificationsModule.Gate) { string arg = active switch { Personality.LowGravity => "Gravity is light here. Enjoy the airtime.", Personality.TreasureStorm => "Riches everywhere - and the enemies know you're greedy.", Personality.MonsterMigration => "The swarms run deep on this one.", Personality.BossTerritory => "Something enormous already lives here.", _ => "", }; Chat.AddMessage($"STAGE PERSONALITY: {active} - {arg} (+{RewardBonus.Value:P0} rewards)"); } } private void SceneDirector_Start(orig_Start orig, SceneDirector self) { if (base.Active && NetworkServer.active && active == Personality.TreasureStorm) { try { int interactableCredit = self.interactableCredit; self.interactableCredit = (int)((float)self.interactableCredit * 1.6f); DebugLog($"Treasure Storm boosted interactable credit {interactableCredit} -> {self.interactableCredit} (x1.6)"); } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"{Name}: credit boost failed: {arg}"); } } orig.Invoke(self); } public override void OnRecalcStats(CharacterBody self) { if (active == Personality.TreasureStorm && ModuleBase.IsHostileEnemy(self)) { self.maxHealth *= 1.15f; self.damage *= 1.15f; } } public override void OnFixedUpdateServer() { if (active != Personality.MonsterMigration || Time.time < nextMigrationPulse) { return; } nextMigrationPulse = Time.time + 25f; try { float num = 40f * (Object.op_Implicit((Object)(object)Run.instance) ? Run.instance.difficultyCoefficient : 1f); int num2 = 0; foreach (CombatDirector instances in CombatDirector.instancesList) { if (Object.op_Implicit((Object)(object)instances)) { instances.monsterCredit += num; num2++; } } DebugLog($"Monster Migration pulse added {num:0.0} credit to {num2} combat director(s) - next pulse in 25s"); } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"{Name}: migration pulse failed: {arg}"); } } public override float RewardMultiplier(DamageReport report) { if (active == Personality.None) { return 1f; } return 1f + RewardBonus.Value; } } internal class WorldEventsModule : ModuleBase { public enum WorldEvent { None, TheHunt, SupplyDrop, MutationWave, TitanArrival, GravityFailure, BloodFrenzy } public class HunterMarker : MonoBehaviour { } private class TitanSizeMarker : MonoBehaviour { public Vector3 baseScale; } public class TitanMarker : MonoBehaviour { } public ConfigEntry MinInterval; public ConfigEntry MaxInterval; public ConfigEntry EventDuration; internal WorldEvent active; private float nextEventTime; private float eventEndTime; private Vector3 baseGravity; private bool gravityStored; private CharacterMaster hunter; private static readonly Random rng = new Random(); public override string Name => "World Events"; public override string StandaloneGuid => null; public override int Stars => 3; public override bool UsesRunStart => true; public override bool UsesStageStart => true; public override bool UsesFixedUpdate => true; public override bool UsesRecalcStats => true; public override bool UsesReward => true; public bool MutationWaveActive => active == WorldEvent.MutationWave; public override void Bind(ConfigFile cfg) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Expected O, but got Unknown Enabled = cfg.Bind("WorldEvents", "Enabled", false, "Every few minutes a temporary world event can trigger: The Hunt, Supply Drop, Mutation Wave, Titan Arrival, Gravity Failure, Blood Frenzy."); MinInterval = cfg.Bind("WorldEvents", "MinimumTimeBetweenEvents", 120f, new ConfigDescription("Shortest wait, in seconds, before another world event can fire. The actual gap is random between this and the maximum.", (AcceptableValueBase)(object)new AcceptableValueRange(30f, 600f), Array.Empty())); MaxInterval = cfg.Bind("WorldEvents", "MaximumTimeBetweenEvents", 300f, new ConfigDescription("Longest wait, in seconds, before a world event must fire. The actual gap is random between the minimum and this.", (AcceptableValueBase)(object)new AcceptableValueRange(60f, 900f), Array.Empty())); EventDuration = cfg.Bind("WorldEvents", "EventDuration", 60f, new ConfigDescription("How long a timed world event stays active, in seconds (The Hunt runs until the hunter dies; instant events like Supply Drop ignore this).", (AcceptableValueBase)(object)new AcceptableValueRange(15f, 180f), Array.Empty())); HealthComponent.TakeDamage += new hook_TakeDamage(HealthComponent_TakeDamage); } public override void Unhook() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown HealthComponent.TakeDamage -= new hook_TakeDamage(HealthComponent_TakeDamage); } public override void OnRunStart() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (gravityStored) { Physics.gravity = baseGravity; gravityStored = false; } active = WorldEvent.None; ScheduleNext(); } public override void OnStageStart(Stage stage) { EndEvent(silent: true); ScheduleNext(); } private void ScheduleNext() { float num = Mathf.Min(MinInterval.Value, MaxInterval.Value); float num2 = Mathf.Max(MinInterval.Value, MaxInterval.Value); float num3 = num + (float)rng.NextDouble() * (num2 - num); nextEventTime = Time.time + num3; DebugLog($"Next world event scheduled in {num3:0.0}s (range {num:0.0}-{num2:0.0}s)"); } public override void OnFixedUpdateServer() { if (active != WorldEvent.None) { bool flag = active == WorldEvent.TheHunt && ((Object)(object)hunter == (Object)null || !hunter.hasBody); if (Time.time >= eventEndTime || flag) { EndEvent(silent: false); } } else if (!(Time.time < nextEventTime)) { WorldEvent worldEvent = (WorldEvent)(1 + rng.Next(6)); DebugLog($"World event timer elapsed - picked {worldEvent}"); StartEvent(worldEvent); } } private void StartEvent(WorldEvent e) { //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) active = e; eventEndTime = Time.time + EventDuration.Value; string arg = ""; try { switch (e) { case WorldEvent.TheHunt: hunter = SummonHunter(); if ((Object)(object)hunter == (Object)null) { DebugLog("The Hunt failed to find a champion candidate or valid target - skipped, rescheduling"); active = WorldEvent.None; ScheduleNext(); return; } eventEndTime = Time.time + 600f; arg = "A hunter has your scent. Kill it for a rich bounty."; DebugLog("The Hunt started - hunter " + Util.GetBestBodyName(((Component)hunter.GetBody()).gameObject) + " summoned, +Fire elite equipment, +30 BoostHp, +15 BoostDamage"); break; case WorldEvent.SupplyDrop: if (!SpawnSupplyDrop()) { DebugLog("Supply Drop rolled but spawn failed (no chest spawn card or no valid director spawn) - skipped, rescheduling"); active = WorldEvent.None; ScheduleNext(); return; } eventEndTime = Time.time + 1f; arg = "Extra supplies have appeared somewhere on the map."; DebugLog("Supply Drop spawned a chest near the lead player"); break; case WorldEvent.MutationWave: arg = "Mutation rates are spiking. Everything spawning now is wrong."; DebugLog($"Mutation Wave started - active for {EventDuration.Value:0.0}s (EnemyMutationsModule reads MutationWaveActive)"); break; case WorldEvent.TitanArrival: if (!TitanizeRandomEnemy()) { DebugLog("Titan Arrival rolled but no eligible non-boss enemy was alive on the stage - skipped, rescheduling"); active = WorldEvent.None; ScheduleNext(); return; } eventEndTime = Time.time + 1f; arg = "One of them just became enormous."; DebugLog("Titan Arrival applied - one enemy scaled to 2.6x model size, x3.5 max health, x1.6 damage, x0.8 move speed"); break; case WorldEvent.GravityFailure: if (!gravityStored) { baseGravity = Physics.gravity; gravityStored = true; } Physics.gravity = baseGravity * 0.45f; arg = "Gravity is failing. Temporarily. Probably."; DebugLog($"Gravity Failure started - gravity set to {Physics.gravity} (base {baseGravity} x0.45), lasts {EventDuration.Value:0.0}s"); break; case WorldEvent.BloodFrenzy: MarkAllStatsDirty(); arg = "Everything attacks faster and dies easier. Including you."; DebugLog($"Blood Frenzy started - x1.5 attack speed for everyone, x1.35 damage taken via TakeDamage hook, lasts {EventDuration.Value:0.0}s"); break; } if (NotificationsModule.Gate) { Chat.AddMessage($"WORLD EVENT: {e} - {arg}"); } } catch (Exception arg2) { PetrichorProtocolPlugin.Log.LogError((object)$"{Name}: event start failed: {arg2}"); active = WorldEvent.None; ScheduleNext(); } } private void EndEvent(bool silent) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) if (active == WorldEvent.None) { ScheduleNext(); return; } WorldEvent worldEvent = active; if (worldEvent == WorldEvent.GravityFailure && gravityStored) { Physics.gravity = baseGravity; } if (worldEvent == WorldEvent.TheHunt && (Object)(object)hunter != (Object)null && hunter.hasBody) { hunter.TrueKill(); } hunter = null; active = WorldEvent.None; if (worldEvent == WorldEvent.BloodFrenzy) { MarkAllStatsDirty(); } DebugLog($"World event {worldEvent} ended (silent={silent})"); if (!silent && NotificationsModule.Gate && worldEvent != WorldEvent.SupplyDrop && worldEvent != WorldEvent.TitanArrival) { Chat.AddMessage($"World event over: {worldEvent}."); } ScheduleNext(); } private static void MarkAllStatsDirty() { foreach (CharacterBody readOnlyInstances in CharacterBody.readOnlyInstancesList) { if (Object.op_Implicit((Object)(object)readOnlyInstances)) { readOnlyInstances.MarkAllStatsDirty(); } } } private CharacterMaster SummonHunter() { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) List list = new List(); foreach (CharacterMaster allAiMaster in MasterCatalog.allAiMasters) { if (Object.op_Implicit((Object)(object)allAiMaster) && Object.op_Implicit((Object)(object)allAiMaster.bodyPrefab)) { CharacterBody component = allAiMaster.bodyPrefab.GetComponent(); if (Object.op_Implicit((Object)(object)component) && component.isChampion) { list.Add(allAiMaster.masterIndex); } } } if (list.Count == 0) { return null; } ReadOnlyCollection instances = PlayerCharacterMasterController.instances; if (instances == null || instances.Count == 0) { return null; } CharacterBody val = (Object.op_Implicit((Object)(object)instances[0].master) ? instances[0].master.GetBody() : null); if (!Object.op_Implicit((Object)(object)val)) { return null; } CharacterMaster val2 = new MasterSummon { masterPrefab = MasterCatalog.GetMasterPrefab(list[rng.Next(list.Count)]), position = val.corePosition + new Vector3(40f, 8f, 40f), rotation = Quaternion.identity, teamIndexOverride = (TeamIndex)2, ignoreTeamMemberLimit = true }.Perform(); if ((Object)(object)val2 == (Object)null) { return null; } if (Object.op_Implicit((Object)(object)val2.inventory)) { EliteDef fire = Elites.Fire; if (Object.op_Implicit((Object)(object)fire) && Object.op_Implicit((Object)(object)fire.eliteEquipmentDef)) { val2.inventory.SetEquipmentIndex(fire.eliteEquipmentDef.equipmentIndex, false); } val2.inventory.GiveItemPermanent(Items.BoostHp, 30); val2.inventory.GiveItemPermanent(Items.BoostDamage, 15); } ((Component)val2).gameObject.AddComponent(); return val2; } private bool SpawnSupplyDrop() { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Expected O, but got Unknown //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown InteractableSpawnCard val = LegacyResourcesAPI.Load("SpawnCards/InteractableSpawnCard/iscChest1"); if (!Object.op_Implicit((Object)(object)val)) { PetrichorProtocolPlugin.Log.LogWarning((object)(Name + ": chest spawn card not found in this game version - skipping Supply Drop.")); return false; } ReadOnlyCollection instances = PlayerCharacterMasterController.instances; if (instances == null || instances.Count == 0) { return false; } CharacterBody val2 = (Object.op_Implicit((Object)(object)instances[0].master) ? instances[0].master.GetBody() : null); if (!Object.op_Implicit((Object)(object)val2)) { return false; } DirectorPlacementRule val3 = new DirectorPlacementRule { placementMode = (PlacementMode)1, position = val2.corePosition, minDistance = 15f, maxDistance = 60f }; DirectorSpawnRequest val4 = new DirectorSpawnRequest((SpawnCard)(object)val, val3, new Xoroshiro128Plus((ulong)rng.Next())); return (Object)(object)(Object.op_Implicit((Object)(object)DirectorCore.instance) ? DirectorCore.instance.TrySpawnObject(val4) : null) != (Object)null; } private bool TitanizeRandomEnemy() { //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) List list = new List(); foreach (CharacterBody readOnlyInstances in CharacterBody.readOnlyInstancesList) { if (Object.op_Implicit((Object)(object)readOnlyInstances) && ModuleBase.IsHostileEnemy(readOnlyInstances) && !readOnlyInstances.isBoss && Object.op_Implicit((Object)(object)readOnlyInstances.healthComponent) && readOnlyInstances.healthComponent.alive) { list.Add(readOnlyInstances); } } if (list.Count == 0) { return false; } CharacterBody val = list[rng.Next(list.Count)]; DebugLog($"Titan Arrival chose {Util.GetBestBodyName(((Component)val).gameObject)} out of {list.Count} eligible enemies"); ModelLocator modelLocator = val.modelLocator; if (Object.op_Implicit((Object)(object)modelLocator) && Object.op_Implicit((Object)(object)modelLocator.modelTransform)) { GameObject gameObject = ((Component)modelLocator.modelTransform).gameObject; TitanSizeMarker titanSizeMarker = gameObject.GetComponent(); if (!Object.op_Implicit((Object)(object)titanSizeMarker)) { titanSizeMarker = gameObject.AddComponent(); titanSizeMarker.baseScale = gameObject.transform.localScale; } gameObject.transform.localScale = titanSizeMarker.baseScale * 2.6f; } ((Component)val).gameObject.AddComponent(); val.RecalculateStats(); if (Object.op_Implicit((Object)(object)val.healthComponent)) { val.healthComponent.HealFraction(1f, default(ProcChainMask)); } return true; } public override void OnRecalcStats(CharacterBody self) { if (Object.op_Implicit((Object)(object)((Component)self).GetComponent())) { self.maxHealth *= 3.5f; self.damage *= 1.6f; self.moveSpeed *= 0.8f; } if (active == WorldEvent.BloodFrenzy) { self.attackSpeed *= 1.5f; } } private void HealthComponent_TakeDamage(orig_TakeDamage orig, HealthComponent self, DamageInfo damageInfo) { if (base.Active && active == WorldEvent.BloodFrenzy && damageInfo != null) { damageInfo.damage *= 1.35f; } orig.Invoke(self, damageInfo); } public override float RewardMultiplier(DamageReport report) { if ((Object)(object)report?.victimBody == (Object)null) { return 1f; } if (Object.op_Implicit((Object)(object)((Component)report.victimBody).GetComponent())) { return 6f; } if (Object.op_Implicit((Object)(object)((Component)report.victimBody).GetComponent())) { return 3f; } return 1f; } } internal class EnemySwarmModule : ModuleBase { public class SwarmMark : MonoBehaviour { } public ConfigEntry SwarmChance; public ConfigEntry SwarmSize; private static readonly Random rng = new Random(); public override string Name => "Enemy Swarm"; public override string StandaloneGuid => null; public override int Stars => 3; public override bool UsesStageStart => true; public override bool UsesReward => true; public override void Bind(ConfigFile cfg) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown Enabled = cfg.Bind("EnemySwarm", "Enabled", false, "Occasionally floods a stage with many copies of one random enemy type, for a themed swarm encounter. Off by default."); SwarmChance = cfg.Bind("EnemySwarm", "SwarmChancePerStage", 35f, new ConfigDescription("Chance (percent) that a stage spawns a single-type swarm.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); SwarmSize = cfg.Bind("EnemySwarm", "SwarmSize", 10, new ConfigDescription("How many copies of the chosen enemy type to spawn.", (AcceptableValueBase)(object)new AcceptableValueRange(3, 25), Array.Empty())); } public override void OnStageStart(Stage stage) { //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) if (!NetworkServer.active) { return; } double num = rng.NextDouble() * 100.0; if (num > (double)SwarmChance.Value) { DebugLog($"Enemy Swarm rolled {num:0.0}% vs {SwarmChance.Value:0.0}% chance - no swarm this stage"); return; } List list = new List(); foreach (CharacterMaster allAiMaster in MasterCatalog.allAiMasters) { if (Object.op_Implicit((Object)(object)allAiMaster) && Object.op_Implicit((Object)(object)allAiMaster.bodyPrefab)) { CharacterBody component = allAiMaster.bodyPrefab.GetComponent(); if (Object.op_Implicit((Object)(object)component) && !component.isChampion) { list.Add(allAiMaster.masterIndex); } } } if (list.Count == 0) { DebugLog($"Enemy Swarm rolled {num:0.0}% vs {SwarmChance.Value:0.0}% chance - hit, but no basic AI master candidates were found - skipped"); return; } MasterIndex val = list[rng.Next(list.Count)]; GameObject masterPrefab = MasterCatalog.GetMasterPrefab(val); if (!Object.op_Implicit((Object)(object)masterPrefab)) { return; } ReadOnlyCollection instances = PlayerCharacterMasterController.instances; if (instances == null || instances.Count == 0) { return; } CharacterBody val2 = (Object.op_Implicit((Object)(object)instances[0].master) ? instances[0].master.GetBody() : null); if (!Object.op_Implicit((Object)(object)val2)) { return; } DebugLog($"Enemy Swarm rolled {num:0.0}% vs {SwarmChance.Value:0.0}% chance - hit, spawning {SwarmSize.Value}x master index {val} around {val2.corePosition}"); string text = null; int num2 = 0; Vector3 val3 = default(Vector3); for (int i = 0; i < SwarmSize.Value; i++) { ((Vector3)(ref val3))..ctor(Random.Range(-35f, 35f), 6f, Random.Range(-35f, 35f)); CharacterMaster val4 = new MasterSummon { masterPrefab = masterPrefab, position = val2.corePosition + val3, rotation = Quaternion.identity, teamIndexOverride = (TeamIndex)2, ignoreTeamMemberLimit = true }.Perform(); if (!((Object)(object)val4 != (Object)null)) { continue; } num2++; ((Component)val4).gameObject.AddComponent(); if (text == null) { CharacterBody body = val4.GetBody(); if (Object.op_Implicit((Object)(object)body)) { text = Util.GetBestBodyName(((Component)body).gameObject); } } } DebugLog(string.Format("Enemy Swarm finished spawning - {0}/{1} {2} actually summoned", num2, SwarmSize.Value, text ?? "enemies")); if (num2 > 0 && NotificationsModule.Gate) { Chat.AddMessage(string.Format("ENEMY SWARM: {0}x {1} have flooded the stage!", num2, text ?? "enemies")); } } public override float RewardMultiplier(DamageReport report) { if (!Object.op_Implicit((Object)(object)report?.victimBody) || !Object.op_Implicit((Object)(object)((Component)report.victimBody).GetComponent())) { return 1f; } return 1.15f; } } internal class ItemDietModule : ModuleBase { public enum Diet { None, NoHealing, GlassBuild, SpeedDemon, CommonOnly, OneRarityPerStage } public ConfigEntry SelectedDiet; public ConfigEntry RewardCompensation; private static readonly Random rng = new Random(); private bool poolsBuilt; private readonly List tier1 = new List(); private readonly List tier2 = new List(); private readonly List tier3 = new List(); public override string Name => "Item Diet"; public override string StandaloneGuid => null; public override int Stars => 2; public override bool UsesRunStart => true; public override bool UsesReward => true; public override void Bind(ConfigFile cfg) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown Enabled = cfg.Bind("ItemDiet", "Enabled", false, "Restrict which items can drop to shape the run. Off by default - this is a deliberate challenge mode."); SelectedDiet = cfg.Bind("ItemDiet", "SelectedDiet", Diet.None, "NoHealing: healing items are swapped out. GlassBuild: defensive items swapped for damage. SpeedDemon: favors mobility. CommonOnly: only white items. OneRarityPerStage: each stage locks to one rarity."); RewardCompensation = cfg.Bind("ItemDiet", "RewardCompensation", true, "Grant bonus gold/XP to compensate for the restriction."); PickupDropletController.OnCollisionEnter += new hook_OnCollisionEnter(DropletCollision); } public override void Unhook() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown PickupDropletController.OnCollisionEnter -= new hook_OnCollisionEnter(DropletCollision); } private static bool IsHealing(ItemDef def) { if (Object.op_Implicit((Object)(object)def)) { return HasTag(def, (ItemTag)2); } return false; } private static bool IsDamage(ItemDef def) { if (Object.op_Implicit((Object)(object)def)) { return HasTag(def, (ItemTag)1); } return false; } private static bool IsUtility(ItemDef def) { if (Object.op_Implicit((Object)(object)def)) { return HasTag(def, (ItemTag)3); } return false; } private static bool HasTag(ItemDef def, ItemTag tag) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between I4 and Unknown if (def.tags == null) { return false; } ItemTag[] tags = def.tags; for (int i = 0; i < tags.Length; i++) { if ((int)tags[i] == (int)tag) { return true; } } return false; } private void BuildPools() { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Invalid comparison between Unknown and I4 //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected I4, but got Unknown //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) poolsBuilt = true; tier1.Clear(); tier2.Clear(); tier3.Clear(); foreach (PickupDef allPickup in PickupCatalog.allPickups) { if (allPickup == null || (int)allPickup.itemIndex == -1) { continue; } ItemDef itemDef = ItemCatalog.GetItemDef(allPickup.itemIndex); if (!((Object)(object)itemDef == (Object)null) && !itemDef.hidden && itemDef.DoesNotContainTag((ItemTag)9)) { ItemTier tier = itemDef.tier; switch ((int)tier) { case 0: tier1.Add(allPickup.pickupIndex); break; case 1: tier2.Add(allPickup.pickupIndex); break; case 2: tier3.Add(allPickup.pickupIndex); break; } } } } private List PoolFor(ItemTier tier) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)tier != 2) { if ((int)tier != 1) { return tier1; } return tier2; } return tier3; } private bool NeedsSwap(ItemDef item, out PickupIndex replacement) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Invalid comparison between Unknown and I4 //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) replacement = default(PickupIndex); if ((Object)(object)item == (Object)null) { return false; } Diet value = SelectedDiet.Value; int num = value switch { Diet.NoHealing => IsHealing(item) ? 1 : 0, Diet.GlassBuild => (!IsDamage(item) && (HasTag(item, (ItemTag)2) || ((int)item.tier == 0 && IsUtility(item)))) ? 1 : 0, Diet.SpeedDemon => IsHealing(item) ? 1 : 0, Diet.CommonOnly => ((int)item.tier > 0) ? 1 : 0, Diet.OneRarityPerStage => (item.tier != StageRarity()) ? 1 : 0, _ => 0, }; string text = Language.GetString(item.nameToken); if (num == 0) { DebugLog($"Item Diet allowed {text} (tier {item.tier}) - fits {value} diet"); return false; } ItemTier val = (ItemTier)(value switch { Diet.OneRarityPerStage => (int)StageRarity(), Diet.CommonOnly => 0, _ => (int)item.tier, }); List list = FilteredPool(val, value); if (list.Count == 0) { DebugLog($"Item Diet blocked {text} (tier {item.tier}) - not in {value} allowlist, but no replacement exists in tier {val} - letting it through unchanged"); return false; } replacement = list[rng.Next(list.Count)]; PickupDef pickupDef = PickupCatalog.GetPickupDef(replacement); ItemDef val2 = ((pickupDef != null) ? ItemCatalog.GetItemDef(pickupDef.itemIndex) : null); string text2 = (((Object)(object)val2 != (Object)null) ? Language.GetString(val2.nameToken) : "unknown"); DebugLog($"Item Diet blocked {text} (tier {item.tier}) - not in {value} allowlist, swapped to {text2} (tier {val})"); return true; } private List FilteredPool(ItemTier tier, Diet diet) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) List list = PoolFor(tier); if (diet != Diet.NoHealing && diet != Diet.SpeedDemon && diet != Diet.GlassBuild) { return list; } List list2 = new List(); foreach (PickupIndex item in list) { PickupDef pickupDef = PickupCatalog.GetPickupDef(item); ItemDef val = ((pickupDef != null) ? ItemCatalog.GetItemDef(pickupDef.itemIndex) : null); if (!((Object)(object)val == (Object)null) && ((diet != Diet.NoHealing && diet != Diet.SpeedDemon) || !IsHealing(val)) && (diet != Diet.GlassBuild || IsDamage(val))) { list2.Add(item); } } if (list2.Count <= 0) { return list; } return list2; } private static ItemTier StageRarity() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) return (ItemTier)(((Object.op_Implicit((Object)(object)Run.instance) ? Run.instance.stageClearCount : 0) % 3) switch { 0 => 0, 1 => 1, _ => 0, }); } private void DropletCollision(orig_OnCollisionEnter orig, PickupDropletController self, Collision collision) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Invalid comparison between Unknown and I4 //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) if (base.Active && NetworkServer.active && SelectedDiet.Value != Diet.None) { try { if (!poolsBuilt) { BuildPools(); } PickupDef pickupDef = PickupCatalog.GetPickupDef(self.pickupState.pickupIndex); if (pickupDef != null && (int)pickupDef.itemIndex != -1) { ItemDef itemDef = ItemCatalog.GetItemDef(pickupDef.itemIndex); if (NeedsSwap(itemDef, out var replacement)) { self.pickupState.pickupIndex = replacement; } } } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"{Name}: droplet swap failed: {arg}"); } } orig.Invoke(self, collision); } public override void OnRunStart() { if (base.Active && SelectedDiet.Value != Diet.None) { DebugLog($"Item Diet run start - diet={SelectedDiet.Value}, rewardCompensation={RewardCompensation.Value}"); if (NotificationsModule.Gate) { Chat.AddMessage($"ITEM DIET: {SelectedDiet.Value} - the drops are curated. Adapt your build."); } } } public override float RewardMultiplier(DamageReport report) { int num; float num2; if (RewardCompensation.Value) { num = ((SelectedDiet.Value != Diet.None) ? 1 : 0); if (num != 0) { num2 = 1.2f; goto IL_002d; } } else { num = 0; } num2 = 1f; goto IL_002d; IL_002d: float num3 = num2; if (num != 0) { DebugLog($"Item Diet reward compensation applied - multiplier x{num3}"); } return num3; } } internal class LootMutationsModule : ModuleBase { public ConfigEntry EnableVolatile; public ConfigEntry EnableEmpowered; public ConfigEntry VolatileChancePerStage; public ConfigEntry EmpoweredThreshold; private static readonly Random rng = new Random(); private readonly Dictionary> empowered = new Dictionary>(); public override string Name => "Loot Mutations"; public override string StandaloneGuid => null; public override int Stars => 2; public override bool UsesStageStart => true; public override bool UsesRunStart => true; public override void Bind(ConfigFile cfg) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected O, but got Unknown Enabled = cfg.Bind("LootMutations", "Enabled", false, "Items become unstable during a run. Off by default - a build-variety challenge layer."); EnableVolatile = cfg.Bind("LootMutations", "VolatileItems", true, "Each stage, one random held item may transform into a different item of the same tier."); VolatileChancePerStage = cfg.Bind("LootMutations", "VolatileChancePerStage", 100f, new ConfigDescription("Chance (percent) each stage that one of your held items transforms into a different item of the same tier.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); EnableEmpowered = cfg.Bind("LootMutations", "EmpoweredDuplicates", true, "When you hold a threshold number of copies of a common (white) item, you are granted one extra free copy of that same item - a reward for committing to a stack. Fires once per item type."); EmpoweredThreshold = cfg.Bind("LootMutations", "EmpoweredThreshold", 5, new ConfigDescription("How many copies of a single white item you must hold before it grants a bonus empowered stack.", (AcceptableValueBase)(object)new AcceptableValueRange(3, 15), Array.Empty())); } public override void OnStageStart(Stage stage) { if (!NetworkServer.active) { return; } if (EnableVolatile.Value) { double num = rng.NextDouble() * 100.0; bool flag = num <= (double)VolatileChancePerStage.Value; DebugLog(string.Format("Volatile Items roll: {0:F1} vs chance {1}% - {2}", num, VolatileChancePerStage.Value, flag ? "triggered" : "did not trigger")); if (flag) { DoVolatileSwaps(); } } if (EnableEmpowered.Value) { DoEmpoweredBonuses(); } } private void DoVolatileSwaps() { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Invalid comparison between Unknown and I4 //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Invalid comparison between Unknown and I4 //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Invalid comparison between Unknown and I4 //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) ReadOnlyCollection instances = PlayerCharacterMasterController.instances; if (instances == null) { return; } foreach (PlayerCharacterMasterController item in instances) { Inventory val = ((Object.op_Implicit((Object)(object)item) && Object.op_Implicit((Object)(object)item.master)) ? item.master.inventory : null); if (!Object.op_Implicit((Object)(object)val)) { continue; } List list = new List(); foreach (ItemIndex item2 in val.itemAcquisitionOrder) { ItemDef itemDef = ItemCatalog.GetItemDef(item2); if ((Object)(object)itemDef != (Object)null && !itemDef.hidden && ((int)itemDef.tier == 0 || (int)itemDef.tier == 1 || (int)itemDef.tier == 2) && itemDef.DoesNotContainTag((ItemTag)9)) { list.Add(item2); } } if (list.Count == 0) { DebugLog("Volatile Items: skipped " + ((Object)item.master).name + " - no eligible held items"); continue; } ItemIndex val2 = list[rng.Next(list.Count)]; ItemDef itemDef2 = ItemCatalog.GetItemDef(val2); ItemIndex val3 = RandomItemOfTier(itemDef2.tier, val2); if ((int)val3 == -1) { DebugLog($"Volatile Items: skipped swap for {Language.GetString(itemDef2.nameToken)} (tier {itemDef2.tier}) - no replacement item available in that tier"); continue; } int itemCountPermanent = val.GetItemCountPermanent(val2); if (itemCountPermanent > 0) { val.RemoveItemPermanent(val2, itemCountPermanent); val.GiveItemPermanent(val3, itemCountPermanent); ItemDef itemDef3 = ItemCatalog.GetItemDef(val3); DebugLog($"Volatile Items: {itemCountPermanent}x {Language.GetString(itemDef2.nameToken)} (tier {itemDef2.tier}) transformed into {Language.GetString(itemDef3.nameToken)} for {((Object)item.master).name}"); if (Object.op_Implicit((Object)(object)item.master.GetBody()) && NotificationsModule.Gate) { Chat.AddMessage($"VOLATILE: {itemCountPermanent}x {Language.GetString(itemDef2.nameToken)} became {Language.GetString(itemDef3.nameToken)}."); } } } } private ItemIndex RandomItemOfTier(ItemTier tier, ItemIndex exclude) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) List list = (((int)tier == 1) ? new List(ItemCatalog.tier2ItemList) : (((int)tier != 2) ? new List(ItemCatalog.tier1ItemList) : new List(ItemCatalog.tier3ItemList))); List list2 = new List(); foreach (ItemIndex item in list) { if (item != exclude) { ItemDef itemDef = ItemCatalog.GetItemDef(item); if ((Object)(object)itemDef != (Object)null && !itemDef.hidden && itemDef.DoesNotContainTag((ItemTag)9)) { list2.Add(item); } } } if (list2.Count <= 0) { return (ItemIndex)(-1); } return list2[rng.Next(list2.Count)]; } private void DoEmpoweredBonuses() { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) ReadOnlyCollection instances = PlayerCharacterMasterController.instances; if (instances == null) { return; } foreach (PlayerCharacterMasterController item in instances) { Inventory val = ((Object.op_Implicit((Object)(object)item) && Object.op_Implicit((Object)(object)item.master)) ? item.master.inventory : null); if (!Object.op_Implicit((Object)(object)val)) { continue; } if (!empowered.TryGetValue(val, out var value)) { value = new HashSet(); empowered[val] = value; } foreach (ItemIndex tier1Item in ItemCatalog.tier1ItemList) { if (!value.Contains(tier1Item) && val.GetItemCountPermanent(tier1Item) >= EmpoweredThreshold.Value) { val.GiveItemPermanent(tier1Item, 1); value.Add(tier1Item); ItemDef itemDef = ItemCatalog.GetItemDef(tier1Item); int itemCountPermanent = val.GetItemCountPermanent(tier1Item); DebugLog($"Empowered Duplicates: {((Object)item.master).name} hit threshold {EmpoweredThreshold.Value} on {Language.GetString(itemDef.nameToken)}, granted bonus stack (now {itemCountPermanent})"); if (NotificationsModule.Gate) { Chat.AddMessage($"EMPOWERED: you held {EmpoweredThreshold.Value}+ {Language.GetString(itemDef.nameToken)}, so you were granted a free bonus stack (now {itemCountPermanent})."); } } } } } public override void OnRunStart() { empowered.Clear(); if (base.Active) { DebugLog($"Loot Mutations run start - volatile={EnableVolatile.Value} (chance {VolatileChancePerStage.Value}%), empowered={EnableEmpowered.Value} (threshold {EmpoweredThreshold.Value})"); if (NotificationsModule.Gate) { Chat.AddMessage("LOOT MUTATIONS active - your items will not hold still."); } } } } internal class PresetsModule : ModuleBase { public enum Preset { None, CleanSlate, VanillaPlus, DavidVsGoliath, KaijuRampage, GremlinSwarm, CursedWorlds, FogOfWar, Blackout, MutationWar, GlassCannon, SizeLottery, TotalChaos } public ConfigEntry Selected; private bool applying; private static readonly Dictionary PresetModules = new Dictionary { { Preset.None, new string[0] }, { Preset.CleanSlate, new string[0] }, { Preset.VanillaPlus, new string[1] { "EnemyMutations" } }, { Preset.DavidVsGoliath, new string[4] { "ShrunkenSurvivor", "EnlargedEnemies", "EnemyMutations", "Combos" } }, { Preset.KaijuRampage, new string[4] { "EnlargedSurvivor", "ShrunkenEnemies", "EnemySwarm", "Combos" } }, { Preset.GremlinSwarm, new string[3] { "ShrunkenEnemies", "EnemySwarm", "EnemyMutations" } }, { Preset.CursedWorlds, new string[4] { "CurseDeck", "Hazards", "StagePersonalities", "TeleporterRules" } }, { Preset.FogOfWar, new string[3] { "Hazards", "EnemySwarm", "TeleporterRules" } }, { Preset.Blackout, new string[3] { "Hazards", "BossMutations", "StagePersonalities" } }, { Preset.MutationWar, new string[4] { "EnemyMutations", "BossMutations", "WorldEvents", "EnemySwarm" } }, { Preset.GlassCannon, new string[3] { "ItemDiet", "LootMutations", "Contracts" } }, { Preset.SizeLottery, new string[2] { "SizeRoulette", "EnemySwarm" } }, { Preset.TotalChaos, new string[12] { "ShrunkenSurvivor", "EnemyMutations", "BossMutations", "CurseDeck", "Hazards", "WorldEvents", "StagePersonalities", "TeleporterRules", "EnemySwarm", "ItemDiet", "LootMutations", "Contracts" } } }; public ConfigEntry CyclePresets; private static readonly Random cycleRng = new Random(); public override string Name => "Challenge Presets"; public override string StandaloneGuid => "dileppy.challengepresets"; public override int Stars => 0; public override bool UsesRunStart => true; public override void Bind(ConfigFile cfg) { Enabled = cfg.Bind("Presets", "Enabled", true, "Allow presets to reconfigure the whole mod. Turn this off if you want to set every feature by hand and never have a preset touch your toggles."); Selected = cfg.Bind("Presets", "SelectedPreset", Preset.None, BuildDescription()); CyclePresets = cfg.Bind("Presets", "CyclePresetsEachRun", false, "Each run, ignore SelectedPreset above and randomly pick a different preset to apply instead - a fresh curated challenge every run without you choosing one. The dropdown still updates to show whichever preset got picked. Setting SelectedPreset to None or Clean Slate switches cycling off, so you always have a way to get a quiet run without hunting for this toggle."); Selected.SettingChanged += delegate { if (!applying) { DebugLog($"Preset changed via dropdown to {Selected.Value}"); Apply(Selected.Value, announce: true); } }; } private static string BuildDescription() { return "THE MASTER CONTROL. Editing this file by hand? Just set SelectedPreset to one of the names below and launch - it turns the listed features on and everything else off for you, so you do not have to toggle each feature individually. Interface options (HUD, notifications, titles) are left alone.\nNone: every gameplay feature OFF, the same as Clean Slate - this is the safe default. To configure features one by one and have nothing ever override you, set 'Presets: Enabled' to false instead.\nClean Slate (Risk 0): every gameplay feature OFF - a blank canvas.\nVanilla Plus (Risk 4): just Enemy Mutations - a light dusting of chaos.\nDavid vs Goliath (Risk ~10): tiny you, giant enemies, mutations, combos.\nKaiju Rampage (Risk ~7): giant you, a shrunken swarm, enemy swarms, combos.\nGremlin Swarm (Risk ~8): tiny fast enemies everywhere, plus swarms and mutations.\nCursed Worlds (Risk ~11): stage curses, meteor storms, stage personalities, teleporter rules.\nFog of War (Risk ~8): heavy fog banks roll in constantly, enemy swarms you will not see coming, and teleporter zones that turn hostile.\nBlackout (Risk ~8): the lights are always going out, mutated bosses come back wrong, and every stage feels off.\nMutation War (Risk ~13): enemy and boss mutations, world events, enemy swarms.\nGlass Cannon (Risk ~6): curated item diet, unstable loot, and contracts.\nSize Lottery (Risk ~6): randomized sizes for everyone, plus swarms.\nTotal Chaos (Risk ~28+): nearly everything on at once - Apocalypse tier."; } private T Find() where T : ModuleBase { foreach (ModuleBase module in PetrichorProtocolPlugin.Modules) { if (module is T result) { return result; } } return null; } private ModuleBase FindBySection(string section) { foreach (ModuleBase module in PetrichorProtocolPlugin.Modules) { if (module.Enabled != null && ((ConfigEntryBase)module.Enabled).Definition.Section == section) { return module; } } return null; } public override void OnRunStart() { if (!base.Active) { return; } bool flag = Selected.Value == Preset.None || Selected.Value == Preset.CleanSlate; if (CyclePresets.Value && flag) { DebugLog($"CyclePresetsEachRun is on but SelectedPreset is {Selected.Value} - honouring the opt-out and skipping the cycle."); } if (CyclePresets.Value && !flag) { List list = new List(); foreach (Preset value in Enum.GetValues(typeof(Preset))) { if (value != Preset.None && value != Preset.CleanSlate) { list.Add(value); } } if (list.Count > 0) { Preset preset2 = list[cycleRng.Next(list.Count)]; DebugLog($"CyclePresetsEachRun: picked {preset2} from a pool of {list.Count} presets"); applying = true; try { Selected.Value = preset2; } finally { applying = false; } Apply(preset2, announce: true); return; } } DebugLog($"Run start safety net: re-applying SelectedPreset {Selected.Value}"); Apply(Selected.Value, announce: true); } internal void ApplyNow(Preset p) { if (PresetModules.ContainsKey(p)) { DebugLog($"Preset applied via Risk of Options button: {p}"); applying = true; try { Selected.Value = p; } finally { applying = false; } Apply(p, announce: true); } } private void Apply(Preset p, bool announce) { if (!base.Active || !PresetModules.TryGetValue(p, out var value)) { return; } applying = true; try { HashSet hashSet = new HashSet(value); DebugLog(string.Format("Applying preset {0} - {1} modules turned on, Risk Score will be computed after: [{2}]", p, hashSet.Count, string.Join(", ", hashSet))); List list = new List(); foreach (ModuleBase module in PetrichorProtocolPlugin.Modules) { if (module.Enabled == null || module.Stars < 0) { continue; } string section = ((ConfigEntryBase)module.Enabled).Definition.Section; switch (section) { case "HUD": case "Notifications": case "RunTitles": case "Presets": case "Randomizer": case "Protocol": continue; } bool flag = hashSet.Contains(section); if (!flag) { list.Add(section); } module.Enabled.Value = flag; } DebugLog(string.Format("Preset {0}: modules turned OFF = [{1}]", p, string.Join(", ", list))); ApplySubOptions(p); int num = ComputeRiskScore(); DebugLog($"Preset {p} applied - computed Risk Score = {num} ({PetrichorProtocolPlugin.TierName(num)})"); if (announce && p != Preset.None) { int num2 = num; Chat.AddMessage("PRESET: " + Prettify(p) + " - " + Flavor(p)); Chat.AddMessage($"Risk Score {num2} - {PetrichorProtocolPlugin.TierName(num2)}. Everything else is off."); } } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"Presets: apply failed: {arg}"); } finally { applying = false; } } private void ApplySubOptions(Preset p) { switch (p) { case Preset.DavidVsGoliath: { ShrunkenSurvivorModule shrunkenSurvivorModule = Find(); if (shrunkenSurvivorModule != null) { shrunkenSurvivorModule.SizeTier.Value = ShrunkenSurvivorModule.Tier.Shrunken; DebugLog($"DavidVsGoliath sub-option: ShrunkenSurvivor.SizeTier = {shrunkenSurvivorModule.SizeTier.Value}"); } else { DebugLog("DavidVsGoliath sub-option: ShrunkenSurvivorModule not found, SizeTier not set"); } EnlargedEnemiesModule enlargedEnemiesModule = Find(); if (enlargedEnemiesModule != null) { enlargedEnemiesModule.SizeTier.Value = EnlargedEnemiesModule.Tier.Enlarged; DebugLog($"DavidVsGoliath sub-option: EnlargedEnemies.SizeTier = {enlargedEnemiesModule.SizeTier.Value}"); } else { DebugLog("DavidVsGoliath sub-option: EnlargedEnemiesModule not found, SizeTier not set"); } break; } case Preset.KaijuRampage: { EnlargedSurvivorModule enlargedSurvivorModule = Find(); if (enlargedSurvivorModule != null) { enlargedSurvivorModule.SizeTier.Value = EnlargedSurvivorModule.Tier.Giant; DebugLog($"KaijuRampage sub-option: EnlargedSurvivor.SizeTier = {enlargedSurvivorModule.SizeTier.Value}"); } else { DebugLog("KaijuRampage sub-option: EnlargedSurvivorModule not found, SizeTier not set"); } ShrunkenEnemiesModule shrunkenEnemiesModule = Find(); if (shrunkenEnemiesModule != null) { shrunkenEnemiesModule.SizeTier.Value = ShrunkenEnemiesModule.Tier.Shrunken; DebugLog($"KaijuRampage sub-option: ShrunkenEnemies.SizeTier = {shrunkenEnemiesModule.SizeTier.Value}"); } else { DebugLog("KaijuRampage sub-option: ShrunkenEnemiesModule not found, SizeTier not set"); } break; } case Preset.GremlinSwarm: { ShrunkenEnemiesModule shrunkenEnemiesModule2 = Find(); if (shrunkenEnemiesModule2 != null) { shrunkenEnemiesModule2.SizeTier.Value = ShrunkenEnemiesModule.Tier.Miniature; DebugLog($"GremlinSwarm sub-option: ShrunkenEnemies.SizeTier = {shrunkenEnemiesModule2.SizeTier.Value}"); } else { DebugLog("GremlinSwarm sub-option: ShrunkenEnemiesModule not found, SizeTier not set"); } break; } case Preset.GlassCannon: { ItemDietModule itemDietModule = Find(); if (itemDietModule != null) { itemDietModule.SelectedDiet.Value = ItemDietModule.Diet.GlassBuild; DebugLog($"GlassCannon sub-option: ItemDiet.SelectedDiet = {itemDietModule.SelectedDiet.Value}"); } else { DebugLog("GlassCannon sub-option: ItemDietModule not found, SelectedDiet not set"); } break; } case Preset.FogOfWar: { HazardsModule hazardsModule2 = Find(); if (hazardsModule2 != null) { hazardsModule2.FogBankChance.Value = 80f; hazardsModule2.HazardChance.Value = 15f; DebugLog($"FogOfWar sub-option: Hazards.FogBankChance = {hazardsModule2.FogBankChance.Value}, HazardChance = {hazardsModule2.HazardChance.Value}"); } else { DebugLog("FogOfWar sub-option: HazardsModule not found, chances not set"); } break; } case Preset.Blackout: { HazardsModule hazardsModule = Find(); if (hazardsModule != null) { hazardsModule.DarknessChance.Value = 80f; hazardsModule.HazardChance.Value = 15f; hazardsModule.FogBankChance.Value = 15f; DebugLog($"Blackout sub-option: Hazards.DarknessChance = {hazardsModule.DarknessChance.Value}, HazardChance = {hazardsModule.HazardChance.Value}, FogBankChance = {hazardsModule.FogBankChance.Value}"); } else { DebugLog("Blackout sub-option: HazardsModule not found, chances not set"); } break; } case Preset.CursedWorlds: case Preset.MutationWar: break; } } private int ComputeRiskScore() { int num = 0; foreach (ModuleBase module in PetrichorProtocolPlugin.Modules) { if (module.Stars > 0 && module.FeatureActive) { num += module.Stars; } } return num; } internal static string Prettify(Preset p) { StringBuilder stringBuilder = new StringBuilder(); string text = p.ToString(); for (int i = 0; i < text.Length; i++) { if (i > 0 && char.IsUpper(text[i]) && !char.IsUpper(text[i - 1])) { stringBuilder.Append(' '); } stringBuilder.Append(text[i]); } return stringBuilder.ToString(); } internal static string Flavor(Preset p) { return p switch { Preset.CleanSlate => "A blank canvas. Build your own run from here.", Preset.VanillaPlus => "Familiar, with just a little something wrong.", Preset.DavidVsGoliath => "You are small. They are giants. Bring them down.", Preset.KaijuRampage => "You are the monster now. The swarm is beneath you.", Preset.GremlinSwarm => "Tiny, fast, and absolutely everywhere.", Preset.CursedWorlds => "Every stage rewrites its own rules.", Preset.FogOfWar => "You cannot see what is coming. Increasingly, that is the point.", Preset.Blackout => "Something is out there. You will not see it until it is close.", Preset.MutationWar => "Nothing that spawns is what it should be.", Preset.GlassCannon => "Curated loot, no safety net. Hit hard or die.", Preset.SizeLottery => "Nobody keeps their size for long.", Preset.TotalChaos => "Everything, all at once. Good luck.", _ => "", }; } } internal class CombosModule : ModuleBase { private bool juggernaut; private bool bounty; public override string Name => "Combo System"; public override string StandaloneGuid => "dileppy.combosystem"; public override int Stars => 1; public override bool UsesRunStart => true; public override bool UsesRecalcStats => true; public override bool UsesReward => true; public override void Bind(ConfigFile cfg) { Enabled = cfg.Bind("Combos", "Enabled", false, "Named bonuses that fire automatically when matching features are on together. Juggernaut's Hide: Enlarged Survivor + Shrunken Enemies grants your giant +20 armor. Giant-Slayer's Bounty: Shrunken Survivor + Enlarged Enemies makes felled giants drop +25% gold."); } private static bool Feature() where T : ModuleBase { foreach (ModuleBase module in PetrichorProtocolPlugin.Modules) { if (module is T) { return module.FeatureActive; } } return false; } public override void OnRunStart() { juggernaut = Feature() && Feature(); bounty = Feature() && Feature(); DebugLog($"Combo check - Juggernaut's Hide active: {juggernaut} (EnlargedSurvivor && ShrunkenEnemies), Giant-Slayer's Bounty active: {bounty} (ShrunkenSurvivor && EnlargedEnemies)"); if (NotificationsModule.Gate) { if (juggernaut) { Chat.AddMessage("COMBO: Juggernaut's Hide - your giant gains +20 armor."); } if (bounty) { Chat.AddMessage("COMBO: Giant-Slayer's Bounty - felled giants drop +25% gold."); } } } public override void OnRecalcStats(CharacterBody self) { if (juggernaut && ModuleBase.IsPlayer(self)) { self.armor += 20f; DebugLog($"Juggernaut's Hide: +20 armor applied to {((Object)self).name}, armor now {self.armor}"); } } public override float RewardMultiplier(DamageReport report) { int num; if (bounty && (Object)(object)report?.victimBody != (Object)null) { num = (ModuleBase.IsHostileEnemy(report.victimBody) ? 1 : 0); if (num != 0) { DebugLog("Giant-Slayer's Bounty: +25% gold reward triggered by kill of " + ((Object)report.victimBody).name); } } else { num = 0; } if (num == 0) { return 1f; } return 1.25f; } } internal class EnemyMutationsModule : ModuleBase { public enum MutationType { None, Giant, Tiny, Frenzied, Volatile, Treasure } public class Marker : MonoBehaviour { public MutationType type; public float rewardMult = 1f; } private class TreasureGlow : MonoBehaviour { private CharacterBody body; private Light glow; private bool tinted; private float retryUntil; private static readonly Color Gold = new Color(1f, 0.78f, 0.2f); private static readonly int EmissionColor = Shader.PropertyToID("_EmColor"); private static readonly int EmissionPower = Shader.PropertyToID("_EmPower"); public void Init(CharacterBody b) { body = b; retryUntil = Time.time + 5f; } private void Update() { //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Expected O, but got Unknown //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) if (tinted) { ((Behaviour)this).enabled = false; return; } if (!Object.op_Implicit((Object)(object)body)) { if (Time.time > retryUntil) { ((Behaviour)this).enabled = false; } return; } ModelLocator modelLocator = body.modelLocator; Transform val = (Object.op_Implicit((Object)(object)modelLocator) ? modelLocator.modelTransform : null); if (!Object.op_Implicit((Object)(object)val)) { if (Time.time > retryUntil) { ((Behaviour)this).enabled = false; } return; } if (!Object.op_Implicit((Object)(object)glow)) { GameObject val2 = new GameObject("TreasureGlow"); val2.transform.SetParent(val, false); val2.transform.localPosition = Vector3.up * 1.5f; glow = val2.AddComponent(); glow.type = (LightType)2; glow.color = Gold; glow.range = 9f; glow.intensity = 4f; glow.renderMode = (LightRenderMode)2; } MaterialPropertyBlock val3 = new MaterialPropertyBlock(); bool flag = false; Renderer[] componentsInChildren = ((Component)val).GetComponentsInChildren(true); foreach (Renderer val4 in componentsInChildren) { if (Object.op_Implicit((Object)(object)val4)) { val4.GetPropertyBlock(val3); val3.SetColor(EmissionColor, Gold); val3.SetFloat(EmissionPower, 6f); val4.SetPropertyBlock(val3); flag = true; } } if (flag) { tinted = true; } } } public ConfigEntry BaseChance; public ConfigEntry PerStage; public ConfigEntry TreasureChance; public ConfigEntry BlastDamage; public ConfigEntry BlastRadius; public ConfigEntry AllowBosses; public ConfigEntry AllowElites; private static readonly Random rng = new Random(); public override string Name => "Enemy Mutations"; public override string StandaloneGuid => "dileppy.enemymutations"; public override int Stars => 4; public override bool UsesBodyStart => true; public override bool UsesRecalcStats => true; public override bool UsesDeath => true; public override bool UsesReward => true; public override void Bind(ConfigFile cfg) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Expected O, but got Unknown //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Expected O, but got Unknown //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Expected O, but got Unknown Enabled = cfg.Bind("EnemyMutations", "Enabled", false, "Enemies can spawn mutated: Giant, Tiny, Frenzied, Volatile, or rare gold Treasure."); BaseChance = cfg.Bind("EnemyMutations", "BaseMutationChance", 12f, new ConfigDescription("Chance (percent) that each enemy spawns mutated on stage 1. Rises each stage by the scaling value below.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); PerStage = cfg.Bind("EnemyMutations", "ChanceScalingPerStage", 2f, new ConfigDescription("Percent added to the enemy mutation chance for every stage cleared, so later stages get steadily more chaotic.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 20f), Array.Empty())); AllowBosses = cfg.Bind("EnemyMutations", "AllowBossMutations", false, "Bosses can roll trash-mob mutations (BossMutations has its own system)."); AllowElites = cfg.Bind("EnemyMutations", "AllowEliteMutations", true, "Let elite enemies receive a mutation in addition to their existing elite affix, stacking both."); TreasureChance = cfg.Bind("EnemyMutations", "TreasureChance", 10f, new ConfigDescription("When an enemy is chosen to mutate, the chance (percent) it becomes a rare gold Treasure enemy worth big rewards instead of a normal mutation.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); BlastDamage = cfg.Bind("EnemyMutations", "VolatileBlastDamage", 15f, new ConfigDescription("How hard a Volatile enemy explodes when it dies, as a multiple of its own base damage. Watch your positioning.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 100f), Array.Empty())); BlastRadius = cfg.Bind("EnemyMutations", "VolatileBlastRadius", 12f, new ConfigDescription("Size of the explosion a Volatile enemy leaves when it dies, in meters.", (AcceptableValueBase)(object)new AcceptableValueRange(2f, 40f), Array.Empty())); } public override void OnBodyStart(CharacterBody self) { //IL_03a2: Unknown result type (might be due to invalid IL or missing references) //IL_03a8: Unknown result type (might be due to invalid IL or missing references) if (!NetworkServer.active || !ModuleBase.IsHostileEnemy(self) || Object.op_Implicit((Object)(object)((Component)self).GetComponent()) || (self.isBoss && !AllowBosses.Value) || (self.isElite && !AllowElites.Value)) { return; } int num = (Object.op_Implicit((Object)(object)Run.instance) ? Run.instance.stageClearCount : 0); float num2 = BaseChance.Value + PerStage.Value * (float)num; foreach (ModuleBase module in PetrichorProtocolPlugin.Modules) { if (module is WorldEventsModule { Active: not false, MutationWaveActive: not false }) { DebugLog($"{self.GetDisplayName()} mutation chance tripled by active Mutation Wave: {num2:0.0}% -> {num2 * 3f:0.0}%"); num2 *= 3f; break; } } if (rng.NextDouble() * 100.0 > (double)num2) { DebugLog($"{self.GetDisplayName()} rolled for a mutation and got none (chance was {num2:0.0}%, stage {num})"); return; } bool flag = rng.NextDouble() * 100.0 < (double)TreasureChance.Value; MutationType mutationType = ((!flag) ? ((MutationType)(1 + rng.Next(4))) : MutationType.Treasure); DebugLog(string.Format("{0} rolled mutation: {1} (mutation chance was {2:0.0}%, treasure sub-roll {3} at {4:0.0}%)", self.GetDisplayName(), mutationType, num2, flag ? "hit" : "missed", TreasureChance.Value)); Marker marker = ((Component)self).gameObject.AddComponent(); marker.type = mutationType; switch (mutationType) { case MutationType.Giant: ModuleBase.ApplyModelScale(self, 1.75f); marker.rewardMult = 2f; DebugLog($"{self.GetDisplayName()} [Giant] applied: model scale x1.75, reward x{marker.rewardMult:0.00}"); break; case MutationType.Tiny: ModuleBase.ApplyModelScale(self, 0.5f); marker.rewardMult = 1.3f; DebugLog($"{self.GetDisplayName()} [Tiny] applied: model scale x0.50, reward x{marker.rewardMult:0.00}"); break; case MutationType.Frenzied: ModuleBase.ApplyModelScale(self, 0.9f); marker.rewardMult = 1.4f; DebugLog($"{self.GetDisplayName()} [Frenzied] applied: model scale x0.90, reward x{marker.rewardMult:0.00}"); break; case MutationType.Volatile: ModuleBase.ApplyModelScale(self, 1.1f); marker.rewardMult = 1.5f; DebugLog($"{self.GetDisplayName()} [Volatile] applied: model scale x1.10, reward x{marker.rewardMult:0.00}, death blast damage x{BlastDamage.Value:0.0}, radius {BlastRadius.Value:0.0}m"); break; case MutationType.Treasure: ModuleBase.ApplyModelScale(self, 1.2f); marker.rewardMult = 4f; ApplyTreasureGlow(self); DebugLog($"{self.GetDisplayName()} [Treasure] applied: model scale x1.20, reward x{marker.rewardMult:0.00}, gold glow VFX attached"); if (NotificationsModule.Gate) { Chat.AddMessage("A Treasure " + self.GetDisplayName() + " has appeared!"); } break; } self.RecalculateStats(); if (Object.op_Implicit((Object)(object)self.healthComponent)) { self.healthComponent.HealFraction(1f, default(ProcChainMask)); } } public override void OnRecalcStats(CharacterBody self) { Marker marker = (Object.op_Implicit((Object)(object)self) ? ((Component)self).GetComponent() : null); if (!((Object)(object)marker == (Object)null)) { switch (marker.type) { case MutationType.Giant: self.maxHealth *= 2f; self.damage *= 1.3f; self.moveSpeed *= 0.8f; DebugLog($"{self.GetDisplayName()} [Giant] stats recalculated: maxHealth x2.0 -> {self.maxHealth:0}, damage x1.3 -> {self.damage:0.0}, moveSpeed x0.8 -> {self.moveSpeed:0.0}"); break; case MutationType.Tiny: self.maxHealth *= 0.6f; self.moveSpeed *= 1.5f; self.attackSpeed *= 1.25f; DebugLog($"{self.GetDisplayName()} [Tiny] stats recalculated: maxHealth x0.6 -> {self.maxHealth:0}, moveSpeed x1.5 -> {self.moveSpeed:0.0}, attackSpeed x1.25 -> {self.attackSpeed:0.00}"); break; case MutationType.Frenzied: self.maxHealth *= 0.75f; self.moveSpeed *= 1.5f; self.attackSpeed *= 1.5f; self.damage *= 1.1f; DebugLog($"{self.GetDisplayName()} [Frenzied] stats recalculated: maxHealth x0.75 -> {self.maxHealth:0}, moveSpeed x1.5 -> {self.moveSpeed:0.0}, attackSpeed x1.5 -> {self.attackSpeed:0.00}, damage x1.1 -> {self.damage:0.0}"); break; case MutationType.Volatile: self.maxHealth *= 0.9f; DebugLog($"{self.GetDisplayName()} [Volatile] stats recalculated: maxHealth x0.9 -> {self.maxHealth:0}"); break; case MutationType.Treasure: self.maxHealth *= 2f; self.damage *= 1.25f; DebugLog($"{self.GetDisplayName()} [Treasure] stats recalculated: maxHealth x2.0 -> {self.maxHealth:0}, damage x1.25 -> {self.damage:0.0}"); break; } } } public override void OnDeath(DamageReport report) { if (NetworkServer.active && !((Object)(object)report?.victimBody == (Object)null)) { Marker component = ((Component)report.victimBody).GetComponent(); if (!((Object)(object)component == (Object)null) && component.type == MutationType.Volatile) { DebugLog($"{report.victimBody.GetDisplayName()} [Volatile] died - triggering death blast: damage x{BlastDamage.Value:0.0} of base ({report.victimBody.baseDamage * BlastDamage.Value:0} flat), radius {BlastRadius.Value:0.0}m"); Explosions.Blast(report.victimBody, BlastDamage.Value, BlastRadius.Value); } } } public override float RewardMultiplier(DamageReport report) { Marker marker = (Object.op_Implicit((Object)(object)report?.victimBody) ? ((Component)report.victimBody).GetComponent() : null); if ((Object)(object)marker != (Object)null) { DebugLog($"{report.victimBody.GetDisplayName()} [{marker.type}] reward multiplier applied: x{marker.rewardMult:0.00}"); } if (!((Object)(object)marker != (Object)null)) { return 1f; } return marker.rewardMult; } private static void ApplyTreasureGlow(CharacterBody body) { if (Object.op_Implicit((Object)(object)body)) { ((Component)body).gameObject.AddComponent().Init(body); } } } internal static class Explosions { public static void Blast(CharacterBody body, float damageMult, float radius) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown try { new BlastAttack { attacker = ((Component)body).gameObject, inflictor = ((Component)body).gameObject, teamIndex = (TeamIndex)((!Object.op_Implicit((Object)(object)body.teamComponent)) ? 2 : ((int)body.teamComponent.teamIndex)), baseDamage = body.baseDamage * damageMult, baseForce = 1000f, position = body.corePosition, radius = radius, falloffModel = (FalloffModel)1, attackerFiltering = (AttackerFiltering)2 }.Fire(); GameObject val = LegacyResourcesAPI.Load("Prefabs/Effects/OmniEffect/OmniExplosionVFX"); if (Object.op_Implicit((Object)(object)val)) { EffectManager.SpawnEffect(val, new EffectData { origin = body.corePosition, scale = radius }, true); } } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"Protocol blast failed: {arg}"); } } } internal class BossMutationsModule : ModuleBase { public enum BossType { None, Colossal, Frenzied, Shielded, Volatile, Splitting } public class Marker : MonoBehaviour { public BossType type; public float rewardMult = 1f; public bool b75; public bool b50; public bool b25; public bool s66; public bool s33; public float nextShield; } public class SplitChild : MonoBehaviour { } public class Watcher : MonoBehaviour { private CharacterBody body; private Marker marker; private BossMutationsModule mod; public void Init(CharacterBody b, Marker m, BossMutationsModule mm) { body = b; marker = m; mod = mm; } private void FixedUpdate() { if (!NetworkServer.active || !Object.op_Implicit((Object)(object)body) || !Object.op_Implicit((Object)(object)body.healthComponent) || !body.healthComponent.alive || (Object)(object)marker == (Object)null || mod == null) { return; } float combinedHealthFraction = body.healthComponent.combinedHealthFraction; if (marker.type == BossType.Volatile) { if (!marker.b75 && combinedHealthFraction <= 0.75f) { marker.b75 = true; mod.DebugLog($"{body.GetDisplayName()} [Volatile] hit 75% HP threshold - burst: damage x{mod.BurstDamage.Value:0.0}, radius {mod.BurstRadius.Value:0.0}m"); Explosions.Blast(body, mod.BurstDamage.Value, mod.BurstRadius.Value); } if (!marker.b50 && combinedHealthFraction <= 0.5f) { marker.b50 = true; mod.DebugLog($"{body.GetDisplayName()} [Volatile] hit 50% HP threshold - burst: damage x{mod.BurstDamage.Value:0.0}, radius {mod.BurstRadius.Value:0.0}m"); Explosions.Blast(body, mod.BurstDamage.Value, mod.BurstRadius.Value); } if (!marker.b25 && combinedHealthFraction <= 0.25f) { marker.b25 = true; mod.DebugLog($"{body.GetDisplayName()} [Volatile] hit 25% HP threshold - burst: damage x{mod.BurstDamage.Value:0.0}, radius {mod.BurstRadius.Value:0.0}m"); Explosions.Blast(body, mod.BurstDamage.Value, mod.BurstRadius.Value); } } else if (marker.type == BossType.Splitting) { if (!marker.s66 && combinedHealthFraction <= 0.66f) { marker.s66 = true; mod.DebugLog($"{body.GetDisplayName()} [Splitting] hit 66% HP threshold - spawning {mod.SplitCount.Value} split copies"); Split(); } if (!marker.s33 && combinedHealthFraction <= 0.33f) { marker.s33 = true; mod.DebugLog($"{body.GetDisplayName()} [Splitting] hit 33% HP threshold - spawning {mod.SplitCount.Value} split copies"); Split(); } } else if (marker.type == BossType.Shielded && Time.time >= marker.nextShield) { marker.nextShield = Time.time + mod.ShieldInterval.Value; float num = body.maxHealth * mod.ShieldFraction.Value; mod.DebugLog($"{body.GetDisplayName()} [Shielded] barrier pulse: +{num:0} barrier ({mod.ShieldFraction.Value * 100f:0}% of {body.maxHealth:0} max HP), next pulse in {mod.ShieldInterval.Value:0.0}s"); body.healthComponent.AddBarrier(num); } } private void Split() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) try { if (!Object.op_Implicit((Object)(object)body.master)) { return; } GameObject masterPrefab = MasterCatalog.GetMasterPrefab(body.master.masterIndex); if (!Object.op_Implicit((Object)(object)masterPrefab)) { return; } int num = 0; for (int i = 0; i < mod.SplitCount.Value; i++) { CharacterMaster val = new MasterSummon { masterPrefab = masterPrefab, position = body.corePosition + Random.insideUnitSphere * 4f, rotation = Quaternion.identity, teamIndexOverride = (TeamIndex)((!Object.op_Implicit((Object)(object)body.teamComponent)) ? 2 : ((int)body.teamComponent.teamIndex)), ignoreTeamMemberLimit = true }.Perform(); if (Object.op_Implicit((Object)(object)val)) { ((Component)val).gameObject.AddComponent(); num++; } } mod.DebugLog($"{body.GetDisplayName()} [Splitting] spawned {num}/{mod.SplitCount.Value} split copies"); } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"Protocol split failed: {arg}"); } } } public ConfigEntry Chance; public ConfigEntry PerLoop; public ConfigEntry ShieldInterval; public ConfigEntry ShieldFraction; public ConfigEntry BurstDamage; public ConfigEntry BurstRadius; public ConfigEntry SplitCount; private static readonly Random rng = new Random(); public override string Name => "Boss Mutations"; public override string StandaloneGuid => "dileppy.bossmutations"; public override int Stars => 3; public override bool UsesBodyStart => true; public override bool UsesRecalcStats => true; public override bool UsesReward => true; public override void Bind(ConfigFile cfg) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected O, but got Unknown //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Expected O, but got Unknown //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Expected O, but got Unknown //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Expected O, but got Unknown //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Expected O, but got Unknown Enabled = cfg.Bind("BossMutations", "Enabled", false, "Bosses can spawn mutated: Colossal, Frenzied, Shielded, Volatile, Splitting."); Chance = cfg.Bind("BossMutations", "BossMutationChance", 25f, new ConfigDescription("Chance (percent) that a boss spawns with a mutation. Rises each loop by the value below.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); PerLoop = cfg.Bind("BossMutations", "ChancePerLoop", 10f, new ConfigDescription("Percent added to the boss mutation chance each time you loop the run, so repeat bosses get more dangerous.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 50f), Array.Empty())); SplitCount = cfg.Bind("BossMutations", "SplitCount", 2, new ConfigDescription("How many smaller copies a Splitting boss spawns when it dies.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 4), Array.Empty())); ShieldInterval = cfg.Bind("BossMutations", "ShieldPulseInterval", 12f, new ConfigDescription("Seconds between each barrier regeneration for a Shielded boss - lower means it re-shields more often.", (AcceptableValueBase)(object)new AcceptableValueRange(4f, 60f), Array.Empty())); ShieldFraction = cfg.Bind("BossMutations", "ShieldPulseFraction", 0.25f, new ConfigDescription("How much barrier a Shielded boss regains per pulse, as a fraction of its max HP (0.25 = 25%).", (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 1f), Array.Empty())); BurstDamage = cfg.Bind("BossMutations", "VolatileBurstDamage", 10f, new ConfigDescription("How hard a Volatile boss bursts when it dies, as a multiple of its base damage.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 50f), Array.Empty())); BurstRadius = cfg.Bind("BossMutations", "VolatileBurstRadius", 18f, new ConfigDescription("Size of a Volatile boss death explosion, in meters.", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 50f), Array.Empty())); } public override void OnBodyStart(CharacterBody self) { //IL_034a: Unknown result type (might be due to invalid IL or missing references) //IL_0350: Unknown result type (might be due to invalid IL or missing references) if (!NetworkServer.active || !self.isBoss || Object.op_Implicit((Object)(object)((Component)self).GetComponent())) { return; } if (Object.op_Implicit((Object)(object)self.master) && Object.op_Implicit((Object)(object)((Component)self.master).GetComponent())) { ModuleBase.ApplyModelScale(self, 0.5f); Marker marker = ((Component)self).gameObject.AddComponent(); marker.type = BossType.None; marker.rewardMult = 0.25f; self.RecalculateStats(); DebugLog(self.GetDisplayName() + " spawned as a Splitting child: model scale x0.5, reward x0.25 (stats recalculated: maxHealth x0.25, damage x0.5)"); return; } int num = (Object.op_Implicit((Object)(object)Run.instance) ? Run.instance.loopClearCount : 0); float num2 = Chance.Value + PerLoop.Value * (float)num; if (rng.NextDouble() * 100.0 > (double)num2) { DebugLog($"{self.GetDisplayName()} rolled for a boss mutation and got none (chance was {num2:0.0}%, loop {num})"); return; } BossType bossType = (BossType)(1 + rng.Next(5)); DebugLog($"{self.GetDisplayName()} rolled boss mutation: {bossType} (chance was {num2:0.0}%, loop {num})"); Marker marker2 = ((Component)self).gameObject.AddComponent(); marker2.type = bossType; switch (bossType) { case BossType.Colossal: ModuleBase.ApplyModelScale(self, 1.8f); marker2.rewardMult = 2f; DebugLog($"{self.GetDisplayName()} [Colossal] applied: model scale x1.8, reward x{marker2.rewardMult:0.00}"); break; case BossType.Frenzied: ModuleBase.ApplyModelScale(self, 0.95f); marker2.rewardMult = 1.6f; DebugLog($"{self.GetDisplayName()} [Frenzied] applied: model scale x0.95, reward x{marker2.rewardMult:0.00}"); break; case BossType.Shielded: marker2.rewardMult = 1.5f; marker2.nextShield = Time.time + ShieldInterval.Value; DebugLog($"{self.GetDisplayName()} [Shielded] applied: reward x{marker2.rewardMult:0.00}, first barrier pulse in {ShieldInterval.Value:0.0}s ({ShieldFraction.Value * 100f:0}% max HP per pulse)"); break; case BossType.Volatile: ModuleBase.ApplyModelScale(self, 1.1f); marker2.rewardMult = 1.6f; DebugLog($"{self.GetDisplayName()} [Volatile] applied: model scale x1.1, reward x{marker2.rewardMult:0.00}, burst damage x{BurstDamage.Value:0.0} at 75/50/25% HP, radius {BurstRadius.Value:0.0}m"); break; case BossType.Splitting: ModuleBase.ApplyModelScale(self, 1.15f); marker2.rewardMult = 1.8f; DebugLog($"{self.GetDisplayName()} [Splitting] applied: model scale x1.15, reward x{marker2.rewardMult:0.00}, splits into {SplitCount.Value} copies at 66/33% HP"); break; } ((Component)self).gameObject.AddComponent().Init(self, marker2, this); self.RecalculateStats(); if (Object.op_Implicit((Object)(object)self.healthComponent)) { self.healthComponent.HealFraction(1f, default(ProcChainMask)); } if (NotificationsModule.Gate) { Chat.AddMessage($"A {bossType} {self.GetDisplayName()} emerges!"); } } public override void OnRecalcStats(CharacterBody self) { Marker marker = (Object.op_Implicit((Object)(object)self) ? ((Component)self).GetComponent() : null); if ((Object)(object)marker == (Object)null) { return; } if (Object.op_Implicit((Object)(object)self.master) && Object.op_Implicit((Object)(object)((Component)self.master).GetComponent())) { self.maxHealth *= 0.25f; self.damage *= 0.5f; DebugLog($"{self.GetDisplayName()} [Split child] stats recalculated: maxHealth x0.25 -> {self.maxHealth:0}, damage x0.5 -> {self.damage:0.0}"); return; } switch (marker.type) { case BossType.Colossal: self.maxHealth *= 2f; self.damage *= 1.3f; self.attackSpeed *= 0.8f; self.moveSpeed *= 0.85f; DebugLog($"{self.GetDisplayName()} [Colossal] stats recalculated: maxHealth x2.0 -> {self.maxHealth:0}, damage x1.3 -> {self.damage:0.0}, attackSpeed x0.8 -> {self.attackSpeed:0.00}, moveSpeed x0.85 -> {self.moveSpeed:0.0}"); break; case BossType.Frenzied: self.maxHealth *= 0.75f; self.moveSpeed *= 1.4f; self.attackSpeed *= 1.4f; DebugLog($"{self.GetDisplayName()} [Frenzied] stats recalculated: maxHealth x0.75 -> {self.maxHealth:0}, moveSpeed x1.4 -> {self.moveSpeed:0.0}, attackSpeed x1.4 -> {self.attackSpeed:0.00}"); break; case BossType.Splitting: self.maxHealth *= 0.85f; DebugLog($"{self.GetDisplayName()} [Splitting] stats recalculated: maxHealth x0.85 -> {self.maxHealth:0}"); break; case BossType.Shielded: case BossType.Volatile: break; } } public override float RewardMultiplier(DamageReport report) { Marker marker = (Object.op_Implicit((Object)(object)report?.victimBody) ? ((Component)report.victimBody).GetComponent() : null); if ((Object)(object)marker != (Object)null) { DebugLog($"{report.victimBody.GetDisplayName()} [{marker.type}] reward multiplier applied: x{marker.rewardMult:0.00}"); } if (!((Object)(object)marker != (Object)null)) { return 1f; } return marker.rewardMult; } } internal class RunTitlesModule : ModuleBase { internal static string CurrentTitle = ""; public override string Name => "Run Titles"; public override string StandaloneGuid => null; public override int Stars => 0; public override bool UsesRunStart => true; public override void Bind(ConfigFile cfg) { Enabled = cfg.Bind("RunTitles", "Enabled", true, "Generate a shareable title for each run from its active modifiers, announced in chat at run start (respects the Notifications master switch)."); } public override void OnRunStart() { CurrentTitle = Generate(); if (CurrentTitle.Length > 0) { DebugLog("Run Title selected: '" + CurrentTitle + "' (announced=" + (NotificationsModule.Gate ? "yes" : "no, Notifications gate closed") + ")"); } else { DebugLog("Run Title selected: none (no active modules matched a title rule)"); } if (NotificationsModule.Gate && CurrentTitle.Length > 0) { Chat.AddMessage("\"" + CurrentTitle + "\""); } } private string Generate() { bool flag = On(); bool flag2 = On(); bool flag3 = On(); bool flag4 = On(); if (flag && flag3) { DebugLog("Title rule matched: signature combo 'The Ant and the Anvil' (ShrunkenSurvivor + EnlargedEnemies)"); return "The Ant and the Anvil"; } if (flag2 && flag4) { DebugLog("Title rule matched: signature combo 'Stomping Grounds' (EnlargedSurvivor + ShrunkenEnemies)"); return "Stomping Grounds"; } if (flag && On()) { DebugLog("Title rule matched: signature combo 'Small World, Big Problems' (ShrunkenSurvivor + EnemyMutations)"); return "Small World, Big Problems"; } List<(bool, string)> obj = new List<(bool, string)> { (On(), "Colossal"), (flag3, "Titanic"), (flag, "Tiny"), (On(), "Cursed"), (On(), "Stormbound"), (On(), "Starving"), (On(), "Volatile"), (On(), "Unstable") }; List<(bool, string)> list = new List<(bool, string)> { (On(), "Apocalypse"), (On(), "Gauntlet"), (On(), "Convergence"), (On(), "Odyssey"), (On(), "Hex"), (On(), "Tempest") }; string text = ""; string text2 = ""; foreach (var item in obj) { if (item.Item1) { text = item.Item2; break; } } foreach (var item2 in list) { if (item2.Item1) { text2 = item2.Item2; break; } } DebugLog("Title generation: adjective='" + ((text.Length > 0) ? text : "none") + "' noun='" + ((text2.Length > 0) ? text2 : "none") + "'"); if (text.Length > 0 && text2.Length > 0) { return text + " " + text2; } if (text2.Length > 0) { return text2; } if (text.Length > 0) { return text + " Run"; } return ""; static bool On() where T : ModuleBase { foreach (ModuleBase module in PetrichorProtocolPlugin.Modules) { if (module is T) { return module.FeatureActive; } } return false; } } } internal class HudModule : ModuleBase { private class HudPanel : MonoBehaviour { private HudModule mod; private Text text; private float nextRefresh; private bool? lastShow; public void Init(HudModule m) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) mod = m; RectTransform obj = ((Component)this).gameObject.AddComponent(); obj.anchorMin = new Vector2(0.5f, 1f); obj.anchorMax = new Vector2(0.5f, 1f); obj.pivot = new Vector2(0.5f, 1f); obj.anchoredPosition = new Vector2(0f, -12f); obj.sizeDelta = new Vector2(520f, 70f); text = ((Component)this).gameObject.AddComponent(); text.font = Resources.GetBuiltinResource("Arial.ttf"); text.fontSize = 14; ((Graphic)text).color = Color.white; text.alignment = (TextAnchor)1; text.supportRichText = true; text.horizontalOverflow = (HorizontalWrapMode)1; text.verticalOverflow = (VerticalWrapMode)1; Refresh(); } private void Update() { if (!(Time.unscaledTime < nextRefresh)) { nextRefresh = Time.unscaledTime + 1f; Refresh(); } } private void Refresh() { if (!((Object)(object)text == (Object)null) && mod != null) { bool flag = mod.Active && RunTitlesModule.CurrentTitle.Length + RiskLine.Length > 0; ((Behaviour)text).enabled = flag; if (flag) { text.text = mod.BuildText(); } if (lastShow != flag) { mod.DebugLog(flag ? ("HUD panel now visible: \"" + mod.BuildText().Replace('\n', ' ') + "\"") : "HUD panel now hidden (no title and no risk line to show)"); lastShow = flag; } } } } public ConfigEntry MinimalMode; internal static string RiskLine = ""; private static HudModule instance; private HudPanel panel; private bool hooked; public override string Name => "HUD"; public override string StandaloneGuid => null; public override int Stars => 0; public bool ShowFullSummary => !MinimalMode.Value; public override void Bind(ConfigFile cfg) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown Enabled = cfg.Bind("HUD", "Enabled", true, "Show a small on-screen panel with the run's Risk Score, tier, and title."); MinimalMode = cfg.Bind("HUD", "MinimalMode", false, "Show only the Risk Score and tier, not the full modifier summary."); instance = this; if (!hooked) { HUD.Awake += new hook_Awake(HUD_Awake); hooked = true; } } public override void Unhook() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown if (hooked) { HUD.Awake -= new hook_Awake(HUD_Awake); hooked = false; } } private void HUD_Awake(orig_Awake orig, HUD self) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown orig.Invoke(self); try { if (!base.Active || !Object.op_Implicit((Object)(object)self.mainContainer)) { DebugLog(string.Format("HUD panel not attached (Active={0}, mainContainer={1})", base.Active, Object.op_Implicit((Object)(object)self.mainContainer) ? "present" : "null")); return; } GameObject val = new GameObject("PetrichorHudPanel"); val.transform.SetParent(self.mainContainer.transform, false); panel = val.AddComponent(); panel.Init(this); DebugLog($"HUD panel attached to mainContainer (MinimalMode={MinimalMode.Value})"); } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"{Name}: HUD attach failed: {arg}"); } } internal string BuildText() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("THE PETRICHOR PROTOCOL"); if (RiskLine.Length > 0) { stringBuilder.Append(" ").Append(RiskLine); } if (RunTitlesModule.CurrentTitle.Length > 0 || ShowFullSummary) { stringBuilder.Append('\n'); } if (RunTitlesModule.CurrentTitle.Length > 0) { stringBuilder.Append("\"" + RunTitlesModule.CurrentTitle + "\""); } if (ShowFullSummary) { int num = 0; foreach (ModuleBase module in PetrichorProtocolPlugin.Modules) { if (module.Stars > 0 && module.FeatureActive) { num++; } } if (RunTitlesModule.CurrentTitle.Length > 0) { stringBuilder.Append(" "); } stringBuilder.Append($"{num} active modifiers"); } return stringBuilder.ToString(); } } internal class TitleBrandingModule : ModuleBase { private bool hooked; public override string Name => "Title Screen Branding"; public override string StandaloneGuid => null; public override int Stars => 0; public override void Bind(ConfigFile cfg) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown Enabled = cfg.Bind("TitleBranding", "Enabled", true, "Show \"THE PETRICHOR PROTOCOL\" under the game title on the main menu, so it is obvious the mod is active before you start a run."); if (!hooked) { MainMenuController.Awake += new hook_Awake(MainMenu_Awake); hooked = true; } } public override void Unhook() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown if (hooked) { MainMenuController.Awake -= new hook_Awake(MainMenu_Awake); hooked = false; } } private void MainMenu_Awake(orig_Awake orig, MainMenuController self) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) orig.Invoke(self); try { if (!base.Active || !Object.op_Implicit((Object)(object)self.titleMenuScreen)) { DebugLog(string.Format("Title branding not applied (Active={0}, titleMenuScreen={1})", base.Active, Object.op_Implicit((Object)(object)self.titleMenuScreen) ? "present" : "null")); return; } GameObject val = new GameObject("PetrichorTitleBranding"); val.transform.SetParent(((Component)self.titleMenuScreen).transform, false); RectTransform val2 = val.AddComponent(); val2.anchorMin = new Vector2(0.5f, 1f); val2.anchorMax = new Vector2(0.5f, 1f); val2.pivot = new Vector2(0.5f, 1f); val2.anchoredPosition = new Vector2(0f, -440f); val2.sizeDelta = new Vector2(700f, 50f); Text obj = val.AddComponent(); obj.font = Resources.GetBuiltinResource("Arial.ttf"); obj.fontSize = 22; ((Graphic)obj).color = new Color(1f, 0.843f, 0f); obj.alignment = (TextAnchor)1; obj.text = "THE PETRICHOR PROTOCOL"; DebugLog($"Title branding applied under title screen at offset {val2.anchoredPosition}"); } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"{Name}: title branding attach failed: {arg}"); } } } internal class RandomizerModule : ModuleBase { public enum Mode { LightChaos, BalancedChallenge, Nightmare, TrueRandom } public ConfigEntry RandomizerMode; public ConfigEntry RerollEachRun; private bool applied; private static readonly Random rng = new Random(); public override string Name => "Randomizer"; public override string StandaloneGuid => null; public override int Stars => 0; public override bool UsesRunStart => true; public override void Bind(ConfigFile cfg) { Enabled = cfg.Bind("Randomizer", "Enabled", false, "Let the Protocol pick a random set of modifiers for you. Overrides your manual module toggles when on."); RandomizerMode = cfg.Bind("Randomizer", "Mode", Mode.BalancedChallenge, "LightChaos: 1-3 low-risk. BalancedChallenge: target ~5-15 risk. Nightmare: high risk. TrueRandom: anything goes."); RerollEachRun = cfg.Bind("Randomizer", "RerollEachRun", true, "Roll a new set every run. If off, rolls once per game session."); } public override void OnRunStart() { if (base.Active) { if (applied && !RerollEachRun.Value) { DebugLog("Roll skipped: RerollEachRun is off and a roll was already applied this session"); return; } applied = true; Apply(); } } private void Apply() { List list = new List(); foreach (ModuleBase module in PetrichorProtocolPlugin.Modules) { if (module.Stars > 0 && module != this) { list.Add(module); } } foreach (ModuleBase item in list) { if (item.Enabled != null) { item.Enabled.Value = false; } } DebugLog($"Randomizer roll starting: candidate pool has {list.Count} scoring modules, all cleared"); Mode value = RandomizerMode.Value; int num; int num2; int num3; int num4; switch (value) { case Mode.LightChaos: num = 1; num2 = 6; num3 = 1; num4 = 3; break; case Mode.Nightmare: num = 16; num2 = 30; num3 = 5; num4 = 8; break; case Mode.TrueRandom: num = 0; num2 = 99; num3 = 1; num4 = list.Count; break; default: num = 5; num2 = 15; num3 = 3; num4 = 5; break; } DebugLog($"Randomizer mode {value}: target risk band [{num}-{num2}], target module count [{num3}-{num4}]"); for (int num5 = list.Count - 1; num5 > 0; num5--) { int num6 = rng.Next(num5 + 1); List list2 = list; int index = num5; int index2 = num6; ModuleBase value2 = list[num6]; ModuleBase value3 = list[num5]; list2[index] = value2; list[index2] = value3; } int num7 = 0; int num8 = 0; List list3 = new List(); foreach (ModuleBase item2 in list) { if (num8 >= num4) { DebugLog($"First pass stopping: reached count cap ({num4})"); break; } if (num7 >= num2) { DebugLog($"First pass stopping: reached risk ceiling ({num7} >= {num2})"); break; } if (value != Mode.TrueRandom && num7 + item2.Stars > num2 && num8 >= num3) { DebugLog($"First pass skipped '{item2.Name}' (Stars={item2.Stars}): would push risk to {num7 + item2.Stars} past ceiling {num2}, count floor {num3} already met"); continue; } if (item2.Enabled != null) { item2.Enabled.Value = true; } num7 += item2.Stars; num8++; list3.Add(item2.Name); DebugLog($"First pass rolled on '{item2.Name}' (Stars={item2.Stars}): running risk={num7}, count={num8}"); } if (value != Mode.TrueRandom && num7 < num) { DebugLog($"First pass undershot the floor ({num7} < {num}): running biggest-first top-up pass"); List list4 = new List(list); list4.Sort((ModuleBase a, ModuleBase b) => b.Stars.CompareTo(a.Stars)); foreach (ModuleBase item3 in list4) { if (num7 >= num) { break; } if (item3.Enabled != null && !item3.Enabled.Value && num7 + item3.Stars <= num2) { item3.Enabled.Value = true; num7 += item3.Stars; num8++; list3.Add(item3.Name); DebugLog($"Top-up rolled on '{item3.Name}' (Stars={item3.Stars}): running risk={num7}, count={num8}"); } } } DebugLog(string.Format("Randomizer roll complete: mode={0}, final risk={1}, modules=[{2}]", value, num7, string.Join(", ", list3))); if (NotificationsModule.Gate) { Chat.AddMessage($"RANDOMIZER ({value}) rolled {num8} modifiers - risk {num7}."); } PetrichorProtocolPlugin.Log.LogInfo((object)string.Format("Randomizer: {0} -> [{1}] risk={2}", value, string.Join(", ", list3), num7)); } } internal class NotificationsModule : ModuleBase { public ConfigEntry MasterEnable; private static NotificationsModule instance; public override string Name => "Notifications"; public override string StandaloneGuid => null; public override int Stars => 0; public override bool UsesRunStart => true; internal static bool Gate { get { if (instance != null && instance.Enabled != null) { return instance.Enabled.Value; } return true; } } public override void Bind(ConfigFile cfg) { Enabled = cfg.Bind("Notifications", "Enabled", true, "Master switch for ALL Protocol chat notifications - curses, events, combos, contracts, titles, size changes, everything. Turn off for a silent run."); MasterEnable = Enabled; instance = this; DebugLog("Notifications master gate bound: " + (Enabled.Value ? "open (chat announcements on)" : "closed (all Protocol chat notifications silenced)")); } } internal class ShrunkenSurvivorModule : ModuleBase { public enum Tier { Shrunken, Tiny, Miniature } private static readonly Dictionary T = new Dictionary { { Tier.Shrunken, (0.7f, 0.85f, 0.85f, 0.75f, 1.25f) }, { Tier.Tiny, (0.5f, 0.7f, 0.7f, 0.55f, 1.6f) }, { Tier.Miniature, (0.3f, 0.55f, 0.55f, 0.4f, 2f) } }; public ConfigEntry SizeTier; public ConfigEntry CameraPullIn; public ConfigEntry GiantWeapon; public ConfigEntry ShrinkProjectiles; internal static readonly string[] WeaponWords = new string[15] { "gun", "weapon", "pistol", "rifle", "musket", "launcher", "crossbow", "bow", "sword", "blade", "hammer", "cleaver", "knife", "railgun", "shotgun" }; public override string Name => "Shrunken Survivor"; public override string StandaloneGuid => "dileppy.shrunkensurvivor"; public override int Stars => 2; public override bool UsesBodyStart => true; public override bool UsesRecalcStats => true; public override bool UsesProjectile => true; public override void Bind(ConfigFile cfg) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown Enabled = cfg.Bind("ShrunkenSurvivor", "Enabled", false, "Shrink YOURSELF: slower, weaker jumps, less damage, faster fire rate."); SizeTier = cfg.Bind("ShrunkenSurvivor", "SizeTier", Tier.Shrunken, "How small to make you: Shrunken (70% size), Tiny (50%), or Miniature (30%). Smaller means weaker but faster-firing."); CameraPullIn = cfg.Bind("ShrunkenSurvivor", "CameraPullIn", 0.6f, new ConfigDescription("Pull the camera in closer so your shrunken survivor stays readable on screen.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); GiantWeapon = cfg.Bind("ShrunkenSurvivor", "GiantWeapon", true, "Keep your weapon full-size on the tiny body, for a comically oversized look."); ShrinkProjectiles = cfg.Bind("ShrunkenSurvivor", "ShrinkProjectiles", true, "Shrink your projectiles along with you. Automatically ignored while Bigger Bullets is on, so they do not fight each other."); Enabled.SettingChanged += delegate { ReapplyToCurrentPlayers(); }; SizeTier.SettingChanged += delegate { ReapplyToCurrentPlayers(); }; } private void ReapplyToCurrentPlayers() { foreach (PlayerCharacterMasterController instance in PlayerCharacterMasterController.instances) { CharacterBody val = (Object.op_Implicit((Object)(object)instance.master) ? instance.master.GetBody() : null); if (Object.op_Implicit((Object)(object)val)) { ApplyToBody(val); val.RecalculateStats(); } } } private void ApplyToBody(CharacterBody self) { if (SizeRouletteModule.RouletteActive()) { DebugLog("Skipped: Size Roulette is active"); } else if (ModuleBase.IsPlayer(self)) { (float, float, float, float, float) tuple = T[SizeTier.Value]; ModuleBase.ApplyModelScale(self, tuple.Item1); DebugLog($"Applied {SizeTier.Value} scale x{tuple.Item1} to player body {((Object)self).name}"); if (GiantWeapon.Value) { ScaleWeaponBones(self, 1.6f); DebugLog("Applied GiantWeapon bone scale x1.6"); } PullCamera(self, Mathf.Lerp(1f, tuple.Item1, CameraPullIn.Value)); } } public override void OnBodyStart(CharacterBody self) { ApplyToBody(self); } public override void OnRecalcStats(CharacterBody self) { if (!SizeRouletteModule.RouletteActive() && ModuleBase.IsPlayer(self)) { (float, float, float, float, float) tuple = T[SizeTier.Value]; self.moveSpeed *= tuple.Item2; self.jumpPower *= tuple.Item3; self.damage *= tuple.Item4; self.attackSpeed *= tuple.Item5; DebugLog($"Applied {SizeTier.Value} stat multipliers: move x{tuple.Item2}, jump x{tuple.Item3}, dmg x{tuple.Item4}, atk x{tuple.Item5}"); } } public override void OnProjectileStart(ProjectileController proj) { if (SizeRouletteModule.RouletteActive() || !ShrinkProjectiles.Value || BulletFeatureActive() || !Object.op_Implicit((Object)(object)proj.owner)) { return; } CharacterBody component = proj.owner.GetComponent(); if (Object.op_Implicit((Object)(object)component) && ModuleBase.IsPlayer(component)) { float item = T[SizeTier.Value].size; ModuleBase.ApplyAbsoluteScale(((Component)proj).gameObject, item); if (Object.op_Implicit((Object)(object)proj.ghost)) { ModuleBase.ApplyAbsoluteScale(((Component)proj.ghost).gameObject, item); } DebugLog($"Shrunk player projectile {((Object)((Component)proj).gameObject).name} to x{item}"); } } private static bool BulletFeatureActive() { foreach (ModuleBase module in PetrichorProtocolPlugin.Modules) { if (module is BiggerBulletsModule && module.FeatureActive) { return true; } } return false; } internal static void ScaleWeaponBones(CharacterBody self, float factor) { ModelLocator modelLocator = self.modelLocator; if (!Object.op_Implicit((Object)(object)modelLocator) || !Object.op_Implicit((Object)(object)modelLocator.modelTransform)) { return; } Transform[] componentsInChildren = ((Component)modelLocator.modelTransform).GetComponentsInChildren(true); foreach (Transform val in componentsInChildren) { string text = ((Object)val).name.ToLowerInvariant(); string[] weaponWords = WeaponWords; foreach (string value in weaponWords) { if (text.Contains(value)) { ModuleBase.ApplyAbsoluteScale(((Component)val).gameObject, factor); break; } } } } internal static void PullCamera(CharacterBody self, float f) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) if (Mathf.Approximately(f, 1f) || !Object.op_Implicit((Object)(object)self)) { return; } CameraTargetParams component = ((Component)self).GetComponent(); if (!Object.op_Implicit((Object)(object)component)) { return; } try { Vector3 value = component.currentCameraParamsData.idealLocalCameraPos.value; if (!(value == Vector3.zero)) { CharacterCameraParamsData cameraParamsData = new CharacterCameraParamsData { idealLocalCameraPos = BlendableVector3.op_Implicit(value * f) }; component.AddParamsOverride(new CameraParamsOverrideRequest { cameraParamsData = cameraParamsData, priority = 0.5f }, 0.4f); } } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"PullCamera failed: {arg}"); } } } internal class EnlargedSurvivorModule : ModuleBase { public enum Tier { Enlarged, Giant, Colossal, Ridiculous } private static readonly Dictionary T = new Dictionary { { Tier.Enlarged, (1.4f, 1.1f, 1.1f, 1.3f, 0.85f, 0.9f) }, { Tier.Giant, (1.8f, 1.2f, 1.15f, 1.7f, 0.7f, 0.75f) }, { Tier.Colossal, (2.5f, 1.35f, 1.25f, 2.5f, 0.55f, 0.6f) }, { Tier.Ridiculous, (5f, 1.35f, 1.35f, 3.3f, 0.4f, 0.45f) } }; public ConfigEntry SizeTier; public ConfigEntry CameraPullBack; public ConfigEntry TinyWeapon; public override string Name => "Enlarged Survivor"; public override string StandaloneGuid => "dileppy.enlargedsurvivor"; public override int Stars => 1; public override bool UsesBodyStart => true; public override bool UsesRecalcStats => true; public override bool UsesReward => true; public override void Bind(ConfigFile cfg) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown Enabled = cfg.Bind("EnlargedSurvivor", "Enabled", false, "Enlarge YOURSELF: stronger and faster, slower fire rate, reduced rewards. (Do not combine with ShrunkenSurvivor.)"); SizeTier = cfg.Bind("EnlargedSurvivor", "SizeTier", Tier.Enlarged, "How big to make you: Enlarged (140% size), Giant (180%), Colossal (250%), or Ridiculous (500% - twice Colossal). Bigger means stronger but slower-firing."); CameraPullBack = cfg.Bind("EnlargedSurvivor", "CameraPullBack", 0.6f, new ConfigDescription("Pull the camera back so your enlarged survivor fits comfortably on screen.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); TinyWeapon = cfg.Bind("EnlargedSurvivor", "TinyWeapon", true, "Keep your weapon at normal size on the giant body, for a comically tiny look."); Enabled.SettingChanged += delegate { ReapplyToCurrentPlayers(); }; SizeTier.SettingChanged += delegate { ReapplyToCurrentPlayers(); }; } private void ReapplyToCurrentPlayers() { foreach (PlayerCharacterMasterController instance in PlayerCharacterMasterController.instances) { CharacterBody val = (Object.op_Implicit((Object)(object)instance.master) ? instance.master.GetBody() : null); if (Object.op_Implicit((Object)(object)val)) { ApplyToBody(val); val.RecalculateStats(); } } } private void ApplyToBody(CharacterBody self) { if (SizeRouletteModule.RouletteActive()) { DebugLog("Skipped: Size Roulette is active"); } else { if (!ModuleBase.IsPlayer(self)) { return; } if (SurvivorConflict()) { DebugLog("Skipped: ShrunkenSurvivor conflict detected"); return; } (float, float, float, float, float, float) tuple = T[SizeTier.Value]; ModuleBase.ApplyModelScale(self, tuple.Item1); DebugLog($"Applied {SizeTier.Value} scale x{tuple.Item1} to player body {((Object)self).name}"); if (TinyWeapon.Value) { ShrunkenSurvivorModule.ScaleWeaponBones(self, 0.45f); DebugLog("Applied TinyWeapon bone scale x0.45"); } ShrunkenSurvivorModule.PullCamera(self, Mathf.Lerp(1f, tuple.Item1, CameraPullBack.Value)); } } public override void OnBodyStart(CharacterBody self) { ApplyToBody(self); } public override void OnRecalcStats(CharacterBody self) { if (!SizeRouletteModule.RouletteActive() && ModuleBase.IsPlayer(self) && !SurvivorConflict()) { (float, float, float, float, float, float) tuple = T[SizeTier.Value]; self.moveSpeed *= tuple.Item2; self.jumpPower *= tuple.Item3; self.damage *= tuple.Item4; self.attackSpeed *= tuple.Item5; DebugLog($"Applied {SizeTier.Value} stat multipliers: move x{tuple.Item2}, jump x{tuple.Item3}, dmg x{tuple.Item4}, atk x{tuple.Item5}"); } } public override float RewardMultiplier(DamageReport report) { if (SizeRouletteModule.RouletteActive() || SurvivorConflict()) { return 1f; } float item = T[SizeTier.Value].reward; DebugLog($"Applied enlarged-survivor reward multiplier x{item} for {SizeTier.Value}"); return item; } private static bool SurvivorConflict() { foreach (ModuleBase module in PetrichorProtocolPlugin.Modules) { if (module is ShrunkenSurvivorModule && module.FeatureActive) { return true; } } return false; } } internal class ShrunkenEnemiesModule : ModuleBase { public enum Tier { Shrunken, Tiny, Miniature } private static readonly Dictionary T = new Dictionary { { Tier.Shrunken, (0.7f, 1.15f, 0.85f, 0.8f, 0.9f) }, { Tier.Tiny, (0.5f, 1.3f, 0.7f, 0.65f, 0.8f) }, { Tier.Miniature, (0.3f, 1.5f, 0.55f, 0.5f, 0.7f) } }; public ConfigEntry SizeTier; public ConfigEntry AffectBosses; public override string Name => "Shrunken Enemies"; public override string StandaloneGuid => "dileppy.shrunkenenemies"; public override int Stars => 1; public override bool UsesBodyStart => true; public override bool UsesRecalcStats => true; public override bool UsesProjectile => true; public override bool UsesReward => true; public override void Bind(ConfigFile cfg) { Enabled = cfg.Bind("ShrunkenEnemies", "Enabled", false, "Shrink all enemies: smaller, faster, weaker, and they drop a little less."); SizeTier = cfg.Bind("ShrunkenEnemies", "SizeTier", Tier.Shrunken, "How small to make enemies: Shrunken (70% size), Tiny (50%), or Miniature (30%). Smaller enemies are weaker and drop less loot."); AffectBosses = cfg.Bind("ShrunkenEnemies", "AffectBosses", true, "Apply the shrink to bosses as well as regular enemies."); } private bool Targets(CharacterBody b) { if (SizeRouletteModule.RouletteActive() || !ModuleBase.IsHostileEnemy(b)) { return false; } if (b.isBoss && !AffectBosses.Value) { return false; } return true; } public override void OnBodyStart(CharacterBody self) { if (Targets(self)) { ModuleBase.ApplyModelScale(self, T[SizeTier.Value].size); DebugLog($"Shrunk enemy body {((Object)self).name} ({SizeTier.Value}) to x{T[SizeTier.Value].size}"); if (Object.op_Implicit((Object)(object)self.healthComponent)) { self.healthComponent.health = self.healthComponent.fullHealth; } } } public override void OnRecalcStats(CharacterBody self) { if (Targets(self)) { (float, float, float, float, float) tuple = T[SizeTier.Value]; self.moveSpeed *= tuple.Item2; self.damage *= tuple.Item3; self.maxHealth *= tuple.Item4; DebugLog($"Applied {SizeTier.Value} enemy stat multipliers to {((Object)self).name}: move x{tuple.Item2}, dmg x{tuple.Item3}, hp x{tuple.Item4}"); } } public override void OnProjectileStart(ProjectileController proj) { if (SizeRouletteModule.RouletteActive() || !Object.op_Implicit((Object)(object)proj.owner)) { return; } CharacterBody component = proj.owner.GetComponent(); if (Object.op_Implicit((Object)(object)component) && Targets(component)) { float item = T[SizeTier.Value].size; ModuleBase.ApplyAbsoluteScale(((Component)proj).gameObject, item); if (Object.op_Implicit((Object)(object)proj.ghost)) { ModuleBase.ApplyAbsoluteScale(((Component)proj.ghost).gameObject, item); } DebugLog($"Shrunk enemy projectile {((Object)((Component)proj).gameObject).name} to x{item}"); } } public override float RewardMultiplier(DamageReport report) { if ((Object)(object)report?.victimBody == (Object)null || !Targets(report.victimBody)) { return 1f; } float item = T[SizeTier.Value].reward; DebugLog($"Applied shrunken-enemy reward multiplier x{item} for {((Object)report.victimBody).name}"); return item; } } internal class EnlargedEnemiesModule : ModuleBase { public enum Tier { Enlarged, Giant, Colossal, Ridiculous } private static readonly Dictionary T = new Dictionary { { Tier.Enlarged, (1.4f, 0.9f, 1.3f, 1.35f, 1.2f) }, { Tier.Giant, (1.8f, 0.8f, 1.7f, 1.8f, 1.45f) }, { Tier.Colossal, (2.5f, 0.7f, 2.5f, 2.6f, 1.8f) }, { Tier.Ridiculous, (5f, 0.6f, 3.3f, 3.4f, 2.15f) } }; public ConfigEntry SizeTier; public ConfigEntry AffectBosses; public override string Name => "Enlarged Enemies"; public override string StandaloneGuid => "dileppy.enlargedenemies"; public override int Stars => 3; public override bool UsesBodyStart => true; public override bool UsesRecalcStats => true; public override bool UsesProjectile => true; public override bool UsesReward => true; public override void Bind(ConfigFile cfg) { Enabled = cfg.Bind("EnlargedEnemies", "Enabled", false, "Enlarge all enemies: bigger, slower, much stronger and tankier, with better loot."); SizeTier = cfg.Bind("EnlargedEnemies", "SizeTier", Tier.Enlarged, "How big to make enemies: Enlarged (140% size), Giant (180%), Colossal (250%), or Ridiculous (500% - twice Colossal). Bigger enemies are tankier and drop more loot."); AffectBosses = cfg.Bind("EnlargedEnemies", "AffectBosses", true, "Apply the enlarge to bosses as well. A Colossal teleporter boss is a genuinely different fight."); } private bool Targets(CharacterBody b) { if (SizeRouletteModule.RouletteActive() || !ModuleBase.IsHostileEnemy(b)) { return false; } if (b.isBoss && !AffectBosses.Value) { return false; } foreach (ModuleBase module in PetrichorProtocolPlugin.Modules) { if (module is ShrunkenEnemiesModule && module.FeatureActive) { DebugLog("Skipped enlarging " + ((Object)b).name + ": ShrunkenEnemies conflict detected"); return false; } } return true; } public override void OnBodyStart(CharacterBody self) { if (Targets(self)) { ModuleBase.ApplyModelScale(self, T[SizeTier.Value].size); DebugLog($"Enlarged enemy body {((Object)self).name} ({SizeTier.Value}) to x{T[SizeTier.Value].size}"); if (Object.op_Implicit((Object)(object)self.healthComponent)) { self.healthComponent.health = self.healthComponent.fullHealth; } } } public override void OnRecalcStats(CharacterBody self) { if (Targets(self)) { (float, float, float, float, float) tuple = T[SizeTier.Value]; self.moveSpeed *= tuple.Item2; self.damage *= tuple.Item3; self.maxHealth *= tuple.Item4; DebugLog($"Applied {SizeTier.Value} enemy stat multipliers to {((Object)self).name}: move x{tuple.Item2}, dmg x{tuple.Item3}, hp x{tuple.Item4}"); } } public override void OnProjectileStart(ProjectileController proj) { if (SizeRouletteModule.RouletteActive() || !Object.op_Implicit((Object)(object)proj.owner)) { return; } CharacterBody component = proj.owner.GetComponent(); if (Object.op_Implicit((Object)(object)component) && Targets(component)) { float item = T[SizeTier.Value].size; ModuleBase.ApplyAbsoluteScale(((Component)proj).gameObject, item); if (Object.op_Implicit((Object)(object)proj.ghost)) { ModuleBase.ApplyAbsoluteScale(((Component)proj.ghost).gameObject, item); } DebugLog($"Enlarged enemy projectile {((Object)((Component)proj).gameObject).name} to x{item}"); } } public override float RewardMultiplier(DamageReport report) { if ((Object)(object)report?.victimBody == (Object)null || !Targets(report.victimBody)) { return 1f; } float item = T[SizeTier.Value].reward; DebugLog($"Applied enlarged-enemy reward multiplier x{item} for {((Object)report.victimBody).name}"); return item; } } internal class BiggerBulletsModule : ModuleBase { private class DamageMarker : MonoBehaviour { public float baseDamage; public bool has; } public ConfigEntry SizeMultiplier; public ConfigEntry ScaleHitscan; public ConfigEntry ScaleProjectiles; public ConfigEntry ScaleTracers; public ConfigEntry PlayerWeaponsOnly; public ConfigEntry MinHitscanRadius; private static float tracerScale; public override string Name => "Bigger Bullets"; public override string StandaloneGuid => "dileppy.biggerbullets"; public override int Stars => 2; public override bool UsesBulletFire => true; public override bool UsesProjectile => true; public override bool UsesSpawnEffect => true; public override bool WantsSpawnEffectHook { get { if (base.Active && ScaleHitscan != null && ScaleHitscan.Value && ScaleTracers != null) { return ScaleTracers.Value; } return false; } } public override void Bind(ConfigFile cfg) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Expected O, but got Unknown Enabled = cfg.Bind("BiggerBullets", "Enabled", false, "Make your bullets bigger: more area and more damage."); SizeMultiplier = cfg.Bind("BiggerBullets", "SizeMultiplier", 2f, new ConfigDescription("How much bigger and stronger your bullets get. Size and damage both scale by this same multiplier.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 6f), Array.Empty())); ScaleHitscan = cfg.Bind("BiggerBullets", "ScaleHitscanWeapons", true, "Scale hitscan (instant-hit) weapons like most pistols and rifles."); ScaleProjectiles = cfg.Bind("BiggerBullets", "ScaleProjectileWeapons", true, "Scale projectile weapons that fire a travelling object, like grenades or arrows."); ScaleTracers = cfg.Bind("BiggerBullets", "ScaleTracers", true, "Grow the visual tracers and muzzle flashes so they match the bigger bullets."); PlayerWeaponsOnly = cfg.Bind("BiggerBullets", "PlayerWeaponsOnly", true, "Scale only attacks you (and your allies) fire, leaving enemy attacks untouched."); MinHitscanRadius = cfg.Bind("BiggerBullets", "MinHitscanRadius", 0.5f, new ConfigDescription("Minimum width given to hitscan rays that normally have none, so the size boost has something to grow from.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 3f), Array.Empty())); Enabled.SettingChanged += delegate { PetrichorProtocolPlugin.RefreshDynamicHooks(); }; ScaleHitscan.SettingChanged += delegate { PetrichorProtocolPlugin.RefreshDynamicHooks(); }; ScaleTracers.SettingChanged += delegate { PetrichorProtocolPlugin.RefreshDynamicHooks(); }; } private bool ShouldScale(GameObject owner) { if (!PlayerWeaponsOnly.Value) { return true; } if (!Object.op_Implicit((Object)(object)owner)) { return false; } CharacterBody component = owner.GetComponent(); if (Object.op_Implicit((Object)(object)component)) { return ModuleBase.IsPlayer(component); } return false; } public override bool OnBulletFire(BulletAttack bullet, orig_Fire orig) { bool flag = ShouldScale(bullet.owner); if (!ScaleHitscan.Value || !flag) { object arg = ScaleHitscan.Value; object arg2 = flag; GameObject owner = bullet.owner; DebugLog($"Hitscan bullet not scaled: ScaleHitscanEnabled={arg}, ShouldScale={arg2}, owner={((owner != null) ? ((Object)owner).name : null)}"); return false; } GameObject owner2 = bullet.owner; DebugLog($"Scaling hitscan bullet from owner {((owner2 != null) ? ((Object)owner2).name : null)} by x{SizeMultiplier.Value}"); float value = SizeMultiplier.Value; float radius = bullet.radius; float damage = bullet.damage; try { bullet.radius = Mathf.Max(bullet.radius, MinHitscanRadius.Value) * value; bullet.damage = damage * value; if (ScaleTracers.Value) { tracerScale = value; } orig.Invoke(bullet); } finally { tracerScale = 0f; bullet.radius = radius; bullet.damage = damage; } return true; } public override void OnSpawnEffect(GameObject prefab, EffectData data) { if (!(tracerScale <= 0f) && data != null && data.scale > 0f) { data.scale *= tracerScale; } } public override void OnProjectileStart(ProjectileController proj) { //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) bool flag = ShouldScale(proj.owner); if (!ScaleProjectiles.Value || !flag) { object arg = ScaleProjectiles.Value; object arg2 = flag; GameObject owner = proj.owner; DebugLog($"Projectile not scaled: ScaleProjectilesEnabled={arg}, ShouldScale={arg2}, owner={((owner != null) ? ((Object)owner).name : null)}"); return; } string name = ((Object)((Component)proj).gameObject).name; GameObject owner2 = proj.owner; DebugLog($"Scaling projectile {name} from owner {((owner2 != null) ? ((Object)owner2).name : null)} by x{SizeMultiplier.Value}"); float value = SizeMultiplier.Value; ((Component)proj).gameObject.transform.localScale = Vector3.one * value; if (Object.op_Implicit((Object)(object)proj.ghost)) { ((Component)proj.ghost).gameObject.transform.localScale = Vector3.one * value; } ProjectileDamage component = ((Component)proj).GetComponent(); if (Object.op_Implicit((Object)(object)component)) { DamageMarker damageMarker = ((Component)proj).GetComponent(); if (!Object.op_Implicit((Object)(object)damageMarker)) { damageMarker = ((Component)proj).gameObject.AddComponent(); damageMarker.baseDamage = component.damage; damageMarker.has = true; } if (damageMarker.has) { component.damage = damageMarker.baseDamage * value; } } } } internal class SizeRouletteModule : ModuleBase { internal class RouletteMark : MonoBehaviour { public int state = -1; public bool isPlayer; public bool pendingHealthRescale; public float healthFractionAtRoll = 1f; } public ConfigEntry RerollOnStage; public ConfigEntry RerollMidStage; public ConfigEntry MidStageInterval; public ConfigEntry RollSurvivors; public ConfigEntry RollEnemies; public ConfigEntry CameraPull; private static readonly (string label, float size, float move, float jump, float dmg, float atk, float hp)[] States = new(string, float, float, float, float, float, float)[7] { ("Shrunken", 0.7f, 0.85f, 0.85f, 0.75f, 1.25f, 0.75f), ("Tiny", 0.5f, 0.7f, 0.7f, 0.55f, 1.6f, 0.55f), ("Miniature", 0.3f, 0.55f, 0.55f, 0.4f, 2f, 0.4f), ("Enlarged", 1.4f, 1.1f, 1.1f, 1.3f, 0.85f, 1.6f), ("Giant", 1.8f, 1.2f, 1.15f, 1.7f, 0.7f, 2.4f), ("Colossal", 2.5f, 1.35f, 1.25f, 2.5f, 0.55f, 3.5f), ("What the f...", 5f, 1.35f, 1.35f, 3.3f, 0.4f, 4.6f) }; private static readonly Random rng = new Random(); private float nextMidRoll; private float lastMidRollTime = -1f; public override string Name => "Size Roulette"; public override string StandaloneGuid => null; public override int Stars => 3; public override bool UsesBodyStart => true; public override bool UsesRecalcStats => true; public override bool UsesStageStart => true; public override bool UsesFixedUpdate => true; internal static bool RouletteActive() { foreach (ModuleBase module in PetrichorProtocolPlugin.Modules) { if (module is SizeRouletteModule && module.Active) { return true; } } return false; } public override void Bind(ConfigFile cfg) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Expected O, but got Unknown Enabled = cfg.Bind("SizeRoulette", "Enabled", false, "Randomize everyone's size over a run: survivors and enemies each get rolled to a random shrunk-or-enlarged state instead of a fixed size. A chaos mode - off by default. Takes over from the manual size features while on."); RerollOnStage = cfg.Bind("SizeRoulette", "RerollEachStage", true, "Roll a new random size for every survivor and enemy at the start of each stage (on teleport). The stable, recommended option."); RerollMidStage = cfg.Bind("SizeRoulette", "RerollMidStage", false, "Also re-roll sizes mid-stage on a timer, so your size can suddenly change while you are still fighting. Maximum chaos."); MidStageInterval = cfg.Bind("SizeRoulette", "MidStageIntervalSeconds", 240f, new ConfigDescription("If mid-stage re-rolls are on, how many seconds between each automatic re-roll.", (AcceptableValueBase)(object)new AcceptableValueRange(30f, 900f), Array.Empty())); RollSurvivors = cfg.Bind("SizeRoulette", "RollSurvivors", true, "Include players in the size roulette."); RollEnemies = cfg.Bind("SizeRoulette", "RollEnemies", true, "Include enemies in the size roulette."); CameraPull = cfg.Bind("SizeRoulette", "CameraPull", 0.6f, new ConfigDescription("Pull the camera in/out to match your rolled size, same as the manual size features already do. Without this the camera stays at its normal distance while your model grows or shrinks, which reads as the camera being too zoomed in at the largest rolls.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); } public override void OnStageStart(Stage stage) { nextMidRoll = Time.time + MidStageInterval.Value; lastMidRollTime = Time.time; DebugLog($"Stage start: next mid-stage reroll due at Time.time={nextMidRoll:0.0} (interval {MidStageInterval.Value}s, RerollMidStage={RerollMidStage.Value})"); if (RerollOnStage.Value) { DebugLog("Stage start: rerolling all sizes"); RerollAll(); } } public override void OnFixedUpdateServer() { if (RerollMidStage.Value && !(Time.time < nextMidRoll)) { float num = Time.time - lastMidRollTime; nextMidRoll = Time.time + MidStageInterval.Value; lastMidRollTime = Time.time; DebugLog($"Mid-stage timer elapsed at Time.time={Time.time:0.0} - actual gap since last reroll: {num:0.0}s (configured interval: {MidStageInterval.Value}s) - rerolling all sizes"); RerollAll(); } } private void RerollAll() { foreach (CharacterBody readOnlyInstances in CharacterBody.readOnlyInstancesList) { if (Object.op_Implicit((Object)(object)readOnlyInstances) && Object.op_Implicit((Object)(object)readOnlyInstances.healthComponent) && readOnlyInstances.healthComponent.alive) { bool flag = ModuleBase.IsPlayer(readOnlyInstances); if ((!flag || RollSurvivors.Value) && (flag || (RollEnemies.Value && ModuleBase.IsHostileEnemy(readOnlyInstances)))) { AssignRandomState(readOnlyInstances, flag, flag && NotificationsModule.Gate); } } } } private void AssignRandomState(CharacterBody body, bool player, bool announce) { RouletteMark rouletteMark = ((Component)body).GetComponent(); bool flag = !Object.op_Implicit((Object)(object)rouletteMark); if (!Object.op_Implicit((Object)(object)rouletteMark)) { rouletteMark = ((Component)body).gameObject.AddComponent(); } rouletteMark.isPlayer = player; rouletteMark.state = rng.Next(States.Length); if (player) { rouletteMark.pendingHealthRescale = false; } else { rouletteMark.healthFractionAtRoll = ((flag || !Object.op_Implicit((Object)(object)body.healthComponent)) ? 1f : Mathf.Clamp01(body.healthComponent.health / Mathf.Max(1f, body.healthComponent.fullHealth))); rouletteMark.pendingHealthRescale = true; } (string, float, float, float, float, float, float) tuple = States[rouletteMark.state]; DebugLog(string.Format("Size Roulette rolled {0} for {1} - size x{2}, hp x{3}", tuple.Item1, rouletteMark.isPlayer ? "player" : "enemy", tuple.Item2, tuple.Item7)); ModuleBase.ApplyModelScale(body, tuple.Item2); body.MarkAllStatsDirty(); if (player) { ShrunkenSurvivorModule.PullCamera(body, Mathf.Lerp(1f, tuple.Item2, CameraPull.Value)); } if (announce) { Chat.AddMessage("SIZE ROULETTE: you are now " + tuple.Item1 + "!"); } } public override void OnBodyStart(CharacterBody self) { if (NetworkServer.active) { bool flag = ModuleBase.IsPlayer(self); if ((!flag || RollSurvivors.Value) && (flag || (RollEnemies.Value && ModuleBase.IsHostileEnemy(self)))) { AssignRandomState(self, flag, announce: false); } } } public override void OnRecalcStats(CharacterBody self) { RouletteMark component = ((Component)self).GetComponent(); if ((Object)(object)component == (Object)null || component.state < 0 || component.state >= States.Length) { return; } (string, float, float, float, float, float, float) tuple = States[component.state]; self.moveSpeed *= tuple.Item3; self.jumpPower *= tuple.Item4; self.damage *= tuple.Item5; self.attackSpeed *= tuple.Item6; if (!component.isPlayer) { self.maxHealth *= tuple.Item7; } DebugLog(string.Format("Applied roulette state {0} stats to {1} {2}: move x{3}, jump x{4}, dmg x{5}, atk x{6}", tuple.Item1, component.isPlayer ? "player" : "enemy", ((Object)self).name, tuple.Item3, tuple.Item4, tuple.Item5, tuple.Item6) + ((!component.isPlayer) ? $", hp x{tuple.Item7}" : "")); if (component.pendingHealthRescale) { component.pendingHealthRescale = false; if (Object.op_Implicit((Object)(object)self.healthComponent)) { self.healthComponent.health = self.healthComponent.fullHealth * component.healthFractionAtRoll; } } } } internal class CurseDeckModule : ModuleBase { public enum Curse { None, TinyTerror, ColossalThreat, BulletStorm, BloodMoon, GreedStage, GravityFailure } private class GiantMark : MonoBehaviour { } public ConfigEntry CurseChance; public ConfigEntry RewardPerCurse; public ConfigEntry CurseChanceScaling; public ConfigEntry StartsOnStage; private Curse active; private Vector3 baseGravity; private bool gravityStored; private static readonly Random rng = new Random(); public override string Name => "Curse Deck"; public override string StandaloneGuid => "dileppy.cursedeck"; public override int Stars => 3; public override bool UsesStageStart => true; public override bool UsesRunStart => true; public override bool UsesBodyStart => true; public override bool UsesRecalcStats => true; public override bool UsesProjectile => true; public override bool UsesHeal => true; public override bool UsesReward => true; public override void OnRunStart() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (gravityStored) { Physics.gravity = baseGravity; gravityStored = false; } active = Curse.None; } public override void Bind(ConfigFile cfg) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Expected O, but got Unknown //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown Enabled = cfg.Bind("CurseDeck", "Enabled", false, "Every stage draws a curse card that changes the rules for that stage only."); CurseChance = cfg.Bind("CurseDeck", "CurseChancePerStage", 100f, new ConfigDescription("Chance (percent) that a stage draws a curse card when you arrive.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); CurseChanceScaling = cfg.Bind("CurseDeck", "CurseChanceScalingPerStage", 0f, new ConfigDescription("Percent added to the curse chance for every stage cleared, so later stages get steadily more cursed. 0 = no scaling (the chance stays flat).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 20f), Array.Empty())); StartsOnStage = cfg.Bind("CurseDeck", "CurseStartsOnStage", 1, new ConfigDescription("Earliest stage number that can draw a curse, so early stages can stay calm.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 10), Array.Empty())); RewardPerCurse = cfg.Bind("CurseDeck", "RewardMultiplierPerCurse", 0.15f, new ConfigDescription("Extra gold/XP granted while a curse is active on the stage, as a fraction (0.15 = +15%).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); } public override void OnStageStart(Stage stage) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) if (gravityStored) { Physics.gravity = baseGravity; gravityStored = false; } active = Curse.None; if (!NetworkServer.active || (Object)(object)Run.instance == (Object)null) { return; } int stageClearCount = Run.instance.stageClearCount; if (stageClearCount + 1 < StartsOnStage.Value) { DebugLog($"Stage {stageClearCount + 1} is before CurseStartsOnStage ({StartsOnStage.Value}) - skipping curse roll."); return; } float num = CurseChance.Value + CurseChanceScaling.Value * (float)stageClearCount; double num2 = rng.NextDouble() * 100.0; if (num2 > (double)num) { DebugLog($"Curse roll missed: {num2:0.0} vs {num:0.0}% chance - stage stays calm."); return; } active = (Curse)(1 + rng.Next(6)); DebugLog($"Curse drawn: {active} (roll {num2:0.0} vs {num:0.0}% chance, +{RewardPerCurse.Value:P0} rewards this stage)"); if (active == Curse.GravityFailure) { if (!gravityStored) { baseGravity = Physics.gravity; gravityStored = true; } Physics.gravity = baseGravity * 0.5f; DebugLog($"GravityFailure: gravity set to {Physics.gravity} (base was {baseGravity})"); } if (NotificationsModule.Gate) { Chat.AddMessage($"CURSE DRAWN: {active} (+{RewardPerCurse.Value:P0} rewards)"); } } public override void OnBodyStart(CharacterBody self) { //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) if (!NetworkServer.active || !ModuleBase.IsHostileEnemy(self)) { return; } ModelLocator modelLocator = self.modelLocator; if (active == Curse.TinyTerror && Object.op_Implicit((Object)(object)modelLocator) && Object.op_Implicit((Object)(object)modelLocator.modelTransform)) { ModuleBase.ApplyAbsoluteScale(((Component)modelLocator.modelTransform).gameObject, 0.6f); DebugLog("TinyTerror: shrank " + ((Object)self).name + " to 0.6x model scale"); } else if (active == Curse.ColossalThreat && rng.NextDouble() < 0.18) { ((Component)self).gameObject.AddComponent(); if (Object.op_Implicit((Object)(object)modelLocator) && Object.op_Implicit((Object)(object)modelLocator.modelTransform)) { ModuleBase.ApplyAbsoluteScale(((Component)modelLocator.modelTransform).gameObject, 1.7f); } self.RecalculateStats(); if (Object.op_Implicit((Object)(object)self.healthComponent)) { self.healthComponent.HealFraction(1f, default(ProcChainMask)); } DebugLog("ColossalThreat: " + ((Object)self).name + " rolled giant (18% chance) - scaled to 1.7x, marked, stats recalculated, healed to full"); } } public override void OnRecalcStats(CharacterBody self) { if (active != Curse.None && ModuleBase.IsHostileEnemy(self)) { switch (active) { case Curse.TinyTerror: self.moveSpeed *= 1.4f; self.maxHealth *= 0.85f; DebugLog("TinyTerror stats on " + ((Object)self).name + ": moveSpeed x1.4, maxHealth x0.85"); break; case Curse.BloodMoon: self.attackSpeed *= 1.3f; self.moveSpeed *= 1.25f; DebugLog("BloodMoon stats on " + ((Object)self).name + ": attackSpeed x1.3, moveSpeed x1.25"); break; case Curse.GreedStage: self.damage *= 1.2f; self.maxHealth *= 1.2f; DebugLog("GreedStage stats on " + ((Object)self).name + ": damage x1.2, maxHealth x1.2"); break; } if (active == Curse.ColossalThreat && Object.op_Implicit((Object)(object)((Component)self).GetComponent())) { self.maxHealth *= 1.8f; self.damage *= 1.3f; self.moveSpeed *= 0.85f; DebugLog("ColossalThreat marked stats on " + ((Object)self).name + ": maxHealth x1.8, damage x1.3, moveSpeed x0.85"); } } } public override void OnProjectileStart(ProjectileController proj) { if (active != Curse.BulletStorm || !Object.op_Implicit((Object)(object)proj.owner)) { return; } CharacterBody component = proj.owner.GetComponent(); if (Object.op_Implicit((Object)(object)component) && ModuleBase.IsHostileEnemy(component)) { ModuleBase.ApplyAbsoluteScale(((Component)proj).gameObject, 1.8f); if (Object.op_Implicit((Object)(object)proj.ghost)) { ModuleBase.ApplyAbsoluteScale(((Component)proj.ghost).gameObject, 1.8f); } ProjectileSimple component2 = ((Component)proj).GetComponent(); if (Object.op_Implicit((Object)(object)component2)) { component2.desiredForwardSpeed *= 0.8f; } ProjectileDamage component3 = ((Component)proj).GetComponent(); if (Object.op_Implicit((Object)(object)component3)) { component3.damage *= 1.25f; } DebugLog("BulletStorm: enlarged projectile from " + ((Object)component).name + " (scale x1.8, speed x0.8, damage x1.25)"); } } public override float ModifyHeal(HealthComponent target, float amount) { if (active == Curse.BloodMoon && Object.op_Implicit((Object)(object)target.body) && ModuleBase.IsPlayer(target.body)) { float num = amount * 0.6f; DebugLog($"BloodMoon: healing on {((Object)target.body).name} reduced {amount:0.0} -> {num:0.0}"); return num; } return amount; } public override float RewardMultiplier(DamageReport report) { if (active == Curse.None) { return 1f; } float num = 1f + RewardPerCurse.Value; if (active == Curse.BloodMoon || active == Curse.GreedStage) { num += 0.15f; } if (active == Curse.ColossalThreat && Object.op_Implicit((Object)(object)report?.victimBody) && Object.op_Implicit((Object)(object)((Component)report.victimBody).GetComponent())) { num += 0.5f; } object arg = active; object arg2 = num; object obj; if (report == null) { obj = null; } else { CharacterBody victimBody = report.victimBody; obj = ((victimBody != null) ? ((Object)victimBody).name : null); } if (obj == null) { obj = "unknown"; } DebugLog($"RewardMultiplier: {arg} curse -> x{arg2:0.00} rewards for kill of {obj}"); return num; } } internal class HazardsModule : ModuleBase { public ConfigEntry HazardChance; public ConfigEntry HazardChanceScaling; public ConfigEntry FogBankChance; public ConfigEntry FogBankChanceScaling; public ConfigEntry FogBankDuration; public ConfigEntry FogBankViewDistance; public ConfigEntry DarknessChance; public ConfigEntry DarknessChanceScaling; public ConfigEntry DarknessDuration; public ConfigEntry DarknessIntensity; public ConfigEntry DarknessPlayerLight; public ConfigEntry DarknessLightRange; public ConfigEntry DarknessLightIntensity; public ConfigEntry OverloadChance; public ConfigEntry OverloadChanceScaling; public ConfigEntry OverloadDuration; public ConfigEntry OverloadIntensity; private static readonly Random rng = new Random(); private static GameObject meteorStormPrefab; private bool fogActive; private float fogEndTime; private bool baseFogEnabled; private float baseFogDensity; private Color baseFogColor; private FogMode baseFogMode; private float baseFogStart; private float baseFogEnd; private bool fogStored; private readonly List<(PostProcessLayer layer, bool wasEnabled, bool wasExcludeSkybox)> fogLayersTouched = new List<(PostProcessLayer, bool, bool)>(); private bool darknessActive; private float darknessEndTime; private GameObject darknessVolumeObject; private GameObject darknessLightObject; private bool overloadActive; private float overloadEndTime; private GameObject overloadVolumeObject; public override string Name => "Environmental Hazards"; public override string StandaloneGuid => "dileppy.environmentalhazards"; public override int Stars => 3; public override bool UsesStageStart => true; public override bool UsesRunStart => true; public override bool UsesFixedUpdate => true; public override void Bind(ConfigFile cfg) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Expected O, but got Unknown //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Expected O, but got Unknown //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Expected O, but got Unknown //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Expected O, but got Unknown //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Expected O, but got Unknown //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Expected O, but got Unknown //IL_0247: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Expected O, but got Unknown //IL_029c: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Expected O, but got Unknown //IL_02d5: Unknown result type (might be due to invalid IL or missing references) //IL_02df: Expected O, but got Unknown //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_0318: Expected O, but got Unknown //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_0351: Expected O, but got Unknown //IL_0380: Unknown result type (might be due to invalid IL or missing references) //IL_038a: Expected O, but got Unknown //IL_03b9: Unknown result type (might be due to invalid IL or missing references) //IL_03c3: Expected O, but got Unknown Enabled = cfg.Bind("Hazards", "Enabled", false, "Stages have a chance to trigger a real Glowing Meteorite-style meteor storm, a Fog Bank that cuts your view distance, Darkness that dims the whole scene, or Overload that blows out the light and fringes your vision. Rolled independently, so more than one can land on the same stage."); HazardChance = cfg.Bind("Hazards", "MeteorShowerChancePerStage", 30f, new ConfigDescription("Chance (percent) that a stage triggers a meteor storm when you arrive.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); HazardChanceScaling = cfg.Bind("Hazards", "MeteorShowerChanceScalingPerStage", 0f, new ConfigDescription("Percent added to the meteor storm chance for every stage cleared. 0 = no scaling (the chance stays flat).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 20f), Array.Empty())); FogBankChance = cfg.Bind("Hazards", "FogBankChancePerStage", 30f, new ConfigDescription("Chance (percent) that a stage triggers a Fog Bank when you arrive, rolled independently of the other hazards.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); FogBankChanceScaling = cfg.Bind("Hazards", "FogBankChanceScalingPerStage", 0f, new ConfigDescription("Percent added to the Fog Bank chance for every stage cleared. 0 = no scaling (the chance stays flat).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 20f), Array.Empty())); FogBankDuration = cfg.Bind("Hazards", "FogBankDurationSeconds", 60f, new ConfigDescription("How long a Fog Bank lasts once triggered, in seconds.", (AcceptableValueBase)(object)new AcceptableValueRange(15f, 300f), Array.Empty())); FogBankViewDistance = cfg.Bind("Hazards", "FogBankViewDistance", 25f, new ConfigDescription("How far you can see, in meters, during a Fog Bank. Lower is harder to see.", (AcceptableValueBase)(object)new AcceptableValueRange(8f, 60f), Array.Empty())); DarknessChance = cfg.Bind("Hazards", "DarknessChancePerStage", 30f, new ConfigDescription("Chance (percent) that a stage triggers Darkness when you arrive, rolled independently of the other hazards.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); DarknessChanceScaling = cfg.Bind("Hazards", "DarknessChanceScalingPerStage", 0f, new ConfigDescription("Percent added to the Darkness chance for every stage cleared. 0 = no scaling (the chance stays flat).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 20f), Array.Empty())); DarknessDuration = cfg.Bind("Hazards", "DarknessDurationSeconds", 60f, new ConfigDescription("How long Darkness lasts once triggered, in seconds.", (AcceptableValueBase)(object)new AcceptableValueRange(15f, 300f), Array.Empty())); DarknessIntensity = cfg.Bind("Hazards", "DarknessIntensity", 0.5f, new ConfigDescription("How dark Darkness gets, from 0 (barely noticeable) to 1 (near black). Start moderate - Fog Bank's first pass at full intensity read as a total whiteout.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); DarknessPlayerLight = cfg.Bind("Hazards", "DarknessPlayerLight", true, "Attach a real light source to you while Darkness is active, so your immediate surroundings stay readable even at high DarknessIntensity. Independent of the screen-darkening effect - this is an actual light in the scene, not a post-process trick."); DarknessLightRange = cfg.Bind("Hazards", "DarknessLightRange", 12f, new ConfigDescription("How far your personal light reaches, in meters, while Darkness is active.", (AcceptableValueBase)(object)new AcceptableValueRange(4f, 30f), Array.Empty())); DarknessLightIntensity = cfg.Bind("Hazards", "DarknessLightIntensity", 2f, new ConfigDescription("Brightness of your personal light while Darkness is active.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 8f), Array.Empty())); OverloadChance = cfg.Bind("Hazards", "OverloadChancePerStage", 30f, new ConfigDescription("Chance (percent) that a stage triggers Overload when you arrive, rolled independently of the other hazards.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); OverloadChanceScaling = cfg.Bind("Hazards", "OverloadChanceScalingPerStage", 0f, new ConfigDescription("Percent added to the Overload chance for every stage cleared. 0 = no scaling (the chance stays flat).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 20f), Array.Empty())); OverloadDuration = cfg.Bind("Hazards", "OverloadDurationSeconds", 60f, new ConfigDescription("How long Overload lasts once triggered, in seconds.", (AcceptableValueBase)(object)new AcceptableValueRange(15f, 300f), Array.Empty())); OverloadIntensity = cfg.Bind("Hazards", "OverloadIntensity", 0.5f, new ConfigDescription("How intense Overload gets, from 0 (barely noticeable) to 1 (blown out and fringing hard). Start moderate, same lesson as Darkness.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); } public override void OnRunStart() { if (fogStored) { RestoreFog(); fogStored = false; } fogActive = false; EndDarkness(); EndOverload(); } public override void OnStageStart(Stage stage) { if (fogActive) { RestoreFog(); fogActive = false; } EndDarkness(); EndOverload(); if (NetworkServer.active) { TryMeteorStorm(); TryFogBank(); TryDarkness(); TryOverload(); } } private static float ScaledChance(ConfigEntry baseChance, ConfigEntry perStage) { int num = (Object.op_Implicit((Object)(object)Run.instance) ? Run.instance.stageClearCount : 0); return baseChance.Value + perStage.Value * (float)num; } private static int FindMatchingVolumeLayer() { PostProcessLayer[] array = Object.FindObjectsOfType(); if (array.Length == 0) { return -1; } int value = ((LayerMask)(ref array[0].volumeLayer)).value; for (int i = 0; i < 32; i++) { if ((value & (1 << i)) != 0) { return i; } } return -1; } private void TryMeteorStorm() { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) double num = rng.NextDouble() * 100.0; float num2 = ScaledChance(HazardChance, HazardChanceScaling); if (num > (double)num2) { DebugLog($"Meteor Storm roll missed: {num:0.0} vs {num2:0.0}% chance - no meteor storm this stage."); return; } try { if (!Object.op_Implicit((Object)(object)meteorStormPrefab)) { meteorStormPrefab = LegacyResourcesAPI.Load("Prefabs/NetworkedObjects/MeteorStorm"); } if (!Object.op_Implicit((Object)(object)meteorStormPrefab)) { PetrichorProtocolPlugin.Log.LogWarning((object)(Name + ": could not find the vanilla MeteorStorm prefab in this game version - skipping.")); return; } Vector3 val = Vector3.zero; ReadOnlyCollection instances = PlayerCharacterMasterController.instances; if (instances != null && instances.Count > 0 && Object.op_Implicit((Object)(object)instances[0]) && Object.op_Implicit((Object)(object)instances[0].master)) { CharacterBody body = instances[0].master.GetBody(); if (Object.op_Implicit((Object)(object)body)) { val = body.corePosition; } } NetworkServer.Spawn(Object.Instantiate(meteorStormPrefab, val, Quaternion.identity)); DebugLog($"Meteor Storm triggered: roll {num:0.0} vs {num2:0.0}% chance, spawned vanilla MeteorStorm prefab at {val}"); if (NotificationsModule.Gate) { Chat.AddMessage("METEOR STORM - the sky is falling. Watch for the rings."); } } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"{Name}: failed to trigger meteor storm: {arg}"); } } private void TryFogBank() { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) double num = rng.NextDouble() * 100.0; float num2 = ScaledChance(FogBankChance, FogBankChanceScaling); if (num > (double)num2) { DebugLog($"Fog Bank roll missed: {num:0.0} vs {num2:0.0}% chance - no fog this stage."); return; } try { if (!fogStored) { baseFogEnabled = RenderSettings.fog; baseFogDensity = RenderSettings.fogDensity; baseFogColor = RenderSettings.fogColor; baseFogMode = RenderSettings.fogMode; baseFogStart = RenderSettings.fogStartDistance; baseFogEnd = RenderSettings.fogEndDistance; fogStored = true; } RenderSettings.fog = true; RenderSettings.fogMode = (FogMode)3; RenderSettings.fogColor = new Color(0.55f, 0.58f, 0.62f); RenderSettings.fogDensity = 1f / Mathf.Max(1f, FogBankViewDistance.Value); fogLayersTouched.Clear(); PostProcessLayer[] array = Object.FindObjectsOfType(); foreach (PostProcessLayer val in array) { if (Object.op_Implicit((Object)(object)val)) { fogLayersTouched.Add((val, val.fog.enabled, val.fog.excludeSkybox)); val.fog.enabled = true; val.fog.excludeSkybox = false; } } fogActive = true; fogEndTime = Time.time + FogBankDuration.Value; DebugLog($"Fog Bank triggered: roll {num:0.0} vs {num2:0.0}% chance, view distance {FogBankViewDistance.Value:0.0}m for {FogBankDuration.Value:0.0}s, {fogLayersTouched.Count} PostProcessLayer(s) gated on"); if (NotificationsModule.Gate) { Chat.AddMessage("FOG BANK - a heavy fog rolls in. You can barely see past your own feet."); } } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"{Name}: failed to trigger fog bank: {arg}"); } } private void TryDarkness() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Expected O, but got Unknown //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) double num = rng.NextDouble() * 100.0; float num2 = ScaledChance(DarknessChance, DarknessChanceScaling); if (num > (double)num2) { DebugLog($"Darkness roll missed: {num:0.0} vs {num2:0.0}% chance - no darkness this stage."); return; } try { darknessVolumeObject = new GameObject("PetrichorDarknessVolume"); int num3 = FindMatchingVolumeLayer(); if (num3 >= 0) { darknessVolumeObject.layer = num3; } PostProcessVolume obj = darknessVolumeObject.AddComponent(); obj.isGlobal = true; obj.priority = 100f; obj.weight = 1f; PostProcessProfile val = (obj.profile = ScriptableObject.CreateInstance()); float num4 = Mathf.Clamp01(DarknessIntensity.Value); ColorGrading val3 = val.AddSettings(); ((PostProcessEffectSettings)val3).active = true; ((ParameterOverride)val3.postExposure).overrideState = true; ((ParameterOverride)(object)val3.postExposure).value = -2.5f * num4; ((ParameterOverride)val3.contrast).overrideState = true; ((ParameterOverride)(object)val3.contrast).value = 15f * num4; Vignette val4 = val.AddSettings(); ((PostProcessEffectSettings)val4).active = true; ((ParameterOverride)val4.intensity).overrideState = true; ((ParameterOverride)(object)val4.intensity).value = 0.6f * num4; ((ParameterOverride)val4.color).overrideState = true; ((ParameterOverride)(object)val4.color).value = Color.black; if (DarknessPlayerLight.Value) { LocalUser firstLocalUser = LocalUserManager.GetFirstLocalUser(); CharacterBody val5 = ((firstLocalUser != null) ? firstLocalUser.cachedBody : null); if (Object.op_Implicit((Object)(object)val5)) { darknessLightObject = new GameObject("PetrichorDarknessLight"); darknessLightObject.transform.SetParent(val5.transform, false); darknessLightObject.transform.localPosition = Vector3.up * 1.5f; Light obj2 = darknessLightObject.AddComponent(); obj2.type = (LightType)2; obj2.range = DarknessLightRange.Value; obj2.intensity = DarknessLightIntensity.Value; obj2.color = new Color(1f, 0.92f, 0.75f); obj2.renderMode = (LightRenderMode)1; obj2.shadows = (LightShadows)0; DebugLog($"Darkness: attached player light on {((Object)val5).name} (range {DarknessLightRange.Value:0.0}m, intensity {DarknessLightIntensity.Value:0.0})"); } else { DebugLog("Darkness: DarknessPlayerLight enabled but no local body found - no light attached."); } } darknessActive = true; darknessEndTime = Time.time + DarknessDuration.Value; DebugLog($"Darkness triggered: roll {num:0.0} vs {num2:0.0}% chance, intensity {num4:0.00} (postExposure {((ParameterOverride)(object)val3.postExposure).value:0.00}, contrast {((ParameterOverride)(object)val3.contrast).value:0.0}, vignette {((ParameterOverride)(object)val4.intensity).value:0.00}) for {DarknessDuration.Value:0.0}s"); if (NotificationsModule.Gate) { Chat.AddMessage("DARKNESS - the light is fading fast."); } } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"{Name}: failed to trigger darkness: {arg}"); } } private void EndDarkness() { if (Object.op_Implicit((Object)(object)darknessVolumeObject)) { Object.Destroy((Object)(object)darknessVolumeObject); } darknessVolumeObject = null; if (Object.op_Implicit((Object)(object)darknessLightObject)) { Object.Destroy((Object)(object)darknessLightObject); } darknessLightObject = null; darknessActive = false; } private void TryOverload() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) double num = rng.NextDouble() * 100.0; float num2 = ScaledChance(OverloadChance, OverloadChanceScaling); if (num > (double)num2) { DebugLog($"Overload roll missed: {num:0.0} vs {num2:0.0}% chance - no overload this stage."); return; } try { overloadVolumeObject = new GameObject("PetrichorOverloadVolume"); int num3 = FindMatchingVolumeLayer(); if (num3 >= 0) { overloadVolumeObject.layer = num3; } PostProcessVolume obj = overloadVolumeObject.AddComponent(); obj.isGlobal = true; obj.priority = 100f; obj.weight = 1f; PostProcessProfile val = (obj.profile = ScriptableObject.CreateInstance()); float num4 = Mathf.Clamp01(OverloadIntensity.Value); Bloom val3 = val.AddSettings(); ((PostProcessEffectSettings)val3).active = true; ((ParameterOverride)val3.intensity).overrideState = true; ((ParameterOverride)(object)val3.intensity).value = 3f * num4; ((ParameterOverride)val3.threshold).overrideState = true; ((ParameterOverride)(object)val3.threshold).value = Mathf.Lerp(1.1f, 0.6f, num4); ((ParameterOverride)val3.color).overrideState = true; ((ParameterOverride)(object)val3.color).value = new Color(1f, 0.55f, 0.45f); ChromaticAberration val4 = val.AddSettings(); ((PostProcessEffectSettings)val4).active = true; ((ParameterOverride)val4.intensity).overrideState = true; ((ParameterOverride)(object)val4.intensity).value = 0.4f * num4; overloadActive = true; overloadEndTime = Time.time + OverloadDuration.Value; DebugLog($"Overload triggered: roll {num:0.0} vs {num2:0.0}% chance, intensity {num4:0.00} (bloom {((ParameterOverride)(object)val3.intensity).value:0.00}, threshold {((ParameterOverride)(object)val3.threshold).value:0.00}, chromatic aberration {((ParameterOverride)(object)val4.intensity).value:0.00}) for {OverloadDuration.Value:0.0}s"); if (NotificationsModule.Gate) { Chat.AddMessage("OVERLOAD - the world burns too bright to look at directly."); } } catch (Exception arg) { PetrichorProtocolPlugin.Log.LogError((object)$"{Name}: failed to trigger overload: {arg}"); } } private void EndOverload() { if (Object.op_Implicit((Object)(object)overloadVolumeObject)) { Object.Destroy((Object)(object)overloadVolumeObject); } overloadVolumeObject = null; overloadActive = false; } public override void OnFixedUpdateServer() { if (fogActive && Time.time >= fogEndTime) { RestoreFog(); fogActive = false; DebugLog("Fog Bank expired: RenderSettings and PostProcessLayer.fog restored to their pre-fog values."); if (NotificationsModule.Gate) { Chat.AddMessage("The fog lifts."); } } if (darknessActive && Time.time >= darknessEndTime) { EndDarkness(); DebugLog("Darkness expired: darkness volume and player light destroyed."); if (NotificationsModule.Gate) { Chat.AddMessage("The darkness lifts."); } } if (overloadActive && Time.time >= overloadEndTime) { EndOverload(); DebugLog("Overload expired: overload volume destroyed."); if (NotificationsModule.Gate) { Chat.AddMessage("The overload fades."); } } } private void RestoreFog() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) RenderSettings.fog = baseFogEnabled; RenderSettings.fogDensity = baseFogDensity; RenderSettings.fogColor = baseFogColor; RenderSettings.fogMode = baseFogMode; RenderSettings.fogStartDistance = baseFogStart; RenderSettings.fogEndDistance = baseFogEnd; foreach (var (val, enabled, excludeSkybox) in fogLayersTouched) { if (Object.op_Implicit((Object)(object)val)) { val.fog.enabled = enabled; val.fog.excludeSkybox = excludeSkybox; } } fogLayersTouched.Clear(); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "PetrichorProtocol"; public const string PLUGIN_NAME = "PetrichorProtocol"; public const string PLUGIN_VERSION = "2.16.0"; } }