Please disclose if your mod was created primarily 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 QuotaOverhaul v2.0.0
plugins/QuotaOverhaul.dll
Decompiled a day agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using CSync.Extensions; using CSync.Lib; using HarmonyLib; using LethalNetworkAPI; using Microsoft.CodeAnalysis; using TMPro; using Unity.Mathematics; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyCompany("LuciusofLegend")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("New quota system that scales based on player count, and gives you control over the consequences of death.")] [assembly: AssemblyFileVersion("2.0.0.0")] [assembly: AssemblyInformationalVersion("2.0.0+5751aa13e4de8b6c721d4e66ee2720049f11fb68")] [assembly: AssemblyProduct("QuotaOverhaul")] [assembly: AssemblyTitle("luciusoflegend.lethalcompany.quotaoverhaul")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/LuciusofLegend/QuotaOverhaul")] [assembly: AssemblyVersion("2.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } internal static class LCMPluginInfo { public const string PLUGIN_GUID = "luciusoflegend.lethalcompany.quotaoverhaul"; public const string PLUGIN_NAME = "QuotaOverhaul"; public const string PLUGIN_VERSION = "2.0.0"; } namespace QuotaOverhaul { public class Config : SyncedConfig2<Config> { [SyncedEntryField] public SyncedEntry<int> StartingCredits; [SyncedEntryField] public SyncedEntry<int> StartingQuota; [SyncedEntryField] public SyncedEntry<int> QuotaBaseIncrease; [SyncedEntryField] public SyncedEntry<float> QuotaIncreaseSteepness; [SyncedEntryField] public SyncedEntry<float> QuotaRandomizerMultiplier; [SyncedEntryField] public SyncedEntry<int> QuotaDeadline; [SyncedEntryField] public SyncedEntry<int> QuotaEarlyFinishLine; [SyncedEntryField] public SyncedEntry<bool> QuotaEnablePlayerMultiplier; [SyncedEntryField] public SyncedEntry<int> QuotaPlayerThreshold; [SyncedEntryField] public SyncedEntry<int> QuotaPlayerCap; [SyncedEntryField] public SyncedEntry<float> QuotaMultiplierPerPlayer; [SyncedEntryField] public SyncedEntry<bool> CreditPenaltiesEnabled; [SyncedEntryField] public SyncedEntry<bool> CreditPenaltiesOnGordion; [SyncedEntryField] public SyncedEntry<float> CreditPenaltyPercentPerPlayer; [SyncedEntryField] public SyncedEntry<bool> CreditPenaltiesDynamic; [SyncedEntryField] public SyncedEntry<float> CreditPenaltyPercentCap; [SyncedEntryField] public SyncedEntry<float> CreditPenaltyPercentThreshold; [SyncedEntryField] public SyncedEntry<float> CreditPenaltyRecoveryBonus; [SyncedEntryField] public SyncedEntry<bool> QuotaPenaltiesEnabled; [SyncedEntryField] public SyncedEntry<bool> QuotaPenaltiesOnGordion; [SyncedEntryField] public SyncedEntry<float> QuotaPenaltyPercentPerPlayer; [SyncedEntryField] public SyncedEntry<bool> QuotaPenaltiesDynamic; [SyncedEntryField] public SyncedEntry<float> QuotaPenaltyPercentCap; [SyncedEntryField] public SyncedEntry<float> QuotaPenaltyPercentThreshold; [SyncedEntryField] public SyncedEntry<float> QuotaPenaltyRecoveryBonus; [SyncedEntryField] public SyncedEntry<bool> ChargeCreditsInsteadOfQuota; [SyncedEntryField] public SyncedEntry<float> CreditsPerQuota; [SyncedEntryField] public SyncedEntry<bool> DespawnPropsPatch; [SyncedEntryField] public SyncedEntry<bool> ScrapLossEnabled; [SyncedEntryField] public SyncedEntry<bool> ScrapLossOnGordion; [SyncedEntryField] public SyncedEntry<float> ItemsSafeChance; [SyncedEntryField] public SyncedEntry<float> LoseEachScrapChance; [SyncedEntryField] public SyncedEntry<int> MaxLostScrapItems; [SyncedEntryField] public SyncedEntry<float> MaxPercentLostScrapItems; [SyncedEntryField] public SyncedEntry<bool> ValueLossEnabled; [SyncedEntryField] public SyncedEntry<float> ValueLossPercent; [SyncedEntryField] public SyncedEntry<bool> EquipmentLossEnabled; [SyncedEntryField] public SyncedEntry<float> LoseEachEquipmentChance; [SyncedEntryField] public SyncedEntry<int> MaxLostEquipmentItems; [SyncedEntryField] public SyncedEntry<float> MaxPercentLostEquipmentItems; public Config(ConfigFile config) : base("luciusoflegend.lethalcompany.quotaoverhaul") { StartingCredits = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Quota Settings", "Starting Credits", 300, "How much cash you want? \nVanilla: 60"); StartingQuota = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Quota Settings", "Starting Quota", 200, "The first quota. \nVanilla: 130"); QuotaBaseIncrease = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Quota Settings", "Quota Base Increase", 125, "The minimum quota increase. \nVanilla: 200"); QuotaIncreaseSteepness = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Quota Settings", "Quota Increase Steepness", 16f, "The quota increases exponentially, and this controls the steepness of the curve. HIGHER steepness value = LOWER quota. \nVanilla: 16"); QuotaRandomizerMultiplier = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Quota Settings", "Quota Randomness Multiplier", 0f, "There is a bit of random variation each time the quota increases. This number controls how much. \nVanilla: 1"); QuotaDeadline = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Quota Settings", "Quota Deadline", 3, "The number of days you are given to complete each quota. \nVanilla: 3"); QuotaEarlyFinishLine = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Quota Settings", "Days Before Early Finish", 0, "The minimum number of days that need to pass before you are allowed to finish the quota. Values lower than 0 mean that you're not allowed to finish early. \nVanilla: 0"); QuotaEnablePlayerMultiplier = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Quota Settings", "Enable Player Count Multiplier", true, "When enabled, the quota scales based on the number of players. \nVanilla: false"); QuotaMultiplierPerPlayer = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Quota Settings", "Multiplier Per Player", 0.25f, "The multiplier for each player. Only applies to player counts above the threshold, and below the cap. The formula is basically Quota * (1 + MultiplierPerPlayer * PlayerCount (adjusted based on the cap and threshold))"); QuotaPlayerThreshold = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Quota Settings", "Player Count Threshold", 1, "Only player counts exceeding this threshold will have an impact on the quota. This option may be confusing, so here's an example: with a Threshold of 2 players, and a total of 2 players in the lobby, the Player Count Multiplier will be 1, meaning the quota is unchanged. Assuming the default Multiplier Per Player of 0.25, 4 players would give you a multiplier of 1.5"); QuotaPlayerCap = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Quota Settings", "Player Count Cap", 8, "Adding more players beyond this cap will not increase the quota multiplier. This makes sense to have because there comes a point where adding more players doesn't make it significantly easier to reach the quota."); CreditPenaltiesEnabled = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Credit Penalties", "Enable", false, "When enabled, you will lose credits a percentage of your credits for every crew memeber that dies. You can also enable dynamic mode, in which case the penalty is based on the ratio of dead players to alive players. \nVanilla: true"); CreditPenaltiesOnGordion = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Credit Penalties", "Enable At The Company", false, "When enabled, you also lose credits for dead players on Gordion/The Company. \nVanilla: true"); CreditPenaltyPercentPerPlayer = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Credit Penalties", "Penalty Per Player", 20f, "The percentage of your credits that you lose for each dead player. Example: Penalty Per Player = 20%, you have 1000 credits, 2 players die, you lose 400. \nValues >= 0"); CreditPenaltiesDynamic = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Credit Penalties", "Dynamic Mode", true, "Instead of calculating the penalty as a flat rate per dead player, Dynamic Mode calculates the penalty based on the portion of your crew that died. This makes it more balanced for large crews.\nVanilla: false"); CreditPenaltyPercentCap = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Credit Penalties", "Penalty Percent Cap", 80f, "The percent penalty in the worst case scenario, all players dead and unrecovered. Any players still alive, and any bodies recovered (see Body Recovery Bonus) will reduce the penalty. \nValues >= 0"); CreditPenaltyPercentThreshold = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Credit Penalties", "Penalty Percent Threshold", 20f, "If the penalty falls below this threshold, the penalty is set to 0. Increasing this value makes minor slip-ups more forgiving. This applies to both the static and dynamic algorithms. \nValues between 0-100"); CreditPenaltyRecoveryBonus = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Credit Penalties", "Body Recovery Bonus", 50f, "How much of the penalty to forgive for recovering bodies. A higher value means a higher incentive to recover bodies. Applies to both normal and dynamic modes. \nValues between 0-100"); QuotaPenaltiesEnabled = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Quota Penalties", "Enable", true, "Increase the quota for each player that dies. Intended to replace losing scrap when all players die. Penalties stack additively and are reset after each quota. \nVanilla: false"); QuotaPenaltiesOnGordion = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Quota Penalties", "Enable At The Company", false, "Whether to allow quota penalties at the company building. \nVanilla: true"); QuotaPenaltyPercentPerPlayer = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Quota Penalties", "Penalty Per Player", 12f, "The amount to increase the quota per dead player, as a percentage of the base quota for this round. \nValues >= 0"); QuotaPenaltiesDynamic = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Quota Penalties", "Dynamic Mode", true, "Instead of calculating the penalty as a flat rate per dead player, Dynamic Mode calculates the penalty based on the portion of your crew that died. This makes it more balanced for large crews.\nVanilla: false"); QuotaPenaltyPercentCap = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Quota Penalties", "Penalty Percent Cap", 50f, "The percent penalty in the worst case scenario, all players dead and unrecovered. Any players still alive, and any bodies recovered (see Body Recovery Bonus) will reduce the penalty. \nValues >= 0"); QuotaPenaltyPercentThreshold = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Quota Penalties", "Penalty Percent Threshold", 25f, "If the penalty falls below this threshold, the penalty is set to 0. Increasing this value makes minor slip-ups more forgiving. This applies to both the static and dynamic algorithms. \nValues between 0-100"); QuotaPenaltyRecoveryBonus = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Quota Penalties", "Body Recovery Bonus", 50f, "How much of the penalty to forgive for recovering bodies. Applies to both normal and dynamic modes. For example: Assuming a fully default coniguration, except without Dynamic Penalties, if you die, the penalty for your body is 12%. If your body is recovered, 50% of the penalty is forgiven, leaving a 6% penalty. \nValues between 0-100"); ChargeCreditsInsteadOfQuota = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Quota Penalties", "Charge Credits Instead", false, "Charges credits instead of increasing the quota. You can set the conversion rate below. Quota will only increase when you've run out of credits."); CreditsPerQuota = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Quota Penalties", "Credits Per Quota", 1f, "The conversion rate from Quota Penalties to Credits. Increasing this makes you lose more credits. This can also be set below 1, to make credits less sensitive. \nValues: > 0"); DespawnPropsPatch = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Scrap Loss", "Despawn Props Patch", true, "Toggles the entire patch of the Despawn Props method. Leave this as true unless you need to disable it for mod compatibility. The Scrap Loss On Gordion toggle is separate from this patch, so for truly vanilla behavior, set it to true. \nVanilla: false"); ScrapLossEnabled = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Scrap Loss", "Enable Scrap Loss", false, "If enabled, you lose scrap when all players die on a moon. \nVanilla: true"); ScrapLossOnGordion = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Scrap Loss", "Scrap Loss at the Company", false, "Toggles scrap loss at the company building. This can be enabled separately from the rest of the scrap loss changes, i.e. even if Despawn Props Patch is false. \n Vanilla: true"); ItemsSafeChance = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Scrap Loss", "Safe Chance", 25f, "A percent chance of all scrap and equipment being safe. When your items are 'safe', it overrides all other settings, and you keep everything. \nValues between 0-100 \nVanilla: 0"); ValueLossEnabled = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Scrap Loss", "Value Loss Enabled", true, "If enabled, you lose a percentage of the total scrap value on board on a crew wipe. \nVanilla: false."); ValueLossPercent = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Scrap Loss", "Value Loss Percent", 100f, "The percentage of total scrap value to lose. \nValues between 0-100"); LoseEachScrapChance = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Scrap Loss", "Lose Each Chance", 50f, "A percent chance of each item being lost. \nValues between 0-100 \nVanilla: 0"); MaxLostScrapItems = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Scrap Loss", "Lost Scrap Max", int.MaxValue, "The maximum number of scrap items that can be lost."); MaxPercentLostScrapItems = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Scrap Loss", "Lost Scrap Max Percent", 100f, "The maximum percentage of scrap items that can be lost. For example, if this is set to 50%, and you have 8 scrap on the ship, you cannot lose more than 4 scrap."); EquipmentLossEnabled = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Equipment Loss", "Equipment Loss Enabled", false, "Allow equipment to be lost. \nVanilla: false."); LoseEachEquipmentChance = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Equipment Loss", "Equipment Loss Chance", 10f, "A chance of each equipment item being lost. \nApplied after SaveAllChance. \nValues between 0-100 \nVanilla: 0"); MaxLostEquipmentItems = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Equipment Loss", "Lost Equipment Max", int.MaxValue, "The maximum amount of equipment that can be lost. \nApplied after EquipmentLossChance."); MaxPercentLostEquipmentItems = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Equipment Loss", "Lost Equipment Max Percent", 100f, "The maximum percentage of equipment items that can be lost. For example, if this is set to 50%, and you have 6 equipment items on the ship, you cannot lose more than 3 equipment items."); ConfigManager.Register<Config>((SyncedConfig2<Config>)(object)this); } } public class DeathConsequences { public static void DoDeathConsequences(int deadBodies, int recoveredBodies) { Terminal val = Object.FindObjectOfType<Terminal>(); int groupCredits = val.groupCredits; double num = CalculateCreditPenalty(deadBodies, recoveredBodies); int num2 = (int)((double)groupCredits * num); Plugin.Log.LogDebug((object)$"You lost {num2} of {val.groupCredits} credits"); if (GameNetworkManager.Instance.isHostingGame) { val.groupCredits -= num2; if (val.groupCredits < 0) { val.groupCredits = 0; } } int profitQuota = TimeOfDay.Instance.profitQuota; double num3 = 0.0; num3 = CalculateQuotaPenalty(deadBodies, recoveredBodies); int num4 = 0; if (Plugin.Config.ChargeCreditsInsteadOfQuota.Value) { Plugin.Log.LogDebug((object)"Applying combined penalty system..."); int num5 = (int)((double)TimeOfDay.Instance.profitQuota * (double)Plugin.Config.CreditsPerQuota.Value * num3); Plugin.Log.LogDebug((object)$"Quota: {TimeOfDay.Instance.profitQuota}, Credits per Quota: {Plugin.Config.CreditsPerQuota.Value}, Multiply: {(float)TimeOfDay.Instance.profitQuota * Plugin.Config.CreditsPerQuota.Value}, Quota Penalty: {num3} \nTotal: {num5}"); if (val.groupCredits >= num5) { Plugin.Log.LogDebug((object)$"You have {val.groupCredits} credits, which is enough to cover the penalty"); num4 = num5; num3 = 0.0; } else { Plugin.Log.LogDebug((object)$"You have {val.groupCredits} credits, which is NOT enough to cover the penalty"); num4 = val.groupCredits; num3 = (double)(1 / num5 * val.groupCredits) * num3 / (double)Plugin.Config.CreditsPerQuota.Value; Plugin.Log.LogDebug((object)$"You've lost all your credits and the remaining quota penalty is {num3}"); } val.groupCredits -= num4; if (val.groupCredits < 0) { val.groupCredits = 0; } } QuotaOverhaul.QuotaPenaltyMultiplier.Increase(num3); string text = $"CASUALTIES: {deadBodies}\nBODIES RECOVERED: {recoveredBodies} \n \nCREDITS: -{(int)(num * 100.0)}% -{num4} \n{groupCredits} -> {val.groupCredits} \n \nQUOTA: +{(int)(num3 * 100.0)}% \n{profitQuota} -> {TimeOfDay.Instance.profitQuota}"; ((TMP_Text)HUDManager.Instance.statsUIElements.penaltyAddition).text = text; ((TMP_Text)HUDManager.Instance.statsUIElements.penaltyTotal).text = ""; } public static double CalculateCreditPenalty(int deadBodies, int recoveredBodies) { if (!Plugin.Config.CreditPenaltiesEnabled.Value) { Plugin.Log.LogDebug((object)"Credit Penalties Disabled"); return 0.0; } if (!Plugin.Config.CreditPenaltiesOnGordion.Value && StartOfRound.Instance.currentLevel.PlanetName == "71 Gordion") { Plugin.Log.LogDebug((object)"Credit Penalties Disabled on Gordion"); return 0.0; } if (Plugin.Config.CreditPenaltiesDynamic.Value) { return CalculateDynamicCreditPenalty(deadBodies, recoveredBodies); } return CalculateStaticCreditPenalty(deadBodies, recoveredBodies); } private static double CalculateStaticCreditPenalty(int deadBodies, int recoveredBodies) { double num = (double)Plugin.Config.CreditPenaltyPercentPerPlayer.Value / 100.0; double num2 = num * (double)Plugin.Config.QuotaPenaltyRecoveryBonus.Value / 100.0; double num3 = (double)deadBodies * num - (double)recoveredBodies * num2; if (num3 < 0.0 || num3 < (double)Plugin.Config.CreditPenaltyPercentThreshold.Value / 100.0) { num3 = 0.0; } Plugin.Log.LogDebug((object)$"Calculated Credit Penalty of {num3}"); return num3; } private static double CalculateDynamicCreditPenalty(int deadBodies, int recoveredBodies) { double num = 1.0 / (double)QuotaOverhaul.GetRecordPlayersThisMoon() * (double)Plugin.Config.CreditPenaltyPercentCap.Value / 100.0; double num2 = num * (double)Plugin.Config.CreditPenaltyRecoveryBonus.Value / 100.0; double num3 = (double)deadBodies * num - (double)recoveredBodies * num2; if (num3 < 0.0 || num3 < (double)Plugin.Config.CreditPenaltyPercentThreshold.Value / 100.0) { num3 = 0.0; } Plugin.Log.LogDebug((object)$"Calculated Dynamic Credit Penalty of {num3}"); return num3; } public static double CalculateQuotaPenalty(int deadBodies, int recoveredBodies) { if (!Plugin.Config.QuotaPenaltiesEnabled.Value) { Plugin.Log.LogDebug((object)"Quota Penalties Disabled"); return 0.0; } if (!Plugin.Config.QuotaPenaltiesOnGordion.Value && StartOfRound.Instance.currentLevel.PlanetName == "71 Gordion") { Plugin.Log.LogDebug((object)"Quota Penalties Disabled on Gordion"); return 0.0; } if (Plugin.Config.QuotaPenaltiesDynamic.Value) { return CalculateDynamicQuotaPenalty(deadBodies, recoveredBodies); } return CalculateStaticQuotaPenalty(deadBodies, recoveredBodies); } private static double CalculateStaticQuotaPenalty(int deadBodies, int recoveredBodies) { double num = (double)Plugin.Config.QuotaPenaltyPercentPerPlayer.Value / 100.0; double num2 = num * (double)Plugin.Config.QuotaPenaltyRecoveryBonus.Value / 100.0; double num3 = (double)deadBodies * num - (double)recoveredBodies * num2; Plugin.Log.LogDebug((object)$"Calculated Quota Penalty of {num3}"); if (num3 < 0.0 || num3 < (double)Plugin.Config.QuotaPenaltyPercentThreshold.Value / 100.0) { Plugin.Log.LogDebug((object)$"Penalty fell below threshold of {(double)Plugin.Config.QuotaPenaltyPercentThreshold.Value / 100.0}. No penalty will be applied."); num3 = 0.0; } return num3; } private static double CalculateDynamicQuotaPenalty(int deadBodies, int recoveredBodies) { double num = 1.0 / (double)QuotaOverhaul.GetRecordPlayersThisMoon() * (double)Plugin.Config.QuotaPenaltyPercentCap.Value / 100.0; double num2 = num * (double)Plugin.Config.QuotaPenaltyRecoveryBonus.Value / 100.0; double num3 = (double)deadBodies * num - (double)recoveredBodies * num2; Plugin.Log.LogDebug((object)$"Calculated Dynamic Quota Penalty of {num3}"); if (num3 < 0.0 || num3 < (double)Plugin.Config.QuotaPenaltyPercentThreshold.Value / 100.0) { num3 = 0.0; Plugin.Log.LogDebug((object)$"Penalty fell below threshold of {(double)Plugin.Config.QuotaPenaltyPercentThreshold.Value / 100.0}. No penalty will be applied."); } return num3; } public static List<GrabbableObject> DetermineLostItems(List<GrabbableObject> items) { ILookup<bool, GrabbableObject> lookup = items.ToLookup((GrabbableObject item) => item.itemProperties.isScrap); List<GrabbableObject> list = lookup[true].ToList(); List<GrabbableObject> list2 = lookup[false].ToList(); List<GrabbableObject> list3 = new List<GrabbableObject>(); Random random = new Random(StartOfRound.Instance.randomMapSeed + 197); bool flag = random.NextDouble() < (double)(Plugin.Config.ItemsSafeChance.Value / 100f); int num = Math.Min(Plugin.Config.MaxLostScrapItems.Value, (int)((float)list.Count * Plugin.Config.MaxPercentLostScrapItems.Value / 100f)); int num2 = Math.Min(Plugin.Config.MaxLostEquipmentItems.Value, (int)((float)list2.Count * Plugin.Config.MaxPercentLostEquipmentItems.Value / 100f)); if (flag) { Plugin.Log.LogDebug((object)"All items are safe!"); } else { if (Plugin.Config.ScrapLossEnabled.Value) { Plugin.Log.LogDebug((object)"Scrap loss is enabled"); list.RemoveAll((GrabbableObject item) => !((NetworkBehaviour)item).IsSpawned); int num3 = list.Sum((GrabbableObject scrap) => scrap.scrapValue); int num4 = 0; int num5 = 0; if (Plugin.Config.ValueLossEnabled.Value) { list = list.OrderByDescending((GrabbableObject scrap) => scrap.scrapValue).ToList(); int num6 = (int)((float)num3 * Plugin.Config.ValueLossPercent.Value / 100f); foreach (GrabbableObject item in list) { if (num5 >= num6 || num4 >= num) { break; } num5 += item.scrapValue; num4++; list3.Add(item); Plugin.Log.LogDebug((object)("Lost a " + ((Object)item).name + " due to Value Loss")); } list.RemoveAll((GrabbableObject item) => !((NetworkBehaviour)item).IsSpawned); Plugin.Log.LogDebug((object)$"Value Loss: {num5}$ of scrap lost"); } foreach (GrabbableObject item2 in list) { if (random.NextDouble() < (double)(Plugin.Config.LoseEachScrapChance.Value / 100f)) { if (num4 >= num) { Plugin.Log.LogDebug((object)$"Reached the maximum for lost scrap items: {num}"); break; } num5 += item2.scrapValue; num4++; list3.Add(item2); Plugin.Log.LogDebug((object)("Lost a " + ((Object)item2).name + " due to random chance")); } } Plugin.Log.LogDebug((object)$"Lost {num4} scrap items worth {num5}"); } else { Plugin.Log.LogDebug((object)"Scrap loss is disabled"); } if (Plugin.Config.EquipmentLossEnabled.Value) { Plugin.Log.LogDebug((object)"Equipment loss is enabled"); list2.RemoveAll((GrabbableObject item) => !((NetworkBehaviour)item).IsSpawned); int num7 = 0; foreach (GrabbableObject item3 in list2) { if (random.NextDouble() < (double)(Plugin.Config.LoseEachEquipmentChance.Value / 100f)) { num7++; if (num7 >= num2) { Plugin.Log.LogDebug((object)$"Reached the maximum for lost equipment items: {num2}"); break; } list3.Add(item3); Plugin.Log.LogDebug((object)("Lost a " + ((Object)item3).name + " to random chance")); } } Plugin.Log.LogDebug((object)$"Lost {num7} equipment items"); } else { Plugin.Log.LogDebug((object)"Equipment loss is disabled"); } } return list3; } } [BepInPlugin("luciusoflegend.lethalcompany.quotaoverhaul", "QuotaOverhaul", "2.0.0")] [BepInDependency("LethalNetworkAPI", "3.3.0")] [BepInDependency("com.sigurd.csync", "5.0.0")] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "luciusoflegend.lethalcompany.quotaoverhaul"; public const string PluginName = "QuotaOverhaul"; public const string PluginVersion = "2.0.0"; public static Plugin Instance; public static Harmony Harmony; public static ManualLogSource Log; internal static Config Config; private void Awake() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown Instance = this; Harmony = new Harmony("luciusoflegend.lethalcompany.quotaoverhaul"); Log = ((BaseUnityPlugin)this).Logger; Config = new Config(((BaseUnityPlugin)this).Config); Harmony.PatchAll(); Log.LogInfo((object)"QuotaOverhaul v2.0.0 is loaded!"); } } public class QuotaMultiplier { private static readonly List<QuotaMultiplier> multipliers = new List<QuotaMultiplier>(); public readonly string name; private readonly LNetworkVariable<double> multiplier; public readonly double defaultMultiplier; public readonly bool persistent; public QuotaMultiplier(string name, double defaultMultiplier = 1.0, bool persistent = true) { this.name = name; this.defaultMultiplier = defaultMultiplier; multiplier = LNetworkVariable<double>.Connect(this.name, 0.0, (LNetworkVariableWritePerms)0, (Action<double, double>)SyncMultiplierToClients); multiplier.Value = defaultMultiplier; this.persistent = persistent; multipliers.Add(this); } private void SyncMultiplierToClients(double oldValue, double newValue) { multiplier.Value = newValue; } public double Get() { return multiplier.Value; } public void Set(double value) { if (GameNetworkManager.Instance.isHostingGame) { multiplier.Value = value; QuotaOverhaul.UpdateProfitQuota(); } } public void Increase(double increase) { Set(Get() + increase); } public void Reset() { Set(defaultMultiplier); } public static void SaveAll(string saveFile) { foreach (QuotaMultiplier multiplier in multipliers) { multiplier.Save(saveFile); } } public void Save(string saveFile) { if (GameNetworkManager.Instance.isHostingGame && persistent) { ES3.Save<double>(name, Get(), saveFile); } } public static void LoadAll(string saveFile) { foreach (QuotaMultiplier multiplier in multipliers) { multiplier.Load(saveFile); } } public void Load(string saveFile) { if (GameNetworkManager.Instance.isHostingGame && persistent && ES3.KeyExists(name, saveFile)) { double value = ES3.Load<double>(name, saveFile); Set(value); } } public static int GetQuotaWithMultipliers(int? baseQuota = null, List<QuotaMultiplier>? blacklist = null) { double num = ((!baseQuota.HasValue) ? QuotaOverhaul.GetBaseProfitQuota() : baseQuota.Value); List<QuotaMultiplier> list = multipliers; if (blacklist != null) { foreach (QuotaMultiplier item in blacklist) { list.Remove(item); } } foreach (QuotaMultiplier item2 in list) { double num2 = num; num *= item2.Get(); Plugin.Log.LogDebug((object)$"Applying quota multiplier: {item2.name} \n {num2} * {item2.Get()} = {num}"); } return (int)num; } } public static class QuotaOverhaul { private static int BaseProfitQuota; private static readonly LNetworkVariable<int> SyncedProfitQuota = LNetworkVariable<int>.Connect("SyncedProfitQuota", 0, (LNetworkVariableWritePerms)0, (Action<int, int>)SyncQuotaToClients); public static readonly QuotaMultiplier QuotaPenaltyMultiplier = new QuotaMultiplier("QuotaPenaltyMultiplier"); public static readonly QuotaMultiplier QuotaPlayerMultiplier = new QuotaMultiplier("QuotaPlayerMultiplier", 1.0, persistent: false); private static int RecordPlayersThisQuota = 1; private static int RecordPlayersThisMoon = 1; public static bool QuotaInProgress; private static void SyncQuotaToClients(int oldValue, int newValue) { TimeOfDay.Instance.profitQuota = newValue; } public static void UpdateProfitQuota() { if (GameNetworkManager.Instance.isHostingGame) { SyncedProfitQuota.Value = QuotaMultiplier.GetQuotaWithMultipliers(); } } public static int GetBaseProfitQuota() { return BaseProfitQuota; } public static void SetBaseProfitQuota(int value) { if (GameNetworkManager.Instance.isHostingGame) { BaseProfitQuota = value; UpdateProfitQuota(); } } public static int GetRecordPlayersThisQuota() { return RecordPlayersThisQuota; } public static void SetRecordPlayersThisQuota(int value) { if (GameNetworkManager.Instance.isHostingGame) { RecordPlayersThisQuota = value; QuotaPlayerMultiplier.Set(CalculatePlayerCountMultiplier()); } } public static double CalculatePlayerCountMultiplier() { int num = math.clamp(RecordPlayersThisQuota, Plugin.Config.QuotaPlayerThreshold.Value, Plugin.Config.QuotaPlayerCap.Value); num -= Plugin.Config.QuotaPlayerThreshold.Value; double result = 1f + Plugin.Config.QuotaMultiplierPerPlayer.Value * (float)math.max(num, 0); Plugin.Log.LogDebug((object)("Calculated player count multiplier of " + result)); return result; } public static int GetRecordPlayersThisMoon() { return RecordPlayersThisMoon; } public static void SetRecordPlayersThisMoon(int value) { if (GameNetworkManager.Instance.isHostingGame) { RecordPlayersThisMoon = value; } } public static void OnNewSession() { if (GameNetworkManager.Instance.isHostingGame) { if (Plugin.Config.StartingQuota.Value != 130) { TimeOfDay.Instance.quotaVariables.startingQuota = Plugin.Config.StartingQuota.Value; } if (Plugin.Config.QuotaBaseIncrease.Value != 200) { TimeOfDay.Instance.quotaVariables.baseIncrease = Plugin.Config.QuotaBaseIncrease.Value; } if (Plugin.Config.QuotaIncreaseSteepness.Value != 16f) { TimeOfDay.Instance.quotaVariables.increaseSteepness = Plugin.Config.QuotaIncreaseSteepness.Value; } if (Plugin.Config.QuotaRandomizerMultiplier.Value != 1f) { TimeOfDay.Instance.quotaVariables.randomizerMultiplier = Plugin.Config.QuotaRandomizerMultiplier.Value; } if (Plugin.Config.StartingCredits.Value != 60) { TimeOfDay.Instance.quotaVariables.startingCredits = Plugin.Config.StartingCredits.Value; } if (Plugin.Config.QuotaDeadline.Value != 3) { TimeOfDay.Instance.quotaVariables.deadlineDaysAmount = Plugin.Config.QuotaDeadline.Value; } LoadData(); if (TimeOfDay.Instance.timesFulfilledQuota == 0 && !QuotaInProgress) { OnNewRun(); } } } public static void OnNewRun() { if (GameNetworkManager.Instance.isHostingGame) { SetBaseProfitQuota(TimeOfDay.Instance.quotaVariables.startingQuota); OnNewQuota(); } } public static void OnNewQuota() { if (GameNetworkManager.Instance.isHostingGame) { QuotaInProgress = false; QuotaPenaltyMultiplier.Reset(); RecordPlayersThisQuota = StartOfRound.Instance.connectedPlayersAmount; UpdateProfitQuota(); } } public static void OnPlayerCountChanged() { if (GameNetworkManager.Instance.isHostingGame) { int num = StartOfRound.Instance.connectedPlayersAmount + 1; if (!StartOfRound.Instance.shipHasLanded || num > RecordPlayersThisMoon) { SetRecordPlayersThisMoon(num); } if (!QuotaInProgress || num > RecordPlayersThisQuota) { SetRecordPlayersThisQuota(num); } Plugin.Log.LogDebug((object)("Player count: " + num)); } } private static void LoadData() { if (GameNetworkManager.Instance.isHostingGame) { string currentSaveFileName = GameNetworkManager.Instance.currentSaveFileName; if (ES3.KeyExists("BaseProfitQuota", currentSaveFileName)) { BaseProfitQuota = ES3.Load<int>("BaseProfitQuota", currentSaveFileName); } if (ES3.KeyExists("RecordPlayersThisQuota", currentSaveFileName)) { RecordPlayersThisQuota = ES3.Load<int>("RecordPlayersThisQuota", currentSaveFileName); } if (ES3.KeyExists("QuotaInProgress", currentSaveFileName)) { QuotaInProgress = ES3.Load<bool>("QuotaInProgress", currentSaveFileName); } QuotaMultiplier.LoadAll(currentSaveFileName); } } public static void SaveData() { if (GameNetworkManager.Instance.isHostingGame) { string currentSaveFileName = GameNetworkManager.Instance.currentSaveFileName; ES3.Save<int>("BaseProfitQuota", BaseProfitQuota, currentSaveFileName); ES3.Save<int>("RecordPlayersThisQuota", RecordPlayersThisQuota, currentSaveFileName); ES3.Save<bool>("QuotaInProgress", QuotaInProgress, currentSaveFileName); QuotaMultiplier.SaveAll(currentSaveFileName); } } } } namespace QuotaOverhaul.Patches { [HarmonyPatch(typeof(GameNetworkManager), "SaveGame")] public class SaveGamePatch { public static void Postfix() { if (GameNetworkManager.Instance.isHostingGame) { QuotaOverhaul.SaveData(); Plugin.Log.LogInfo((object)"Saving data"); } } } [HarmonyPatch(typeof(HUDManager), "ApplyPenalty")] public class DeathPenaltyPatch { public static bool Prefix() { return false; } public static void Postfix(int playersDead, int bodiesInsured) { DeathConsequences.DoDeathConsequences(playersDead, bodiesInsured); } } [HarmonyPatch(typeof(RoundManager), "DespawnPropsAtEndOfRound")] public class DespawnPropsPatch { public static bool Prefix() { if (!SyncedEntry<bool>.op_Implicit(Plugin.Config.ScrapLossOnGordion) && StartOfRound.Instance.currentLevel.PlanetName != "71 Gordion") { return false; } return Plugin.Config.DespawnPropsPatch.Value; } public static void Postfix(bool despawnAllItems = false) { if (!GameNetworkManager.Instance.isHostingGame) { return; } if (Plugin.Config.DespawnPropsPatch.Value) { Plugin.Log.LogDebug((object)"Vanilla Scrap Loss is on. Skipping custom DespawnProps method."); return; } VehicleController[] array = Object.FindObjectsOfType<VehicleController>(); VehicleController[] array2 = array; foreach (VehicleController vehicle in array2) { DespawnVehicle(vehicle); } BeltBagItem[] array3 = Object.FindObjectsOfType<BeltBagItem>(); BeltBagItem[] array4 = array3; foreach (BeltBagItem val in array4) { if (Object.op_Implicit((Object)(object)val.insideAnotherBeltBag) && (((GrabbableObject)val.insideAnotherBeltBag).isInShipRoom || ((GrabbableObject)val.insideAnotherBeltBag).isHeld)) { ((GrabbableObject)val).isInElevator = true; ((GrabbableObject)val).isInShipRoom = true; } if (!((GrabbableObject)val).isInShipRoom && !((GrabbableObject)val).isHeld) { continue; } foreach (GrabbableObject item in val.objectsInBag) { item.isInElevator = true; item.isInShipRoom = true; } } Random random = new Random(StartOfRound.Instance.randomMapSeed + 369); List<GrabbableObject> list = Object.FindObjectsOfType<GrabbableObject>().ToList(); List<GrabbableObject> list2 = new List<GrabbableObject>(); if (despawnAllItems) { Plugin.Log.LogDebug((object)"Despawning all items"); { foreach (GrabbableObject item2 in list) { DespawnItem(item2); } return; } } foreach (GrabbableObject item3 in list) { if (!((Object)(object)item3 == (Object)null)) { if ((!item3.isInShipRoom && !item3.isHeld) || item3.deactivated) { Plugin.Log.LogDebug((object)((item3.itemProperties.itemName ?? ((Object)item3).name) + " Lost Outside")); DespawnItem(item3); } else { list2.Add(item3); } } } if (!StartOfRound.Instance.allPlayersDead) { return; } if (!SyncedEntry<bool>.op_Implicit(Plugin.Config.ScrapLossOnGordion) && StartOfRound.Instance.currentLevel.PlanetName != "71 Gordion") { Plugin.Log.LogDebug((object)"Scrap loss disabled at the Company. Skipping Scrap Loss."); return; } List<GrabbableObject> list3 = DeathConsequences.DetermineLostItems(list2); foreach (GrabbableObject item4 in list3) { DespawnItem(item4); } } private static void DespawnVehicle(VehicleController vehicle) { try { if (!vehicle.magnetedToShip) { if ((Object)(object)((NetworkBehaviour)vehicle).NetworkObject != (Object)null) { ((NetworkBehaviour)vehicle).NetworkObject.Despawn(false); Plugin.Log.LogDebug((object)"Despawned vehicle"); } } else { vehicle.CollectItemsInTruck(); } } catch (Exception arg) { Plugin.Log.LogError((object)$"Error despawning vehicle: {arg}"); } } private static void DespawnItem(GrabbableObject item) { if (item.isHeld && (Object)(object)item.playerHeldBy != (Object)null) { item.playerHeldBy.DropAllHeldItemsAndSyncNonexact(); } NetworkObject component = ((Component)item).gameObject.GetComponent<NetworkObject>(); if ((Object)(object)component != (Object)null && component.IsSpawned) { Plugin.Log.LogDebug((object)("Despawning " + (item.itemProperties.itemName ?? ((Object)item).name))); component.Despawn(true); } else { Plugin.Log.LogDebug((object)("Error/warning: " + (item.itemProperties.itemName ?? item.itemProperties.itemName ?? ((Object)item).name) + " was not spawned or did not have a NetworkObject component! Skipped despawning and destroyed it instead.")); Object.Destroy((Object)(object)((Component)item).gameObject); } if (RoundManager.Instance.spawnedSyncedObjects.Contains(((Component)item).gameObject)) { RoundManager.Instance.spawnedSyncedObjects.Remove(((Component)item).gameObject); } } } [HarmonyPatch(typeof(StartOfRound), "ResetShip")] public class ResetShipPatch { public static void Postfix() { if (GameNetworkManager.Instance.isHostingGame) { QuotaOverhaul.OnNewRun(); } } } [HarmonyPatch(typeof(StartOfRound), "OnClientConnect")] public class OnPlayerConnectPatch { public static void Postfix() { Plugin.Log.LogInfo((object)"Player joined"); QuotaOverhaul.OnPlayerCountChanged(); } } [HarmonyPatch(typeof(StartOfRound), "OnClientDisconnect")] public class OnPlayerDisconnectPatch { public static void Postfix() { Plugin.Log.LogInfo((object)"Player disconnected"); QuotaOverhaul.OnPlayerCountChanged(); } } [HarmonyPatch(typeof(StartOfRound), "OnShipLandedMiscEvents")] public class OnShipLandedPatch { public static void Postfix() { if (GameNetworkManager.Instance.isHostingGame) { QuotaOverhaul.QuotaInProgress = true; } } } [HarmonyPatch(typeof(TimeOfDay), "Awake")] public class NewSessionPatch { public static void Postfix() { QuotaOverhaul.OnNewSession(); } } [HarmonyPatch(typeof(TimeOfDay), "SetNewProfitQuota")] public class QuotaUpdatePatch { public static bool Prefix() { if (!GameNetworkManager.Instance.isHostingGame) { return false; } if (!CanFinishQuota()) { return false; } TimeOfDay.Instance.profitQuota = QuotaOverhaul.GetBaseProfitQuota(); Plugin.Log.LogDebug((object)$"Calculating new Base Quota, based on a quota of {TimeOfDay.Instance.profitQuota}"); Plugin.Log.LogDebug((object)$"Base Increase: {TimeOfDay.Instance.quotaVariables.baseIncrease}"); Plugin.Log.LogDebug((object)$"Increase Steepness: {TimeOfDay.Instance.quotaVariables.increaseSteepness}"); Plugin.Log.LogDebug((object)$"Randomness Mult: {TimeOfDay.Instance.quotaVariables.randomizerMultiplier}"); return true; } public static void Postfix() { if (GameNetworkManager.Instance.isHostingGame && CanFinishQuota()) { Plugin.Log.LogDebug((object)$"New Base Quota is {TimeOfDay.Instance.profitQuota}"); QuotaOverhaul.SetBaseProfitQuota(TimeOfDay.Instance.profitQuota); QuotaOverhaul.OnNewQuota(); } } private static bool CanFinishQuota() { int num = TimeOfDay.Instance.quotaVariables.deadlineDaysAmount - TimeOfDay.Instance.daysUntilDeadline; if (Plugin.Config.QuotaEarlyFinishLine.Value < 0 && TimeOfDay.Instance.daysUntilDeadline > 0) { return false; } if (num < SyncedEntry<int>.op_Implicit(Plugin.Config.QuotaEarlyFinishLine)) { Plugin.Log.LogDebug((object)$"Could not finish quota. We are {num} days into the quota, and we can't finish until day ${Plugin.Config.QuotaEarlyFinishLine.Value}"); return false; } Plugin.Log.LogDebug((object)$"We are able to finish the quota. We are {num} days into the quota, and we can finish any time after day ${Plugin.Config.QuotaEarlyFinishLine.Value}"); return true; } } }