using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using RepoQuota.Systems;
using RepoQuota.UI;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("RepoQuota")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("RepoQuota")]
[assembly: AssemblyTitle("RepoQuota")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.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.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace RepoQuota
{
[BepInPlugin("com.repoquota.mod", "RepoQuota", "1.0.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BaseUnityPlugin
{
public static ConfigEntry<string> DifficultyPreset;
public static ConfigEntry<int> BaseQuotaPercent;
public static ConfigEntry<int> PercentIncreasePerCycle;
public static ConfigEntry<int> MaxQuotaPercent;
public static ConfigEntry<int> LevelsPerCycle;
public static ConfigEntry<string> FailurePenalty;
public static ConfigEntry<int> MoneyPenaltyPercent;
public static ConfigEntry<bool> ResetQuotaOnFail;
public static ConfigEntry<bool> ShowLevelResults;
public static ConfigEntry<bool> ShowCompanyMessages;
private Harmony _harmony;
public static Plugin Instance { get; private set; }
public static ManualLogSource Log { get; private set; }
private void Awake()
{
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_003d: Expected O, but got Unknown
Instance = this;
Log = ((BaseUnityPlugin)this).Logger;
BindConfig();
DifficultyPreset.SettingChanged += delegate
{
if (DifficultyPreset.Value.ToLower() != "custom")
{
ApplyPreset(DifficultyPreset.Value);
Log.LogInfo((object)("Preset changed to: " + DifficultyPreset.Value));
}
};
_harmony = new Harmony("com.repoquota.mod");
_harmony.PatchAll();
QuotaManager.Initialize();
((Component)this).gameObject.SetActive(true);
((Behaviour)this).enabled = true;
((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
Log.LogInfo((object)"RepoQuota v1.0.0 loaded!");
Log.LogInfo((object)("[Config] Preset=" + DifficultyPreset.Value + ", " + $"BaseQuota={BaseQuotaPercent.Value}%, " + $"Increase={PercentIncreasePerCycle.Value}%, " + $"Max={MaxQuotaPercent.Value}%, " + $"LevelsPerCycle={LevelsPerCycle.Value}, " + "Penalty=" + FailurePenalty.Value + ", " + $"MoneyLoss={MoneyPenaltyPercent.Value}%, " + $"ResetOnFail={ResetQuotaOnFail.Value}, " + $"ShowResults={ShowLevelResults.Value}, " + $"ShowMessages={ShowCompanyMessages.Value}"));
}
private void Update()
{
QuotaManager.UpdateNotification(Time.deltaTime);
}
private void BindConfig()
{
//IL_0052: Unknown result type (might be due to invalid IL or missing references)
//IL_005c: Expected O, but got Unknown
//IL_0086: Unknown result type (might be due to invalid IL or missing references)
//IL_0090: Expected O, but got Unknown
//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
//IL_00c2: Expected O, but got Unknown
//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
//IL_00f6: Expected O, but got Unknown
//IL_011e: Unknown result type (might be due to invalid IL or missing references)
//IL_0128: Expected O, but got Unknown
//IL_016f: Unknown result type (might be due to invalid IL or missing references)
//IL_0179: Expected O, but got Unknown
//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
//IL_01ac: Expected O, but got Unknown
DifficultyPreset = ((BaseUnityPlugin)this).Config.Bind<string>("1. Difficulty Preset", "Preset", "Normal", new ConfigDescription("Choose a difficulty preset or set to 'Custom' to use your own values below.\n\nEasy = 50% quota, 4 levels, warning on fail\nNormal = 60% quota, 3 levels, lose 30% cash on fail\nHard = 70% quota, 3 levels, lose 50% cash on fail\nLethal = 80% quota, 2 levels, game over on fail\nCustom = Use the values you set in sections 2 and 3", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[5] { "Easy", "Normal", "Hard", "Lethal", "Custom" }), Array.Empty<object>()));
BaseQuotaPercent = ((BaseUnityPlugin)this).Config.Bind<int>("2. Quota Settings", "Starting Quota Percent", 60, new ConfigDescription("What percentage of available loot you need to collect.\nExample: 60 means you must extract 60% of all valuables across the cycle.\nOnly used when Preset is set to 'Custom'.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 99), Array.Empty<object>()));
PercentIncreasePerCycle = ((BaseUnityPlugin)this).Config.Bind<int>("2. Quota Settings", "Increase Per Cycle", 5, new ConfigDescription("How much harder it gets each cycle.\nExample: 5 means the quota goes from 60% → 65% → 70% each cycle.\nSet to 0 for a flat difficulty that never changes.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 20), Array.Empty<object>()));
MaxQuotaPercent = ((BaseUnityPlugin)this).Config.Bind<int>("2. Quota Settings", "Max Quota Percent", 90, new ConfigDescription("The quota percentage will never go above this value.\nExample: 90 means even after many cycles, you'll never need more than 90%.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(50, 99), Array.Empty<object>()));
LevelsPerCycle = ((BaseUnityPlugin)this).Config.Bind<int>("2. Quota Settings", "Levels Per Cycle", 3, new ConfigDescription("How many levels you get to meet each quota.\nYour loot collection is tracked across all levels in the cycle.\nMore levels = more chances to make up for a bad run.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>()));
FailurePenalty = ((BaseUnityPlugin)this).Config.Bind<string>("3. Penalty Settings", "Failure Penalty", "MoneyDrain", new ConfigDescription("What happens when you fail to meet quota.\n\nWarning = Scary message, no real consequence. Good for casual play.\nMoneyDrain = Lose a percentage of your money (set below). Default.\nGameOver = Run ends. The Taxman terminates your contract.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "Warning", "MoneyDrain", "GameOver" }), Array.Empty<object>()));
MoneyPenaltyPercent = ((BaseUnityPlugin)this).Config.Bind<int>("3. Penalty Settings", "Money Loss Percent", 30, new ConfigDescription("How much money you lose when the penalty is 'MoneyDrain'.\nExample: 30 means you lose 30% of your current cash.\nIgnored if penalty is set to Warning or GameOver.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(5, 90), Array.Empty<object>()));
ResetQuotaOnFail = ((BaseUnityPlugin)this).Config.Bind<bool>("3. Penalty Settings", "Reset Quota On Fail", false, "If true, failing a quota resets the percentage back to the starting value.\nIf false, the next cycle keeps the same difficulty.\nExample: You're at 75% quota, you fail. With this ON, next cycle is 60% again.");
ShowLevelResults = ((BaseUnityPlugin)this).Config.Bind<bool>("4. Display", "Show Level Results", true, "Show a notification after each level with your extraction percentage.");
ShowCompanyMessages = ((BaseUnityPlugin)this).Config.Bind<bool>("4. Display", "Show Company Messages", true, "Show themed messages from The Taxman when quotas are met or failed.");
}
private void ApplyPreset(string preset)
{
switch (preset.ToLower())
{
case "easy":
BaseQuotaPercent.Value = 50;
PercentIncreasePerCycle.Value = 3;
MaxQuotaPercent.Value = 80;
LevelsPerCycle.Value = 4;
FailurePenalty.Value = "Warning";
ResetQuotaOnFail.Value = false;
break;
case "normal":
BaseQuotaPercent.Value = 60;
PercentIncreasePerCycle.Value = 5;
MaxQuotaPercent.Value = 90;
LevelsPerCycle.Value = 3;
FailurePenalty.Value = "MoneyDrain";
MoneyPenaltyPercent.Value = 30;
ResetQuotaOnFail.Value = false;
break;
case "hard":
BaseQuotaPercent.Value = 70;
PercentIncreasePerCycle.Value = 5;
MaxQuotaPercent.Value = 95;
LevelsPerCycle.Value = 3;
FailurePenalty.Value = "MoneyDrain";
MoneyPenaltyPercent.Value = 50;
ResetQuotaOnFail.Value = false;
break;
case "lethal":
BaseQuotaPercent.Value = 80;
PercentIncreasePerCycle.Value = 5;
MaxQuotaPercent.Value = 99;
LevelsPerCycle.Value = 2;
FailurePenalty.Value = "GameOver";
ResetQuotaOnFail.Value = false;
break;
default:
Log.LogInfo((object)"Using custom config values.");
break;
}
Log.LogInfo((object)("Preset applied: " + preset));
}
public static float GetBasePercent()
{
return (float)BaseQuotaPercent.Value / 100f;
}
public static float GetIncreasePerCycle()
{
return (float)PercentIncreasePerCycle.Value / 100f;
}
public static float GetMaxPercent()
{
return (float)MaxQuotaPercent.Value / 100f;
}
public static float GetMoneyPenaltyPercent()
{
return (float)MoneyPenaltyPercent.Value / 100f;
}
private void OnDestroy()
{
Harmony harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
}
}
public static class PluginInfo
{
public const string GUID = "com.repoquota.mod";
public const string NAME = "RepoQuota";
public const string VERSION = "1.0.0";
}
}
namespace RepoQuota.UI
{
public class QuotaSign : MonoBehaviour
{
private TextMeshPro _quotaText;
private TextMeshPro _deadlineText;
private ExtractionPoint _extractionPoint;
private int _bankedExtraction;
private bool _hasCompleted;
private static List<QuotaSign> _allSigns = new List<QuotaSign>();
private static FieldInfo _haulCurrentField;
private static bool _haulCurrentLookedUp = false;
private static int _unclaimedBanked = 0;
public static void SpawnAtExtractionPoints()
{
//IL_0055: Unknown result type (might be due to invalid IL or missing references)
//IL_005a: Unknown result type (might be due to invalid IL or missing references)
//IL_0072: Unknown result type (might be due to invalid IL or missing references)
//IL_0077: Unknown result type (might be due to invalid IL or missing references)
//IL_0079: Unknown result type (might be due to invalid IL or missing references)
//IL_007f: Unknown result type (might be due to invalid IL or missing references)
//IL_0087: Unknown result type (might be due to invalid IL or missing references)
//IL_0091: Unknown result type (might be due to invalid IL or missing references)
//IL_0096: Unknown result type (might be due to invalid IL or missing references)
//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
ExtractionPoint[] array = Object.FindObjectsOfType<ExtractionPoint>();
foreach (ExtractionPoint val in array)
{
if (!((Object)(object)((Component)val).transform.Find("QuotaSign") != (Object)null))
{
TextMeshPro haulGoalScreen = val.haulGoalScreen;
if ((Object)(object)haulGoalScreen == (Object)null)
{
Plugin.Log.LogWarning((object)"[Quota] ExtractionPoint missing haulGoalScreen.");
continue;
}
GameObject val2 = new GameObject("QuotaSign");
val2.transform.SetParent(((Component)val).transform, false);
Vector3 position = haulGoalScreen.transform.position;
val2.transform.position = position + haulGoalScreen.transform.up * 0.7f;
val2.transform.rotation = haulGoalScreen.transform.rotation;
QuotaSign quotaSign = val2.AddComponent<QuotaSign>();
quotaSign._extractionPoint = val;
quotaSign.BuildSign(haulGoalScreen);
Plugin.Log.LogInfo((object)"[Quota] Sign placed.");
}
}
}
private void OnEnable()
{
_allSigns.Add(this);
}
private void OnDisable()
{
_allSigns.Remove(this);
}
public static void ResetCompleted(ExtractionPoint ep)
{
foreach (QuotaSign allSign in _allSigns)
{
if ((Object)(object)allSign._extractionPoint == (Object)(object)ep)
{
allSign._hasCompleted = false;
break;
}
}
}
public static void BankExtraction(ExtractionPoint ep, int amount)
{
foreach (QuotaSign allSign in _allSigns)
{
if ((Object)(object)allSign._extractionPoint == (Object)(object)ep)
{
allSign._bankedExtraction += amount;
allSign._hasCompleted = true;
Plugin.Log.LogInfo((object)$"[Quota] Sign banked ${amount}. Total for this EP: ${allSign._bankedExtraction}");
return;
}
}
_unclaimedBanked += amount;
Plugin.Log.LogInfo((object)$"[Quota] No sign for EP, added to unclaimed: ${amount}");
}
private static int ReadHaulCurrent(ExtractionPoint ep)
{
if (!_haulCurrentLookedUp)
{
_haulCurrentField = typeof(ExtractionPoint).GetField("haulCurrent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
_haulCurrentLookedUp = true;
if (_haulCurrentField == null)
{
Plugin.Log.LogWarning((object)"[Quota] Could not find haulCurrent field on ExtractionPoint.");
}
}
if (_haulCurrentField == null || (Object)(object)ep == (Object)null)
{
return 0;
}
object value = _haulCurrentField.GetValue(ep);
if (value is int)
{
return (int)value;
}
return 0;
}
public static int GetTotalLiveExtraction()
{
int num = _unclaimedBanked;
foreach (QuotaSign allSign in _allSigns)
{
if (!((Object)(object)allSign._extractionPoint == (Object)null))
{
num += allSign._bankedExtraction;
if (!allSign._hasCompleted)
{
num += ReadHaulCurrent(allSign._extractionPoint);
}
}
}
return num;
}
public static int CollectAndResetExtraction()
{
int totalLiveExtraction = GetTotalLiveExtraction();
_unclaimedBanked = 0;
foreach (QuotaSign allSign in _allSigns)
{
allSign._bankedExtraction = 0;
allSign._hasCompleted = false;
}
return totalLiveExtraction;
}
private void BuildSign(TextMeshPro reference)
{
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
//IL_006e: Unknown result type (might be due to invalid IL or missing references)
//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
//IL_0109: Unknown result type (might be due to invalid IL or missing references)
//IL_0128: Unknown result type (might be due to invalid IL or missing references)
//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
//IL_0183: Unknown result type (might be due to invalid IL or missing references)
//IL_018a: Expected O, but got Unknown
//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
//IL_0235: Unknown result type (might be due to invalid IL or missing references)
//IL_0254: Unknown result type (might be due to invalid IL or missing references)
//IL_0263: Unknown result type (might be due to invalid IL or missing references)
//IL_026a: Expected O, but got Unknown
//IL_02b9: Unknown result type (might be due to invalid IL or missing references)
//IL_0315: Unknown result type (might be due to invalid IL or missing references)
//IL_0334: Unknown result type (might be due to invalid IL or missing references)
//IL_0174: Unknown result type (might be due to invalid IL or missing references)
TMP_FontAsset font = ((TMP_Text)reference).font;
GameObject obj = GameObject.CreatePrimitive((PrimitiveType)5);
((Object)obj).name = "SignBacking";
obj.transform.SetParent(((Component)this).transform, false);
obj.transform.localPosition = new Vector3(0f, 0f, 0.01f);
obj.transform.localRotation = Quaternion.identity;
obj.transform.localScale = new Vector3(0.9f, 0.28f, 1f);
Collider component = obj.GetComponent<Collider>();
if ((Object)(object)component != (Object)null)
{
Object.Destroy((Object)(object)component);
}
MeshRenderer component2 = obj.GetComponent<MeshRenderer>();
if ((Object)(object)component2 != (Object)null)
{
((Renderer)component2).material.color = new Color(0.02f, 0.02f, 0.03f, 1f);
}
GameObject obj2 = GameObject.CreatePrimitive((PrimitiveType)5);
((Object)obj2).name = "SignBorder";
obj2.transform.SetParent(((Component)this).transform, false);
obj2.transform.localPosition = new Vector3(0f, 0f, 0.015f);
obj2.transform.localRotation = Quaternion.identity;
obj2.transform.localScale = new Vector3(0.95f, 0.32f, 1f);
Collider component3 = obj2.GetComponent<Collider>();
if ((Object)(object)component3 != (Object)null)
{
Object.Destroy((Object)(object)component3);
}
MeshRenderer component4 = obj2.GetComponent<MeshRenderer>();
if ((Object)(object)component4 != (Object)null)
{
((Renderer)component4).material.color = new Color(0.18f, 0.18f, 0.2f, 1f);
}
GameObject val = new GameObject("QuotaText");
val.transform.SetParent(((Component)this).transform, false);
_quotaText = val.AddComponent<TextMeshPro>();
((TMP_Text)_quotaText).font = font;
((TMP_Text)_quotaText).fontSize = 3f;
((TMP_Text)_quotaText).fontStyle = (FontStyles)1;
((Graphic)_quotaText).color = ((Graphic)reference).color;
((TMP_Text)_quotaText).alignment = (TextAlignmentOptions)514;
((TMP_Text)_quotaText).enableWordWrapping = false;
((TMP_Text)_quotaText).overflowMode = (TextOverflowModes)0;
((TMP_Text)_quotaText).text = "QUOTA: <color=#bd4300>$</color>0 / <color=#bd4300>$</color>0";
((Transform)((TMP_Text)_quotaText).rectTransform).localPosition = new Vector3(0f, 0.055f, 0f);
((TMP_Text)_quotaText).rectTransform.sizeDelta = new Vector2(1f, 0.2f);
GameObject val2 = new GameObject("DeadlineText");
val2.transform.SetParent(((Component)this).transform, false);
_deadlineText = val2.AddComponent<TextMeshPro>();
((TMP_Text)_deadlineText).font = font;
((TMP_Text)_deadlineText).fontSize = 2f;
((TMP_Text)_deadlineText).fontStyle = (FontStyles)0;
((Graphic)_deadlineText).color = ((Graphic)reference).color;
((TMP_Text)_deadlineText).alignment = (TextAlignmentOptions)514;
((TMP_Text)_deadlineText).enableWordWrapping = false;
((TMP_Text)_deadlineText).overflowMode = (TextOverflowModes)0;
((TMP_Text)_deadlineText).text = "DEADLINE: 3 DAYS";
((Transform)((TMP_Text)_deadlineText).rectTransform).localPosition = new Vector3(0f, -0.18f, 0f);
((TMP_Text)_deadlineText).rectTransform.sizeDelta = new Vector2(1f, 0.2f);
}
private void Update()
{
//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
if (!((Object)(object)_quotaText == (Object)null) && QuotaManager.IsActive)
{
int num = (QuotaManager.IsInGameLevel ? GetTotalLiveExtraction() : 0);
int num2 = QuotaManager.CycleTotalExtracted + num;
int dollarTarget = QuotaManager.DollarTarget;
string text = SemiFunc.DollarGetString(num2);
string text2 = SemiFunc.DollarGetString(dollarTarget);
((TMP_Text)_quotaText).text = "QUOTA: <color=#bd4300>$</color>" + text + " / <color=#bd4300>$</color>" + text2;
int levelsRemaining = QuotaManager.LevelsRemaining;
string arg = ((levelsRemaining == 1) ? "DAY" : "DAYS");
if (QuotaManager.QuotaMet)
{
((TMP_Text)_deadlineText).text = "QUOTA MET";
((Graphic)_deadlineText).color = new Color(0.2f, 1f, 0.3f);
}
else
{
((TMP_Text)_deadlineText).text = $"DEADLINE: {levelsRemaining} {arg}";
((Graphic)_deadlineText).color = (Color)((levelsRemaining <= 1) ? new Color(1f, 0.3f, 0.2f) : ((Graphic)_quotaText).color);
}
}
}
}
}
namespace RepoQuota.Systems
{
public static class CompanyMessages
{
private static string Pick(string[] pool)
{
return pool[Random.Range(0, pool.Length)];
}
public static string GetQuotaMetMessage(float pct, int streak)
{
string text = Pick(new string[4]
{
$"Quota met — {pct:F0}% collected. Acceptable.",
$"{pct:F0}% extraction rate. The Taxman is satisfied... for now.",
"Quota fulfilled. You may continue operating.",
$"Target reached at {pct:F0}%. Don't get comfortable."
});
if (streak >= 3)
{
text += $"\n{streak} in a row. Impressive.";
}
else if (streak >= 2)
{
text += $"\n{streak} consecutive. Keep it up.";
}
return text;
}
public static string GetFailureMessage(float got, float needed)
{
return Pick(new string[4]
{
$"QUOTA MISSED. Collected {got:F0}%, needed {needed:F0}%.",
$"Insufficient extraction. {got:F0}% does not meet the {needed:F0}% requirement.",
$"The Taxman is displeased. {got:F0}% vs {needed:F0}% target.",
$"You have failed to meet your obligation. {got:F0}% collected."
});
}
public static string GetGameOverMessage()
{
return Pick(new string[3] { "CONTRACT TERMINATED.\nThe Company has no further use for you.", "YOUR SERVICES ARE NO LONGER REQUIRED.", "TERMINATED. You have been deemed unprofitable." });
}
public static string GetProgressComment(float currentRate, float targetRate)
{
float num = ((targetRate > 0f) ? (currentRate / targetRate) : 0f);
if (num >= 1f)
{
return "On track. Quota already met.";
}
if (num >= 0.75f)
{
return "Nearly there. Stay thorough.";
}
if (num >= 0.5f)
{
return "Adequate pace. Don't slack off.";
}
if (num >= 0.25f)
{
return "Below expectations. Extract more.";
}
return "The Taxman is watching. Pick up the pace.";
}
}
public static class QuotaManager
{
public struct LevelRecord
{
public int LevelNumber;
public int TotalAvailable;
public int TotalExtracted;
public float CollectionRate;
}
private static bool _gameOverTriggered = false;
public static int CycleNumber { get; private set; }
public static float RequiredPercent { get; private set; }
public static int LevelsPerCycle { get; private set; }
public static int LevelsCompleted { get; private set; }
public static int LevelsRemaining => LevelsPerCycle - LevelsCompleted;
public static int CycleTotalAvailable { get; private set; }
public static int CycleTotalExtracted { get; private set; }
public static float CycleCollectionRate
{
get
{
if (CycleTotalAvailable <= 0)
{
return 0f;
}
return (float)CycleTotalExtracted / (float)CycleTotalAvailable;
}
}
public static bool QuotaMet => CycleCollectionRate >= RequiredPercent;
public static int DollarTarget => Mathf.RoundToInt((float)CycleTotalAvailable * RequiredPercent);
public static int CurrentLevelAvailable { get; private set; }
public static int CurrencyAtLevelStart { get; private set; }
public static List<LevelRecord> CurrentCycleRecords { get; private set; } = new List<LevelRecord>();
public static int ConsecutiveQuotasMet { get; private set; }
public static int TotalLifetimeExtracted { get; private set; }
public static int HighestStreakEver { get; private set; }
public static bool IsActive { get; private set; }
public static bool IsInGameLevel { get; private set; }
public static string PendingNotification { get; private set; } = "";
public static float NotificationTimer { get; private set; }
public static event Action OnCycleStart;
public static event Action OnLevelEarningsRecorded;
public static event Action<bool> OnCycleEnd;
public static void Initialize()
{
CycleNumber = 0;
ConsecutiveQuotasMet = 0;
TotalLifetimeExtracted = 0;
IsActive = true;
IsInGameLevel = false;
_gameOverTriggered = false;
StartNewCycle();
}
public static void StartNewCycle()
{
CycleNumber++;
LevelsCompleted = 0;
CycleTotalAvailable = 0;
CycleTotalExtracted = 0;
CurrentCycleRecords.Clear();
CurrentLevelAvailable = 0;
LevelsPerCycle = Plugin.LevelsPerCycle.Value;
RequiredPercent = CalculateRequiredPercent();
string arg = $"{RequiredPercent * 100f:F0}%";
string text = $"QUOTA #{CycleNumber}: Collect {arg} of loot across {LevelsPerCycle} levels";
ShowNotification(text, 5f);
QuotaManager.OnCycleStart?.Invoke();
Plugin.Log.LogInfo((object)text);
}
private static float CalculateRequiredPercent()
{
float basePercent = Plugin.GetBasePercent();
float increasePerCycle = Plugin.GetIncreasePerCycle();
float maxPercent = Plugin.GetMaxPercent();
return Mathf.Min(basePercent + increasePerCycle * (float)(CycleNumber - 1), maxPercent);
}
public static void OnEnterGameLevel()
{
if (IsActive)
{
IsInGameLevel = true;
CurrentLevelAvailable = 0;
CurrencyAtLevelStart = ReadCurrency();
Plugin.Log.LogInfo((object)"[Quota] Entered game level — waiting for level gen to scan valuables.");
}
}
public static void RescanAvailableLoot()
{
if (IsActive && IsInGameLevel)
{
CycleTotalAvailable -= CurrentLevelAvailable;
CurrentLevelAvailable = ScanAvailableLoot();
CycleTotalAvailable += CurrentLevelAvailable;
Plugin.Log.LogInfo((object)($"[Quota] Loot scan complete: ${CurrentLevelAvailable:N0} available. " + $"Cycle total: ${CycleTotalAvailable:N0}"));
}
}
private static int ScanAvailableLoot()
{
int num = 0;
try
{
ValuableObject[] array = Object.FindObjectsOfType<ValuableObject>();
ValuableObject[] array2 = array;
foreach (ValuableObject valuable in array2)
{
num += GetValuableValue(valuable);
}
Plugin.Log.LogInfo((object)$"[Quota] Scanned {array.Length} valuables, total: ${num:N0}");
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("[Quota] Error scanning valuables: " + ex.Message));
num = EstimateFromExtractionQuota();
}
return Mathf.Max(num, 1);
}
private static int GetValuableValue(ValuableObject valuable)
{
try
{
FieldInfo fieldInfo = AccessTools.Field(typeof(ValuableObject), "dollarValueCurrent");
if (fieldInfo != null)
{
object value = fieldInfo.GetValue(valuable);
if (value is float num)
{
return Mathf.RoundToInt(num);
}
if (value is int result)
{
return result;
}
}
FieldInfo fieldInfo2 = AccessTools.Field(typeof(ValuableObject), "dollarValueOriginal");
if (fieldInfo2 != null)
{
object value2 = fieldInfo2.GetValue(valuable);
if (value2 is float num2)
{
return Mathf.RoundToInt(num2);
}
if (value2 is int result2)
{
return result2;
}
}
Plugin.Log.LogWarning((object)"[Quota] Could not read valuable value — field name may be wrong.");
return 0;
}
catch
{
return 0;
}
}
private static int EstimateFromExtractionQuota()
{
try
{
ExtractionPoint[] array = Object.FindObjectsOfType<ExtractionPoint>();
int num = 0;
ExtractionPoint[] array2 = array;
foreach (ExtractionPoint obj in array2)
{
FieldInfo fieldInfo = AccessTools.Field(typeof(ExtractionPoint), "goalValue");
if (fieldInfo != null)
{
object value = fieldInfo.GetValue(obj);
if (value is int num2)
{
num += num2;
}
if (value is float num3)
{
num += Mathf.RoundToInt(num3);
}
}
}
int num4 = Mathf.RoundToInt((float)num * 1.75f);
Plugin.Log.LogInfo((object)$"[Quota] Estimated available loot from quotas: ${num4:N0}");
return num4;
}
catch
{
return 10000;
}
}
public static void OnLeaveGameLevel()
{
if (IsActive && IsInGameLevel)
{
IsInGameLevel = false;
int num = QuotaSign.CollectAndResetExtraction();
CycleTotalExtracted += num;
TotalLifetimeExtracted += num;
LevelsCompleted++;
LevelRecord levelRecord = default(LevelRecord);
levelRecord.LevelNumber = RunManager.instance.levelsCompleted;
levelRecord.TotalAvailable = CurrentLevelAvailable;
levelRecord.TotalExtracted = num;
levelRecord.CollectionRate = ((CurrentLevelAvailable > 0) ? ((float)num / (float)CurrentLevelAvailable) : 0f);
LevelRecord item = levelRecord;
CurrentCycleRecords.Add(item);
Plugin.Log.LogInfo((object)($"[Quota] Level done: extracted ${num:N0} / ${CurrentLevelAvailable:N0} " + $"({item.CollectionRate * 100f:F0}%). " + $"Cycle: ${CycleTotalExtracted:N0} / ${CycleTotalAvailable:N0} " + $"({CycleCollectionRate * 100f:F0}% — need {RequiredPercent * 100f:F0}%)"));
QuotaManager.OnLevelEarningsRecorded?.Invoke();
if (Plugin.ShowLevelResults.Value)
{
ShowNotification($"Level {item.LevelNumber}: ${num:N0} extracted ({item.CollectionRate * 100f:F0}%)\n" + $"Cycle progress: {CycleCollectionRate * 100f:F0}% / {RequiredPercent * 100f:F0}%", 4f);
}
if (LevelsCompleted >= LevelsPerCycle)
{
EndCycle();
}
}
}
private static void EndCycle()
{
bool quotaMet = QuotaMet;
QuotaManager.OnCycleEnd?.Invoke(quotaMet);
if (quotaMet)
{
ConsecutiveQuotasMet++;
if (ConsecutiveQuotasMet > HighestStreakEver)
{
HighestStreakEver = ConsecutiveQuotasMet;
}
float num = CycleCollectionRate * 100f;
ShowNotification(Plugin.ShowCompanyMessages.Value ? CompanyMessages.GetQuotaMetMessage(num, ConsecutiveQuotasMet) : $"QUOTA MET — {num:F0}% collected.", 6f);
Plugin.Log.LogInfo((object)$"[Quota] CYCLE {CycleNumber} MET! ({num:F0}%)");
StartNewCycle();
}
else
{
ConsecutiveQuotasMet = 0;
float num2 = CycleCollectionRate * 100f;
string value = Plugin.FailurePenalty.Value;
Plugin.Log.LogWarning((object)($"[Quota] CYCLE {CycleNumber} FAILED. " + $"({num2:F0}% vs {RequiredPercent * 100f:F0}% needed)"));
ApplyPenalty(value);
}
}
private static void ApplyPenalty(string penalty)
{
switch (penalty.ToLower())
{
case "gameover":
IsActive = false;
if (Plugin.ShowCompanyMessages.Value)
{
ShowNotification(CompanyMessages.GetGameOverMessage(), 10f);
}
try
{
RunManager instance = RunManager.instance;
if ((Object)(object)instance != (Object)null && !_gameOverTriggered)
{
_gameOverTriggered = true;
Plugin.Log.LogInfo((object)"[Quota] GAME OVER — triggering arena (last man standing).");
instance.levelCurrent = instance.levelArena;
instance.ChangeLevel(true, false, (ChangeLevelType)3);
}
break;
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("[Quota] Failed to trigger game over: " + ex.Message));
break;
}
case "moneydrain":
{
int num = ReadCurrency();
float moneyPenaltyPercent = Plugin.GetMoneyPenaltyPercent();
int num2 = Mathf.RoundToInt((float)num * moneyPenaltyPercent);
int num3 = num - num2;
try
{
PunManager.instance.SetRunStatSet("currency", num3);
}
catch
{
Plugin.Log.LogWarning((object)"[Quota] Failed to set currency.");
}
ShowNotification((Plugin.ShowCompanyMessages.Value ? CompanyMessages.GetFailureMessage(CycleCollectionRate * 100f, RequiredPercent * 100f) : "QUOTA FAILED.") + $"\n${num2:N0} confiscated ({Plugin.MoneyPenaltyPercent.Value}% penalty).", 8f);
if (Plugin.ResetQuotaOnFail.Value)
{
CycleNumber = 0;
}
StartNewCycle();
break;
}
default:
ShowNotification(Plugin.ShowCompanyMessages.Value ? CompanyMessages.GetFailureMessage(CycleCollectionRate * 100f, RequiredPercent * 100f) : $"QUOTA MISSED. Collected {CycleCollectionRate * 100f:F0}%, needed {RequiredPercent * 100f:F0}%.", 6f);
if (Plugin.ResetQuotaOnFail.Value)
{
CycleNumber = 0;
}
StartNewCycle();
break;
}
}
public static int ReadCurrency()
{
try
{
return StatsManager.instance.runStats["currency"];
}
catch
{
Plugin.Log.LogWarning((object)"[Quota] Could not read currency from StatsManager.");
return 0;
}
}
public static void SnapshotCurrencyAtLevelStart()
{
CurrencyAtLevelStart = ReadCurrency();
}
public static bool IsGameLevel()
{
try
{
RunManager instance = RunManager.instance;
return (Object)(object)instance.levelCurrent != (Object)(object)instance.levelShop && (Object)(object)instance.levelCurrent != (Object)(object)instance.levelMainMenu && (Object)(object)instance.levelCurrent != (Object)(object)instance.levelLobbyMenu && (Object)(object)instance.levelCurrent != (Object)(object)instance.levelTutorial && (Object)(object)instance.levelCurrent != (Object)(object)instance.levelArena;
}
catch
{
return false;
}
}
public static void ShowNotification(string message, float duration)
{
PendingNotification = message;
NotificationTimer = duration;
}
public static void UpdateNotification(float deltaTime)
{
if (NotificationTimer > 0f)
{
NotificationTimer -= deltaTime;
if (NotificationTimer <= 0f)
{
PendingNotification = "";
}
}
}
public static string GetStatusText()
{
if (!IsActive)
{
return "Quota system inactive.";
}
return $"Cycle #{CycleNumber} | " + $"Collected: {CycleCollectionRate * 100f:F0}% / {RequiredPercent * 100f:F0}% | " + $"Levels: {LevelsCompleted}/{LevelsPerCycle} | " + $"${CycleTotalExtracted:N0} / ${CycleTotalAvailable:N0} | " + $"Streak: {ConsecutiveQuotasMet}";
}
}
}
namespace RepoQuota.Patches
{
[HarmonyPatch(typeof(RunManager), "ChangeLevel")]
public class RunManager_ChangeLevel_Patch
{
[HarmonyPrefix]
private static void Prefix()
{
try
{
if (QuotaManager.IsActive && QuotaManager.IsGameLevel())
{
QuotaManager.OnLeaveGameLevel();
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("[Quota] ChangeLevel Prefix error: " + ex.Message));
}
}
[HarmonyPostfix]
private static void Postfix(RunManager __instance)
{
try
{
if (QuotaManager.IsActive && !((Object)(object)__instance.levelCurrent == (Object)(object)__instance.levelMainMenu) && !((Object)(object)__instance.levelCurrent == (Object)(object)__instance.levelLobbyMenu) && !((Object)(object)__instance.levelCurrent == (Object)(object)__instance.levelTutorial) && (Object)(object)__instance.levelCurrent != (Object)(object)__instance.levelShop && (Object)(object)__instance.levelCurrent != (Object)(object)__instance.levelArena)
{
QuotaManager.OnEnterGameLevel();
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("[Quota] ChangeLevel Postfix error: " + ex.Message));
}
}
}
[HarmonyPatch(typeof(RoundDirector), "StartRoundLogic")]
public class RoundDirector_StartRoundLogic_Patch
{
[HarmonyPostfix]
private static void Postfix()
{
try
{
if (QuotaManager.IsActive)
{
QuotaManager.SnapshotCurrencyAtLevelStart();
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("[Quota] StartRoundLogic Postfix error: " + ex.Message));
}
}
}
[HarmonyPatch(typeof(StatsManager), "ResetAllStats")]
public class StatsManager_ResetAllStats_Patch
{
[HarmonyPostfix]
private static void Postfix()
{
QuotaManager.Initialize();
Plugin.Log.LogInfo((object)"[Quota] New run — quota initialized.");
}
}
[HarmonyPatch(typeof(ExtractionPoint), "StateSetRPC")]
public class ExtractionPoint_StateSetRPC_Patch
{
[HarmonyPrefix]
private static void Prefix(ExtractionPoint __instance, State state)
{
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_0028: Invalid comparison between Unknown and I4
//IL_0030: Unknown result type (might be due to invalid IL or missing references)
//IL_0032: Invalid comparison between Unknown and I4
//IL_0034: Unknown result type (might be due to invalid IL or missing references)
//IL_0036: Invalid comparison between Unknown and I4
try
{
if (!QuotaManager.IsActive)
{
return;
}
Plugin.Log.LogInfo((object)$"[Quota] EP StateSetRPC: {state}");
if ((int)state == 2)
{
QuotaSign.ResetCompleted(__instance);
}
if ((int)state != 7 && (int)state != 1)
{
return;
}
FieldInfo fieldInfo = AccessTools.Field(typeof(ExtractionPoint), "haulCurrent");
if (fieldInfo != null)
{
int num = (int)fieldInfo.GetValue(__instance);
if (num > 0)
{
QuotaSign.BankExtraction(__instance, num);
Plugin.Log.LogInfo((object)$"[Quota] Banked ${num} from extraction point.");
}
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("[Quota] StateSetRPC Prefix error: " + ex.Message));
}
}
}
[HarmonyPatch(typeof(GameDirector), "SetStart")]
public class GameDirector_SetStart_Patch
{
[HarmonyPostfix]
private static void Postfix()
{
try
{
if (QuotaManager.IsActive && QuotaManager.IsGameLevel())
{
QuotaManager.RescanAvailableLoot();
QuotaSign.SpawnAtExtractionPoints();
}
}
catch (Exception ex)
{
Plugin.Log.LogError((object)("[Quota] GameDirector.SetStart Postfix error: " + ex.Message));
}
}
}
}