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 v2.0.0
BepInEx/plugins/ConfigurableQuota/ConfigurableQuota.dll
Decompiled 2 weeks 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("2.0.0.0")] [assembly: AssemblyInformationalVersion("2.0.0+d407c9cd6adf7fb4b7fb3535f854186d21604a56")] [assembly: AssemblyProduct("ConfigurableQuota")] [assembly: AssemblyTitle("ConfigurableQuota")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace 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> OvertimeExcludesRollover; public static ConfigEntry<float> RolloverWipePenalty; public static ConfigEntry<bool> RolloverAlertEnabled; public static ConfigEntry<bool> CreditPenaltiesEnabled; public static ConfigEntry<bool> CreditPenaltiesOnCompanyMoons; 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> QuotaPenaltiesOnCompanyMoons; 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> EnableDeadlineBonus; public static ConfigEntry<int> DeadlineBonusPerQuota; public static ConfigEntry<int> DeadlineBonusMax; public static ConfigEntry<string> DeadlineBonusThresholds; public static ConfigEntry<bool> EnableGrowthDampening; public static ConfigEntry<int> DampeningStartAt; public static ConfigEntry<float> DampeningSharpness; public static ConfigEntry<string> ForcedCompanyMoons; public static ConfigEntry<string> ExcludedCompanyMoons; public static ConfigEntry<bool> ApplyLossesOnCompanyMoons; public static ConfigEntry<bool> OnlyLoseCurrentDayLoot; public static ConfigEntry<int> OnlyCurrentDayMaxPlayers; 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<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> 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; public static ConfigEntry<bool> BeeHiveEnabled; public static ConfigEntry<float> BeeHiveDistance; public static ConfigEntry<int> BeeHiveNearMin; public static ConfigEntry<int> BeeHiveNearMax; public static ConfigEntry<int> BeeHiveFarMin; public static ConfigEntry<int> BeeHiveFarMax; private static void MigrateBool(ConfigFile config, string section, string oldKey, ConfigEntry<bool> target) { string key = ((ConfigEntryBase)target).Definition.Key; ConfigEntry<bool> val = config.Bind<bool>(section, oldKey, false, "Replaced by " + key + "."); bool value = val.Value; config.Remove(((ConfigEntryBase)val).Definition); if (value) { target.Value = true; Plugin.Log.LogInfo((object)("Carried '" + section + "/" + oldKey + "' over to " + key + ".")); } } internal static void Initialize(ConfigFile config) { //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Expected O, but got Unknown //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Expected O, but got Unknown //IL_031c: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Expected O, but got Unknown //IL_036f: Unknown result type (might be due to invalid IL or missing references) //IL_0379: Expected O, but got Unknown //IL_05c6: Unknown result type (might be due to invalid IL or missing references) //IL_05d0: Expected O, but got Unknown //IL_05fe: Unknown result type (might be due to invalid IL or missing references) //IL_0608: Expected O, but got Unknown //IL_0636: Unknown result type (might be due to invalid IL or missing references) //IL_0640: Expected O, but got Unknown //IL_06a4: Unknown result type (might be due to invalid IL or missing references) //IL_06ae: Expected O, but got Unknown //IL_06f7: Unknown result type (might be due to invalid IL or missing references) //IL_0701: Expected O, but got Unknown //IL_07a3: Unknown result type (might be due to invalid IL or missing references) //IL_07ad: Expected O, but got Unknown //IL_07f6: Unknown result type (might be due to invalid IL or missing references) //IL_0800: Expected O, but got Unknown //IL_082e: Unknown result type (might be due to invalid IL or missing references) //IL_0838: Expected O, but got Unknown //IL_089c: Unknown result type (might be due to invalid IL or missing references) //IL_08a6: Expected O, but got Unknown //IL_08d4: Unknown result type (might be due to invalid IL or missing references) //IL_08de: Expected O, but got Unknown //IL_090c: Unknown result type (might be due to invalid IL or missing references) //IL_0916: Expected O, but got Unknown //IL_097a: Unknown result type (might be due to invalid IL or missing references) //IL_0984: Expected O, but got Unknown //IL_09b2: Unknown result type (might be due to invalid IL or missing references) //IL_09bc: Expected O, but got Unknown //IL_09ea: Unknown result type (might be due to invalid IL or missing references) //IL_09f4: Expected O, but got Unknown //IL_0a58: Unknown result type (might be due to invalid IL or missing references) //IL_0a62: Expected O, but got Unknown //IL_0aab: Unknown result type (might be due to invalid IL or missing references) //IL_0ab5: Expected O, but got Unknown //IL_0ad8: Unknown result type (might be due to invalid IL or missing references) //IL_0ae2: Expected O, but got Unknown //IL_0b2b: Unknown result type (might be due to invalid IL or missing references) //IL_0b35: Expected O, but got Unknown //IL_0b7e: Unknown result type (might be due to invalid IL or missing references) //IL_0b88: Expected O, but got Unknown //IL_0bb6: Unknown result type (might be due to invalid IL or missing references) //IL_0bc0: Expected O, but got Unknown //IL_0be3: Unknown result type (might be due to invalid IL or missing references) //IL_0bed: Expected O, but got Unknown //IL_0c36: Unknown result type (might be due to invalid IL or missing references) //IL_0c40: Expected O, but got Unknown //IL_0c99: Unknown result type (might be due to invalid IL or missing references) //IL_0ca3: Expected O, but got Unknown //IL_0cec: Unknown result type (might be due to invalid IL or missing references) //IL_0cf6: Expected O, but got Unknown //IL_0d85: Unknown result type (might be due to invalid IL or missing references) //IL_0d8f: Expected O, but got Unknown //IL_0dbd: Unknown result type (might be due to invalid IL or missing references) //IL_0dc7: Expected O, but got Unknown //IL_0df5: Unknown result type (might be due to invalid IL or missing references) //IL_0dff: Expected O, but got Unknown //IL_0e48: Unknown result type (might be due to invalid IL or missing references) //IL_0e52: Expected O, but got Unknown //IL_0e79: Unknown result type (might be due to invalid IL or missing references) //IL_0e83: Expected O, but got Unknown //IL_0eaa: Unknown result type (might be due to invalid IL or missing references) //IL_0eb4: Expected O, but got Unknown //IL_0edb: Unknown result type (might be due to invalid IL or missing references) //IL_0ee5: Expected O, but got Unknown //IL_0f0f: Unknown result type (might be due to invalid IL or missing references) //IL_0f19: 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."); EnableDeadlineBonus = config.Bind<bool>("0. Basic", "EnableDeadlineBonus", false, "Increase deadline as quota grows using either fixed credit interval or exact thresholds."); DeadlineBonusPerQuota = config.Bind<int>("0. Basic", "DeadlineBonusPerQuota", 1000, "Add one day to deadline for every this many credits of quota. 1000 means 1000 quota gets +1 day, 2000 gets +2 and so on. REQUIRES 'Enable Deadline Bonus' SET TO TRUE."); DeadlineBonusMax = config.Bind<int>("0. Basic", "DeadlineBonusMax", 3, "Maximum days quota bonus can add on top of deadline. 0 means no limit. Applies to both interval and thresholds. REQUIRES 'Enable Deadline Bonus' SET TO TRUE."); DeadlineBonusThresholds = config.Bind<string>("0. Basic", "DeadlineBonusThresholds", "", "Exact quota thresholds instead of fixed interval, comma separated. Example - 1000,2500,5000 adds one day at each one reached. Overrides 'Deadline Bonus Per Quota' whenever it is set. REQUIRES 'Enable Deadline Bonus' SET TO TRUE."); BaseIncrease = config.Bind<int>("0. Basic", "BaseIncrease", 100, new ConfigDescription("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.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000000), Array.Empty<object>())); 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, new ConfigDescription("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%. Above 2 the quota could shrink, so 2 is the limit.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>())); 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 used once the quota reaches Final Level. Replaces the curve, so CurveSharpness stops mattering."); 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>())); OvertimeExcludesRollover = config.Bind<bool>("3. Optional", "OvertimeExcludesRollover", false, "If true, overtime bonus only counts scrap you sold at the Company above the quota. Stops rollover from inflating the bonus. Only affects scrap conversion part (sold / 5), not the days-left part. Requires RolloverAmount > 0 to have any effect."); RolloverWipePenalty = config.Bind<float>("3. Optional", "RolloverWipePenalty", 0f, new ConfigDescription("Percentage of rollover that is lost when the entire crew dies. 0.5 = lose 50%. Requires RolloverAmount > 0 to have any effect.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); RolloverAlertEnabled = config.Bind<bool>("3. Optional", "RolloverAlertEnabled", true, "Show red alert once rollover stops covering next quota, after the new quota screen ends. Local to this client. Requires RolloverAmount > 0 to have any effect."); CreditPenaltiesEnabled = config.Bind<bool>("4. Penalties.Credits", "Enabled", false, "Reduce credits when crew members die."); CreditPenaltiesOnCompanyMoons = config.Bind<bool>("4. Penalties.Credits", "OnCompanyMoons", false, "Apply credit penalties even when visiting company moon."); MigrateBool(config, "4. Penalties.Credits", "OnGordion", CreditPenaltiesOnCompanyMoons); 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."); QuotaPenaltiesOnCompanyMoons = config.Bind<bool>("5. Penalties.Quota", "OnCompanyMoons", false, "Apply quota penalties even when visiting a company moon."); MigrateBool(config, "5. Penalties.Quota", "OnGordion", QuotaPenaltiesOnCompanyMoons); 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. Odds come from ItemsSafeChance and LoseEachScrapChance."); OnlyLoseCurrentDayLoot = config.Bind<bool>("6. Loss.Scrap", "OnlyLoseCurrentDayLoot", false, "Only scrap collected on day of wipe can be lost, anything gathered on earlier days is safe. Applies to Loss Scrap and Loss Value."); MigrateBool(config, "6. Loss.Scrap", "OnlyLoseCurrentRoundLoot", OnlyLoseCurrentDayLoot); OnlyCurrentDayMaxPlayers = config.Bind<int>("6. Loss.Scrap", "OnlyCurrentDayMaxPlayers", 0, new ConfigDescription("Older scrap is only safe while the lobby has this many players or fewer at the wipe. 1 = solo only, 0 = no limit. REQUIRES 'OnlyLoseCurrentDayLoot' SET TO TRUE.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 50), Array.Empty<object>())); 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 wipe. 0 = no limit. Loose scrap is rolled first."); 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 wipe. 1 = at most one item is lost. 0 = no limit."); ForcedCompanyMoons = config.Bind<string>("E. Company.Moons", "ForcedCompanyMoons", "", "Comma separated moon names always treated as a company moon even when not detected as one. Company moon also skips Dynamic sections."); ExcludedCompanyMoons = config.Bind<string>("E. Company.Moons", "ExcludedCompanyMoons", "", "Comma separated moon names never treated as a company moon even when detected as one. Wins over Forced Company Moons when a name is in both."); ApplyLossesOnCompanyMoons = config.Bind<bool>("E. Company.Moons", "ApplyLossesOnCompanyMoons", false, "Apply Loss Scrap, Loss Value and Loss Equipment on a full crew wipe at a company moon too."); 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%. REQUIRES 'JackpotEnabled' SET TO TRUE.", (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%. REQUIRES 'JackpotEnabled' SET TO TRUE.", (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 moons interior based on lobby size."); DynamicInteriorSizeBase = config.Bind<float>("A. Dynamic.Interior.Size", "BaseSize", 1f, new ConfigDescription("Interior size at the PlayerThreshold, times moons own size.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 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, "Multiply value of every scrap moon spawns by player factor below."); DynamicScrapValueMinMult = config.Bind<float>("B. Dynamic.Scrap.Value", "MinValueMultiplier", 1f, new ConfigDescription("Low end of an extra multiplier rolled once per landing from the map seed.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); DynamicScrapValueMaxMult = config.Bind<float>("B. Dynamic.Scrap.Value", "MaxValueMultiplier", 1f, new ConfigDescription("High end of that extra multiplier.", (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, "Multiply moons own min and max scrap count by the player factor below. Titans 28-32 becomes 32-37 at factor 1.15."); DynamicScrapAmountCap = config.Bind<int>("C. Dynamic.Scrap.Amount", "MaxScrapItemsCap", -1, "Most scrap items moon can roll after scaling. -1 = no cap."); 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). REQUIRES 'Enabled' SET TO TRUE."); DynamicEnemyPowerScaleOutside = config.Bind<bool>("D. Dynamic.Enemy.Power", "ScaleOutside", true, "Apply scaling to maxOutsideEnemyPowerCount (night time outside enemies). REQUIRES 'Enabled' SET TO TRUE."); DynamicEnemyPowerScaleDaytime = config.Bind<bool>("D. Dynamic.Enemy.Power", "ScaleDaytime", false, "Apply scaling to maxDaytimeEnemyPowerCount (daytime outside enemies). REQUIRES 'Enabled' SET TO TRUE."); 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>())); BeeHiveEnabled = config.Bind<bool>("F. Bee.Hive", "Enabled", false, "Roll bee hive values from ranges below."); BeeHiveDistance = config.Bind<float>("F. Bee.Hive", "DistanceThreshold", 40f, new ConfigDescription("Hives closer to ship than this many meters use Near range, rest use Far range.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 500f), Array.Empty<object>())); BeeHiveNearMin = config.Bind<int>("F. Bee.Hive", "NearMinValue", 40, new ConfigDescription("Lowest value of hive near ship.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10000), Array.Empty<object>())); BeeHiveNearMax = config.Bind<int>("F. Bee.Hive", "NearMaxValue", 99, new ConfigDescription("Highest value of hive near ship, this value can roll too.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10000), Array.Empty<object>())); BeeHiveFarMin = config.Bind<int>("F. Bee.Hive", "FarMinValue", 50, new ConfigDescription("Lowest value of hive past DistanceThreshold.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10000), Array.Empty<object>())); BeeHiveFarMax = config.Bind<int>("F. Bee.Hive", "FarMaxValue", 149, new ConfigDescription("Highest value of hive past DistanceThreshold.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 10000), Array.Empty<object>())); config.Save(); } } internal static class ModGUIDs { public const string LethalNetworkAPI = "LethalNetworkAPI"; public const string LethalConstellations = "com.github.darmuh.LethalConstellations"; public const string LethalMoonUnlocks = "com.xmods.lethalmoonunlocks"; public const string OpenLib = "darmuh.OpenLib"; public const string LethalLevelLoader = "imabatby.lethallevelloader"; public const string LunarConfig = "Crafty.LunarConfig"; public const string SelfSortingStorage = "zigzag.SelfSortingStorage"; public const string MoreShipUpgrades = "com.malco.lethalcompany.moreshipupgrades"; public const string ScrapInsurance = "com.github.WhiteSpike.ScrapInsurance"; public const string ButteryFixes = "butterystancakes.lethalcompany.butteryfixes"; public const string WeatherRegistry = "mrov.WeatherRegistry"; } [BepInPlugin("com.seeya.configurablequota", "Configurable Quota", "2.0.0")] [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.*/)] [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)"Hello Configurable Quota!"); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "com.seeya.configurablequota"; public const string PLUGIN_NAME = "Configurable Quota"; public const string PLUGIN_VERSION = "2.0.0"; } } namespace ConfigurableQuota.Patches { [HarmonyPatch(typeof(RedLocustBees))] internal static class BeeHivePatch { private static readonly HashSet<RedLocustBees> _rolled = new HashSet<RedLocustBees>(); [HarmonyPatch("SpawnHiveClientRpc")] [HarmonyPrefix] private static void SpawnHiveClientRpc_Prefix(RedLocustBees __instance, NetworkObjectReference hiveObject, ref int hiveScrapValue, Vector3 hivePosition) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (((NetworkBehaviour)__instance).IsServer && !_rolled.Remove(__instance) && ConfigManager.BeeHiveEnabled.Value) { _rolled.Add(__instance); float num = Vector3.Distance(hivePosition, StartOfRound.Instance.shipLandingPosition.position); bool flag = num < ConfigManager.BeeHiveDistance.Value; int num2 = (flag ? ConfigManager.BeeHiveNearMin.Value : ConfigManager.BeeHiveFarMin.Value); int num3 = (flag ? ConfigManager.BeeHiveNearMax.Value : ConfigManager.BeeHiveFarMax.Value); Random random = new Random(StartOfRound.Instance.randomMapSeed + 1314 + (int)((NetworkObjectReference)(ref hiveObject)).NetworkObjectId); hiveScrapValue = random.Next(Mathf.Min(num2, num3), Mathf.Max(num2, num3) + 1); } } } [HarmonyPatch(typeof(TimeOfDay))] internal static class BuyingRatePatch { private const float ReapplyDelaySeconds = 3f; [HarmonyPatch("SetBuyingRateForDay")] [HarmonyPostfix] [HarmonyPriority(800)] private static void SetBuyingRateForDay_Postfix(TimeOfDay __instance) { try { if (((NetworkBehaviour)__instance).IsServer && !ConfigManager.DisableQuota.Value) { StartOfRound instance = StartOfRound.Instance; float vanillaRate = Mathf.Max(0.3f, instance.companyBuyingRate); int daysUntilDeadline = __instance.daysUntilDeadline; (float rate, bool isJackpot, string source) rate = GetRate(vanillaRate, daysUntilDeadline); float item = rate.rate; bool item2 = rate.isJackpot; string item3 = rate.source; instance.companyBuyingRate = item; ((MonoBehaviour)__instance).StartCoroutine(FinalizeBuyRate(item2, item3)); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not apply buy rate modifiers: " + ex.Message)); } } internal static void ApplyReceivedBuyingRate(float rate, bool isJackpot, bool silent = false) { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance != (Object)null) { instance.companyBuyingRate = rate; } if (!silent) { DisplayBuyRateAlert(rate, isJackpot); } } private static (float rate, bool isJackpot, string source) GetRate(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 (rate: item, isJackpot: true, source: value4 ? "jackpot last-day" : "jackpot any-day"); } if (value6 && flag) { if (value7 == num5) { return (rate: value7, isJackpot: false, source: "last-day fixed"); } if (Random.value <= num4) { return (rate: Random.Range(value7, num5), isJackpot: false, source: "last-day ranged"); } return (rate: 1f, isJackpot: false, source: "last-day fallback"); } if (value8 && value) { return (rate: Random.Range(value2, num), isJackpot: false, source: "random in range"); } if (value) { return (rate: Mathf.Clamp(vanillaRate, value2, num), isJackpot: false, source: "min/max clamp"); } return (rate: vanillaRate, isJackpot: false, source: "vanilla"); } private static IEnumerator FinalizeBuyRate(bool isJackpot, string source) { yield return null; int quota = TimeOfDay.Instance.timesFulfilledQuota; float finalRate = StartOfRound.Instance.companyBuyingRate; Plugin.Log.LogInfo((object)$"Buy rate set to {Mathf.RoundToInt(finalRate * 100f)}% ({source})."); NetworkSync.SyncBuyingRateToClients(finalRate, isJackpot); DisplayBuyRateAlert(finalRate, isJackpot); yield return (object)new WaitForSeconds(3f); if (TimeOfDay.Instance.timesFulfilledQuota == quota) { if (!Mathf.Approximately(StartOfRound.Instance.companyBuyingRate, finalRate)) { StartOfRound.Instance.companyBuyingRate = finalRate; Plugin.Log.LogDebug((object)$"Re-applied buy rate {Mathf.RoundToInt(finalRate * 100f)}% after {3f:0.#}s."); } NetworkSync.SyncBuyingRateToClients(finalRate, isJackpot, silent: true); } } internal static void DisplayBuyRateAlert(float rate, bool isJackpot) { try { if (isJackpot ? ConfigManager.JackpotAlertEnabled.Value : ConfigManager.BuyRateAlertEnabled.Value) { int rateRounded = Mathf.RoundToInt(rate * 100f); float delay = Mathf.Max(0f, ConfigManager.AlertDelaySeconds.Value); ((MonoBehaviour)TimeOfDay.Instance).StartCoroutine(AlertCoroutine(rateRounded, isJackpot, delay, TimeOfDay.Instance.timesFulfilledQuota)); } } catch (Exception ex) { Plugin.Log.LogDebug((object)("Could not schedule buy-rate alert: " + ex.Message)); } } private static IEnumerator AlertCoroutine(int rateRounded, bool isJackpot, float delay, int quota) { yield return (object)new WaitForSecondsRealtime(delay); if (TimeOfDay.Instance.timesFulfilledQuota != quota) { yield break; } HUDManager instance = HUDManager.Instance; 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"); } } } [HarmonyPatch(typeof(DepositItemsDesk))] internal static class DepositItemsDeskPatches { internal static int SoldThisQuota; [HarmonyPatch("SellAndDisplayItemProfits")] [HarmonyPostfix] private static void SellAndDisplayItemProfits_Postfix(int profit) { if (NetworkManager.Singleton.IsServer) { SoldThisQuota += profit; } } } public enum PlayerScalingDirection { PerMissingPlayer, PerExtraPlayer } [HarmonyPatch(typeof(RoundManager))] internal static class DynamicLevelPatches { private static int _savedMinScrap; private static int _savedMaxScrap; private static float _savedValueMultiplier; private static bool _valueScaled; private static SelectableLevel? _scrapLevel; private static int _savedMaxEnemyPower; private static int _savedMaxOutsidePower; private static int _savedMaxDaytimePower; private static SelectableLevel? _enemyPowerLevel; internal static float InteriorScale = 1f; private static SelectableLevel? _scaledLevel; private static float _savedInteriorSize; internal static float GetInteriorScale() { if (!ConfigManager.DynamicInteriorSizeEnabled.Value || ConfigManager.DisableQuota.Value) { return 1f; } if (PenaltyHelpers.IsAtCompany()) { return 1f; } float playerFactor = GetPlayerFactor(ConfigManager.DynamicInteriorSizePlayerThreshold.Value, ConfigManager.DynamicInteriorSizeMultPerPlayer.Value, ConfigManager.DynamicInteriorSizeDirection.Value); return Mathf.Max(0.1f, ConfigManager.DynamicInteriorSizeBase.Value * playerFactor); } [HarmonyPatch("GenerateNewFloor")] [HarmonyPrefix] [HarmonyAfter(new string[] { "imabatby.lethallevelloader", "Crafty.LunarConfig" })] private static void GenerateNewFloor_Prefix(RoundManager __instance) { RestoreInteriorSize(); SelectableLevel currentLevel = __instance.currentLevel; if (!((Object)(object)currentLevel == (Object)null) && !Mathf.Approximately(InteriorScale, 1f)) { _scaledLevel = currentLevel; _savedInteriorSize = currentLevel.factorySizeMultiplier; currentLevel.factorySizeMultiplier = _savedInteriorSize * InteriorScale; if (PenaltyHelpers.IsServer) { Plugin.Log.LogInfo((object)$"Interior size set to {currentLevel.factorySizeMultiplier:F2}, the moon's {_savedInteriorSize:F2} times {InteriorScale:F2} for {PenaltyHelpers.GetPlayerCount()} players."); } } } [HarmonyPatch("DespawnPropsAtEndOfRound")] [HarmonyPostfix] private static void RestoreInteriorOnRoundEnd() { RestoreInteriorSize(); if (!PenaltyHelpers.IsServer) { InteriorScale = 1f; } } internal static void RestoreInteriorSize() { if (!((Object)(object)_scaledLevel == (Object)null)) { _scaledLevel.factorySizeMultiplier = _savedInteriorSize; _scaledLevel = null; } } [HarmonyPatch("SpawnScrapInLevel")] [HarmonyPrefix] [HarmonyPriority(0)] [HarmonyAfter(new string[] { "imabatby.lethallevelloader", "Crafty.LunarConfig", "mrov.WeatherRegistry" })] private static void SpawnScrapInLevel_Prefix(RoundManager __instance) { RestoreScrapCounts(); if (ShouldRunServer(__instance, out SelectableLevel level)) { _scrapLevel = level; _savedMinScrap = level.minScrap; _savedMaxScrap = level.maxScrap; _savedValueMultiplier = __instance.scrapValueMultiplier; if (ConfigManager.DynamicScrapValueEnabled.Value) { ApplyScrapValue(__instance); } if (ConfigManager.DynamicScrapAmountEnabled.Value) { ApplyScrapAmount(level); } } } [HarmonyPatch("SpawnScrapInLevel")] [HarmonyFinalizer] private static void SpawnScrapInLevel_Finalizer(RoundManager __instance) { if (_valueScaled) { __instance.scrapValueMultiplier = _savedValueMultiplier; } _valueScaled = false; RestoreScrapCounts(); } private static void RestoreScrapCounts() { if (!((Object)(object)_scrapLevel == (Object)null)) { _scrapLevel.minScrap = _savedMinScrap; _scrapLevel.maxScrap = _savedMaxScrap; _scrapLevel = null; } } [HarmonyPatch("GenerateNewFloor")] [HarmonyPrefix] [HarmonyAfter(new string[] { "imabatby.lethallevelloader", "Crafty.LunarConfig" })] private static void EnemyPower_GenerateNewFloor_Prefix(RoundManager __instance) { RestoreEnemyPower(); if (ShouldRunServer(__instance, out SelectableLevel level) && ConfigManager.DynamicEnemyPowerEnabled.Value) { _enemyPowerLevel = level; _savedMaxEnemyPower = level.maxEnemyPowerCount; _savedMaxOutsidePower = level.maxOutsideEnemyPowerCount; _savedMaxDaytimePower = level.maxDaytimeEnemyPowerCount; float playerFactor = GetPlayerFactor(ConfigManager.DynamicEnemyPowerPlayerThreshold.Value, ConfigManager.DynamicEnemyPowerMultPerPlayer.Value, PlayerScalingDirection.PerExtraPlayer); playerFactor = Mathf.Min(playerFactor, Mathf.Max(1f, ConfigManager.DynamicEnemyPowerMaxFactor.Value)); if (ConfigManager.DynamicEnemyPowerScaleInside.Value) { level.maxEnemyPowerCount = Mathf.RoundToInt((float)_savedMaxEnemyPower * playerFactor); } if (ConfigManager.DynamicEnemyPowerScaleOutside.Value) { level.maxOutsideEnemyPowerCount = Mathf.RoundToInt((float)_savedMaxOutsidePower * playerFactor); } if (ConfigManager.DynamicEnemyPowerScaleDaytime.Value) { level.maxDaytimeEnemyPowerCount = Mathf.RoundToInt((float)_savedMaxDaytimePower * playerFactor); } Plugin.Log.LogInfo((object)($"Enemy power set for {PenaltyHelpers.GetPlayerCount()} players, factor {playerFactor:F2}. " + $"Inside {level.maxEnemyPowerCount}, outside {level.maxOutsideEnemyPowerCount}, daytime {level.maxDaytimeEnemyPowerCount}.")); } } [HarmonyPatch("DespawnPropsAtEndOfRound")] [HarmonyPostfix] private static void EnemyPower_RestoreOnRoundEnd() { RestoreEnemyPower(); } internal static void RestoreEnemyPower() { if (!((Object)(object)_enemyPowerLevel == (Object)null)) { _enemyPowerLevel.maxEnemyPowerCount = _savedMaxEnemyPower; _enemyPowerLevel.maxOutsideEnemyPowerCount = _savedMaxOutsidePower; _enemyPowerLevel.maxDaytimeEnemyPowerCount = _savedMaxDaytimePower; _enemyPowerLevel = null; } } 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.IsAtCompany()) { return false; } return true; } private static bool ShouldRunServer(RoundManager rm, out SelectableLevel level) { if (!PenaltyHelpers.IsServer) { level = null; return false; } return ShouldRun(rm, out level); } private static float GetPlayerFactor(int threshold, float mult, PlayerScalingDirection direction) { int num = ((direction == PlayerScalingDirection.PerMissingPlayer) ? Mathf.Max(0, threshold - PenaltyHelpers.GetPlayerCount()) : Mathf.Max(0, PenaltyHelpers.GetPlayerCount() - threshold)); return 1f + (float)num * Mathf.Max(0f, mult); } private static void ApplyScrapValue(RoundManager rm) { float playerFactor = GetPlayerFactor(ConfigManager.DynamicScrapValuePlayerThreshold.Value, ConfigManager.DynamicScrapValueMultPerPlayer.Value, ConfigManager.DynamicScrapValueDirection.Value); float value = ConfigManager.DynamicScrapValueMinMult.Value; float num = Mathf.Max(value, ConfigManager.DynamicScrapValueMaxMult.Value); Random random = new Random(StartOfRound.Instance.randomMapSeed + 2718); float num2 = value + (float)random.NextDouble() * (num - value); rm.scrapValueMultiplier = _savedValueMultiplier * playerFactor * num2; _valueScaled = true; Plugin.Log.LogInfo((object)$"Scrap value multiplier set to {rm.scrapValueMultiplier:F2}, the moon's {_savedValueMultiplier:F2} times {playerFactor * num2:F2}."); } private static void ApplyScrapAmount(SelectableLevel level) { float playerFactor = GetPlayerFactor(ConfigManager.DynamicScrapAmountPlayerThreshold.Value, ConfigManager.DynamicScrapAmountMultPerPlayer.Value, ConfigManager.DynamicScrapAmountDirection.Value); int num = Mathf.Max(1, Mathf.RoundToInt((float)_savedMinScrap * playerFactor)); int num2 = Mathf.Max(num, Mathf.RoundToInt((float)_savedMaxScrap * playerFactor)); int value = ConfigManager.DynamicScrapAmountCap.Value; if (value >= 0) { num2 = Mathf.Min(num2, value); num = Mathf.Min(num, num2); } level.minScrap = num; level.maxScrap = num2; Plugin.Log.LogInfo((object)$"Scrap amount set to {num} to {num2} items from the moon's {_savedMinScrap} to {_savedMaxScrap}, factor {playerFactor:F2}."); } } [HarmonyPatch(typeof(StartOfRound))] internal static class InteriorScaleSyncPatch { [HarmonyPatch("Start")] [HarmonyPostfix] private static void Start_Postfix() { DynamicLevelPatches.RestoreInteriorSize(); DynamicLevelPatches.RestoreEnemyPower(); DynamicLevelPatches.InteriorScale = 1f; } [HarmonyPatch("OnDestroy")] [HarmonyPostfix] private static void OnDestroy_Postfix() { DynamicLevelPatches.RestoreInteriorSize(); DynamicLevelPatches.RestoreEnemyPower(); } [HarmonyPatch("StartGame")] [HarmonyPrefix] private static void StartGame_Prefix(StartOfRound __instance) { if (!((NetworkBehaviour)__instance).IsServer || !__instance.inShipPhase) { return; } try { NetworkSync.SyncInteriorScaleToClients(DynamicLevelPatches.InteriorScale = DynamicLevelPatches.GetInteriorScale()); } catch (Exception ex) { DynamicLevelPatches.InteriorScale = 1f; Plugin.Log.LogWarning((object)("Could not work out the interior size, using the normal size: " + ex.Message)); } } } [HarmonyPatch(typeof(GameNetworkManager))] internal static class GameNetworkManagerPatches { private const float SettleTimeoutSeconds = 12f; private static int _firedResetGeneration; [HarmonyPatch("Start")] [HarmonyPostfix] [HarmonyPriority(800)] private static void Start_Postfix() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown Harmony harmony = new Harmony("com.seeya.configurablequota"); try { SelfSortingStorageCompat.Init(harmony); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not set up SSS compatibility: " + ex.Message)); } try { HQoLCompat.Init(harmony); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Could not set up HQoL compatibility: " + ex2.Message)); } } [HarmonyPatch("ResetSavedGameValues")] [HarmonyPostfix] [HarmonyAfter(new string[] { "com.xmods.lethalmoonunlocks" })] private static void ResetSavedGameValues_Postfix() { try { QuotaSaveData.Clear(); TimeOfDay instance = TimeOfDay.Instance; if (!((Object)(object)instance == (Object)null) && ((NetworkBehaviour)instance).IsServer) { TimeOfDayQuotaPatch.ResetInitialDeadlineTracking(); int generation = ++_firedResetGeneration; if (!LethalConstellationsCompat.IsInstalled) { ((MonoBehaviour)instance).StartCoroutine(ReapplyDeadlineAfterShipReset(generation)); return; } LethalConstellationsCompat.TryGetCurrentConstellationName(out string constellationName); ((MonoBehaviour)instance).StartCoroutine(ReapplyDeadlineWhenConstellationSettles(generation, constellationName)); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not schedule deadline reapply after firing: " + ex.Message)); } } private static IEnumerator ReapplyDeadlineAfterShipReset(int generation) { float elapsed = 0f; while (elapsed < 12f && generation == _firedResetGeneration) { TimeOfDay instance = TimeOfDay.Instance; if (instance.timesFulfilledQuota == 0) { ApplySettledDeadline(instance); break; } elapsed += Time.deltaTime; yield return null; } } private static IEnumerator ReapplyDeadlineWhenConstellationSettles(int generation, string staleConstellation) { bool hadStale = !string.IsNullOrEmpty(staleConstellation); float elapsed = 0f; TimeOfDay instance; while (true) { if (generation != _firedResetGeneration) { yield break; } instance = TimeOfDay.Instance; bool flag = false; if (instance.timesFulfilledQuota == 0) { string constellationName; bool flag2 = LethalConstellationsCompat.TryGetCurrentConstellationName(out constellationName); if (hadStale) { flag = flag2 && !string.Equals(constellationName, staleConstellation, StringComparison.OrdinalIgnoreCase); } } if (flag || elapsed >= 12f) { break; } elapsed += Time.deltaTime; yield return null; } ApplySettledDeadline(instance); } private static void ApplySettledDeadline(TimeOfDay tod) { try { if (tod.timesFulfilledQuota == 0 && ((NetworkBehaviour)tod).IsServer) { ConstellationDeadlineConfig.RefreshSections(); TimeOfDayQuotaPatch.ResetInitialDeadlineTracking(); if (TimeOfDayQuotaPatch.TryApplyInitialDeadlineFromCurrentMode(tod, allowConstellationOverride: true, logSelection: true)) { NetworkSync.SyncDeadlineToClients(tod.daysUntilDeadline); TimeOfDayQuotaPatch.RefreshExternalMonitors(); Plugin.Log.LogInfo((object)$"Deadline reapplied after firing: {tod.daysUntilDeadline} days."); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not reapply deadline after firing: " + ex.Message)); } } } [HarmonyPatch(typeof(HUDManager))] internal static class HudQuotaAnimationPatch { [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; } private static IEnumerator CustomRackUp(HUDManager hud, float speed) { yield return (object)new WaitForSeconds(3.5f / speed); int quotaTextAmount = 0; int target = TimeOfDay.Instance.profitQuota; while (quotaTextAmount < target) { float num = Time.deltaTime * 250f * speed; quotaTextAmount = (int)Mathf.Clamp((float)quotaTextAmount + num, (float)(quotaTextAmount + 3), (float)(target + 10)); ((TMP_Text)hud.newProfitQuotaText).text = "$" + quotaTextAmount; yield return null; } ((TMP_Text)hud.newProfitQuotaText).text = "$" + target; TimeOfDay.Instance.UpdateProfitQuotaCurrentTime(); hud.UIAudio.PlayOneShot(hud.newProfitQuotaSFX); yield return (object)new WaitForSeconds(1.25f / Mathf.Clamp(speed, 0.5f, 2f)); hud.displayingNewQuota = false; hud.reachedProfitQuotaAnimator.SetBool("display", false); } [HarmonyPatch("FillEndGameStats")] [HarmonyPrefix] private static void FillEndGameStats_Prefix() { PenaltiesOnLandingPatch.HasAllDeadSnapshot = false; PenaltiesOnLandingPatch.ClearScrapLossSummary(); } [HarmonyPatch("FillEndGameStats")] [HarmonyPostfix] [HarmonyAfter(new string[] { "com.example.Advancedfeatures" })] private static void FillEndGameStats_Postfix() { TryApplyAdvancedFeaturesEndscreen(); } internal static void TryApplyAdvancedFeaturesEndscreen() { if (!Chainloader.PluginInfos.ContainsKey("com.example.Advancedfeatures") || !PenaltiesOnLandingPatch.HasAllDeadSnapshot) { return; } try { if (!((Behaviour)HUDManager.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() { if (!PenaltiesOnLandingPatch.HasPenaltyCache) { return !PenaltyHelpers.CreditPenaltiesApply(PenaltyHelpers.IsAtCompany()); } return !PenaltiesOnLandingPatch.Summary.CreditApplied; } [HarmonyPatch("ApplyPenalty")] [HarmonyPostfix] [HarmonyAfter(new string[] { "butterystancakes.lethalcompany.butteryfixes" })] private static void ApplyPenalty_Postfix(HUDManager __instance, int playersDead, int bodiesInsured) { try { bool atCompany = PenaltyHelpers.IsAtCompany(); SyncPenaltySummary penaltySummary = GetPenaltySummary(playersDead, bodiesInsured, atCompany); if (!penaltySummary.CreditApplied) { if (penaltySummary.QuotaPercent > 0f) { TextMeshProUGUI penaltyAddition = __instance.statsUIElements.penaltyAddition; ((TMP_Text)penaltyAddition).text = ((TMP_Text)penaltyAddition).text + $"\n\nQuota: {Mathf.RoundToInt(penaltySummary.QuotaPercent * 100f)}% (${penaltySummary.QuotaDelta})"; } } else { ((TMP_Text)__instance.statsUIElements.penaltyAddition).text = BuildPenaltyText(penaltySummary.Dead, penaltySummary.Recovered, penaltySummary.CreditPercent, penaltySummary.QuotaPercent, penaltySummary.QuotaDelta); ((TMP_Text)__instance.statsUIElements.penaltyTotal).text = ((penaltySummary.CreditLoss > 0) ? $"DUE: ${penaltySummary.CreditLoss}" : ""); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not update penalty text on the end screen: " + ex.Message)); } } private static SyncPenaltySummary GetPenaltySummary(int playersDead, int bodiesInsured, bool atCompany) { if (PenaltiesOnLandingPatch.HasPenaltyCache) { return PenaltiesOnLandingPatch.Summary; } var (num, num2, recovered) = PenaltyHelpers.CountDeathsAndRecovered(); if (num == 0 && playersDead > 0) { num = playersDead; recovered = bodiesInsured; num2 = Mathf.Max(num + 1, num2); } var (creditPercent, creditLoss) = GetCreditPenalty(num, num2, recovered, atCompany); var (quotaPercent, quotaDelta) = GetQuotaPenalty(num, num2, recovered, atCompany); return new SyncPenaltySummary(num, num2, recovered, PenaltyHelpers.CreditPenaltiesApply(atCompany), creditPercent, creditLoss, quotaPercent, quotaDelta); } private static (float pct, int loss) GetCreditPenalty(int dead, int total, int recovered, bool atCompany) { if (dead == 0 || !PenaltyHelpers.CreditPenaltiesApply(atCompany)) { return (pct: 0f, loss: 0); } int num = Object.FindObjectOfType<Terminal>()?.groupCredits ?? 0; float penaltyPercent = PenaltyHelpers.GetPenaltyPercent(ConfigManager.CreditPenaltiesDynamic.Value, ConfigManager.CreditPenaltyPercentPerPlayer.Value, ConfigManager.CreditPenaltyPercentCap.Value, ConfigManager.CreditPenaltyPercentThreshold.Value, ConfigManager.CreditPenaltyRecoveryBonus.Value, dead, total, recovered); penaltyPercent = MoreShipUpgradesCompat.ApplyLifeInsurance(penaltyPercent); return (pct: penaltyPercent, loss: Mathf.RoundToInt((float)num * penaltyPercent)); } private static (float pct, int delta) GetQuotaPenalty(int dead, int total, int recovered, bool atCompany) { if (dead == 0 || !PenaltyHelpers.QuotaPenaltiesApply(atCompany)) { return (pct: 0f, delta: 0); } float penaltyPercent = PenaltyHelpers.GetPenaltyPercent(ConfigManager.QuotaPenaltiesDynamic.Value, ConfigManager.QuotaPenaltyPercentPerPlayer.Value, ConfigManager.QuotaPenaltyPercentCap.Value, ConfigManager.QuotaPenaltyPercentThreshold.Value, ConfigManager.QuotaPenaltyRecoveryBonus.Value, dead, total, recovered); int num = PenaltiesOnLandingPatch.CachedQuotaPenaltyDelta; if (num <= 0) { num = Mathf.RoundToInt((float)Mathf.Max(1, TimeOfDay.Instance.profitQuota) * penaltyPercent); } return (pct: penaltyPercent, delta: num); } 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; } } [HarmonyPatch(typeof(StartOfRound))] internal static class LateJoinSync { [HarmonyPatch("SyncAlreadyHeldObjectsServerRpc")] [HarmonyPostfix] private static void SyncAlreadyHeldObjectsServerRpc_Postfix(StartOfRound __instance, int joiningClientId) { if (((NetworkBehaviour)__instance).IsServer) { ulong num = (ulong)joiningClientId; if (num != NetworkManager.Singleton.LocalClientId) { ResyncToClient(num); } } } private static void ResyncToClient(ulong clientId) { try { if (NetworkManager.Singleton.ConnectedClients.ContainsKey(clientId)) { TimeOfDay instance = TimeOfDay.Instance; NetworkSync.SyncDeadlineLengthToClient(instance.quotaVariables.deadlineDaysAmount, clientId); NetworkSync.SyncQuotaToClient(instance.profitQuota, clientId); NetworkSync.SyncRolloverToClient(instance.quotaFulfilled, clientId); NetworkSync.SyncBuyingRateToClient(StartOfRound.Instance.companyBuyingRate, isJackpot: false, clientId, silent: true); } } catch (Exception ex) { Plugin.Log.LogWarning((object)$"Late join resync to client {clientId} failed: {ex.Message}"); } } } internal static class NetworkSync { private static LNetworkMessage<SyncQuotaData>? _syncQuotaMessage; private static LNetworkMessage<SyncValueLossData>? _syncValueLossMessage; private static LNetworkMessage<int>? _syncDeadlineMessage; private static LNetworkMessage<int>? _syncRolloverMessage; private static LNetworkMessage<RolloverStatusData>? _rolloverStatusMessage; private static LNetworkMessage<SyncScrapLossSummary>? _syncScrapLossSummaryMessage; private static LNetworkMessage<SyncBuyingRateData>? _syncBuyingRateMessage; private static LNetworkMessage<SyncPenaltySummary>? _syncPenaltySummaryMessage; private static LNetworkMessage<float>? _syncInteriorScaleMessage; private static LNetworkMessage<int>? _syncDeadlineLengthMessage; public static void Initialize() { try { _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); _rolloverStatusMessage = LNetworkMessage<RolloverStatusData>.Connect("ConfigurableQuota_RolloverStatus", (Action<RolloverStatusData, ulong>)null, (Action<RolloverStatusData>)OnRolloverStatusReceived, (Action<RolloverStatusData, 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); _syncPenaltySummaryMessage = LNetworkMessage<SyncPenaltySummary>.Connect("ConfigurableQuota_SyncPenaltySummary", (Action<SyncPenaltySummary, ulong>)null, (Action<SyncPenaltySummary>)OnPenaltySummaryReceived, (Action<SyncPenaltySummary, ulong>)null); _syncInteriorScaleMessage = LNetworkMessage<float>.Connect("ConfigurableQuota_SyncInteriorScale", (Action<float, ulong>)null, (Action<float>)OnInteriorScaleReceived, (Action<float, ulong>)null); _syncDeadlineLengthMessage = LNetworkMessage<int>.Connect("ConfigurableQuota_SyncDeadlineLength", (Action<int, ulong>)null, (Action<int>)OnDeadlineLengthReceived, (Action<int, ulong>)null); Plugin.Log.LogDebug((object)"Network messages registered."); } catch (Exception ex) { Plugin.Log.LogError((object)("Could not initialize network sync: " + 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) { if (NetworkManager.Singleton.IsServer) { return; } try { if (data.BeforeValue > 0) { PenaltiesOnLandingPatch.CacheScrapLossSummary(data.BeforeValue, data.AfterValue); } else { PenaltiesOnLandingPatch.ClearScrapLossSummary(); } PenaltiesOnLandingPatch.HasAllDeadSnapshot = true; HudQuotaAnimationPatch.TryApplyAdvancedFeaturesEndscreen(); } catch (Exception ex) { Plugin.Log.LogError((object)("Could not apply synced scrap loss summary on client: " + ex.Message)); } } public static void SyncPenaltySummaryToClients(SyncPenaltySummary summary) { try { if (_syncPenaltySummaryMessage == null) { Plugin.Log.LogWarning((object)"Penalty summary message not initialized"); } else { _syncPenaltySummaryMessage.SendClients(summary); } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not sync penalty summary: " + ex.Message)); } } private static void OnPenaltySummaryReceived(SyncPenaltySummary summary) { if (!NetworkManager.Singleton.IsServer) { PenaltiesOnLandingPatch.Summary = summary; PenaltiesOnLandingPatch.HasPenaltyCache = true; } } public static void SyncInteriorScaleToClients(float scale) { try { if (_syncInteriorScaleMessage == null) { Plugin.Log.LogWarning((object)"Interior scale message not initialized"); } else { _syncInteriorScaleMessage.SendClients(scale); } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not sync interior scale: " + ex.Message)); } } private static void OnInteriorScaleReceived(float scale) { if (!NetworkManager.Singleton.IsServer) { DynamicLevelPatches.InteriorScale = scale; } } 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)); } } public static void SyncQuotaToClient(int quota, ulong clientId) { try { if (_syncQuotaMessage == null) { Plugin.Log.LogWarning((object)"Quota message not initialized"); } else { _syncQuotaMessage.SendClient(new SyncQuotaData(quota, 0), clientId); } } catch (Exception ex) { Plugin.Log.LogError((object)$"Could not sync quota to client {clientId}: {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 { NetworkSpawnManager spawnManager = NetworkManager.Singleton.SpawnManager; if (!spawnManager.SpawnedObjects.TryGetValue(data.NetworkObjectId, out var value) || (Object)(object)value == (Object)null) { return; } GrabbableObject component = ((Component)value).GetComponent<GrabbableObject>(); if ((Object)(object)component == (Object)null) { return; } Item itemProperties = component.itemProperties; if (itemProperties == null || !itemProperties.isScrap) { return; } component.scrapValue = data.NewValue; try { component.SetScrapValue(data.NewValue); } 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 { if (ApplyDeadline(days) && !NetworkManager.Singleton.IsServer) { TimeOfDay.Instance.UpdateProfitQuotaCurrentTime(); TimeOfDayQuotaPatch.RefreshExternalMonitors(); } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not apply synced deadline on client: " + ex.Message)); } } public static void SyncDeadlineLengthToClient(int days, ulong clientId) { try { if (_syncDeadlineLengthMessage == null) { Plugin.Log.LogWarning((object)"Deadline length message not initialized"); } else { _syncDeadlineLengthMessage.SendClient(days, clientId); } } catch (Exception ex) { Plugin.Log.LogError((object)$"Could not sync deadline length to client {clientId}: {ex.Message}"); } } private static void OnDeadlineLengthReceived(int days) { if (!NetworkManager.Singleton.IsServer) { TimeOfDay.Instance.quotaVariables.deadlineDaysAmount = Mathf.Max(1, days); } } private static bool ApplyDeadline(int days) { TimeOfDay instance = TimeOfDay.Instance; if ((Object)(object)instance == (Object)null) { return false; } 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; } return true; } 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)); } } public static void SyncRolloverToClient(int rollover, ulong clientId) { try { if (_syncRolloverMessage == null) { Plugin.Log.LogWarning((object)"Rollover message not initialized"); } else { _syncRolloverMessage.SendClient(rollover, clientId); } } catch (Exception ex) { Plugin.Log.LogError((object)$"Could not sync rollover to client {clientId}: {ex.Message}"); } } private static void OnRolloverReceived(int rollover) { try { TimeOfDay instance = TimeOfDay.Instance; if ((Object)(object)instance != (Object)null) { instance.quotaFulfilled = Math.Max(0, rollover); } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not apply synced rollover on client: " + ex.Message)); } } public static void ReportRolloverStatus(int bank, int quota, int deadline) { ShowRolloverStatus(bank, quota, deadline); try { if (_rolloverStatusMessage == null) { Plugin.Log.LogWarning((object)"Rollover status message not initialized"); } else { _rolloverStatusMessage.SendClients(new RolloverStatusData(bank, quota, deadline)); } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not send the rollover status: " + ex.Message)); } } private static void OnRolloverStatusReceived(RolloverStatusData data) { if (!NetworkManager.Singleton.IsServer) { ShowRolloverStatus(data.Bank, data.Quota, data.Deadline); } } private static void ShowRolloverStatus(int bank, int quota, int deadline) { try { if (ConfigManager.RolloverAlertEnabled.Value) { HUDManager instance = HUDManager.Instance; ((MonoBehaviour)instance).StartCoroutine(RolloverStatusRoutine(instance, bank, quota, deadline)); } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not show the rollover status: " + ex.Message)); } } private static IEnumerator RolloverStatusRoutine(HUDManager hud, int bank, int quota, int deadline) { yield return (object)new WaitForSecondsRealtime(1f); float waited = 0f; while (hud.displayingNewQuota && waited < 60f) { waited += Time.unscaledDeltaTime; yield return null; } yield return (object)new WaitForSecondsRealtime(1.5f); string text = ((deadline == 1) ? "1 day" : $"{deadline} days"); hud.DisplayTip("<color=#ffc526>ROLLOVER SPENT</color>", $"<color=#fcbf17>\n* Rollover only covers ${bank} of ${quota}, sell ${quota - bank} within {text}</color>", true, false, "LC_RolloverTip"); } public static void SyncBuyingRateToClients(float rate, bool isJackpot, bool silent = false) { try { if (_syncBuyingRateMessage == null) { Plugin.Log.LogWarning((object)"Buying rate message not initialized"); } else { _syncBuyingRateMessage.SendClients(new SyncBuyingRateData(rate, isJackpot, silent)); } } catch (Exception ex) { Plugin.Log.LogError((object)("Could not sync buying rate: " + ex.Message)); } } public static void SyncBuyingRateToClient(float rate, bool isJackpot, ulong clientId, bool silent = false) { try { if (_syncBuyingRateMessage == null) { Plugin.Log.LogWarning((object)"Buying rate message not initialized"); } else { _syncBuyingRateMessage.SendClient(new SyncBuyingRateData(rate, isJackpot, silent), clientId); } } catch (Exception ex) { Plugin.Log.LogError((object)$"Could not sync buying rate to client {clientId}: {ex.Message}"); } } private static void OnBuyingRateReceived(SyncBuyingRateData data) { if (NetworkManager.Singleton.IsServer) { return; } try { BuyingRatePatch.ApplyReceivedBuyingRate(data.Rate, data.IsJackpot, data.Silent); } 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 RolloverStatusData { public int Bank; public int Quota; public int Deadline; public RolloverStatusData(int bank, int quota, int deadline) { Bank = bank; Quota = quota; Deadline = deadline; } } [Serializable] public struct SyncBuyingRateData { public float Rate; public bool IsJackpot; public bool Silent; public SyncBuyingRateData(float rate, bool isJackpot, bool silent = false) { Rate = rate; IsJackpot = isJackpot; Silent = silent; } } [Serializable] public struct SyncValueLossData { public ulong NetworkObjectId; public int NewValue; public SyncValueLossData(ulong networkObjectId, int newValue) { NetworkObjectId = networkObjectId; NewValue = newValue; } } [Serializable] public struct SyncPenaltySummary { public int Dead; public int Total; public int Recovered; public bool CreditApplied; public float CreditPercent; public int CreditLoss; public float QuotaPercent; public int QuotaDelta; public SyncPenaltySummary(int dead, int total, int recovered, bool creditApplied, float creditPercent, int creditLoss, float quotaPercent, int quotaDelta) { Dead = dead; Total = total; Recovered = recovered; CreditApplied = creditApplied; CreditPercent = creditPercent; CreditLoss = creditLoss; QuotaPercent = quotaPercent; QuotaDelta = quotaDelta; } } [Serializable] public struct SyncScrapLossSummary { public int BeforeValue; public int AfterValue; public SyncScrapLossSummary(int beforeValue, int afterValue) { BeforeValue = beforeValue; AfterValue = afterValue; } } internal static class PenaltyHelpers { private static readonly HashSet<int> _recoveredAtTakeoff = new HashSet<int>(); private static SelectableLevel? _classifiedLevel; private static string _classifiedLists = string.Empty; private static bool _classifiedIsCompany; public static bool IsServer => NetworkManager.Singleton.IsServer; public static int GetPlayerCount() { return StartOfRound.Instance.connectedPlayersAmount + 1; } public static (int dead, int total, int recovered) CountDeathsAndRecovered() { int num = 0; int num2 = 0; int num3 = 0; DeadBodyInfo[] bodies = null; PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts; for (int i = 0; i < allPlayerScripts.Length; i++) { PlayerControllerB val = allPlayerScripts[i]; if ((Object)(object)val == (Object)null) { continue; } if (val.isPlayerControlled || val.isPlayerDead) { num2++; } if (val.isPlayerDead) { num++; if (_recoveredAtTakeoff.Contains(i) || IsBodyInShip(FindBody(val, ref bodies))) { num3++; } } } return (dead: num, total: Math.Max(num2, 1), recovered: num3); } internal static void TakeBodySnapshot() { _recoveredAtTakeoff.Clear(); DeadBodyInfo[] bodies = null; PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts; for (int i = 0; i < allPlayerScripts.Length; i++) { PlayerControllerB val = allPlayerScripts[i]; if ((Object)(object)val != (Object)null && val.isPlayerDead && IsBodyInShip(FindBody(val, ref bodies))) { _recoveredAtTakeoff.Add(i); } } Plugin.Log.LogDebug((object)$"Bodies in the ship at takeoff: {_recoveredAtTakeoff.Count}."); } internal static void ClearBodySnapshot() { _recoveredAtTakeoff.Clear(); } private static DeadBodyInfo? FindBody(PlayerControllerB player, ref DeadBodyInfo[]? bodies) { if ((Object)(object)player.deadBody != (Object)null) { return player.deadBody; } if (bodies == null) { bodies = Object.FindObjectsOfType<DeadBodyInfo>(); } DeadBodyInfo[] array = bodies; foreach (DeadBodyInfo val in array) { if ((Object)(object)val != (Object)null && (Object)(object)val.playerScript == (Object)(object)player) { return val; } } return null; } private static bool IsBodyInShip(DeadBodyInfo? body) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)body == (Object)null || body.deactivated) { return false; } if (body.isInShip) { return true; } GrabbableObject grabBodyObject = body.grabBodyObject; if ((Object)(object)grabBodyObject != (Object)null && grabBodyObject.isInShipRoom) { return true; } Vector3 val = (((Object)(object)grabBodyObject != (Object)null) ? ((Component)grabBodyObject).transform.position : ((Component)body).transform.position); Bounds bounds = StartOfRound.Instance.shipBounds.bounds; return ((Bounds)(ref bounds)).Contains(val); } public static bool IsAtCompany() { try { SelectableLevel currentLevel = StartOfRound.Instance.currentLevel; if ((Object)(object)currentLevel == (Object)null) { return false; } string text = ConfigManager.ForcedCompanyMoons.Value + "|" + ConfigManager.ExcludedCompanyMoons.Value; if (currentLevel != _classifiedLevel || text != _classifiedLists) { _classifiedLevel = currentLevel; _classifiedLists = text; _classifiedIsCompany = ClassifyCompanyMoon(currentLevel); } return _classifiedIsCompany; } catch { return false; } } public static bool CreditPenaltiesApply(bool atCompany) { if (ConfigManager.CreditPenaltiesEnabled.Value) { if (atCompany) { return ConfigManager.CreditPenaltiesOnCompanyMoons.Value; } return true; } return false; } public static bool QuotaPenaltiesApply(bool atCompany) { if (ConfigManager.QuotaPenaltiesEnabled.Value) { if (atCompany) { return ConfigManager.QuotaPenaltiesOnCompanyMoons.Value; } return true; } return false; } private static bool ClassifyCompanyMoon(SelectableLevel level) { if (MatchesMoonList(level, ConfigManager.ExcludedCompanyMoons.Value)) { return false; } if (MatchesMoonList(level, ConfigManager.ForcedCompanyMoons.Value)) { return true; } if (level.sceneName == "CompanyBuilding") { return true; } return !level.planetHasTime && !level.spawnEnemiesAndScrap && level.maxEnemyPowerCount <= 0 && level.maxOutsideEnemyPowerCount <= 0; } private static bool MatchesMoonList(SelectableLevel level, string raw) { if (string.IsNullOrWhiteSpace(raw)) { return false; } string text = level.PlanetName ?? string.Empty; int num = text.IndexOf(' '); string b = ((num >= 0) ? text.Substring(num + 1) : text); string[] array = raw.Split(','); foreach (string text2 in array) { string text3 = text2.Trim(); if (text3.Length != 0) { if (string.Equals(text3, level.sceneName, StringComparison.OrdinalIgnoreCase)) { return true; } if (string.Equals(text3, text, StringComparison.OrdinalIgnoreCase)) { return true; } if (string.Equals(text3, b, StringComparison.OrdinalIgnoreCase)) { return true; } } } return false; } public static float GetPenaltyPercent(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 { internal static bool _appliedThisRound; private static bool _creditScheduled; internal static bool _lossesAppliedThisRound; internal static SyncPenaltySummary Summary; 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 ResetCreditSchedule() { _creditScheduled = false; } private static void ApplyPenalties(int dead, int total, int recovered, bool atCompany) { bool flag = PenaltyHelpers.CreditPenaltiesApply(atCompany); Summary = new SyncPenaltySummary(dead, total, recovered, flag, 0f, 0, 0f, 0); HasPenaltyCache = true; if (flag) { ScheduleCreditPenalty(dead, total, recovered); } if (PenaltyHelpers.QuotaPenaltiesApply(atCompany)) { ApplyQuotaPenalty(dead, total, recovered); } NetworkSync.SyncPenaltySummaryToClients(Summary); } 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) { bool flag = false; try { if (!PenaltyHelpers.IsServer) { return true; } HasPenaltyCache = false; HasAllDeadSnapshot = false; CachedQuotaPenaltyDelta = 0; ClearScrapLossSummary(); SelfSortingStorageCompat.Processed = false; HQoLCompat.Processed = false; bool flag2 = PenaltyHelpers.IsAtCompany(); (int dead, int total, int recovered) tuple = PenaltyHelpers.CountDeathsAndRecovered(); int item = tuple.dead; int item2 = tuple.total; int item3 = tuple.recovered; bool flag3 = !flag2 || ConfigManager.ApplyLossesOnCompanyMoons.Value; if (!despawnAllItems && flag3 && item > 0 && item >= item2 && !_lossesAppliedThisRound) { flag = true; _appliedThisRound = true; _lossesAppliedThisRound = true; MarkItemsInShipRoom(); CollectVehicleItems(); MarkBeltBagContentsAsShipItems(); DespawnFacilityItems(); ApplyLossesWhenAllDead(); MarkSurvivingItemsAsPersisted(); HasAllDeadSnapshot = true; HudQuotaAnimationPatch.TryApplyAdvancedFeaturesEndscreen(); ApplyPenalties(item, item2, item3, flag2); if (ConfigManager.RolloverWipePenalty.Value > 0f) { ApplyRolloverWipePenalty(); } return false; } return true; } catch (Exception ex) { Plugin.Log.LogWarning((object)(flag ? ("Error in despawn prefix: " + ex.Message + ". The crew wipe was already handled, so the game is not allowed to despawn on top of it.") : ("Error in despawn prefix: " + ex.Message))); return !flag; } } [HarmonyPatch("DespawnPropsAtEndOfRound")] [HarmonyPostfix] private static void DespawnPostfix(bool despawnAllItems) { try { if (!despawnAllItems && PenaltyHelpers.IsServer && !_appliedThisRound) { var (num, total, recovered) = PenaltyHelpers.CountDeathsAndRecovered(); if (num > 0) { ApplyPenalties(num, total, recovered, PenaltyHelpers.IsAtCompany()); _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) { int currentCredits = GetCurrentCredits(); float penaltyPercent = PenaltyHelpers.GetPenaltyPercent(ConfigManager.CreditPenaltiesDynamic.Value, ConfigManager.CreditPenaltyPercentPerPlayer.Value, ConfigManager.CreditPenaltyPercentCap.Value, ConfigManager.CreditPenaltyPercentThreshold.Value, ConfigManager.CreditPenaltyRecoveryBonus.Value, dead, total, recovered); penaltyPercent = MoreShipUpgradesCompat.ApplyLifeInsurance(penaltyPercent); if (!(penaltyPercent <= 0f)) { int num = Mathf.Max(0, currentCredits - Mathf.RoundToInt((float)currentCredits * penaltyPercent)); Summary.CreditPercent = penaltyPercent; Summary.CreditLoss = currentCredits - num; _creditScheduled = true; ((MonoBehaviour)StartOfRound.Instance).StartCoroutine(FinalizeCreditPenaltyAfterDelay(num)); } } } 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; } private static IEnumerator FinalizeCreditPenaltyAfterDelay(int desiredFinal) { yield return (object)new WaitForSeconds(1.5f); try { int currentCredits = GetCurrentCredits(); SetCredits(desiredFinal); Plugin.Log.LogInfo((object)$"Took {currentCredits - desiredFinal} credits for deaths, {desiredFinal} left."); } finally { _creditScheduled = false; } } 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 penaltyPercent = PenaltyHelpers.GetPenaltyPercent(ConfigManager.QuotaPenaltiesDynamic.Value, ConfigManager.QuotaPenaltyPercentPerPlayer.Value, ConfigManager.QuotaPenaltyPercentCap.Value, ConfigManager.QuotaPenaltyPercentThreshold.Value, ConfigManager.QuotaPenaltyRecoveryBonus.Value, dead, total, recovered); if (!(penaltyPercent <= 0f)) { TimeOfDay instance = TimeOfDay.Instance; int profitQuota = instance.profitQuota; int num = Mathf.Rou