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 QuotaOverhaul;
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+8e62ce30671dab88b4d9fe9cd4a2ad6cbf54c91c")]
[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;
}
}
}
[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 PLUGIN_GUID = "luciusoflegend.lethalcompany.quotaoverhaul";
public const string PLUGIN_NAME = "QuotaOverhaul";
public const string PLUGIN_VERSION = "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!");
}
}
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> quotaMultPerPlayer;
[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", 60, "The amount of money you start with. \nVanilla: 60");
startingQuota = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Quota Settings", "Starting Quota", 300, "The quota value at the start of a new game. \nVanilla: 130");
quotaBaseIncrease = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Quota Settings", "Quota Base Increase", 200, "The minimum amount of quota increase. \nVanilla: 200");
quotaIncreaseSteepness = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Quota Settings", "Quota Increase Steepness", 4f, "The steepness of the quota increase curve - higher value means a less steep exponential increase. \nVanilla: 4");
quotaRandomizerMultiplier = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Quota Settings", "Quota Randomizer Multiplier", 1f, "The multiplier for the quota randomizer - this determines the severity of the randomizer curve. \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", "Early Finish Line", 0, "The minimum number of days that need to pass before the quota is allowed to end. Values lower than 0 make this equal to the Quota Deadline. \nVanilla: 0");
quotaEnablePlayerMultiplier = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Quota Settings", "Enable Player Count Multiplier", true, "Multiply the quota based on the number of players.\nVanilla: false");
quotaPlayerThreshold = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Quota Settings", "Player Count Threshold", 3, "The quota multiplier will increase for every player beyond this threshold.");
quotaPlayerCap = SyncedBindingExtensions.BindSyncedEntry<int>(config, "Quota Settings", "Player Count Cap", 8, "Adding more players beyond this cap will not increase the quota multiplier.");
quotaMultPerPlayer = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Quota Settings", "Multiplier Per Player", 0.3f, "The multiplier for each player above the threshold.");
creditPenaltiesEnabled = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Credit Penalties", "Enable", false, "Toggle losing credits for each player that dies. Works just like vanilla when enabled. \nVanilla: true");
creditPenaltiesOnGordion = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Credit Penalties", "Enable At The Company", false, "Whether to allow credit penalties at the company building.");
creditPenaltyPercentPerPlayer = SyncedBindingExtensions.BindSyncedEntry<float>(config, "Credit Penalties", "Penalty Per Player", 20f, "The amount of credits to lose per dead player, as a percentage of current credits. \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 what fraction of total players have died. AKA it scales with player count.");
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 Threshold Percent", 20f, "Applied after penalty is calculated. If the penalty falls below this threshold, the penalty is set to 0. Increasing this value makes minor slip-ups more forgiving. \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 what fraction of total players have died. AKA it scales with player count.");
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 Threshold Percent", 15f, "Applied after penalty is calculated. If the penalty falls below this threshold, the penalty is set to 0. Increasing this value makes minor slip-ups more forgiving. \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. A higher value means a higher incentive to recover bodies. Applies to both normal and dynamic modes. \nValues between 0-100");
scrapLossEnabled = SyncedBindingExtensions.BindSyncedEntry<bool>(config, "Scrap Loss", "Scrap Loss Enabled", false, "Toggle losing scrap when all players die. \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. \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);
}
}
[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)
{
bool flag = SyncedEntry<bool>.op_Implicit(Config.creditPenaltiesEnabled) && (SyncedEntry<bool>.op_Implicit(Config.creditPenaltiesOnGordion) || StartOfRound.Instance.currentLevel.PlanetName != "71 Gordion");
bool flag2 = SyncedEntry<bool>.op_Implicit(Config.quotaPenaltiesEnabled) && (SyncedEntry<bool>.op_Implicit(Config.quotaPenaltiesOnGordion) || StartOfRound.Instance.currentLevel.PlanetName != "71 Gordion");
int profitQuota = TimeOfDay.Instance.profitQuota;
string text = $"{playersDead} casualties | {bodiesInsured} bodies recovered";
string text2 = "";
if (flag)
{
double num = ((!Config.creditPenaltiesDynamic.Value) ? CalculateCreditPenalty(playersDead, bodiesInsured) : CalculateDynamicCreditPenalty(playersDead, bodiesInsured));
Terminal val = Object.FindObjectOfType<Terminal>();
int groupCredits = val.groupCredits;
if (GameNetworkManager.Instance.isHostingGame)
{
val.groupCredits -= (int)((double)groupCredits * num);
if (val.groupCredits < 0)
{
val.groupCredits = 0;
}
}
text += $"\nCREDITS: -{(int)(num * 100.0)}%";
text2 += $"\ncharged {(int)((double)groupCredits * num)} credits";
}
if (flag2)
{
double num2 = ((!Config.quotaPenaltiesDynamic.Value) ? CalculateQuotaPenalty(playersDead, bodiesInsured) : CalculateDynamicQuotaPenalty(playersDead, bodiesInsured));
QuotaOverhaul.quotaPenaltyMultiplier += num2;
QuotaOverhaul.profitQuota.Value = QuotaOverhaul.CalculateProfitQuota();
text += $"\nQUOTA: +{(int)(num2 * 100.0)}%";
text2 += $"\nraised quota by {TimeOfDay.Instance.profitQuota - profitQuota}";
}
((TMP_Text)HUDManager.Instance.statsUIElements.penaltyAddition).text = text;
((TMP_Text)HUDManager.Instance.statsUIElements.penaltyTotal).text = text2;
}
public 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 = 0.0;
}
Plugin.Log.LogInfo((object)$"Calculated Credit Penalty of {num3}");
return num3;
}
public 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.quotaPenaltyPercentThreshold.Value / 100.0)
{
num3 = 0.0;
}
Plugin.Log.LogInfo((object)$"Calculated Dynamic Credit Penalty of {num3}");
return num3;
}
public 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 = 0.0;
}
Plugin.Log.LogInfo((object)$"Calculated Quota Penalty of {num3}");
return num3;
}
public static double CalculateDynamicQuotaPenalty(int deadBodies, int recoveredBodies)
{
Plugin.Log.LogInfo((object)"Calculaing 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
{
[HarmonyPrefix]
public static bool SkipOriginalDespawnProps()
{
return false;
}
[HarmonyPostfix]
public static void CustomDespawnProps(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 = false;
if (random.NextDouble() < (double)(Config.itemsSafeChance.Value / 100f))
{
flag = true;
}
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.Count() > 0)
{
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));
}
}
public 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}");
}
}
public 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);
}
}
public static IEnumerator DisplayAlert(string headerAlertText = "Quota Overhaul", string bodyAlertText = "", string messageText = "")
{
int index = 0;
while (index < 20 && !StartOfRound.Instance.inShipPhase)
{
index++;
yield return (object)new WaitForSeconds(5f);
}
yield return (object)new WaitForSeconds(2f);
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);
}
}
}
[HarmonyPatch(typeof(StartOfRound), "ResetShip")]
public class ResetShipPatch
{
public static void Postfix()
{
if (GameNetworkManager.Instance.isHostingGame)
{
QuotaOverhaul.OnNewRun();
}
}
}
[HarmonyPatch(typeof(StartOfRound), "SetTimeAndPlanetToSavedSettings")]
public class LoadDataPatch
{
public static void Postfix()
{
if (GameNetworkManager.Instance.isHostingGame)
{
QuotaOverhaul.LoadData();
}
}
}
[HarmonyPatch(typeof(StartOfRound), "OnClientConnect")]
public class OnPlayerConnectPatch
{
public static void Postfix()
{
QuotaOverhaul.OnPlayerCountChanged();
Plugin.Log.LogInfo((object)"Player joined");
}
}
[HarmonyPatch(typeof(StartOfRound), "OnClientDisconnect")]
public class OnPlayerDisconnectPatch
{
public static void Postfix()
{
QuotaOverhaul.OnPlayerCountChanged();
Plugin.Log.LogInfo((object)"Player joined");
}
}
[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();
}
}
public 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;
}
}
public class QuotaOverhaul
{
public static int baseProfitQuota = 0;
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 = false;
public static 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 (SyncedEntry<bool>.op_Implicit(Config.quotaEnablePlayerMultiplier) && usePlayerCountMultiplier)
{
num = (int)((double)num * quotaPlayerMultiplier);
}
if (SyncedEntry<bool>.op_Implicit(Config.quotaPenaltiesEnabled) && usePenaltyMultiplier)
{
num = (int)((double)num * quotaPenaltyMultiplier);
}
return num;
}
public static void UpdatePlayerCountMultiplier()
{
if (Config.quotaEnablePlayerMultiplier.Value)
{
quotaPlayerMultiplier = CalculatePlayerCountMultiplier();
profitQuota.Value = CalculateProfitQuota();
Plugin.Log.LogInfo((object)$"Player Count Multiplier: {quotaPlayerMultiplier}");
}
}
public static double CalculatePlayerCountMultiplier()
{
int num = math.clamp(recordPlayersThisQuota, Config.quotaPlayerThreshold.Value, Config.quotaPlayerCap.Value);
num -= Config.quotaPlayerThreshold.Value;
return Config.quotaMultPerPlayer.Value * (float)math.max(num, 0);
}
public static void OnNewSession()
{
if (GameNetworkManager.Instance.isHostingGame)
{
QuotaSettings quotaVariables = TimeOfDay.Instance.quotaVariables;
quotaVariables.startingQuota = Config.startingQuota.Value;
quotaVariables.baseIncrease = Config.quotaBaseIncrease.Value;
quotaVariables.increaseSteepness = Config.quotaIncreaseSteepness.Value;
quotaVariables.randomizerMultiplier = Config.quotaRandomizerMultiplier.Value;
quotaVariables.startingCredits = SyncedEntry<int>.op_Implicit(Config.startingCredits);
quotaVariables.deadlineDaysAmount = SyncedEntry<int>.op_Implicit(Config.quotaDeadline);
TimeOfDay.Instance.quotaVariables = quotaVariables;
Plugin.Log.LogInfo((object)"Quota Variables Configured");
OnNewRun();
}
}
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()
{
int num = StartOfRound.Instance.connectedPlayersAmount + 1;
if (!StartOfRound.Instance.shipHasLanded)
{
recordPlayersThisMoon = num;
}
else if (num > recordPlayersThisMoon)
{
recordPlayersThisMoon = num;
Plugin.Log.LogInfo((object)$"Record Players this Moon: {recordPlayersThisMoon}");
}
if (!quotaInProgress)
{
recordPlayersThisQuota = num;
}
else if (num > recordPlayersThisQuota)
{
recordPlayersThisQuota = num;
UpdatePlayerCountMultiplier();
Plugin.Log.LogInfo((object)$"Record Players this Quota: {recordPlayersThisQuota}");
}
}
public static void LoadData()
{
if (GameNetworkManager.Instance.isHostingGame)
{
string currentSaveFileName = GameNetworkManager.Instance.currentSaveFileName;
if (ES3.KeyExists("baseProfitQuota", currentSaveFileName))
{
baseProfitQuota = ES3.Load<int>("baseProfitQuota", currentSaveFileName);
}
else
{
baseProfitQuota = SyncedEntry<int>.op_Implicit(Config.startingQuota);
}
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);
}
}
}
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);
}
}
}
}