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 System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("")] [assembly: AssemblyCompany("Empress")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+79ae26948f15ddd0825c08f9c67eab7df2c0586c")] [assembly: AssemblyProduct("RepoAchievements")] [assembly: AssemblyTitle("RepoAchievements")] [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.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 Empress.REPO.Achievements { [HarmonyPatch] internal static class EmpressAchievementsPlus { private readonly struct ThresholdAchievement { public readonly string Id; public readonly long Threshold; public readonly string Title; public readonly string Description; public ThresholdAchievement(string id, long threshold, string title, string description) { Id = id; Threshold = threshold; Title = title; Description = description; } } private readonly struct SpreeAchievement { public readonly string Id; public readonly int Count; public readonly float WindowSeconds; public readonly string Title; public readonly string Description; public SpreeAchievement(string id, int count, float windowSeconds, string title, string description) { Id = id; Count = count; WindowSeconds = windowSeconds; Title = title; Description = description; } } private readonly struct EnemyTypeInfo { public readonly string Id; public readonly string Label; public readonly string HundredTitle; public EnemyTypeInfo(string id, string label, string hundredTitle) { Id = id; Label = label; HundredTitle = hundredTitle; } } private static readonly Queue KillTimestamps = new Queue(); private static bool Defined; private static readonly ThresholdAchievement[] MetaAchievements = new ThresholdAchievement[10] { new ThresholdAchievement("ach_meta_10", 10L, "Warmed Up", "Unlock 10 achievements."), new ThresholdAchievement("ach_meta_25", 25L, "On Your Way", "Unlock 25 achievements."), new ThresholdAchievement("ach_meta_50", 50L, "Certified Collector", "Unlock 50 achievements."), new ThresholdAchievement("ach_meta_75", 75L, "Overachiever", "Unlock 75 achievements."), new ThresholdAchievement("ach_meta_90", 90L, "No Brakes", "Unlock 90 achievements."), new ThresholdAchievement("ach_meta_100", 100L, "Triple Digits", "Unlock 100 achievements."), new ThresholdAchievement("ach_meta_125", 125L, "Badge Buffet", "Unlock 125 achievements."), new ThresholdAchievement("ach_meta_150", 150L, "Archive Monster", "Unlock 150 achievements."), new ThresholdAchievement("ach_meta_175", 175L, "Cabinet Crusher", "Unlock 175 achievements."), new ThresholdAchievement("ach_meta_200", 200L, "Empress Certified", "Unlock 200 achievements.") }; private static readonly ThresholdAchievement[] ExtractionAchievements = new ThresholdAchievement[5] { new ThresholdAchievement("extract_300", 300L, "Night Shift", "Complete 300 extractions (lifetime)."), new ThresholdAchievement("extract_500", 500L, "Route Owned", "Complete 500 extractions (lifetime)."), new ThresholdAchievement("extract_750", 750L, "Platform Ghost", "Complete 750 extractions (lifetime)."), new ThresholdAchievement("extract_1000", 1000L, "Timetable Tyrant", "Complete 1,000 extractions (lifetime)."), new ThresholdAchievement("extract_2000", 2000L, "Terminal Deity", "Complete 2,000 extractions (lifetime).") }; private static readonly ThresholdAchievement[] DiscoveryAchievements = new ThresholdAchievement[10] { new ThresholdAchievement("discover_5", 5L, "Shiny Thing", "Discover 5 valuables."), new ThresholdAchievement("discover_10", 10L, "Curio Finder", "Discover 10 valuables."), new ThresholdAchievement("discover_25", 25L, "Antiquarian", "Discover 25 valuables."), new ThresholdAchievement("discover_50", 50L, "Treasure Sleuth", "Discover 50 valuables."), new ThresholdAchievement("discover_75", 75L, "Display Case", "Discover 75 valuables."), new ThresholdAchievement("discover_100", 100L, "Museum Curator", "Discover 100 valuables."), new ThresholdAchievement("discover_150", 150L, "Treasure Map Brain", "Discover 150 valuables."), new ThresholdAchievement("discover_250", 250L, "Vault Tourist", "Discover 250 valuables."), new ThresholdAchievement("discover_500", 500L, "Curation Problem", "Discover 500 valuables."), new ThresholdAchievement("discover_1000", 1000L, "Relic Census", "Discover 1,000 valuables.") }; private static readonly ThresholdAchievement[] LifetimePurchaseAchievements = new ThresholdAchievement[6] { new ThresholdAchievement("big_spender_5", 5L, "Browsing No More", "Purchase 5 items (lifetime)."), new ThresholdAchievement("big_spender_75", 75L, "Impulse Engine", "Purchase 75 items (lifetime)."), new ThresholdAchievement("big_spender_150", 150L, "Receipt Collector", "Purchase 150 items (lifetime)."), new ThresholdAchievement("big_spender_250", 250L, "Bag Secured", "Purchase 250 items (lifetime)."), new ThresholdAchievement("big_spender_500", 500L, "Market Mover", "Purchase 500 items (lifetime)."), new ThresholdAchievement("big_spender_1000", 1000L, "Checkout Monarch", "Purchase 1,000 items (lifetime).") }; private static readonly ThresholdAchievement[] LifetimeDeathAchievements = new ThresholdAchievement[6] { new ThresholdAchievement("death_75", 75L, "Bruise Budget", "Die 75 times."), new ThresholdAchievement("death_150", 150L, "Frequent Flyer Home", "Die 150 times."), new ThresholdAchievement("death_250", 250L, "Ragdoll Royalty", "Die 250 times."), new ThresholdAchievement("death_750", 750L, "Pain Dividend", "Die 750 times."), new ThresholdAchievement("death_1000", 1000L, "Afterlife Residency", "Die 1,000 times."), new ThresholdAchievement("death_2500", 2500L, "Professional Corpse", "Die 2,500 times.") }; private static readonly ThresholdAchievement[] LifetimeReviveAchievements = new ThresholdAchievement[6] { new ThresholdAchievement("revived_5", 5L, "Still Standing", "Be revived 5 times."), new ThresholdAchievement("revived_25", 25L, "Miracle Mileage", "Be revived 25 times."), new ThresholdAchievement("revived_100", 100L, "Defibrillator Darling", "Be revived 100 times."), new ThresholdAchievement("revived_250", 250L, "Return Policy", "Be revived 250 times."), new ThresholdAchievement("revived_500", 500L, "Unfinished Business", "Be revived 500 times."), new ThresholdAchievement("revived_1000", 1000L, "Too Stubborn To Die", "Be revived 1,000 times.") }; private static readonly ThresholdAchievement[] LifetimeHaulAchievements = new ThresholdAchievement[12] { new ThresholdAchievement("life_haul_75k", 75000L, "Coin Pile", "Accumulate 75,000 haul (lifetime)."), new ThresholdAchievement("life_haul_150k", 150000L, "Payroll Problem", "Accumulate 150,000 haul (lifetime)."), new ThresholdAchievement("life_haul_300k", 300000L, "Treasure Warehouse", "Accumulate 300,000 haul (lifetime)."), new ThresholdAchievement("life_haul_750k", 750000L, "Bag Mountain", "Accumulate 750,000 haul (lifetime)."), new ThresholdAchievement("life_haul_1p5m", 1500000L, "Capital Gain", "Accumulate 1,500,000 haul (lifetime)."), new ThresholdAchievement("life_haul_3m", 3000000L, "Fortune Engine", "Accumulate 3,000,000 haul (lifetime)."), new ThresholdAchievement("life_haul_7p5m", 7500000L, "Vault Fever", "Accumulate 7,500,000 haul (lifetime)."), new ThresholdAchievement("life_haul_15m", 15000000L, "Estate Sale", "Accumulate 15,000,000 haul (lifetime)."), new ThresholdAchievement("life_haul_30m", 30000000L, "Treasure State", "Accumulate 30,000,000 haul (lifetime)."), new ThresholdAchievement("life_haul_75m", 75000000L, "Museum Theft", "Accumulate 75,000,000 haul (lifetime)."), new ThresholdAchievement("life_haul_100m", 100000000L, "Nine Digits", "Accumulate 100,000,000 haul (lifetime)."), new ThresholdAchievement("life_haul_250m", 250000000L, "Economic Event", "Accumulate 250,000,000 haul (lifetime).") }; private static readonly ThresholdAchievement[] RunPurchaseAchievements = new ThresholdAchievement[9] { new ThresholdAchievement("run_purchases_5", 5L, "Bag Starter", "Buy 5 items in a single run."), new ThresholdAchievement("run_purchases_10", 10L, "Shopaholic (Run)", "Buy 10 items in a single run."), new ThresholdAchievement("run_purchases_15", 15L, "Basket Case", "Buy 15 items in a single run."), new ThresholdAchievement("run_purchases_20", 20L, "Receipt Storm", "Buy 20 items in a single run."), new ThresholdAchievement("run_purchases_25", 25L, "Retail Bender", "Buy 25 items in a single run."), new ThresholdAchievement("run_purchases_30", 30L, "Checkout Sprint", "Buy 30 items in a single run."), new ThresholdAchievement("run_purchases_40", 40L, "Mall Marathon", "Buy 40 items in a single run."), new ThresholdAchievement("run_purchases_50", 50L, "Store Sweep", "Buy 50 items in a single run."), new ThresholdAchievement("run_purchases_75", 75L, "Inventory Collapse", "Buy 75 items in a single run.") }; private static readonly ThresholdAchievement[] RunDeathAchievements = new ThresholdAchievement[8] { new ThresholdAchievement("deaths_run_3", 3L, "Trip Hazard", "Die 3 times in a single run."), new ThresholdAchievement("deaths_run_5", 5L, "Learning Hurts", "Die 5 times in a single run."), new ThresholdAchievement("deaths_run_7", 7L, "Bruise Loop", "Die 7 times in a single run."), new ThresholdAchievement("deaths_run_10", 10L, "Pain Enthusiast", "Die 10 times in a single run."), new ThresholdAchievement("deaths_run_15", 15L, "Red Screen Residency", "Die 15 times in a single run."), new ThresholdAchievement("deaths_run_20", 20L, "Certified Masochist", "Die 20 times in a single run."), new ThresholdAchievement("deaths_run_25", 25L, "Spawn Tax", "Die 25 times in a single run."), new ThresholdAchievement("deaths_run_50", 50L, "Hazard Magnet", "Die 50 times in a single run.") }; private static readonly ThresholdAchievement[] RunKillAchievements = new ThresholdAchievement[10] { new ThresholdAchievement("run_kills_5", 5L, "Warmup Violence", "Eliminate 5 enemies in a single run."), new ThresholdAchievement("run_kills_10", 10L, "Cleanup Crew", "Eliminate 10 enemies in a single run."), new ThresholdAchievement("run_kills_15", 15L, "Run Riot", "Eliminate 15 enemies in a single run."), new ThresholdAchievement("run_kills_25", 25L, "Bloody Hands", "Eliminate 25 enemies in a single run."), new ThresholdAchievement("run_kills_50", 50L, "Crowd Control", "Eliminate 50 enemies in a single run."), new ThresholdAchievement("run_kills_75", 75L, "Corridor Clearer", "Eliminate 75 enemies in a single run."), new ThresholdAchievement("run_kills_100", 100L, "Neighborhood Watch", "Eliminate 100 enemies in a single run."), new ThresholdAchievement("run_kills_150", 150L, "Run Terminator", "Eliminate 150 enemies in a single run."), new ThresholdAchievement("run_kills_250", 250L, "Room Zero", "Eliminate 250 enemies in a single run."), new ThresholdAchievement("run_kills_500", 500L, "Extinction Event", "Eliminate 500 enemies in a single run.") }; private static readonly ThresholdAchievement[] LifetimeKillAchievements = new ThresholdAchievement[7] { new ThresholdAchievement("kills_750", 750L, "Bad Neighborhood", "Eliminate 750 enemies."), new ThresholdAchievement("kills_7500", 7500L, "Hazard Department", "Eliminate 7,500 enemies."), new ThresholdAchievement("kills_10000", 10000L, "Bureau of Population", "Eliminate 10,000 enemies."), new ThresholdAchievement("kills_15000", 15000L, "Local Legend", "Eliminate 15,000 enemies."), new ThresholdAchievement("kills_25000", 25000L, "Body Count Clerk", "Eliminate 25,000 enemies."), new ThresholdAchievement("kills_50000", 50000L, "Extinction Budget", "Eliminate 50,000 enemies."), new ThresholdAchievement("kills_100000", 100000L, "Disaster Economist", "Eliminate 100,000 enemies.") }; private static readonly ThresholdAchievement[] ChargeSessionAchievements = new ThresholdAchievement[8] { new ThresholdAchievement("charge_session_1", 1L, "Juiced", "Start charging at a station."), new ThresholdAchievement("charge_session_5", 5L, "Outlet Friend", "Start charging at stations 5 times."), new ThresholdAchievement("charge_session_10", 10L, "Power User", "Start charging at stations 10 times."), new ThresholdAchievement("charge_session_25", 25L, "Grid Hog", "Start charging at stations 25 times."), new ThresholdAchievement("charge_session_50", 50L, "Socket Specialist", "Start charging at stations 50 times."), new ThresholdAchievement("charge_session_100", 100L, "Grid Romantic", "Start charging at stations 100 times."), new ThresholdAchievement("charge_session_250", 250L, "Transformer Whisperer", "Start charging at stations 250 times."), new ThresholdAchievement("charge_session_500", 500L, "Power Parasite", "Start charging at stations 500 times.") }; private static readonly ThresholdAchievement[] ChargeTotalAchievements = new ThresholdAchievement[10] { new ThresholdAchievement("charge_total_50", 50L, "Spark Touched", "Accumulate 50 total station charge in a run."), new ThresholdAchievement("charge_total_100", 100L, "Watt About It", "Accumulate 100 total station charge in a run."), new ThresholdAchievement("charge_total_250", 250L, "Static Drinker", "Accumulate 250 total station charge in a run."), new ThresholdAchievement("charge_total_500", 500L, "Peak Demand", "Accumulate 500 total station charge in a run."), new ThresholdAchievement("charge_total_750", 750L, "Amp Appetite", "Accumulate 750 total station charge in a run."), new ThresholdAchievement("charge_total_1000", 1000L, "Brownout", "Accumulate 1,000 total station charge in a run."), new ThresholdAchievement("charge_total_1500", 1500L, "Voltage Appetite", "Accumulate 1,500 total station charge in a run."), new ThresholdAchievement("charge_total_2500", 2500L, "Overclocked Soul", "Accumulate 2,500 total station charge in a run."), new ThresholdAchievement("charge_total_5000", 5000L, "Grid Collapse", "Accumulate 5,000 total station charge in a run."), new ThresholdAchievement("charge_total_10000", 10000L, "Citywide Brownout", "Accumulate 10,000 total station charge in a run.") }; private static readonly ThresholdAchievement[] RunMoneyAchievements = new ThresholdAchievement[10] { new ThresholdAchievement("run_money_125000", 125000L, "Loaded", "Reach 125,000 run currency."), new ThresholdAchievement("run_money_150000", 150000L, "High Roller", "Reach 150,000 run currency."), new ThresholdAchievement("run_money_200000", 200000L, "Cash Current", "Reach 200,000 run currency."), new ThresholdAchievement("run_money_250000", 250000L, "Mega Whale", "Reach 250,000 run currency."), new ThresholdAchievement("run_money_300000", 300000L, "Money Pit Boss", "Reach 300,000 run currency."), new ThresholdAchievement("run_money_400000", 400000L, "Wall of Bills", "Reach 400,000 run currency."), new ThresholdAchievement("run_money_500000", 500000L, "Infinite Wallet", "Reach 500,000 run currency."), new ThresholdAchievement("run_money_750000", 750000L, "Private Bank", "Reach 750,000 run currency."), new ThresholdAchievement("run_money_1000000", 1000000L, "Seven Digits", "Reach 1,000,000 run currency."), new ThresholdAchievement("run_money_2000000", 2000000L, "Money Printer", "Reach 2,000,000 run currency.") }; private static readonly ThresholdAchievement[] RunHaulAchievements = new ThresholdAchievement[7] { new ThresholdAchievement("run_haul_75000", 75000L, "Packed Tight", "Reach 75,000 haul value in a run."), new ThresholdAchievement("run_haul_100000", 100000L, "Full Truck", "Reach 100,000 haul value in a run."), new ThresholdAchievement("run_haul_150000", 150000L, "Heavy Freight", "Reach 150,000 haul value in a run."), new ThresholdAchievement("run_haul_250000", 250000L, "Haul Beast", "Reach 250,000 haul value in a run."), new ThresholdAchievement("run_haul_500000", 500000L, "Warehouse On Legs", "Reach 500,000 haul value in a run."), new ThresholdAchievement("run_haul_750000", 750000L, "Museum Eviction", "Reach 750,000 haul value in a run."), new ThresholdAchievement("run_haul_1000000", 1000000L, "Everything Must Go", "Reach 1,000,000 haul value in a run.") }; private static readonly ThresholdAchievement[] RunLevelAchievements = new ThresholdAchievement[11] { new ThresholdAchievement("run_level_2", 2L, "Second Stop", "Reach level 2 in a run."), new ThresholdAchievement("run_level_3", 3L, "Third Shift", "Reach level 3 in a run."), new ThresholdAchievement("run_level_4", 4L, "Fourth Wind", "Reach level 4 in a run."), new ThresholdAchievement("run_level_5", 5L, "Warm Depths", "Reach level 5 in a run."), new ThresholdAchievement("run_level_6", 6L, "Below Comfortable", "Reach level 6 in a run."), new ThresholdAchievement("run_level_7", 7L, "Further Down", "Reach level 7 in a run."), new ThresholdAchievement("run_level_8", 8L, "Depth Charge", "Reach level 8 in a run."), new ThresholdAchievement("run_level_10", 10L, "Deep Delver", "Reach level 10 in a run."), new ThresholdAchievement("run_level_12", 12L, "Dark Mileage", "Reach level 12 in a run."), new ThresholdAchievement("run_level_15", 15L, "Abyss Dweller", "Reach level 15 in a run."), new ThresholdAchievement("run_level_20", 20L, "No Sun Left", "Reach level 20 in a run.") }; private static readonly ThresholdAchievement[] NoDeathStreakAchievements = new ThresholdAchievement[9] { new ThresholdAchievement("nodeath_streak_2", 2L, "Back-to-Back", "Extract deathless twice in a row."), new ThresholdAchievement("nodeath_streak_3", 3L, "Clean Hands", "Extract deathless 3 times in a row."), new ThresholdAchievement("nodeath_streak_5", 5L, "On Fire", "Extract deathless 5 times in a row."), new ThresholdAchievement("nodeath_streak_7", 7L, "Never Touched", "Extract deathless 7 times in a row."), new ThresholdAchievement("nodeath_streak_10", 10L, "Untouchable++", "Extract deathless 10 times in a row."), new ThresholdAchievement("nodeath_streak_15", 15L, "Ghost Route", "Extract deathless 15 times in a row."), new ThresholdAchievement("nodeath_streak_25", 25L, "Perfect Habit", "Extract deathless 25 times in a row."), new ThresholdAchievement("nodeath_streak_50", 50L, "Deathless Religion", "Extract deathless 50 times in a row."), new ThresholdAchievement("nodeath_streak_100", 100L, "Myth Of Safety", "Extract deathless 100 times in a row.") }; private static readonly SpreeAchievement[] KillSpreeAchievements = new SpreeAchievement[5] { new SpreeAchievement("kill_spree_3_5s", 3, 5f, "Opening Flurry", "Eliminate 3 enemies within 5 seconds."), new SpreeAchievement("kill_spree_5_10s", 5, 10f, "Quick Hands", "Eliminate 5 enemies within 10 seconds."), new SpreeAchievement("kill_spree_7_10s", 7, 10f, "Chain Reaction", "Eliminate 7 enemies within 10 seconds."), new SpreeAchievement("kill_spree_10_15s", 10, 15f, "No Survivors Nearby", "Eliminate 10 enemies within 15 seconds."), new SpreeAchievement("kill_spree_15_20s", 15, 20f, "Screen Wipe", "Eliminate 15 enemies within 20 seconds.") }; private static readonly EnemyTypeInfo[] EnemyTypes = new EnemyTypeInfo[5] { new EnemyTypeInfo("VeryLight", "Very Light", "Snack Extinction"), new EnemyTypeInfo("Light", "Light", "Light's Out"), new EnemyTypeInfo("Medium", "Medium", "Middle Management Axed"), new EnemyTypeInfo("Heavy", "Heavy", "Heavy Industry"), new EnemyTypeInfo("VeryHeavy", "Very Heavy", "Gravitational Collapse") }; [HarmonyPostfix] [HarmonyPatch(typeof(AchievementBook), "DefineDefaults")] private static void PostDefineDefaults() { if (!Defined) { Defined = true; DefineMany(MetaAchievements); DefineMany(ExtractionAchievements); DefineMany(DiscoveryAchievements); DefineMany(LifetimePurchaseAchievements); DefineMany(LifetimeDeathAchievements); DefineMany(LifetimeReviveAchievements); DefineMany(LifetimeHaulAchievements); DefineMany(RunPurchaseAchievements); DefineMany(RunDeathAchievements); DefineMany(RunKillAchievements); DefineMany(LifetimeKillAchievements); DefineMany(ChargeSessionAchievements); DefineMany(ChargeTotalAchievements); DefineMany(RunMoneyAchievements); DefineMany(RunHaulAchievements); DefineMany(RunLevelAchievements); DefineMany(NoDeathStreakAchievements); Define("no_purchase_run", "Barebones", "Extract without buying anything that run."); DefineSpreeAchievements(); DefineEnemyTypeAchievements(); ManualLogSource log = EmpressAchievementsPlugin.Log; if (log != null) { log.LogInfo((object)("[ACH+] Extra definitions loaded: " + AchievementBook.All.Count)); } } } [HarmonyPostfix] [HarmonyPatch(typeof(RoundDirector), "StartRoundRPC")] private static void PostRoundStart() { ResetRunCounter("kills_run"); ResetRunCounter("run_purchases"); KillTimestamps.Clear(); } [HarmonyPostfix] [HarmonyPatch(typeof(ValuableObject), "DiscoverRPC")] private static void PostDiscover() { EmpressAchievementsPlugin.Store.AddToCounter("discoveries", 1L); } [HarmonyPostfix] [HarmonyPatch(typeof(StatsManager), "ItemPurchase")] private static void PostItemPurchase() { EmpressAchievementsPlugin.Store.AddToCounter("run_purchases", 1L); } [HarmonyPostfix] [HarmonyPatch(typeof(ChargingStation), "StartChargeRPC")] private static void PostChargeStart() { EmpressAchievementsPlugin.Store.AddToCounter("charge_sessions", 1L); } [HarmonyPostfix] [HarmonyPatch(typeof(PunManager), "SetRunStatSet")] private static void PostRunStatSet(string statName, int value) { if (string.Equals(statName, "currency", StringComparison.OrdinalIgnoreCase)) { UnlockThresholds(value, RunMoneyAchievements); } else if (string.Equals(statName, "level", StringComparison.OrdinalIgnoreCase)) { UnlockThresholds(value, RunLevelAchievements); } else if (string.Equals(statName, "chargingStationChargeTotal", StringComparison.OrdinalIgnoreCase)) { UnlockThresholds(value, ChargeTotalAchievements); } else if (string.Equals(statName, "totalHaul", StringComparison.OrdinalIgnoreCase)) { UnlockThresholds(value, RunHaulAchievements); } } [HarmonyPostfix] [HarmonyPatch(typeof(RoundDirector), "ExtractionCompleted")] private static void PostExtractionCompleted() { if (EmpressAchievementsPlugin.Store.GetCounter("run_purchases") == 0) { EmpressAchievementsPlugin.Unlock("no_purchase_run"); } if (EmpressAchievementsPlugin.GetStat("run_deaths") == 0) { long value = EmpressAchievementsPlugin.Store.GetCounter("nodeath_streak") + 1; EmpressAchievementsPlugin.Store.SetCounter("nodeath_streak", value); UnlockThresholds(value, NoDeathStreakAchievements); } } [HarmonyPostfix] [HarmonyPatch(typeof(PlayerAvatar), "PlayerDeathRPC")] private static void PostPlayerDeath(PlayerAvatar __instance) { PhotonView val = (((Object)(object)__instance != (Object)null) ? ((Component)__instance).GetComponent() : null); if (!((Object)(object)val != (Object)null) || val.IsMine) { EmpressAchievementsPlugin.Store.SetCounter("nodeath_streak", 0L); } } [HarmonyPostfix] [HarmonyPatch(typeof(AchievementStore), "AddToCounter")] private static void PostAddToCounter(AchievementStore __instance, string key, long delta) { switch (key) { case "extractions": UnlockThresholds(__instance.GetCounter(key), ExtractionAchievements); break; case "discoveries": UnlockThresholds(__instance.GetCounter(key), DiscoveryAchievements); break; case "purchases": UnlockThresholds(__instance.GetCounter(key), LifetimePurchaseAchievements); break; case "deaths": UnlockThresholds(__instance.GetCounter(key), LifetimeDeathAchievements); break; case "revives": UnlockThresholds(__instance.GetCounter(key), LifetimeReviveAchievements); break; case "life_haul": UnlockThresholds(__instance.GetCounter(key), LifetimeHaulAchievements); break; case "run_purchases": UnlockThresholds(__instance.GetCounter(key), RunPurchaseAchievements); break; case "run_deaths": UnlockThresholds(__instance.GetCounter(key), RunDeathAchievements); break; case "kills_run": UnlockThresholds(__instance.GetCounter(key), RunKillAchievements); break; case "kills_total": if (delta > 0) { __instance.AddToCounter("kills_run", delta); RegisterKillTime(Time.unscaledTime); } UnlockThresholds(__instance.GetCounter(key), LifetimeKillAchievements); break; case "charge_sessions": UnlockThresholds(__instance.GetCounter(key), ChargeSessionAchievements); break; default: if (key.StartsWith("kills_type_", StringComparison.OrdinalIgnoreCase)) { UnlockEnemyTypeThresholds(key.Substring("kills_type_".Length), __instance.GetCounter(key)); } break; } } [HarmonyPostfix] [HarmonyPatch(typeof(AchievementStore), "Unlock")] private static void PostUnlock(AchievementStore __instance, string id) { AchievementStore __instance2 = __instance; try { int num = AchievementBook.All.Keys.Count((string k) => __instance2.IsUnlocked(k)); UnlockThresholds(num, MetaAchievements); } catch (Exception ex) { ManualLogSource log = EmpressAchievementsPlugin.Log; if (log != null) { log.LogWarning((object)("[ACH+] Meta unlock check failed: " + ex)); } } } private static void ResetRunCounter(string key) { long counter = EmpressAchievementsPlugin.Store.GetCounter(key); if (counter != 0) { EmpressAchievementsPlugin.Store.AddToCounter(key, -counter); } } private static void RegisterKillTime(float now) { KillTimestamps.Enqueue(now); float num = 0f; SpreeAchievement[] killSpreeAchievements = KillSpreeAchievements; for (int i = 0; i < killSpreeAchievements.Length; i++) { SpreeAchievement spreeAchievement = killSpreeAchievements[i]; if (spreeAchievement.WindowSeconds > num) { num = spreeAchievement.WindowSeconds; } } while (KillTimestamps.Count > 0 && now - KillTimestamps.Peek() > num) { KillTimestamps.Dequeue(); } float[] array = KillTimestamps.ToArray(); SpreeAchievement[] killSpreeAchievements2 = KillSpreeAchievements; for (int j = 0; j < killSpreeAchievements2.Length; j++) { SpreeAchievement spreeAchievement2 = killSpreeAchievements2[j]; int num2 = 0; int num3 = array.Length - 1; while (num3 >= 0 && !(now - array[num3] > spreeAchievement2.WindowSeconds)) { num2++; num3--; } if (num2 >= spreeAchievement2.Count) { EmpressAchievementsPlugin.Unlock(spreeAchievement2.Id); } } } private static void DefineMany(IEnumerable achievements) { foreach (ThresholdAchievement achievement in achievements) { Define(achievement.Id, achievement.Title, achievement.Description); } } private static void DefineSpreeAchievements() { SpreeAchievement[] killSpreeAchievements = KillSpreeAchievements; for (int i = 0; i < killSpreeAchievements.Length; i++) { SpreeAchievement spreeAchievement = killSpreeAchievements[i]; Define(spreeAchievement.Id, spreeAchievement.Title, spreeAchievement.Description); } } private static void DefineEnemyTypeAchievements() { EnemyTypeInfo[] enemyTypes = EnemyTypes; for (int i = 0; i < enemyTypes.Length; i++) { EnemyTypeInfo enemyTypeInfo = enemyTypes[i]; Define("kill_type_" + enemyTypeInfo.Id + "_10", enemyTypeInfo.Label + " Hunter I", "Eliminate 10 " + enemyTypeInfo.Label + " enemies."); Define("kill_type_" + enemyTypeInfo.Id + "_50", enemyTypeInfo.Label + " Hunter II", "Eliminate 50 " + enemyTypeInfo.Label + " enemies."); Define("kill_type_" + enemyTypeInfo.Id + "_100", enemyTypeInfo.HundredTitle, "Eliminate 100 " + enemyTypeInfo.Label + " enemies."); Define("kill_type_" + enemyTypeInfo.Id + "_250", enemyTypeInfo.Label + " Hunter III", "Eliminate 250 " + enemyTypeInfo.Label + " enemies."); Define("kill_type_" + enemyTypeInfo.Id + "_500", enemyTypeInfo.Label + " Hunter IV", "Eliminate 500 " + enemyTypeInfo.Label + " enemies."); } } private static void UnlockEnemyTypeThresholds(string typeId, long value) { if (value >= 10) { EmpressAchievementsPlugin.Unlock("kill_type_" + typeId + "_10"); } if (value >= 50) { EmpressAchievementsPlugin.Unlock("kill_type_" + typeId + "_50"); } if (value >= 100) { EmpressAchievementsPlugin.Unlock("kill_type_" + typeId + "_100"); } if (value >= 250) { EmpressAchievementsPlugin.Unlock("kill_type_" + typeId + "_250"); } if (value >= 500) { EmpressAchievementsPlugin.Unlock("kill_type_" + typeId + "_500"); } } private static void UnlockThresholds(long value, IEnumerable achievements) { foreach (ThresholdAchievement achievement in achievements) { if (value >= achievement.Threshold) { EmpressAchievementsPlugin.Unlock(achievement.Id); } } } private static void Define(string id, string title, string description) { AchievementBook.All[id] = new AchievementDef(id, title, description); } } [BepInPlugin("empress.repo.achievements", "Empress Achievements", "1.3.8")] public class EmpressAchievementsPlugin : BaseUnityPlugin { public const string PluginGuid = "empress.repo.achievements"; public const string PluginName = "Empress Achievements"; public const string PluginVersion = "1.3.8"; private Harmony _harmony; internal static ConfigEntry ToggleUiKey; internal static ConfigEntry EnableSound; internal static ConfigEntry SoundFileName; internal static ConfigEntry SoundVolume; internal static ConfigEntry floatToastSeconds; internal static ConfigEntry UiScalePercent; internal static ConfigEntry DebugLogJsonPreview; internal static ManualLogSource Log; internal static EmpressAchievementsPlugin Instance; internal static EmpressAchievementsRuntime Runtime; internal static ToastUi Toast; internal static AchievementsUi AchievementsPanel; internal static AchievementStore Store; internal static bool TalliedExtractionThisRound = false; internal static float LastLocalDeathTime = -999f; internal static float LastPurchaseTime = -999f; internal static HashSet ProcessedKillHashes = new HashSet(); private static int LastUiUpdateFrame = -1; private void Awake() { //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Expected O, but got Unknown //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; ToggleUiKey = ((BaseUnityPlugin)this).Config.Bind("UI", "ToggleKey", (KeyCode)289, "Key to open/close achievements panel"); UiScalePercent = ((BaseUnityPlugin)this).Config.Bind("UI", "UiScalePercent", 100, "UI scale percentage (100=default)"); floatToastSeconds = ((BaseUnityPlugin)this).Config.Bind("UI", "ToastSeconds", 4f, "Seconds a toast stays visible"); DebugLogJsonPreview = ((BaseUnityPlugin)this).Config.Bind("Debug", "LogJsonPreview", false, "Log first ~300 chars of JSON after save."); EnableSound = ((BaseUnityPlugin)this).Config.Bind("Sound", "EnableSound", true, "Play a sound when an achievement pops"); SoundFileName = ((BaseUnityPlugin)this).Config.Bind("Sound", "SoundFileName", "empress_achievement.ogg", "Audio file placed next to the DLL (wav/ogg/mp3)"); SoundVolume = ((BaseUnityPlugin)this).Config.Bind("Sound", "Volume", 70f, new ConfigDescription("Toast sound volume percent (0-100). Older 0-1 values are auto-converted.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), Array.Empty())); SoundVolume.SettingChanged += delegate { ClampSoundVolumeConfig(); }; ClampSoundVolumeConfig(); string pluginFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? Paths.PluginPath; Store = new AchievementStore(pluginFolder, "empress_achievements.json"); Log.LogInfo((object)("[ACH] Canonical save => " + Store.CanonicalFile)); Store.Load(); DumpFileMap("Boot"); _harmony = new Harmony("empress.repo.achievements"); _harmony.PatchAll(Assembly.GetExecutingAssembly()); EnsureRuntimeHost(); BootstrapUi(); AchievementBook.DefineDefaults(); Log.LogInfo((object)("Empress Achievements v1.3.8 loaded. Achievements: " + AchievementBook.All.Count)); } private static void EnsureRuntimeHost() { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown if ((Object)(object)Runtime != (Object)null) { Runtime.EmpressBoot(); return; } Runtime = Object.FindObjectOfType(); if ((Object)(object)Runtime != (Object)null) { Runtime.EmpressBoot(); return; } GameObject val = new GameObject("Empress_AchievementsRuntime"); val.transform.SetParent((Transform)null); ((Object)val).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)val); Runtime = val.AddComponent(); Runtime.EmpressBoot(); } private static bool InitializeUIComponents() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) try { EnsureRuntimeHost(); GameObject val = (GameObject)(((Object)(object)Runtime != (Object)null) ? ((object)((Component)Runtime).gameObject) : ((object)new GameObject("Empress_AchievementUI_Root"))); if ((Object)(object)Runtime == (Object)null) { Object.DontDestroyOnLoad((Object)(object)val); } Toast = val.GetComponent(); if ((Object)(object)Toast == (Object)null) { Toast = val.AddComponent(); } Toast.Duration = floatToastSeconds.Value; Toast.Volume = GetSoundVolume01(); Toast.EnableSound = EnableSound.Value; string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? Paths.PluginPath; Toast.SoundFilePath = (Path.IsPathRooted(SoundFileName.Value) ? SoundFileName.Value : Path.Combine(path, SoundFileName.Value)); Toast.UiScale = (float)UiScalePercent.Value / 100f; AchievementsPanel = val.GetComponent(); if ((Object)(object)AchievementsPanel == (Object)null) { AchievementsPanel = val.AddComponent(); } AchievementsPanel.UiScale = Toast.UiScale; return true; } catch (Exception ex) { Log.LogError((object)("[ACH] UI init failed: " + ex)); return false; } } private static bool EnsureUiReady() { EnsureRuntimeHost(); bool flag = (Object)(object)Toast != (Object)null && Object.op_Implicit((Object)(object)Toast); bool flag2 = (Object)(object)AchievementsPanel != (Object)null && Object.op_Implicit((Object)(object)AchievementsPanel); if (!flag || !flag2) { Toast = null; AchievementsPanel = null; if (!InitializeUIComponents()) { return false; } } Toast.Duration = floatToastSeconds.Value; Toast.Volume = GetSoundVolume01(); Toast.EnableSound = EnableSound.Value; Toast.UiScale = (float)UiScalePercent.Value / 100f; string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? Paths.PluginPath; Toast.SoundFilePath = (Path.IsPathRooted(SoundFileName.Value) ? SoundFileName.Value : Path.Combine(path, SoundFileName.Value)); AchievementsPanel.UiScale = Toast.UiScale; RequestToastSoundLoad(); return true; } private static void RequestToastSoundLoad() { if (!((Object)(object)Toast == (Object)null) && Toast.NeedsSoundLoad()) { if ((Object)(object)Instance != (Object)null && Object.op_Implicit((Object)(object)Instance)) { ((MonoBehaviour)Instance).StartCoroutine(Toast.LoadSoundCoroutine()); } else if ((Object)(object)Runtime != (Object)null && Object.op_Implicit((Object)(object)Runtime)) { Runtime.StartManagedCoroutine(Toast.LoadSoundCoroutine()); } } } private void OnDestroy() { if (Instance == this) { Instance = null; } try { Store?.Save(); } catch { } } private void OnApplicationQuit() { try { Store?.Save(); } catch { } } private void Update() { RuntimeUpdate(); } internal static void RuntimeUpdate() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) int frameCount = Time.frameCount; if (LastUiUpdateFrame == frameCount) { return; } LastUiUpdateFrame = frameCount; if (EnsureUiReady()) { RequestToastSoundLoad(); if (Input.GetKeyDown(ToggleUiKey.Value) || Input.GetKeyDown((KeyCode)289)) { AchievementsPanel.Toggle(); } float num = (float)UiScalePercent.Value / 100f; if (Mathf.Abs(num - Toast.UiScale) > 0.001f) { Toast.UiScale = num; AchievementsPanel.UiScale = num; Toast.RefreshScale(); AchievementsPanel.RefreshScale(); } } } private static void BootstrapUi() { if (EnsureUiReady()) { AchievementsPanel.Prime(); RequestToastSoundLoad(); } } private static float GetSoundVolume01() { ClampSoundVolumeConfig(); return Mathf.Clamp(SoundVolume.Value, 0f, 100f) / 100f; } private static void ClampSoundVolumeConfig() { if (SoundVolume != null) { float num = SoundVolume.Value; if (num > 0f && num <= 1.01f) { num *= 100f; } float num2 = Mathf.Clamp(num, 0f, 100f); if (Math.Abs(SoundVolume.Value - num2) > 0.001f) { SoundVolume.Value = num2; } } } internal static void DumpFileMap(string tag) { try { string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? Paths.PluginPath; string[] source = new string[3] { Path.Combine(Paths.ConfigPath, "EmpressAchievements", "empress_achievements.json"), Path.Combine(path, "empress_achievements.json"), Path.Combine(Paths.ConfigPath, "empress.repo.achievements.json") }; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)("[ACH][FileMap/" + tag + "] Scanning known locations:")); } foreach (string item in source.Distinct()) { if (File.Exists(item)) { FileInfo fileInfo = new FileInfo(item); ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)$"[ACH][FileMap/{tag}] {item} | size={fileInfo.Length} | modified={fileInfo.LastWriteTimeUtc:O}"); } } else { ManualLogSource log3 = Log; if (log3 != null) { log3.LogInfo((object)("[ACH][FileMap/" + tag + "] (missing) " + item)); } } } } catch (Exception ex) { ManualLogSource log4 = Log; if (log4 != null) { log4.LogWarning((object)("[ACH][FileMap] " + ex)); } } } public static void Pop(string title, string body) { if (EnsureUiReady()) { Toast.Pop(title, body); } } public static void Unlock(string id) { AchievementDef achievementDef = AchievementBook.Get(id); if (achievementDef != null && !Store.IsUnlocked(id)) { Store.Unlock(id); Pop(achievementDef.Title, achievementDef.Description); if ((Object)(object)AchievementsPanel != (Object)null && Object.op_Implicit((Object)(object)AchievementsPanel)) { AchievementsPanel.RefreshAllIfOpen(); } ManualLogSource log = Log; if (log != null) { log.LogInfo((object)("[ACH] Unlocked: " + achievementDef.Id + " - " + achievementDef.Title)); } Store.Save(); } } public static void BumpStat(string key, int delta = 1) { Store.AddToCounter(key, delta); if ((Object)(object)AchievementsPanel != (Object)null && Object.op_Implicit((Object)(object)AchievementsPanel)) { AchievementsPanel.RefreshStatsIfOpen(); } } public static int GetStat(string key) { return (int)Store.GetCounter(key); } internal static string FormatK(long value) { long num = Math.Abs(value); if (num >= 1000) { float num2 = (float)value / 1000f; string text = ((Mathf.Abs(num2 - Mathf.Round(num2)) < 0.05f) ? num2.ToString("0") : num2.ToString("0.#")); return text + "K"; } return value.ToString("N0"); } } internal sealed class EmpressAchievementsRuntime : MonoBehaviour { private bool _booted; internal void EmpressBoot() { if (!_booted) { _booted = true; EmpressAchievementsPlugin.RuntimeUpdate(); } } internal Coroutine StartManagedCoroutine(IEnumerator routine) { return ((MonoBehaviour)this).StartCoroutine(routine); } private void Update() { EmpressAchievementsPlugin.RuntimeUpdate(); } } [Serializable] public class AchievementState { public string Id; public bool Unlocked; public int UnlockedAtUnix; } [Serializable] public class CounterKV { public string Key; public int Value; } [Serializable] public class AchievementsSave { public List Achievements = new List(); public List Counters = new List(); public int SchemaVersion = 2; } internal static class SaveJson { private static string Esc(string s) { if (s == null) { return ""; } StringBuilder stringBuilder = new StringBuilder(s.Length + 8); foreach (char c in s) { switch (c) { case '"': stringBuilder.Append("\\\""); continue; case '\\': stringBuilder.Append("\\\\"); continue; case '\b': stringBuilder.Append("\\b"); continue; case '\f': stringBuilder.Append("\\f"); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '\t': stringBuilder.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder2 = stringBuilder.Append("\\u"); int num = c; stringBuilder2.Append(num.ToString("x4")); } else { stringBuilder.Append(c); } } return stringBuilder.ToString(); } public static string Serialize(AchievementsSave data) { StringBuilder stringBuilder = new StringBuilder(Mathf.Max(1024, (data.Achievements.Count + data.Counters.Count) * 64)); stringBuilder.Append("{\n \"Achievements\": ["); for (int i = 0; i < data.Achievements.Count; i++) { AchievementState achievementState = data.Achievements[i]; if (i > 0) { stringBuilder.Append(','); } stringBuilder.Append("\n {").Append("\"Id\":\"").Append(Esc(achievementState.Id ?? "")) .Append("\",") .Append("\"Unlocked\":") .Append(achievementState.Unlocked ? "true" : "false") .Append(',') .Append("\"UnlockedAtUnix\":") .Append(achievementState.UnlockedAtUnix) .Append('}'); } stringBuilder.Append("\n ],\n \"Counters\": ["); for (int j = 0; j < data.Counters.Count; j++) { CounterKV counterKV = data.Counters[j]; if (j > 0) { stringBuilder.Append(','); } stringBuilder.Append("\n {").Append("\"Key\":\"").Append(Esc(counterKV.Key ?? "")) .Append("\",") .Append("\"Value\":") .Append(counterKV.Value) .Append('}'); } stringBuilder.Append("\n ],\n \"SchemaVersion\": ").Append(data.SchemaVersion).Append("\n}"); return stringBuilder.ToString(); } } internal static class LoadJsonFallback { private static bool ArrayLooksPopulated(string json, string key) { Regex regex = new Regex("\"" + Regex.Escape(key) + "\"\\s*:\\s*\\[\\s*\\{", RegexOptions.Singleline); return regex.IsMatch(json); } private static string ExtractArray(string json, string key) { int num = json.IndexOf("\"" + key + "\"", StringComparison.Ordinal); if (num < 0) { return null; } int num2 = json.IndexOf('[', num); if (num2 < 0) { return null; } int num3 = 0; for (int i = num2; i < json.Length; i++) { switch (json[i]) { case '[': num3++; break; case ']': num3--; if (num3 == 0) { return json.Substring(num2, i - num2 + 1); } break; } } return null; } public static bool JsonIndicatesItems(string json, out bool hasA, out bool hasC) { hasA = ArrayLooksPopulated(json, "Achievements"); hasC = ArrayLooksPopulated(json, "Counters"); return hasA | hasC; } public static bool TryParse(string json, out AchievementsSave save) { save = new AchievementsSave(); try { string text = ExtractArray(json, "Achievements"); string text2 = ExtractArray(json, "Counters"); if (!string.IsNullOrEmpty(text)) { Regex regex = new Regex("\\{\\s*\"Id\"\\s*:\\s*\"(?.*?)\"\\s*,\\s*\"Unlocked\"\\s*:\\s*(?true|false)\\s*,\\s*\"UnlockedAtUnix\"\\s*:\\s*(?-?\\d+)\\s*\\}", RegexOptions.Singleline); foreach (Match item in regex.Matches(text)) { int result; AchievementState achievementState = new AchievementState { Id = item.Groups["id"].Value, Unlocked = (item.Groups["u"].Value == "true"), UnlockedAtUnix = (int.TryParse(item.Groups["t"].Value, out result) ? result : 0) }; if (!string.IsNullOrEmpty(achievementState.Id)) { save.Achievements.Add(achievementState); } } } if (!string.IsNullOrEmpty(text2)) { Regex regex2 = new Regex("\\{\\s*\"Key\"\\s*:\\s*\"(?.*?)\"\\s*,\\s*\"Value\"\\s*:\\s*(?-?\\d+)\\s*\\}", RegexOptions.Singleline); foreach (Match item2 in regex2.Matches(text2)) { int result2; CounterKV counterKV = new CounterKV { Key = item2.Groups["k"].Value, Value = (int.TryParse(item2.Groups["v"].Value, out result2) ? result2 : 0) }; if (!string.IsNullOrEmpty(counterKV.Key)) { save.Counters.Add(counterKV); } } } Regex regex3 = new Regex("\"SchemaVersion\"\\s*:\\s*(?\\d+)"); Match match3 = regex3.Match(json); if (match3.Success && int.TryParse(match3.Groups["s"].Value, out var result3)) { save.SchemaVersion = result3; } return true; } catch { save = new AchievementsSave(); return false; } } } public class AchievementStore { private readonly string _pluginFolder; private readonly string _pluginFile; private AchievementsSave _data = new AchievementsSave(); private readonly Dictionary _counters = new Dictionary(); public string CanonicalFolder => Path.Combine(Paths.ConfigPath, "EmpressAchievements"); public string CanonicalFile => Path.Combine(CanonicalFolder, "empress_achievements.json"); private string PluginOldFile => _pluginFile; private string LegacyConfigFile => Path.Combine(Paths.ConfigPath, "empress.repo.achievements.json"); public AchievementStore(string pluginFolder, string filename) { _pluginFolder = pluginFolder; _pluginFile = Path.Combine(pluginFolder, filename); } public bool IsUnlocked(string id) { string id2 = id; return _data.Achievements.Any((AchievementState a) => a.Id == id2 && a.Unlocked); } public long GetCounter(string key) { long value; return _counters.TryGetValue(key, out value) ? value : 0; } public void AddToCounter(string key, long delta) { if (!_counters.ContainsKey(key)) { _counters[key] = 0L; } _counters[key] += delta; Save(); } public void SetCounter(string key, long value) { _counters[key] = value; Save(); } public void Unlock(string id) { string id2 = id; AchievementState achievementState = _data.Achievements.FirstOrDefault((AchievementState a) => a.Id == id2); if (achievementState == null) { achievementState = new AchievementState { Id = id2 }; _data.Achievements.Add(achievementState); } achievementState.Unlocked = true; long num = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); achievementState.UnlockedAtUnix = (int)((num > int.MaxValue) ? int.MaxValue : num); Save(); } public void Load() { try { EnsureCanonicalDir(); List list = new List(); if (File.Exists(CanonicalFile)) { list.Add(CanonicalFile); } if (File.Exists(PluginOldFile)) { list.Add(PluginOldFile); } if (File.Exists(LegacyConfigFile)) { list.Add(LegacyConfigFile); } string text = ((list.Count > 0) ? list.OrderByDescending(SafeGetWriteTimeUtc).First() : null); if (text == null) { PersistCanonical(SaveJson.Serialize(_data), verify: true, "Fresh"); ManualLogSource log = EmpressAchievementsPlugin.Log; if (log != null) { log.LogInfo((object)"[ACH] Created fresh save."); } return; } string text2 = File.ReadAllText(text, Encoding.UTF8); AchievementsSave achievementsSave = null; bool flag = false; try { if (!string.IsNullOrWhiteSpace(text2)) { achievementsSave = JsonUtility.FromJson(text2); } } catch { achievementsSave = null; } LoadJsonFallback.JsonIndicatesItems(text2, out var hasA, out var hasC); if ((achievementsSave == null || achievementsSave.Achievements == null || achievementsSave.Counters == null || (achievementsSave.Achievements != null && achievementsSave.Achievements.Count == 0 && hasA) || (achievementsSave.Counters != null && achievementsSave.Counters.Count == 0 && hasC)) && LoadJsonFallback.TryParse(text2, out AchievementsSave save)) { achievementsSave = save; flag = true; } if (achievementsSave != null) { _data = achievementsSave; AchievementsSave data = _data; if (data.Achievements == null) { data.Achievements = new List(); } data = _data; if (data.Counters == null) { data.Counters = new List(); } _counters.Clear(); foreach (CounterKV counter in _data.Counters) { if (counter != null && !string.IsNullOrEmpty(counter.Key)) { _counters[counter.Key] = counter.Value; } } bool flag2 = text != CanonicalFile; if (flag2 || flag) { PersistCanonical(SaveJson.Serialize(_data), verify: true, flag2 ? "Migrate" : "Normalize"); } if (text != CanonicalFile) { TryDelete(text); } ManualLogSource log2 = EmpressAchievementsPlugin.Log; if (log2 != null) { log2.LogInfo((object)("[ACH] Loaded save from: " + text)); } } else { _data = new AchievementsSave(); _counters.Clear(); ManualLogSource log3 = EmpressAchievementsPlugin.Log; if (log3 != null) { log3.LogWarning((object)("[ACH] Load parse failed for " + text + ". Keeping file on disk unchanged; in-memory starts fresh.")); } } } catch (Exception ex) { ManualLogSource log4 = EmpressAchievementsPlugin.Log; if (log4 != null) { log4.LogWarning((object)("[ACH] Load exception: " + ex)); } _data = new AchievementsSave(); _counters.Clear(); } } public void Save() { try { AchievementsSave data = _data; if (data.Achievements == null) { data.Achievements = new List(); } _data.Counters = _counters.Select, CounterKV>((KeyValuePair kv) => new CounterKV { Key = kv.Key, Value = (int)((kv.Value > int.MaxValue) ? int.MaxValue : ((kv.Value < int.MinValue) ? int.MinValue : kv.Value)) }).ToList(); if (_data.Achievements.Count == 0 && _data.Counters.Count == 0 && OnDiskLooksPopulated()) { ManualLogSource log = EmpressAchievementsPlugin.Log; if (log != null) { log.LogWarning((object)"[ACH] Save skipped to avoid clobber: memory is empty but disk has items."); } return; } string text = SaveJson.Serialize(_data); PersistCanonical(text, verify: true, "Save"); long num = _data.Achievements.Count; long num2 = _data.Counters.Count; long num3 = SafeGetLength(CanonicalFile); if ((Object)(object)EmpressAchievementsPlugin.Instance != (Object)null && EmpressAchievementsPlugin.DebugLogJsonPreview.Value) { string text2 = ((text.Length > 300) ? (text.Substring(0, 300) + "...") : text); ManualLogSource log2 = EmpressAchievementsPlugin.Log; if (log2 != null) { log2.LogInfo((object)("[ACH] JSON preview: " + text2.Replace("\n", "\\n"))); } } ManualLogSource log3 = EmpressAchievementsPlugin.Log; if (log3 != null) { log3.LogInfo((object)$"[ACH] Saved {num} achievements, {num2} counters -> {CanonicalFile} ({num3} bytes) [verify OK]"); } EmpressAchievementsPlugin.DumpFileMap("AfterSave"); } catch (Exception ex) { ManualLogSource log4 = EmpressAchievementsPlugin.Log; if (log4 != null) { log4.LogWarning((object)("[ACH] Save failed: " + ex)); } } } private bool OnDiskLooksPopulated() { try { if (!File.Exists(CanonicalFile)) { return false; } string json = File.ReadAllText(CanonicalFile, Encoding.UTF8); bool hasA; bool hasC; return LoadJsonFallback.JsonIndicatesItems(json, out hasA, out hasC) && (hasA || hasC); } catch { return false; } } private void EnsureCanonicalDir() { try { Directory.CreateDirectory(CanonicalFolder); } catch { } } private void PersistCanonical(string json, bool verify, string context) { EnsureCanonicalDir(); string text = CanonicalFile + ".tmp"; try { using FileStream fileStream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough); using StreamWriter streamWriter = new StreamWriter(fileStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); streamWriter.Write(json); streamWriter.Flush(); fileStream.Flush(flushToDisk: true); } catch (Exception arg) { ManualLogSource log = EmpressAchievementsPlugin.Log; if (log != null) { log.LogWarning((object)$"[ACH] {context} tmp write failed: {arg}"); } throw; } try { if (File.Exists(CanonicalFile)) { try { File.Replace(text, CanonicalFile, CanonicalFile + ".bak"); } catch { try { File.Delete(CanonicalFile); } catch { } File.Move(text, CanonicalFile); } } else { File.Move(text, CanonicalFile); } } catch (Exception arg2) { ManualLogSource log2 = EmpressAchievementsPlugin.Log; if (log2 != null) { log2.LogWarning((object)$"[ACH] {context} replace failed: {arg2}"); } throw; } if (!verify) { return; } try { string text2 = File.ReadAllText(CanonicalFile, Encoding.UTF8); if (!string.Equals(text2, json, StringComparison.Ordinal)) { string text3 = ((text2.Length > 300) ? (text2.Substring(0, 300) + "...") : text2); ManualLogSource log3 = EmpressAchievementsPlugin.Log; if (log3 != null) { log3.LogWarning((object)$"[ACH] VERIFY STRING MISMATCH after {context}: lenWritten={json.Length}, lenRead={text2.Length}"); } ManualLogSource log4 = EmpressAchievementsPlugin.Log; if (log4 != null) { log4.LogWarning((object)("[ACH] On-disk head: " + text3.Replace("\n", "\\n"))); } } } catch (Exception arg3) { ManualLogSource log5 = EmpressAchievementsPlugin.Log; if (log5 != null) { log5.LogWarning((object)$"[ACH] Verify read failed after {context}: {arg3}"); } } } private static DateTime SafeGetWriteTimeUtc(string path) { try { return File.GetLastWriteTimeUtc(path); } catch { return DateTime.MinValue; } } private static long SafeGetLength(string path) { try { return new FileInfo(path).Length; } catch { return 0L; } } private static void TryDelete(string path) { try { if (File.Exists(path)) { File.Delete(path); } } catch { } } } public class AchievementDef { public string Id; public string Title; public string Description; public AchievementDef(string id, string title, string description) { Id = id; Title = title; Description = description; } } public static class AchievementBook { public static readonly Dictionary All = new Dictionary(); public static AchievementDef Get(string id) { AchievementDef value; return All.TryGetValue(id, out value) ? value : null; } public static IEnumerable AllOrdered() { return All.Values.OrderBy((AchievementDef d) => d.Title); } public static void DefineDefaults() { All.Clear(); Add("first_extract", "First Extraction", "Complete any extraction."); Add("extract_5", "Commuter", "Complete 5 extractions (lifetime)."); Add("extract_20", "Frequent Flyer", "Complete 20 extractions (lifetime)."); Add("extract_50", "Shuttle Regular", "Complete 50 extractions (lifetime)."); Add("extract_100", "Season Pass Holder", "Complete 100 extractions (lifetime)."); Add("extract_200", "Lives Here Now", "Complete 200 extractions (lifetime)."); Add("run_money_10000", "Cash Flash", "Reach 10,000 run currency."); Add("run_money_25000", "Bankroller", "Reach 25,000 run currency."); Add("run_money_50000", "Whale", "Reach 50,000 run currency."); Add("run_money_100000", "Making It Rain", "Reach 100,000 run currency."); Add("run_haul_10000", "Hauler", "Reach 10,000 haul value in a run."); Add("run_haul_25000", "Mover", "Reach 25,000 haul value in a run."); Add("run_haul_50000", "Freight Lord", "Reach 50,000 haul value in a run."); Add("life_haul_50k", "Thrifty Tycoon", "Accumulate 50,000 haul (lifetime)."); Add("life_haul_100k", "Millionaire-ish", "Accumulate 100,000 haul (lifetime)."); Add("life_haul_250k", "Quarter Mil", "Accumulate 250,000 haul (lifetime)."); Add("life_haul_500k", "Half Ticket", "Accumulate 500,000 haul (lifetime)."); Add("life_haul_1m", "Seven Figures", "Accumulate 1,000,000 haul (lifetime)."); Add("life_haul_2m", "Eight-ish", "Accumulate 2,000,000 haul (lifetime)."); Add("life_haul_5m", "Big Bags", "Accumulate 5,000,000 haul (lifetime)."); Add("life_haul_10m", "Dragon Hoard", "Accumulate 10,000,000 haul (lifetime)."); Add("life_haul_20m", "Vault Dweller", "Accumulate 20,000,000 haul (lifetime)."); Add("life_haul_50m", "Scrooge McDuck", "Accumulate 50,000,000 haul (lifetime)."); Add("first_purchase", "Window Shopper No More", "Purchase your first item."); Add("big_spender_10", "Bulk Buyer", "Purchase 10 items (lifetime)."); Add("big_spender_25", "Cart Commander", "Purchase 25 items (lifetime)."); Add("big_spender_50", "Mall Rat", "Purchase 50 items (lifetime)."); Add("big_spender_100", "Retail Therapy", "Purchase 100 items (lifetime)."); Add("first_death", "Oops", "Die for the first time."); Add("death_5", "Oops, I Did It Again", "Die 5 times."); Add("death_10", "Try, Try Again", "Die 10 times."); Add("death_20", "Professional Victim", "Die 20 times."); Add("death_30", "Speedrun to the Afterlife", "Die 30 times."); Add("death_40", "Grave Connoisseur", "Die 40 times."); Add("death_50", "Certified Floor Tester", "Die 50 times."); Add("death_100", "Immortal (In Theory)", "Die 100 times."); Add("death_200", "Groundhog Day", "Die 200 times."); Add("death_500", "The Floor Is My Home", "Die 500 times."); Add("revived", "Back On Your Feet", "Get revived."); Add("revived_10", "Serial Resurrectee", "Be revived 10 times."); Add("revived_50", "Phoenix Program", "Be revived 50 times."); Add("nodeath_run", "Untouchable", "Complete an extraction without dying."); Add("first_discover", "Treasure Hunter", "Discover your first valuable."); Add("kills_10", "First Bloodbath", "Eliminate 10 enemies."); Add("kills_50", "Pest Control", "Eliminate 50 enemies."); Add("kills_100", "Exterminator", "Eliminate 100 enemies."); Add("kills_250", "Reaper Intern", "Eliminate 250 enemies."); Add("kills_500", "Reaper Associate", "Eliminate 500 enemies."); Add("kills_1000", "Full-Time Reaper", "Eliminate 1,000 enemies."); Add("kills_2000", "Warfare Dept.", "Eliminate 2,000 enemies."); Add("kills_5000", "Population Control", "Eliminate 5,000 enemies."); Add("kill_type_VeryLight", "Light Snack", "Eliminate a Very Light enemy."); Add("kill_type_Light", "Featherweight Fighter", "Eliminate a Light enemy."); Add("kill_type_Medium", "Middle Management", "Eliminate a Medium enemy."); Add("kill_type_Heavy", "Heavy Metal", "Eliminate a Heavy enemy."); Add("kill_type_VeryHeavy", "Heaviest Hand", "Eliminate a Very Heavy enemy."); Add("kill_type_VeryLight_25", "Snack Attack", "Eliminate 25 Very Light enemies."); Add("kill_type_Light_25", "Light Work", "Eliminate 25 Light enemies."); Add("kill_type_Medium_25", "Middle Manager", "Eliminate 25 Medium enemies."); Add("kill_type_Heavy_25", "Heavy Lifter", "Eliminate 25 Heavy enemies."); Add("kill_type_VeryHeavy_25", "Atlas", "Eliminate 25 Very Heavy enemies."); } private static void Add(string id, string title, string desc) { All[id] = new AchievementDef(id, title, desc); } } internal class ToastMessage { public string Title; public string Body; public ToastMessage(string t, string b) { Title = t; Body = b; } } public class ToastUi : MonoBehaviour { public float Duration = 3f; public float UiScale = 1f; public bool EnableSound = true; public string SoundFilePath; public float Volume = 0.7f; private Canvas _canvas; private RectTransform _root; private AudioSource _audio; private AudioClip _clip; private readonly Queue _queue = new Queue(); private bool _busy; private bool _soundLoadRequested; private string _lastSoundFilePath; public void RefreshScale() { //IL_0016: 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) if (Object.op_Implicit((Object)(object)_root)) { ((Transform)_root).localScale = Vector3.one * UiScale; } } private void Ensure() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)_canvas)) { EmpressAchievementsPlugin.Log.LogInfo((object)"[ACH] Creating Toast Canvas..."); _canvas = new GameObject("Empress_AchievementToastCanvas").AddComponent(); Object.DontDestroyOnLoad((Object)(object)_canvas); _canvas.renderMode = (RenderMode)0; _canvas.sortingOrder = 32760; CanvasScaler val = ((Component)_canvas).gameObject.AddComponent(); val.uiScaleMode = (ScaleMode)1; val.referenceResolution = new Vector2(1920f, 1080f); val.screenMatchMode = (ScreenMatchMode)0; val.matchWidthOrHeight = 0.5f; ((Component)_canvas).gameObject.AddComponent(); _root = new GameObject("ToastRoot").AddComponent(); ((Transform)_root).SetParent(((Component)_canvas).transform, false); _root.anchorMin = new Vector2(0.5f, 0f); _root.anchorMax = new Vector2(0.5f, 0f); _root.pivot = new Vector2(0.5f, 0f); _root.anchoredPosition = new Vector2(0f, 80f); ((Transform)_root).localScale = Vector3.one * UiScale; _audio = ((Component)_canvas).gameObject.AddComponent(); _audio.playOnAwake = false; _audio.spatialBlend = 0f; _audio.loop = false; _audio.ignoreListenerPause = true; _audio.volume = 1f; EmpressAchievementsPlugin.Log.LogInfo((object)"[ACH] Toast Canvas ready"); } } public IEnumerator LoadSoundCoroutine() { Ensure(); _clip = null; _soundLoadRequested = true; _lastSoundFilePath = SoundFilePath; if (!EnableSound) { yield break; } if (string.IsNullOrEmpty(SoundFilePath) || !File.Exists(SoundFilePath)) { EmpressAchievementsPlugin.Log.LogInfo((object)("[ACH] No sound file at: " + (SoundFilePath ?? "null"))); _soundLoadRequested = false; yield break; } EmpressAchievementsPlugin.Log.LogInfo((object)("[ACH] Loading sound from: " + SoundFilePath)); string uri = "file://" + SoundFilePath.Replace("\\", "/"); UnityWebRequest req = UnityWebRequestMultimedia.GetAudioClip(uri, GetAudioType(SoundFilePath)); yield return req.SendWebRequest(); if (!req.isNetworkError && !req.isHttpError) { _clip = DownloadHandlerAudioClip.GetContent(req); if ((Object)(object)_clip != (Object)null && !_clip.preloadAudioData) { _clip.LoadAudioData(); } ManualLogSource log = EmpressAchievementsPlugin.Log; if (log != null) { log.LogInfo((object)"[ACH] Sound ready"); } } else { ManualLogSource log2 = EmpressAchievementsPlugin.Log; if (log2 != null) { log2.LogWarning((object)("[ACH] Audio load failed: " + req.error)); } _soundLoadRequested = false; } req.Dispose(); } public bool NeedsSoundLoad() { if (!EnableSound) { return false; } if ((Object)(object)_clip != (Object)null) { return false; } if (!string.Equals(_lastSoundFilePath, SoundFilePath, StringComparison.OrdinalIgnoreCase)) { _soundLoadRequested = false; } return !_soundLoadRequested; } private static AudioType GetAudioType(string path) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0054: 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) return (AudioType)(Path.GetExtension(path)?.ToLowerInvariant() switch { ".wav" => 20, ".ogg" => 14, ".mp3" => 13, _ => 20, }); } public void Pop(string title, string body) { EmpressAchievementsPlugin.Log.LogInfo((object)("[ACH] Toast: " + title)); Ensure(); _queue.Enqueue(new ToastMessage(title, body)); if (!_busy) { if ((Object)(object)EmpressAchievementsPlugin.Runtime != (Object)null && Object.op_Implicit((Object)(object)EmpressAchievementsPlugin.Runtime)) { EmpressAchievementsPlugin.Runtime.StartManagedCoroutine(Run()); } else { ((MonoBehaviour)this).StartCoroutine(Run()); } } } private IEnumerator Run() { _busy = true; while (_queue.Count > 0) { ToastMessage msg = _queue.Dequeue(); yield return ShowToast(msg.Title, msg.Body); } _busy = false; } private IEnumerator ShowToast(string title, string body) { GameObject go = new GameObject("Toast"); RectTransform rt = go.AddComponent(); ((Transform)rt).SetParent((Transform)(object)_root, false); rt.sizeDelta = new Vector2(520f, 100f); Image bg = go.AddComponent(); ((Graphic)bg).color = new Color(0.05f, 0.05f, 0.05f, 0.9f); ((Graphic)bg).raycastTarget = false; Outline outline = go.AddComponent(); ((Shadow)outline).effectDistance = new Vector2(2f, -2f); ((Shadow)outline).effectColor = new Color(0.9f, 0.7f, 0.2f, 0.85f); GameObject titleGO = new GameObject("Title"); RectTransform titleRT = titleGO.AddComponent(); ((Transform)titleRT).SetParent((Transform)(object)rt, false); titleRT.anchorMin = new Vector2(0f, 0.5f); titleRT.anchorMax = new Vector2(1f, 1f); titleRT.offsetMin = new Vector2(15f, -5f); titleRT.offsetMax = new Vector2(-15f, -5f); TextMeshProUGUI titleTMP = titleGO.AddComponent(); ((TMP_Text)titleTMP).text = title; ((TMP_Text)titleTMP).fontSize = 24f; ((Graphic)titleTMP).color = new Color(1f, 0.93f, 0.6f); ((TMP_Text)titleTMP).alignment = (TextAlignmentOptions)4097; ((TMP_Text)titleTMP).enableWordWrapping = true; ((TMP_Text)titleTMP).overflowMode = (TextOverflowModes)1; ((TMP_Text)titleTMP).fontStyle = (FontStyles)1; ((Graphic)titleTMP).raycastTarget = false; GameObject bodyGO = new GameObject("Body"); RectTransform bodyRT = bodyGO.AddComponent(); ((Transform)bodyRT).SetParent((Transform)(object)rt, false); bodyRT.anchorMin = new Vector2(0f, 0f); bodyRT.anchorMax = new Vector2(1f, 0.5f); bodyRT.offsetMin = new Vector2(15f, 5f); bodyRT.offsetMax = new Vector2(-15f, 5f); TextMeshProUGUI bodyTMP = bodyGO.AddComponent(); ((TMP_Text)bodyTMP).text = body; ((TMP_Text)bodyTMP).fontSize = 16f; ((Graphic)bodyTMP).color = new Color(0.9f, 0.9f, 0.9f); ((TMP_Text)bodyTMP).alignment = (TextAlignmentOptions)4097; ((TMP_Text)bodyTMP).enableWordWrapping = true; ((TMP_Text)bodyTMP).overflowMode = (TextOverflowModes)1; ((Graphic)bodyTMP).raycastTarget = false; float usableWidth = rt.sizeDelta.x - 30f; Vector2 titlePref = ((TMP_Text)titleTMP).GetPreferredValues(((TMP_Text)titleTMP).text, usableWidth, 0f); Vector2 bodyPref = ((TMP_Text)bodyTMP).GetPreferredValues(((TMP_Text)bodyTMP).text, usableWidth, 0f); float newHeight = Mathf.Max(90f, titlePref.y + bodyPref.y + 28f); rt.sizeDelta = new Vector2(rt.sizeDelta.x, newHeight); float t2 = 0f; Vector2 start = new Vector2(0f, -150f); Vector2 mid = new Vector2(0f, 0f); Vector2 end = new Vector2(0f, -150f); rt.anchoredPosition = start; CanvasGroup cg = go.AddComponent(); cg.alpha = 0f; if (EnableSound && (Object)(object)_clip != (Object)null && (Object)(object)_audio != (Object)null) { _audio.Stop(); _audio.PlayOneShot(_clip, Mathf.Clamp01(Volume)); } float inDur = 0.25f; while (t2 < inDur) { t2 += Time.unscaledDeltaTime; float p = t2 / inDur; float e = 1f - (1f - p) * (1f - p); cg.alpha = p; rt.anchoredPosition = Vector2.Lerp(start, mid, e); yield return null; } cg.alpha = 1f; rt.anchoredPosition = mid; yield return (object)new WaitForSecondsRealtime(Mathf.Max(1f, Duration)); t2 = 0f; float outDur = 0.25f; while (t2 < outDur) { t2 += Time.unscaledDeltaTime; float p2 = t2 / outDur; float e2 = p2 * p2; cg.alpha = 1f - p2; rt.anchoredPosition = Vector2.Lerp(mid, end, e2); yield return null; } Object.Destroy((Object)(object)go); } } public class AchievementsUi : MonoBehaviour { private enum AchievementListMode { All, Unlocked, Locked } private sealed class FilterTab { public AchievementListMode Mode { get; } public Image Background { get; } public TextMeshProUGUI Label { get; } public FilterTab(AchievementListMode mode, Image background, TextMeshProUGUI label) { Mode = mode; Background = background; Label = label; } } public float UiScale = 1f; private Canvas _canvas; private RectTransform _root; private GameObject _panel; private RectTransform _listRoot; private ScrollRect _scroll; private RectTransform _scrollRT; private RectTransform _scrollbarRT; private Scrollbar _scrollbar; private TextMeshProUGUI _statsLabel; private RectTransform _statsRT; private RectTransform _headerRT; private RectTransform _tabsRT; private bool _open; private float _nextStatsTick; private readonly List _tabs = new List(); private AchievementListMode _listMode = AchievementListMode.All; private bool _cursorCaptured; private bool _previousCursorVisible; private CursorLockMode _previousCursorLockState; private Texture2D _empressCursorTexture; private const float HeaderHeight = 66f; private const float TabsHeight = 44f; public void RefreshScale() { //IL_0016: 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) if (Object.op_Implicit((Object)(object)_root)) { ((Transform)_root).localScale = Vector3.one * UiScale; } if ((Object)(object)_panel != (Object)null && _panel.activeSelf) { RefreshList(); RecomputeTopLayout(); Canvas.ForceUpdateCanvases(); } } private void Ensure() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)_canvas)) { EmpressAchievementsPlugin.Log.LogInfo((object)"[ACH] Building Achievements Panel...]"); _canvas = new GameObject("Empress_AchievementPanelCanvas").AddComponent(); Object.DontDestroyOnLoad((Object)(object)_canvas); _canvas.renderMode = (RenderMode)0; _canvas.sortingOrder = 32700; CanvasScaler val = ((Component)_canvas).gameObject.AddComponent(); val.uiScaleMode = (ScaleMode)1; val.referenceResolution = new Vector2(1920f, 1080f); val.screenMatchMode = (ScreenMatchMode)0; val.matchWidthOrHeight = 0.5f; ((Component)_canvas).gameObject.AddComponent(); _root = new GameObject("PanelRoot").AddComponent(); ((Transform)_root).SetParent(((Component)_canvas).transform, false); _root.anchorMin = new Vector2(0.5f, 0.5f); _root.anchorMax = new Vector2(0.5f, 0.5f); _root.pivot = new Vector2(0.5f, 0.5f); _root.sizeDelta = new Vector2(1000f, 700f); ((Transform)_root).localScale = Vector3.one * UiScale; _panel = BuildPanel(_root); _panel.SetActive(false); } } public void Toggle() { Ensure(); _open = !_open; _panel.SetActive(_open); if (_open) { ApplyUiCursor(); RefreshList(resetScroll: true); UpdateStatsLabel(); RecomputeTopLayout(); _nextStatsTick = Time.unscaledTime + 0.5f; Canvas.ForceUpdateCanvases(); } else { RestoreCursorState(); } } public void Prime() { Ensure(); } public void RefreshAllIfOpen() { if ((Object)(object)_panel != (Object)null && _panel.activeSelf) { UpdateStatsLabel(); RecomputeTopLayout(); RefreshList(); } } public void RefreshStatsIfOpen() { if ((Object)(object)_panel != (Object)null && _panel.activeSelf) { UpdateStatsLabel(); RecomputeTopLayout(); } } public void RefreshList(bool resetScroll = false) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown Ensure(); if ((Object)(object)_panel == (Object)null) { return; } foreach (Transform item in (Transform)_listRoot) { Transform val = item; Object.Destroy((Object)(object)((Component)val).gameObject); } foreach (AchievementDef item2 in AchievementBook.AllOrdered()) { bool unlocked = EmpressAchievementsPlugin.Store.IsUnlocked(item2.Id); if (ShouldShowAchievement(unlocked)) { BuildEntry(_listRoot, item2, unlocked); } } LayoutRebuilder.ForceRebuildLayoutImmediate(_listRoot); RefreshTabVisuals(); if (resetScroll && (Object)(object)_scroll != (Object)null) { Canvas.ForceUpdateCanvases(); _scroll.verticalNormalizedPosition = 1f; } } private void Update() { if ((Object)(object)_panel == (Object)null || !_panel.activeSelf) { if (_cursorCaptured) { RestoreCursorState(); } return; } ApplyUiCursor(); ApplyUiControlLock(); if (Time.unscaledTime >= _nextStatsTick) { UpdateStatsLabel(); RecomputeTopLayout(); _nextStatsTick = Time.unscaledTime + 0.5f; } } private void UpdateStatsLabel() { if (!((Object)(object)_statsLabel == (Object)null)) { int count = AchievementBook.All.Count; int num = AchievementBook.All.Values.Count((AchievementDef def) => EmpressAchievementsPlugin.Store.IsUnlocked(def.Id)); int num2 = AchievementBook.All.Values.Count((AchievementDef def) => ShouldShowAchievement(EmpressAchievementsPlugin.Store.IsUnlocked(def.Id))); int stat = EmpressAchievementsPlugin.GetStat("extractions"); int stat2 = EmpressAchievementsPlugin.GetStat("purchases"); int stat3 = EmpressAchievementsPlugin.GetStat("deaths"); int stat4 = EmpressAchievementsPlugin.GetStat("revives"); long counter = EmpressAchievementsPlugin.Store.GetCounter("life_haul"); long counter2 = EmpressAchievementsPlugin.Store.GetCounter("kills_total"); string text = "$" + EmpressAchievementsPlugin.FormatK(counter); ((TMP_Text)_statsLabel).text = $"Showing {num2} | Unlocked {num}/{count}\n" + $"Extractions {stat} | Purchases {stat2} | Deaths {stat3} | Revives {stat4} | Lifetime Haul {text} | Kills {counter2}"; } } private GameObject BuildPanel(RectTransform parent) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00de: 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_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Expected O, but got Unknown //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02fe: Unknown result type (might be due to invalid IL or missing references) //IL_030b: Unknown result type (might be due to invalid IL or missing references) //IL_0322: Unknown result type (might be due to invalid IL or missing references) //IL_0339: Unknown result type (might be due to invalid IL or missing references) //IL_0395: Unknown result type (might be due to invalid IL or missing references) //IL_03b9: Unknown result type (might be due to invalid IL or missing references) //IL_03c0: Expected O, but got Unknown //IL_03eb: Unknown result type (might be due to invalid IL or missing references) //IL_0406: Unknown result type (might be due to invalid IL or missing references) //IL_0421: Unknown result type (might be due to invalid IL or missing references) //IL_043c: Unknown result type (might be due to invalid IL or missing references) //IL_0457: Unknown result type (might be due to invalid IL or missing references) //IL_0481: Unknown result type (might be due to invalid IL or missing references) //IL_04d7: Unknown result type (might be due to invalid IL or missing references) //IL_04e1: Expected O, but got Unknown //IL_0554: Unknown result type (might be due to invalid IL or missing references) //IL_055b: Expected O, but got Unknown //IL_0586: Unknown result type (might be due to invalid IL or missing references) //IL_05a1: Unknown result type (might be due to invalid IL or missing references) //IL_05bc: Unknown result type (might be due to invalid IL or missing references) //IL_05d7: Unknown result type (might be due to invalid IL or missing references) //IL_0601: Unknown result type (might be due to invalid IL or missing references) //IL_0617: Unknown result type (might be due to invalid IL or missing references) //IL_068a: Unknown result type (might be due to invalid IL or missing references) //IL_0691: Expected O, but got Unknown //IL_06c2: Unknown result type (might be due to invalid IL or missing references) //IL_06dd: Unknown result type (might be due to invalid IL or missing references) //IL_06f8: Unknown result type (might be due to invalid IL or missing references) //IL_0709: Unknown result type (might be due to invalid IL or missing references) //IL_071a: Unknown result type (might be due to invalid IL or missing references) //IL_072b: Unknown result type (might be due to invalid IL or missing references) //IL_077a: Unknown result type (might be due to invalid IL or missing references) //IL_0784: Expected O, but got Unknown //IL_07c0: Unknown result type (might be due to invalid IL or missing references) //IL_07c7: Expected O, but got Unknown //IL_07f2: Unknown result type (might be due to invalid IL or missing references) //IL_080d: Unknown result type (might be due to invalid IL or missing references) //IL_0828: Unknown result type (might be due to invalid IL or missing references) //IL_0843: Unknown result type (might be due to invalid IL or missing references) //IL_086d: Unknown result type (might be due to invalid IL or missing references) //IL_0897: Unknown result type (might be due to invalid IL or missing references) //IL_089e: Expected O, but got Unknown //IL_08b8: Unknown result type (might be due to invalid IL or missing references) //IL_08c5: Unknown result type (might be due to invalid IL or missing references) //IL_08dc: Unknown result type (might be due to invalid IL or missing references) //IL_08f3: Unknown result type (might be due to invalid IL or missing references) //IL_0903: Unknown result type (might be due to invalid IL or missing references) //IL_090a: Expected O, but got Unknown //IL_092a: Unknown result type (might be due to invalid IL or missing references) //IL_0941: Unknown result type (might be due to invalid IL or missing references) //IL_094e: Unknown result type (might be due to invalid IL or missing references) //IL_095b: Unknown result type (might be due to invalid IL or missing references) //IL_0985: Unknown result type (might be due to invalid IL or missing references) //IL_09c3: Unknown result type (might be due to invalid IL or missing references) //IL_09ca: Expected O, but got Unknown //IL_09e9: Unknown result type (might be due to invalid IL or missing references) //IL_0a00: Unknown result type (might be due to invalid IL or missing references) //IL_0a17: Unknown result type (might be due to invalid IL or missing references) //IL_0a2e: Unknown result type (might be due to invalid IL or missing references) //IL_0a45: Unknown result type (might be due to invalid IL or missing references) //IL_0a6f: Unknown result type (might be due to invalid IL or missing references) //IL_0a91: Unknown result type (might be due to invalid IL or missing references) //IL_0a9b: Expected O, but got Unknown //IL_0aa1: Unknown result type (might be due to invalid IL or missing references) //IL_0aa8: Expected O, but got Unknown //IL_0ac3: Unknown result type (might be due to invalid IL or missing references) //IL_0ad0: Unknown result type (might be due to invalid IL or missing references) //IL_0add: Unknown result type (might be due to invalid IL or missing references) //IL_0b1a: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Panel"); RectTransform val2 = val.AddComponent(); ((Transform)val2).SetParent((Transform)(object)parent, false); val2.sizeDelta = parent.sizeDelta; Image val3 = val.AddComponent(); ((Graphic)val3).color = new Color(0.02f, 0.02f, 0.02f, 0.9f); ((Graphic)val3).raycastTarget = true; GameObject val4 = new GameObject("Header"); _headerRT = val4.AddComponent(); ((Transform)_headerRT).SetParent((Transform)(object)val2, false); _headerRT.anchorMin = new Vector2(0f, 1f); _headerRT.anchorMax = new Vector2(1f, 1f); _headerRT.pivot = new Vector2(0.5f, 1f); _headerRT.anchoredPosition = new Vector2(0f, -10f); _headerRT.sizeDelta = new Vector2(-20f, 66f); Image val5 = val4.AddComponent(); ((Graphic)val5).color = new Color(0.24f, 0.11f, 0.03f, 0.98f); TextMeshProUGUI val6 = new GameObject("Title").AddComponent(); RectTransform rectTransform = ((TMP_Text)val6).rectTransform; ((Transform)rectTransform).SetParent((Transform)(object)_headerRT, false); rectTransform.anchorMin = Vector2.zero; rectTransform.anchorMax = Vector2.one; rectTransform.offsetMin = new Vector2(20f, 0f); rectTransform.offsetMax = new Vector2(-70f, 0f); ((TMP_Text)val6).text = "EMPRESS Achievements"; ((TMP_Text)val6).fontSize = 36f; ((Graphic)val6).color = new Color(1f, 0.92f, 0.6f); ((TMP_Text)val6).alignment = (TextAlignmentOptions)4097; ((TMP_Text)val6).fontStyle = (FontStyles)1; ((Graphic)val6).raycastTarget = false; GameObject val7 = new GameObject("Stats"); _statsRT = val7.AddComponent(); ((Transform)_statsRT).SetParent((Transform)(object)val2, false); _statsRT.anchorMin = new Vector2(0f, 1f); _statsRT.anchorMax = new Vector2(1f, 1f); _statsRT.pivot = new Vector2(0.5f, 1f); _statsRT.anchoredPosition = new Vector2(0f, -82f); _statsRT.sizeDelta = new Vector2(-20f, 92f); Image val8 = val7.AddComponent(); ((Graphic)val8).color = new Color(0.08f, 0.08f, 0.08f, 0.92f); _statsLabel = new GameObject("StatsText").AddComponent(); RectTransform rectTransform2 = ((TMP_Text)_statsLabel).rectTransform; ((Transform)rectTransform2).SetParent((Transform)(object)_statsRT, false); rectTransform2.anchorMin = Vector2.zero; rectTransform2.anchorMax = Vector2.one; rectTransform2.offsetMin = new Vector2(16f, 12f); rectTransform2.offsetMax = new Vector2(-16f, -12f); ((TMP_Text)_statsLabel).fontSize = 18f; ((TMP_Text)_statsLabel).alignment = (TextAlignmentOptions)257; ((TMP_Text)_statsLabel).enableWordWrapping = true; ((TMP_Text)_statsLabel).overflowMode = (TextOverflowModes)0; ((Graphic)_statsLabel).color = new Color(0.85f, 0.85f, 0.85f); ((Graphic)_statsLabel).raycastTarget = false; UpdateStatsLabel(); GameObject val9 = new GameObject("Tabs"); _tabsRT = val9.AddComponent(); ((Transform)_tabsRT).SetParent((Transform)(object)val2, false); _tabsRT.anchorMin = new Vector2(0f, 1f); _tabsRT.anchorMax = new Vector2(1f, 1f); _tabsRT.pivot = new Vector2(0.5f, 1f); _tabsRT.anchoredPosition = new Vector2(0f, -170f); _tabsRT.sizeDelta = new Vector2(-20f, 44f); Image val10 = val9.AddComponent(); ((Graphic)val10).color = new Color(0.06f, 0.06f, 0.06f, 0.92f); HorizontalLayoutGroup val11 = val9.AddComponent(); ((LayoutGroup)val11).childAlignment = (TextAnchor)3; ((HorizontalOrVerticalLayoutGroup)val11).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)val11).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)val11).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)val11).childForceExpandHeight = true; ((HorizontalOrVerticalLayoutGroup)val11).spacing = 10f; ((LayoutGroup)val11).padding = new RectOffset(10, 10, 6, 6); _tabs.Clear(); _tabs.Add(BuildTabButton(_tabsRT, "ALL", AchievementListMode.All)); _tabs.Add(BuildTabButton(_tabsRT, "UNLOCKED", AchievementListMode.Unlocked)); _tabs.Add(BuildTabButton(_tabsRT, "LOCKED", AchievementListMode.Locked)); RefreshTabVisuals(); GameObject val12 = new GameObject("ScrollViewport"); _scrollRT = val12.AddComponent(); ((Transform)_scrollRT).SetParent((Transform)(object)val2, false); _scrollRT.anchorMin = new Vector2(0f, 0f); _scrollRT.anchorMax = new Vector2(1f, 1f); _scrollRT.offsetMin = new Vector2(20f, 20f); _scrollRT.offsetMax = new Vector2(-52f, -220f); Image val13 = val12.AddComponent(); ((Graphic)val13).color = new Color(0.08f, 0.08f, 0.08f, 0.96f); RectMask2D val14 = val12.AddComponent(); val14.padding = Vector4.zero; _scroll = val12.AddComponent(); _scroll.horizontal = false; _scroll.vertical = true; _scroll.movementType = (MovementType)2; _scroll.scrollSensitivity = 32f; _scroll.verticalScrollbarSpacing = 10f; _scroll.verticalScrollbarVisibility = (ScrollbarVisibility)0; GameObject val15 = new GameObject("Content"); _listRoot = val15.AddComponent(); ((Transform)_listRoot).SetParent(val12.transform, false); _listRoot.anchorMin = new Vector2(0f, 1f); _listRoot.anchorMax = new Vector2(1f, 1f); _listRoot.pivot = new Vector2(0.5f, 1f); _listRoot.offsetMin = Vector2.zero; _listRoot.offsetMax = Vector2.zero; _listRoot.anchoredPosition = Vector2.zero; VerticalLayoutGroup val16 = val15.AddComponent(); ((HorizontalOrVerticalLayoutGroup)val16).childForceExpandHeight = false; ((HorizontalOrVerticalLayoutGroup)val16).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)val16).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)val16).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)val16).spacing = 8f; ((LayoutGroup)val16).padding = new RectOffset(12, 12, 12, 12); ContentSizeFitter val17 = val15.AddComponent(); val17.verticalFit = (FitMode)2; _scroll.content = _listRoot; _scroll.viewport = _scrollRT; GameObject val18 = new GameObject("Scrollbar"); _scrollbarRT = val18.AddComponent(); ((Transform)_scrollbarRT).SetParent((Transform)(object)val2, false); _scrollbarRT.anchorMin = new Vector2(1f, 0f); _scrollbarRT.anchorMax = new Vector2(1f, 1f); _scrollbarRT.offsetMin = new Vector2(-36f, 20f); _scrollbarRT.offsetMax = new Vector2(-18f, -220f); Image val19 = val18.AddComponent(); ((Graphic)val19).color = new Color(0.23f, 0.12f, 0.05f, 0.95f); _scrollbar = val18.AddComponent(); _scrollbar.direction = (Direction)2; GameObject val20 = new GameObject("SlidingArea"); RectTransform val21 = val20.AddComponent(); ((Transform)val21).SetParent((Transform)(object)_scrollbarRT, false); val21.anchorMin = Vector2.zero; val21.anchorMax = Vector2.one; val21.offsetMin = new Vector2(3f, 3f); val21.offsetMax = new Vector2(-3f, -3f); GameObject val22 = new GameObject("Handle"); RectTransform val23 = val22.AddComponent(); ((Transform)val23).SetParent((Transform)(object)val21, false); val23.anchorMin = new Vector2(0f, 0f); val23.anchorMax = new Vector2(1f, 1f); val23.offsetMin = Vector2.zero; val23.offsetMax = Vector2.zero; Image val24 = val22.AddComponent(); ((Graphic)val24).color = new Color(1f, 0.62f, 0.25f, 0.98f); ((Selectable)_scrollbar).targetGraphic = (Graphic)(object)val24; _scrollbar.handleRect = val23; _scroll.verticalScrollbar = _scrollbar; GameObject val25 = new GameObject("CloseButton"); RectTransform val26 = val25.AddComponent(); ((Transform)val26).SetParent((Transform)(object)val2, false); val26.anchorMin = new Vector2(1f, 1f); val26.anchorMax = new Vector2(1f, 1f); val26.pivot = new Vector2(1f, 1f); val26.anchoredPosition = new Vector2(-18f, -18f); val26.sizeDelta = new Vector2(42f, 42f); Image val27 = val25.AddComponent(); ((Graphic)val27).color = new Color(0.75f, 0.18f, 0.14f, 0.95f); Button val28 = val25.AddComponent