using System;
using System.Collections;
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("Making the quota system more fun, rewarding, and configurable")]
[assembly: AssemblyFileVersion("1.4.0.0")]
[assembly: AssemblyInformationalVersion("1.4.0+176c897f3d2160c809884eea5f7bc6a6fe8da894")]
[assembly: AssemblyProduct("QuotaOverhaul")]
[assembly: AssemblyTitle("luciusoflegend.lethalcompany.quotaoverhaul")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/LuciusofLegend/QuotaOverhaul")]
[assembly: AssemblyVersion("1.4.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 = "1.4.0";
}
namespace QuotaOverhaul
{
public class Config : SyncedConfig2<Config>
{
[SyncedEntryField]
public static SyncedEntry<int> StartingCredits;
[SyncedEntryField]
public static SyncedEntry<int> StartingQuota;
[SyncedEntryField]
public static SyncedEntry<int> QuotaBaseIncrease;
[SyncedEntryField]
public static SyncedEntry<float> QuotaIncreaseSteepness;
[SyncedEntryField]
public static SyncedEntry<float> QuotaRandomizerMultiplier;
[SyncedEntryField]
public static SyncedEntry<int> QuotaDeadline;
[SyncedEntryField]
public static SyncedEntry<int> QuotaEarlyFinishLine;
[SyncedEntryField]
public static SyncedEntry<bool> QuotaEnablePlayerMultiplier;
[SyncedEntryField]
public static SyncedEntry<int> QuotaPlayerThreshold;
[SyncedEntryField]
public static SyncedEntry<int> QuotaPlayerCap;
[SyncedEntryField]
public static SyncedEntry<float> QuotaMultiplierPerPlayer;
[SyncedEntryField]
public static SyncedEntry<bool> CreditPenaltiesEnabled;
[SyncedEntryField]
public static SyncedEntry<bool> CreditPenaltiesOnGordion;
[SyncedEntryField]
public static SyncedEntry<float> CreditPenaltyPercentPerPlayer;
[SyncedEntryField]
public static SyncedEntry<bool> CreditPenaltiesDynamic;
[SyncedEntryField]
public static SyncedEntry<float> CreditPenaltyPercentCap;
[SyncedEntryField]
public static SyncedEntry<float> CreditPenaltyPercentThreshold;
[SyncedEntryField]
public static SyncedEntry<float> CreditPenaltyRecoveryBonus;
[SyncedEntryField]
public static SyncedEntry<bool> QuotaPenaltiesEnabled;
[SyncedEntryField]
public static SyncedEntry<bool> QuotaPenaltiesOnGordion;
[SyncedEntryField]
public static SyncedEntry<float> QuotaPenaltyPercentPerPlayer;
[SyncedEntryField]
public static SyncedEntry<bool> QuotaPenaltiesDynamic;
[SyncedEntryField]
public static SyncedEntry<float> QuotaPenaltyPercentCap;
[SyncedEntryField]
public static SyncedEntry<float> QuotaPenaltyPercentThreshold;
[SyncedEntryField]
public static SyncedEntry<float> QuotaPenaltyRecoveryBonus;
[SyncedEntryField]
public static SyncedEntry<bool> ScrapLossEnabled;
[SyncedEntryField]
public static SyncedEntry<float> ItemsSafeChance;
[SyncedEntryField]
public static SyncedEntry<float> LoseEachScrapChance;
[SyncedEntryField]
public static SyncedEntry<int> MaxLostScrapItems;
[SyncedEntryField]
public static SyncedEntry<bool> ValueLossEnabled;
[SyncedEntryField]
public static SyncedEntry<float> ValueLossPercent;
[SyncedEntryField]
public static SyncedEntry<bool> EquipmentLossEnabled;
[SyncedEntryField]
public static SyncedEntry<float> LoseEachEquipmentChance;
[SyncedEntryField]
public static SyncedEntry<int> MaxLostEquipmentItems;
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", 250, "The first quota. \nVanilla: 130");
QuotaBaseIncrease = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Quota Settings", "Quota Base Increase", 150, "The minimum quota increase. \nVanilla: 200");
QuotaIncreaseSteepness = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Quota Settings", "Quota Increase Steepness", 4f, "The quota increases exponentially, and this controls the steepness of the curve. Bigger number means higher quotas. \nVanilla: 4");
QuotaRandomizerMultiplier = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Quota Settings", "Quota Randomizer Multiplier", 1f, "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", 2, "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 (which is the default), if you have 2 players (including the host), 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.");
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 are applied as a percent of the base quota for the round. Penalties only affect the current quota, and do not carry to future rounds. \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", 15f, "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");
ScrapLossEnabled = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Scrap Loss", "Scrap Loss Enabled", false, "If enabled, you will lose scrap when all players die. (Dynamic scrap loss planned). \nVanilla: 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", false, "Lose a percent of total scrap value. \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", "Scrap Loss Max", int.MaxValue, "The maximum number of scrap items that can be lost.");
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", "Equipment Loss Max", int.MaxValue, "The maximum amount of equipment that can be lost.\nApplied after EquipmentLossChance.");
ConfigManager.Register<Config>((SyncedConfig2<Config>)(object)this);
}
}
[BepInPlugin("luciusoflegend.lethalcompany.quotaoverhaul", "QuotaOverhaul", "1.4.0")]
[BepInDependency("LethalNetworkAPI", "3.0.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 = "1.4.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 = Logger.CreateLogSource("QuotaOverhaul");
config = new Config(((BaseUnityPlugin)this).Config);
Harmony.PatchAll();
Log.LogInfo((object)"QuotaOverhaul v1.4.0 is loaded!");
}
}
public class QuotaOverhaul
{
public static int BaseProfitQuota;
public static double QuotaPenaltyMultiplier = 1.0;
public static double QuotaPlayerMultiplier = 1.0;
public static int RecordPlayersThisQuota = 1;
public static int RecordPlayersThisMoon = 1;
public static bool QuotaInProgress;
public static readonly LNetworkVariable<int> ProfitQuota = LNetworkVariable<int>.Connect("ProfitQuota", 0, (LNetworkVariableWritePerms)0, (Action<int, int>)SyncProfitQuota);
private static void SyncProfitQuota(int oldValue, int newValue)
{
TimeOfDay.Instance.profitQuota = newValue;
Plugin.Log.LogInfo((object)$"Synced Profit Quota: {newValue}");
}
public static int CalculateProfitQuota(bool usePlayerCountMultiplier = true, bool usePenaltyMultiplier = true)
{
int num = BaseProfitQuota;
if (Config.QuotaEnablePlayerMultiplier.Value && usePlayerCountMultiplier)
{
num = (int)((double)num * QuotaPlayerMultiplier);
}
Plugin.Log.LogInfo((object)$"QuotaPlayerMultiplier: ${QuotaPlayerMultiplier}");
if (Config.QuotaPenaltiesEnabled.Value && usePenaltyMultiplier)
{
num = (int)((double)num * QuotaPenaltyMultiplier);
}
Plugin.Log.LogInfo((object)$"QuotaPenaltyModifier: ${QuotaPenaltyMultiplier}");
return num;
}
private static void UpdatePlayerCountMultiplier()
{
if (Config.QuotaEnablePlayerMultiplier.Value)
{
QuotaPlayerMultiplier = CalculatePlayerCountMultiplier();
ProfitQuota.Value = CalculateProfitQuota();
}
}
private static double CalculatePlayerCountMultiplier()
{
int num = math.clamp(RecordPlayersThisQuota, Config.QuotaPlayerThreshold.Value, Config.QuotaPlayerCap.Value);
num -= Config.QuotaPlayerThreshold.Value;
return 1f + Config.QuotaMultiplierPerPlayer.Value * (float)math.max(num, 0);
}
public static void OnNewSession()
{
if (GameNetworkManager.Instance.isHostingGame)
{
TimeOfDay.Instance.quotaVariables.startingQuota = Config.StartingQuota.Value;
TimeOfDay.Instance.quotaVariables.baseIncrease = Config.QuotaBaseIncrease.Value;
TimeOfDay.Instance.quotaVariables.increaseSteepness = Config.QuotaIncreaseSteepness.Value;
TimeOfDay.Instance.quotaVariables.randomizerMultiplier = Config.QuotaRandomizerMultiplier.Value;
TimeOfDay.Instance.quotaVariables.startingCredits = Config.StartingCredits.Value;
TimeOfDay.Instance.quotaVariables.deadlineDaysAmount = Config.QuotaDeadline.Value;
LoadData();
}
}
public static void OnNewRun()
{
if (GameNetworkManager.Instance.isHostingGame)
{
BaseProfitQuota = TimeOfDay.Instance.quotaVariables.startingQuota;
OnNewQuota();
}
}
public static void OnNewQuota()
{
if (GameNetworkManager.Instance.isHostingGame)
{
QuotaInProgress = false;
QuotaPenaltyMultiplier = 1.0;
RecordPlayersThisQuota = StartOfRound.Instance.connectedPlayersAmount;
ProfitQuota.Value = CalculateProfitQuota();
}
}
public static void OnPlayerCountChanged()
{
if (GameNetworkManager.Instance.isHostingGame)
{
int num = StartOfRound.Instance.connectedPlayersAmount + 1;
Plugin.Log.LogInfo((object)("Player count: " + num));
if (!StartOfRound.Instance.shipHasLanded || num > RecordPlayersThisMoon)
{
RecordPlayersThisMoon = num;
}
if (!QuotaInProgress || num > RecordPlayersThisQuota)
{
RecordPlayersThisQuota = num;
}
UpdatePlayerCountMultiplier();
}
}
public 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("QuotaPenaltyMultiplier", currentSaveFileName))
{
QuotaPenaltyMultiplier = ES3.Load<double>("QuotaPenaltyMultiplier", currentSaveFileName);
}
if (ES3.KeyExists("RecordPlayersThisQuota", currentSaveFileName))
{
RecordPlayersThisQuota = ES3.Load<int>("RecordPlayersThisQuota", currentSaveFileName);
}
if (ES3.KeyExists("QuotaInProgress", currentSaveFileName))
{
QuotaInProgress = ES3.Load<bool>("QuotaInProgress", currentSaveFileName);
}
if (TimeOfDay.Instance.timesFulfilledQuota == 0 && !QuotaInProgress)
{
OnNewRun();
}
}
}
public static void SaveData()
{
if (GameNetworkManager.Instance.isHostingGame)
{
string currentSaveFileName = GameNetworkManager.Instance.currentSaveFileName;
ES3.Save<int>("BaseProfitQuota", BaseProfitQuota, currentSaveFileName);
ES3.Save<double>("QuotaPenaltyMultiplier", QuotaPenaltyMultiplier, currentSaveFileName);
ES3.Save<int>("RecordPlayersThisQuota", RecordPlayersThisQuota, currentSaveFileName);
ES3.Save<bool>("QuotaInProgress", QuotaInProgress, currentSaveFileName);
}
}
}
}
namespace QuotaOverhaul.Patches
{
[HarmonyPatch(typeof(GameNetworkManager), "SaveGame")]
public class SaveGamePatch
{
public static void Postfix()
{
if (GameNetworkManager.Instance.isHostingGame)
{
QuotaOverhaul.SaveData();
}
}
}
[HarmonyPatch(typeof(HUDManager), "ApplyPenalty")]
public class DeathPenaltyPatch
{
public static bool Prefix()
{
return false;
}
public static void Postfix(int playersDead, int bodiesInsured)
{
Terminal val = Object.FindObjectOfType<Terminal>();
int groupCredits = val.groupCredits;
bool flag = Config.CreditPenaltiesEnabled.Value && (Config.CreditPenaltiesOnGordion.Value || StartOfRound.Instance.currentLevel.PlanetName != "71 Gordion");
double num = 0.0;
if (flag)
{
num = (Config.CreditPenaltiesDynamic.Value ? CalculateDynamicCreditPenalty(playersDead, bodiesInsured) : CalculateCreditPenalty(playersDead, bodiesInsured));
if (GameNetworkManager.Instance.isHostingGame)
{
val.groupCredits -= (int)((double)groupCredits * num);
if (val.groupCredits < 0)
{
val.groupCredits = 0;
}
}
}
int profitQuota = TimeOfDay.Instance.profitQuota;
bool flag2 = Config.QuotaPenaltiesEnabled.Value && (Config.QuotaPenaltiesOnGordion.Value || StartOfRound.Instance.currentLevel.PlanetName != "71 Gordion");
double num2 = 0.0;
if (flag2)
{
num2 = (Config.QuotaPenaltiesDynamic.Value ? CalculateDynamicQuotaPenalty(playersDead, bodiesInsured) : CalculateQuotaPenalty(playersDead, bodiesInsured));
if (GameNetworkManager.Instance.isHostingGame)
{
QuotaOverhaul.QuotaPenaltyMultiplier += num2;
QuotaOverhaul.ProfitQuota.Value = QuotaOverhaul.CalculateProfitQuota();
}
}
string text = $"CASUALTIES: ${playersDead}\nBODIES RECOVERED: ${bodiesInsured} \n \nCREDITS: -{(int)(num * 100.0)}% \n${groupCredits} -> ${val.groupCredits} \n \nQUOTA: +{(int)(num2 * 100.0)}% \n${profitQuota} -> ${TimeOfDay.Instance.profitQuota}";
((TMP_Text)HUDManager.Instance.statsUIElements.penaltyAddition).text = text;
((TMP_Text)HUDManager.Instance.statsUIElements.penaltyTotal).text = "";
}
private static double CalculateCreditPenalty(int deadBodies, int recoveredBodies)
{
double num = (double)Config.CreditPenaltyPercentPerPlayer.Value / 100.0;
double num2 = num * (double)Config.QuotaPenaltyRecoveryBonus.Value / 100.0;
double num3 = (double)deadBodies * num - (double)recoveredBodies * num2;
if (num3 < 0.0 || num3 < (double)Config.CreditPenaltyPercentThreshold.Value / 100.0)
{
num3 = 0.0;
}
Plugin.Log.LogInfo((object)$"Calculated Credit Penalty of {num3}");
return num3;
}
private static double CalculateDynamicCreditPenalty(int deadBodies, int recoveredBodies)
{
double num = 1.0 / (double)QuotaOverhaul.RecordPlayersThisMoon * (double)Config.CreditPenaltyPercentCap.Value / 100.0;
double num2 = num * (double)Config.CreditPenaltyRecoveryBonus.Value / 100.0;
double num3 = (double)deadBodies * num - (double)recoveredBodies * num2;
if (num3 < 0.0 || num3 < (double)Config.CreditPenaltyPercentThreshold.Value / 100.0)
{
num3 = 0.0;
}
Plugin.Log.LogInfo((object)$"Calculated Dynamic Credit Penalty of {num3}");
return num3;
}
private static double CalculateQuotaPenalty(int deadBodies, int recoveredBodies)
{
double num = (double)Config.QuotaPenaltyPercentPerPlayer.Value / 100.0;
double num2 = num * (double)Config.QuotaPenaltyRecoveryBonus.Value / 100.0;
double num3 = (double)deadBodies * num - (double)recoveredBodies * num2;
if (num3 < 0.0 || num3 < (double)Config.QuotaPenaltyPercentThreshold.Value / 100.0)
{
num3 = 0.0;
}
Plugin.Log.LogInfo((object)$"Calculated Quota Penalty of {num3}");
return num3;
}
private static double CalculateDynamicQuotaPenalty(int deadBodies, int recoveredBodies)
{
Plugin.Log.LogInfo((object)"Calculating Dynamic Quota Penalty");
double num = 1.0 / (double)QuotaOverhaul.RecordPlayersThisMoon * (double)Config.QuotaPenaltyPercentCap.Value / 100.0;
double num2 = num * (double)Config.QuotaPenaltyRecoveryBonus.Value / 100.0;
double num3 = (double)deadBodies * num - (double)recoveredBodies * num2;
if (num3 < 0.0 || num3 < (double)Config.QuotaPenaltyPercentThreshold.Value / 100.0)
{
num3 = 0.0;
Plugin.Log.LogInfo((object)$"Penalty fell below threshold of {(double)Config.QuotaPenaltyPercentThreshold.Value / 100.0}");
}
Plugin.Log.LogInfo((object)$"Calculated Dynamic Quota Penalty of {num3}");
return num3;
}
}
[HarmonyPatch(typeof(RoundManager), "DespawnPropsAtEndOfRound")]
public class DespawnPropsPatch
{
[CompilerGenerated]
private sealed class <DisplayAlert>d__4 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public string headerAlertText;
public string bodyAlertText;
public string messageText;
private int <index>5__2;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <DisplayAlert>d__4(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0077: Unknown result type (might be due to invalid IL or missing references)
//IL_0081: Expected O, but got Unknown
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
//IL_0057: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<index>5__2 = 0;
goto IL_0067;
case 1:
<>1__state = -1;
goto IL_0067;
case 2:
{
<>1__state = -1;
if (!string.IsNullOrEmpty(headerAlertText) || !string.IsNullOrEmpty(bodyAlertText))
{
HUDManager.Instance.DisplayTip(headerAlertText, bodyAlertText, false, false, "LC_Tip1");
}
if (!string.IsNullOrEmpty(messageText))
{
HUDManager.Instance.AddTextToChatOnServer(messageText, -1);
}
return false;
}
IL_0067:
if (<index>5__2 < 20 && !StartOfRound.Instance.inShipPhase)
{
<index>5__2++;
<>2__current = (object)new WaitForSeconds(5f);
<>1__state = 1;
return true;
}
<>2__current = (object)new WaitForSeconds(2f);
<>1__state = 2;
return true;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
public static bool Prefix()
{
return false;
}
public static void Postfix(bool despawnAllItems = false)
{
if (!GameNetworkManager.Instance.isHostingGame)
{
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.LogInfo((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.LogInfo((object)((item3.itemProperties.itemName ?? ((Object)item3).name) + " Lost Outside"));
DespawnItem(item3);
}
else
{
list2.Add(item3);
}
}
}
if (!StartOfRound.Instance.allPlayersDead)
{
return;
}
ILookup<bool, GrabbableObject> lookup = list2.ToLookup((GrabbableObject item) => item.itemProperties.isScrap);
List<GrabbableObject> list3 = lookup[true].ToList();
List<GrabbableObject> list4 = lookup[false].ToList();
List<string> list5 = new List<string>();
bool flag = random.NextDouble() < (double)(Config.ItemsSafeChance.Value / 100f);
if (!Config.ScrapLossEnabled.Value)
{
Plugin.Log.LogInfo((object)"Scrap loss is disabled");
}
else if (!flag)
{
list3.RemoveAll((GrabbableObject item) => !((NetworkBehaviour)item).IsSpawned);
int num = list3.Sum((GrabbableObject scrap) => scrap.scrapValue);
int num2 = 0;
int num3 = 0;
if (Config.ValueLossEnabled.Value)
{
list3 = list3.OrderByDescending((GrabbableObject scrap) => scrap.scrapValue).ToList();
int num4 = (int)((float)num * Config.ValueLossPercent.Value / 100f);
foreach (GrabbableObject item4 in list3)
{
if (num3 >= num4 || num2 >= Config.MaxLostScrapItems.Value)
{
break;
}
num3 += item4.scrapValue;
num2++;
list5.Add(item4.itemProperties?.itemName ?? ((Object)item4).name);
DespawnItem(item4);
Plugin.Log.LogInfo((object)$"Lost {((Object)item4).name} worth {item4.scrapValue}");
}
list3.RemoveAll((GrabbableObject item) => !((NetworkBehaviour)item).IsSpawned);
Plugin.Log.LogInfo((object)$"Value Loss: {num3}$ of scrap lost");
}
foreach (GrabbableObject item5 in list3)
{
if (random.NextDouble() < (double)(Config.LoseEachScrapChance.Value / 100f))
{
if (num2 >= Config.MaxLostScrapItems.Value)
{
break;
}
num3 += item5.scrapValue;
num2++;
list5.Add(item5.itemProperties?.itemName ?? ((Object)item5).name);
DespawnItem(item5);
Plugin.Log.LogInfo((object)$"Lost {((Object)item5).name} worth {item5.scrapValue}");
}
}
Plugin.Log.LogInfo((object)$"Lost {num2} scrap items worth {num3}");
}
if (!Config.EquipmentLossEnabled.Value)
{
Plugin.Log.LogInfo((object)"Equipment loss is disabled");
}
else if (!flag)
{
list4.RemoveAll((GrabbableObject item) => !((NetworkBehaviour)item).IsSpawned);
int num5 = 0;
foreach (GrabbableObject item6 in list4)
{
if (random.NextDouble() < (double)(Config.LoseEachEquipmentChance.Value / 100f))
{
num5++;
if (num5 > Config.MaxLostEquipmentItems.Value)
{
break;
}
list5.Add(item6.itemProperties?.itemName ?? ((Object)item6).name);
DespawnItem(item6);
Plugin.Log.LogInfo((object)("Lost " + ((Object)item6).name));
}
}
Plugin.Log.LogInfo((object)$"Lost {num5} equipment items");
}
if (list5.Any())
{
string text = $"Lost items ({list5.Count()}/{list2.Count()}): ";
text += string.Join("; ", from s in list5
group s by s into s
select new
{
name = s.Key,
count = s.Count()
} into item
select (item.count <= 1) ? item.name : $"{item.name} x{item.count}");
((MonoBehaviour)HUDManager.Instance).StartCoroutine(DisplayAlert("Quota Overhaul", "", text));
}
}
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.LogInfo((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.DropAllHeldItemsAndSync();
}
NetworkObject component = ((Component)item).gameObject.GetComponent<NetworkObject>();
if ((Object)(object)component != (Object)null && component.IsSpawned)
{
Plugin.Log.LogInfo((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);
}
}
[IteratorStateMachine(typeof(<DisplayAlert>d__4))]
private static IEnumerator DisplayAlert(string headerAlertText = "Quota Overhaul", string bodyAlertText = "", string messageText = "")
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <DisplayAlert>d__4(0)
{
headerAlertText = headerAlertText,
bodyAlertText = bodyAlertText,
messageText = messageText
};
}
}
[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;
}
Plugin.Log.LogInfo((object)"Calculating New Profit Quota...");
TimeOfDay.Instance.profitQuota = QuotaOverhaul.BaseProfitQuota;
return true;
}
public static void Postfix()
{
if (GameNetworkManager.Instance.isHostingGame && CanFinishQuota())
{
QuotaOverhaul.BaseProfitQuota = TimeOfDay.Instance.profitQuota;
QuotaOverhaul.OnNewQuota();
}
}
private static bool CanFinishQuota()
{
int num = TimeOfDay.Instance.quotaVariables.deadlineDaysAmount - TimeOfDay.Instance.daysUntilDeadline;
int num2 = SyncedEntry<int>.op_Implicit(Config.QuotaEarlyFinishLine);
if (num2 < 0)
{
num2 = TimeOfDay.Instance.quotaVariables.deadlineDaysAmount;
}
if (num < num2)
{
return false;
}
return true;
}
}
}