using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using ConfigurableQuota.Compat;
using ConfigurableQuota.Patches;
using GameNetcodeStuff;
using HarmonyLib;
using LethalNetworkAPI;
using Microsoft.CodeAnalysis;
using OpenLib.Events;
using TMPro;
using Unity.Netcode;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ConfigurableQuota")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Allows users to configure every aspect of the quota the way they want.")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyInformationalVersion("1.2.0+08e8f3d1c646cdb7041c38fc64d78aeaa349ab18")]
[assembly: AssemblyProduct("ConfigurableQuota")]
[assembly: AssemblyTitle("ConfigurableQuota")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.2.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
internal sealed class NullableAttribute : Attribute
{
public readonly byte[] NullableFlags;
public NullableAttribute(byte P_0)
{
NullableFlags = new byte[1] { P_0 };
}
public NullableAttribute(byte[] P_0)
{
NullableFlags = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
internal sealed class NullableContextAttribute : Attribute
{
public readonly byte Flag;
public NullableContextAttribute(byte P_0)
{
Flag = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace ConfigurableQuota
{
public static class ConfigManager
{
public static ConfigEntry<int> StartingCredits;
public static ConfigEntry<int> StartingQuota;
public static ConfigEntry<int> DaysToDeadline;
public static ConfigEntry<int> BaseIncrease;
public static ConfigEntry<float> CurveSharpness;
public static ConfigEntry<float> RandomizerMultiplier;
public static ConfigEntry<int> FinalLevel;
public static ConfigEntry<int> FinalIncrease;
public static ConfigEntry<int> QuotaCap;
public static ConfigEntry<bool> EnablePlayerMultiplier;
public static ConfigEntry<int> PlayerThreshold;
public static ConfigEntry<int> PlayerCap;
public static ConfigEntry<float> MultPerPlayer;
public static ConfigEntry<bool> DisableQuota;
public static ConfigEntry<float> RolloverAmount;
public static ConfigEntry<bool> CreditPenaltiesEnabled;
public static ConfigEntry<bool> CreditPenaltiesOnGordion;
public static ConfigEntry<float> CreditPenaltyPercentPerPlayer;
public static ConfigEntry<bool> CreditPenaltiesDynamic;
public static ConfigEntry<float> CreditPenaltyPercentCap;
public static ConfigEntry<float> CreditPenaltyPercentThreshold;
public static ConfigEntry<float> CreditPenaltyRecoveryBonus;
public static ConfigEntry<bool> QuotaPenaltiesEnabled;
public static ConfigEntry<bool> QuotaPenaltiesOnGordion;
public static ConfigEntry<float> QuotaPenaltyPercentPerPlayer;
public static ConfigEntry<bool> QuotaPenaltiesDynamic;
public static ConfigEntry<float> QuotaPenaltyPercentCap;
public static ConfigEntry<float> QuotaPenaltyPercentThreshold;
public static ConfigEntry<float> QuotaPenaltyRecoveryBonus;
public static ConfigEntry<bool> ScrapLossEnabled;
public static ConfigEntry<float> ItemsSafeChance;
public static ConfigEntry<float> LoseEachScrapChance;
public static ConfigEntry<int> MaxLostScrapItems;
public static ConfigEntry<bool> ValueLossEnabled;
public static ConfigEntry<float> ValueLossPercent;
public static ConfigEntry<bool> RandomizeDeadline;
public static ConfigEntry<int> DeadlineMin;
public static ConfigEntry<int> DeadlineMax;
public static ConfigEntry<bool> DeadlineMustChange;
public static ConfigEntry<bool> EnableGrowthDampening;
public static ConfigEntry<int> DampeningStartAt;
public static ConfigEntry<float> DampeningSharpness;
public static ConfigEntry<bool> EquipmentLossEnabled;
public static ConfigEntry<float> LoseEachEquipmentChance;
public static ConfigEntry<int> MaxLostEquipmentItems;
public static ConfigEntry<float> QuotaAnimationSpeed;
public static ConfigEntry<bool> MinMaxRateEnabled;
public static ConfigEntry<float> MinRate;
public static ConfigEntry<float> MaxRate;
public static ConfigEntry<bool> RandomRateEnabled;
public static ConfigEntry<bool> LastDayRateEnabled;
public static ConfigEntry<float> LastDayRangeChance;
public static ConfigEntry<float> LastDayMinRate;
public static ConfigEntry<float> LastDayMaxRate;
public static ConfigEntry<bool> JackpotEnabled;
public static ConfigEntry<bool> JackpotLastDayOnly;
public static ConfigEntry<float> JackpotChance;
public static ConfigEntry<float> JackpotMinRate;
public static ConfigEntry<float> JackpotMaxRate;
public static ConfigEntry<bool> BuyRateAlertEnabled;
public static ConfigEntry<bool> JackpotAlertEnabled;
public static ConfigEntry<float> AlertDelaySeconds;
internal static void Initialize(ConfigFile config)
{
//IL_027a: Unknown result type (might be due to invalid IL or missing references)
//IL_0284: Expected O, but got Unknown
//IL_0467: Unknown result type (might be due to invalid IL or missing references)
//IL_0471: Expected O, but got Unknown
//IL_049f: Unknown result type (might be due to invalid IL or missing references)
//IL_04a9: Expected O, but got Unknown
//IL_050d: Unknown result type (might be due to invalid IL or missing references)
//IL_0517: Expected O, but got Unknown
//IL_0560: Unknown result type (might be due to invalid IL or missing references)
//IL_056a: Expected O, but got Unknown
//IL_05b3: Unknown result type (might be due to invalid IL or missing references)
//IL_05bd: Expected O, but got Unknown
//IL_0606: Unknown result type (might be due to invalid IL or missing references)
//IL_0610: Expected O, but got Unknown
//IL_063e: Unknown result type (might be due to invalid IL or missing references)
//IL_0648: Expected O, but got Unknown
//IL_06ac: Unknown result type (might be due to invalid IL or missing references)
//IL_06b6: Expected O, but got Unknown
//IL_06e4: Unknown result type (might be due to invalid IL or missing references)
//IL_06ee: Expected O, but got Unknown
//IL_071c: Unknown result type (might be due to invalid IL or missing references)
//IL_0726: Expected O, but got Unknown
//IL_078a: Unknown result type (might be due to invalid IL or missing references)
//IL_0794: Expected O, but got Unknown
//IL_07c2: Unknown result type (might be due to invalid IL or missing references)
//IL_07cc: Expected O, but got Unknown
//IL_07fa: Unknown result type (might be due to invalid IL or missing references)
//IL_0804: Expected O, but got Unknown
//IL_0868: Unknown result type (might be due to invalid IL or missing references)
//IL_0872: Expected O, but got Unknown
StartingCredits = config.Bind<int>("0. Basic", "StartingCredits", 60, "Starting credits for a new lobby.");
StartingQuota = config.Bind<int>("0. Basic", "StartingQuota", 130, "Starting quota for a new lobby.");
DaysToDeadline = config.Bind<int>("0. Basic", "DaysToDeadline", 3, "Number of days to meet each quota. Ignored if RandomizeDeadline is enabled.");
RandomizeDeadline = config.Bind<bool>("0. Basic", "RandomizeDeadline", false, "Randomize the deadline each quota using Deadline Min/Max instead of a fixed Days To Deadline.");
DeadlineMin = config.Bind<int>("0. Basic", "DeadlineMin", 3, "Minimum days for deadline. REQUIRES 'Randomize Deadline' SET TO TRUE");
DeadlineMax = config.Bind<int>("0. Basic", "DeadlineMax", 5, "Maximum days for deadline. REQUIRES 'Randomize Deadline' SET TO TRUE");
DeadlineMustChange = config.Bind<bool>("0. Basic", "DeadlineMustChange", true, "New deadline after fulfilling quota must differ from the previous one. REQUIRES 'Randomize Deadline' SET TO TRUE");
BaseIncrease = config.Bind<int>("0. Basic", "BaseIncrease", 100, "Base quota increase per quota. Combined with CurveSharpness via: increase ≈ BaseIncrease * (1 + quota² / Sharpness). Example with defaults (100, 16) at quota 3: 100 * (1 + 9/16) ≈ 156.");
CurveSharpness = config.Bind<float>("0. Basic", "CurveSharpness", 16f, "Quota growth curve. Higher = slower growth. Formula: increase ≈ BaseIncrease * (1 + quota² / Sharpness). Example: BaseIncrease=100, Sharpness=16, quota 5 → 100 * (1 + 25/16) ≈ 256.");
RandomizerMultiplier = config.Bind<float>("0. Basic", "RandomizerMultiplier", 1f, "Adds random variance via factor in [1 - 0.5*M, 1 + 0.5*M]. 0 = no randomness, 1 = ±50% (vanilla), 2 = ±100%.");
FinalLevel = config.Bind<int>("1. Leveling", "FinalLevel", -1, "When the previous quota meets or exceeds this value, growth switches from the curve to a flat FinalIncrease. Example: FinalLevel=10000, FinalIncrease=200 → next quota = previous + 200. Set -1 to disable.");
FinalIncrease = config.Bind<int>("1. Leveling", "FinalIncrease", 200, "Fixed increase amount used after reaching Final Level value.");
QuotaCap = config.Bind<int>("1. Leveling", "QuotaCap", -1, "Maximum quota value, quota will never increase more than this amount. Set -1 for no limit.");
EnableGrowthDampening = config.Bind<bool>("1. Leveling", "EnableGrowthDampening", false, "After DampeningStartAt fulfilled quotas, divide the curve increase by (1 + (excess / DampeningSharpness)²) where excess = currentQuota - DampeningStartAt. Reduces growth the longer you play.");
DampeningStartAt = config.Bind<int>("1. Leveling", "DampeningStartAt", 6, "Number of fulfilled quotas before dampening starts. Example: 6 means quotas 1–6 are unaffected; dampening kicks in from quota 7 onward.");
DampeningSharpness = config.Bind<float>("1. Leveling", "DampeningSharpness", 11f, "Lower = stronger dampening. With DampeningStartAt=6, Sharpness=11, at quota 10: divisor = 1 + (4/11)² ≈ 1.13, so growth shrinks ~12%.");
EnablePlayerMultiplier = config.Bind<bool>("2. Player.Scaling", "EnablePlayerMultiplier", false, "Scale each quota increase by (1 + extra * MultPerPlayer) where extra = clamp(playerCount - PlayerThreshold, 0, PlayerCap - PlayerThreshold).");
PlayerThreshold = config.Bind<int>("2. Player.Scaling", "PlayerThreshold", 2, "Player count where scaling begins. Example: 2 means scaling starts at 3+ players.");
PlayerCap = config.Bind<int>("2. Player.Scaling", "PlayerCap", 4, "Maximum players counted for scaling. Example: 4 means player 5+ will not increase the quota multiplier.");
MultPerPlayer = config.Bind<float>("2. Player.Scaling", "MultPerPlayer", 0.25f, "Extra multiplier per player above the threshold. Example: 4 players, Threshold=2, Cap=4, MultPerPlayer=0.25 → multiplier = 1 + 2*0.25 = 1.5x quota increase.");
DisableQuota = config.Bind<bool>("3. Optional", "DisableQuota", false, "Completely disables the quota system.");
RolloverAmount = config.Bind<float>("3. Optional", "RolloverAmount", 0f, new ConfigDescription("Percentage of extra scrap value that goes above the set limit and is added to the next quota. 0 = none (vanilla), 0.5 = 50%, 1.0 = 100%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
CreditPenaltiesEnabled = config.Bind<bool>("4. Penalties.Credits", "Enabled", false, "Reduce credits when crew members die.");
CreditPenaltiesOnGordion = config.Bind<bool>("4. Penalties.Credits", "OnGordion", false, "Apply credit penalties even when visiting The Company.");
CreditPenaltyPercentPerPlayer = config.Bind<float>("4. Penalties.Credits", "PercentPerPlayer", 0.15f, "Credits lost per dead player. Example: 0.15 = lose 15% of credits per death. Ignored if 'Dynamic' is true.");
CreditPenaltiesDynamic = config.Bind<bool>("4. Penalties.Credits", "Dynamic", false, "Scale credit penalty by (dead/total) * PercentCap. Example: 2 dead out of 8 total with PercentCap=0.05 -> (2/8)*0.05 = 1.25% penalty. When false, uses PercentPerPlayer * dead, capped at PercentCap.");
CreditPenaltyPercentCap = config.Bind<float>("4. Penalties.Credits", "PercentCap", 0.8f, "Maximum percentage of credits that can be lost.");
CreditPenaltyPercentThreshold = config.Bind<float>("4. Penalties.Credits", "PercentThreshold", 0f, "Ignore penalties below this percentage. Example: 0.1 = penalties under 10% are not applied.");
CreditPenaltyRecoveryBonus = config.Bind<float>("4. Penalties.Credits", "RecoveryBonus", 0f, "Multiply the penalty by (1 - RecoveryBonus * recovered/dead). Example: 4 dead, 2 recovered, RecoveryBonus=0.5 → final = base * (1 - 0.5*0.5) = base * 0.75 (25% reduction).");
QuotaPenaltiesEnabled = config.Bind<bool>("5. Penalties.Quota", "Enabled", false, "Increase the current quota when crew members die.");
QuotaPenaltiesOnGordion = config.Bind<bool>("5. Penalties.Quota", "OnGordion", false, "Apply quota penalties even when visiting The Company.");
QuotaPenaltyPercentPerPlayer = config.Bind<float>("5. Penalties.Quota", "PercentPerPlayer", 0.1f, "Quota increase per dead player. Example: 0.1 = +10% to current quota per death. Ignored if 'Dynamic' is true.");
QuotaPenaltiesDynamic = config.Bind<bool>("5. Penalties.Quota", "Dynamic", false, "Scale quota increase by (dead/total) * PercentCap. Example: 2 dead out of 8 total with PercentCap=0.5 -> (2/8)*0.5 = 12.5% increase. When false, uses PercentPerPlayer * dead, capped at PercentCap.");
QuotaPenaltyPercentCap = config.Bind<float>("5. Penalties.Quota", "PercentCap", 0.5f, "Maximum percentage the quota can increase. Example: 0.5 = quota can increase by at most 50%.");
QuotaPenaltyPercentThreshold = config.Bind<float>("5. Penalties.Quota", "PercentThreshold", 0f, "Ignore penalties below this percentage. Example: 0.15 = increases under 15% are not applied.");
QuotaPenaltyRecoveryBonus = config.Bind<float>("5. Penalties.Quota", "RecoveryBonus", 0f, "Multiply the penalty by (1 - RecoveryBonus * recovered/dead). Example: 4 dead, 2 recovered, RecoveryBonus=0.5 → final = base * (1 - 0.5*0.5) = base * 0.75 (25% reduction).");
ScrapLossEnabled = config.Bind<bool>("6. Loss.Scrap", "Enabled", false, "Randomly lose collected scrap when all crew dies.");
ItemsSafeChance = config.Bind<float>("6. Loss.Scrap", "ItemsSafeChance", 0.5f, new ConfigDescription("Chance for each scrap to be protected from loss. Combined with LoseEachScrapChance: actual loss chance per item = (1 - SafeChance) * LoseChance. Example: Safe=0.5, Lose=0.1 → 5% per item.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
LoseEachScrapChance = config.Bind<float>("6. Loss.Scrap", "LoseEachScrapChance", 0.1f, new ConfigDescription("If scrap is not safe, this is the chance it gets lost. Example: 0.2 = 20% chance to lose unprotected scrap.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
MaxLostScrapItems = config.Bind<int>("6. Loss.Scrap", "MaxLostScrapItems", 2, "Maximum scrap that can be lost per round.");
ValueLossEnabled = config.Bind<bool>("7. Loss.Value", "Enabled", false, "Reduce the scrap value of all ship items when the entire crew is wiped.");
ValueLossPercent = config.Bind<float>("7. Loss.Value", "Percent", 0.2f, new ConfigDescription("Percentage to reduce scrap value. Example: 0.25 = all scrap items lose 25% of their value, stacks on repeated wipes.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
EquipmentLossEnabled = config.Bind<bool>("8. Loss.Equipment", "Enabled", false, "Randomly lose purchased equipment when all crew dies.");
LoseEachEquipmentChance = config.Bind<float>("8. Loss.Equipment", "LoseEachEquipmentChance", 0.05f, new ConfigDescription("Chance for each equipment item to be lost. Example: 0.1 = 10% chance per item.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
MaxLostEquipmentItems = config.Bind<int>("8. Loss.Equipment", "MaxLostEquipmentItems", 1, "Maximum equipment items that can be lost per round. Example: 1 = lose at most 1 item.");
QuotaAnimationSpeed = config.Bind<float>("9. UI", "QuotaAnimationSpeed", 1f, new ConfigDescription("Speed multiplier for the new quota animation. Higher = faster.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 2f), Array.Empty<object>()));
MinMaxRateEnabled = config.Bind<bool>("X. Buy.Rate", "MinMaxEnabled", false, "Clamp the Company's daily buy rate to [MinRate, MaxRate]. Required for RandomRateEnabled to take effect.");
MinRate = config.Bind<float>("X. Buy.Rate", "MinRate", 0.2f, new ConfigDescription("Minimum buy rate the Company will pay. 0.2 = 20%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
MaxRate = config.Bind<float>("X. Buy.Rate", "MaxRate", 1.2f, new ConfigDescription("Maximum buy rate the Company will pay. 1.2 = 120%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
RandomRateEnabled = config.Bind<bool>("X. Buy.Rate", "RandomRateEnabled", false, "Pick the daily buy rate uniformly in [MinRate, MaxRate] each day. Requires MinMaxEnabled. Example: MinRate=0.4, MaxRate=1.5 → daily rate is a random value between 40% and 150%.");
LastDayRateEnabled = config.Bind<bool>("X. Buy.Rate", "LastDayRateEnabled", false, "On the deadline's last day, override the rate via LastDayMinRate / LastDayMaxRate. Behavior: if min == max, always that value. If min < max, roll LastDayRangeChance — on hit pick a random value in [min, max], on miss fall back to 100%.");
LastDayRangeChance = config.Bind<float>("X. Buy.Rate", "LastDayRangeChance", 0.3f, new ConfigDescription("Chance to use the LastDayMin/Max range instead of the 100% fallback. Example: 0.3 = 30% chance for the random pick.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
LastDayMinRate = config.Bind<float>("X. Buy.Rate", "LastDayMinRate", 1f, new ConfigDescription("Minimum buy rate on the last day. 1.0 = 100%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
LastDayMaxRate = config.Bind<float>("X. Buy.Rate", "LastDayMaxRate", 1.2f, new ConfigDescription("Maximum buy rate on the last day. 1.2 = 120%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
JackpotEnabled = config.Bind<bool>("X. Buy.Rate", "JackpotEnabled", false, "Allow a chance to roll a jackpot rate. Jackpot is checked first (before LastDayRate or RandomRate).");
JackpotLastDayOnly = config.Bind<bool>("X. Buy.Rate", "JackpotLastDayOnly", true, "Jackpot rolls only happen at the deadline's last day.");
JackpotChance = config.Bind<float>("X. Buy.Rate", "JackpotChance", 0.01f, new ConfigDescription("Chance to roll a jackpot. Example: 0.01 = 1% chance per day (or only on last day if JackpotLastDayOnly).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
JackpotMinRate = config.Bind<float>("X. Buy.Rate", "JackpotMinRate", 1.5f, new ConfigDescription("Minimum jackpot rate. 1.5 = 150%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
JackpotMaxRate = config.Bind<float>("X. Buy.Rate", "JackpotMaxRate", 3f, new ConfigDescription("Maximum jackpot rate. 3.0 = 300%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
BuyRateAlertEnabled = config.Bind<bool>("X. Buy.Rate", "BuyRateAlertEnabled", false, "Show a yellow on-screen alert with the new buy rate each day. Local to this client.");
JackpotAlertEnabled = config.Bind<bool>("X. Buy.Rate", "JackpotAlertEnabled", false, "Show a red SCRAP EMERGENCY alert with sound when a jackpot is rolled. Local to this client.");
AlertDelaySeconds = config.Bind<float>("X. Buy.Rate", "AlertDelaySeconds", 3f, new ConfigDescription("Seconds to wait before showing the buy-rate alert. Recommended: 3s when alone, 8+s when running BetterEXP / DiscountAlerts to avoid overlap.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
}
}
internal static class Metadata
{
public const string GUID = "com.seeya.configurablequota";
public const string PLUGIN_NAME = "Configurable Quota";
public const string VERSION = "1.1.0";
public const string LETHAL_NETWORK_API_GUID = "LethalNetworkAPI";
public const string LETHAL_CONSTELLATIONS_GUID = "com.github.darmuh.LethalConstellations";
public const string LETHAL_MOON_UNLOCKS_GUID = "com.xmods.lethalmoonunlocks";
public const string OPEN_LIB_GUID = "darmuh.OpenLib";
}
[BepInPlugin("com.seeya.configurablequota", "Configurable Quota", "1.1.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BaseUnityPlugin
{
private readonly Harmony _harmony = new Harmony("com.seeya.configurablequota");
public static Plugin Instance { get; private set; }
public static ManualLogSource Log { get; private set; }
private void Awake()
{
Instance = this;
Log = ((BaseUnityPlugin)this).Logger;
Log.LogInfo((object)"Initializing Configurable Quota");
ConfigManager.Initialize(((BaseUnityPlugin)this).Config);
ConstellationDeadlineConfig.Initialize();
NetworkSync.Initialize();
_harmony.PatchAll();
OpenLibEventBridge.TrySubscribe();
Log.LogInfo((object)"Configurable Quota is loaded!");
}
}
}
namespace ConfigurableQuota.Patches
{
[HarmonyPatch(typeof(TimeOfDay))]
internal static class BuyingRatePatch
{
[CompilerGenerated]
private sealed class <AlertCoroutine>d__6 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public float delay;
public bool isJackpot;
public int rateRounded;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <AlertCoroutine>d__6(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0028: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>2__current = (object)new WaitForSecondsRealtime(delay);
<>1__state = 1;
return true;
case 1:
{
<>1__state = -1;
HUDManager instance = HUDManager.Instance;
if ((Object)(object)instance == (Object)null)
{
return false;
}
if (isJackpot)
{
instance.DisplayTip("<color=#ffc526>SCRAP EMERGENCY</color>", $"<color=#fcbf17>\n* Buying rates have jumped to {rateRounded}%</color>", true, false, "LC_JackpotTip1");
if ((Object)(object)instance.UIAudio != (Object)null && (Object)(object)instance.globalNotificationSFX != (Object)null)
{
instance.UIAudio.PlayOneShot(instance.globalNotificationSFX);
}
}
else
{
instance.DisplayTip("New Scrap Rate", $"\n* Buying rates have changed to {rateRounded}%", false, false, "LC_JackpotTip2");
}
return false;
}
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <ReapplyBuyRate>d__4 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public float delay;
public float rate;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <ReapplyBuyRate>d__4(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0028: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>2__current = (object)new WaitForSeconds(delay);
<>1__state = 1;
return true;
case 1:
{
<>1__state = -1;
StartOfRound instance = StartOfRound.Instance;
if ((Object)(object)instance == (Object)null)
{
return false;
}
if (!Mathf.Approximately(instance.companyBuyingRate, rate))
{
instance.companyBuyingRate = rate;
Plugin.Log.LogDebug((object)$"Re-applied buy rate {Mathf.RoundToInt(rate * 100f)}% after {delay:0.#}s.");
}
return false;
}
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
private const float ReapplyDelaySeconds = 3f;
[HarmonyPatch("SetBuyingRateForDay")]
[HarmonyPostfix]
[HarmonyPriority(200)]
private static void SetBuyingRateForDay_Postfix(TimeOfDay __instance)
{
try
{
if (((NetworkBehaviour)__instance).IsServer && !ConfigManager.DisableQuota.Value)
{
StartOfRound instance = StartOfRound.Instance;
if (!((Object)(object)instance == (Object)null))
{
float companyBuyingRate = instance.companyBuyingRate;
int daysUntilDeadline = __instance.daysUntilDeadline;
(float rate, bool isJackpot, string source) tuple = ResolveRate(companyBuyingRate, daysUntilDeadline);
float item = tuple.rate;
bool item2 = tuple.isJackpot;
string item3 = tuple.source;
instance.companyBuyingRate = item;
int num = Mathf.RoundToInt(item * 100f);
Plugin.Log.LogInfo((object)$"Buy rate set to {num}% ({item3}, jackpot={item2}).");
((MonoBehaviour)__instance).StartCoroutine(ReapplyBuyRate(item, 3f));
NetworkSync.SyncBuyingRateToClients(item, item2);
DisplayBuyRateAlert(item, item2);
}
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not apply buy rate modifiers: " + ex.Message));
}
}
internal static void ApplyReceivedBuyingRate(float rate, bool isJackpot)
{
StartOfRound instance = StartOfRound.Instance;
if ((Object)(object)instance != (Object)null)
{
instance.companyBuyingRate = rate;
}
DisplayBuyRateAlert(rate, isJackpot);
}
private static (float rate, bool isJackpot, string source) ResolveRate(float vanillaRate, int daysUntilDeadline)
{
bool value = ConfigManager.MinMaxRateEnabled.Value;
float value2 = ConfigManager.MinRate.Value;
float num = Mathf.Max(value2, ConfigManager.MaxRate.Value);
bool value3 = ConfigManager.JackpotEnabled.Value;
bool value4 = ConfigManager.JackpotLastDayOnly.Value;
float num2 = Mathf.Clamp01(ConfigManager.JackpotChance.Value);
float value5 = ConfigManager.JackpotMinRate.Value;
float num3 = Mathf.Max(value5, ConfigManager.JackpotMaxRate.Value);
bool value6 = ConfigManager.LastDayRateEnabled.Value;
float num4 = Mathf.Clamp01(ConfigManager.LastDayRangeChance.Value);
float value7 = ConfigManager.LastDayMinRate.Value;
float num5 = Mathf.Max(value7, ConfigManager.LastDayMaxRate.Value);
bool value8 = ConfigManager.RandomRateEnabled.Value;
bool flag = daysUntilDeadline == 0;
if (value3 && (!value4 || flag) && Random.value <= num2)
{
float item = ((value5 == num3) ? value5 : Random.Range(value5, num3));
return (item, true, value4 ? "jackpot last-day" : "jackpot any-day");
}
if (value6 && flag)
{
if (value7 == num5)
{
return (value7, false, "last-day fixed");
}
if (Random.value <= num4)
{
return (Random.Range(value7, num5), false, "last-day ranged");
}
return (1f, false, "last-day fallback");
}
if (value8 && value)
{
return (Random.Range(value2, num), false, "random in range");
}
if (value)
{
return (Mathf.Clamp(vanillaRate, value2, num), false, "min/max clamp");
}
return (vanillaRate, false, "vanilla");
}
[IteratorStateMachine(typeof(<ReapplyBuyRate>d__4))]
private static IEnumerator ReapplyBuyRate(float rate, float delay)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <ReapplyBuyRate>d__4(0)
{
rate = rate,
delay = delay
};
}
internal static void DisplayBuyRateAlert(float rate, bool isJackpot)
{
try
{
HUDManager instance = HUDManager.Instance;
TimeOfDay instance2 = TimeOfDay.Instance;
if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance2 == (Object)null) && (isJackpot ? ConfigManager.JackpotAlertEnabled.Value : ConfigManager.BuyRateAlertEnabled.Value))
{
int rateRounded = Mathf.RoundToInt(rate * 100f);
float delay = Mathf.Max(0f, ConfigManager.AlertDelaySeconds.Value);
((MonoBehaviour)instance2).StartCoroutine(AlertCoroutine(rateRounded, isJackpot, delay));
}
}
catch (Exception ex)
{
Plugin.Log.LogDebug((object)("Could not schedule buy-rate alert: " + ex.Message));
}
}
[IteratorStateMachine(typeof(<AlertCoroutine>d__6))]
private static IEnumerator AlertCoroutine(int rateRounded, bool isJackpot, float delay)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <AlertCoroutine>d__6(0)
{
rateRounded = rateRounded,
isJackpot = isJackpot,
delay = delay
};
}
}
[HarmonyPatch(typeof(HUDManager))]
internal static class HudQuotaAnimationPatch
{
[CompilerGenerated]
private sealed class <CustomRackUp>d__1 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public float speed;
public HUDManager hud;
private int <quotaTextAmount>5__2;
private int <target>5__3;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <CustomRackUp>d__1(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_003d: Expected O, but got Unknown
//IL_0163: Unknown result type (might be due to invalid IL or missing references)
//IL_016d: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>2__current = (object)new WaitForSeconds(3.5f / speed);
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
if ((Object)(object)TimeOfDay.Instance == (Object)null)
{
return false;
}
<quotaTextAmount>5__2 = 0;
<target>5__3 = TimeOfDay.Instance.profitQuota;
goto IL_00ec;
case 2:
<>1__state = -1;
goto IL_00ec;
case 3:
{
<>1__state = -1;
hud.displayingNewQuota = false;
hud.reachedProfitQuotaAnimator.SetBool("display", false);
return false;
}
IL_00ec:
if (<quotaTextAmount>5__2 < <target>5__3)
{
float num = Time.deltaTime * 250f * speed;
<quotaTextAmount>5__2 = (int)Mathf.Clamp((float)<quotaTextAmount>5__2 + num, (float)(<quotaTextAmount>5__2 + 3), (float)(<target>5__3 + 10));
((TMP_Text)hud.newProfitQuotaText).text = "$" + <quotaTextAmount>5__2;
<>2__current = null;
<>1__state = 2;
return true;
}
((TMP_Text)hud.newProfitQuotaText).text = "$" + <target>5__3;
TimeOfDay.Instance.UpdateProfitQuotaCurrentTime();
hud.UIAudio.PlayOneShot(hud.newProfitQuotaSFX);
<>2__current = (object)new WaitForSeconds(1.25f / Mathf.Clamp(speed, 0.5f, 2f));
<>1__state = 3;
return true;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[HarmonyPatch("rackUpNewQuotaText")]
[HarmonyPrefix]
private static bool RackUpNewQuotaText_Prefix(HUDManager __instance, ref IEnumerator __result)
{
float speed = Mathf.Clamp(ConfigManager.QuotaAnimationSpeed.Value, 0.1f, 2f);
__result = CustomRackUp(__instance, speed);
return false;
}
[IteratorStateMachine(typeof(<CustomRackUp>d__1))]
private static IEnumerator CustomRackUp(HUDManager hud, float speed)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <CustomRackUp>d__1(0)
{
hud = hud,
speed = speed
};
}
[HarmonyPatch("FillEndGameStats")]
[HarmonyPostfix]
[HarmonyAfter(new string[] { "com.example.Advancedfeatures" })]
private static void FillEndGameStats_Postfix()
{
TryApplyAdvancedFeaturesEndscreen();
}
internal static void TryApplyAdvancedFeaturesEndscreen()
{
if (!ConfigManager.ScrapLossEnabled.Value || !Chainloader.PluginInfos.ContainsKey("com.example.Advancedfeatures") || !PenaltiesOnLandingPatch.HasAllDeadSnapshot)
{
return;
}
try
{
HUDManager instance = HUDManager.Instance;
if (!((Object)(object)instance?.statsUIElements?.allPlayersDeadOverlay != (Object)null) || !((Behaviour)instance.statsUIElements.allPlayersDeadOverlay).enabled)
{
return;
}
Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "AdvancedFeatures");
if (!(assembly == null))
{
Type type = assembly.GetType("AdvancedFeatures.Endscreen");
if (!(type == null))
{
ApplyAdvancedFeaturesEndscreen(type);
}
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not apply Advanced Features end screen compatibility: " + ex.Message));
}
}
private static void ApplyAdvancedFeaturesEndscreen(Type endscreenType)
{
FieldInfo fieldInfo = AccessTools.Field(endscreenType, "AreAllDead");
FieldInfo fieldInfo2 = AccessTools.Field(endscreenType, "ScrapLost");
FieldInfo fieldInfo3 = AccessTools.Field(endscreenType, "ScrapLostText");
FieldInfo fieldInfo4 = AccessTools.Field(endscreenType, "CollectedText");
FieldInfo fieldInfo5 = AccessTools.Field(endscreenType, "TotalText");
FieldInfo fieldInfo6 = AccessTools.Field(endscreenType, "CollectedLine");
FieldInfo fieldInfo7 = AccessTools.Field(endscreenType, "CollectedLabel");
object? obj = fieldInfo2?.GetValue(null);
Transform val = (Transform)((obj is Transform) ? obj : null);
if (PenaltiesOnLandingPatch.TryGetScrapLossSummary(out var beforeValue, out var afterValue, out var lostPercent))
{
int num = Mathf.Max(0, beforeValue - afterValue);
int num2 = Mathf.RoundToInt(Mathf.Clamp01(lostPercent) * 100f);
string value = $"Lost {num2}% scrap (${num}/{beforeValue})";
object? obj2 = fieldInfo4?.GetValue(null);
Component val2 = (Component)((obj2 is Component) ? obj2 : null);
object? obj3 = fieldInfo5?.GetValue(null);
Component val3 = (Component)((obj3 is Component) ? obj3 : null);
object? obj4 = fieldInfo6?.GetValue(null);
Transform val4 = (Transform)((obj4 is Transform) ? obj4 : null);
object? obj5 = fieldInfo7?.GetValue(null);
Transform val5 = (Transform)((obj5 is Transform) ? obj5 : null);
object obj6 = fieldInfo3?.GetValue(null);
PropertyInfo propertyInfo = obj6?.GetType().GetProperty("text");
if ((Object)(object)val2 != (Object)null)
{
val2.gameObject.SetActive(false);
}
if ((Object)(object)val3 != (Object)null)
{
val3.gameObject.SetActive(false);
}
if ((Object)(object)val4 != (Object)null)
{
((Component)val4).gameObject.SetActive(false);
}
if ((Object)(object)val5 != (Object)null)
{
((Component)val5).gameObject.SetActive(false);
}
if ((Object)(object)val != (Object)null)
{
((Component)val).gameObject.SetActive(true);
}
propertyInfo?.SetValue(obj6, value);
Plugin.Log.LogInfo((object)$"Updated Advanced Features scrap-loss text: {num2}% (${num}/{beforeValue}).");
}
else if ((Object)(object)val != (Object)null)
{
((Component)val).gameObject.SetActive(false);
}
fieldInfo?.SetValue(null, false);
}
[HarmonyPatch("ApplyPenalty")]
[HarmonyPrefix]
private static bool ApplyPenalty_Prefix()
{
return false;
}
[HarmonyPatch("ApplyPenalty")]
[HarmonyPostfix]
private static void ApplyPenalty_Postfix(HUDManager __instance, int playersDead, int bodiesInsured)
{
try
{
(int dead, int total, int recovered) tuple = ResolvePenaltyCounts(playersDead, bodiesInsured);
int item = tuple.dead;
int item2 = tuple.total;
int item3 = tuple.recovered;
bool atCompany = PenaltyHelpers.IsOnGordion();
var (creditPct, num) = ComputeCreditPenalty(item, item2, item3, atCompany);
var (quotaPct, quotaDelta) = ComputeQuotaPenalty(item, item2, item3, atCompany);
((TMP_Text)__instance.statsUIElements.penaltyAddition).text = BuildPenaltyText(item, item3, creditPct, quotaPct, quotaDelta);
((TMP_Text)__instance.statsUIElements.penaltyTotal).text = ((num > 0) ? $"DUE: ${num}" : "");
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not update penalty text on the end screen: " + ex.Message));
}
}
private static (int dead, int total, int recovered) ResolvePenaltyCounts(int playersDead, int bodiesInsured)
{
if (PenaltiesOnLandingPatch.HasPenaltyCache)
{
return (PenaltiesOnLandingPatch.CachedDead, PenaltiesOnLandingPatch.CachedTotal, PenaltiesOnLandingPatch.CachedRecovered);
}
var (num, num2, item) = PenaltyHelpers.CountDeathsAndRecovered();
if (num == 0 && playersDead > 0)
{
num = playersDead;
item = bodiesInsured;
num2 = Mathf.Max(num + 1, num2);
}
return (num, num2, item);
}
private static (float pct, int loss) ComputeCreditPenalty(int dead, int total, int recovered, bool atCompany)
{
if (!ConfigManager.CreditPenaltiesEnabled.Value || dead == 0 || (atCompany && !ConfigManager.CreditPenaltiesOnGordion.Value))
{
return (0f, 0);
}
int num = Object.FindObjectOfType<Terminal>()?.groupCredits ?? 0;
float num2 = PenaltyHelpers.ComputePenaltyPercent(ConfigManager.CreditPenaltiesDynamic.Value, ConfigManager.CreditPenaltyPercentPerPlayer.Value, ConfigManager.CreditPenaltyPercentCap.Value, ConfigManager.CreditPenaltyPercentThreshold.Value, ConfigManager.CreditPenaltyRecoveryBonus.Value, dead, total, recovered);
return (num2, Mathf.RoundToInt((float)num * num2));
}
private static (float pct, int delta) ComputeQuotaPenalty(int dead, int total, int recovered, bool atCompany)
{
if (!ConfigManager.QuotaPenaltiesEnabled.Value || dead == 0 || (atCompany && !ConfigManager.QuotaPenaltiesOnGordion.Value))
{
return (0f, 0);
}
float num = PenaltyHelpers.ComputePenaltyPercent(ConfigManager.QuotaPenaltiesDynamic.Value, ConfigManager.QuotaPenaltyPercentPerPlayer.Value, ConfigManager.QuotaPenaltyPercentCap.Value, ConfigManager.QuotaPenaltyPercentThreshold.Value, ConfigManager.QuotaPenaltyRecoveryBonus.Value, dead, total, recovered);
int num2 = PenaltiesOnLandingPatch.CachedQuotaPenaltyDelta;
if (num2 <= 0)
{
TimeOfDay instance = TimeOfDay.Instance;
num2 = (((Object)(object)instance != (Object)null) ? Mathf.RoundToInt((float)Mathf.Max(1, instance.profitQuota) * num) : 0);
}
return (num, num2);
}
private static string BuildPenaltyText(int dead, int recovered, float creditPct, float quotaPct, int quotaDelta)
{
string text = ((creditPct > 0f) ? $"{dead} casualties: -{Mathf.RoundToInt(creditPct * 100f)}%" : $"{dead} casualties");
string text2 = text + $"\n({recovered} of {dead} bodies recovered.)";
if (quotaPct > 0f)
{
text2 += $"\n\nQuota: {Mathf.RoundToInt(quotaPct * 100f)}% (${quotaDelta})";
}
return text2;
}
}
internal static class NetworkSync
{
private static LNetworkMessage<int>? _syncCreditsMessage;
private static LNetworkMessage<SyncQuotaData>? _syncQuotaMessage;
private static LNetworkMessage<SyncValueLossData>? _syncValueLossMessage;
private static LNetworkMessage<int>? _syncDeadlineMessage;
private static LNetworkMessage<int>? _syncRolloverMessage;
private static LNetworkMessage<SyncScrapLossSummary>? _syncScrapLossSummaryMessage;
private static LNetworkMessage<SyncBuyingRateData>? _syncBuyingRateMessage;
public static void Initialize()
{
try
{
_syncCreditsMessage = LNetworkMessage<int>.Connect("ConfigurableQuota_SyncCredits", (Action<int, ulong>)null, (Action<int>)OnCreditsReceived, (Action<int, ulong>)null);
_syncQuotaMessage = LNetworkMessage<SyncQuotaData>.Connect("ConfigurableQuota_SyncQuota", (Action<SyncQuotaData, ulong>)null, (Action<SyncQuotaData>)OnQuotaReceived, (Action<SyncQuotaData, ulong>)null);
_syncValueLossMessage = LNetworkMessage<SyncValueLossData>.Connect("ConfigurableQuota_SyncValueLoss", (Action<SyncValueLossData, ulong>)null, (Action<SyncValueLossData>)OnValueLossReceived, (Action<SyncValueLossData, ulong>)null);
_syncDeadlineMessage = LNetworkMessage<int>.Connect("ConfigurableQuota_SyncDeadline", (Action<int, ulong>)null, (Action<int>)OnDeadlineReceived, (Action<int, ulong>)null);
_syncRolloverMessage = LNetworkMessage<int>.Connect("ConfigurableQuota_SyncRollover", (Action<int, ulong>)null, (Action<int>)OnRolloverReceived, (Action<int, ulong>)null);
_syncScrapLossSummaryMessage = LNetworkMessage<SyncScrapLossSummary>.Connect("ConfigurableQuota_SyncScrapLossSummary", (Action<SyncScrapLossSummary, ulong>)null, (Action<SyncScrapLossSummary>)OnScrapLossSummaryReceived, (Action<SyncScrapLossSummary, ulong>)null);
_syncBuyingRateMessage = LNetworkMessage<SyncBuyingRateData>.Connect("ConfigurableQuota_SyncBuyingRate", (Action<SyncBuyingRateData, ulong>)null, (Action<SyncBuyingRateData>)OnBuyingRateReceived, (Action<SyncBuyingRateData, ulong>)null);
Plugin.Log.LogInfo((object)"Network sync is ready.");
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not initialize network sync: " + ex.Message));
}
}
public static void SyncCreditsToClients(int credits)
{
try
{
if (_syncCreditsMessage == null)
{
Plugin.Log.LogWarning((object)"Credits message not initialized");
}
else
{
_syncCreditsMessage.SendClients(credits);
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not sync credits: " + ex.Message));
}
}
private static void OnCreditsReceived(int credits)
{
try
{
Terminal val = Object.FindObjectOfType<Terminal>();
if ((Object)(object)val != (Object)null)
{
val.groupCredits = credits;
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not apply synced credits on client: " + ex.Message));
}
}
public static void SyncScrapLossSummaryToClients(int beforeValue, int afterValue)
{
try
{
if (_syncScrapLossSummaryMessage == null)
{
Plugin.Log.LogWarning((object)"Scrap loss summary message not initialized");
}
else
{
_syncScrapLossSummaryMessage.SendClients(new SyncScrapLossSummary(beforeValue, afterValue));
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not sync scrap loss summary: " + ex.Message));
}
}
private static void OnScrapLossSummaryReceived(SyncScrapLossSummary data)
{
try
{
if (data.BeforeValue > 0)
{
PenaltiesOnLandingPatch.CacheScrapLossSummary(data.BeforeValue, data.AfterValue);
}
else
{
PenaltiesOnLandingPatch.ClearScrapLossSummary();
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not apply synced scrap loss summary on client: " + ex.Message));
}
}
public static void SyncQuotaToClients(int quota, int penaltyDelta = 0)
{
try
{
if (_syncQuotaMessage == null)
{
Plugin.Log.LogWarning((object)"Quota message not initialized");
}
else
{
_syncQuotaMessage.SendClients(new SyncQuotaData(quota, penaltyDelta));
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not sync quota: " + ex.Message));
}
}
private static void OnQuotaReceived(SyncQuotaData data)
{
try
{
TimeOfDay instance = TimeOfDay.Instance;
if ((Object)(object)instance != (Object)null)
{
instance.profitQuota = data.Quota;
}
PenaltiesOnLandingPatch.CachedQuotaPenaltyDelta = data.PenaltyDelta;
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not apply synced quota on client: " + ex.Message));
}
}
public static void SyncValueLossToClients(SyncValueLossData[] items)
{
try
{
if (_syncValueLossMessage == null)
{
Plugin.Log.LogWarning((object)"Value loss message not initialized");
return;
}
foreach (SyncValueLossData syncValueLossData in items)
{
_syncValueLossMessage.SendClients(syncValueLossData);
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not sync value loss: " + ex.Message));
}
}
private static void OnValueLossReceived(SyncValueLossData data)
{
try
{
GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>();
GrabbableObject[] array2 = array;
foreach (GrabbableObject val in array2)
{
try
{
NetworkObject component = ((Component)val).GetComponent<NetworkObject>();
if (!((Object)(object)val != (Object)null) || !((Object)(object)component != (Object)null) || component.NetworkObjectId != data.NetworkObjectId)
{
continue;
}
Item itemProperties = val.itemProperties;
if (itemProperties != null && itemProperties.isScrap)
{
val.scrapValue = data.NewValue;
try
{
val.SetScrapValue(data.NewValue);
break;
}
catch
{
break;
}
}
}
catch
{
}
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not apply synced value loss on client: " + ex.Message));
}
}
public static void SyncDeadlineToClients(int days)
{
try
{
ApplyDeadline(days);
if (_syncDeadlineMessage == null)
{
Plugin.Log.LogWarning((object)"Deadline message not initialized");
}
else
{
_syncDeadlineMessage.SendClients(days);
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not sync deadline: " + ex.Message));
}
}
private static void OnDeadlineReceived(int days)
{
try
{
ApplyDeadline(days);
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not apply synced deadline on client: " + ex.Message));
}
}
private static void ApplyDeadline(int days)
{
TimeOfDay instance = TimeOfDay.Instance;
if (!((Object)(object)instance == (Object)null))
{
int num = Mathf.Max(1, days);
float num2 = instance.totalTime;
if (num2 <= 0f && instance.daysUntilDeadline > 0 && instance.timeUntilDeadline > 0f)
{
num2 = instance.timeUntilDeadline / (float)instance.daysUntilDeadline;
}
if (instance.quotaVariables != null)
{
instance.quotaVariables.deadlineDaysAmount = num;
}
instance.daysUntilDeadline = num;
if (num2 > 0f)
{
instance.timeUntilDeadline = (float)num * num2;
}
}
}
public static void SyncRolloverToClients(int rollover)
{
try
{
if (_syncRolloverMessage == null)
{
Plugin.Log.LogWarning((object)"Rollover message not initialized");
}
else
{
_syncRolloverMessage.SendClients(rollover);
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not sync rollover: " + ex.Message));
}
}
private static void OnRolloverReceived(int rollover)
{
try
{
TimeOfDay instance = TimeOfDay.Instance;
if ((Object)(object)instance != (Object)null)
{
instance.quotaFulfilled = rollover;
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not apply synced rollover on client: " + ex.Message));
}
}
public static void SyncBuyingRateToClients(float rate, bool isJackpot)
{
try
{
if (_syncBuyingRateMessage == null)
{
Plugin.Log.LogWarning((object)"Buying rate message not initialized");
}
else
{
_syncBuyingRateMessage.SendClients(new SyncBuyingRateData(rate, isJackpot));
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not sync buying rate: " + ex.Message));
}
}
private static void OnBuyingRateReceived(SyncBuyingRateData data)
{
try
{
BuyingRatePatch.ApplyReceivedBuyingRate(data.Rate, data.IsJackpot);
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not apply synced buying rate on client: " + ex.Message));
}
}
}
[Serializable]
public struct SyncQuotaData
{
public int Quota;
public int PenaltyDelta;
public SyncQuotaData(int quota, int penaltyDelta)
{
Quota = quota;
PenaltyDelta = penaltyDelta;
}
}
[Serializable]
public struct SyncBuyingRateData
{
public float Rate;
public bool IsJackpot;
public SyncBuyingRateData(float rate, bool isJackpot)
{
Rate = rate;
IsJackpot = isJackpot;
}
}
[Serializable]
public struct SyncValueLossData
{
public ulong NetworkObjectId;
public int NewValue;
public SyncValueLossData(ulong networkObjectId, int newValue)
{
NetworkObjectId = networkObjectId;
NewValue = newValue;
}
}
[Serializable]
public struct SyncScrapLossSummary
{
public int BeforeValue;
public int AfterValue;
public SyncScrapLossSummary(int beforeValue, int afterValue)
{
BeforeValue = beforeValue;
AfterValue = afterValue;
}
}
internal static class PenaltyHelpers
{
public static bool IsServerSafe
{
get
{
if ((Object)(object)NetworkManager.Singleton != (Object)null)
{
return NetworkManager.Singleton.IsServer;
}
return false;
}
}
public static (int dead, int total, int recovered) CountDeathsAndRecovered()
{
//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
StartOfRound instance = StartOfRound.Instance;
if ((Object)(object)instance == (Object)null)
{
return (0, 0, 0);
}
int num = 0;
int num2 = 0;
int num3 = 0;
PlayerControllerB[] allPlayerScripts = instance.allPlayerScripts;
foreach (PlayerControllerB val in allPlayerScripts)
{
if ((Object)(object)val == (Object)null)
{
continue;
}
bool isPlayerControlled = val.isPlayerControlled;
bool isPlayerDead = val.isPlayerDead;
if (isPlayerControlled || isPlayerDead)
{
num2++;
}
if (!isPlayerDead)
{
continue;
}
num++;
GrabbableObject obj = val.deadBody?.grabBodyObject;
RagdollGrabbableObject val2 = (RagdollGrabbableObject)(object)((obj is RagdollGrabbableObject) ? obj : null);
if ((Object)(object)val2 == (Object)null)
{
RagdollGrabbableObject[] array = Object.FindObjectsOfType<RagdollGrabbableObject>();
foreach (RagdollGrabbableObject val3 in array)
{
if ((Object)(object)((val3 == null) ? null : ((Component)val3).GetComponent<DeadBodyInfo>()?.playerScript) == (Object)(object)val)
{
val2 = val3;
break;
}
}
}
bool flag = false;
if ((Object)(object)val2 != (Object)null)
{
bool isInShipRoom = ((GrabbableObject)val2).isInShipRoom;
bool flag2 = IsPositionInsideShip(((Component)val2).transform.position);
flag = isInShipRoom || flag2;
}
if (flag)
{
num3++;
}
}
return (num, Math.Max(num2, 1), Mathf.Clamp(num3, 0, num));
}
public static bool IsPositionInsideShip(Vector3 pos)
{
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
try
{
Collider val = StartOfRound.Instance?.shipBounds;
int result;
if ((Object)(object)val != (Object)null)
{
Bounds bounds = val.bounds;
result = (((Bounds)(ref bounds)).Contains(pos) ? 1 : 0);
}
else
{
result = 0;
}
return (byte)result != 0;
}
catch
{
return false;
}
}
public static bool IsOnGordion()
{
try
{
SelectableLevel val = StartOfRound.Instance?.currentLevel;
if ((Object)(object)val == (Object)null)
{
return false;
}
return val.sceneName == "CompanyBuilding";
}
catch
{
return false;
}
}
public static float ComputePenaltyPercent(bool dynamicMode, float percentPerPlayer, float cap, float threshold, float recoveryBonus, int dead, int total, int recovered)
{
if (dead <= 0 || total <= 0)
{
return 0f;
}
float num = ((cap >= 0f) ? Mathf.Clamp01(cap) : 1f);
float num2 = (dynamicMode ? ((float)dead / (float)total * num) : ((float)dead * Mathf.Max(0f, percentPerPlayer)));
if (recovered > 0 && dead > 0)
{
float num3 = Mathf.Clamp01((float)recovered / (float)dead);
num2 *= Mathf.Clamp01(1f - Mathf.Clamp01(recoveryBonus) * num3);
}
if (!dynamicMode && cap >= 0f)
{
num2 = Mathf.Min(num2, num);
}
if (!(num2 < threshold))
{
return Mathf.Clamp01(num2);
}
return 0f;
}
}
[HarmonyPatch(typeof(RoundManager))]
internal static class PenaltiesOnLandingPatch
{
[CompilerGenerated]
private sealed class <FinalizeCreditPenaltyAfterDelay>d__20 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public int desiredFinal;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <FinalizeCreditPenaltyAfterDelay>d__20(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
//IL_001d: Unknown result type (might be due to invalid IL or missing references)
//IL_0027: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>2__current = (object)new WaitForSeconds(1.5f);
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
try
{
StartOfRound instance = StartOfRound.Instance;
if ((Object)(object)instance == (Object)null)
{
return false;
}
int currentCredits = GetCurrentCredits();
SetCredits(desiredFinal);
Plugin.Log.LogInfo((object)$"Credits penalty applied: {currentCredits} -> {desiredFinal} (-{currentCredits - desiredFinal}).");
}
finally
{
_creditScheduled = false;
}
return false;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
internal static bool _appliedThisRound;
private static bool _creditScheduled;
internal static bool _lossesAppliedThisRound;
internal static int CachedDead;
internal static int CachedTotal;
internal static int CachedRecovered;
internal static bool HasPenaltyCache;
internal static int CachedQuotaPenaltyDelta;
internal static int CachedShipScrapBeforeLoss;
internal static int CachedShipScrapAfterLoss;
internal static bool HasScrapLossSummary;
internal static bool HasAllDeadSnapshot;
internal static void CachePenaltyCounts(int dead, int total, int recovered)
{
CachedDead = dead;
CachedTotal = total;
CachedRecovered = recovered;
HasPenaltyCache = true;
}
internal static void CacheScrapLossSummary(int beforeValue, int afterValue)
{
int num = Mathf.Max(0, beforeValue);
int cachedShipScrapAfterLoss = Mathf.Clamp(afterValue, 0, num);
CachedShipScrapBeforeLoss = num;
CachedShipScrapAfterLoss = cachedShipScrapAfterLoss;
HasScrapLossSummary = num > 0;
}
internal static void ClearScrapLossSummary()
{
CachedShipScrapBeforeLoss = 0;
CachedShipScrapAfterLoss = 0;
HasScrapLossSummary = false;
}
internal static bool TryGetScrapLossSummary(out int beforeValue, out int afterValue, out float lostPercent)
{
beforeValue = CachedShipScrapBeforeLoss;
afterValue = CachedShipScrapAfterLoss;
lostPercent = 0f;
if (!HasScrapLossSummary || beforeValue <= 0)
{
return false;
}
lostPercent = Mathf.Clamp01((float)(beforeValue - afterValue) / (float)beforeValue);
return true;
}
[HarmonyPatch("DespawnPropsAtEndOfRound")]
[HarmonyPrefix]
private static bool DespawnPrefix(bool despawnAllItems)
{
try
{
if (!PenaltyHelpers.IsServerSafe)
{
return true;
}
HasPenaltyCache = false;
HasAllDeadSnapshot = false;
CachedQuotaPenaltyDelta = 0;
ClearScrapLossSummary();
bool flag = PenaltyHelpers.IsOnGordion();
var (num, num2, recovered) = PenaltyHelpers.CountDeathsAndRecovered();
if (!despawnAllItems && !flag && num >= num2 && !_lossesAppliedThisRound)
{
DespawnFacilityItems();
ApplyLossesWhenAllDead();
_lossesAppliedThisRound = true;
HasAllDeadSnapshot = true;
CachePenaltyCounts(num, num2, recovered);
HudQuotaAnimationPatch.TryApplyAdvancedFeaturesEndscreen();
if (ConfigManager.CreditPenaltiesEnabled.Value)
{
ScheduleCreditPenalty(num, num2, recovered);
}
if (ConfigManager.QuotaPenaltiesEnabled.Value)
{
ApplyQuotaPenalty(num, num2, recovered);
}
_appliedThisRound = true;
return false;
}
return true;
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Error in despawn prefix: " + ex.Message));
return true;
}
}
[HarmonyPatch("DespawnPropsAtEndOfRound")]
[HarmonyPostfix]
private static void DespawnPostfix(bool despawnAllItems)
{
try
{
if (despawnAllItems || !PenaltyHelpers.IsServerSafe || _appliedThisRound)
{
return;
}
var (num, total, recovered) = PenaltyHelpers.CountDeathsAndRecovered();
if (num > 0)
{
CachePenaltyCounts(num, total, recovered);
bool flag = PenaltyHelpers.IsOnGordion();
if (ConfigManager.CreditPenaltiesEnabled.Value && (!flag || ConfigManager.CreditPenaltiesOnGordion.Value))
{
ScheduleCreditPenalty(num, total, recovered);
}
if (ConfigManager.QuotaPenaltiesEnabled.Value && (!flag || ConfigManager.QuotaPenaltiesOnGordion.Value))
{
ApplyQuotaPenalty(num, total, recovered);
}
_appliedThisRound = true;
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Error in despawn postfix: " + ex.Message));
}
}
private static void ScheduleCreditPenalty(int dead, int total, int recovered)
{
try
{
if (_creditScheduled)
{
return;
}
StartOfRound instance = StartOfRound.Instance;
if (!((Object)(object)instance == (Object)null))
{
int currentCredits = GetCurrentCredits();
float num = PenaltyHelpers.ComputePenaltyPercent(ConfigManager.CreditPenaltiesDynamic.Value, ConfigManager.CreditPenaltyPercentPerPlayer.Value, ConfigManager.CreditPenaltyPercentCap.Value, ConfigManager.CreditPenaltyPercentThreshold.Value, ConfigManager.CreditPenaltyRecoveryBonus.Value, dead, total, recovered);
if (!(num <= 0f))
{
int desiredFinal = Mathf.Max(0, currentCredits - Mathf.RoundToInt((float)currentCredits * num));
_creditScheduled = true;
((MonoBehaviour)instance).StartCoroutine(FinalizeCreditPenaltyAfterDelay(desiredFinal));
}
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not schedule credit penalty: " + ex.Message));
}
}
private static int GetCurrentCredits()
{
try
{
Terminal val = Object.FindObjectOfType<Terminal>();
if ((Object)(object)val != (Object)null)
{
return val.groupCredits;
}
}
catch (Exception ex)
{
Plugin.Log.LogDebug((object)("Could not read current credits: " + ex.Message));
}
return 0;
}
[IteratorStateMachine(typeof(<FinalizeCreditPenaltyAfterDelay>d__20))]
private static IEnumerator FinalizeCreditPenaltyAfterDelay(int desiredFinal)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <FinalizeCreditPenaltyAfterDelay>d__20(0)
{
desiredFinal = desiredFinal
};
}
private static void SetCredits(int value)
{
try
{
Terminal val = Object.FindObjectOfType<Terminal>();
if ((Object)(object)val != (Object)null)
{
val.SyncGroupCreditsServerRpc(value, val.numberOfItemsInDropship);
}
}
catch (Exception ex)
{
Plugin.Log.LogDebug((object)("Could not sync credits to terminal: " + ex.Message));
}
}
private static void ApplyQuotaPenalty(int dead, int total, int recovered)
{
float num = PenaltyHelpers.ComputePenaltyPercent(ConfigManager.QuotaPenaltiesDynamic.Value, ConfigManager.QuotaPenaltyPercentPerPlayer.Value, ConfigManager.QuotaPenaltyPercentCap.Value, ConfigManager.QuotaPenaltyPercentThreshold.Value, ConfigManager.QuotaPenaltyRecoveryBonus.Value, dead, total, recovered);
if (!(num <= 0f))
{
TimeOfDay instance = TimeOfDay.Instance;
if ((Object)(object)instance != (Object)null)
{
int profitQuota = instance.profitQuota;
int num2 = Mathf.RoundToInt((float)Math.Max(1, profitQuota) * num);
int num3 = Mathf.Max(1, profitQuota + num2);
CachedQuotaPenaltyDelta = num2;
instance.profitQuota = num3;
NetworkSync.SyncQuotaToClients(num3, num2);
Plugin.Log.LogInfo((object)$"Quota penalty applied: {profitQuota} -> {num3} (+{num2}, {num:P0}, {dead}/{total} dead).");
}
}
}
private static void DespawnFacilityItems()
{
try
{
GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>();
if (array == null || array.Length == 0)
{
return;
}
GrabbableObject[] array2 = array;
foreach (GrabbableObject g in array2)
{
try
{
if (!IsShipItem(g))
{
DespawnObject(g);
}
}
catch (Exception ex)
{
Plugin.Log.LogDebug((object)("Skipped despawn check for facility item: " + ex.Message));
}
}
}
catch (Exception ex2)
{
Plugin.Log.LogWarning((object)("Error despawning facility items: " + ex2.Message));
}
}
private static void ApplyLossesWhenAllDead()
{
try
{
GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>();
if (array != null && array.Length != 0)
{
GrabbableObject[] source = array.Where(IsShipItem).ToArray();
GrabbableObject[] array2 = source.Where((GrabbableObject g) => g.itemProperties.isScrap).ToArray();
GrabbableObject[] array3 = source.Where((GrabbableObject g) => !g.itemProperties.isScrap && !IsBodyOrBlacklisted(g)).ToArray();
int num = SumScrapValue(array2);
if (ConfigManager.ValueLossEnabled.Value && array2.Length != 0)
{
ApplyValueLoss(array2);
}
if (ConfigManager.ScrapLossEnabled.Value && array2.Length != 0)
{
SelectAndRemoveScrap(array2);
}
if (ConfigManager.EquipmentLossEnabled.Value && array3.Length != 0)
{
SelectAndRemoveEquipment(array3);
}
int num2 = SumCurrentShipScrapValue(array2);
CacheScrapLossSummary(num, num2);
NetworkSync.SyncScrapLossSummaryToClients(num, num2);
if (num > 0)
{
int num3 = Mathf.Max(0, num - num2);
float num4 = Mathf.Clamp01((float)num3 / (float)num);
Plugin.Log.LogInfo((object)$"Scrap lost: {Mathf.RoundToInt(num4 * 100f)}% (${num3}/${num}).");
}
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not apply ship loss rules: " + ex.Message));
}
}
private static int SumScrapValue(IEnumerable<GrabbableObject> items)
{
int num = 0;
foreach (GrabbableObject item in items)
{
try
{
if (item != null && (item.itemProperties?.isScrap).GetValueOrDefault())
{
num += Mathf.Max(0, item.scrapValue);
}
}
catch (Exception ex)
{
Plugin.Log.LogDebug((object)("Skipped scrap value sum for item: " + ex.Message));
}
}
return num;
}
private static int SumCurrentShipScrapValue(GrabbableObject[] shipScrap)
{
int num = 0;
foreach (GrabbableObject val in shipScrap)
{
try
{
if (val != null && (val.itemProperties?.isScrap).GetValueOrDefault() && IsShipItem(val))
{
num += Mathf.Max(0, val.scrapValue);
}
}
catch (Exception ex)
{
Plugin.Log.LogDebug((object)("Skipped scrap value sum for item: " + ex.Message));
}
}
return num;
}
private static bool IsShipItem(GrabbableObject g)
{
if ((Object)(object)g == (Object)null || (Object)(object)g.itemProperties == (Object)null || !g.isInShipRoom)
{
return false;
}
try
{
NetworkObject component = ((Component)g).GetComponent<NetworkObject>();
return (Object)(object)component != (Object)null && component.IsSpawned;
}
catch
{
return false;
}
}
private static bool IsBodyOrBlacklisted(GrabbableObject g)
{
if ((Object)(object)g == (Object)null)
{
return true;
}
if (g is RagdollGrabbableObject)
{
return true;
}
if (g is ClipboardItem)
{
return true;
}
try
{
string text = g.itemProperties?.itemName ?? ((Object)g).name;
return text.IndexOf("sticky note", StringComparison.OrdinalIgnoreCase) >= 0;
}
catch
{
return false;
}
}
private static void SelectAndRemoveScrap(GrabbableObject[] scrapItems)
{
int num = Mathf.Max(0, ConfigManager.MaxLostScrapItems.Value);
float num2 = Mathf.Clamp01(ConfigManager.ItemsSafeChance.Value);
float num3 = Mathf.Clamp01(ConfigManager.LoseEachScrapChance.Value);
int num4 = 0;
int num5 = 0;
List<string> list = new List<string>();
foreach (GrabbableObject val in scrapItems)
{
try
{
if ((Object)(object)val == (Object)null)
{
continue;
}
Item itemProperties = val.itemProperties;
if (itemProperties != null && itemProperties.isScrap)
{
num4++;
if ((num <= 0 || num5 < num) && !(Random.value < num2) && Random.value < num3)
{
DespawnObject(val);
num5++;
list.Add(val.itemProperties.itemName);
}
}
}
catch (Exception ex)
{
Plugin.Log.LogDebug((object)("Skipped scrap removal for item: " + ex.Message));
}
}
Plugin.Log.LogInfo((object)string.Format("Scrap items removed: {0}/{1} [{2}].", num5, num4, string.Join(", ", list)));
}
private static void SelectAndRemoveEquipment(GrabbableObject[] equipItems)
{
int num = Mathf.Max(0, ConfigManager.MaxLostEquipmentItems.Value);
float num2 = Mathf.Clamp01(ConfigManager.LoseEachEquipmentChance.Value);
int num3 = 0;
int num4 = 0;
List<string> list = new List<string>();
foreach (GrabbableObject val in equipItems)
{
try
{
if ((Object)(object)val == (Object)null)
{
continue;
}
Item itemProperties = val.itemProperties;
if (itemProperties != null && !itemProperties.isScrap)
{
num3++;
if ((num <= 0 || num4 < num) && Random.value < num2)
{
DespawnObject(val);
num4++;
list.Add(val.itemProperties.itemName);
}
}
}
catch (Exception ex)
{
Plugin.Log.LogDebug((object)("Skipped equipment removal for item: " + ex.Message));
}
}
Plugin.Log.LogInfo((object)string.Format("Equipment items removed: {0}/{1} [{2}].", num4, num3, string.Join(", ", list)));
}
private static void ApplyValueLoss(GrabbableObject[] scrapItems)
{
float num = Mathf.Clamp01(ConfigManager.ValueLossPercent.Value);
if (num <= 0f)
{
return;
}
float num2 = 1f - num;
int num3 = 0;
int num4 = 0;
int num5 = 0;
List<SyncValueLossData> list = new List<SyncValueLossData>();
foreach (GrabbableObject val in scrapItems)
{
try
{
if (val != null && (val.itemProperties?.isScrap).GetValueOrDefault() && val.scrapValue > 0)
{
int scrapValue = val.scrapValue;
int num6 = (val.scrapValue = Mathf.Max(0, Mathf.RoundToInt((float)val.scrapValue * num2)));
try
{
val.SetScrapValue(num6);
}
catch (Exception ex)
{
Plugin.Log.LogDebug((object)("SetScrapValue failed: " + ex.Message));
}
NetworkObject component = ((Component)val).GetComponent<NetworkObject>();
if ((Object)(object)component != (Object)null)
{
list.Add(new SyncValueLossData(component.NetworkObjectId, num6));
}
num4 += scrapValue;
num5 += num6;
num3++;
}
}
catch (Exception ex2)
{
Plugin.Log.LogWarning((object)("Could not reduce a scrap item's value: " + ex2.Message));
}
}
if (list.Count > 0)
{
NetworkSync.SyncValueLossToClients(list.ToArray());
}
Plugin.Log.LogInfo((object)$"Scrap value reduced on {num3} items by {num:P0} (${num4} -> ${num5}).");
}
private static void DespawnObject(GrabbableObject g)
{
try
{
NetworkObject component = ((Component)g).GetComponent<NetworkObject>();
if ((Object)(object)component != (Object)null && component.IsSpawned)
{
component.Despawn(true);
}
else
{
Object.Destroy((Object)(object)((Component)g).gameObject);
}
}
catch (Exception ex)
{
Plugin.Log.LogDebug((object)("Could not despawn object: " + ex.Message));
}
}
}
[HarmonyPatch(typeof(StartOfRound))]
internal static class ResetPenaltyFlags
{
[HarmonyPatch("StartGame")]
[HarmonyPostfix]
private static void ResetFlagsOnNewGame()
{
PenaltiesOnLandingPatch._appliedThisRound = false;
PenaltiesOnLandingPatch._lossesAppliedThisRound = false;
PenaltiesOnLandingPatch.HasPenaltyCache = false;
PenaltiesOnLandingPatch.HasAllDeadSnapshot = false;
PenaltiesOnLandingPatch.CachedQuotaPenaltyDelta = 0;
PenaltiesOnLandingPatch.ClearScrapLossSummary();
}
}
[HarmonyPatch(typeof(Terminal))]
internal static class StartOfRoundPatches
{
[HarmonyPatch("Awake")]
[HarmonyPostfix]
private static void ApplyStartingCredits(Terminal __instance)
{
try
{
int value = ConfigManager.StartingCredits.Value;
if (value >= 0)
{
__instance.groupCredits = value;
Plugin.Log.LogInfo((object)$"Starting credits set to {value}.");
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not apply starting credits: " + ex.Message));
}
}
}
[HarmonyPatch(typeof(TimeOfDay))]
internal static class TimeOfDayQuotaPatch
{
private readonly struct DeadlineSelection
{
internal int Days { get; }
internal bool IsRandomized { get; }
internal bool FromConstellation { get; }
internal ConstellationDeadlineMode ConstellationMode { get; }
internal string ConstellationName { get; }
internal string Source { get; }
internal DeadlineSelection(int days, bool isRandomized, bool fromConstellation, ConstellationDeadlineMode constellationMode, string constellationName, string source)
{
Days = days;
IsRandomized = isRandomized;
FromConstellation = fromConstellation;
ConstellationMode = constellationMode;
ConstellationName = constellationName;
Source = source;
}
}
[CompilerGenerated]
private sealed class <GIMonitorRefreshRoutine>d__10 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <GIMonitorRefreshRoutine>d__10(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>2__current = null;
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
RefreshExternalMonitors();
return false;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
private static bool _initialDeadlineApplied;
private static bool _initialDeadlineWasConstellationSpecific;
[HarmonyPatch("SetNewProfitQuota")]
[HarmonyPrefix]
[HarmonyPriority(200)]
[HarmonyAfter(new string[] { "com.xmods.lethalmoonunlocks" })]
private static bool SetNewProfitQuota_Prefix(TimeOfDay __instance, ref int ___timesFulfilledQuota, ref int ___profitQuota, ref float ___timeUntilDeadline, ref int ___quotaFulfilled, ref int ___daysUntilDeadline, ref float ___totalTime)
{
try
{
if (!((NetworkBehaviour)__instance).IsServer)
{
return false;
}
if (ConfigManager.DisableQuota.Value)
{
___profitQuota = Mathf.Max(0, ConfigManager.StartingQuota.Value);
SetDeadlineTimer(___totalTime, ref ___daysUntilDeadline, ref ___timeUntilDeadline);
return false;
}
int num = ___profitQuota;
___timesFulfilledQuota++;
int num2 = CalculateNewQuota(num, ___timesFulfilledQuota);
int num3 = ___daysUntilDeadline;
int num4 = ___quotaFulfilled - num;
int num5 = num4 / 5 + 15 * num3;
___profitQuota = num2;
int num6 = CalculateRollover(num4);
int num7 = SetDeadlineTimer(___totalTime, ref ___daysUntilDeadline, ref ___timeUntilDeadline, num3, logSelection: true);
__instance.quotaVariables.deadlineDaysAmount = num7;
__instance.SyncNewProfitQuotaClientRpc(___profitQuota, num5, ___timesFulfilledQuota);
___quotaFulfilled = num6;
___daysUntilDeadline = num7;
___timeUntilDeadline = ___totalTime * (float)num7;
NetworkSync.SyncDeadlineToClients(num7);
if (num6 > 0)
{
NetworkSync.SyncRolloverToClients(num6);
}
Plugin.Log.LogInfo((object)$"Quota {___timesFulfilledQuota}: {num} -> {num2}, deadline {num7} days, rollover {num6}, overtime {num5} credits.");
return false;
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not calculate the next quota: " + ex.Message));
return true;
}
}
private static int CalculateNewQuota(int previousQuota, int timesFulfilled)
{
int value = ConfigManager.FinalLevel.Value;
int num;
if (value != -1 && previousQuota >= value)
{
num = previousQuota + Math.Max(0, ConfigManager.FinalIncrease.Value);
}
else
{
float num2 = Mathf.Max(0.1f, ConfigManager.CurveSharpness.Value);
float num3 = timesFulfilled;
float num4 = Mathf.Clamp(1f + num3 * (num3 / num2), 0f, 10000f);
float num5 = 1f;
float value2 = ConfigManager.RandomizerMultiplier.Value;
if (value2 > 0f)
{
num5 = 1f + Random.Range(-0.5f, 0.5f) * value2;
}
float num6 = (float)ConfigManager.BaseIncrease.Value * num4 * num5;
if (ConfigManager.EnablePlayerMultiplier.Value)
{
num6 *= CalculatePlayerMultiplier();
}
if (ConfigManager.EnableGrowthDampening.Value)
{
int value3 = ConfigManager.DampeningStartAt.Value;
if (timesFulfilled > value3)
{
float num7 = timesFulfilled - value3;
float num8 = Mathf.Max(0.1f, ConfigManager.DampeningSharpness.Value);
num6 /= 1f + Mathf.Pow(num7 / num8, 2f);
}
}
num = Mathf.RoundToInt(Mathf.Clamp((float)previousQuota + num6, 0f, 1E+09f));
}
int value4 = ConfigManager.QuotaCap.Value;
if (value4 == -1)
{
return num;
}
return Mathf.Min(num, value4);
}
private static float CalculatePlayerMultiplier()
{
NetworkManager singleton = NetworkManager.Singleton;
if ((Object)(object)singleton == (Object)null || !singleton.IsServer)
{
return 1f;
}
int num = Mathf.Max(1, singleton.ConnectedClientsList?.Count ?? 1);
int value = ConfigManager.PlayerThreshold.Value;
int num2 = num - value;
if (num2 <= 0)
{
return 1f;
}
int value2 = ConfigManager.PlayerCap.Value;
int num3 = Mathf.Max(0, value2 - value);
num2 = Mathf.Clamp(num2, 0, num3);
return 1f + (float)num2 * Mathf.Max(0f, ConfigManager.MultPerPlayer.Value);
}
private static int CalculateRollover(int overage)
{
float value = ConfigManager.RolloverAmount.Value;
if (value <= 0f || overage <= 0)
{
return 0;
}
return Mathf.RoundToInt((float)overage * Mathf.Clamp01(value));
}
private static int SetDeadlineTimer(float dayDuration, ref int days, ref float timeUntilDeadline, int prevDays = -1, bool logSelection = false)
{
DeadlineSelection deadlineSelection = ResolveDeadlineSelection(prevDays);
int num = (days = deadlineSelection.Days);
timeUntilDeadline = (float)num * dayDuration;
if (logSelection)
{
Plugin.Log.LogInfo((object)$"Deadline selected ({deadlineSelection.Source}): {num} day(s).");
}
return num;
}
[HarmonyPatch("Awake")]
[HarmonyPostfix]
private static void TimeOfDay_Awake_Postfix(TimeOfDay __instance)
{
ApplyQuotaVariables(__instance);
}
[HarmonyPatch("Start")]
[HarmonyPostfix]
private static void TimeOfDay_Start_Postfix(TimeOfDay __instance)
{
ConstellationDeadlineConfig.RefreshSections();
if (__instance.timesFulfilledQuota == 0)
{
TryApplyInitialDeadlineFromCurrentMode(__instance, allowConstellationOverride: true, logSelection: true);
if (((NetworkBehaviour)__instance).IsServer)
{
NetworkSync.SyncDeadlineToClients(__instance.daysUntilDeadline);
Plugin.Log.LogInfo((object)$"Initial deadline synced: {__instance.daysUntilDeadline} days.");
}
string text = DescribeCurrentDeadlineMode(__instance.daysUntilDeadline);
string text2 = ((ConfigManager.QuotaCap.Value != -1) ? $", cap={ConfigManager.QuotaCap.Value}" : "");
string text3 = ((ConfigManager.FinalLevel.Value != -1) ? $", finalLevel={ConfigManager.FinalLevel.Value} (+{ConfigManager.FinalIncrease.Value} flat)" : "");
string text4 = (ConfigManager.CreditPenaltiesEnabled.Value ? $"credits={ConfigManager.CreditPenaltyPercentPerPlayer.Value:P0}/player (cap {ConfigManager.CreditPenaltyPercentCap.Value:P0})" : "credits=off");
string text5 = (ConfigManager.QuotaPenaltiesEnabled.Value ? $"quota={ConfigManager.QuotaPenaltyPercentPerPlayer.Value:P0}/player (cap {ConfigManager.QuotaPenaltyPercentCap.Value:P0})" : "quota=off");
string text6 = $"scrap={ConfigManager.ScrapLossEnabled.Value}" + $", value={ConfigManager.ValueLossEnabled.Value}({ConfigManager.ValueLossPercent.Value:P0})" + $", equip={ConfigManager.EquipmentLossEnabled.Value}";
Plugin.Log.LogInfo((object)$"Settings loaded: quota start {ConfigManager.StartingQuota.Value}, base +{ConfigManager.BaseIncrease.Value}/cycle, sharpness {ConfigManager.CurveSharpness.Value}{text2}{text3}; deadline {text}; credits start {ConfigManager.StartingCredits.Value}; penalties [{text4}, {text5}]; losses [{text6}].");
}
if (Chainloader.PluginInfos.ContainsKey("ShaosilGaming.GeneralImprovements"))
{
((MonoBehaviour)__instance).StartCoroutine(GIMonitorRefreshRoutine());
}
}
[IteratorStateMachine(typeof(<GIMonitorRefreshRoutine>d__10))]
private static IEnumerator GIMonitorRefreshRoutine()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <GIMonitorRefreshRoutine>d__10(0);
}
internal static void RefreshExternalMonitors()
{
try
{
Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "GeneralImprovements");
if (!(assembly == null))
{
Type type = assembly.GetType("GeneralImprovements.Utilities.MonitorsHelper");
type?.GetMethod("UpdateTotalDaysMonitors")?.Invoke(null, null);
type?.GetMethod("UpdateTotalQuotasMonitors")?.Invoke(null, null);
}
}
catch
{
}
}
private static void ApplyQuotaVariables(TimeOfDay instance)
{
try
{
if (instance.quotaVariables != null)
{
instance.quotaVariables.startingQuota = ConfigManager.StartingQuota.Value;
instance.quotaVariables.startingCredits = ConfigManager.StartingCredits.Value;
}
ResetInitialDeadlineTracking();
TryApplyInitialDeadlineFromCurrentMode(instance, allowConstellationOverride: false, logSelection: true);
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("Could not apply quota settings at startup: " + ex.Message));
}
}
internal static bool TryApplyInitialDeadlineFromCurrentMode(TimeOfDay instance, bool allowConstellationOverride, bool logSelection)
{
if ((Object)(object)instance == (Object)null || instance.timesFulfilledQuota != 0)
{
return false;
}
DeadlineSelection deadlineSelection = ResolveDeadlineSelection(-1);
bool flag = !_initialDeadlineApplied;
if (!flag && allowConstellationOverride)
{
bool flag2 = deadlineSelection.FromConstellation && deadlineSelection.ConstellationMode != ConstellationDeadlineMode.UseGlobal;
flag = !_initialDeadlineWasConstellationSpecific && flag2;
}
if (!flag)
{
return false;
}
int days = deadlineSelection.Days;
if (instance.quotaVariables != null)
{
instance.quotaVariables.deadlineDaysAmount = days;
}
instance.daysUntilDeadline = days;
if (instance.totalTime > 0f)
{
instance.timeUntilDeadline = instance.totalTime * (float)days;
}
_initialDeadlineApplied = true;
_initialDeadlineWasConstellationSpecific = deadlineSelection.FromConstellation && deadlineSelection.ConstellationMode != ConstellationDeadlineMode.UseGlobal;
if (logSelection)
{
Plugin.Log.LogInfo((object)$"Initial deadline selected ({deadlineSelection.Source}): {days} day(s).");
}
return true;
}
private static DeadlineSelection ResolveDeadlineSelection(int prevDays)
{
if (ConstellationDeadlineConfig.TryGetCurrentConstellationSettings(out string constellationName, out ConstellationDeadlineMode mode, out int fixedDays, out int min, out int max))
{
switch (mode)
{
case ConstellationDeadlineMode.Fixed:
{
int days2 = Math.Max(1, fixedDays);
return new DeadlineSelection(days2, isRandomized: false, fromConstellation: true, mode, constellationName, "constellation '" + constellationName + "' fixed");
}
case ConstellationDeadlineMode.Random:
{
int min2;
int max2;
int days = RollRandomDeadline(min, max, prevDays, out min2, out max2);
return new DeadlineSelection(days, isRandomized: true, fromConstellation: true, mode, constellationName, $"constellation '{constellationName}' random {min2}-{max2}");
}
default:
{
DeadlineSelection deadlineSelection = ResolveGlobalDeadlineSelection(prevDays);
return new DeadlineSelection(deadlineSelection.Days, deadlineSelection.IsRandomized, fromConstellation: true, mode, constellationName, "constellation '" + constellationName + "' use-global (" + deadlineSelection.Source + ")");
}
}
}
return ResolveGlobalDeadlineSelection(prevDays);
}
private static DeadlineSelection ResolveGlobalDeadlineSelection(int prevDays)
{
if (ConfigManager.RandomizeDeadline.Value)
{
int min;
int max;
int days = RollRandomDeadline(ConfigManager.DeadlineMin.Value, ConfigManager.DeadlineMax.Value, prevDays, out min, out max);
return new DeadlineSelection(days, isRandomized: true, fromConstellation: false, ConstellationDeadlineMode.UseGlobal, string.Empty, $"global random {min}-{max}");
}
int days2 = Math.Max(1, ConfigManager.DaysToDeadline.Value);
return new DeadlineSelection(days2, isRandomized: false, fromConstellation: false, ConstellationDeadlineMode.UseGlobal, string.Empty, "global fixed");
}
private static int RollRandomDeadline(int rawMin, int rawMax, int prevDays, out int min, out int max)
{
min = Math.Max(1, rawMin);
max = Math.Max(min, rawMax);
int num = Random.Range(min, max + 1);
if (ConfigManager.DeadlineMustChange.Value && min != max)
{
for (int i = 0; i < 8; i++)
{
if (num != prevDays)
{
break;
}
num = Random.Range(min, max + 1);
}
}
return num;
}
private static string DescribeCurrentDeadlineMode(int firstDeadline)
{
if (ConstellationDeadlineConfig.TryGetCurrentConstellationSettings(out string constellationName, out ConstellationDeadlineMode mode, out int fixedDays, out int min, out int max))
{
return mode switch
{
ConstellationDeadlineMode.Fixed => $"constellation '{constellationName}' fixed {Math.Max(1, fixedDays)}d",
ConstellationDeadlineMode.Random => $"constellation '{constellationName}' randomized {Math.Max(1, min)}-{Math.Max(Math.Max(1, min), max)}d (first: {firstDeadline}d)",
_ => "constellation '" + constellationName + "' use-global (" + DescribeGlobalDeadlineMode(firstDeadline) + ")",
};
}
return DescribeGlobalDeadlineMode(firstDeadline);
}
private static string DescribeGlobalDeadlineMode(int firstDeadline)
{
int num = Math.Max(1, ConfigManager.DeadlineMin.Value);
int num2 = Math.Max(num, ConfigManager.DeadlineMax.Value);
if (!ConfigManager.RandomizeDeadline.Value)
{
return $"fixed {Math.Max(1, ConfigManager.DaysToDeadline.Value)}d";
}
return $"randomized {num}-{num2}d (first: {firstDeadline}d)";
}
private static void ResetInitialDeadlineTracking()
{
_initialDeadlineApplied = false;
_initialDeadlineWasConstellationSpecific = false;
}
[HarmonyPatch("UpdateProfitQuotaCurrentTime")]
[HarmonyPostfix]
private static void UpdateProfitQuotaCurrentTime_Postfix(ref float ___timeUntilDeadline, ref float ___totalTime, ref int ___daysUntilDeadline)
{
if (ConfigManager.DisableQuota.Value)
{
ApplyDisableQuotaState(ref ___daysUntilDeadline, ref ___totalTime, ref ___timeUntilDeadline);
}
}
[HarmonyPatch("SetBuyingRateForDay")]
[HarmonyPostfix]
private static void SetBuyingRateForDay_Postfix(ref float ___timeUntilDeadline, ref float ___totalTime, ref int ___daysUntilDeadline)
{
if (ConfigManager.DisableQuota.Value)
{
ApplyDisableQuotaState(ref ___daysUntilDeadline, ref ___totalTime, ref ___timeUntilDeadline);
}
}
private static void ApplyDisableQuotaState(ref int days, ref float totalTime, ref float timeUntilDeadline)
{
try
{
SetDeadlineTimer(totalTime, ref days, ref timeUntilDeadline);
StartOfRound instance = StartOfRound.Instance;
if ((Object)(object)instance != (Object)null)
{
instance.companyBuyingRate = 1f;
if ((Object)(object)instance.deadlineMonitorText != (Object)null)
{
((TMP_Text)instance.deadlineMonitorText).text = "DEADLINE:\nNEVER";
}
if ((Object)(object)instance.profitQuotaMonitorText != (Object)null)
{
((TMP_Text)instance.profitQuotaMonitorText).text = "QUOTA:\nDISABLED";
}
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not update DisableQuota monitor state: " + ex.Message));
}
}
}
}
namespace ConfigurableQuota.Compat
{
internal enum ConstellationDeadlineMode
{
UseGlobal,
Fixed,
Random
}
internal static class ConstellationDeadlineConfig
{
private sealed class ConstellationEntries
{
internal ConfigEntry<string> DeadlineMode;
internal ConfigEntry<int> FixedDays;
internal ConfigEntry<int> DeadlineMin;
internal ConfigEntry<int> DeadlineMax;
}
private const string FileName = "ConfigurableQuota_Constellations.cfg";
private const string SectionPrefix = "Constellation: ";
private static readonly Dictionary<string, ConstellationEntries> Entries = new Dictionary<string, ConstellationEntries>(StringComparer.OrdinalIgnoreCase);
private static ConfigFile? _constellationConfig;
private static bool _initialized;
private static bool _loggedConfigFailure;
internal static void Initialize()
{
if (!_initialized)
{
_initialized = true;
if (LethalConstellationsCompat.IsInstalled)
{
EnsureConfigReady();
RefreshSections();
}
}
}
internal static void RefreshSections()
{
if (!LethalConstellationsCompat.IsInstalled || !EnsureConfigReady())
{
return;
}
try
{
List<string> knownConstellationNames = LethalConstellationsCompat.GetKnownConstellationNames();
if (knownConstellationNames.Count == 0)
{
return;
}
bool flag = false;
foreach (string item in knownConstellationNames)
{
if (item.Length != 0 && EnsureConstellationEntry(item))
{
flag = true;
}
}
if (flag)
{
_constellationConfig.Save();
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not refresh constellation deadline sections: " + ex.Message));
}
}
internal static bool TryGetCurrentConstellationSettings(out string constellationName, out ConstellationDeadlineMode mode, out int fixedDays, out int min, out int max)
{
constellationName = string.Empty;
mode = ConstellationDeadlineMode.UseGlobal;
fixedDays = Math.Max(1, ConfigManager.DaysToDeadline.Value);
min = Math.Max(1, ConfigManager.DeadlineMin.Value);
max = Math.Max(min, ConfigManager.DeadlineMax.Value);
if (!LethalConstellationsCompat.IsInstalled)
{
return false;
}
if (!LethalConstellationsCompat.TryGetCurrentConstellationName(out constellationName))
{
return false;
}
if (!EnsureConfigReady())
{
return false;
}
if (!Entries.TryGetValue(constellationName, out ConstellationEntries value))
{
if (!EnsureConstellationEntry(constellationName))
{
return false;
}
_constellationConfig.Save();
value = Entries[constellationName];
}
mode = ParseMode(value.DeadlineMode.Value);
fixedDays = Math.Max(1, value.FixedDays.Value);
min = Math.Max(1, value.DeadlineMin.Value);
max = Math.Max(min, value.DeadlineMax.Value);
return true;
}
private static bool EnsureConfigReady()
{
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Expected O, but got Unknown
if (_constellationConfig != null)
{
return true;
}
try
{
string text = Path.Combine(Paths.ConfigPath, "ConfigurableQuota_Constellations.cfg");
_constellationConfig = new ConfigFile(text, true);
return true;
}
catch (Exception ex)
{
if (!_loggedConfigFailure)
{
_loggedConfigFailure = true;
Plugin.Log.LogWarning((object)("Could not initialize ConfigurableQuota_Constellations.cfg: " + ex.Message));
}
return false;
}
}
private static bool EnsureConstellationEntry(string constellationName)
{
//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
//IL_00d3: Expected O, but got Unknown
if (_constellationConfig == null)
{
return false;
}
if (Entries.ContainsKey(constellationName))
{
return false;
}
string text = "Constellation: " + constellationName;
int num = Math.Max(1, ConfigManager.DaysToDeadline.Value);
int num2 = Math.Max(1, ConfigManager.DeadlineMin.Value);
int num3 = Math.Max(num2, ConfigManager.DeadlineMax.Value);
ConstellationEntries constellationEntries = new ConstellationEntries();
constellationEntries.DeadlineMode = _constellationConfig.Bind<string>(text, "DeadlineMode", ConstellationDeadlineMode.UseGlobal.ToString(), new ConfigDescription("Deadline mode for this constellation. UseGlobal uses main ConfigurableQuota deadline settings, Fixed uses FixedDaysToDeadline, Random uses DeadlineMin/DeadlineMax.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3]
{
ConstellationDeadlineMode.UseGlobal.ToString(),
ConstellationDeadlineMode.Fixed.ToString(),
ConstellationDeadlineMode.Random.ToString()
}), Array.Empty<object>()));
constellationEntries.FixedDays = _constellationConfig.Bind<int>(text, "FixedDaysToDeadline", num, "Used when Deadline Mode is Fixed.");
constellationEntries.DeadlineMin = _constellationConfig.Bind<int>(text, "DeadlineMin", num2, "Used when Deadline Mode is Random.");
constellationEntries.DeadlineMax = _constellationConfig.Bind<int>(text, "DeadlineMax", num3, "Used when Deadline Mode is Random.");
ConstellationEntries value = constellationEntries;
Entries[constellationName] = value;
Plugin.Log.LogInfo((object)("Added constellation deadline settings section for '" + constellationName + "'."));
return true;
}
private static ConstellationDeadlineMode ParseMode(string rawMode)
{
if (Enum.TryParse<ConstellationDeadlineMode>(rawMode, ignoreCase: true, out var result))
{
return result;
}
return ConstellationDeadlineMode.UseGlobal;
}
}
internal static class LethalConstellationsCompat
{
private const string AssemblyName = "LethalConstellations";
private const string CollectionsTypeName = "LethalConstellations.PluginCore.Collections";
private const string CurrentConstellationFieldName = "CurrentConstellation";
private const string ConstellationListFieldName = "ConstellationStuff";
private const string ConstellationNameFieldName = "consName";
private static Type? _collectionsType;
private static FieldInfo? _currentConstellationField;
private static FieldInfo? _constellationStuffField;
private static FieldInfo? _constellationNameField;
private static bool _loggedReflectionFailure;
internal static bool IsInstalled => Chainloader.PluginInfos.ContainsKey("com.github.darmuh.LethalConstellations");
internal static bool TryGetCurrentConstellationName(out string constellationName)
{
constellationName = string.Empty;
if (!IsInstalled || !EnsureReflectionReady())
{
return false;
}
try
{
if (_currentConstellationField?.GetValue(null) is string text)
{
constellationName = text.Trim();
return constellationName.Length > 0;
}
}
catch (Exception ex)
{
Plugin.Log.LogDebug((object)("Could not read current constellation name: " + ex.Message));
}
return false;
}
internal static List<string> GetKnownConstellationNames()
{
HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (!IsInstalled || !EnsureReflectionReady())
{
return new List<string>();
}
try
{
if (!(_constellationStuffField?.GetValue(null) is IEnumerable enumerable))
{
return new List<string>();
}
foreach (object item in enumerable)
{
if (item != null)
{
string constellationName = GetConstellationName(item);
if (constellationName.Length > 0)
{
hashSet.Add(constellationName);
}
}
}
}
catch (Exception ex)
{
Plugin.Log.LogDebug((object)("Could not collect constellation names: " + ex.Message));
}
return hashSet.OrderBy<string, string>((string name) => name, StringComparer.OrdinalIgnoreCase).ToList();
}
private static string GetConstellationName(object entry)
{
try
{
if (_constellationNameField == null || _constellationNameField.DeclaringType != entry.GetType())
{
_constellationNameField = entry.GetType().GetField("consName", BindingFlags.Instance | BindingFlags.Public);
}
if (_constellationNameField?.GetValue(entry) is string text)
{
return text.Trim();
}
}
catch
{
}
return string.Empty;
}
private static bool EnsureReflectionReady()
{
if (_collectionsType != null && _currentConstellationField != null && _constellationStuffField != null)
{
return true;
}
try
{
Assembly assembly2 = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly assembly) => assembly.GetName().Name == "LethalConstellations");
if (assembly2 == null)
{
return false;
}
_collectionsType = assembly2.GetType("LethalConstellations.PluginCore.Collections");
_currentConstellationField = _collectionsType?.GetField("CurrentConstellation", BindingFlags.Static | BindingFlags.Public);
_constellationStuffField = _collectionsType?.GetField("ConstellationStuff", BindingFlags.Static | BindingFlags.Public);
if (!(_collectionsType != null) || !(_currentConstellationField != null) || !(_constellationStuffField != null))
{
LogReflectionWarningOnce("Required LethalConstellations symbols were not found. Falling back to global deadlines.");
return false;
}
return true;
}
catch (Exception ex)
{
LogReflectionWarningOnce("Failed to initialize LethalConstellations reflection: " + ex.Message);
return false;
}
}
private static void LogReflectionWarningOnce(string message)
{
if (!_loggedReflectionFailure)
{
_loggedReflectionFailure = true;
Plugin.Log.LogWarning((object)message);
}
}
}
internal static class OpenLibEventBridge
{
[CompilerGenerated]
private static class <>O
{
public static Event <0>__OnLevelChanged;
}
private static bool _subscribed;
internal static void TrySubscribe()
{
if (_subscribed)
{
return;
}
if (!Chainloader.PluginInfos.ContainsKey("darmuh.OpenLib"))
{
Plugin.Log.LogDebug((object)"OpenLib not loaded; constellation deadline event subscription skipped.");
return;
}
try
{
Subscribe();
_subscribed = true;
Plugin.Log.LogInfo((object)"Subscribed to OpenLib StartOfRoundChangeLevel for constellation deadline updates.");
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not subscribe to OpenLib events: " + ex.Message));
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void Subscribe()
{
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_0020: Expected O, but got Unknown
CustomEvent startOfRoundChangeLevel = EventManager.StartOfRoundChangeLevel;
object obj = <>O.<0>__OnLevelChanged;
if (obj == null)
{
Event val = OnLevelChanged;
<>O.<0>__OnLevelChanged = val;
obj = (object)val;
}
startOfRoundChangeLevel.AddListener((Event)obj);
}
private static void OnLevelChanged()
{
try
{
TimeOfDay instance = TimeOfDay.Instance;
if ((Object)(object)instance == (Object)null || instance.timesFulfilledQuota != 0)
{
return;
}
ConstellationDeadlineConfig.RefreshSections();
if (TimeOfDayQuotaPatch.TryApplyInitialDeadlineFromCurrentMode(instance, allowConstellationOverride: true, logSelection: true))
{
if (((NetworkBehaviour)instance).IsServer)
{
NetworkSync.SyncDeadlineToClients(instance.daysUntilDeadline);
Plugin.Log.LogInfo((object)$"Constellation deadline synced: {instance.daysUntilDeadline} days.");
}
TimeOfDayQuotaPatch.RefreshExternalMonitors();
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("OpenLib level-change handler failed: " + ex.Message));
}
}
}
}