Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Configurable Quota v1.3.1
BepInEx/plugins/ConfigurableQuota/ConfigurableQuota.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using ConfigurableQuota.Compat; using ConfigurableQuota.Patches; using GameNetcodeStuff; using HarmonyLib; using LethalNetworkAPI; using Microsoft.CodeAnalysis; using OpenLib.Events; using TMPro; using Unity.Netcode; using UnityEngine; [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("ConfigurableQuota")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Allows users to configure every aspect of the quota the way they want.")] [assembly: AssemblyFileVersion("1.3.1.0")] [assembly: AssemblyInformationalVersion("1.3.1+3ee64c39f75175f518fabb3c1dd67599cd02b9c0")] [assembly: AssemblyProduct("ConfigurableQuota")] [assembly: AssemblyTitle("ConfigurableQuota")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.3.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.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 ConfigurableQuota { public static class ConfigManager { public static ConfigEntry<int> StartingCredits; public static ConfigEntry<int> StartingQuota; public static ConfigEntry<int> DaysToDeadline; public static ConfigEntry<int> BaseIncrease; public static ConfigEntry<float> CurveSharpness; public static ConfigEntry<float> RandomizerMultiplier; public static ConfigEntry<int> FinalLevel; public static ConfigEntry<int> FinalIncrease; public static ConfigEntry<int> QuotaCap; public static ConfigEntry<bool> EnablePlayerMultiplier; public static ConfigEntry<int> PlayerThreshold; public static ConfigEntry<int> PlayerCap; public static ConfigEntry<float> MultPerPlayer; public static ConfigEntry<bool> DisableQuota; public static ConfigEntry<float> RolloverAmount; public static ConfigEntry<bool> CreditPenaltiesEnabled; public static ConfigEntry<bool> CreditPenaltiesOnGordion; public static ConfigEntry<float> CreditPenaltyPercentPerPlayer; public static ConfigEntry<bool> CreditPenaltiesDynamic; public static ConfigEntry<float> CreditPenaltyPercentCap; public static ConfigEntry<float> CreditPenaltyPercentThreshold; public static ConfigEntry<float> CreditPenaltyRecoveryBonus; public static ConfigEntry<bool> QuotaPenaltiesEnabled; public static ConfigEntry<bool> QuotaPenaltiesOnGordion; public static ConfigEntry<float> QuotaPenaltyPercentPerPlayer; public static ConfigEntry<bool> QuotaPenaltiesDynamic; public static ConfigEntry<float> QuotaPenaltyPercentCap; public static ConfigEntry<float> QuotaPenaltyPercentThreshold; public static ConfigEntry<float> QuotaPenaltyRecoveryBonus; public static ConfigEntry<bool> ScrapLossEnabled; public static ConfigEntry<float> ItemsSafeChance; public static ConfigEntry<float> LoseEachScrapChance; public static ConfigEntry<int> MaxLostScrapItems; public static ConfigEntry<bool> ValueLossEnabled; public static ConfigEntry<float> ValueLossPercent; public static ConfigEntry<bool> RandomizeDeadline; public static ConfigEntry<int> DeadlineMin; public static ConfigEntry<int> DeadlineMax; public static ConfigEntry<bool> DeadlineMustChange; public static ConfigEntry<bool> EnableGrowthDampening; public static ConfigEntry<int> DampeningStartAt; public static ConfigEntry<float> DampeningSharpness; public static ConfigEntry<bool> EquipmentLossEnabled; public static ConfigEntry<float> LoseEachEquipmentChance; public static ConfigEntry<int> MaxLostEquipmentItems; public static ConfigEntry<float> QuotaAnimationSpeed; public static ConfigEntry<bool> MinMaxRateEnabled; public static ConfigEntry<float> MinRate; public static ConfigEntry<float> MaxRate; public static ConfigEntry<bool> RandomRateEnabled; public static ConfigEntry<bool> LastDayRateEnabled; public static ConfigEntry<float> LastDayRangeChance; public static ConfigEntry<float> LastDayMinRate; public static ConfigEntry<float> LastDayMaxRate; public static ConfigEntry<bool> JackpotEnabled; public static ConfigEntry<bool> JackpotLastDayOnly; public static ConfigEntry<float> JackpotChance; public static ConfigEntry<float> JackpotMinRate; public static ConfigEntry<float> JackpotMaxRate; public static ConfigEntry<bool> BuyRateAlertEnabled; public static ConfigEntry<bool> JackpotAlertEnabled; public static ConfigEntry<float> AlertDelaySeconds; public static ConfigEntry<bool> DynamicInteriorSizeEnabled; public static ConfigEntry<float> DynamicInteriorSizeBase; public static ConfigEntry<int> DynamicInteriorSizePlayerThreshold; public static ConfigEntry<PlayerScalingDirection> DynamicInteriorSizeDirection; public static ConfigEntry<float> DynamicInteriorSizeMultPerPlayer; public static ConfigEntry<bool> DynamicScrapValueEnabled; public static ConfigEntry<int> DynamicScrapValueOffset; public static ConfigEntry<float> DynamicScrapValueMinMult; public static ConfigEntry<float> DynamicScrapValueMaxMult; public static ConfigEntry<int> DynamicScrapValuePlayerThreshold; public static ConfigEntry<PlayerScalingDirection> DynamicScrapValueDirection; public static ConfigEntry<float> DynamicScrapValueMultPerPlayer; public static ConfigEntry<bool> DynamicScrapAmountEnabled; public static ConfigEntry<int> DynamicScrapAmountValuePerItem; public static ConfigEntry<float> DynamicScrapAmountMinFraction; public static ConfigEntry<int> DynamicScrapAmountCap; public static ConfigEntry<int> DynamicScrapAmountPlayerThreshold; public static ConfigEntry<PlayerScalingDirection> DynamicScrapAmountDirection; public static ConfigEntry<float> DynamicScrapAmountMultPerPlayer; public static ConfigEntry<bool> DynamicEnemyPowerEnabled; public static ConfigEntry<bool> DynamicEnemyPowerScaleInside; public static ConfigEntry<bool> DynamicEnemyPowerScaleOutside; public static ConfigEntry<bool> DynamicEnemyPowerScaleDaytime; public static ConfigEntry<int> DynamicEnemyPowerPlayerThreshold; public static ConfigEntry<float> DynamicEnemyPowerMultPerPlayer; public static ConfigEntry<float> DynamicEnemyPowerMaxFactor; internal static void Initialize(ConfigFile config) { //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Expected O, but got Unknown //IL_0467: Unknown result type (might be due to invalid IL or missing references) //IL_0471: Expected O, but got Unknown //IL_049f: Unknown result type (might be due to invalid IL or missing references) //IL_04a9: Expected O, but got Unknown //IL_050d: Unknown result type (might be due to invalid IL or missing references) //IL_0517: Expected O, but got Unknown //IL_0560: Unknown result type (might be due to invalid IL or missing references) //IL_056a: Expected O, but got Unknown //IL_05b3: Unknown result type (might be due to invalid IL or missing references) //IL_05bd: Expected O, but got Unknown //IL_0606: Unknown result type (might be due to invalid IL or missing references) //IL_0610: Expected O, but got Unknown //IL_063e: Unknown result type (might be due to invalid IL or missing references) //IL_0648: Expected O, but got Unknown //IL_06ac: Unknown result type (might be due to invalid IL or missing references) //IL_06b6: Expected O, but got Unknown //IL_06e4: Unknown result type (might be due to invalid IL or missing references) //IL_06ee: Expected O, but got Unknown //IL_071c: Unknown result type (might be due to invalid IL or missing references) //IL_0726: Expected O, but got Unknown //IL_078a: Unknown result type (might be due to invalid IL or missing references) //IL_0794: Expected O, but got Unknown //IL_07c2: Unknown result type (might be due to invalid IL or missing references) //IL_07cc: Expected O, but got Unknown //IL_07fa: Unknown result type (might be due to invalid IL or missing references) //IL_0804: Expected O, but got Unknown //IL_0868: Unknown result type (might be due to invalid IL or missing references) //IL_0872: Expected O, but got Unknown //IL_08bb: Unknown result type (might be due to invalid IL or missing references) //IL_08c5: Expected O, but got Unknown //IL_08e8: Unknown result type (might be due to invalid IL or missing references) //IL_08f2: Expected O, but got Unknown //IL_093b: Unknown result type (might be due to invalid IL or missing references) //IL_0945: Expected O, but got Unknown //IL_09a9: Unknown result type (might be due to invalid IL or missing references) //IL_09b3: Expected O, but got Unknown //IL_09e1: Unknown result type (might be due to invalid IL or missing references) //IL_09eb: Expected O, but got Unknown //IL_0a0e: Unknown result type (might be due to invalid IL or missing references) //IL_0a18: Expected O, but got Unknown //IL_0a61: Unknown result type (might be due to invalid IL or missing references) //IL_0a6b: Expected O, but got Unknown //IL_0aad: Unknown result type (might be due to invalid IL or missing references) //IL_0ab7: Expected O, but got Unknown //IL_0ae5: Unknown result type (might be due to invalid IL or missing references) //IL_0aef: Expected O, but got Unknown //IL_0b2d: Unknown result type (might be due to invalid IL or missing references) //IL_0b37: Expected O, but got Unknown //IL_0b80: Unknown result type (might be due to invalid IL or missing references) //IL_0b8a: Expected O, but got Unknown //IL_0c19: Unknown result type (might be due to invalid IL or missing references) //IL_0c23: Expected O, but got Unknown //IL_0c51: Unknown result type (might be due to invalid IL or missing references) //IL_0c5b: Expected O, but got Unknown //IL_0c89: Unknown result type (might be due to invalid IL or missing references) //IL_0c93: Expected O, but got Unknown StartingCredits = config.Bind<int>("0. Basic", "StartingCredits", 60, "Starting credits for a new lobby."); StartingQuota = config.Bind<int>("0. Basic", "StartingQuota", 130, "Starting quota for a new lobby."); DaysToDeadline = config.Bind<int>("0. Basic", "DaysToDeadline", 3, "Number of days to meet each quota. Ignored if RandomizeDeadline is enabled."); RandomizeDeadline = config.Bind<bool>("0. Basic", "RandomizeDeadline", false, "Randomize the deadline each quota using Deadline Min/Max instead of a fixed Days To Deadline."); DeadlineMin = config.Bind<int>("0. Basic", "DeadlineMin", 3, "Minimum days for the deadline. REQUIRES 'Randomize Deadline' SET TO TRUE."); DeadlineMax = config.Bind<int>("0. Basic", "DeadlineMax", 5, "Maximum days for the deadline. REQUIRES 'Randomize Deadline' SET TO TRUE."); DeadlineMustChange = config.Bind<bool>("0. Basic", "DeadlineMustChange", true, "After fulfilling a quota, next random deadline has to be different from the previous one. REQUIRES 'Randomize Deadline' SET TO TRUE."); BaseIncrease = config.Bind<int>("0. Basic", "BaseIncrease", 100, "Base amount the quota increase per each quota. Combined with CurveSharpness as: increase ≈ BaseIncrease * (1 + quota² / Sharpness). With defaults (100, 16) at quota 3, that's 100 * (1 + 9/16) ≈ 156."); CurveSharpness = config.Bind<float>("0. Basic", "CurveSharpness", 16f, "Controls how fast the quota ramps up. Higher = slower growth. Formula: increase ≈ BaseIncrease * (1 + quota² / Sharpness). With BaseIncrease=100, Sharpness=16, quota 5 -> 100 * (1 + 25/16) ≈ 256."); RandomizerMultiplier = config.Bind<float>("0. Basic", "RandomizerMultiplier", 1f, "Random variance on each quota increase. Multiplies by a factor in [1 - 0.5*M, 1 + 0.5*M]. 0 = no randomness, 1 = ±50% (vanilla), 2 = ±100%."); FinalLevel = config.Bind<int>("1. Leveling", "FinalLevel", -1, "Once the previous quota hits this value, growth stops using the curve and switches to a flat Final Increase per quota. With FinalLevel=10000 and FinalIncrease=200, every quota after $10000 just adds $200. Set -1 to disable."); FinalIncrease = config.Bind<int>("1. Leveling", "FinalIncrease", 200, "Fixed increase amount used after reaching Final Level value."); QuotaCap = config.Bind<int>("1. Leveling", "QuotaCap", -1, "Maximum quota value, quota will never increase more than this amount. Set -1 for no limit."); EnableGrowthDampening = config.Bind<bool>("1. Leveling", "EnableGrowthDampening", false, "Slows quota growth down after a while. After Dampening Start At fulfilled quotas, the curve increase gets divided by (1 + (excess / DampeningSharpness)²), where excess = currentQuota - DampeningStartAt."); DampeningStartAt = config.Bind<int>("1. Leveling", "DampeningStartAt", 6, "How many quotas you have to clear before dampening starts. 6 means quotas 1 - 6 are untouched and quota 7 is the first one slowed down."); DampeningSharpness = config.Bind<float>("1. Leveling", "DampeningSharpness", 11f, "Lower values dampen harder. At Start At=6, Sharpness=11, quota 10 -> divisor = 1 + (4/11)² ≈ 1.13, so growth shrinks by ~12%."); EnablePlayerMultiplier = config.Bind<bool>("2. Player.Scaling", "EnablePlayerMultiplier", false, "Scale each quota increase by lobby size. Formula: (1 + extra * MultPerPlayer), where extra = clamp(playerCount - PlayerThreshold, 0, PlayerCap - PlayerThreshold)."); PlayerThreshold = config.Bind<int>("2. Player.Scaling", "PlayerThreshold", 2, "Player count where the scaling starts. 2 means scaling starts at 3+ players."); PlayerCap = config.Bind<int>("2. Player.Scaling", "PlayerCap", 4, "Player count where scaling stops. 4 means player 5+ don't bump the multiplier further."); MultPerPlayer = config.Bind<float>("2. Player.Scaling", "MultPerPlayer", 0.25f, "Extra multiplier each player adds past the threshold. With 4 players, Threshold=2, Cap=4, MultPerPlayer=0.25, you get 1 + 2*0.25 = 1.5x on quota increases."); DisableQuota = config.Bind<bool>("3. Optional", "DisableQuota", false, "Completely disables the quota system."); RolloverAmount = config.Bind<float>("3. Optional", "RolloverAmount", 0f, new ConfigDescription("Percentage of extra scrap value that goes above the set limit and is added to the next quota. 0 = none (vanilla), 0.5 = 50%, 1.0 = 100%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); CreditPenaltiesEnabled = config.Bind<bool>("4. Penalties.Credits", "Enabled", false, "Reduce credits when crew members die."); CreditPenaltiesOnGordion = config.Bind<bool>("4. Penalties.Credits", "OnGordion", false, "Apply credit penalties even when visiting The Company."); CreditPenaltyPercentPerPlayer = config.Bind<float>("4. Penalties.Credits", "PercentPerPlayer", 0.15f, "Credits lost per dead player. 0.15 = lose 15% of credits per death. Ignored when Dynamic is set to true."); CreditPenaltiesDynamic = config.Bind<bool>("4. Penalties.Credits", "Dynamic", false, "Scale credit penalty by: (dead/total) * Percent Cap. With 2 dead out of 8 and Percent Cap=0.05 -> (2/8)*0.05 = 1.25%. When false, the penalty is Percent Per Player * dead, capped at Percent Cap."); CreditPenaltyPercentCap = config.Bind<float>("4. Penalties.Credits", "PercentCap", 0.8f, "Maximum percentage of credits that can be lost."); CreditPenaltyPercentThreshold = config.Bind<float>("4. Penalties.Credits", "PercentThreshold", 0f, "Ignore penalties below this percentage. 0.1 = penalties under 10% are not applied."); CreditPenaltyRecoveryBonus = config.Bind<float>("4. Penalties.Credits", "RecoveryBonus", 0f, "Reduce the penalty when body is recovered. Final = base * (1 - RecoveryBonus * recovered/dead). With 4 dead, 2 recovered, and RecoveryBonus=0.5 the penalty drops by 25% (base * 0.75)."); QuotaPenaltiesEnabled = config.Bind<bool>("5. Penalties.Quota", "Enabled", false, "Increase the current quota when crew members die."); QuotaPenaltiesOnGordion = config.Bind<bool>("5. Penalties.Quota", "OnGordion", false, "Apply quota penalties even when visiting The Company."); QuotaPenaltyPercentPerPlayer = config.Bind<float>("5. Penalties.Quota", "PercentPerPlayer", 0.1f, "Quota increase per dead player. 0.1 = +10% to current quota per death. Ignored when Dynamic is true."); QuotaPenaltiesDynamic = config.Bind<bool>("5. Penalties.Quota", "Dynamic", false, "Scale quota increase by: (dead/total) * Percent Cap. With 2 dead out of 8 and Percent Cap=0.5 -> (2/8)*0.5 = 12.5%. When false, the penalty is Percent Per Player * dead, capped at Percent Cap."); QuotaPenaltyPercentCap = config.Bind<float>("5. Penalties.Quota", "PercentCap", 0.5f, "Maximum percentage the quota can increase. 0.5 = quota can increase by at most 50%."); QuotaPenaltyPercentThreshold = config.Bind<float>("5. Penalties.Quota", "PercentThreshold", 0f, "Ignore penalties below this percentage. 0.15 = anything under 15% are not applied."); QuotaPenaltyRecoveryBonus = config.Bind<float>("5. Penalties.Quota", "RecoveryBonus", 0f, "Reduce the quota penalty when bodies are recovered. Final = base * (1 - RecoveryBonus * recovered/dead). With 4 dead, 2 recovered, Recovery Bonus=0.5 penalty shrinks by 25% (base * 0.75)."); ScrapLossEnabled = config.Bind<bool>("6. Loss.Scrap", "Enabled", false, "Randomly lose collected scrap when all crew dies."); ItemsSafeChance = config.Bind<float>("6. Loss.Scrap", "ItemsSafeChance", 0.5f, new ConfigDescription("Chance for each scrap to be protected. Combined with Lose Each Scrap Chance, actual loss chance is (1 - SafeChance) * LoseChance. So Safe=0.5, Lose=0.1 -> 5% per item.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); LoseEachScrapChance = config.Bind<float>("6. Loss.Scrap", "LoseEachScrapChance", 0.1f, new ConfigDescription("Chance for unprotected scrap to get lost. 0.2 means 20% on the items that weren't saved by Items Safe Chance.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); MaxLostScrapItems = config.Bind<int>("6. Loss.Scrap", "MaxLostScrapItems", 2, "Maximum scrap that can be lost per round."); ValueLossEnabled = config.Bind<bool>("7. Loss.Value", "Enabled", false, "Reduce the scrap value of all ship items when the entire crew is wiped."); ValueLossPercent = config.Bind<float>("7. Loss.Value", "Percent", 0.2f, new ConfigDescription("How much of the scrap value to reduce per crew wipe. 0.25 = each item loses 25%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); EquipmentLossEnabled = config.Bind<bool>("8. Loss.Equipment", "Enabled", false, "Randomly lose purchased equipment when all crew dies."); LoseEachEquipmentChance = config.Bind<float>("8. Loss.Equipment", "LoseEachEquipmentChance", 0.05f, new ConfigDescription("Chance for each equipment item to be lost. 0.1 = 10% per item.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); MaxLostEquipmentItems = config.Bind<int>("8. Loss.Equipment", "MaxLostEquipmentItems", 1, "Maximum equipment items that can be lost per round. 1 = at most one item is lost."); QuotaAnimationSpeed = config.Bind<float>("9. UI", "QuotaAnimationSpeed", 1f, new ConfigDescription("Speed multiplier for the new quota animation. Higher = faster.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 2f), Array.Empty<object>())); MinMaxRateEnabled = config.Bind<bool>("X. Buy.Rate", "MinMaxEnabled", false, "Keep the Company's daily buy rate between Min Rate and Max Rate. Random Rate Enabled needs to be true."); MinRate = config.Bind<float>("X. Buy.Rate", "MinRate", 0.2f, new ConfigDescription("Minimum buy rate the Company will pay. 0.2 = 20%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())); MaxRate = config.Bind<float>("X. Buy.Rate", "MaxRate", 1.2f, new ConfigDescription("Maximum buy rate the Company will pay. 1.2 = 120%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())); RandomRateEnabled = config.Bind<bool>("X. Buy.Rate", "RandomRateEnabled", false, "Roll a random buy rate between Min Rate and Max Rate every day. Needs Min Max Enabled. With MinRate=0.4 and MaxRate=1.5 the daily rate lands somewhere between 40% and 150%."); LastDayRateEnabled = config.Bind<bool>("X. Buy.Rate", "LastDayRateEnabled", false, "On the deadline's last day, force the rate through Last Day Min Rate and Last Day Max Rate. If min and max are equal you always get that rate. If they differ, roll Last Day Range Chance: a hit picks a random value in the range, a miss falls back to 100%."); LastDayRangeChance = config.Bind<float>("X. Buy.Rate", "LastDayRangeChance", 0.3f, new ConfigDescription("Chance the last-day rate actually uses the range instead of falling back to 100%. 0.3 = 30%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); LastDayMinRate = config.Bind<float>("X. Buy.Rate", "LastDayMinRate", 1f, new ConfigDescription("Minimum buy rate on the last day. 1.0 = 100%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())); LastDayMaxRate = config.Bind<float>("X. Buy.Rate", "LastDayMaxRate", 1.2f, new ConfigDescription("Maximum buy rate on the last day. 1.2 = 120%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())); JackpotEnabled = config.Bind<bool>("X. Buy.Rate", "JackpotEnabled", false, "Let the daily roll try for a jackpot rate. Checked before Last Day Rate or RandomRate. A hit picks a random value between JackpotMinRate and JackpotMaxRate (or that exact value if they match)."); JackpotLastDayOnly = config.Bind<bool>("X. Buy.Rate", "JackpotLastDayOnly", true, "Only roll for a jackpot on the deadline's last day."); JackpotChance = config.Bind<float>("X. Buy.Rate", "JackpotChance", 0.01f, new ConfigDescription("Chance the daily roll lands a jackpot. 0.01 = 1% per day. If Jackpot Last Day Only is true, this only fires on the last day.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); JackpotMinRate = config.Bind<float>("X. Buy.Rate", "JackpotMinRate", 1.5f, new ConfigDescription("Minimum jackpot rate. 1.5 = 150%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())); JackpotMaxRate = config.Bind<float>("X. Buy.Rate", "JackpotMaxRate", 3f, new ConfigDescription("Maximum jackpot rate. 3.0 = 300%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())); BuyRateAlertEnabled = config.Bind<bool>("X. Buy.Rate", "BuyRateAlertEnabled", false, "Show a yellow on-screen alert with the new buy rate each day. Local to this client."); JackpotAlertEnabled = config.Bind<bool>("X. Buy.Rate", "JackpotAlertEnabled", false, "Show a red SCRAP EMERGENCY alert with sound when a jackpot is rolled. Local to this client."); AlertDelaySeconds = config.Bind<float>("X. Buy.Rate", "AlertDelaySeconds", 3f, new ConfigDescription("How long to wait before the buy-rate alert pops up. 3s feels fine solo. Bump to 8+ if BetterEXP or DiscountAlerts step on it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>())); DynamicInteriorSizeEnabled = config.Bind<bool>("A. Dynamic.Interior.Size", "Enabled", false, "Resize the moons interior based on lobby size."); DynamicInteriorSizeBase = config.Bind<float>("A. Dynamic.Interior.Size", "BaseSize", 1f, new ConfigDescription("factorySizeMultiplier at the PlayerThreshold. 1.0 = vanilla size.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())); DynamicInteriorSizePlayerThreshold = config.Bind<int>("A. Dynamic.Interior.Size", "PlayerThreshold", 2, new ConfigDescription("Player count where scaling starts. With PerExtraPlayer it activates above this, with PerMissingPlayer it activates below.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 32), Array.Empty<object>())); DynamicInteriorSizeDirection = config.Bind<PlayerScalingDirection>("A. Dynamic.Interior.Size", "ScalingDirection", PlayerScalingDirection.PerExtraPlayer, "PerExtraPlayer: more players = bigger dungeon. PerMissingPlayer: fewer players = bigger dungeon."); DynamicInteriorSizeMultPerPlayer = config.Bind<float>("A. Dynamic.Interior.Size", "MultPerPlayer", 0.1f, new ConfigDescription("How much the size multiplier changes per qualifying player. threshold=2, PerExtraPlayer, this=0.1 -> 3 players get BaseSize * 1.1.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())); DynamicScrapValueEnabled = config.Bind<bool>("B. Dynamic.Scrap.Value", "Enabled", false, "Scale the moons min/max scrap value by player count."); DynamicScrapValueOffset = config.Bind<int>("B. Dynamic.Scrap.Value", "ScrapValueOffset", 0, "Flat credits added to both min and max after scaling. Leave at 0 to only use proportional scaling."); DynamicScrapValueMinMult = config.Bind<float>("B. Dynamic.Scrap.Value", "MinValueMultiplier", 1f, new ConfigDescription("minTotalScrapValue = round(baseMinTotalScrapValue * this * factor) + ScrapValueOffset.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); DynamicScrapValueMaxMult = config.Bind<float>("B. Dynamic.Scrap.Value", "MaxValueMultiplier", 1f, new ConfigDescription("maxTotalScrapValue = round(baseMaxTotalScrapValue * this * factor) + ScrapValueOffset. Keep it at or above MinValueMultiplier.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); DynamicScrapValuePlayerThreshold = config.Bind<int>("B. Dynamic.Scrap.Value", "PlayerThreshold", 2, new ConfigDescription("Player count where scaling starts. With PerExtraPlayer it activates above this, with PerMissingPlayer it activates below.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 32), Array.Empty<object>())); DynamicScrapValueDirection = config.Bind<PlayerScalingDirection>("B. Dynamic.Scrap.Value", "ScalingDirection", PlayerScalingDirection.PerExtraPlayer, "PerMissingPlayer: fewer players = more scrap. PerExtraPlayer: more players = more scrap."); DynamicScrapValueMultPerPlayer = config.Bind<float>("B. Dynamic.Scrap.Value", "MultPerPlayer", 0.15f, new ConfigDescription("Boost per qualifying player. threshold=2, PerMissingPlayer, this=0.15 -> solo gets factor=1.15.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())); DynamicScrapAmountEnabled = config.Bind<bool>("C. Dynamic.Scrap.Amount", "Enabled", false, "Scale the moons min/max scrap item count off baseMinTotalScrapValue."); DynamicScrapAmountValuePerItem = config.Bind<int>("C. Dynamic.Scrap.Amount", "ValuePerScrapItem", 25, new ConfigDescription("Divisor that turns scaled baseMinTotalScrapValue into item count. maxScrap = (baseMinTotalScrapValue * factor) / this. Lower numbers mean more items per moon. At baseMinTotalScrapValue=200, factor=1.15, this=25 you get maxScrap = floor(230/25) = 9.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 1000), Array.Empty<object>())); DynamicScrapAmountMinFraction = config.Bind<float>("C. Dynamic.Scrap.Amount", "MinScrapFraction", 0.6f, new ConfigDescription("minScrap = round(maxScrap * this). At maxScrap=9 and this=0.6 you get minScrap = round(5.4) = 5.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); DynamicScrapAmountCap = config.Bind<int>("C. Dynamic.Scrap.Amount", "MaxScrapItemsCap", -1, "Hard upper bound on maxScrap no matter what the formula says. Set -1 to turn cap off."); DynamicScrapAmountPlayerThreshold = config.Bind<int>("C. Dynamic.Scrap.Amount", "PlayerThreshold", 2, new ConfigDescription("Player count where scaling starts. With PerExtraPlayer it activates above this, with PerMissingPlayer it activates below.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 32), Array.Empty<object>())); DynamicScrapAmountDirection = config.Bind<PlayerScalingDirection>("C. Dynamic.Scrap.Amount", "ScalingDirection", PlayerScalingDirection.PerExtraPlayer, "PerMissingPlayer: fewer players = more items. PerExtraPlayer: more players = more items."); DynamicScrapAmountMultPerPlayer = config.Bind<float>("C. Dynamic.Scrap.Amount", "MultPerPlayer", 0.15f, new ConfigDescription("Boost per qualifying player. threshold=2, PerMissingPlayer, this=0.15 -> solo gets factor=1.15.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())); DynamicEnemyPowerEnabled = config.Bind<bool>("D. Dynamic.Enemy.Power", "Enabled", false, "Scale moons enemy power budget by player count. Higher budget = more / stronger enemies can spawn."); DynamicEnemyPowerScaleInside = config.Bind<bool>("D. Dynamic.Enemy.Power", "ScaleInside", true, "Apply scaling to maxEnemyPowerCount (inside enemies)."); DynamicEnemyPowerScaleOutside = config.Bind<bool>("D. Dynamic.Enemy.Power", "ScaleOutside", true, "Apply scaling to maxOutsideEnemyPowerCount (night time outside enemies)."); DynamicEnemyPowerScaleDaytime = config.Bind<bool>("D. Dynamic.Enemy.Power", "ScaleDaytime", false, "Apply scaling to maxDaytimeEnemyPowerCount (daytime outside enemies)."); DynamicEnemyPowerPlayerThreshold = config.Bind<int>("D. Dynamic.Enemy.Power", "PlayerThreshold", 2, new ConfigDescription("Player count where scaling starts. More players above this = more enemies.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 32), Array.Empty<object>())); DynamicEnemyPowerMultPerPlayer = config.Bind<float>("D. Dynamic.Enemy.Power", "MultPerPlayer", 0.15f, new ConfigDescription("Boost per qualifying player. threshold=2, PerExtraPlayer, this=0.15: 4 players -> factor = 1 + 2*0.15 = 1.30.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())); DynamicEnemyPowerMaxFactor = config.Bind<float>("D. Dynamic.Enemy.Power", "MaxFactor", 3f, new ConfigDescription("Safety cap on the resolved factor. Floor is always 1.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 20f), Array.Empty<object>())); } } internal static class Metadata { public const string GUID = "com.seeya.configurablequota"; public const string PLUGIN_NAME = "Configurable Quota"; public const string VERSION = "1.3.1"; public const string LETHAL_NETWORK_API_GUID = "LethalNetworkAPI"; public const string LETHAL_CONSTELLATIONS_GUID = "com.github.darmuh.LethalConstellations"; public const string LETHAL_MOON_UNLOCKS_GUID = "com.xmods.lethalmoonunlocks"; public const string OPEN_LIB_GUID = "darmuh.OpenLib"; public const string LLL_GUID = "imabatby.lethallevelloader"; public const string LUNAR_CONFIG_GUID = "Crafty.LunarConfig"; } [BepInPlugin("com.seeya.configurablequota", "Configurable Quota", "1.3.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { private readonly Harmony _harmony = new Harmony("com.seeya.configurablequota"); public static Plugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } private void Awake() { Instance = this; Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"Initializing Configurable Quota"); ConfigManager.Initialize(((BaseUnityPlugin)this).Config); ConstellationDeadlineConfig.Initialize(); NetworkSync.Initialize(); _harmony.PatchAll(); OpenLibEventBridge.TrySubscribe(); Log.LogInfo((object)"Configurable Quota is loaded!"); } } } namespace ConfigurableQuota.Patches { [HarmonyPatch(typeof(TimeOfDay))] internal static class BuyingRatePatch { [CompilerGenerated] private sealed class <AlertCoroutine>d__6 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public float delay; public bool isJackpot; public int rateRounded; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <AlertCoroutine>d__6(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSecondsRealtime(delay); <>1__state = 1; return true; case 1: { <>1__state = -1; HUDManager instance = HUDManager.Instance; if ((Object)(object)instance == (Object)null) { return false; } if (isJackpot) { instance.DisplayTip("<color=#ffc526>SCRAP EMERGENCY</color>", $"<color=#fcbf17>\n* Buying rates have jumped to {rateRounded}%</color>", true, false, "LC_JackpotTip1"); if ((Object)(object)instance.UIAudio != (Object)null && (Object)(object)instance.globalNotificationSFX != (Object)null) { instance.UIAudio.PlayOneShot(instance.globalNotificationSFX); } } else { instance.DisplayTip("New Scrap Rate", $"\n* Buying rates have changed to {rateRounded}%", false, false, "LC_JackpotTip2"); } return false; } } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <ReapplyBuyRate>d__4 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public float delay; public float rate; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <ReapplyBuyRate>d__4(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(delay); <>1__state = 1; return true; case 1: { <>1__state = -1; StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return false; } if (!Mathf.Approximately(instance.companyBuyingRate, rate)) { instance.companyBuyingRate = rate; Plugin.Log.LogDebug((object)$"Re-applied buy rate {Mathf.RoundToInt(rate * 100f)}% after {delay:0.#}s."); } return false; } } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const float ReapplyDelaySeconds = 3f; [HarmonyPatch("SetBuyingRateForDay")] [HarmonyPostfix] [HarmonyPriority(200)] private static void SetBuyingRateForDay_Postfix(TimeOfDay __instance) { try { if (((NetworkBehaviour)__instance).IsServer && !ConfigManager.DisableQuota.Value) { StartOfRound instance = StartOfRound.Instance; if (!((Object)(object)instance == (Object)null)) { float companyBuyingRate = instance.companyBuyingRate; int daysUntilDeadline = __instance.daysUntilDeadline; (float rate, bool isJackpot, string source) tuple = ResolveRate(companyBuyingRate, daysUntilDeadline); float item = tuple.rate; bool item2 = tuple.isJackpot; string item3 = tuple.source; instance.companyBuyingRate = item; int num = Mathf.RoundToInt(item * 100f); Plugin.Log.LogInfo((object)$"Buy rate set to {num}% ({item3}, jackpot={item2})."); ((MonoBehaviour)__instance).StartCoroutine(ReapplyBuyRate(item, 3f)); NetworkSync.SyncBuyingRateToClients(item, item2); DisplayBuyRateAlert(item, item2); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not apply buy rate modifiers: " + ex.Message)); } } internal static void ApplyReceivedBuyingRate(float rate, bool isJackpot) { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance != (Object)null) { instance.companyBuyingRate = rate; } DisplayBuyRateAlert(rate, isJackpot); } private static (float rate, bool isJackpot, string source) ResolveRate(float vanillaRate, int daysUntilDeadline) { bool value = ConfigManager.MinMaxRateEnabled.Value; float value2 = ConfigManager.MinRate.Value; float num = Mathf.Max(value2, ConfigManager.MaxRate.Value); bool value3 = ConfigManager.JackpotEnabled.Value; bool value4 = ConfigManager.JackpotLastDayOnly.Value; float num2 = Mathf.Clamp01(ConfigManager.JackpotChance.Value); float value5 = ConfigManager.JackpotMinRate.Value; float num3 = Mathf.Max(value5, ConfigManager.JackpotMaxRate.Value); bool value6 = ConfigManager.LastDayRateEnabled.Value; float num4 = Mathf.Clamp01(ConfigManager.LastDayRangeChance.Value); float value7 = ConfigManager.LastDayMinRate.Value; float num5 = Mathf.Max(value7, ConfigManager.LastDayMaxRate.Value); bool value8 = ConfigManager.RandomRateEnabled.Value; bool flag = daysUntilDeadline == 0; if (value3 && (!value4 || flag) && Random.value <= num2) { float item = ((value5 == num3) ? value5 : Random.Range(value5, num3)); return (item, true, value4 ? "jackpot last-day" : "jackpot any-day"); } if (value6 && flag) { if (value7 == num5) { return (value7, false, "last-day fixed"); } if (Random.value <= num4) { return (Random.Range(value7, num5), false, "last-day ranged"); } return (1f, false, "last-day fallback"); } if (value8 && value) { return (Random.Range(value2, num), false, "random in range"); } if (value) { return (Mathf.Clamp(vanillaRate, value2, num), false, "min/max clamp"); } return (vanillaRate, false, "vanilla"); } [IteratorStateMachine(typeof(<ReapplyBuyRate>d__4))] private static IEnumerator ReapplyBuyRate(float rate, float delay) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <ReapplyBuyRate>d__4(0) { rate = rate, delay = delay }; } internal static void DisplayBuyRateAlert(float rate, bool isJackpot) { try { HUDManager instance = HUDManager.Instance; TimeOfDay instance2 = TimeOfDay.Instance; if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance2 == (Object)null) && (isJackpot ? ConfigManager.JackpotAlertEnabled.Value : ConfigManager.BuyRateAlertEnabled.Value)) { int rateRounded = Mathf.RoundToInt(rate * 100f); float delay = Mathf.Max(0f, ConfigManager.AlertDelaySeconds.Value); ((MonoBehaviour)instance2).StartCoroutine(AlertCoroutine(rateRounded, isJackpot, delay)); } } catch (Exception ex) { Plugin.Log.LogDebug((object)("Could not schedule buy-rate alert: " + ex.Message)); } } [IteratorStateMachine(typeof(<AlertCoroutine>d__6))] private static IEnumerator AlertCoroutine(int rateRounded, bool isJackpot, float delay) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <AlertCoroutine>d__6(0) { rateRounded = rateRounded, isJackpot = isJackpot, delay = delay }; } } public enum PlayerScalingDirection { PerMissingPlayer, PerExtraPlayer } [HarmonyPatch(typeof(RoundManager))] internal static class DynamicLevelPatches { private static int _savedMinScrapValue; private static int _savedMaxScrapValue; private static int _savedMinScrap; private static int _savedMaxScrap; private static int _savedMaxEnemyPower; private static int _savedMaxOutsidePower; private static int _savedMaxDaytimePower; private static bool _enemyPowerSaved; [HarmonyPatch("GenerateNewFloor")] [HarmonyPrefix] [HarmonyAfter(new string[] { "imabatby.lethallevelloader", "Crafty.LunarConfig" })] private static void GenerateNewFloor_Prefix(RoundManager __instance) { if (ShouldRunServer(__instance, out SelectableLevel level) && ConfigManager.DynamicInteriorSizeEnabled.Value) { float num = ComputePlayerFactor(ConfigManager.DynamicInteriorSizePlayerThreshold.Value, ConfigManager.DynamicInteriorSizeMultPerPlayer.Value, ConfigManager.DynamicInteriorSizeDirection.Value); float num2 = (level.factorySizeMultiplier = ConfigManager.DynamicInteriorSizeBase.Value * num); Plugin.Log.LogInfo((object)$"Dynamic interior size - factorySizeMultiplier = {num2:F2} (players={GetPlayerCount()}, factor={num:F2})."); } } [HarmonyPatch("SpawnScrapInLevel")] [HarmonyPrefix] [HarmonyAfter(new string[] { "imabatby.lethallevelloader", "Crafty.LunarConfig" })] private static void SpawnScrapInLevel_Prefix(RoundManager __instance) { if (ShouldRunServer(__instance, out SelectableLevel level)) { _savedMinScrapValue = level.minTotalScrapValue; _savedMaxScrapValue = level.maxTotalScrapValue; _savedMinScrap = level.minScrap; _savedMaxScrap = level.maxScrap; if (ConfigManager.DynamicScrapValueEnabled.Value) { ApplyScrapValue(level); } if (ConfigManager.DynamicScrapAmountEnabled.Value) { ApplyScrapAmount(level); } } } [HarmonyPatch("SpawnScrapInLevel")] [HarmonyPostfix] private static void SpawnScrapInLevel_Postfix(RoundManager __instance) { if (ShouldRunServer(__instance, out SelectableLevel level)) { level.minTotalScrapValue = _savedMinScrapValue; level.maxTotalScrapValue = _savedMaxScrapValue; level.minScrap = _savedMinScrap; level.maxScrap = _savedMaxScrap; } } [HarmonyPatch("GenerateNewFloor")] [HarmonyPrefix] [HarmonyAfter(new string[] { "imabatby.lethallevelloader", "Crafty.LunarConfig" })] private static void EnemyPower_GenerateNewFloor_Prefix(RoundManager __instance) { if (ShouldRunServer(__instance, out SelectableLevel level) && ConfigManager.DynamicEnemyPowerEnabled.Value) { _savedMaxEnemyPower = level.maxEnemyPowerCount; _savedMaxOutsidePower = level.maxOutsideEnemyPowerCount; _savedMaxDaytimePower = level.maxDaytimeEnemyPowerCount; _enemyPowerSaved = true; float num = ComputePlayerFactor(ConfigManager.DynamicEnemyPowerPlayerThreshold.Value, ConfigManager.DynamicEnemyPowerMultPerPlayer.Value, PlayerScalingDirection.PerExtraPlayer); num = Mathf.Min(num, Mathf.Max(1f, ConfigManager.DynamicEnemyPowerMaxFactor.Value)); if (ConfigManager.DynamicEnemyPowerScaleInside.Value) { level.maxEnemyPowerCount = Mathf.RoundToInt((float)_savedMaxEnemyPower * num); } if (ConfigManager.DynamicEnemyPowerScaleOutside.Value) { level.maxOutsideEnemyPowerCount = Mathf.RoundToInt((float)_savedMaxOutsidePower * num); } if (ConfigManager.DynamicEnemyPowerScaleDaytime.Value) { level.maxDaytimeEnemyPowerCount = Mathf.RoundToInt((float)_savedMaxDaytimePower * num); } Plugin.Log.LogInfo((object)($"Dynamic enemy power - factor={num:F2} (players={GetPlayerCount()}), " + $"inside={level.maxEnemyPowerCount}, outside={level.maxOutsideEnemyPowerCount}, daytime={level.maxDaytimeEnemyPowerCount}.")); } } [HarmonyPatch("DespawnPropsAtEndOfRound")] [HarmonyPostfix] private static void EnemyPower_RestoreOnRoundEnd(RoundManager __instance) { if (_enemyPowerSaved) { SelectableLevel val = __instance?.currentLevel; if ((Object)(object)val != (Object)null) { val.maxEnemyPowerCount = _savedMaxEnemyPower; val.maxOutsideEnemyPowerCount = _savedMaxOutsidePower; val.maxDaytimeEnemyPowerCount = _savedMaxDaytimePower; } _enemyPowerSaved = false; } } private static bool ShouldRun(RoundManager rm, out SelectableLevel level) { level = rm?.currentLevel; if ((Object)(object)level == (Object)null) { return false; } if (ConfigManager.DisableQuota.Value) { return false; } if (PenaltyHelpers.IsOnGordion()) { return false; } return true; } private static bool ShouldRunServer(RoundManager rm, out SelectableLevel level) { if (!PenaltyHelpers.IsServerSafe) { level = null; return false; } return ShouldRun(rm, out level); } private static int GetPlayerCount() { return Mathf.Max(1, (StartOfRound.Instance?.connectedPlayersAmount ?? 0) + 1); } private static float ComputePlayerFactor(int threshold, float mult, PlayerScalingDirection direction) { int num = ((direction == PlayerScalingDirection.PerMissingPlayer) ? Mathf.Max(0, threshold - GetPlayerCount()) : Mathf.Max(0, GetPlayerCount() - threshold)); return 1f + (float)num * Mathf.Max(0f, mult); } private static void ApplyScrapValue(SelectableLevel level) { int num = Mathf.Max(1, _savedMinScrapValue); int num2 = Mathf.Max(num, _savedMaxScrapValue); float num3 = ComputePlayerFactor(ConfigManager.DynamicScrapValuePlayerThreshold.Value, ConfigManager.DynamicScrapValueMultPerPlayer.Value, ConfigManager.DynamicScrapValueDirection.Value); int value = ConfigManager.DynamicScrapValueOffset.Value; int num4 = Mathf.RoundToInt((float)num * ConfigManager.DynamicScrapValueMinMult.Value * num3) + value; int num5 = Mathf.RoundToInt((float)num2 * ConfigManager.DynamicScrapValueMaxMult.Value * num3) + value; if (num4 < 1) { num4 = 1; } if (num5 < 1) { num5 = 1; } if (num5 < num4) { num5 = num4; } level.minTotalScrapValue = num4; level.maxTotalScrapValue = num5; Plugin.Log.LogInfo((object)$"Dynamic scrap value: factor={num3:F2}, base=${num}/${num2}, min/max=${num4}/${num5}."); } private static void ApplyScrapAmount(SelectableLevel level) { float num = ComputePlayerFactor(ConfigManager.DynamicScrapAmountPlayerThreshold.Value, ConfigManager.DynamicScrapAmountMultPerPlayer.Value, ConfigManager.DynamicScrapAmountDirection.Value); int num2 = Mathf.Max(0, _savedMinScrapValue); if (num2 <= 0) { int num3 = Mathf.Max(0, _savedMinScrap); int num4 = Mathf.Max(num3, _savedMaxScrap); level.minScrap = num3; level.maxScrap = num4; Plugin.Log.LogInfo((object)$"Dynamic scrap amount - factor={num:F2}, baseline scrap value <= 0 so keeping base min/max items={num3}/{num4}."); return; } int num5 = Mathf.Max(1, ConfigManager.DynamicScrapAmountValuePerItem.Value); int num6 = Mathf.RoundToInt((float)num2 * num); int num7 = Mathf.Max(1, num6 / num5); int value = ConfigManager.DynamicScrapAmountCap.Value; if (value >= 0) { num7 = Mathf.Min(num7, value); } int num8 = Mathf.Max(1, Mathf.RoundToInt((float)num7 * ConfigManager.DynamicScrapAmountMinFraction.Value)); if (num8 > num7) { num8 = num7; } level.minScrap = num8; level.maxScrap = num7; Plugin.Log.LogInfo((object)$"Dynamic scrap amount - factor={num:F2}, min/max items={num8}/{num7}."); } } [HarmonyPatch(typeof(HUDManager))] internal static class HudQuotaAnimationPatch { [CompilerGenerated] private sealed class <CustomRackUp>d__1 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public float speed; public HUDManager hud; private int <quotaTextAmount>5__2; private int <target>5__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <CustomRackUp>d__1(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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 switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(3.5f / speed); <>1__state = 1; return true; case 1: <>1__state = -1; if ((Object)(object)TimeOfDay.Instance == (Object)null) { return false; } <quotaTextAmount>5__2 = 0; <target>5__3 = TimeOfDay.Instance.profitQuota; goto IL_00ec; case 2: <>1__state = -1; goto IL_00ec; case 3: { <>1__state = -1; hud.displayingNewQuota = false; hud.reachedProfitQuotaAnimator.SetBool("display", false); return false; } IL_00ec: if (<quotaTextAmount>5__2 < <target>5__3) { float num = Time.deltaTime * 250f * speed; <quotaTextAmount>5__2 = (int)Mathf.Clamp((float)<quotaTextAmount>5__2 + num, (float)(<quotaTextAmount>5__2 + 3), (float)(<target>5__3 + 10)); ((TMP_Text)hud.newProfitQuotaText).text = "$" + <quotaTextAmount>5__2; <>2__current = null; <>1__state = 2; return true; } ((TMP_Text)hud.newProfitQuotaText).text = "$" + <target>5__3; TimeOfDay.Instance.UpdateProfitQuotaCurrentTime(); hud.UIAudio.PlayOneShot(hud.newProfitQuotaSFX); <>2__current = (object)new WaitForSeconds(1.25f / Mathf.Clamp(speed, 0.5f, 2f)); <>1__state = 3; return true; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [HarmonyPatch("rackUpNewQuotaText")] [HarmonyPrefix] private static bool RackUpNewQuotaText_Prefix(HUDManager __instance, ref IEnumerator __result) { float speed = Mathf.Clamp(ConfigManager.QuotaAnimationSpeed.Value, 0.1f, 2f); __result = CustomRackUp(__instance, speed); return false; } [IteratorStateMachine(typeof(<CustomRackUp>d__1))] private static IEnumerator CustomRackUp(HUDManager hud, float speed) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <CustomRackUp>d__1(0) { hud = hud, speed = speed }; } [HarmonyPatch("FillEndGameStats")] [HarmonyPostfix] [HarmonyAfter(new string[] { "com.example.Advancedfeatures" })] private static void FillEndGameStats_Postfix() { TryApplyAdvancedFeaturesEndscreen(); } internal static void TryApplyAdvancedFeaturesEndscreen() { if (!ConfigManager.ScrapLossEnabled.Value || !Chainloader.PluginInfos.ContainsKey("com.example.Advancedfeatures") || !PenaltiesOnLandingPatch.HasAllDeadSnapshot) { return; } try { HUDManager instance = HUDManager.Instance; if (!((Object)(object)instance?.statsUIElements?.allPlayersDeadOverlay != (Object)null) || !((Behaviour)instance.statsUIElements.allPlayersDeadOverlay).enabled) { return; } Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "AdvancedFeatures"); if (!(assembly == null)) { Type type = assembly.GetType("AdvancedFeatures.Endscreen"); if (!(type == null)) { ApplyAdvancedFeaturesEndscreen(type); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not apply Advanced Features end screen compatibility: " + ex.Message)); } } private static void ApplyAdvancedFeaturesEndscreen(Type endscreenType) { FieldInfo fieldInfo = AccessTools.Field(endscreenType, "AreAllDead"); FieldInfo fieldInfo2 = AccessTools.Field(endscreenType, "ScrapLost"); FieldInfo fieldInfo3 = AccessTools.Field(endscreenType, "ScrapLostText"); FieldInfo fieldInfo4 = AccessTools.Field(endscreenType, "CollectedText"); FieldInfo fieldInfo5 = AccessTools.Field(endscreenType, "TotalText"); FieldInfo fieldInfo6 = AccessTools.Field(endscreenType, "CollectedLine"); FieldInfo fieldInfo7 = AccessTools.Field(endscreenType, "CollectedLabel"); object? obj = fieldInfo2?.GetValue(null); Transform val = (Transform)((obj is Transform) ? obj : null); if (PenaltiesOnLandingPatch.TryGetScrapLossSummary(out var beforeValue, out var afterValue, out var lostPercent)) { int num = Mathf.Max(0, beforeValue - afterValue); int num2 = Mathf.RoundToInt(Mathf.Clamp01(lostPercent) * 100f); string value = $"Lost {num2}% scrap (${num}/{beforeValue})"; object? obj2 = fieldInfo4?.GetValue(null); Component val2 = (Component)((obj2 is Component) ? obj2 : null); object? obj3 = fieldInfo5?.GetValue(null); Component val3 = (Component)((obj3 is Component) ? obj3 : null); object? obj4 = fieldInfo6?.GetValue(null); Transform val4 = (Transform)((obj4 is Transform) ? obj4 : null); object? obj5 = fieldInfo7?.GetValue(null); Transform val5 = (Transform)((obj5 is Transform) ? obj5 : null); object obj6 = fieldInfo3?.GetValue(null); PropertyInfo propertyInfo = obj6?.GetType().GetProperty("text"); if ((Object)(object)val2 != (Object)null) { val2.gameObject.SetActive(false); } if ((Object)(object)val3 != (Object)null) { val3.gameObject.SetActive(false); } if ((Object)(object)val4 != (Object)null) { ((Component)val4).gameObject.SetActive(false); } if ((Object)(object)val5 != (Object)null) { ((Component)val5).gameObject.SetActive(false); } if ((Object)(object)val != (Object)null) { ((Component)val).gameObject.SetActive(true); } propertyInfo?.SetValue(obj6, value); Plugin.Log.LogInfo((object)$"Updated Advanced Features scrap-loss text: {num2}% (${num}/{beforeValue})."); } else if ((Object)(object)val != (Object)null) { ((Component)val).gameObject.SetActive(false); } fieldInfo?.SetValue(null, false); } [HarmonyPatch("ApplyPenalty")] [HarmonyPrefix] private static bool ApplyPenalty_Prefix() { return false; } [HarmonyPatch("ApplyPenalty")] [HarmonyPostfix] private static void ApplyPenalty_Postfix(HUDManager __instance, int playersDead, int bodiesInsured) { try { (int dead, int total, int recovered) tuple = ResolvePenaltyCounts(playersDead, bodiesInsured); int item = tuple.dead; int item2 = tuple.total; int item3 = tuple.recovered; bool atCompany = PenaltyHelpers.IsOnGordion(); var (creditPct, num) = ComputeCreditPenalty(item, item2, item3, atCompany); var (quotaPct, quotaDelta) = ComputeQuotaPenalty(item, item2, item3, atCompany); ((TMP_Text)__instance.statsUIElements.penaltyAddition).text = BuildPenaltyText(item, item3, creditPct, quotaPct, quotaDelta); ((TMP_Text)__instance.statsUIElements.penaltyTotal).text = ((num > 0) ? $"DUE: ${num}" : ""); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not update penalty text on the end screen: " + ex.Message)); } } private static (int dead, int total, int recovered) ResolvePenaltyCounts(int playersDead, int bodiesInsured) { if (PenaltiesOnLandingPatch.HasPenaltyCache) { return (PenaltiesOnLandingPatch.CachedDead, PenaltiesOnLandingPatch.CachedTotal, PenaltiesOnLandingPatch.CachedRecovered); } var (num, num2, item) = PenaltyHelpers.CountDeathsAndRecovered(); if (num == 0 && playersDead > 0) { num = playersDead; item = bodiesInsured; num2 = Mathf.Max(num + 1, num2); } return (num, num2, item); } private static (float pct, int loss) ComputeCreditPenalty(int dead, int total, int recovered, bool atCompany) { if (!ConfigManager.CreditPenaltiesEnabled.Value || dead == 0 || (atCompany && !ConfigManager.CreditPenaltiesOnGordion.Value)) { return (0f, 0); } int num = Object.FindObjectOfType<Terminal>()?.groupCredits ?? 0; float num2 = PenaltyHelpers.ComputePenaltyPercent(ConfigManager.CreditPenaltiesDynamic.Value, ConfigManager.CreditPenaltyPercentPerPlayer.Value, ConfigManager.CreditPenaltyPercentCap.Value, ConfigManager.CreditPenaltyPercentThreshold.Value, ConfigManager.CreditPenaltyRecoveryBonus.Value, dead, total, recovered); return (num2, Mathf.RoundToInt((float)num * num2)); } private static (float pct, int delta) ComputeQuotaPenalty(int dead, int total, int recovered, bool atCompany) { if (!ConfigManager.QuotaPenaltiesEnabled.Value || dead == 0 || (atCompany && !ConfigManager.QuotaPenaltiesOnGordion.Value)) { return (0f, 0); } float num = PenaltyHelpers.ComputePenaltyPercent(ConfigManager.QuotaPenaltiesDynamic.Value, ConfigManager.QuotaPenaltyPercentPerPlayer.Value, ConfigManager.QuotaPenaltyPercentCap.Value, ConfigManager.QuotaPenaltyPercentThreshold.Value, ConfigManager.QuotaPenaltyRecoveryBonus.Value, dead, total, recovered); int num2 = PenaltiesOnLandingPatch.CachedQuotaPenaltyDelta; if (num2 <= 0) { TimeOfDay instance = TimeOfDay.Instance; num2 = (((Object)(object)instance != (Object)null) ? Mathf.RoundToInt((float)Mathf.Max(1, instance.profitQuota) * num) : 0); } return (num, num2); } private static string BuildPenaltyText(int dead, int recovered, float creditPct, float quotaPct, int quotaDelta) { string text = ((creditPct > 0f) ? $"{dead} casualties: -{Mathf.RoundToInt(creditPct * 100f)}%" : $"{dead} casualties"); string text2 = text + $"\n({recovered} of {dead} bodies recovered.)"; if (quotaPct > 0f) { text2 += $"\n\nQuota: {Mathf.RoundToInt(quotaPct * 100f)}% (${quotaDelta})"; } return text2; } } internal static class NetworkSync { private static LNetworkMessage<int>? _syncCreditsMessage; private static LNetworkMessage<SyncQuotaData>? _syncQuotaMessage; private static LNetworkMessage<SyncValueLossData>? _syncValueLossMessage; private static LNetworkMessage<int>? _syncDeadlineMessage; private static LNetworkMessage<int>? _syncRolloverMessage; private static LNetworkMessage<SyncScrapLossSummary>? _syncScrapLossSummaryMessage; private static LNetworkMessage<SyncBuyingRateData>? _syncBuyingRateMessage; public static void Initialize() { try { _syncCreditsMessage = LNetworkMessage<int>.Connect("ConfigurableQuota_SyncCredits", (Action<int, ulong>)null, (Action<int>)OnCreditsReceived, (Action<int, ulong>)null); _syncQuotaMessage = LNetworkMessage<SyncQuotaData>.Connect("ConfigurableQuota_SyncQuota", (Action<SyncQuotaData, ulong>)null, (Action<SyncQuotaData>)OnQuotaReceived, (Action<SyncQuotaData, ulong>)null); _syncValueLossMessage = LNetworkMessage<SyncValueLossData>.Connect("ConfigurableQuota_SyncValueLoss", (Action<SyncValueLossData, ulong>)null, (Action<SyncValueLossData>)OnValueLossReceived, (Action<SyncValueLossData, ulong>)null); _syncDeadlineMessage = LNetworkMessage<int>.Connect("ConfigurableQuota_SyncDeadline", (Action<int, ulong>)null, (Action<int>)OnDeadlineReceived, (Action<int, ulong>)null); _syncRolloverMessage = LNetworkMessage<int>.Connect("ConfigurableQuota_SyncRollover", (Action<int, ulong>)null, (Action<int>)OnRolloverReceived, (Action<int, ulong>)null); _syncScrapLossSummaryMessage = LNetworkMessage<SyncScrapLossSummary>.Connect("ConfigurableQuota_SyncScrapLossSummary", (Action<SyncScrapLossSummary, ulong>)null, (Action<SyncScrapLossSummary>)OnScrapLossSummaryReceived, (Action<SyncScrapLossSummary, ulong>)null); _syncBuyingRateMessage = LNetworkMessage<SyncBuyingRateData>.Connect("ConfigurableQuota_SyncBuyingRate", (Action<SyncBuyingRateData, ulong>)null, (Action<SyncBuyingRateData>)OnBuyingRateReceived, (Action<SyncBuyingRateData, ulong>)null); Plugin.Log.LogInfo((object)"Network sync is ready."); } catch (Exception ex) { Plugin.Log.LogError((object)("Could not initialize network sync: " + ex.Message)); } } public static void SyncCreditsToClients(int credits) { try { if (_syncCreditsMessage == null) { Plugin.Log.LogWarning((object)"Credits message not initialized"); } else { _syncCreditsMessage.SendClients(credits); } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not sync credits: " + ex.Message)); } } private static void OnCreditsReceived(int credits) { try { Terminal val = Object.FindObjectOfType<Terminal>(); if ((Object)(object)val != (Object)null) { val.groupCredits = credits; } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not apply synced credits on client: " + ex.Message)); } } public static void SyncScrapLossSummaryToClients(int beforeValue, int afterValue) { try { if (_syncScrapLossSummaryMessage == null) { Plugin.Log.LogWarning((object)"Scrap loss summary message not initialized"); } else { _syncScrapLossSummaryMessage.SendClients(new SyncScrapLossSummary(beforeValue, afterValue)); } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not sync scrap loss summary: " + ex.Message)); } } private static void OnScrapLossSummaryReceived(SyncScrapLossSummary data) { try { if (data.BeforeValue > 0) { PenaltiesOnLandingPatch.CacheScrapLossSummary(data.BeforeValue, data.AfterValue); } else { PenaltiesOnLandingPatch.ClearScrapLossSummary(); } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not apply synced scrap loss summary on client: " + ex.Message)); } } public static void SyncQuotaToClients(int quota, int penaltyDelta = 0) { try { if (_syncQuotaMessage == null) { Plugin.Log.LogWarning((object)"Quota message not initialized"); } else { _syncQuotaMessage.SendClients(new SyncQuotaData(quota, penaltyDelta)); } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not sync quota: " + ex.Message)); } } private static void OnQuotaReceived(SyncQuotaData data) { try { TimeOfDay instance = TimeOfDay.Instance; if ((Object)(object)instance != (Object)null) { instance.profitQuota = data.Quota; } PenaltiesOnLandingPatch.CachedQuotaPenaltyDelta = data.PenaltyDelta; } catch (Exception ex) { Plugin.Log.LogError((object)("Could not apply synced quota on client: " + ex.Message)); } } public static void SyncValueLossToClients(SyncValueLossData[] items) { try { if (_syncValueLossMessage == null) { Plugin.Log.LogWarning((object)"Value loss message not initialized"); return; } foreach (SyncValueLossData syncValueLossData in items) { _syncValueLossMessage.SendClients(syncValueLossData); } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not sync value loss: " + ex.Message)); } } private static void OnValueLossReceived(SyncValueLossData data) { try { GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>(); GrabbableObject[] array2 = array; foreach (GrabbableObject val in array2) { try { NetworkObject component = ((Component)val).GetComponent<NetworkObject>(); if (!((Object)(object)val != (Object)null) || !((Object)(object)component != (Object)null) || component.NetworkObjectId != data.NetworkObjectId) { continue; } Item itemProperties = val.itemProperties; if (itemProperties != null && itemProperties.isScrap) { val.scrapValue = data.NewValue; try { val.SetScrapValue(data.NewValue); break; } catch { break; } } } catch { } } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not apply synced value loss on client: " + ex.Message)); } } public static void SyncDeadlineToClients(int days) { try { ApplyDeadline(days); if (_syncDeadlineMessage == null) { Plugin.Log.LogWarning((object)"Deadline message not initialized"); } else { _syncDeadlineMessage.SendClients(days); } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not sync deadline: " + ex.Message)); } } private static void OnDeadlineReceived(int days) { try { ApplyDeadline(days); } catch (Exception ex) { Plugin.Log.LogError((object)("Could not apply synced deadline on client: " + ex.Message)); } } private static void ApplyDeadline(int days) { TimeOfDay instance = TimeOfDay.Instance; if (!((Object)(object)instance == (Object)null)) { int num = Mathf.Max(1, days); float num2 = instance.totalTime; if (num2 <= 0f && instance.daysUntilDeadline > 0 && instance.timeUntilDeadline > 0f) { num2 = instance.timeUntilDeadline / (float)instance.daysUntilDeadline; } if (instance.quotaVariables != null) { instance.quotaVariables.deadlineDaysAmount = num; } instance.daysUntilDeadline = num; if (num2 > 0f) { instance.timeUntilDeadline = (float)num * num2; } } } public static void SyncRolloverToClients(int rollover) { try { if (_syncRolloverMessage == null) { Plugin.Log.LogWarning((object)"Rollover message not initialized"); } else { _syncRolloverMessage.SendClients(rollover); } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not sync rollover: " + ex.Message)); } } private static void OnRolloverReceived(int rollover) { try { TimeOfDay instance = TimeOfDay.Instance; if ((Object)(object)instance != (Object)null) { int quotaFulfilled = Math.Min(rollover, Math.Max(0, instance.profitQuota - 1)); instance.quotaFulfilled = quotaFulfilled; } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not apply synced rollover on client: " + ex.Message)); } } public static void SyncBuyingRateToClients(float rate, bool isJackpot) { try { if (_syncBuyingRateMessage == null) { Plugin.Log.LogWarning((object)"Buying rate message not initialized"); } else { _syncBuyingRateMessage.SendClients(new SyncBuyingRateData(rate, isJackpot)); } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not sync buying rate: " + ex.Message)); } } private static void OnBuyingRateReceived(SyncBuyingRateData data) { try { BuyingRatePatch.ApplyReceivedBuyingRate(data.Rate, data.IsJackpot); } catch (Exception ex) { Plugin.Log.LogError((object)("Could not apply synced buying rate on client: " + ex.Message)); } } } [Serializable] public struct SyncQuotaData { public int Quota; public int PenaltyDelta; public SyncQuotaData(int quota, int penaltyDelta) { Quota = quota; PenaltyDelta = penaltyDelta; } } [Serializable] public struct SyncBuyingRateData { public float Rate; public bool IsJackpot; public SyncBuyingRateData(float rate, bool isJackpot) { Rate = rate; IsJackpot = isJackpot; } } [Serializable] public struct SyncValueLossData { public ulong NetworkObjectId; public int NewValue; public SyncValueLossData(ulong networkObjectId, int newValue) { NetworkObjectId = networkObjectId; NewValue = newValue; } } [Serializable] public struct SyncScrapLossSummary { public int BeforeValue; public int AfterValue; public SyncScrapLossSummary(int beforeValue, int afterValue) { BeforeValue = beforeValue; AfterValue = afterValue; } } internal static class PenaltyHelpers { public static bool IsServerSafe { get { if ((Object)(object)NetworkManager.Singleton != (Object)null) { return NetworkManager.Singleton.IsServer; } return false; } } public static (int dead, int total, int recovered) CountDeathsAndRecovered() { //IL_00f9: Unknown result type (might be due to invalid IL or missing references) StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return (0, 0, 0); } int num = 0; int num2 = 0; int num3 = 0; PlayerControllerB[] allPlayerScripts = instance.allPlayerScripts; foreach (PlayerControllerB val in allPlayerScripts) { if ((Object)(object)val == (Object)null) { continue; } bool isPlayerControlled = val.isPlayerControlled; bool isPlayerDead = val.isPlayerDead; if (isPlayerControlled || isPlayerDead) { num2++; } if (!isPlayerDead) { continue; } num++; GrabbableObject obj = val.deadBody?.grabBodyObject; RagdollGrabbableObject val2 = (RagdollGrabbableObject)(object)((obj is RagdollGrabbableObject) ? obj : null); if ((Object)(object)val2 == (Object)null) { RagdollGrabbableObject[] array = Object.FindObjectsOfType<RagdollGrabbableObject>(); foreach (RagdollGrabbableObject val3 in array) { if ((Object)(object)((val3 == null) ? null : ((Component)val3).GetComponent<DeadBodyInfo>()?.playerScript) == (Object)(object)val) { val2 = val3; break; } } } bool flag = false; if ((Object)(object)val2 != (Object)null) { bool isInShipRoom = ((GrabbableObject)val2).isInShipRoom; bool flag2 = IsPositionInsideShip(((Component)val2).transform.position); flag = isInShipRoom || flag2; } if (flag) { num3++; } } return (num, Math.Max(num2, 1), Mathf.Clamp(num3, 0, num)); } public static bool IsPositionInsideShip(Vector3 pos) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) try { Collider val = StartOfRound.Instance?.shipBounds; int result; if ((Object)(object)val != (Object)null) { Bounds bounds = val.bounds; result = (((Bounds)(ref bounds)).Contains(pos) ? 1 : 0); } else { result = 0; } return (byte)result != 0; } catch { return false; } } public static bool IsOnGordion() { try { SelectableLevel val = StartOfRound.Instance?.currentLevel; if ((Object)(object)val == (Object)null) { return false; } return val.sceneName == "CompanyBuilding"; } catch { return false; } } public static float ComputePenaltyPercent(bool dynamicMode, float percentPerPlayer, float cap, float threshold, float recoveryBonus, int dead, int total, int recovered) { if (dead <= 0 || total <= 0) { return 0f; } float num = ((cap >= 0f) ? Mathf.Clamp01(cap) : 1f); float num2 = (dynamicMode ? ((float)dead / (float)total * num) : ((float)dead * Mathf.Max(0f, percentPerPlayer))); if (recovered > 0 && dead > 0) { float num3 = Mathf.Clamp01((float)recovered / (float)dead); num2 *= Mathf.Clamp01(1f - Mathf.Clamp01(recoveryBonus) * num3); } if (!dynamicMode && cap >= 0f) { num2 = Mathf.Min(num2, num); } if (!(num2 < threshold)) { return Mathf.Clamp01(num2); } return 0f; } } [HarmonyPatch(typeof(RoundManager))] internal static class PenaltiesOnLandingPatch { [CompilerGenerated] private sealed class <FinalizeCreditPenaltyAfterDelay>d__20 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public int desiredFinal; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <FinalizeCreditPenaltyAfterDelay>d__20(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(1.5f); <>1__state = 1; return true; case 1: <>1__state = -1; try { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return false; } int currentCredits = GetCurrentCredits(); SetCredits(desiredFinal); Plugin.Log.LogInfo((object)$"Credits penalty applied: {currentCredits} -> {desiredFinal} (-{currentCredits - desiredFinal})."); } finally { _creditScheduled = false; } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } internal static bool _appliedThisRound; private static bool _creditScheduled; internal static bool _lossesAppliedThisRound; internal static int CachedDead; internal static int CachedTotal; internal static int CachedRecovered; internal static bool HasPenaltyCache; internal static int CachedQuotaPenaltyDelta; internal static int CachedShipScrapBeforeLoss; internal static int CachedShipScrapAfterLoss; internal static bool HasScrapLossSummary; internal static bool HasAllDeadSnapshot; internal static void CachePenaltyCounts(int dead, int total, int recovered) { CachedDead = dead; CachedTotal = total; CachedRecovered = recovered; HasPenaltyCache = true; } internal static void CacheScrapLossSummary(int beforeValue, int afterValue) { int num = Mathf.Max(0, beforeValue); int cachedShipScrapAfterLoss = Mathf.Clamp(afterValue, 0, num); CachedShipScrapBeforeLoss = num; CachedShipScrapAfterLoss = cachedShipScrapAfterLoss; HasScrapLossSummary = num > 0; } internal static void ClearScrapLossSummary() { CachedShipScrapBeforeLoss = 0; CachedShipScrapAfterLoss = 0; HasScrapLossSummary = false; } internal static bool TryGetScrapLossSummary(out int beforeValue, out int afterValue, out float lostPercent) { beforeValue = CachedShipScrapBeforeLoss; afterValue = CachedShipScrapAfterLoss; lostPercent = 0f; if (!HasScrapLossSummary || beforeValue <= 0) { return false; } lostPercent = Mathf.Clamp01((float)(beforeValue - afterValue) / (float)beforeValue); return true; } [HarmonyPatch("DespawnPropsAtEndOfRound")] [HarmonyPrefix] private static bool DespawnPrefix(bool despawnAllItems) { try { if (!PenaltyHelpers.IsServerSafe) { return true; } HasPenaltyCache = false; HasAllDeadSnapshot = false; CachedQuotaPenaltyDelta = 0; ClearScrapLossSummary(); bool flag = PenaltyHelpers.IsOnGordion(); var (num, num2, recovered) = PenaltyHelpers.CountDeathsAndRecovered(); if (!despawnAllItems && !flag && num >= num2 && !_lossesAppliedThisRound) { DespawnFacilityItems(); ApplyLossesWhenAllDead(); _lossesAppliedThisRound = true; HasAllDeadSnapshot = true; CachePenaltyCounts(num, num2, recovered); HudQuotaAnimationPatch.TryApplyAdvancedFeaturesEndscreen(); if (ConfigManager.CreditPenaltiesEnabled.Value) { ScheduleCreditPenalty(num, num2, recovered); } if (ConfigManager.QuotaPenaltiesEnabled.Value) { ApplyQuotaPenalty(num, num2, recovered); } _appliedThisRound = true; return false; } return true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("Error in despawn prefix: " + ex.Message)); return true; } } [HarmonyPatch("DespawnPropsAtEndOfRound")] [HarmonyPostfix] private static void DespawnPostfix(bool despawnAllItems) { try { if (despawnAllItems || !PenaltyHelpers.IsServerSafe || _appliedThisRound) { return; } var (num, total, recovered) = PenaltyHelpers.CountDeathsAndRecovered(); if (num > 0) { CachePenaltyCounts(num, total, recovered); bool flag = PenaltyHelpers.IsOnGordion(); if (ConfigManager.CreditPenaltiesEnabled.Value && (!flag || ConfigManager.CreditPenaltiesOnGordion.Value)) { ScheduleCreditPenalty(num, total, recovered); } if (ConfigManager.QuotaPenaltiesEnabled.Value && (!flag || ConfigManager.QuotaPenaltiesOnGordion.Value)) { ApplyQuotaPenalty(num, total, recovered); } _appliedThisRound = true; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Error in despawn postfix: " + ex.Message)); } } private static void ScheduleCreditPenalty(int dead, int total, int recovered) { try { if (_creditScheduled) { return; } StartOfRound instance = StartOfRound.Instance; if (!((Object)(object)instance == (Object)null)) { int currentCredits = GetCurrentCredits(); float num = PenaltyHelpers.ComputePenaltyPercent(ConfigManager.CreditPenaltiesDynamic.Value, ConfigManager.CreditPenaltyPercentPerPlayer.Value, ConfigManager.CreditPenaltyPercentCap.Value, ConfigManager.CreditPenaltyPercentThreshold.Value, ConfigManager.CreditPenaltyRecoveryBonus.Value, dead, total, recovered); if (!(num <= 0f)) { int desiredFinal = Mathf.Max(0, currentCredits - Mathf.RoundToInt((float)currentCredits * num)); _creditScheduled = true; ((MonoBehaviour)instance).StartCoroutine(FinalizeCreditPenaltyAfterDelay(desiredFinal)); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not schedule credit penalty: " + ex.Message)); } } private static int GetCurrentCredits() { try { Terminal val = Object.FindObjectOfType<Terminal>(); if ((Object)(object)val != (Object)null) { return val.groupCredits; } } catch (Exception ex) { Plugin.Log.LogDebug((object)("Could not read current credits: " + ex.Message)); } return 0; } [IteratorStateMachine(typeof(<FinalizeCreditPenaltyAfterDelay>d__20))] private static IEnumerator FinalizeCreditPenaltyAfterDelay(int desiredFinal) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <FinalizeCreditPenaltyAfterDelay>d__20(0) { desiredFinal = desiredFinal }; } private static void SetCredits(int value) { try { Terminal val = Object.FindObjectOfType<Terminal>(); if ((Object)(object)val != (Object)null) { val.SyncGroupCreditsServerRpc(value, val.numberOfItemsInDropship); } } catch (Exception ex) { Plugin.Log.LogDebug((object)("Could not sync credits to terminal: " + ex.Message)); } } private static void ApplyQuotaPenalty(int dead, int total, int recovered) { float num = PenaltyHelpers.ComputePenaltyPercent(ConfigManager.QuotaPenaltiesDynamic.Value, ConfigManager.QuotaPenaltyPercentPerPlayer.Value, ConfigManager.QuotaPenaltyPercentCap.Value, ConfigManager.QuotaPenaltyPercentThreshold.Value, ConfigManager.QuotaPenaltyRecoveryBonus.Value, dead, total, recovered); if (!(num <= 0f)) { TimeOfDay instance = TimeOfDay.Instance; if ((Object)(object)instance != (Object)null) { int profitQuota = instance.profitQuota; int num2 = Mathf.RoundToInt((float)Math.Max(1, profitQuota) * num); int num3 = Mathf.Max(1, profitQuota + num2); CachedQuotaPenaltyDelta = num2; instance.profitQuota = num3; NetworkSync.SyncQuotaToClients(num3, num2); Plugin.Log.LogInfo((object)$"Quota penalty applied: {profitQuota} -> {num3} (+{num2}, {num:P0}, {dead}/{total} dead)."); } } } private static void DespawnFacilityItems() { try { GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>(); if (array == null || array.Length == 0) { return; } GrabbableObject[] array2 = array; foreach (GrabbableObject g in array2) { try { if (!IsShipItem(g)) { DespawnObject(g); } } catch (Exception ex) { Plugin.Log.LogDebug((object)("Skipped despawn check for facility item: " + ex.Message)); } } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Error despawning facility items: " + ex2.Message)); } } private static void ApplyLossesWhenAllDead() { try { GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>(); if (array != null && array.Length != 0) { GrabbableObject[] source = array.Where(IsShipItem).ToArray(); GrabbableObject[] array2 = source.Where((GrabbableObject g) => g.itemProperties.isScrap).ToArray(); GrabbableObject[] array3 = source.Where((GrabbableObject g) => !g.itemProperties.isScrap && !IsBodyOrBlacklisted(g)).ToArray(); int num = SumScrapValue(array2); if (ConfigManager.ValueLossEnabled.Value && array2.Length != 0) { ApplyValueLoss(array2); } if (ConfigManager.ScrapLossEnabled.Value && array2.Length != 0) { SelectAndRemoveScrap(array2); } if (ConfigManager.EquipmentLossEnabled.Value && array3.Length != 0) { SelectAndRemoveEquipment(array3); } int num2 = SumCurrentShipScrapValue(array2); CacheScrapLossSummary(num, num2); NetworkSync.SyncScrapLossSummaryToClients(num, num2); if (num > 0) { int num3 = Mathf.Max(0, num - num2); float num4 = Mathf.Clamp01((float)num3 / (float)num); Plugin.Log.LogInfo((object)$"Scrap lost: {Mathf.RoundToInt(num4 * 100f)}% (${num3}/${num})."); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not apply ship loss rules: " + ex.Message)); } } private static int SumScrapValue(IEnumerable<GrabbableObject> items) { int num = 0; foreach (GrabbableObject item in items) { try { if (item != null && (item.itemProperties?.isScrap).GetValueOrDefault()) { num += Mathf.Max(0, item.scrapValue); } } catch (Exception ex) { Plugin.Log.LogDebug((object)("Skipped scrap value sum for item: " + ex.Message)); } } return num; } private static int SumCurrentShipScrapValue(GrabbableObject[] shipScrap) { int num = 0; foreach (GrabbableObject val in shipScrap) { try { if (val != null && (val.itemProperties?.isScrap).GetValueOrDefault() && IsShipItem(val)) { num += Mathf.Max(0, val.scrapValue); } } catch (Exception ex) { Plugin.Log.LogDebug((object)("Skipped scrap value sum for item: " + ex.Message)); } } return num; } private static bool IsShipItem(GrabbableObject g) { if ((Object)(object)g == (Object)null || (Object)(object)g.itemProperties == (Object)null || !g.isInShipRoom) { return false; } try { NetworkObject component = ((Component)g).GetComponent<NetworkObject>(); return (Object)(object)component != (Object)null && component.IsSpawned; } catch { return false; } } private static bool IsBodyOrBlacklisted(GrabbableObject g) { if ((Object)(object)g == (Object)null) { return true; } if (g is RagdollGrabbableObject) { return true; } if (g is ClipboardItem) { return true; } try { string text = g.itemProperties?.itemName ?? ((Object)g).name; return text.IndexOf("sticky note", StringComparison.OrdinalIgnoreCase) >= 0; } catch { return false; } } private static void SelectAndRemoveScrap(GrabbableObject[] scrapItems) { int num = Mathf.Max(0, ConfigManager.MaxLostScrapItems.Value); float num2 = Mathf.Clamp01(ConfigManager.ItemsSafeChance.Value); float num3 = Mathf.Clamp01(ConfigManager.LoseEachScrapChance.Value); int num4 = 0; int num5 = 0; List<string> list = new List<string>(); foreach (GrabbableObject val in scrapItems) { try { if ((Object)(object)val == (Object)null) { continue; } Item itemProperties = val.itemProperties; if (itemProperties != null && itemProperties.isScrap) { num4++; if ((num <= 0 || num5 < num) && !(Random.value < num2) && Random.value < num3) { DespawnObject(val); num5++; list.Add(val.itemProperties.itemName); } } } catch (Exception ex) { Plugin.Log.LogDebug((object)("Skipped scrap removal for item: " + ex.Message)); } } Plugin.Log.LogInfo((object)string.Format("Scrap items removed: {0}/{1} [{2}].", num5, num4, string.Join(", ", list))); } private static void SelectAndRemoveEquipment(GrabbableObject[] equipItems) { int num = Mathf.Max(0, ConfigManager.MaxLostEquipmentItems.Value); float num2 = Mathf.Clamp01(ConfigManager.LoseEachEquipmentChance.Value); int num3 = 0; int num4 = 0; List<string> list = new List<string>(); foreach (GrabbableObject val in equipItems) { try { if ((Object)(object)val == (Object)null) { continue; } Item itemProperties = val.itemProperties; if (itemProperties != null && !itemProperties.isScrap) { num3++; if ((num <= 0 || num4 < num) && Random.value < num2) { DespawnObject(val); num4++; list.Add(val.itemProperties.itemName); } } } catch (Exception ex) { Plugin.Log.LogDebug((object)("Skipped equipment removal for item: " + ex.Message)); } } Plugin.Log.LogInfo((object)string.Format("Equipment items removed: {0}/{1} [{2}].", num4, num3, string.Join(", ", list))); } private static void ApplyValueLoss(GrabbableObject[] scrapItems) { float num = Mathf.Clamp01(ConfigManager.ValueLossPercent.Value); if (num <= 0f) { return; } float num2 = 1f - num; int num3 = 0; int num4 = 0; int num5 = 0; List<SyncValueLossData> list = new List<SyncValueLossData>(); foreach (GrabbableObject val in scrapItems) { try { if (val != null && (val.itemProperties?.isScrap).GetValueOrDefault() && val.scrapValue > 0) { int scrapValue = val.scrapValue; int num6 = (val.scrapValue = Mathf.Max(0, Mathf.RoundToInt((float)val.scrapValue * num2))); try { val.SetScrapValue(num6); } catch (Exception ex) { Plugin.Log.LogDebug((object)("SetScrapValue failed: " + ex.Message)); } NetworkObject component = ((Component)val).GetComponent<NetworkObject>(); if ((Object)(object)component != (Object)null) { list.Add(new SyncValueLossData(component.NetworkObjectId, num6)); } num4 += scrapValue; num5 += num6; num3++; } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Could not reduce a scrap item's value: " + ex2.Message)); } } if (list.Count > 0) { NetworkSync.SyncValueLossToClients(list.ToArray()); } Plugin.Log.LogInfo((object)$"Scrap value reduced on {num3} items by {num:P0} (${num4} -> ${num5})."); } private static void DespawnObject(GrabbableObject g) { try { NetworkObject component = ((Component)g).GetComponent<NetworkObject>(); if ((Object)(object)component != (Object)null && component.IsSpawned) { component.Despawn(true); } else { Object.Destroy((Object)(object)((Component)g).gameObject); } } catch (Exception ex) { Plugin.Log.LogDebug((object)("Could not despawn object: " + ex.Message)); } } } [HarmonyPatch(typeof(StartOfRound))] internal static class ResetPenaltyFlags { [HarmonyPatch("StartGame")] [HarmonyPostfix] private static void ResetFlagsOnNewGame() { PenaltiesOnLandingPatch._appliedThisRound = false; PenaltiesOnLandingPatch._lossesAppliedThisRound = false; PenaltiesOnLandingPatch.HasPenaltyCache = false; PenaltiesOnLandingPatch.HasAllDeadSnapshot = false; PenaltiesOnLandingPatch.CachedQuotaPenaltyDelta = 0; PenaltiesOnLandingPatch.ClearScrapLossSummary(); } } [HarmonyPatch(typeof(Terminal))] internal static class StartOfRoundPatches { [HarmonyPatch("Awake")] [HarmonyPostfix] private static void ApplyStartingCredits(Terminal __instance) { try { int value = ConfigManager.StartingCredits.Value; if (value >= 0) { __instance.groupCredits = value; Plugin.Log.LogInfo((object)$"Starting credits set to {value}."); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not apply starting credits: " + ex.Message)); } } } [HarmonyPatch(typeof(TimeOfDay))] internal static class TimeOfDayQuotaPatch { private readonly struct DeadlineSelection { internal int Days { get; } internal bool IsRandomized { get; } internal bool FromConstellation { get; } internal ConstellationDeadlineMode ConstellationMode { get; } internal string ConstellationName { get; } internal string Source { get; } internal DeadlineSelection(int days, bool isRandomized, bool fromConstellation, ConstellationDeadlineMode constellationMode, string constellationName, string source) { Days = days; IsRandomized = isRandomized; FromConstellation = fromConstellation; ConstellationMode = constellationMode; ConstellationName = constellationName; Source = source; } } [CompilerGenerated] private sealed class <GIMonitorRefreshRoutine>d__11 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <GIMonitorRefreshRoutine>d__11(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; RefreshExternalMonitors(); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static bool _initialDeadlineApplied; private static bool _initialDeadlineWasConstellationSpecific; private static bool _loggedPlayerCapAutofix; [HarmonyPatch("SetNewProfitQuota")] [HarmonyPrefix] [HarmonyPriority(200)] [HarmonyAfter(new string[] { "com.xmods.lethalmoonunlocks" })] private static bool SetNewProfitQuota_Prefix(TimeOfDay __instance, ref int ___timesFulfilledQuota, ref int ___profitQuota, ref float ___timeUntilDeadline, ref int ___quotaFulfilled, ref int ___daysUntilDeadline, ref float ___totalTime) { try { if (!((NetworkBehaviour)__instance).IsServer) { return false; } if (ConfigManager.DisableQuota.Value) { ___profitQuota = Mathf.Max(0, ConfigManager.StartingQuota.Value); SetDeadlineTimer(___totalTime, ref ___daysUntilDeadline, ref ___timeUntilDeadline); return false; } int num = ___profitQuota; ___timesFulfilledQuota++; int num2 = CalculateNewQuota(num, ___timesFulfilledQuota); int num3 = ___daysUntilDeadline; int num4 = ___quotaFulfilled - num; int num5 = num4 / 5 + 15 * num3; ___profitQuota = num2; int num6 = CalculateRollover(num4); int val = Math.Max(0, ___profitQuota - 1); int num7 = Math.Min(num6, val); int num8 = SetDeadlineTimer(___totalTime, ref ___daysUntilDeadline, ref ___timeUntilDeadline, num3, logSelection: true); __instance.quotaVariables.deadlineDaysAmount = num8; __instance.SyncNewProfitQuotaClientRpc(___profitQuota, num5, ___timesFulfilledQuota); ___quotaFulfilled = num7; ___daysUntilDeadline = num8; ___timeUntilDeadline = ___totalTime * (float)num8; NetworkSync.SyncDeadlineToClients(num8); if (num7 > 0) { NetworkSync.SyncRolloverToClients(num7); } string text = ((num7 < num6) ? $"{num7} (capped from {num6})" : num7.ToString()); Plugin.Log.LogInfo((object)$"Quota {___timesFulfilledQuota}: {num} -> {num2}, deadline {num8} days, rollover {text}, overtime {num5} credits."); return false; } catch (Exception ex) { Plugin.Log.LogError((object)("Could not calculate the next quota: " + ex.Message)); return true; } } private static int CalculateNewQuota(int previousQuota, int timesFulfilled) { int value = ConfigManager.FinalLevel.Value; int num; if (value != -1 && previousQuota >= value) { num = previousQuota + Math.Max(0, ConfigManager.FinalIncrease.Value); } else { float num2 = Mathf.Max(0.1f, ConfigManager.CurveSharpness.Value); float num3 = timesFulfilled; float num4 = Mathf.Clamp(1f + num3 * (num3 / num2), 0f, 10000f); float num5 = 1f; float value2 = ConfigManager.RandomizerMultiplier.Value; if (value2 > 0f) { num5 = 1f + Random.Range(-0.5f, 0.5f) * value2; } float num6 = (float)ConfigManager.BaseIncrease.Value * num4 * num5; if (ConfigManager.EnablePlayerMultiplier.Value) { num6 *= CalculatePlayerMultiplier(); } if (ConfigManager.EnableGrowthDampening.Value) { int value3 = ConfigManager.DampeningStartAt.Value; if (timesFulfilled > value3) { float num7 = timesFulfilled - value3; float num8 = Mathf.Max(0.1f, ConfigManager.DampeningSharpness.Value); num6 /= 1f + Mathf.Pow(num7 / num8, 2f); } } num = Mathf.RoundToInt(Mathf.Clamp((float)previousQuota + num6, 0f, 1E+09f)); } int value4 = ConfigManager.QuotaCap.Value; if (value4 == -1) { return num; } return Mathf.Min(num, value4); } private static float CalculatePlayerMultiplier() { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsServer) { return 1f; } int num = Mathf.Max(1, singleton.ConnectedClientsList?.Count ?? 1); int num2 = Mathf.Max(0, ConfigManager.PlayerThreshold.Value); int num3 = num - num2; if (num3 <= 0) { return 1f; } int num4 = ConfigManager.PlayerCap.Value; if (num4 <= num2) { int num5 = num2 + 1; ConfigManager.PlayerCap.Value = num5; num4 = num5; if (!_loggedPlayerCapAutofix) { _loggedPlayerCapAutofix = true; Plugin.Log.LogWarning((object)$"Player scaling config was invalid (PlayerCap <= PlayerThreshold). Auto-fixed PlayerCap to {num5}."); } } int num6 = Mathf.Max(0, num4 - num2); num3 = Mathf.Clamp(num3, 0, num6); return 1f + (float)num3 * Mathf.Max(0f, ConfigManager.MultPerPlayer.Value); } private static int CalculateRollover(int overage) { float value = ConfigManager.RolloverAmount.Value; if (value <= 0f || overage <= 0) { return 0; } return Mathf.RoundToInt((float)overage * Mathf.Clamp01(value)); } private static int SetDeadlineTimer(float dayDuration, ref int days, ref float timeUntilDeadline, int prevDays = -1, bool logSelection = false) { DeadlineSelection deadlineSelection = ResolveDeadlineSelection(prevDays); int num = (days = deadlineSelection.Days); timeUntilDeadline = (float)num * dayDuration; if (logSelection) { Plugin.Log.LogInfo((object)$"Deadline selected ({deadlineSelection.Source}): {num} day(s)."); } return num; } [HarmonyPatch("Awake")] [HarmonyPostfix] private static void TimeOfDay_Awake_Postfix(TimeOfDay __instance) { ApplyQuotaVariables(__instance); } [HarmonyPatch("Start")] [HarmonyPostfix] private static void TimeOfDay_Start_Postfix(TimeOfDay __instance) { ConstellationDeadlineConfig.RefreshSections(); if (__instance.timesFulfilledQuota == 0) { TryApplyInitialDeadlineFromCurrentMode(__instance, allowConstellationOverride: true, logSelection: true); if (((NetworkBehaviour)__instance).IsServer) { NetworkSync.SyncDeadlineToClients(__instance.daysUntilDeadline); Plugin.Log.LogInfo((object)$"Initial deadline synced: {__instance.daysUntilDeadline} days."); } string text = DescribeCurrentDeadlineMode(__instance.daysUntilDeadline); string text2 = ((ConfigManager.QuotaCap.Value != -1) ? $", cap={ConfigManager.QuotaCap.Value}" : ""); string text3 = ((ConfigManager.FinalLevel.Value != -1) ? $", finalLevel={ConfigManager.FinalLevel.Value} (+{ConfigManager.FinalIncrease.Value} flat)" : ""); string text4 = (ConfigManager.CreditPenaltiesEnabled.Value ? $"credits={ConfigManager.CreditPenaltyPercentPerPlayer.Value:P0}/player (cap {ConfigManager.CreditPenaltyPercentCap.Value:P0})" : "credits=off"); string text5 = (ConfigManager.QuotaPenaltiesEnabled.Value ? $"quota={ConfigManager.QuotaPenaltyPercentPerPlayer.Value:P0}/player (cap {ConfigManager.QuotaPenaltyPercentCap.Value:P0})" : "quota=off"); string text6 = $"scrap={ConfigManager.ScrapLossEnabled.Value}" + $", value={ConfigManager.ValueLossEnabled.Value}({ConfigManager.ValueLossPercent.Value:P0})" + $", equip={ConfigManager.EquipmentLossEnabled.Value}"; Plugin.Log.LogInfo((object)$"Settings loaded: quota start {ConfigManager.StartingQuota.Value}, base +{ConfigManager.BaseIncrease.Value}/cycle, sharpness {ConfigManager.CurveSharpness.Value}{text2}{text3}; deadline {text}; credits start {ConfigManager.StartingCredits.Value}; penalties [{text4}, {text5}]; losses [{text6}]."); } if (Chainloader.PluginInfos.ContainsKey("ShaosilGaming.GeneralImprovements")) { ((MonoBehaviour)__instance).StartCoroutine(GIMonitorRefreshRoutine()); } } [IteratorStateMachine(typeof(<GIMonitorRefreshRoutine>d__11))] private static IEnumerator GIMonitorRefreshRoutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <GIMonitorRefreshRoutine>d__11(0); } internal static void RefreshExternalMonitors() { try { Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "GeneralImprovements"); if (!(assembly == null)) { Type type = assembly.GetType("GeneralImprovements.Utilities.MonitorsHelper"); type?.GetMethod("UpdateTotalDaysMonitors")?.Invoke(null, null); type?.GetMethod("UpdateTotalQuotasMonitors")?.Invoke(null, null); } } catch { } } private static void ApplyQuotaVariables(TimeOfDay instance) { try { if (instance.quotaVariables != null) { instance.quotaVariables.startingQuota = ConfigManager.StartingQuota.Value; instance.quotaVa