using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using Dawn; using Dusk; using GameNetcodeStuff; using HarmonyLib; using LethalConstellations.ConfigManager; using LethalConstellations.EventStuff; using LethalConstellations.PluginCore; using LethalLevelLoader; using LethalMoonUnlocks.Compatibility; using LethalMoonUnlocks.Patches; using LethalMoonUnlocks.Util; using LethalNetworkAPI; using LethalQuantities; using LethalQuantities.Json; using LethalQuantities.Objects; using Microsoft.CodeAnalysis; using OpenLib.Events; using TerminalStuff.Configs; using TerminalStuff.MoonsTweaks; using Unity.Netcode; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; using UnityEngine.UI; using WeatherTweaks; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: IgnoresAccessChecksTo("AmazingAssets.TerrainToMesh")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp-firstpass")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: IgnoresAccessChecksTo("ClientNetworkTransform")] [assembly: IgnoresAccessChecksTo("com.github.teamxiaolan.dawnlib")] [assembly: IgnoresAccessChecksTo("com.github.teamxiaolan.dawnlib.dusk")] [assembly: IgnoresAccessChecksTo("DissonanceVoip")] [assembly: IgnoresAccessChecksTo("Facepunch Transport for Netcode for GameObjects")] [assembly: IgnoresAccessChecksTo("Facepunch.Steamworks.Win64")] [assembly: IgnoresAccessChecksTo("Unity.AI.Navigation")] [assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging")] [assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging.DocCodeExamples")] [assembly: IgnoresAccessChecksTo("Unity.Burst")] [assembly: IgnoresAccessChecksTo("Unity.Burst.Unsafe")] [assembly: IgnoresAccessChecksTo("Unity.Collections")] [assembly: IgnoresAccessChecksTo("Unity.Collections.LowLevel.ILSupport")] [assembly: IgnoresAccessChecksTo("Unity.InputSystem")] [assembly: IgnoresAccessChecksTo("Unity.InputSystem.ForUI")] [assembly: IgnoresAccessChecksTo("Unity.Jobs")] [assembly: IgnoresAccessChecksTo("Unity.Mathematics")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.Common")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.MetricTypes")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStats")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Component")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Configuration")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Implementation")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsReporting")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkProfiler.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkSolutionInterface")] [assembly: IgnoresAccessChecksTo("Unity.Netcode.Components")] [assembly: IgnoresAccessChecksTo("Unity.Netcode.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.Networking.Transport")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Csg")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.KdTree")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Poly2Tri")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Stl")] [assembly: IgnoresAccessChecksTo("Unity.Profiling.Core")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.ShaderLibrary")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Config.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary")] [assembly: IgnoresAccessChecksTo("Unity.Services.Authentication")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Analytics")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Configuration")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Device")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments.Internal")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Internal")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Networking")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Registration")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Scheduler")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Telemetry")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Threading")] [assembly: IgnoresAccessChecksTo("Unity.Services.QoS")] [assembly: IgnoresAccessChecksTo("Unity.Services.Relay")] [assembly: IgnoresAccessChecksTo("Unity.TextMeshPro")] [assembly: IgnoresAccessChecksTo("Unity.Timeline")] [assembly: IgnoresAccessChecksTo("Unity.VisualEffectGraph.Runtime")] [assembly: IgnoresAccessChecksTo("UnityEngine.ARModule")] [assembly: IgnoresAccessChecksTo("UnityEngine.NVIDIAModule")] [assembly: IgnoresAccessChecksTo("UnityEngine.UI")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LethalMoonUnlocks")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyDescription("Permanently unlock moons and more")] [assembly: AssemblyFileVersion("2.4.7.0")] [assembly: AssemblyInformationalVersion("2.4.7+15518d5397de3ecfe32b2d513ca5bdaebb6e1e3a")] [assembly: AssemblyProduct("LethalMoonUnlocks")] [assembly: AssemblyTitle("LethalMoonUnlocks")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.4.7.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 LethalMoonUnlocks { internal enum ResetWhenFiredBehavior { All, AllButStoryProgression, Nothing } internal enum StoryReleaseBehavior { HiddenBacklog, ImmediateDiscovery } public class ConfigManager { private static ConfigFile _configFile; private static int _quotaUnlockCountMin; private static int _quotaUnlockCountMax; private static int _quotaDiscountCountMin; private static int _quotaDiscountCountMax; private static int _quotaFullDiscountCountMin; private static int _quotaFullDiscountCountMax; private const string LCStartingConstellationSelectionPolicyCheapest = "Cheapest"; private const string LCStartingConstellationSelectionPolicyRandom = "Random"; private const string LCDiscoveryTargetModeMoonsOnly = "MoonsOnly"; private const string LCDiscoveryTargetModeMoonsAndConstellations = "MoonsAndConstellations"; private const string LCDiscoveryTargetModeConstellationsOnly = "ConstellationsOnly"; private const string LCDiscoveryTargetModeConstellationsOnlyWithMoonFallback = "ConstellationsOnlyWithMoonFallback"; private const string LCQuotaRewardScopeAllDiscoveredConstellations = "AllDiscoveredConstellations"; private const string LCQuotaRewardScopeCurrentOnly = "CurrentConstellationOnly"; private static int _quotaDiscoveryCountMin; private static int _quotaDiscoveryCountMax; private static int _travelDiscoveryCountMin; private static int _travelDiscoveryCountMax; private static int _newDayDiscoveryCountMin; private static int _newDayDiscoveryCountMax; private static int _salesRateMin; private static int _salesRateMax; internal static ResetWhenFiredBehavior ResetWhenFired { get; private set; } internal static bool DisplayTerminalTags { get; private set; } internal static bool ShowTagInOrbit { get; private set; } internal static bool ShowTagNewDiscovery { get; private set; } internal static bool ShowTagExplored { get; private set; } internal static bool ShowTagUnlockDiscount { get; private set; } internal static bool ShowTagPermanentDiscovery { get; private set; } internal static bool ShowTagSale { get; private set; } internal static bool ShowTagGroups { get; private set; } internal static bool ShowAlerts { get; private set; } internal static bool ChatMessages { get; private set; } internal static bool UnlockMode { get; private set; } internal static int UnlocksResetAfterVisits { get; private set; } internal static bool UnlocksResetAfterVisitsPermDiscovery { get; private set; } internal static bool QuotaUnlocks { get; private set; } internal static int QuotaUnlockChance { get; private set; } internal static int QuotaUnlockCount => RandomHelper.Range(_quotaUnlockCountMin, _quotaUnlockCountMax + 1); internal static int QuotaUnlockMaxCount { get; private set; } internal static int QuotaUnlockMaxPrice { get; private set; } internal static bool DiscountMode { get; private set; } private static string DiscountsString { get; set; } internal static List Discounts { get { string[] array = DiscountsString.Split(',', StringSplitOptions.RemoveEmptyEntries); List list = new List(); string[] array2 = array; foreach (string s in array2) { list.Add(int.Parse(s)); } return list; } } internal static int DiscountsCount => Discounts.Count(); internal static int DiscountsResetAfterVisits { get; private set; } internal static bool DiscountsResetAfterVisitsPermDiscovery { get; private set; } internal static bool QuotaDiscounts { get; private set; } internal static int QuotaDiscountChance { get; private set; } internal static int QuotaDiscountCount => RandomHelper.Range(_quotaDiscountCountMin, _quotaDiscountCountMax + 1); internal static int QuotaDiscountMaxCount { get; private set; } internal static int QuotaDiscountMaxPrice { get; private set; } internal static bool QuotaFullDiscounts { get; private set; } internal static int QuotaFullDiscountChance { get; private set; } internal static int QuotaFullDiscountCount => RandomHelper.Range(_quotaFullDiscountCountMin, _quotaFullDiscountCountMax + 1); internal static int QuotaFullDiscountMaxCount { get; private set; } internal static int QuotaFullDiscountMaxPrice { get; private set; } public static bool DiscoveryMode { get; private set; } private static string DiscoveryWhitelist { get; set; } internal static List DiscoveryWhitelistMoons => ParseCommaList(DiscoveryWhitelist); private static string LethalConstellationsWhitelistString { get; set; } internal static List LethalConstellationsWhitelist => ParseCommaList(LethalConstellationsWhitelistString); private static string AcceptableStartingConstellationsString { get; set; } internal static List AcceptableStartingConstellations => ParseCommaList(AcceptableStartingConstellationsString); internal static string LCStartingConstellationSelectionPolicy { get; private set; } internal static StoryReleaseBehavior LCStoryReleaseBehavior { get; private set; } internal static string LethalConstellationsQuotaDiscoveryTargetMode { get; private set; } internal static int LethalConstellationsQuotaDiscoveryChance { get; private set; } internal static string LethalConstellationsTravelDiscoveryTargetMode { get; private set; } internal static int LethalConstellationsTravelDiscoveryChance { get; private set; } internal static string LethalConstellationsNewDayDiscoveryTargetMode { get; private set; } internal static int LethalConstellationsNewDayDiscoveryChance { get; private set; } internal static bool DiscoveryKeepUnlocks { get; private set; } internal static bool DiscoveryKeepDiscounts { get; private set; } internal static int DiscoveryFreeCountBase { get; private set; } internal static int DiscoveryFreeCountIncreaseBy { get; private set; } internal static int DiscoveryDynamicFreeCountBase { get; private set; } internal static int DiscoveryDynamicFreeCountIncreaseBy { get; private set; } internal static int DiscoveryPaidCountBase { get; private set; } internal static int DiscoveryPaidCountIncreaseBy { get; private set; } internal static int PermanentlyDiscoverFreeMoonsOnLanding { get; private set; } internal static int PermanentlyDiscoverPaidMoonsOnLanding { get; private set; } internal static bool PermanentlyDiscoverHiddenMoonsOnVisit { get; private set; } internal static bool DiscoveryShuffleEveryDay { get; private set; } internal static bool DiscoveryNeverShuffle { get; private set; } internal static bool QuotaDiscoveries { get; private set; } internal static int QuotaDiscoveryChance { get; private set; } internal static int QuotaDiscoveryCount => RandomHelper.Range(_quotaDiscoveryCountMin, _quotaDiscoveryCountMax + 1); internal static bool QuotaDiscoveryPermanent { get; private set; } internal static bool QuotaDiscoveryCheapestGroup { get; private set; } internal static bool QuotaDiscoveryCheapestGroupFallback { get; private set; } internal static bool QuotaDiscoveryCheapestConstellation { get; private set; } internal static bool TravelDiscoveries { get; private set; } internal static int TravelDiscoveryChance { get; private set; } internal static int TravelDiscoveryCount => RandomHelper.Range(_travelDiscoveryCountMin, _travelDiscoveryCountMax + 1); internal static bool TravelDiscoveryPermanent { get; private set; } internal static bool TravelDiscoveryMatchGroup { get; private set; } internal static bool TravelDiscoveryMatchGroupFallback { get; private set; } internal static bool NewDayDiscoveries { get; private set; } internal static int NewDayDiscoveryChance { get; private set; } internal static int NewDayDiscoveryCount => RandomHelper.Range(_newDayDiscoveryCountMin, _newDayDiscoveryCountMax + 1); internal static bool NewDayDiscoveryPermanent { get; private set; } internal static bool NewDayDiscoveryMatchGroup { get; private set; } internal static bool NewDayDiscoveryMatchGroupFallback { get; private set; } internal static bool Sales { get; private set; } internal static int SalesChance { get; private set; } internal static bool SalesShuffleDaily { get; private set; } internal static int SalesMinDayCount { get; private set; } internal static int SalesRate => RandomHelper.Range(_salesRateMin, _salesRateMax); private static bool AdvancedPrintMoonNames { get; set; } internal static bool AutoRerouteToCompany { get; set; } internal static bool GroupCreditsSavingBandAid { get; private set; } internal static bool EnableStoryProgression { get; private set; } internal static StoryReleaseBehavior MoonStoryReleaseBehavior { get; private set; } internal static bool LMUStoryProgression { get; private set; } internal static bool GaletryStoryLock { get; private set; } internal static int GaletryStoryLockPaintingsAmount { get; private set; } internal static bool CheapMoonBiasIgnorePriceChanges { get; private set; } internal static bool CheapMoonBiasPaidRotation { get; private set; } internal static float CheapMoonBiasPaidRotationValue { get; private set; } internal static bool CheapMoonBiasQuotaDiscovery { get; private set; } internal static float CheapMoonBiasQuotaDiscoveryValue { get; private set; } internal static bool CheapMoonBiasNewDayDiscovery { get; private set; } internal static float CheapMoonBiasNewDayDiscoveryValue { get; private set; } internal static bool CheapMoonBiasTravelDiscovery { get; private set; } internal static float CheapMoonBiasTravelDiscoveryValue { get; private set; } internal static bool CheapMoonBiasQuotaUnlock { get; private set; } internal static float CheapMoonBiasQuotaUnlockValue { get; private set; } internal static bool CheapMoonBiasQuotaDiscount { get; private set; } internal static float CheapMoonBiasQuotaDiscountValue { get; private set; } internal static bool CheapMoonBiasQuotaFullDiscount { get; private set; } internal static float CheapMoonBiasQuotaFullDiscountValue { get; private set; } internal static string MoonGroupMatchingMethod { get; private set; } internal static int MoonGroupMatchingPriceRange { get; private set; } private static string MoonGroupMatchingCustom { get; set; } internal static Dictionary> MoonGroupMatchingCustomDict { get; private set; } internal static int TerminalTagLineWidth { get; set; } internal static bool TerminalFontSizeOverride { get; set; } internal static float TerminalFontSize { get; set; } internal static int TerminalScrollAmount { get; set; } internal static bool TerminalShowRiskWeather { get; set; } internal static bool PreferLQRisk { get; private set; } internal static bool MalfunctionsNavigation { get; private set; } internal static bool AlertMessageQueueing { get; private set; } internal static HashSet AlertMessageQueueExcludedPlugins { get; private set; } = new HashSet(StringComparer.OrdinalIgnoreCase); public static bool LethalConstellationsOverridePrice { get; private set; } internal static bool LethalConstellationsMirrorDefaultMoonRoute { get; private set; } internal static string LethalConstellationsQuotaRewardScope { get; private set; } internal static bool PreferGaletry { get; private set; } internal static bool OverrideHidden { get; private set; } private static string OverrideHiddenList { get; set; } internal static List OverrideHiddenListMoons => ParseCommaList(OverrideHiddenList); internal static bool OverrideLocked { get; private set; } private static string OverrideLockedList { get; set; } internal static List OverrideLockedListMoons => ParseCommaList(OverrideLockedList); internal static void Initialize(ConfigFile cfg) { string text = Path.Combine(Paths.ConfigPath, "LethalMoonUnlocks.cfg"); if (File.Exists(text)) { Logger.LogWarning("Legacy config file found. Migrating to default config.."); MigrateLegacyConfig(text, cfg); } else { _configFile = cfg; } } internal static void RefreshConfig() { Logger.LogInfo("Refreshing config.."); if (_configFile != null && File.Exists(_configFile.ConfigFilePath)) { _configFile.Reload(); } RefreshValues(); MoonGroupMatchingCustomDict = ParseCustomMoonGroups(); } internal static Dictionary> ParseCustomMoonGroups() { if (AdvancedPrintMoonNames) { Logger.LogWarning("Printing available moon names for custom moon groups.."); Logger.LogWarning(string.Join(", ", from level in PatchedContent.ExtendedLevels where level.NumberlessPlanetName != "Gordion" && level.NumberlessPlanetName != "Liquidation" select ((Object)level).name) ?? ""); } Dictionary> dictionary = new Dictionary>(); if (MoonGroupMatchingCustom == string.Empty) { Logger.LogInfo("No custom moon group defined. Skip parsing.."); return dictionary; } string[] array = MoonGroupMatchingCustom.Split('|'); string[] array2 = array; foreach (string text in array2) { string text2 = text.Split(":").First().Trim(); string text3 = text.Split(':').Last().Trim(); string[] array3 = text3.Split(","); List list = new List(); string[] array4 = array3; foreach (string text4 in array4) { list.Add(text4.Trim()); } if (text2 == null || text2 == string.Empty) { Logger.LogWarning("Couldn't parse custom moon group name. Make sure you're using the correct format!"); } else if (text3 == string.Empty || list.Count == 0) { Logger.LogWarning("Couldn't parse custom moon group! Name null or empty or members not found!"); } else { dictionary[text2] = list; } } Logger.LogInfo("Parsing custom moon groups:"); foreach (KeyValuePair> item in dictionary) { Logger.LogInfo(item.Key + ": [" + string.Join(", ", item.Value) + "]"); } return dictionary; } private static void RefreshValues() { //IL_0e6e: Unknown result type (might be due to invalid IL or missing references) //IL_0e78: Expected O, but got Unknown ResetWhenFired = BindValue("1 - General settings", "Reset when fired", ResetWhenFiredBehavior.All, "Controls what LMU resets when the crew gets fired.\n'All' wipes all LMU progression.\n'AllButStoryProgression' wipes all LMU progression except unlocked story locks.\n'Nothing' keeps LMU progression (per save)."); ChatMessages = BindValue("1 - General settings", "Show chat messages", defaultValue: true, "When enabled, LethalMoonUnlocks will send messages to the in-game chat whenever something relevant happens."); ShowAlerts = BindValue("1 - General settings", "Show alert messages", defaultValue: false, "When enabled, LethalMoonUnlocks will display alert messages whenever something relevant happens."); DisplayTerminalTags = BindValue("1.1 - Terminal moon tags", "Display tags in terminal", defaultValue: false, "When enabled, LethalMoonUnlocks will display additional tags in the Terminal moon catalog.\nThese tags will indicate various conditions, such as a moon being unlocked or discounted, being on sale, etc.\nIf custom moon groups or matching by LLL tag are enabled, you'll also see the custom groups or LLL tags a moon is associated with.\nNOTE: At this time additional tags will only show in the standard LLL moon catalog and TerminalFormatter. Any other mod replacing the 'moons' command will probably cause issues."); ShowTagInOrbit = BindValue("1.1 - Terminal moon tags", "In orbit tag", defaultValue: true, "Display a tag to indicate the moon you're currently orbiting."); ShowTagExplored = BindValue("1.1 - Terminal moon tags", "Exploration tag", defaultValue: true, "Display a tag to indicate which moons have not been landed on yet. After landing once, it will keep track of how many times you've landed in total."); ShowTagUnlockDiscount = BindValue("1.1 - Terminal moon tags", "Unlock discount tag", defaultValue: true, "Display a tag to indicate Unlocks and Discounts as well as how many routes are left before they expire."); ShowTagNewDiscovery = BindValue("1.1 - Terminal moon tags", "New discovery tag", defaultValue: true, "Discovery Mode only: display a tag to indicate which moons are new discoveries i.e. available in the moon catalog for the first time. The tag will vanish when you route to the moon or the moon catalog is shuffled."); ShowTagPermanentDiscovery = BindValue("1.1 - Terminal moon tags", "Permanent discovery tag", defaultValue: true, "Discovery Mode only: display a tag to indicate permanently discovered moons.\nDisplays as [PINNED]."); ShowTagSale = BindValue("1.1 - Terminal moon tags", "Sales tag", defaultValue: true, "Moon Sales only: display a tag to indicate which moons are on sale, as well as the percentage of the sale."); ShowTagGroups = BindValue("1.1 - Terminal moon tags", "Group tag", defaultValue: true, "Moon Group Matching only: display a tag to indicate groups a moon belongs to. Limited to custom group and LLL tag matching methods."); UnlockMode = BindValue("2 - Unlock Mode (Default)", "Enable Unlock Mode", defaultValue: true, "Unlock Mode is the default mode, akin to the original Permanent Moons mod. In Unlock Mode, when you buy a paid moon, it will be 'unlocked'.\nOnce unlocked, moons are completely free, and by default, will stay free permanently.\nNOTE: This setting and all settings relating to Unlocks will have no effect if Discount Mode is enabled!"); UnlocksResetAfterVisits = BindValue("2 - Unlock Mode (Default)", "Unlocks expire", 0, "Unlocks will expire after a set number of free routes, after which they will become paid again.\nSet to 0 to disable this feature."); DiscoveryKeepUnlocks = BindValue("2 - Unlock Mode (Default)", "Unlocked moons are permanently discovered", defaultValue: false, "Discovery Mode only: Every unlocked moon is also permanently discovered i.e. added to the moon catalog on top of your base selection."); UnlocksResetAfterVisitsPermDiscovery = BindValue("2 - Unlock Mode (Default)", "Reset permanent discovery on unlock expiry", defaultValue: false, "Discovery Mode only: Reset a moon's permanent discovery status when its unlock expires.\nThis is the only way permanent discoveries can vanish during a run in Unlock Mode.\n"); QuotaUnlocks = BindValue("2.1 - Quota Unlocks", "Enable Quota Unlocks", defaultValue: false, "Quota Unlocks are rewarded for meeting the quota. When triggered, Quota Unlocks will grant you one or more unlocks for free.\nThe moons that are unlocked are randomly selected."); QuotaUnlockChance = BindValue("2.1 - Quota Unlocks", "Quota Unlock trigger chance", 100, "The chance to trigger a Quota Unlock every time you meet the quota.", new AcceptableValueRange(0, 100)); _quotaUnlockCountMin = BindValue("2.1 - Quota Unlocks", "Minimum unlocked moon count", 1, "The minimum number of moons that will be unlocked each time a Quota Unlock is triggered.", new AcceptableValueRange(1, 10)); _quotaUnlockCountMax = BindValue("2.1 - Quota Unlocks", "Maximum unlocked moon count", 1, "The maximum number of moons that will be unlocked each time a Quota Unlock is triggered.", new AcceptableValueRange(1, 10)); QuotaUnlockMaxPrice = BindValue("2.1 - Quota Unlocks", "Maximum moon price to unlock", 0, "Only consider moons up to this price to be unlocked.\nSet to 0 to disable this feature."); QuotaUnlockMaxCount = BindValue("2.1 - Quota Unlocks", "Limit number of unlocks", 0, "Limit how many Quota Unlocks you can receive during a run. After reaching the limit, Quota Unlocks will no longer be granted.\nSet to 0 to disable this feature."); DiscountMode = BindValue("3 - Discount Mode", "Enable Discount Mode", defaultValue: false, "In Discount Mode, Unlocks are replaced with Discounts.\nEach time you route to a paid moon, you will unlock the next available discount rate until the final discount is reached.\nThe discount rates are fully customizable."); DiscountsString = BindValue("3 - Discount Mode", "Discount rates", "50,75,100", "The discount rates that are applied to moon prices as a % off of the original routing price.\nFor example, '50,75,100', would make each moon 50% off after the first purchase, 75% off after the second purchase, and free after the third purchase.\nDiscount rates are separated by commas and can contain any number of rates"); Logger.LogInfo("Discount rates (% off): " + string.Join(", ", Discounts.Select((int discount) => discount + "%"))); DiscountsResetAfterVisits = BindValue("3 - Discount Mode", "Discounts expire", 0, "Discounts will expire after a set number of free routes, after which they will return to their original price.\nSet to 0 to disable this feature.\nNOTE: The final discount rate must be set to '100' for this to work!"); DiscoveryKeepDiscounts = BindValue("3 - Discount Mode", "Discounted moons are permanently discovered", defaultValue: false, "Discovery Mode only: Every discounted moon is also permanently discovered i.e. added to the moon catalog on top of your base selection."); DiscountsResetAfterVisitsPermDiscovery = BindValue("3 - Discount Mode", "Reset permanent discoveries on discount expiry", defaultValue: false, "Discovery Mode only: Reset a moon's permanent discovery status when its discount expires.\nThis is the only way permanent discoveries can vanish during a run in Discount Mode.\n"); QuotaDiscounts = BindValue("3.1 - Quota Discounts", "Enable Quota Discounts", defaultValue: false, "Quota Discounts are rewarded for meeting the quota. When triggered Quota Discounts will grant you one or more discounts for free.\nThe moons that are discounted are randomly selected."); QuotaDiscountChance = BindValue("3.1 - Quota Discounts", "Quota Discount trigger chance", 100, "The chance to trigger a Quota Discount every time you meet the quota.\n", new AcceptableValueRange(0, 100)); _quotaDiscountCountMin = BindValue("3.1 - Quota Discounts", "Minimum discounted moon count", 1, "The minimum number of moons that will receive a discount each time a Quota Discount is triggered.", new AcceptableValueRange(1, 10)); _quotaDiscountCountMax = BindValue("3.1 - Quota Discounts", "Maximum discounted moon count", 1, "The maximum number of moons that will receive a discount each time a Quota Discount is triggered.", new AcceptableValueRange(1, 10)); QuotaDiscountMaxPrice = BindValue("3.1 - Quota Discounts", "Maximum moon price to discount", 0, "Only consider moons up to this price to receive a discount.\nSet to 0 to disable this feature"); QuotaDiscountMaxCount = BindValue("3.1 - Quota Discounts", "Limit number of discounts", 0, "Limit how many Quota Discounts you can receive during a run. After reaching the limit, Quota Discounts will no longer be granted.\nSet to 0 to disable this feature"); QuotaFullDiscounts = BindValue("3.2 - Quota Full Discounts", "Enable Quota Full Discounts", defaultValue: false, "Quota Full Discounts are rewarded for meeting the quota. When triggered, Quota Full Discounts will apply the final discount rate to one or more moons for free.\nThe moons that are discounted are randomly selected."); QuotaFullDiscountChance = BindValue("3.2 - Quota Full Discounts", "Quota Full Discount trigger chance", 100, "The chance to trigger a Quota Full Discount every time you meet the quota.", new AcceptableValueRange(0, 100)); _quotaFullDiscountCountMin = BindValue("3.2 - Quota Full Discounts", "Minimum fully discounted moon count", 1, "The minimum number of moons that will receive a full discount each time a Quota Full Discount is triggered.", new AcceptableValueRange(1, 10)); _quotaFullDiscountCountMax = BindValue("3.2 - Quota Full Discounts", "Maximum fully discounted moon count", 1, "The maximum number of moons that will receive a full discount each time a Quota Full Discount is triggered.", new AcceptableValueRange(1, 10)); QuotaFullDiscountMaxPrice = BindValue("3.2 - Quota Full Discounts", "Maximum moon price to fully discount", 0, "Only consider moons up to this price to receive a full discount.\nSet to 0 to disable this feature"); QuotaFullDiscountMaxCount = BindValue("3.2 - Quota Full Discounts", "Limit number of full discounts", 0, "Limit how many Quota Full Discounts you can receive during a run. After reaching the limit, Quota Full Discounts will no longer be granted.\nSet to 0 to disable this feature"); DiscoveryMode = BindValue("4 - Discovery Mode", "Enable Discovery Mode", defaultValue: false, "In Discovery Mode, you start with a limited selection of moons in the Terminal's moon catalog.\nBy default, this base selection of moons will be shuffled after every quota, and can also be configured to expand over time.\nThere are also various options to discover additional moons as you play.\nPermanently discovered moons are added to the moon catalog on top of the base selection, and are not lost on shuffle."); DiscoveryNeverShuffle = BindValue("4 - Discovery Mode", "Never shuffle", defaultValue: false, "Never shuffle the rotation of moons available in the moon catalog.\nNew moons must be discovered through other means, but once discovered, they won't vanish, since the selection is never shuffled.\nNOTE: Overrides the 'Shuffle every day' option."); DiscoveryShuffleEveryDay = BindValue("4 - Discovery Mode", "Shuffle every day", defaultValue: false, "Shuffle the rotation of moons available in the moon catalog every day, instead of after every quota."); DiscoveryWhitelist = BindValue("4 - Discovery Mode", "Whitelist", "", "List of moons to keep discovered at all times.\nFor example, 'Experimentation, Assurance, Vow' would make these three moons start out as permanently discovered on every run.\nMoon names must be separated by commas and must be exact matches. You can print the moon names to console/log by using the option in 'Advanced Settings'."); DiscoveryFreeCountBase = BindValue("4 - Discovery Mode", "Free moons base count", 1, "The base amount of randomly selected free moons available in the moon catalog.\nNOTE: 'Free' only considers moons that are free by default, or configured to be free. Moons that are free due to unlocks or discounts are excluded!"); DiscoveryDynamicFreeCountBase = BindValue("4 - Discovery Mode", "Dynamic free moons base count", 2, "The base amount of randomly selected dynamic free moons available in the moon catalog.\nNOTE: 'Dynamic free' considers moons that are free due to unlocks or discounts in addition to those that are free by default, or configured to be free."); DiscoveryPaidCountBase = BindValue("4 - Discovery Mode", "Paid moons base count", 3, "The base amount of randomly selected paid moons available in the moon catalog.\nThis is your paid moon rotation and typically the main way to discover new moons to buy - earning unlocks and discounts as you progress."); DiscoveryFreeCountIncreaseBy = BindValue("4 - Discovery Mode", "Increase free moon count on shuffle", 0, "The amount of randomly selected free moons added to the rotation each time it's shuffled.\nSet to 0 to disable this feature."); DiscoveryDynamicFreeCountIncreaseBy = BindValue("4 - Discovery Mode", "Increase dynamic free moon count on shuffle by", 0, "The amount of randomly selected dynamic free moons added to the rotation each time it's shuffled.\nSet to 0 to disable this feature."); DiscoveryPaidCountIncreaseBy = BindValue("4 - Discovery Mode", "Increase paid moon count on shuffle", 0, "The amount of randomly selected paid moons added to the rotation each time it's shuffled.\nSet to 0 to disable this feature."); PermanentlyDiscoverFreeMoonsOnLanding = BindValue("4 - Discovery Mode", "Landings required to permanently discover free moons", -1, "Any free moon will be permanently discovered after a set amount of landings.\nSet to -1 to disable this feature.\nNOTE: A value of 0 makes every free moon ever discovered in any way permanently discovered. Not recommended."); PermanentlyDiscoverPaidMoonsOnLanding = BindValue("4 - Discovery Mode", "Landings required to permanently discover paid moons", -1, "Any free moon will be permanently discovered after a set amount of landings.\nSet to -1 to disable this feature.\nNOTE: A value of 0 makes every paid moon ever discovered in any way permanently discovered. Not recommended."); PermanentlyDiscoverHiddenMoonsOnVisit = BindValue("4 - Discovery Mode", "Permanently discover hidden moons after routing", defaultValue: false, "Any hidden (LLL config e.g. Embrion) will be permanently discovered after routed to once."); QuotaDiscoveries = BindValue("4.1 - Quota Discoveries", "Enable Quota Discoveries", defaultValue: false, "Quota Discoveries grant additional moon discoveries when a new quota begins.\nThe moons that are discovered are randomly selected."); QuotaDiscoveryChance = BindValue("4.1 - Quota Discoveries", "Quota Discovery trigger chance", 100, "The chance to trigger a Quota Discovery every time you meet the quota.", new AcceptableValueRange(0, 100)); _quotaDiscoveryCountMin = BindValue("4.1 - Quota Discoveries", "Minimum quota discovery moon count", 1, "The minimum number of moons that will be discovered each time a Quota Discovery is triggered.", new AcceptableValueRange(1, 10)); _quotaDiscoveryCountMax = BindValue("4.1 - Quota Discoveries", "Maximum quota discovery moon count", 1, "The maximum number of moons that will be discovered each time a Quota Discovery is triggered.", new AcceptableValueRange(1, 10)); QuotaDiscoveryPermanent = BindValue("4.1 - Quota Discoveries", "Quota Discoveries are permanent", defaultValue: false, "Moons discovered through Quota Discoveries will stay permanently discovered i.e. they won't vanish on shuffle."); QuotaDiscoveryCheapestGroup = BindValue("4.1 - Quota Discoveries", "Quota Discovery match cheapest group", defaultValue: false, "Only considers moons from the group/constellation that has the currently cheapest undiscovered moon.\nCan effectively discover the 'next tier' or group of moons. Set counts high to discover the entire group.\nNOTE: Highly recommended to only use this with 'Quota Discoveries are permanent' or 'Never shuffle'!"); QuotaDiscoveryCheapestGroupFallback = BindValue("4.1 - Quota Discoveries", "Quota Discovery match cheapest group fallback", defaultValue: true, "When enabled will fallback to selecting from all discoverable moons when no moons could be matched.\nNOTE: Only relevant when you have moons that are not assigned to any group/constellation."); QuotaDiscoveryCheapestConstellation = BindValue("4.1 - Quota Discoveries", "Quota Discovery match cheapest constellation", defaultValue: false, "LethalConstellations only: when a Quota Discovery is configured to discover a constellation, prefer the cheapest eligible undiscovered constellation."); TravelDiscoveries = BindValue("4.2 - Travel Discoveries", "Enable Travel Discoveries", defaultValue: false, "Travel Discoveries grant additional moon discoveries when routing to a paid moon\nThe moons that are discovered are randomly selected."); TravelDiscoveryChance = BindValue("4.2 - Travel Discoveries", "Travel Discovery trigger chance", 20, "The chance to trigger a Travel Discovery every time you route to a paid moon.", new AcceptableValueRange(0, 100)); _travelDiscoveryCountMin = BindValue("4.2 - Travel Discoveries", "Minimum travel discovery moon count", 1, "The minimum number of moons that will be discovered each time a Travel Discovery is triggered.", new AcceptableValueRange(1, 10)); _travelDiscoveryCountMax = BindValue("4.2 - Travel Discoveries", "Maximum travel discovery moon count", 1, "The maximum number of moons that will be discovered each time a Travel Discovery is triggered.", new AcceptableValueRange(1, 10)); TravelDiscoveryPermanent = BindValue("4.2 - Travel Discoveries", "Travel Discoveries are permanent", defaultValue: false, "Moons discovered through Travel Discoveries will stay permanently discovered i.e. they won't vanish on shuffle."); TravelDiscoveryMatchGroup = BindValue("4.2 - Travel Discoveries", "Travel Discovery group matching", defaultValue: false, "Only consider moons of the same group you're routing to for Travel Discoveries."); TravelDiscoveryMatchGroupFallback = BindValue("4.2 - Travel Discoveries", "Travel Discovery group matching fallback", defaultValue: true, "When enabled will fallback to selecting from all discoverable moons when no moons could be matched.\nNOTE: It is recommended to keep this on for matching by exact price but with other methods you might prefer to turn it off."); NewDayDiscoveries = BindValue("4.3 - New Day Discoveries", "Enable New Day Discoveries", defaultValue: false, "New Day Discoveries grant additional moon discoveries at the start of a new day.\nThe moons that are discovered are randomly selected."); NewDayDiscoveryChance = BindValue("4.3 - New Day Discoveries", "New Day Discovery trigger chance", 20, "The chance to trigger a New Day Discovery at the start of a new day.\nMake it a random occurence or guaranteed.", new AcceptableValueRange(0, 100)); _newDayDiscoveryCountMin = BindValue("4.3 - New Day Discoveries", "Minimum new day discovery moon count", 1, "The minimum number of moons to be discovered each time a New Day Discovery is granted.", new AcceptableValueRange(1, 10)); _newDayDiscoveryCountMax = BindValue("4.3 - New Day Discoveries", "Maximum new day discovery moon count", 1, "The maximum number of moons to be discovered each time a New Day Discovery is granted.", new AcceptableValueRange(1, 10)); NewDayDiscoveryPermanent = BindValue("4.3 - New Day Discoveries", "New Day Discoveries are permanent", defaultValue: false, "Moons discovered through New Day Discoveries will stay permanently discovered i.e. they won't vanish on shuffle."); NewDayDiscoveryMatchGroup = BindValue("4.3 - New Day Discoveries", "New Day Discovery group matching", defaultValue: false, "Only consider moons of the same group as the moon you're currently orbiting for New Day Discoveries."); NewDayDiscoveryMatchGroupFallback = BindValue("4.3 - New Day Discoveries", "New Day Discovery group matching fallback", defaultValue: true, "When enabled will fallback to selecting from all discoverable moons when no moons could be matched.\nNOTE: It is recommended to keep this on for matching by exact price but with other methods you might prefer to turn it off."); LethalConstellationsWhitelistString = BindValue("4.4 - LethalConstellations Discoveries", "Constellation whitelist", "", "List of LethalConstellations entries to keep discovered at all times.\nFor example, 'Andromeda, Great Journey Supercluster' would make these constellations start out discovered on every run.\nConstellation names must be separated by commas and must be exact matches. This has priority over LethalConstellation's setting."); AcceptableStartingConstellationsString = BindValue("4.4 - LethalConstellations Discoveries", "Acceptable starting constellations", "", "List of constellations LMU is allowed to use as the first discovered constellation in LethalConstellations mode.\nLeave empty to allow any eligible constellation.\nConstellation names must be separated by commas and must be exact matches. This has priority over LethalConstellation's setting."); LCStartingConstellationSelectionPolicy = BindValue("4.4 - LethalConstellations Discoveries", "Starting constellation selection policy", "Cheapest", "How LMU chooses the first discovered constellation in LethalConstellations mode.\nThe constellation still has to be story-unlocked and otherwise eligible.", new AcceptableValueList(new string[2] { "Cheapest", "Random" })); LCStoryReleaseBehavior = BindValue("4.4 - LethalConstellations Discoveries", "Story release behavior", StoryReleaseBehavior.HiddenBacklog, "What happens when a constellation story release is triggered in LethalConstellations mode.\nThis applies both to default-moon story releases and satisfied custom unlock conditions.\n'HiddenBacklog' keeps the constellation hidden until discovery grants it.\n'ImmediateDiscovery' makes the constellation discovered immediately."); LethalConstellationsQuotaDiscoveryTargetMode = BindValue("4.4 - LethalConstellations Discoveries", "Quota discovery target mode", "MoonsOnly", "What Quota Discoveries target when LethalConstellations is active.", new AcceptableValueList(new string[4] { "MoonsOnly", "MoonsAndConstellations", "ConstellationsOnly", "ConstellationsOnlyWithMoonFallback" })); LethalConstellationsQuotaDiscoveryChance = BindValue("4.4 - LethalConstellations Discoveries", "Quota discovery constellation chance", 100, "The chance for Quota Discoveries to (also) discover a constellation in LethalConstellations mode.", new AcceptableValueRange(0, 100)); LethalConstellationsTravelDiscoveryTargetMode = BindValue("4.4 - LethalConstellations Discoveries", "Travel discovery target mode", "MoonsOnly", "What Travel Discoveries target when LethalConstellations is active.", new AcceptableValueList(new string[4] { "MoonsOnly", "MoonsAndConstellations", "ConstellationsOnly", "ConstellationsOnlyWithMoonFallback" })); LethalConstellationsTravelDiscoveryChance = BindValue("4.4 - LethalConstellations Discoveries", "Travel discovery constellation chance", 100, "The chance for Travel Discoveries to (also) discover a constellation in LethalConstellations mode.", new AcceptableValueRange(0, 100)); LethalConstellationsNewDayDiscoveryTargetMode = BindValue("4.4 - LethalConstellations Discoveries", "New day discovery target mode", "MoonsOnly", "What New Day Discoveries target when LethalConstellations is active.", new AcceptableValueList(new string[4] { "MoonsOnly", "MoonsAndConstellations", "ConstellationsOnly", "ConstellationsOnlyWithMoonFallback" })); LethalConstellationsNewDayDiscoveryChance = BindValue("4.4 - LethalConstellations Discoveries", "New day discovery constellation chance", 100, "The chance for New Day Discoveries to (also) discover a constellation in LethalConstellations mode.", new AcceptableValueRange(0, 100)); LethalConstellationsOverridePrice = BindValue("4.4 - LethalConstellations Discoveries", "LethalConstellations override price", defaultValue: false, "When enabled and LethalConstellations is present, the configured default moon provides the base routing price for the constellation.\nLMU still applies the constellation's own unlocks, discounts, and sales on top of that base price. To also mirror route progression and travel discovery onto the default moon, enable the separate option below."); LethalConstellationsMirrorDefaultMoonRoute = BindValue("4.4 - LethalConstellations Discoveries", "LethalConstellations mirror default moon route", defaultValue: false, "When enabled, routing to a constellation will also apply the route side effects to its default moon.\nThis includes moon buy progression when the route was paid and travel discovery side effects even though the constellation itself remains a first-class progression target."); LethalConstellationsQuotaRewardScope = BindValue("4.4 - LethalConstellations Discoveries", "LethalConstellations quota reward scope", "AllDiscoveredConstellations", "Where quota-granted unlocks, discounts, and full discounts may target when LethalConstellations is active.", new AcceptableValueList(new string[2] { "AllDiscoveredConstellations", "CurrentConstellationOnly" })); Sales = BindValue("5 - Moon Sales", "Moon Sales", defaultValue: false, "Each moon has a chance to go on sale for a reduced routing price.\nBy default, Moon Sales are shuffled after every quota. Only non-free moons can go on sale.\nNOTE: These sales are separate from discounts received via Discount Mode."); SalesShuffleDaily = BindValue("5 - Moon Sales", "Shuffle sales daily", defaultValue: false, "Shuffle moon sales daily, instead of after every quota"); SalesMinDayCount = BindValue("5 - Moon Sales", "Minimum completed days before sales", 0, "Do not allow any moon sales until at least this many days have passed.\nBefore this threshold is reached, you can not get new sales when they're shuffled.", new AcceptableValueRange(0, 30)); SalesChance = BindValue("5 - Moon Sales", "Moon Sale chance", 20, "The chance for each moon to go on sale every time sales are shuffled.", new AcceptableValueRange(0, 100)); _salesRateMin = BindValue("5 - Moon Sales", "Minimum sale percent", 5, "The minimum sale percentage a moon can receive.", new AcceptableValueRange(0, 100)); _salesRateMax = BindValue("5 - Moon Sales", "Maximum sale percent", 30, "The maximum sale percentage a moon can receive", new AcceptableValueRange(1, 100)); BindValue("6 - Advanced Settings", "I have read this", "false", "This section contains advanced configuration options for various features of the mod. Incorrectly tweaking these might cause unexpected behaviour!\nThis setting has no effect."); GroupCreditsSavingBandAid = BindValue("6 - Advanced Settings", "Group credits saving fix", defaultValue: false, "Legacy compatibility switch. Despite the old name, this no longer saves credits separately.\nWhen enabled, LMU will save its own progression even when you're quitting mid round (save-scumming).\nKeep disabled to align LMU progression with the game's regular behavior where it only saves when a new day begins.\nEnable if your setup needs to for consistency. For example maybe another mod restores some mid-round state like the current moon you're orbiting."); AdvancedPrintMoonNames = BindValue("6 - Advanced Settings", "Print moon names to console", defaultValue: false, "Print the names you need to define your custom groups to console/log. They will be logged after you've loaded into a save game. You can also grab moons names from the LMU table that is periodically printed to logs even when this is not enabled."); AutoRerouteToCompany = BindValue("6 - Advanced Settings", "Auto reroute to company", defaultValue: true, "When enabled automatically reroutes the ship to the company on deadline day."); CheapMoonBiasPaidRotation = BindValue("6.1 - Cheap Moon Bias", "Discovery Mode paid rotation", defaultValue: true, "Use Cheap Moon Bias when selecting moons for the paid moon rotation when it's shuffled."); CheapMoonBiasPaidRotationValue = BindValue("6.1 - Cheap Moon Bias", "Discovery Mode paid rotation bias value", 0.66f, "Controls how strongly cheaper moons are favored when Cheap Moon Bias is enabled.\nLMU compares each moon's price against the average price of the current candidate pool and turns that into a selection weight.\n0.0 gives all candidates equal weight.\n1.0 uses inverse-price weighting, so a 100 credit moon is 4x as likely as a 400 credit moon.\n2.0 squares that effect, so the same 100 credit moon is 16x as likely as the 400 credit moon.\nValues between 0.0 and 1.0 soften the bias. Values above 1.0 strengthen it.\nThe calculation uses original prices or current prices depending on the Ignore price changes setting.", new AcceptableValueRange(0f, 2f)); CheapMoonBiasQuotaDiscovery = BindValue("6.1 - Cheap Moon Bias", "Quota Discovery", defaultValue: true, "Use Cheap Moon Bias when selecting moons during Quota Discovery."); CheapMoonBiasQuotaDiscoveryValue = BindValue("6.1 - Cheap Moon Bias", "Quota Discovery bias value", 0.66f, "Controls how strongly cheaper moons are favored when Cheap Moon Bias is enabled.\nLMU compares each moon's price against the average price of the current candidate pool and turns that into a selection weight.\n0.0 gives all candidates equal weight.\n1.0 uses inverse-price weighting, so a 100 credit moon is 4x as likely as a 400 credit moon.\n2.0 squares that effect, so the same 100 credit moon is 16x as likely as the 400 credit moon.\nValues between 0.0 and 1.0 soften the bias. Values above 1.0 strengthen it.\nThe calculation uses original prices or current prices depending on the Ignore price changes setting.", new AcceptableValueRange(0f, 2f)); CheapMoonBiasTravelDiscovery = BindValue("6.1 - Cheap Moon Bias", "Travel Discovery", defaultValue: true, "Use Cheap Moon Bias when selecting moons to discover during Travel Discovery."); CheapMoonBiasTravelDiscoveryValue = BindValue("6.1 - Cheap Moon Bias", "Travel Discovery bias value", 0.66f, "Controls how strongly cheaper moons are favored when Cheap Moon Bias is enabled.\nLMU compares each moon's price against the average price of the current candidate pool and turns that into a selection weight.\n0.0 gives all candidates equal weight.\n1.0 uses inverse-price weighting, so a 100 credit moon is 4x as likely as a 400 credit moon.\n2.0 squares that effect, so the same 100 credit moon is 16x as likely as the 400 credit moon.\nValues between 0.0 and 1.0 soften the bias. Values above 1.0 strengthen it.\nThe calculation uses original prices or current prices depending on the Ignore price changes setting.", new AcceptableValueRange(0f, 2f)); CheapMoonBiasNewDayDiscovery = BindValue("6.1 - Cheap Moon Bias", "New Day Discovery", defaultValue: true, "Use Cheap Moon Bias when selecting moons during New Day Discovery."); CheapMoonBiasNewDayDiscoveryValue = BindValue("6.1 - Cheap Moon Bias", "New Day Discovery bias value", 0.66f, "Controls how strongly cheaper moons are favored when Cheap Moon Bias is enabled.\nLMU compares each moon's price against the average price of the current candidate pool and turns that into a selection weight.\n0.0 gives all candidates equal weight.\n1.0 uses inverse-price weighting, so a 100 credit moon is 4x as likely as a 400 credit moon.\n2.0 squares that effect, so the same 100 credit moon is 16x as likely as the 400 credit moon.\nValues between 0.0 and 1.0 soften the bias. Values above 1.0 strengthen it.\nThe calculation uses original prices or current prices depending on the Ignore price changes setting.", new AcceptableValueRange(0f, 2f)); CheapMoonBiasQuotaUnlock = BindValue("6.1 - Cheap Moon Bias", "Quota Unlock", defaultValue: true, "Use Cheap Moon Bias when selecting moons during Quota Unlocks."); CheapMoonBiasQuotaUnlockValue = BindValue("6.1 - Cheap Moon Bias", "Quota Unlock bias value", 0.66f, "Controls how strongly cheaper moons are favored when Cheap Moon Bias is enabled.\nLMU compares each moon's price against the average price of the current candidate pool and turns that into a selection weight.\n0.0 gives all candidates equal weight.\n1.0 uses inverse-price weighting, so a 100 credit moon is 4x as likely as a 400 credit moon.\n2.0 squares that effect, so the same 100 credit moon is 16x as likely as the 400 credit moon.\nValues between 0.0 and 1.0 soften the bias. Values above 1.0 strengthen it.\nThe calculation uses original prices or current prices depending on the Ignore price changes setting.", new AcceptableValueRange(0f, 2f)); CheapMoonBiasQuotaDiscount = BindValue("6.1 - Cheap Moon Bias", "Quota Discount", defaultValue: true, "Use Cheap Moon Bias when selecting moons during Quota Discounts."); CheapMoonBiasQuotaDiscountValue = BindValue("6.1 - Cheap Moon Bias", "Quota Discount bias value", 0.66f, "Controls how strongly cheaper moons are favored when Cheap Moon Bias is enabled.\nLMU compares each moon's price against the average price of the current candidate pool and turns that into a selection weight.\n0.0 gives all candidates equal weight.\n1.0 uses inverse-price weighting, so a 100 credit moon is 4x as likely as a 400 credit moon.\n2.0 squares that effect, so the same 100 credit moon is 16x as likely as the 400 credit moon.\nValues between 0.0 and 1.0 soften the bias. Values above 1.0 strengthen it.\nThe calculation uses original prices or current prices depending on the Ignore price changes setting.", new AcceptableValueRange(0f, 2f)); CheapMoonBiasQuotaFullDiscount = BindValue("6.1 - Cheap Moon Bias", "Quota Full Discount", defaultValue: true, "Use Cheap Moon Bias when selecting moons during Quota Full Discounts."); CheapMoonBiasQuotaFullDiscountValue = BindValue("6.1 - Cheap Moon Bias", "Quota Full Discount bias value", 0.66f, "Controls how strongly cheaper moons are favored when Cheap Moon Bias is enabled.\nLMU compares each moon's price against the average price of the current candidate pool and turns that into a selection weight.\n0.0 gives all candidates equal weight.\n1.0 uses inverse-price weighting, so a 100 credit moon is 4x as likely as a 400 credit moon.\n2.0 squares that effect, so the same 100 credit moon is 16x as likely as the 400 credit moon.\nValues between 0.0 and 1.0 soften the bias. Values above 1.0 strengthen it.\nThe calculation uses original prices or current prices depending on the Ignore price changes setting.", new AcceptableValueRange(0f, 2f)); CheapMoonBiasIgnorePriceChanges = BindValue("6.1 - Cheap Moon Bias", "Ignore price changes", defaultValue: true, "Ignore any changes to moon prices by discounts or sales and only consider original price for biased selections."); MoonGroupMatchingMethod = _configFile.Bind("6.2 - Moon Group Matching", "Group Matching Method", "Price", new ConfigDescription("The method used to group moons. Group Matching can be used to limit some discoveries to moons of the same group.\n'Price': All moons of the same price are considered a group. This method ignores price changes by unlocks, discounts, or sales.\n'PriceRange': All moons within a set price range are considered a group. Upper and lower range is defined by the price range setting below.\n'PriceRangeUpper': All moons within a set upper price range are considered a group. Upper range is defined by the price range setting below.\n'Tag': All moons that have at least one tag in common are considered a group.\n'Custom': Define custom named groups of moons below.", (AcceptableValueBase)(object)new AcceptableValueList(new string[5] { "Price", "PriceRange", "PriceRangeUpper", "Tag", "Custom" }), Array.Empty())).Value; if (string.Equals(MoonGroupMatchingMethod, "LethalConstellations", StringComparison.OrdinalIgnoreCase)) { Logger.LogWarning("Group Matching Method 'LethalConstellations' was removed. Falling back to 'Price'. Check the new LethalConstellations config settings."); MoonGroupMatchingMethod = "Price"; } MoonGroupMatchingPriceRange = BindValue("6.2 - Moon Group Matching", "Price range", 200, "The price range used for matching moons via 'PriceRange' and 'PriceRangeUpper' methods.\nIt will match all moons priced within the original price +- this value (+ this value for upper range)."); MoonGroupMatchingCustom = BindValue("6.2 - Moon Group Matching", "Custom moon groups", "", "Define your own custom moon groups.\nExpected Format: Separate moon groups by \"|\" and moons by \",\".\nExample: 'Group name 1: Experimentation, Assurance, Vow | Group name 2: Offense, March, Adamance'\nNames must be exact matches. The option below can be used to get the names."); TerminalTagLineWidth = BindValue("6.3 - Terminal", "Maximum tag line length", 49, "By default LMU tries to fit as many tags as possible into a single line.\nDecrease this value if you want to have a more organized look at the cost of more scrolling depending on the amount of tags you see.\nNOTE: Don't worry about setting it too low. It will always put at least one tag per line. Only if any additional tag would exceed this value it puts a line break.\nDo not set it larger than default unless you are also decreasing font size below.", new AcceptableValueRange(10, 100)); TerminalFontSizeOverride = BindValue("6.3 - Terminal", "Override Terminal font size", defaultValue: true, "Override the font size in the Terminal's moon catalog.\nPrevents inconsistencies with formatting. Disable to let LLL dynamically size the font depending on the number of moons visible\nNOTE: With very few moons you might see some ugly line breaks with custom weathers with long names (Meteor Shower)."); TerminalFontSize = BindValue("6.3 - Terminal", "Terminal font size", 15f, "Customize the Terminal's moon catalog font size.\nNOTE: When using smaller fonts you can increase the maximum tag line width above.", new AcceptableValueRange(8f, 15f)); TerminalScrollAmount = BindValue("6.3 - Terminal", "Terminal scroll amount", 0, "Override the Terminal's moon catalog scroll amount. 1 is close to vanilla scroll amount but normalized to account for more text/moons. Increase to make scrolling smoother or rather steps smaller. 0 to disable\nNOTE: This can help when you have so many moons that some are skipped when scrolling.", new AcceptableValueRange(0, 20)); TerminalShowRiskWeather = BindValue("6.3 - Terminal", "Terminal show weather in risk preview", defaultValue: false, "Also show the weather when using `preview difficulty`"); AlertMessageQueueing = BindValue("6.4 - Compatibility", "Avoid alert messages overlapping", defaultValue: true, "When enabled, LethalMoonUnlocks will intercept all alert messages (yellow/red pop-up) and add them to a queue. This avoids alert messages from other mods and Vanilla from overlapping or not showing at all. Disable if you experience issues."); AlertMessageQueueExcludedPlugins = ParseCommaList(BindValue("6.4 - Compatibility", "Alert queue excluded plugins", "giosuel.Imperium, mrgrm7.LethalCasino", "Comma-separated list of BepInEx plugin GUIDs that should bypass LMU's alert queue when they attempt to show alerts.\nUsed to prevent spammy mods from congesting the queue. You probably don't have to change this.")).ToHashSet(StringComparer.OrdinalIgnoreCase); PreferLQRisk = BindValue("6.4 - Compatibility", "Prefer LethalQuantities risk level", defaultValue: false, "Show the moon risk levels set by LethalQuantities in the moon catalog instead of the default risk levels."); MalfunctionsNavigation = BindValue("6.4 - Compatibility", "Malfunctions navigation buys moon", defaultValue: false, "When the Malfunctions navigation malfunction is triggered LMU will interpret it as if the moon routed to was bought."); PreferGaletry = BindValue("6.4 - Compatibility", "Prefer Galetry over Gordion", defaultValue: true, "When enabled and Galetry (from Wesley's moons journey) is available and routable, LMU will auto reroute the ship to Galetry instead of Gordion (the company)."); OverrideHidden = BindValue("6.5 - Overrides", "Override moons hidden by default", defaultValue: false, "Enable to hard override any hidden by default information using the list below. Any other information will be ignored. This includes moons hidden in vanilla, via LLL config, etc."); OverrideHiddenList = BindValue("6.5 - Overrides", "Override hidden list", "", "List of moons LMU will consider to be hidden by default.\nFor example, 'Vow, March, Artifice'. Those three will be the only moons hidden by default. You can still unhide them in various ways. Note that setting this would make Embrion not hidden.\nMoon names must be separated by commas and must be exact matches. You can print the moon names to console/log by using the option in 'Advanced Settings'."); OverrideLocked = BindValue("6.5 - Overrides", "Override moons locked by default", defaultValue: false, "Enable to hard override any locked by default information using the list below. Any other information will be ignored. This includes moons locked in vanilla, via LLL config, etc."); OverrideLockedList = BindValue("6.5 - Overrides", "Override locked list", "", "List of moons LMU will consider to be locked by default.\nFor example, 'Vow, March, Artifice'. Those three will be the only moons locked by default.\nMoon names must be separated by commas and must be exact matches. You can print the moon names to console/log by using the option in 'Advanced Settings'."); EnableStoryProgression = BindValue("6.6 - Story Progression", "Enable Story Progression", defaultValue: true, "Story progression allows locking moons behind various conditions. This can be employed by other mods like Wesley's moons (JLL).\nDisabling this settings will globally ignore any requests to lock moons behind story progressions inlcuding LMU's own Vanilla Story progression."); MoonStoryReleaseBehavior = BindValue("6.6 - Story Progression", "Moon story release behavior", StoryReleaseBehavior.HiddenBacklog, "What happens when a regular moon story release is triggered while Discovery Mode is enabled.\n'HiddenBacklog' keeps the moon hidden until discovery grants it.\n'ImmediateDiscovery' makes the moon discovered immediately."); LMUStoryProgression = BindValue("6.6 - Story Progression", "Vanilla Story Progression", defaultValue: false, "Enable to lock the two hidden vanilla moons behind story progression. To release the lock for Artifice you have to land three times on Adamance, for Embrion you have to scan an old bird. After completing these tasks the moons will be available (for discovery). They will not be hidden."); GaletryStoryLock = BindValue("6.6 - Story Progression", "Restrict access to Galetry", defaultValue: false, "When enabled and Wesley's moons is installed Galetry is not available from the start. To gain access you will need to sell a specified number of paintings to the company."); GaletryStoryLockPaintingsAmount = BindValue("6.6 - Story Progression", "Galetry number of paintings", 3, "The number of sold paintings required to gain access to Galetry."); } private static T BindValue(string section, string key, T defaultValue, string description) { return _configFile.Bind(section, key, defaultValue, description).Value; } private static T BindValue(string section, string key, T defaultValue, string description, AcceptableValueRange range) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown return _configFile.Bind(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)range, Array.Empty())).Value; } private static T BindValue(string section, string key, T defaultValue, string description, AcceptableValueRange range) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown return _configFile.Bind(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)range, Array.Empty())).Value; } private static string BindValue(string section, string key, string defaultValue, string description, AcceptableValueList acceptableValues) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown return _configFile.Bind(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)(object)acceptableValues, Array.Empty())).Value; } private static List ParseCommaList(string value) { if (string.IsNullOrWhiteSpace(value)) { return new List(); } return (from item in value.Split(",", StringSplitOptions.RemoveEmptyEntries) select item.Trim() into item where !string.IsNullOrWhiteSpace(item) select item).ToList(); } private static void MigrateLegacyConfig(string legacyConfigPath, ConfigFile cfg) { File.Copy(legacyConfigPath, Path.Combine(Paths.ConfigPath, "com.xmods.lethalmoonunlocks.cfg"), overwrite: true); _configFile = cfg; _configFile.Reload(); RefreshValues(); Logger.LogInfo("Legacy configuration migrated. Renaming legacy config file.."); File.Copy(legacyConfigPath, Path.Combine(Paths.ConfigPath, "com.xmods.lethalmoonunlocks.cfg.legacy"), overwrite: true); File.Delete(legacyConfigPath); } } [Serializable] internal sealed class ConstellationRouteSyncData { public string constellationName = string.Empty; public int chargedPrice; internal ConstellationRouteSyncData() { } internal ConstellationRouteSyncData(string constellationName, int chargedPrice) { this.constellationName = constellationName ?? string.Empty; this.chargedPrice = chargedPrice; } } [Serializable] internal sealed class LMUnlockableSyncData { public string name = string.Empty; public int routePrice; public int originalPrice; public bool originallyLocked; public bool originallyHidden; public bool remainingHidden; public bool storyUnlock; public bool storyIsUnlocked; public int buyCount; public int visitCount; public int freeVisitCount; public int landingCount; public bool discovered; public bool newDiscovery; public bool discoveredOnce; public bool permanentlyDiscovered; public bool onSale; public int salesRate; } [Serializable] internal class ProgressionSaveData { internal int PaintingsSold { get; set; } internal List ReadBestiaryEntries { get; set; } = new List(); internal List ReadStoryLogs { get; set; } = new List(); internal ProgressionSaveData Copy() { return new ProgressionSaveData { PaintingsSold = PaintingsSold, ReadBestiaryEntries = (ReadBestiaryEntries?.ToList() ?? new List()), ReadStoryLogs = (ReadStoryLogs?.ToList() ?? new List()) }; } internal bool HasData() { return PaintingsSold > 0 || (ReadBestiaryEntries?.Count ?? 0) > 0 || (ReadStoryLogs?.Count ?? 0) > 0; } } internal enum TerminalReadKind { Bestiary, StoryLog } [Serializable] internal sealed class TerminalReadSyncData { public TerminalReadKind readKind; public string entryName = string.Empty; internal TerminalReadSyncData() { } internal TerminalReadSyncData(TerminalReadKind readKind, string entryName) { this.readKind = readKind; this.entryName = entryName ?? string.Empty; } } [Serializable] internal sealed class UnlockSyncData { public List Unlockables = new List(); public LethalConstellationsSyncData LethalConstellationsSyncData = new LethalConstellationsSyncData(); internal UnlockSyncData() { } internal UnlockSyncData(List unlockables, LethalConstellationsSyncData lethalConstellationsSyncData) { Unlockables = unlockables ?? new List(); LethalConstellationsSyncData = lethalConstellationsSyncData ?? new LethalConstellationsSyncData(); } } public readonly struct LMGroup { public string Name { get; init; } public List Members { get; init; } public LMGroup() { Name = string.Empty; Members = new List(); } } [Serializable] [ES3Serializable] public class LMUnlockable { [SerializeField] [ES3Serializable] private bool isHidden; [SerializeField] [ES3Serializable] private bool isLocked; [SerializeField] [ES3Serializable] private bool originallyLocked; [SerializeField] [ES3Serializable] private bool originallyHidden; [ES3NonSerializable] public ExtendedLevel ExtendedLevel { get; private set; } [ES3Serializable] public string Name { get; private set; } [ES3NonSerializable] internal int OriginalPrice { get; private set; } internal bool OriginallyLocked { get { if (ConfigManager.OverrideLocked) { return ConfigManager.OverrideLockedListMoons.Contains(Name); } return originallyLocked; } private set { originallyLocked = value; } } internal bool OriginallyHidden { get { if (ConfigManager.OverrideHidden) { return ConfigManager.OverrideHiddenListMoons.Contains(Name); } return originallyHidden; } private set { originallyHidden = value; } } [ES3NonSerializable] internal bool IsHidden { get { return isHidden; } private set { isHidden = value; } } [ES3NonSerializable] internal bool IsLocked { get { return isLocked; } private set { isLocked = value; } } [ES3Serializable] internal bool RemainingHidden { get; set; } [ES3Serializable] internal bool StoryUnlock { get; private set; } [ES3Serializable] internal bool StoryIsUnlocked { get; set; } [ES3Serializable] internal int BuyCount { get; set; } [ES3Serializable] internal int VisitCount { get; set; } [ES3Serializable] internal int FreeVisitCount { get; set; } [ES3Serializable] internal int LandingCount { get; set; } [ES3Serializable] public bool Discovered { get; set; } [ES3Serializable] internal bool NewDiscovery { get; set; } [ES3Serializable] internal bool DiscoveredOnce { get; set; } [ES3Serializable] internal bool PermanentlyDiscovered { get; set; } [ES3Serializable] internal bool OnSale { get; set; } [ES3Serializable] internal int SalesRate { get; set; } [ES3NonSerializable] internal int RoutePrice { get; set; } internal LMUnlockable(ExtendedLevel extendedLevel) { Name = extendedLevel.NumberlessPlanetName; ExtendedLevel = extendedLevel; RoutePrice = extendedLevel.RoutePrice; OriginalPrice = extendedLevel.RoutePrice; OriginallyHidden = extendedLevel.IsRouteHidden; OriginallyLocked = extendedLevel.IsRouteLocked; RefreshStoryUnlockFromStartupState(); } internal void OverrideDefaultsDawnLib(DawnMoonInfo dawnMoon) { RoutePrice = dawnMoon.DawnPurchaseInfo.Cost.Provide(); OriginalPrice = dawnMoon.DawnPurchaseInfo.Cost.Provide(); TerminalPurchaseResult val = dawnMoon.DawnPurchaseInfo.PurchasePredicate.CanPurchase(); bool flag = false; bool flag2 = false; HiddenPurchaseResult val2 = (HiddenPurchaseResult)(object)((val is HiddenPurchaseResult) ? val : null); if (val2 != null) { flag = true; if (val2.IsFailure) { flag2 = true; } } else if (val is FailedPurchaseResult) { flag2 = true; } OriginallyHidden = flag; OriginallyLocked = flag2; IsHidden = flag; IsLocked = flag2; RefreshStoryUnlockFromStartupState(); Logger.LogDebug($"DawnMoon: Name: {dawnMoon.GetNumberlessPlanetName()}, Hidden: {IsHidden}, Locked = {IsLocked}"); if (OriginallyHidden && !OriginallyLocked) { RemainingHidden = true; } } internal void OverrideData(LMUnlockable newData) { if (Name != newData.Name) { Logger.LogError("Name mismatch during override LMUnlockable data!"); return; } StoryIsUnlocked = newData.StoryIsUnlocked; BuyCount = newData.BuyCount; VisitCount = newData.VisitCount; FreeVisitCount = newData.FreeVisitCount; LandingCount = newData.LandingCount; if (UnlockManager.Instance == null || !UnlockManager.Instance.UseConstellationDiscovery) { Discovered = newData.Discovered; } NewDiscovery = newData.NewDiscovery; DiscoveredOnce = newData.DiscoveredOnce; PermanentlyDiscovered = newData.PermanentlyDiscovered; OnSale = newData.OnSale; SalesRate = newData.SalesRate; RoutePrice = newData.RoutePrice; RefreshStoryUnlockFromStartupState(); } internal LMUnlockableSyncData BuildSyncData() { return new LMUnlockableSyncData { name = Name, routePrice = RoutePrice, originalPrice = OriginalPrice, originallyLocked = originallyLocked, originallyHidden = originallyHidden, remainingHidden = RemainingHidden, storyUnlock = StoryUnlock, storyIsUnlocked = StoryIsUnlocked, buyCount = BuyCount, visitCount = VisitCount, freeVisitCount = FreeVisitCount, landingCount = LandingCount, discovered = Discovered, newDiscovery = NewDiscovery, discoveredOnce = DiscoveredOnce, permanentlyDiscovered = PermanentlyDiscovered, onSale = OnSale, salesRate = SalesRate }; } internal void ApplySyncData(LMUnlockableSyncData syncData) { if (syncData != null) { if (!string.Equals(Name, syncData.name, StringComparison.OrdinalIgnoreCase)) { Logger.LogError("Name mismatch during LMUnlockable sync data import!"); return; } OriginalPrice = syncData.originalPrice; originallyLocked = syncData.originallyLocked; originallyHidden = syncData.originallyHidden; RemainingHidden = syncData.remainingHidden; StoryUnlock = syncData.storyUnlock; StoryIsUnlocked = syncData.storyIsUnlocked; BuyCount = syncData.buyCount; VisitCount = syncData.visitCount; FreeVisitCount = syncData.freeVisitCount; LandingCount = syncData.landingCount; Discovered = syncData.discovered; NewDiscovery = syncData.newDiscovery; DiscoveredOnce = syncData.discoveredOnce; PermanentlyDiscovered = syncData.permanentlyDiscovered; OnSale = syncData.onSale; SalesRate = syncData.salesRate; RoutePrice = syncData.routePrice; } } internal void SetDiscoveryState(bool discovered, bool suppressNewDiscovery = false) { Discovered = discovered; if (discovered) { if (suppressNewDiscovery) { DiscoveredOnce = true; NewDiscovery = false; } else if (!DiscoveredOnce) { DiscoveredOnce = true; NewDiscovery = true; } } } internal void RestoreOriginalState() { RoutePrice = OriginalPrice; IsHidden = originallyHidden; IsLocked = originallyLocked; RefreshStoryUnlockFromStartupState(); if (Object.op_Implicit((Object)(object)ExtendedLevel)) { ExtendedLevel.RoutePrice = OriginalPrice; } ApplyVisibilityLLL(); if (Plugin.DawnLibPresent && Object.op_Implicit((Object)(object)ExtendedLevel)) { DawnMoonInfo val = ((Registry)(object)LethalContent.Moons).Values.FirstOrDefault((Func)((DawnMoonInfo x) => x.Level.levelID == ExtendedLevel.SelectableLevel.levelID)); if (val != null) { val.DawnPurchaseInfo.Cost = (IProvider)(object)new SimpleProvider(OriginalPrice); ((DawnBaseInfo)(object)val).Internal_AddTag(DawnLibTags.LunarConfig); ApplyVisibilityDawnLib(); } } } internal void ForceStoryLockAtStartup() { OriginallyHidden = true; OriginallyLocked = true; IsHidden = true; IsLocked = true; RemainingHidden = false; RefreshStoryUnlockFromStartupState(); } internal void RefreshStoryUnlockFromStartupState() { StoryUnlock = OriginallyHidden && OriginallyLocked; } internal void IterateState() { RoutePrice = CalculatePrice(); if (BuyCount > 0 && ((ConfigManager.UnlockMode && !ConfigManager.DiscountMode && ConfigManager.DiscoveryKeepUnlocks) || (ConfigManager.DiscountMode && ConfigManager.DiscoveryKeepDiscounts)) && !PermanentlyDiscovered) { PermanentlyDiscovered = true; Logger.LogInfo(Name + " set to permanently discovered because it's " + (ConfigManager.UnlockMode ? "unlocked" : "discounted.")); } if (Discovered && !OriginallyHidden && !OriginallyLocked && OriginalPrice == 0 && ConfigManager.PermanentlyDiscoverFreeMoonsOnLanding >= 0 && LandingCount >= ConfigManager.PermanentlyDiscoverFreeMoonsOnLanding && !PermanentlyDiscovered) { PermanentlyDiscovered = true; Logger.LogInfo($"{Name} set to permanently discovered because it's been landed on {LandingCount} times."); } if (Discovered && !OriginallyHidden && !OriginallyLocked && OriginalPrice > 0 && ConfigManager.PermanentlyDiscoverPaidMoonsOnLanding >= 0 && LandingCount >= ConfigManager.PermanentlyDiscoverPaidMoonsOnLanding && !PermanentlyDiscovered) { PermanentlyDiscovered = true; Logger.LogInfo($"{Name} set to permanently discovered because it's been landed on {LandingCount} times."); } if (OriginallyHidden && !OriginallyLocked) { if (ConfigManager.PermanentlyDiscoverHiddenMoonsOnVisit && VisitCount > 0) { RemainingHidden = false; PermanentlyDiscovered = true; } else { RemainingHidden = true; PermanentlyDiscovered = false; } } else { RemainingHidden = false; } if (!DiscoveredOnce && Discovered) { NewDiscovery = true; DiscoveredOnce = true; } if (Name == "Adamance" && LandingCount > 2) { LMUnlockable lMUnlockable = UnlockManager.Instance.Unlocks.FirstOrDefault((LMUnlockable u) => u.Name == "Artifice"); if (lMUnlockable != null && lMUnlockable.StoryUnlock && !lMUnlockable.StoryIsUnlocked) { lMUnlockable.StoryIsUnlocked = true; Logger.LogInfo(lMUnlockable.Name + ": Releasing story lock.. " + lMUnlockable.Name + " now available (for discovery)."); NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "Autopilot", Text = "Incoming transmission! Decoding location data...", Key = "LMU_StoryLockReleasedArtifice" }); } } } internal void RefreshCalculatedPrice() { RoutePrice = CalculatePrice(); } internal void ApplyState() { ApplyPriceLLL(); if (Plugin.DawnLibPresent) { ApplyPriceDawnLib(); } if (StoryUnlock) { if (StoryIsUnlocked) { if (!ConfigManager.DiscoveryMode) { Unlock(); return; } if (ConfigManager.DiscoveryMode && (Discovered || PermanentlyDiscovered)) { Unlock(); return; } } LockAndHide(); } else if (OriginallyLocked) { LockAndHide(); } else if (OriginallyHidden) { Unlock(); } else if (!ConfigManager.DiscoveryMode) { Unlock(); } else if (ConfigManager.DiscoveryMode) { if (Discovered || PermanentlyDiscovered) { Unlock(); } else { LockAndHide(); } } } public void Unlock() { IsLocked = false; if (RemainingHidden) { IsHidden = true; } else { IsHidden = false; } } public void LockAndHide() { IsHidden = true; IsLocked = true; } internal void ApplyVisibility() { ApplyVisibilityLLL(); if (Plugin.DawnLibPresent) { ApplyVisibilityDawnLib(); } } private void ApplyVisibilityDawnLib() { //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Expected O, but got Unknown //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Expected O, but got Unknown DawnMoonInfo val = ((Registry)(object)LethalContent.Moons).Values.FirstOrDefault((Func)((DawnMoonInfo x) => x.Level.levelID == ExtendedLevel.SelectableLevel.levelID)); if (val == null) { return; } ITerminalPurchasePredicate purchasePredicate = ITerminalPurchasePredicate.AlwaysSuccess(); TerminalNode val2 = ScriptableObject.CreateInstance(); val2.displayText = "Error calculating route: UNKNOWN LOCATION"; ((DawnBaseInfo)(object)val).Internal_AddTag(DawnLibTags.LunarConfig); ITerminalPurchasePredicate purchasePredicate2 = val.DawnPurchaseInfo.PurchasePredicate; ProgressivePredicate val3 = (ProgressivePredicate)(object)((purchasePredicate2 is ProgressivePredicate) ? purchasePredicate2 : null); if (val3 != null) { val3.ProgressiveStates.IsHidden = IsHidden; val3.ProgressiveStates.IsUnlocked = !IsLocked; return; } if (IsHidden) { purchasePredicate = (ITerminalPurchasePredicate)((!IsLocked) ? new ConstantTerminalPredicate((TerminalPurchaseResult)(object)new HiddenPurchaseResult().SetFailure(false)) : new ConstantTerminalPredicate((TerminalPurchaseResult)(object)new HiddenPurchaseResult().SetFailure(true).SetFailNode(val2))); } else if (IsLocked) { purchasePredicate = ITerminalPurchasePredicate.AlwaysFail(val2); } val.DawnPurchaseInfo.PurchasePredicate = purchasePredicate; } private void ApplyVisibilityLLL() { if (!Object.op_Implicit((Object)(object)ExtendedLevel)) { return; } if (IsHidden) { if (IsLocked) { ExtendedLevel.IsRouteHidden = true; ExtendedLevel.IsRouteLocked = true; } else { ExtendedLevel.IsRouteHidden = true; ExtendedLevel.IsRouteLocked = false; } } else if (IsLocked) { ExtendedLevel.IsRouteHidden = false; ExtendedLevel.IsRouteLocked = true; } else { ExtendedLevel.IsRouteHidden = false; ExtendedLevel.IsRouteLocked = false; } } internal void ApplyPriceLLL() { if (Object.op_Implicit((Object)(object)ExtendedLevel)) { ExtendedLevel.RoutePrice = RoutePrice; } } internal void ApplyPriceDawnLib() { DawnMoonInfo val = ((Registry)(object)LethalContent.Moons).Values.FirstOrDefault((Func)((DawnMoonInfo x) => x.Level.levelID == ExtendedLevel.SelectableLevel.levelID)); if (val != null) { val.DawnPurchaseInfo.Cost = (IProvider)(object)new SimpleProvider(RoutePrice); ((DawnBaseInfo)(object)val).Internal_AddTag(DawnLibTags.LunarConfig); } } internal void RefreshSale() { Logger.LogDebug(Name + ": Refreshing sale rate.."); if (RandomHelper.Chance(ConfigManager.SalesChance) && RoutePrice > 0) { OnSale = true; SalesRate = ConfigManager.SalesRate; Logger.LogInfo($"{Name} is on SALE for {SalesRate}% OFF!"); } else { OnSale = false; SalesRate = 0; } } internal void Land() { LandingCount++; } internal void VisitMoon() { VisitCount++; Logger.LogDebug($"{Name}: Set visit count to {VisitCount}"); if ((RoutePrice == 0 || (ConfigManager.DiscountMode && BuyCount == ConfigManager.DiscountsCount)) && OriginalPrice != RoutePrice) { FreeVisitCount++; Logger.LogDebug($"{Name}: Set free visit count to {FreeVisitCount}"); if (ConfigManager.UnlockMode && !ConfigManager.DiscountMode && ConfigManager.UnlocksResetAfterVisits > 0) { if (FreeVisitCount > ConfigManager.UnlocksResetAfterVisits) { Logger.LogInfo($"{Name}: Reset unlock due to free visit count ({FreeVisitCount - 1}) reached."); NotificationHelper.SendChatMessage("Unlock expired:\n" + Name + ""); NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "Unlock expired!", Text = "Your unlock for " + Name + " has been used " + (FreeVisitCount - 1).NumberOfWords("time") + " and expired.", IsWarning = true, Key = "LMU_UnlockExpired" }); BuyCount = 0; FreeVisitCount = 0; if (ConfigManager.UnlocksResetAfterVisitsPermDiscovery) { Logger.LogInfo(Name + ": Also resetting permanent discovery status."); PermanentlyDiscovered = false; } } else if (FreeVisitCount > 1) { NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = (Name ?? ""), Text = "Unlock redeemed! " + (FreeVisitCount - 1).CountToText() + " use.\nYou have " + (ConfigManager.UnlocksResetAfterVisits - FreeVisitCount + 1).NumberOfWords("use") + " left.", Key = "LMU_UnlockUsed" }); } } if (ConfigManager.DiscountMode && ConfigManager.DiscountsResetAfterVisits > 0) { if (FreeVisitCount > ConfigManager.DiscountsResetAfterVisits) { Logger.LogInfo($"{Name}: Reset discount due to free visit count ({FreeVisitCount - 1}) reached."); NotificationHelper.SendChatMessage("Discount expired:\n" + Name + ""); NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "Discount expired!", Text = "Your discount for " + Name + " has been used " + (FreeVisitCount - 1).NumberOfWords("time") + " and expired.", IsWarning = true, Key = "LMU_DiscountExpired" }); BuyCount = 0; FreeVisitCount = 0; if (ConfigManager.DiscountsResetAfterVisitsPermDiscovery) { Logger.LogInfo(Name + ": Also resetting permanent discovery status."); PermanentlyDiscovered = false; } } else if (FreeVisitCount > 1) { NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "Discount: " + Name, Text = "Discount redeemed! " + (FreeVisitCount - 1).CountToText() + " use.\nYou have " + (ConfigManager.DiscountsResetAfterVisits - FreeVisitCount + 1).NumberOfWords("use") + " left.", Key = "LMU_DiscountUsed" }); } } } DelayHelper.Instance.ExecuteAfterDelay(NetworkManager.Instance.ServerSendAlertQueueEvent, 1f); } internal Dictionary> GetMatchingCustomGroups() { Dictionary> moonGroupMatchingCustomDict = ConfigManager.MoonGroupMatchingCustomDict; Dictionary> dictionary = new Dictionary>(); foreach (KeyValuePair> item in moonGroupMatchingCustomDict) { if (item.Value.Contains(Name)) { dictionary.Add(item.Key, item.Value); } } return dictionary; } internal string GetMoonPreviewText(PreviewInfoType infoType) { int num = Name.Count(); string format = "{0, -" + Math.Clamp(18 - num, 0, 18) + "} {1, -7} {2, -9} {3, -13}"; string text = string.Empty; string empty = string.Empty; string empty2 = string.Empty; empty2 = ((!Plugin.WeatherTweaksPresent) ? ((object)(LevelWeatherType)(ref ExtendedLevel.SelectableLevel.currentWeather)).ToString() : WTCompatibility.GetWeatherTweaksWeather(this)); if (empty2.Trim().Equals("None")) { empty2 = string.Empty; } if (empty2.Count() > 13) { empty2 = empty2.Substring(0, 11) + ".."; } string text2 = string.Empty; if (Plugin.LQPresent && ConfigManager.PreferLQRisk) { text2 = LQCompatibility.GetLQRiskLevel(this); } if (string.IsNullOrEmpty(text2)) { text2 = ExtendedLevel.SelectableLevel.riskLevel; } if (text2.Count() > 7) { text2 = text2.Substring(0, 5) + ".."; } if (((object)(PreviewInfoType)(ref infoType)).Equals((object)(PreviewInfoType)2)) { text = string.Format(format, empty, empty, "$" + RoutePrice, empty2); } else if (((object)(PreviewInfoType)(ref infoType)).Equals((object)(PreviewInfoType)0)) { text = string.Format(format, empty, empty, "$" + RoutePrice, empty); } else if (((object)(PreviewInfoType)(ref infoType)).Equals((object)(PreviewInfoType)1)) { text = ((!ConfigManager.TerminalShowRiskWeather) ? string.Format(format, empty, text2, empty, empty) : string.Format(format, empty, text2, empty, empty2)); } else if (((object)(PreviewInfoType)(ref infoType)).Equals((object)(PreviewInfoType)3)) { text = string.Format(format, empty, empty, empty, empty); } else if (((object)(PreviewInfoType)(ref infoType)).Equals((object)(PreviewInfoType)4)) { text = string.Format(format, empty, text2, "$" + RoutePrice, empty2); } else if (((object)(PreviewInfoType)(ref infoType)).Equals((object)(PreviewInfoType)6)) { text = string.Format(format, empty, empty, empty, empty); } else if (((object)(PreviewInfoType)(ref infoType)).Equals((object)(PreviewInfoType)7)) { text = string.Format(format, empty, empty, empty, empty); } if (IsLocked) { text += "\n * (Locked)"; } if (!ConfigManager.DisplayTerminalTags) { return text; } string text3 = BuildTagString(); if (!string.IsNullOrEmpty(text3)) { text += text3; } return text; } internal string BuildAdditionalInfoString() { string text = string.Empty; if ((Object)(object)ExtendedLevel == (Object)(object)LevelManager.CurrentExtendedLevel && ConfigManager.ShowTagInOrbit) { text = AddTagToPreviewText("[IN ORBIT]", text); } if (NewDiscovery && ConfigManager.DiscoveryMode && ConfigManager.ShowTagNewDiscovery) { text = AddTagToPreviewText("[NEW]", text); } if (LandingCount > 0 && ConfigManager.ShowTagExplored) { text = AddTagToPreviewText($"[LANDINGS:{LandingCount}]", text); } else if (LandingCount == 0 && ConfigManager.ShowTagExplored) { text = AddTagToPreviewText("[UNEXPLORED]", text); } if (FreeVisitCount > 0 && ConfigManager.UnlockMode && !ConfigManager.DiscountMode && ConfigManager.UnlocksResetAfterVisits > 0 && ConfigManager.ShowTagUnlockDiscount) { text = AddTagToPreviewText($"[UNLOCK EXPIRES:{ConfigManager.UnlocksResetAfterVisits - FreeVisitCount + 1}]", text); } else if (FreeVisitCount > 0 && ConfigManager.DiscountMode && ConfigManager.DiscountsResetAfterVisits > 0 && ConfigManager.ShowTagUnlockDiscount) { text = AddTagToPreviewText($"[DISCOUNT EXPIRES:{ConfigManager.DiscountsResetAfterVisits - FreeVisitCount + 1}]", text); } else if (ConfigManager.UnlockMode && !ConfigManager.DiscountMode && BuyCount > 0 && ConfigManager.ShowTagUnlockDiscount) { text = AddTagToPreviewText("[UNLOCKED]", text); } else if (ConfigManager.DiscountMode && BuyCount > 0 && ConfigManager.ShowTagUnlockDiscount) { int discountPercentOff = Plugin.GetDiscountPercentOff(BuyCount); text = ((discountPercentOff == 100) ? AddTagToPreviewText("[FULL DISCOUNT]", text) : AddTagToPreviewText($"[DISCOUNT {discountPercentOff}%]", text)); } if (PermanentlyDiscovered && !ConfigManager.DiscoveryNeverShuffle && ConfigManager.DiscoveryMode && ConfigManager.ShowTagPermanentDiscovery && ((OriginalPrice == 0 && ConfigManager.PermanentlyDiscoverFreeMoonsOnLanding != 0) || (OriginalPrice > 0 && ConfigManager.PermanentlyDiscoverPaidMoonsOnLanding != 0))) { text = AddTagToPreviewText("[PINNED]", text); } if (OnSale && SalesRate > 0 && RoutePrice > 0 && ConfigManager.Sales && ConfigManager.ShowTagSale) { text = AddTagToPreviewText($"[SALE {SalesRate}%]", text); } return text + "\n"; } internal string BuildTagString() { string text = string.Empty; if ((Object)(object)ExtendedLevel == (Object)(object)LevelManager.CurrentExtendedLevel && ConfigManager.ShowTagInOrbit) { text = AddTagToPreviewText("[IN ORBIT]", text); } if (NewDiscovery && ConfigManager.DiscoveryMode && ConfigManager.ShowTagNewDiscovery) { text = AddTagToPreviewText("[NEW]", text); } if (LandingCount > 0 && ConfigManager.ShowTagExplored) { text = AddTagToPreviewText($"[LANDINGS:{LandingCount}]", text); } else if (LandingCount == 0 && ConfigManager.ShowTagExplored) { text = AddTagToPreviewText("[UNEXPLORED]", text); } if (FreeVisitCount > 0 && ConfigManager.UnlockMode && !ConfigManager.DiscountMode && ConfigManager.UnlocksResetAfterVisits > 0 && ConfigManager.ShowTagUnlockDiscount) { text = AddTagToPreviewText($"[UNLOCK EXPIRES:{ConfigManager.UnlocksResetAfterVisits - FreeVisitCount + 1}]", text); } else if (FreeVisitCount > 0 && ConfigManager.DiscountMode && ConfigManager.DiscountsResetAfterVisits > 0 && ConfigManager.ShowTagUnlockDiscount) { text = AddTagToPreviewText($"[DISCOUNT EXPIRES:{ConfigManager.DiscountsResetAfterVisits - FreeVisitCount + 1}]", text); } else if (ConfigManager.UnlockMode && !ConfigManager.DiscountMode && BuyCount > 0 && ConfigManager.ShowTagUnlockDiscount) { text = AddTagToPreviewText("[UNLOCKED]", text); } else if (ConfigManager.DiscountMode && BuyCount > 0 && ConfigManager.ShowTagUnlockDiscount) { int discountPercentOff = Plugin.GetDiscountPercentOff(BuyCount); text = ((discountPercentOff == 100) ? AddTagToPreviewText("[FULL DISCOUNT]", text) : AddTagToPreviewText($"[DISCOUNT {discountPercentOff}%]", text)); } if (PermanentlyDiscovered && !ConfigManager.DiscoveryNeverShuffle && ConfigManager.DiscoveryMode && ConfigManager.ShowTagPermanentDiscovery && ((OriginalPrice == 0 && ConfigManager.PermanentlyDiscoverFreeMoonsOnLanding != 0) || (OriginalPrice > 0 && ConfigManager.PermanentlyDiscoverPaidMoonsOnLanding != 0))) { text = AddTagToPreviewText("[PINNED]", text); } if (OnSale && SalesRate > 0 && RoutePrice > 0 && ConfigManager.Sales && ConfigManager.ShowTagSale) { text = AddTagToPreviewText($"[SALE {SalesRate}%]", text); } Dictionary> matchingCustomGroups = GetMatchingCustomGroups(); if (ConfigManager.MoonGroupMatchingMethod == "Custom" && matchingCustomGroups.Count > 0 && ConfigManager.ShowTagGroups) { string text2 = string.Empty; if (matchingCustomGroups.Count > 1) { text2 = string.Join("/", matchingCustomGroups.Keys); } else if (matchingCustomGroups.Count == 1) { text2 = matchingCustomGroups.Keys.First(); } text = AddTagToPreviewText("[" + text2.Trim().ToUpper() + "]", text); } else if (ConfigManager.MoonGroupMatchingMethod == "Tag") { List contentTags = ((ExtendedContent)ExtendedLevel).ContentTags; string text3 = string.Empty; if (contentTags.Count > 1) { text3 = string.Join("/", contentTags.Select((ContentTag tag) => tag.contentTagName.ToUpper())); } else if (contentTags.Count == 1) { text3 = ((object)contentTags.FirstOrDefault()).ToString(); } if (!string.IsNullOrEmpty(text3)) { text = AddTagToPreviewText("[" + text3 + "]", text); } } return text; } private string AddTagToPreviewText(string newTag, string previewText) { if (previewText == string.Empty) { previewText = "\n *"; } string[] array = previewText.Split('\n', StringSplitOptions.RemoveEmptyEntries); previewText = ((array[^1].Length + newTag.Length >= ConfigManager.TerminalTagLineWidth && !(array[^1] == " *")) ? (previewText + "\n * " + newTag) : (previewText + " " + newTag)); return previewText; } private int CalculatePrice() { string text = Name + ": Calculating price.. "; int num = OriginalPrice; text += $"Original price -> {OriginalPrice}"; if (BuyCount > 0) { if (ConfigManager.DiscountMode) { float discountRate = Plugin.GetDiscountRate(BuyCount); num = (int)((float)num * discountRate); if (num <= 0 && discountRate > 0f) { num = 1; } text += $", Discount -> {num}"; } else if (ConfigManager.UnlockMode) { num = 0; text += $", Unlock -> {num}, "; } } if (OnSale && num > 0) { num = (int)((float)(num * (100 - SalesRate)) / 100f); if (num <= 0) { num = 1; } text += $", Sales rate ({SalesRate}%) -> {num}"; } Logger.LogDebug(text); return num; } public override string ToString() { int num = VisitCount; if (FreeVisitCount > VisitCount) { num = FreeVisitCount; } string text = "*"; if (IsHidden && !IsLocked) { text = "Hidden"; } else if (!IsHidden && IsLocked) { text = "Locked"; } else if (IsHidden && IsLocked) { text = "H/L"; } string text2 = string.Empty; if (Discovered) { text2 = "*"; } if (NewDiscovery) { text2 = "New"; } if (!DiscoveredOnce) { text2 = "Never"; } if (PermanentlyDiscovered) { text2 = "Permanent"; } string text3 = "-"; if (SalesRate > 0) { text3 = SalesRate + "%"; } string text4 = "*"; if (OriginallyHidden && !OriginallyLocked) { text4 = "Hidden"; } else if (!OriginallyHidden && OriginallyLocked) { text4 = "Locked"; } else if (OriginallyHidden && OriginallyLocked) { text4 = "H/L"; } string text5 = "-"; if (StoryUnlock && !StoryIsUnlocked) { text5 = "Locked"; } else if (StoryUnlock && StoryIsUnlocked) { text5 = "Unlocked"; } return UnlockManager.FormatLogRow(Name, RoutePrice, BuyCount, num, text, text2, text3, OriginalPrice, text4, text5); } } public static class Logger { private static ManualLogSource _mls; internal static void Initialize(ManualLogSource mls) { _mls = mls; } public static void LogDebug(object data) { ManualLogSource mls = _mls; if (mls != null) { mls.LogDebug(data); } } public static void LogMessage(object data) { ManualLogSource mls = _mls; if (mls != null) { mls.LogMessage(data); } } public static void LogInfo(object data) { ManualLogSource mls = _mls; if (mls != null) { mls.LogInfo(data); } } public static void LogWarning(object data) { ManualLogSource mls = _mls; if (mls != null) { mls.LogWarning(data); } } public static void LogError(object data) { ManualLogSource mls = _mls; if (mls != null) { mls.LogError(data); } } public static void LogFatal(object data) { ManualLogSource mls = _mls; if (mls != null) { mls.LogFatal(data); } } public static void Log(LogLevel level, object data) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) ManualLogSource mls = _mls; if (mls != null) { mls.Log(level, data); } } } public class NetworkManager { private static LNetworkMessage _unlockablesMessage; private static LNetworkMessage _buyMoonMessage; private static LNetworkMessage _routeConstellationMessage; private static LNetworkMessage _terminalReadMessage; private static LNetworkEvent _requestSyncEvent; private static LNetworkMessage _alertMessage; private static LNetworkEvent _sendAlertQueueEvent; public static NetworkManager Instance { get; private set; } internal NetworkManager() { if (Instance == null) { Instance = this; } Logger.LogInfo("Register Network messages.."); _unlockablesMessage = LNetworkMessage.Connect("LMU_Unlocks", (Action)null, (Action)ClientReceiveUnlockables, (Action)null); _buyMoonMessage = LNetworkMessage.Connect("LMU_BuyMoonMessage", (Action)ServerReceiveBuyMoon, (Action)null, (Action)null); _routeConstellationMessage = LNetworkMessage.Connect("LMU_RouteConstellationMessage", (Action)ServerReceiveRouteConstellation, (Action)null, (Action)null); _terminalReadMessage = LNetworkMessage.Connect("LMU_TerminalReadMessage", (Action)ServerReceiveTerminalRead, (Action)null, (Action)null); _requestSyncEvent = LNetworkEvent.Connect("LMU_RequestSyncEvent", (Action)ServerReceiveRequestSyncEvent, (Action)null, (Action)null); _alertMessage = LNetworkMessage.Connect("LMU_AlertMessage", (Action)null, (Action)ClientReceiveAlertMessage, (Action)null); _sendAlertQueueEvent = LNetworkEvent.Connect("LMU_SendAlertQueueEvent", (Action)null, (Action)ClientReceiveSendAlertQueueEvent, (Action)null); Logger.LogInfo("NetworkManager created."); } public bool IsServer() { return NetworkManager.Singleton.IsServer; } internal void ServerSendUnlockables(List unlockables, ulong client_id = 0uL) { if (IsServer()) { UnlockSyncData unlockSyncData = new UnlockSyncData(BuildUnlockableSyncData(unlockables), BuildConstellationSyncData()); int num = unlockSyncData.Unlockables?.Count ?? (-1); int valueOrDefault = (unlockSyncData.LethalConstellationsSyncData?.constellations?.Count).GetValueOrDefault(-1); if (client_id != 0) { Logger.LogInfo($"Syncing unlockables to client with id {client_id} (Unlockables={num}, Constellations={valueOrDefault})"); _unlockablesMessage.SendClient(unlockSyncData, client_id); } else { Logger.LogInfo($"Syncing unlockables to all clients (Unlockables={num}, Constellations={valueOrDefault}).."); _unlockablesMessage.SendClients(unlockSyncData); } } } public void ServerSendAlertMessage(Notification alert) { if (IsServer()) { _alertMessage.SendClients(alert); } } internal void ServerSendAlertQueueEvent() { if (IsServer()) { _sendAlertQueueEvent.InvokeClients(); } } public void ClientBuyMoon(string moon) { if (!IsServer()) { UnlockManager.Instance?.ApplyLocalClientMoonPurchasePreview(moon); Logger.LogInfo("Sending buy message to host.."); _buyMoonMessage.SendServer(moon); } } internal void ClientRouteConstellation(string constellationName, int chargedPrice) { if (!IsServer()) { Logger.LogInfo($"Sending constellation route message to host for {constellationName} at price {chargedPrice}.."); _routeConstellationMessage.SendServer(new ConstellationRouteSyncData(constellationName, chargedPrice)); } } internal void ClientReportTerminalRead(TerminalReadSyncData terminalReadData) { if (!IsServer()) { if (terminalReadData == null || string.IsNullOrWhiteSpace(terminalReadData.entryName)) { Logger.LogDebug("Skipping terminal-read report with blank payload."); return; } Logger.LogInfo($"Sending terminal-read message to host for {terminalReadData.readKind}: '{terminalReadData.entryName}'."); _terminalReadMessage.SendServer(terminalReadData); } } internal void ClientRequestSync() { if (!IsServer()) { Logger.LogInfo("Requesting sync from host.."); _requestSyncEvent.InvokeServer(); } } private void ClientReceiveUnlockables(UnlockSyncData payload) { if (payload == null) { return; } if (!IsServer()) { Logger.LogInfo("Receiving LMU data.."); Logger.LogInfo($"LMU sync payload status: UnlockablesNull={payload.Unlockables == null}, UnlockableCount={payload.Unlockables?.Count ?? (-1)}, ConstellationsNull={payload.LethalConstellationsSyncData == null}, ConstellationCount={(payload.LethalConstellationsSyncData?.constellations?.Count).GetValueOrDefault(-1)}"); if (payload.Unlockables == null) { Logger.LogWarning("Received LMU unlock sync with a null unlock list. Skipping import."); } else { UnlockManager.Instance.ImportUnlockableSyncData(payload.Unlockables); } } ApplyConstellationSyncData(payload.LethalConstellationsSyncData); UnlockManager.Instance.ApplyUnlocks(); } private void ClientReceiveAlertMessage(Notification alert) { if (ConfigManager.ShowAlerts) { Logger.LogDebug("Receiving alert message.."); NotificationHelper.AddNotificationToQueue(alert); } } private void ClientReceiveSendAlertQueueEvent() { if (ConfigManager.ShowAlerts) { ((MonoBehaviour)DelayHelper.Instance).StartCoroutine(NotificationHelper.SendQueuedNotifications()); } } private void ServerReceiveBuyMoon(string moon, ulong id) { if (IsServer()) { Logger.LogInfo($"Received buy message for moon {moon} from client with id {id}."); UnlockManager.Instance.BuyMoon(moon); } } private void ServerReceiveRouteConstellation(ConstellationRouteSyncData routeData, ulong id) { if (IsServer()) { if (routeData == null || string.IsNullOrWhiteSpace(routeData.constellationName)) { Logger.LogWarning($"Received invalid constellation route message from client with id {id}."); return; } if (Plugin.ConstellationManager == null || !Plugin.ConstellationManager.TryGetConstellationEconomyTarget(routeData.constellationName, out var target) || target == null) { Logger.LogError($"Received constellation route message for '{routeData.constellationName}' from client with id {id}, but the host could not resolve route pricing. Ignoring client-supplied price {routeData.chargedPrice}."); return; } Logger.LogInfo($"Received constellation route message for {routeData.constellationName} from client with id {id}. Using host-side route price {target.EffectivePrice} instead of client-supplied price {routeData.chargedPrice}."); Plugin.LethalConstellationsExtension?.HandleConstellationRoute(routeData.constellationName, target.EffectivePrice); } } private void ServerReceiveTerminalRead(TerminalReadSyncData terminalReadData, ulong id) { if (!IsServer()) { return; } if (terminalReadData == null || string.IsNullOrWhiteSpace(terminalReadData.entryName) || ProgressionManager.Instance == null) { Logger.LogWarning($"Received invalid terminal-read message from client with id {id}."); return; } TerminalReadKind readKind = terminalReadData.readKind; if (1 == 0) { } bool flag = readKind switch { TerminalReadKind.Bestiary => ProgressionManager.Instance.RecordBestiaryRead(terminalReadData.entryName), TerminalReadKind.StoryLog => ProgressionManager.Instance.RecordStoryLogRead(terminalReadData.entryName), _ => false, }; if (1 == 0) { } if (!flag) { Logger.LogDebug($"Ignoring duplicate terminal-read message from client with id {id} for {terminalReadData.readKind}: '{terminalReadData.entryName}'."); return; } Logger.LogInfo($"Recorded terminal-read message from client with id {id} for {terminalReadData.readKind}: '{terminalReadData.entryName}'."); UnlockManager.Instance?.HandleRecordedTerminalRead(terminalReadData.readKind, terminalReadData.entryName); } private void ServerReceiveRequestSyncEvent(ulong client_id) { Logger.LogInfo($"Received sync request from client with id {client_id}.."); ServerSendUnlockables(UnlockManager.Instance.Unlocks, client_id); } private static List BuildUnlockableSyncData(List unlockables) { return (unlockables == null) ? new List() : (from unlock in unlockables where unlock != null select unlock.BuildSyncData()).ToList(); } private static LethalConstellationsSyncData BuildConstellationSyncData() { LethalConstellationsSyncData result = new LethalConstellationsSyncData(); if (!Plugin.LethalConstellationsPresent || Plugin.LethalConstellationsExtension == null) { return result; } LethalConstellationsSaveData saveData = Plugin.LethalConstellationsExtension.GetSaveData(); saveData.ConstellationRotationMoons = ((Plugin.ConstellationManager != null) ? Plugin.ConstellationManager.GetAllConstellationRotations() : new Dictionary>()); saveData.LocalConstellationDiscoveries = ((Plugin.ConstellationManager != null) ? Plugin.ConstellationManager.GetAllLocalMoonDiscoveries() : new Dictionary>()); return LethalConstellationsSyncData.FromSaveData(saveData); } private static void ApplyConstellationSyncData(LethalConstellationsSyncData syncData) { if (UnlockManager.Instance != null && Plugin.LethalConstellationsPresent && Plugin.LethalConstellationsExtension != null && syncData != null) { LethalConstellationsSaveData lethalConstellationsSaveData = syncData.ToSaveData(); Plugin.LethalConstellationsExtension.LoadSaveData(lethalConstellationsSaveData); Plugin.ConstellationManager?.ReplaceLocalMoonDiscoveries(lethalConstellationsSaveData.LocalConstellationDiscoveries); Plugin.ConstellationManager?.ReplaceAllConstellationRotations(lethalConstellationsSaveData.ConstellationRotationMoons); } } } [BepInPlugin("com.xmods.lethalmoonunlocks", "LethalMoonUnlocks", "2.4.7")] [BepInDependency("imabatby.lethallevelloader", "1.4.11")] [BepInDependency("LethalNetworkAPI", "3.3.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { private readonly Harmony _harmony = new Harmony("LethalMoonUnlocks"); internal static bool LQPresent; internal static bool LethalConstellationsPresent; internal static bool DarmuhsTerminalStuffPresent; internal static bool WeatherTweaksPresent; internal static bool DawnLibPresent; private bool _loaded; internal static Plugin Instance { get; private set; } internal static LethalConstellationsExtension LethalConstellationsExtension { get; private set; } internal static LethalConstellationsManager ConstellationManager { get; private set; } internal static ConstellationUnlockConditionsConfig ConstellationUnlockConditions { get; private set; } internal NetworkManager NetworkManager { get; private set; } internal UnlockManager UnlockManager { get; private set; } internal ProgressionManager ProgressionManager { get; private set; } private void Awake() { if ((Object)(object)Instance == (Object)null) { Instance = this; } ManualLogSource mls = Logger.CreateLogSource("LethalMoonUnlocks"); Logger.Initialize(mls); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Hello world (:"); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Applying patches.."); _harmony.PatchAll(typeof(GameNetworkManagerPatch)); _harmony.PatchAll(typeof(RoundManagerPatch)); _harmony.PatchAll(typeof(StartOfRoundPatch)); _harmony.PatchAll(typeof(TerminalPatch)); _harmony.PatchAll(typeof(TimeOfDayPatch)); _harmony.PatchAll(typeof(HUDManagerPatch)); _harmony.PatchAll(typeof(LLLSaveManagerInitPatch)); _harmony.PatchAll(typeof(DepositItemsDeskPatch)); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Patching complete."); if (!_loaded) { Initialize(); } } public void Start() { if (!_loaded) { Initialize(); } } public void OnDestroy() { if (!_loaded) { Initialize(); } } public void Initialize() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown ((BaseUnityPlugin)this).Logger.LogInfo((object)"Initializing.."); GameObject val = new GameObject("DelayHelper"); Object.DontDestroyOnLoad((Object)(object)val); ((Object)val).hideFlags = (HideFlags)61; val.AddComponent(); SceneManager.sceneUnloaded += AfterGameInit; ConfigManager.Initialize(((BaseUnityPlugin)this).Config); ((BaseUnityPlugin)this).Logger.LogInfo((object)"LethalMoonUnlocks 2.4.7 initialized!"); _loaded = true; } private void AfterGameInit(Scene scene) { if (!(((Scene)(ref scene)).name != "InitScene") || !(((Scene)(ref scene)).name != "InitSceneLANMode")) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Checking for compatible mods.."); if (Chainloader.PluginInfos.ContainsKey("LethalQuantities")) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Lethal Quantities found! Enabling compatibility.."); LQPresent = true; } if (Chainloader.PluginInfos.ContainsKey("com.zealsprince.malfunctions")) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Malfunctions found! Enabling compatibility.."); _harmony.PatchAll(typeof(MalfunctionsCompatibility)); } if (Chainloader.PluginInfos.ContainsKey("com.github.darmuh.LethalConstellations")) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"LethalConstellations found! Enabling compatibility.."); _harmony.PatchAll(typeof(LethalConstellationsPatch)); LethalConstellationsPresent = LoadLethalConstellationsExtension(); } if (Chainloader.PluginInfos.ContainsKey("darmuh.TerminalStuff")) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"darmuhsTerminalStuff found! Enabling compatibility.."); DarmuhsTerminalStuffPresent = true; RegisterTerminalStuffEvent(); _harmony.PatchAll(typeof(TerminalStuffCompatibility)); } if (Chainloader.PluginInfos.ContainsKey("WeatherTweaks")) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"WeatherTweaks found! Enabling compatibility.."); WeatherTweaksPresent = true; _harmony.PatchAll(typeof(WTCompatibility)); } if (Chainloader.PluginInfos.ContainsKey("com.github.teamxiaolan.dawnlib")) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"DawnLib found! Enabling compatibility.."); DawnLibPresent = true; _harmony.PatchAll(typeof(DawnLibMoonCataloguePatch)); _harmony.PatchAll(typeof(DawnLibSaveDataPatch)); } ConfigManager.RefreshConfig(); ConstellationUnlockConditions?.RefreshDefinitions(); if (ConfigManager.TerminalScrollAmount > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"TerminalScrollAmount is set to a positive value! Patching scroll amount.."); _harmony.PatchAll(typeof(PlayerControllerBPatch)); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Unpatching other terminal scroll.."); MethodInfo method = typeof(PlayerControllerB).GetMethod("ScrollMouse_performed", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[1] { typeof(CallbackContext) }, null); ((BaseUnityPlugin)this).Logger.LogInfo((object)method.Name); _harmony.Unpatch((MethodBase)method, (HarmonyPatchType)1, "imabatby.lethallevelloader"); } NetworkManager = new NetworkManager(); UnlockManager = new UnlockManager(); ProgressionManager = new ProgressionManager(); SceneManager.sceneUnloaded -= AfterGameInit; } } private void RegisterTerminalStuffEvent() { MoonsPlus.UpdateMoonsDisplayed.AddListener((ParameterEvent>)TerminalStuffCompatibility.OnUpdateMoonsDisplayed); } private bool LoadLethalConstellationsExtension() { try { LethalConstellationsExtension lethalConstellationsExtension = new LethalConstellationsExtension(); LethalConstellationsManager lethalConstellationsManager = new LethalConstellationsManager(lethalConstellationsExtension); lethalConstellationsExtension.SetManager(lethalConstellationsManager); LethalConstellationsExtension = lethalConstellationsExtension; ConstellationManager = lethalConstellationsManager; ConstellationUnlockConditions = new ConstellationUnlockConditionsConfig(); return true; } catch (Exception arg) { ((BaseUnityPlugin)this).Logger.LogError((object)$"Failed to load LethalConstellations compatibility due to {arg}"); LethalConstellationsExtension = null; ConstellationManager = null; ConstellationUnlockConditions = null; return false; } } internal static int GetDiscountPercentOff(int discountNumber) { List discounts = ConfigManager.Discounts; if (discounts.Count == 0) { return 0; } discountNumber = Mathf.Clamp(discountNumber, 1, discounts.Count); return Mathf.Clamp(discounts[discountNumber - 1], 0, 100); } internal static float GetDiscountRate(int discount_number) { int discountPercentOff = GetDiscountPercentOff(discount_number); return (float)(100 - discountPercentOff) / 100f; } } public static class PluginMetadata { public const string PLUGIN_GUID = "com.xmods.lethalmoonunlocks"; public const string PLUGIN_NAME = "LethalMoonUnlocks"; public const string PLUGIN_VERSION = "2.4.7"; } internal class ProgressionManager { private readonly HashSet _readBestiaryEntries = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly HashSet _readStoryLogs = new HashSet(StringComparer.OrdinalIgnoreCase); internal static ProgressionManager Instance { get; private set; } internal int PaintingsSold { get; set; } internal IReadOnlyCollection ReadBestiaryEntries => _readBestiaryEntries; internal IReadOnlyCollection ReadStoryLogs => _readStoryLogs; internal ProgressionManager() { if (Instance == null) { Instance = this; } Reset(); } internal ProgressionSaveData GetSaveData() { return new ProgressionSaveData { PaintingsSold = PaintingsSold, ReadBestiaryEntries = _readBestiaryEntries.OrderBy((string name) => name, StringComparer.OrdinalIgnoreCase).ToList(), ReadStoryLogs = _readStoryLogs.OrderBy((string name) => name, StringComparer.OrdinalIgnoreCase).ToList() }; } internal void LoadSaveData(ProgressionSaveData progressionSaveData) { Reset(); if (progressionSaveData != null) { PaintingsSold = progressionSaveData.PaintingsSold; LoadEntries(progressionSaveData.ReadBestiaryEntries, _readBestiaryEntries); LoadEntries(progressionSaveData.ReadStoryLogs, _readStoryLogs); } } internal void Reset() { PaintingsSold = 0; _readBestiaryEntries.Clear(); _readStoryLogs.Clear(); } internal bool RecordBestiaryRead(string entryName) { return RecordRead(_readBestiaryEntries, entryName, "bestiary"); } internal bool RecordStoryLogRead(string entryName) { return RecordRead(_readStoryLogs, entryName, "story log"); } internal bool HasReadBestiaryEntry(string entryName) { string text = NormalizeEntryName(entryName); return text.Length > 0 && _readBestiaryEntries.Contains(text); } internal bool HasReadStoryLog(string entryName) { string text = NormalizeEntryName(entryName); return text.Length > 0 && _readStoryLogs.Contains(text); } private static string NormalizeEntryName(string entryName) { return string.IsNullOrWhiteSpace(entryName) ? string.Empty : entryName.Trim(); } private static void LoadEntries(IEnumerable source, HashSet target) { if (source == null) { return; } foreach (string item in source) { string text = NormalizeEntryName(item); if (text.Length > 0) { target.Add(text); } } } private static bool RecordRead(HashSet target, string entryName, string categoryName) { string text = NormalizeEntryName(entryName); if (text.Length == 0) { Logger.LogDebug("Ignoring blank " + categoryName + " progression entry."); return false; } if (!target.Add(text)) { Logger.LogDebug(categoryName + ": '" + text + "' was already recorded."); return false; } Logger.LogInfo("Recorded " + categoryName + " progression entry '" + text + "'."); return true; } } internal static class RandomHelper { private static readonly object RandomLock = new object(); private static readonly Random Random = new Random(Environment.TickCount ^ (int)DateTime.UtcNow.Ticks); internal static int Range(int minInclusive, int maxExclusive) { lock (RandomLock) { return Random.Next(minInclusive, maxExclusive); } } internal static bool Chance(int percent) { if (percent <= 0) { return false; } if (percent >= 100) { return true; } return Range(0, 100) < percent; } internal static List Select(List objects, int amount) { if (objects.Count < amount || objects.Count == 0 || objects == null) { return objects; } List list = new List(objects); List list2 = new List(); while (list2.Count < amount) { list2.Add(list[Range(0, list.Count)]); list.Remove(list2.Last()); } if (list2.Count < amount) { Logger.LogWarning("Couldn't select the desired amount of elements!"); } return list2; } internal static List SelectWeighted(Dictionary objects, int amount) { if (objects.Count < amount || objects.Count == 0 || objects == null) { return new List(objects.Keys); } Dictionary dictionary = new Dictionary(objects); List list = new List(); while (list.Count < amount) { int maxExclusive = dictionary.Sum((KeyValuePair entry) => entry.Value); int num = Range(0, maxExclusive); T val = default(T); foreach (KeyValuePair item in dictionary) { if (item.Value != 0) { if (num < item.Value) { val = item.Key; break; } num -= item.Value; } } if (val == null) { break; } list.Add(val); dictionary.Remove(val); } if (list.Count < amount) { Logger.LogWarning("Couldn't select the desired amount of elements!"); } return list; } internal static Dictionary CalculateBiasedWeights(List unlocks, float bias) { Dictionary dictionary = new Dictionary(); if (unlocks.Count < 1) { return dictionary; } Dictionary dictionary2 = unlocks.ToDictionary((LMUnlockable unlock) => unlock, (LMUnlockable unlock) => ConfigManager.CheapMoonBiasIgnorePriceChanges ? Math.Clamp(unlock.OriginalPrice, 1, int.MaxValue) : Math.Clamp(unlock.ExtendedLevel.RoutePrice, 1, int.MaxValue)); double num = dictionary2.Values.Average(); foreach (LMUnlockable unlock in unlocks) { double x = num / (double)dictionary2[unlock]; long num2 = Math.Clamp((long)Math.Round(Math.Pow(x, bias) * 1000.0), 1L, int.MaxValue / (unlocks.Count + 1)); dictionary[unlock] = (int)num2; } Logger.LogDebug("Cheap moon bias: Assigned the following weights: [ " + string.Join(", ", dictionary.Select((KeyValuePair weight) => weight.Key.Name + ":" + weight.Value)) + " ]"); return dictionary; } } internal static class SaveManager { private static readonly string[] CurrentSaveKeys = new string[8] { "LMU_Unlockables", "LMU_QuotaCount", "LMU_DayCount", "LMU_QuotaUnlocksCount", "LMU_QuotaDiscountsCount", "LMU_QuotaFullDiscountsCount", "LMU_Progression", "LMU_LethalConstellations" }; internal static Dictionary Savedata => Load(); private static bool HasCurrentSaveData(string currentSave) { return CurrentSaveKeys.Any((string key) => ES3.KeyExists(key, currentSave)); } private static Dictionary Load() { Logger.LogInfo("Loading save data.."); string currentSaveFileName = GameNetworkManager.Instance.currentSaveFileName; Dictionary dictionary = new Dictionary(); if (HasCurrentSaveData(currentSaveFileName)) { if (ES3.KeyExists("LMU_Unlockables", currentSaveFileName)) { List list = ES3.Load>("LMU_Unlockables", currentSaveFileName); dictionary.Add("LMU_Unlockables", list); Logger.LogInfo("Found LMU_Unlockables: " + string.Join(", ", list.Select((LMUnlockable unlock) => unlock.Name))); } if (ES3.KeyExists("LMU_QuotaCount", currentSaveFileName)) { int num = ES3.Load("LMU_QuotaCount", currentSaveFileName); dictionary.Add("LMU_QuotaCount", num); Logger.LogInfo($"Found LMU_QuotaCount: {num}"); } if (ES3.KeyExists("LMU_DayCount", currentSaveFileName)) { int num2 = ES3.Load("LMU_DayCount", currentSaveFileName); dictionary.Add("LMU_DayCount", num2); Logger.LogInfo($"Found LMU_DayCount: {num2}"); } if (ES3.KeyExists("LMU_QuotaUnlocksCount", currentSaveFileName)) { int num3 = ES3.Load("LMU_QuotaUnlocksCount", currentSaveFileName); dictionary.Add("LMU_QuotaUnlocksCount", num3); Logger.LogInfo($"Found LMU_QuotaUnlocksCount: {num3}"); } if (ES3.KeyExists("LMU_QuotaDiscountsCount", currentSaveFileName)) { int num4 = ES3.Load("LMU_QuotaDiscountsCount", currentSaveFileName); dictionary.Add("LMU_QuotaDiscountsCount", num4); Logger.LogInfo($"Found LMU_QuotaDiscountsCount: {num4}"); } if (ES3.KeyExists("LMU_QuotaFullDiscountsCount", currentSaveFileName)) { int num5 = ES3.Load("LMU_QuotaFullDiscountsCount", currentSaveFileName); dictionary.Add("LMU_QuotaFullDiscountsCount", num5); Logger.LogInfo($"Found LMU_QuotaFullDiscountsCount: {num5}"); } if (ES3.KeyExists("LMU_Progression", currentSaveFileName)) { ProgressionSaveData progressionSaveData = ES3.Load("LMU_Progression", currentSaveFileName); dictionary.Add("LMU_Progression", progressionSaveData); Logger.LogInfo($"Found LMU_Progression: PaintingsSold={progressionSaveData.PaintingsSold}, BestiaryReads={progressionSaveData.ReadBestiaryEntries?.Count ?? 0}, StoryLogReads={progressionSaveData.ReadStoryLogs?.Count ?? 0}"); } if (ES3.KeyExists("LMU_LethalConstellations", currentSaveFileName)) { LethalConstellationsSaveData lethalConstellationsSaveData = ES3.Load("LMU_LethalConstellations", currentSaveFileName); dictionary.Add("LMU_LethalConstellations", lethalConstellationsSaveData); Logger.LogInfo($"Found LMU_LethalConstellations: {(lethalConstellationsSaveData?.Constellations?.Count).GetValueOrDefault()} constellations, rotations={(lethalConstellationsSaveData?.ConstellationRotationMoons?.Count).GetValueOrDefault()}, locals={(lethalConstellationsSaveData?.LocalConstellationDiscoveries?.Count).GetValueOrDefault()}"); } return dictionary; } if (ES3.KeyExists("LMU_UnlockedMoons", currentSaveFileName)) { Dictionary dictionary2 = ES3.Load>("LMU_UnlockedMoons", currentSaveFileName); dictionary.Add("LMU_UnlockedMoons", dictionary2); Logger.LogInfo("Found deprecated LMU_UnlockedMoons: " + string.Join(", ", dictionary2)); } if (ES3.KeyExists("LMU_OriginalMoonPrices", currentSaveFileName)) { Dictionary dictionary3 = ES3.Load>("LMU_OriginalMoonPrices", currentSaveFileName); dictionary.Add("LMU_OriginalMoonPrices", dictionary3); Logger.LogInfo("Found deprecated LMU_OriginalMoonPrices: " + string.Join(", ", dictionary3)); } if (ES3.KeyExists("UnlockedMoons", currentSaveFileName)) { List list2 = ES3.Load>("UnlockedMoons", currentSaveFileName); dictionary.Add("UnlockedMoons", list2); Logger.LogInfo("Found Permanent Moons data UnlockedMoons: " + string.Join(", ", list2)); } if (ES3.KeyExists("MoonQuotaNum", currentSaveFileName)) { int num6 = ES3.Load("MoonQuotaNum", currentSaveFileName); dictionary.Add("MoonQuotaNum", num6); Logger.LogInfo($"Found Permanent Moons data MoonQuotaNum: {num6}"); } return dictionary; } internal static void StoreSaveData() { Logger.LogInfo("Saving data.."); string currentSaveFileName = GameNetworkManager.Instance.currentSaveFileName; if (UnlockManager.Instance.Unlocks.Count != 0) { ES3.Save>("LMU_Unlockables", UnlockManager.Instance.Unlocks, currentSaveFileName); Logger.LogInfo("Saving LMU_Unlockables.."); UnlockManager.Instance.LogUnlockables(debug: true); } else if (ES3.KeyExists("LMU_Unlockables", currentSaveFileName)) { ES3.DeleteKey("LMU_Unlockables", currentSaveFileName); } if (UnlockManager.Instance.QuotaCount > 0) { ES3.Save("LMU_QuotaCount", UnlockManager.Instance.QuotaCount, currentSaveFileName); Logger.LogInfo("Saving LMU_QuotaCount: " + string.Join(", ", UnlockManager.Instance.QuotaCount)); } else if (ES3.KeyExists("LMU_QuotaCount", currentSaveFileName)) { ES3.DeleteKey("LMU_QuotaCount", currentSaveFileName); } if (UnlockManager.Instance.DayCount > 0) { ES3.Save("LMU_DayCount", UnlockManager.Instance.DayCount, currentSaveFileName); Logger.LogInfo("Saving LMU_DayCount: " + string.Join(", ", UnlockManager.Instance.DayCount)); } else if (ES3.KeyExists("LMU_DayCount", currentSaveFileName)) { ES3.DeleteKey("LMU_DayCount", currentSaveFileName); } if (UnlockManager.Instance.QuotaUnlocksCount > 0) { ES3.Save("LMU_QuotaUnlocksCount", UnlockManager.Instance.QuotaUnlocksCount, currentSaveFileName); Logger.LogInfo("Saving LMU_QuotaUnlocksCount: " + string.Join(", ", UnlockManager.Instance.QuotaUnlocksCount)); } else if (ES3.KeyExists("LMU_QuotaUnlocksCount", currentSaveFileName)) { ES3.DeleteKey("LMU_QuotaUnlocksCount", currentSaveFileName); } if (UnlockManager.Instance.QuotaDiscountsCount > 0) { ES3.Save("LMU_QuotaDiscountsCount", UnlockManager.Instance.QuotaDiscountsCount, currentSaveFileName); Logger.LogInfo("Saving LMU_QuotaDiscountsCount: " + string.Join(", ", UnlockManager.Instance.QuotaDiscountsCount)); } else if (ES3.KeyExists("LMU_QuotaDiscountsCount", currentSaveFileName)) { ES3.DeleteKey("LMU_QuotaDiscountsCount", currentSaveFileName); } if (UnlockManager.Instance.QuotaFullDiscountsCount > 0) { ES3.Save("LMU_QuotaFullDiscountsCount", UnlockManager.Instance.QuotaFullDiscountsCount, currentSaveFileName); Logger.LogInfo("Saving LMU_QuotaFullDiscountsCount: " + string.Join(", ", UnlockManager.Instance.QuotaFullDiscountsCount)); } else if (ES3.KeyExists("LMU_QuotaFullDiscountsCount", currentSaveFileName)) { ES3.DeleteKey("LMU_QuotaFullDiscountsCount", currentSaveFileName); } if (ProgressionManager.Instance != null) { ProgressionSaveData saveData = ProgressionManager.Instance.GetSaveData(); if (saveData.HasData()) { ES3.Save("LMU_Progression", saveData, currentSaveFileName); Logger.LogInfo("Saving LMU_Progression."); } else if (ES3.KeyExists("LMU_Progression", currentSaveFileName)) { ES3.DeleteKey("LMU_Progression", currentSaveFileName); } } if (Plugin.LethalConstellationsPresent && Plugin.LethalConstellationsExtension != null) { LethalConstellationsSaveData saveData2 = Plugin.LethalConstellationsExtension.GetSaveData(); saveData2.ConstellationRotationMoons = ((Plugin.ConstellationManager != null) ? Plugin.ConstellationManager.GetAllConstellationRotations() : new Dictionary>()); saveData2.LocalConstellationDiscoveries = ((Plugin.ConstellationManager != null) ? Plugin.ConstellationManager.GetAllLocalMoonDiscoveries() : new Dictionary>()); if (saveData2.HasData()) { ES3.Save("LMU_LethalConstellations", saveData2, currentSaveFileName); Logger.LogInfo($"Saving LMU_LethalConstellations: {saveData2.Constellations.Count} constellations, rotations={saveData2.ConstellationRotationMoons.Count}, locals={saveData2.LocalConstellationDiscoveries.Count}"); } else if (ES3.KeyExists("LMU_LethalConstellations", currentSaveFileName)) { ES3.DeleteKey("LMU_LethalConstellations", currentSaveFileName); } } if (ES3.KeyExists("LMU_UnlockedMoons", currentSaveFileName)) { Logger.LogInfo("Deleting deprecated save field: LMU_UnlockedMoons"); ES3.DeleteKey("LMU_UnlockedMoons", currentSaveFileName); } if (ES3.KeyExists("LMU_OriginalMoonPrices", currentSaveFileName)) { Logger.LogInfo("Deleting deprecated save field: LMU_OriginalMoonPrices"); ES3.DeleteKey("LMU_OriginalMoonPrices", currentSaveFileName); } } } public class UnlockManager { private sealed class FiredResetPreservedState { internal HashSet StoryUnlockedMoons { get; } = new HashSet(StringComparer.OrdinalIgnoreCase); internal HashSet StoryUnlockedConstellations { get; } = new HashSet(StringComparer.OrdinalIgnoreCase); internal bool HasData() { return StoryUnlockedMoons.Count > 0 || StoryUnlockedConstellations.Count > 0; } } [Obsolete("Story-lock designation callbacks are obsolete. Do not subscribe to this API anymore; initialize story-gated moons as hidden+locked at startup and call TryReleaseStoryLock* when progression should release the gate.", false)] public delegate List DesignateStoryUnlocks(); private const string DiscoveryTargetModeMoonsOnly = "MoonsOnly"; private const string DiscoveryTargetModeMoonsAndConstellations = "MoonsAndConstellations"; private const string DiscoveryTargetModeConstellationsOnly = "ConstellationsOnly"; private const string DiscoveryTargetModeConstellationsOnlyWithMoonFallback = "ConstellationsOnlyWithMoonFallback"; private int _discoveredFreeCount; private int _discoveredDynamicFreeCount; private int _discoveredPaidCount; public static UnlockManager Instance { get; private set; } internal static string LogFormatString { get; } = "| {0, -20} | {1, 6} | {2, 7} | {3, 7} | {4, 8} | {5, 11} | {6, 5} | {7, 12} | {8, 12} | {9, 11} |"; internal static List LogHeader { get; } = new List(10) { "Name", "Price", "Bought", "Visits", "Catalog", "Discovered", "Sale", "Orig. Price", "Orig. State", "Story Lock" }; internal Terminal Terminal { get; set; } internal List AllLevels { get; private set; } = PatchedContent.ExtendedLevels; public List Unlocks { get; set; } = new List(); internal int QuotaCount { get; set; } internal int DayCount { get; set; } internal int QuotaUnlocksCount { get; set; } internal int QuotaDiscountsCount { get; set; } internal int QuotaFullDiscountsCount { get; set; } internal bool UseConstellationEconomy => Plugin.LethalConstellationsPresent && Plugin.LethalConstellationsExtension != null && Plugin.ConstellationManager != null; internal bool UseConstellationDiscovery => ConfigManager.DiscoveryMode && Plugin.LethalConstellationsPresent && Plugin.LethalConstellationsExtension != null && Plugin.ConstellationManager != null; internal int DiscoveredFreeCount { get { if (UseConstellationDiscovery) { return _discoveredFreeCount; } return (_discoveredFreeCount > DiscoveryFreeCandidates.Count) ? DiscoveryFreeCandidates.Count : _discoveredFreeCount; } set { _discoveredFreeCount = value; } } internal int DiscoveredDynamicFreeCount { get { if (UseConstellationDiscovery) { return _discoveredDynamicFreeCount; } return (_discoveredDynamicFreeCount > DiscoveryDynamicFreeCandidates.Count) ? DiscoveryDynamicFreeCandidates.Count : _discoveredDynamicFreeCount; } set { _discoveredDynamicFreeCount = value; } } internal int DiscoveredPaidCount { get { if (UseConstellationDiscovery) { return _discoveredPaidCount; } return (_discoveredPaidCount > DiscoveryPaidCandidates.Count) ? DiscoveryPaidCandidates.Count : _discoveredPaidCount; } set { _discoveredPaidCount = value; } } internal List FreeMoons => Unlocks.Where((LMUnlockable unlock) => unlock.OriginalPrice == 0).ToList(); internal List DynamicFreeMoons => Unlocks.Where((LMUnlockable unlock) => unlock.RoutePrice == 0).ToList(); internal List PaidMoons => Unlocks.Where((LMUnlockable unlock) => unlock.RoutePrice > 0).ToList(); internal List DiscoveryCandidates => Unlocks.Where((LMUnlockable unlock) => ((!unlock.OriginallyLocked && !unlock.OriginallyHidden && !unlock.StoryUnlock) || (unlock.StoryUnlock && unlock.StoryIsUnlocked)) && !IsGloballyDiscovered(unlock)).ToList(); internal List DiscoveryFreeCandidates => DiscoveryCandidates.Where((LMUnlockable candidate) => candidate.OriginalPrice == 0).ToList(); internal List DiscoveryDynamicFreeCandidates => DiscoveryCandidates.Where((LMUnlockable candidate) => candidate.RoutePrice == 0).ToList(); internal List DiscoveryPaidCandidates => DiscoveryCandidates.Where((LMUnlockable candidate) => candidate.RoutePrice > 0).ToList(); [Obsolete("Story-lock designation callbacks are obsolete. Do not subscribe to this API anymore; initialize story-gated moons as hidden+locked at startup and call TryReleaseStoryLock* when progression should release the gate.", false)] public static event DesignateStoryUnlocks OnCollectStoryLockedMoons; internal static string FormatLogRow(params object[] values) { return string.Format(LogFormatString, values); } internal UnlockManager() { if (Instance == null) { Instance = this; } } public static bool TryReleaseStoryLock(string numberlessPlanetName) { if (!TryReleaseStoryLockInternal(numberlessPlanetName, out var _, out var _)) { return false; } Instance?.IterateUnlocks(); NetworkManager.Instance?.ServerSendUnlockables(Instance?.Unlocks, 0uL); return true; } public static bool TryReleaseStoryLockShowAlert(string numberlessPlanetName) { if (!TryReleaseStoryLockInternal(numberlessPlanetName, out var unlock, out var constellationReleaseResult)) { return false; } Instance?.IterateUnlocks(); NetworkManager.Instance?.ServerSendUnlockables(Instance?.Unlocks, 0uL); NetworkManager.Instance?.ServerSendAlertMessage(new Notification { Header = "Autopilot", Text = "Location data detected!\nQueued for processing.", Key = "LMU_StoryLockReleasedGeneric" }); SendStoryReleaseAlert(unlock, constellationReleaseResult); NetworkManager.Instance?.ServerSendAlertQueueEvent(); return true; } private static bool TryReleaseStoryLockInternal(string numberlessPlanetName, out LMUnlockable unlock, out LethalConstellationsManager.StoryReleaseResult constellationReleaseResult) { unlock = null; constellationReleaseResult = null; if (!ConfigManager.EnableStoryProgression) { Logger.LogInfo("Received request to release story lock but story locks are ignored by user config."); return false; } unlock = Instance?.Unlocks.FirstOrDefault((LMUnlockable u) => u.Name == numberlessPlanetName); if (unlock == null) { Logger.LogWarning("Received request to release story lock but the LMUnlockable associated with the level name was not found!"); return false; } if (!unlock.StoryUnlock) { Logger.LogWarning("Received request to release story lock but the LMUnlockable associated with the level name is not currently inferred as story-gated!"); return false; } unlock.StoryIsUnlocked = true; if (ConfigManager.DiscoveryMode && IsImmediateMoonStoryReleaseBehavior()) { unlock.SetDiscoveryState(discovered: true); } if (Plugin.LethalConstellationsPresent && LethalConstellationsManager.Instance != null) { constellationReleaseResult = LethalConstellationsManager.Instance.ReleaseStoryLockForMoon(numberlessPlanetName); } Logger.LogInfo(unlock.Name + ": Request to release story lock received! Releasing lock.. " + unlock.Name + " now available (for discovery)."); return true; } private static void SendStoryReleaseAlert(LMUnlockable unlock, LethalConstellationsManager.StoryReleaseResult constellationReleaseResult) { if (constellationReleaseResult != null && constellationReleaseResult.AnyAffected) { if (constellationReleaseResult.ImmediateDiscovery) { string text = string.Join(", ", constellationReleaseResult.AffectedConstellations); NetworkManager.Instance?.ServerSendAlertMessage(new Notification { Header = "Autopilot", Text = "Success! New " + constellationReleaseResult.AffectedConstellations.Count.SinglePluralWord("constellation") + " discovered:\n" + text + ".", Key = "LMU_StoryLockReleasedGeneric" }); } else { NetworkManager.Instance?.ServerSendAlertMessage(new Notification { Header = "Autopilot", Text = "Destination unreachable! Status: UNKNOWN. Writing location data to backlog...", IsWarning = true, Key = "LMU_StoryLockReleasedGeneric" }); } } else if (!ConfigManager.DiscoveryMode || IsImmediateMoonStoryReleaseBehavior()) { NetworkManager.Instance?.ServerSendAlertMessage(new Notification { Header = "Autopilot", Text = "Success! New moon discovered:\n" + unlock.ExtendedLevel.SelectableLevel.PlanetName + ".", Key = "LMU_StoryLockReleasedGeneric" }); } else { NetworkManager.Instance?.ServerSendAlertMessage(new Notification { Header = "Autopilot", Text = "Destination unreachable! Status: UNKNOWN. Writing location data to backlog...", IsWarning = true, Key = "LMU_StoryLockReleasedGeneric" }); } } private static bool IsImmediateMoonStoryReleaseBehavior() { return ConfigManager.MoonStoryReleaseBehavior == StoryReleaseBehavior.ImmediateDiscovery; } internal void InitializeUnlocks() { //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Expected O, but got Unknown //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Expected O, but got Unknown //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Expected O, but got Unknown if (AllLevels == null || AllLevels.Count == 0) { Logger.LogFatal("Unable to find levels!"); return; } Logger.LogInfo("Initializing LMUnlockables from Extended levels.."); foreach (ExtendedLevel level in AllLevels) { if ((Object)(object)level == (Object)null || (Object)(object)level.SelectableLevel == (Object)null || level.NumberlessPlanetName == "Liquidation" || level.NumberlessPlanetName == "Gordion" || Unlocks.Any((LMUnlockable unlock) => unlock.Name == level.NumberlessPlanetName)) { string text = string.Empty; if ((Object)(object)level != (Object)null && (Object)(object)level.SelectableLevel != (Object)null) { text = ": " + level.NumberlessPlanetName; } Logger.LogDebug("Skipping level" + text + ".."); } else { Unlocks.Add(new LMUnlockable(level)); } } Unlocks = Unlocks.OrderBy((LMUnlockable unlock) => unlock.OriginalPrice).ToList(); LogUnlockables(debug: true); if (ConfigManager.DisplayTerminalTags || ConfigManager.TerminalFontSizeOverride) { TerminalManager.onBeforePreviewInfoTextAdded -= new PreviewInfoText(ReplaceTerminalPreview); TerminalManager.onBeforePreviewInfoTextAdded += new PreviewInfoText(ReplaceTerminalPreview); } else { TerminalManager.onBeforePreviewInfoTextAdded -= new PreviewInfoText(ReplaceTerminalPreview); } } internal void InitializeUnlocksDawnLib() { if (((Registry)(object)LethalContent.Moons).Count == 0) { Logger.LogFatal("Unable to find levels!"); return; } Logger.LogInfo("Initializing LMUnlockables from DawnLib registry.."); foreach (DawnMoonInfo dawnMoon in ((Registry)(object)LethalContent.Moons).Values) { if (dawnMoon == null || ((DawnBaseInfo)(object)dawnMoon).Key.Key == "test" || dawnMoon.GetNumberlessPlanetName() == "Liquidation" || dawnMoon.GetNumberlessPlanetName() == "Gordion") { string text = string.Empty; if (dawnMoon != null && Object.op_Implicit((Object)(object)dawnMoon.Level)) { text = ": " + dawnMoon.GetNumberlessPlanetName(); } Logger.LogDebug("Skipping level " + text + ".."); } else { LMUnlockable lMUnlockable = Unlocks.FirstOrDefault((LMUnlockable u) => u.ExtendedLevel.SelectableLevel.levelID == dawnMoon.Level.levelID); if (lMUnlockable != null) { lMUnlockable.OverrideDefaultsDawnLib(dawnMoon); } else { Logger.LogWarning("Got moon " + dawnMoon.GetNumberlessPlanetName() + " from DawnLib registry that we didn't previously initialize from LLL. Will probably cause errors or misbehaviour."); } } } Unlocks = Unlocks.OrderBy((LMUnlockable unlock) => unlock.OriginalPrice).ToList(); LogUnlockables(debug: true); } internal void ImportUnlockableData(List newData) { Logger.LogInfo("Importing LMU_Unlockable data.."); if (newData == null) { Logger.LogWarning("Received null LMU_Unlockable data list. Skipping import."); return; } foreach (LMUnlockable newDatum in newData) { if (newDatum == null) { Logger.LogWarning("Received null LMUnlockable entry during import. Skipping entry."); continue; } foreach (LMUnlockable unlock in Unlocks) { if (unlock.Name == newDatum.Name) { unlock.OverrideData(newDatum); } } } } internal void ImportUnlockableSyncData(List newData) { Logger.LogInfo("Importing LMU unlock sync data.."); if (newData == null) { Logger.LogWarning("Received null LMU unlock sync data list. Skipping import."); return; } foreach (LMUnlockableSyncData newDatum in newData) { if (newDatum == null) { Logger.LogWarning("Received null LMUnlockable sync entry during import. Skipping entry."); continue; } foreach (LMUnlockable unlock in Unlocks) { if (string.Equals(unlock.Name, newDatum.name, StringComparison.OrdinalIgnoreCase)) { unlock.ApplySyncData(newDatum); } } } } internal void CollectStoryLockedMoons() { if (UnlockManager.OnCollectStoryLockedMoons == null) { return; } Delegate[] invocationList = UnlockManager.OnCollectStoryLockedMoons.GetInvocationList(); Delegate[] array = invocationList; for (int i = 0; i < array.Length; i++) { DesignateStoryUnlocks designateStoryUnlocks = (DesignateStoryUnlocks)array[i]; try { List list = designateStoryUnlocks(); if (list == null || list.Count == 0) { Logger.LogDebug("Observed story-lock designation callback '" + designateStoryUnlocks.Method.DeclaringType?.FullName + "." + designateStoryUnlocks.Method.Name + "' with no moon names returned. Ignoring callback result."); } else { Logger.LogDebug("Observed obsolete story-lock designation callback '" + designateStoryUnlocks.Method.DeclaringType?.FullName + "." + designateStoryUnlocks.Method.Name + "' with moon names [" + string.Join(", ", list) + "]. Ignoring callback result."); } } catch (Exception ex) { Logger.LogError("Couldn't handle subscriber response while collecting story locked moons! Error: " + ex.Message); } } } internal void IterateUnlocks() { Logger.LogDebug("Iterating states.."); foreach (LMUnlockable unlock in Unlocks) { unlock.IterateState(); } } internal void ApplyUnlocks() { foreach (LMUnlockable unlock in Unlocks) { unlock.ApplyState(); unlock.ApplyVisibility(); } if (Plugin.LethalConstellationsPresent) { Plugin.LethalConstellationsExtension.ApplyUnlocks(); } LogUnlockables(); } public void BuyMoon(string moon) { Logger.LogInfo(moon + ": Moon was bought!"); LMUnlockable lMUnlockable = Unlocks.FirstOrDefault((LMUnlockable candidate) => candidate.Name == moon); if (lMUnlockable == null) { Logger.LogError("Couldn't find moon '" + moon + "' for route progression."); } else { ApplyMoonRouteProgression(lMUnlockable, wasPaid: true, allowTravelDiscovery: true, broadcastState: true); } } internal void ApplyLocalClientMoonPurchasePreview(string moon) { if (string.IsNullOrWhiteSpace(moon)) { return; } LMUnlockable lMUnlockable = Unlocks.FirstOrDefault((LMUnlockable candidate) => string.Equals(candidate.Name, moon, StringComparison.OrdinalIgnoreCase)); if (lMUnlockable == null) { Logger.LogWarning("Couldn't find moon '" + moon + "' for local client route progression preview."); return; } if (ConfigManager.DiscountMode) { if (lMUnlockable.BuyCount < ConfigManager.DiscountsCount) { lMUnlockable.BuyCount++; } } else { lMUnlockable.BuyCount++; } lMUnlockable.RefreshCalculatedPrice(); lMUnlockable.ApplyState(); lMUnlockable.ApplyVisibility(); Logger.LogInfo($"{lMUnlockable.Name}: Applied local client route progression preview. Buy count is now {lMUnlockable.BuyCount}, price is now {lMUnlockable.RoutePrice}."); } internal void ApplyMoonRouteProgression(LMUnlockable unlock, bool wasPaid, bool allowTravelDiscovery, bool broadcastState) { if (unlock == null) { return; } if (wasPaid) { if (ConfigManager.DiscountMode) { if (unlock.BuyCount < ConfigManager.DiscountsCount) { unlock.BuyCount++; } } else { unlock.BuyCount++; } Logger.LogInfo($"{unlock.Name}: Set buy count to {unlock.BuyCount}"); } if (allowTravelDiscovery && ConfigManager.DiscoveryMode) { List triggerMoonDiscoveryCandidates = GetTriggerMoonDiscoveryCandidates(); if (ConfigManager.TravelDiscoveries && (UseConstellationDiscovery ? HasDiscoveryTargets(GetConstellationDiscoveryTargetMode("Travel"), triggerMoonDiscoveryCandidates) : (DiscoveryCandidates.Count > 0)) && RandomHelper.Chance(ConfigManager.TravelDiscoveryChance)) { Logger.LogInfo($"Travel Discovery triggered! (Chance: {ConfigManager.TravelDiscoveryChance}%)"); if (UseConstellationDiscovery) { TryHandleDiscoveryTrigger("Travel", triggerMoonDiscoveryCandidates, (List candidates) => TravelDiscovery(unlock, candidates)); } else { TravelDiscovery(unlock, DiscoveryCandidates); } } } if (broadcastState) { IterateUnlocks(); NetworkManager.Instance.ServerSendUnlockables(Unlocks, 0uL); DelayHelper.Instance.ExecuteAfterDelay(NetworkManager.Instance.ServerSendAlertQueueEvent, 2f); } } internal void LogUnlockables(bool debug = false) { string message2 = FormatLogRow(LogHeader.Cast().ToArray()); string message3 = FormatLogRow(new string('-', 20), new string('-', 6), new string('-', 7), new string('-', 7), new string('-', 8), new string('-', 11), new string('-', 5), new string('-', 12), new string('-', 12), new string('-', 11)); LogLine("| LMUnlockable state table"); if (Plugin.LethalConstellationsPresent && Plugin.LethalConstellationsExtension != null && Plugin.ConstellationManager != null) { Dictionary> dictionary = new Dictionary>(); List list = new List(); foreach (LMUnlockable unlock in Unlocks) { string constellationName = Plugin.LethalConstellationsExtension.GetConstellationName(unlock); if (string.IsNullOrWhiteSpace(constellationName)) { list.Add(unlock); continue; } if (!dictionary.TryGetValue(constellationName, out var value2)) { value2 = (dictionary[constellationName] = new List()); } value2.Add(unlock); } if (dictionary.Count > 0) { string text = Plugin.ConstellationManager.GetCurrentConstellationName() ?? string.Empty; foreach (KeyValuePair> item in dictionary) { string text2 = "| Constellation: " + item.Key; if (!string.IsNullOrWhiteSpace(text) && string.Equals(text, item.Key, StringComparison.OrdinalIgnoreCase)) { text2 += " (Current)"; } LogLine(text2); LogLine(message3); LogLine(message2); LogLine(message3); foreach (LMUnlockable item2 in item.Value) { LogLine(item2.ToString()); } LogLine(message3); } if (list.Count <= 0) { return; } LogLine("| Moons without Constellation"); LogLine(message3); LogLine(message2); LogLine(message3); foreach (LMUnlockable item3 in list) { LogLine(item3.ToString()); } LogLine(message3); Logger.LogWarning(string.Format("Found {0} moon(s) without a LethalConstellations group while constellation grouping is active. If this happens past round initialization something is broken: {1}", list.Count, string.Join(", ", list.Select((LMUnlockable unlock) => unlock.Name)))); return; } } LogLine(message2); foreach (var item4 in Unlocks.Select((LMUnlockable value, int i) => new { i, value })) { if (item4.i % 5 == 0) { LogLine(message3); } LogLine(item4.value.ToString()); } void LogLine(string message) { if (debug) { Logger.LogDebug(message); } else { Logger.LogInfo(message); } } } internal void OnLobbyStart() { ConfigManager.RefreshConfig(); DiscoveredFreeCount = ConfigManager.DiscoveryFreeCountBase; DiscoveredDynamicFreeCount = ConfigManager.DiscoveryDynamicFreeCountBase; DiscoveredPaidCount = ConfigManager.DiscoveryPaidCountBase; bool flag = LoadAndImportSavaData(); if (!flag) { InitializeNewGame(); } TryEvaluateConstellationUnlockConditions(); if (flag && UseConstellationDiscovery) { Plugin.ConstellationManager.ApplyCurrentConstellationVisibility(); } if (ShouldForceDiscoveryShuffleOnLoad(flag)) { ShuffleDiscoverable(); } IterateUnlocks(); NetworkManager.Instance.ServerSendUnlockables(Unlocks, 0uL); DelayHelper.Instance.ExecuteAfterDelay(NetworkManager.Instance.ServerSendAlertQueueEvent, 2f); } internal void OnNewQuota() { QuotaCount++; Logger.LogInfo($"New quota! Completed quota count: {QuotaCount}"); ConfigManager.RefreshConfig(); TryEvaluateConstellationUnlockConditions(); if (ConfigManager.DiscoveryMode) { CollectionExtensions.Do(Unlocks.Where((LMUnlockable unlock) => unlock.NewDiscovery), (Action)delegate(LMUnlockable unlock) { unlock.NewDiscovery = false; }); if (!ConfigManager.DiscoveryNeverShuffle) { Logger.LogInfo("Shuffling moon rotation on new Quota.."); ShuffleDiscoverable(); } List triggerMoonDiscoveryCandidates = GetTriggerMoonDiscoveryCandidates(); if (ConfigManager.QuotaDiscoveries && (UseConstellationDiscovery ? HasDiscoveryTargets(GetConstellationDiscoveryTargetMode("Quota"), triggerMoonDiscoveryCandidates) : (DiscoveryCandidates.Count > 0)) && RandomHelper.Chance(ConfigManager.QuotaDiscoveryChance)) { Logger.LogInfo($"Quota Discovery triggered! (Chance: {ConfigManager.QuotaDiscoveryChance}%)"); if (UseConstellationDiscovery) { TryHandleDiscoveryTrigger("Quota", triggerMoonDiscoveryCandidates, (List candidates) => (ConfigManager.QuotaDiscoveryCheapestGroup && ConfigManager.MoonGroupMatchingMethod == "Custom") ? QuotaDiscoveryGroup(candidates) : QuotaDiscovery(candidates), ConfigManager.QuotaDiscoveryCheapestConstellation); } else if (ConfigManager.QuotaDiscoveryCheapestGroup && ConfigManager.MoonGroupMatchingMethod == "Custom") { QuotaDiscoveryGroup(DiscoveryCandidates); } else { QuotaDiscovery(DiscoveryCandidates); } } } if (ConfigManager.Sales) { RefreshSales(); } IterateUnlocks(); if (!ConfigManager.DiscountMode && ConfigManager.QuotaUnlocks && HasQuotaRewardMoonCandidates() && RandomHelper.Chance(ConfigManager.QuotaUnlockChance) && (ConfigManager.QuotaUnlockMaxCount < 1 || QuotaUnlocksCount < ConfigManager.QuotaUnlockMaxCount)) { Logger.LogInfo($"Quota unlock triggered! (Chance: {ConfigManager.QuotaUnlockChance}%)"); QuotaUnlock(); } if (ConfigManager.DiscountMode) { if (ConfigManager.QuotaDiscounts && HasQuotaRewardMoonCandidates() && RandomHelper.Chance(ConfigManager.QuotaDiscountChance) && (ConfigManager.QuotaDiscountMaxCount < 1 || QuotaDiscountsCount < ConfigManager.QuotaDiscountMaxCount)) { Logger.LogInfo($"Quota Discount triggered! (Chance: {ConfigManager.QuotaDiscountChance}%)"); QuotaDiscount(); } if (ConfigManager.QuotaFullDiscounts && HasQuotaRewardMoonCandidates() && RandomHelper.Chance(ConfigManager.QuotaFullDiscountChance) && (ConfigManager.QuotaFullDiscountMaxCount < 1 || QuotaFullDiscountsCount < ConfigManager.QuotaFullDiscountMaxCount)) { Logger.LogInfo($"Quota Full Discount triggered! (Chance: {ConfigManager.QuotaFullDiscountChance}%)"); QuotaFullDiscount(); } } NetworkManager.Instance.ServerSendUnlockables(Unlocks, 0uL); DelayHelper.Instance.ExecuteAfterDelay(NetworkManager.Instance.ServerSendAlertQueueEvent, 5f); } internal void OnNewDay() { Logger.LogDebug($"DaysUntilDeadlineHUD: {(int)Mathf.Floor(TimeOfDay.Instance.timeUntilDeadline / TimeOfDay.Instance.totalTime)}, DaysUntilDeadline: {TimeOfDay.Instance.daysUntilDeadline}, deadlineDaysAmount: {TimeOfDay.Instance.quotaVariables.deadlineDaysAmount}"); if ((int)Mathf.Floor(TimeOfDay.Instance.timeUntilDeadline / TimeOfDay.Instance.totalTime) == TimeOfDay.Instance.quotaVariables.deadlineDaysAmount || (int)Mathf.Floor(TimeOfDay.Instance.timeUntilDeadline / TimeOfDay.Instance.totalTime) < 0) { DayCount++; Logger.LogInfo($"New day! Completed days: {DayCount}"); Logger.LogInfo("New day is also new quota! Skip new day routine.."); } else if ((int)Mathf.Floor(TimeOfDay.Instance.timeUntilDeadline / TimeOfDay.Instance.totalTime) == 0 && ConfigManager.DiscoveryMode) { Logger.LogInfo("New day is last day of the quota! Not shuffling."); if (ConfigManager.AutoRerouteToCompany) { ExtendedLevel val = ((IEnumerable)AllLevels).FirstOrDefault((Func)((ExtendedLevel level) => level.NumberlessPlanetName == "Galetry")); ExtendedLevel destination; if (ConfigManager.PreferGaletry && (Object)(object)val != (Object)null && !val.IsRouteHidden && !val.IsRouteLocked) { destination = val; } else { destination = ((IEnumerable)AllLevels).FirstOrDefault((Func)((ExtendedLevel level) => level.NumberlessPlanetName == "Gordion")); } if ((Object)(object)destination == (Object)null) { Logger.LogError("Couldn't find reroute destination!"); } else if ((Object)(object)LevelManager.CurrentExtendedLevel != (Object)(object)destination) { string text = ((destination.NumberlessPlanetName == "Gordion") ? "the Company building" : destination.NumberlessPlanetName); Logger.LogInfo("Rerouting ship to " + text + "!"); DelayHelper.Instance.ExecuteAfterDelay(delegate { StartOfRound.Instance.ChangeLevelServerRpc(destination.SelectableLevel.levelID, Terminal.groupCredits); }, 3f); NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "Deadline!", Text = "Auto routing ship to " + text + ".", Key = "LMU_RerouteCompany" }); } else { string text2 = ((destination.NumberlessPlanetName == "Gordion") ? "the Company building" : destination.NumberlessPlanetName); Logger.LogInfo("Already at " + text2 + ". No need to reroute."); } } } else { DayCount++; Logger.LogInfo($"New day! Completed days: {DayCount}"); ConfigManager.RefreshConfig(); if (ConfigManager.DiscoveryMode) { CollectionExtensions.Do(Unlocks.Where((LMUnlockable unlock) => unlock.NewDiscovery), (Action)delegate(LMUnlockable unlock) { unlock.NewDiscovery = false; }); IterateUnlocks(); if (ConfigManager.DiscoveryShuffleEveryDay) { Logger.LogInfo("Shuffling moon rotation on new day!"); ShuffleDiscoverable(); } List triggerMoonDiscoveryCandidates = GetTriggerMoonDiscoveryCandidates(); if (ConfigManager.NewDayDiscoveries && (UseConstellationDiscovery ? HasDiscoveryTargets(GetConstellationDiscoveryTargetMode("NewDay"), triggerMoonDiscoveryCandidates) : (DiscoveryCandidates.Count > 0)) && RandomHelper.Chance(ConfigManager.NewDayDiscoveryChance)) { Logger.LogInfo($"New Day Discovery triggered! (Chance: {ConfigManager.NewDayDiscoveryChance}%)"); if (UseConstellationDiscovery) { TryHandleDiscoveryTrigger("NewDay", triggerMoonDiscoveryCandidates, NewDayDiscovery); } else { NewDayDiscovery(DiscoveryCandidates); } } } if (ConfigManager.Sales && ConfigManager.SalesShuffleDaily) { RefreshSales(); } } IterateUnlocks(); NetworkManager.Instance.ServerSendUnlockables(Unlocks, 0uL); DelayHelper.Instance.ExecuteAfterDelay(NetworkManager.Instance.ServerSendAlertQueueEvent, 3f); } private void RefreshSales() { if (DayCount < ConfigManager.SalesMinDayCount) { Logger.LogInfo($"Skipping moon sales shuffle because completed days {DayCount} is below configured minimum {ConfigManager.SalesMinDayCount}."); CollectionExtensions.Do((IEnumerable)Unlocks, (Action)delegate(LMUnlockable unlock) { unlock.OnSale = false; unlock.SalesRate = 0; }); if (!UseConstellationEconomy) { return; } foreach (LMConstellationUnlockable value in Plugin.LethalConstellationsExtension.ConstellationStates.Values) { value.OnSale = false; value.SalesRate = 0; } Plugin.ConstellationManager.ApplyConstellationState(); } else { CollectionExtensions.Do((IEnumerable)Unlocks, (Action)delegate(LMUnlockable unlock) { unlock.RefreshSale(); }); if (UseConstellationEconomy) { Plugin.ConstellationManager.RefreshConstellationSales(); } } } internal void OnArrive() { LMUnlockable lMUnlockable = Unlocks.FirstOrDefault((LMUnlockable unlock) => unlock.Name == LevelManager.CurrentExtendedLevel.NumberlessPlanetName); if (lMUnlockable != null) { Logger.LogInfo("Visiting moon " + lMUnlockable.Name + "!"); lMUnlockable.VisitMoon(); } TryEvaluateConstellationUnlockConditions(); IterateUnlocks(); NetworkManager.Instance.ServerSendUnlockables(Unlocks, 0uL); } internal void OnLanding(SelectableLevel level) { Unlocks.FirstOrDefault((LMUnlockable unlock) => unlock.ExtendedLevel.SelectableLevel.levelID == level.levelID)?.Land(); } internal void OnResetGame() { switch (ConfigManager.ResetWhenFired) { case ResetWhenFiredBehavior.All: Logger.LogInfo("Resetting all progress on getting fired!"); ScheduleFiredReset(); break; case ResetWhenFiredBehavior.AllButStoryProgression: Logger.LogInfo("Resetting all progress except story progression on getting fired!"); ScheduleFiredReset(CaptureFiredResetPreservedState()); break; default: NetworkManager.Instance.ServerSendUnlockables(Unlocks, 0uL); break; } } internal void OnDisconnect() { ProgressionManager.Instance?.Reset(); Reset(); } internal bool HandleRecordedTerminalRead(TerminalReadKind readKind, string entryName) { string text = (string.IsNullOrWhiteSpace(entryName) ? string.Empty : entryName.Trim()); if (text.Length == 0) { Logger.LogDebug($"Skipping recorded terminal-read handling for blank {readKind} entry name."); return false; } bool flag = false; LMUnlockable unlock = null; LethalConstellationsManager.StoryReleaseResult constellationReleaseResult = null; if (readKind == TerminalReadKind.Bestiary && ConfigManager.LMUStoryProgression) { ProgressionManager instance = ProgressionManager.Instance; if (instance != null && instance.HasReadBestiaryEntry("Old birds")) { flag |= TryReleaseStoryLockInternal("Embrion", out unlock, out constellationReleaseResult); } } if (!(flag | TryEvaluateConstellationUnlockConditions())) { Logger.LogDebug($"No dependent progression changes after recorded {readKind} entry '{text}'."); return false; } IterateUnlocks(); NetworkManager.Instance?.ServerSendUnlockables(Unlocks, 0uL); if (unlock != null) { NetworkManager.Instance?.ServerSendAlertMessage(new Notification { Header = "Autopilot", Text = "Location data detected!\nQueued for processing.", Key = "LMU_StoryLockReleasedGeneric" }); SendStoryReleaseAlert(unlock, constellationReleaseResult); NetworkManager.Instance?.ServerSendAlertQueueEvent(); } return true; } private bool TryEvaluateConstellationUnlockConditions() { if (!Plugin.LethalConstellationsPresent || Plugin.ConstellationManager == null) { return false; } return Plugin.ConstellationManager.EvaluateCustomUnlockConditions(); } private List GetTriggerMoonDiscoveryCandidates() { if (!UseConstellationDiscovery || Plugin.ConstellationManager == null) { return DiscoveryCandidates; } return Plugin.ConstellationManager.GetCurrentConstellationDiscoveryCandidates(); } private static string GetDiscoveryTriggerName(string triggerKey) { if (1 == 0) { } string result = triggerKey switch { "Quota" => "Quota Discovery", "Travel" => "Travel Discovery", "NewDay" => "New Day Discovery", _ => "Discovery", }; if (1 == 0) { } return result; } private string GetConstellationDiscoveryTargetMode(string triggerKey) { if (1 == 0) { } string result = triggerKey switch { "Quota" => ConfigManager.LethalConstellationsQuotaDiscoveryTargetMode, "Travel" => ConfigManager.LethalConstellationsTravelDiscoveryTargetMode, "NewDay" => ConfigManager.LethalConstellationsNewDayDiscoveryTargetMode, _ => "MoonsOnly", }; if (1 == 0) { } return result; } private int GetConstellationDiscoveryChance(string triggerKey) { if (1 == 0) { } int result = triggerKey switch { "Quota" => ConfigManager.LethalConstellationsQuotaDiscoveryChance, "Travel" => ConfigManager.LethalConstellationsTravelDiscoveryChance, "NewDay" => ConfigManager.LethalConstellationsNewDayDiscoveryChance, _ => 0, }; if (1 == 0) { } return result; } private List GetQuotaRewardMoonCandidates() { if (UseConstellationDiscovery && Plugin.ConstellationManager != null) { return Plugin.ConstellationManager.GetQuotaRewardMoonTargets(); } return PaidMoons; } private bool HasQuotaRewardMoonCandidates() { return GetQuotaRewardMoonCandidates().Any((LMUnlockable unlock) => unlock.RoutePrice > 0); } private bool HasDiscoveryTargets(string targetMode, List moonCandidates) { bool flag = moonCandidates.Count > 0; bool flag2 = Plugin.ConstellationManager?.HasUndiscoveredConstellations() ?? false; bool flag3 = Plugin.ConstellationManager?.HasEligibleUndiscoveredConstellations() ?? false; if (1 == 0) { } bool result = targetMode switch { "MoonsOnly" => flag, "MoonsAndConstellations" => flag || flag3, "ConstellationsOnly" => flag3, "ConstellationsOnlyWithMoonFallback" => flag2 ? flag3 : flag, _ => flag, }; if (1 == 0) { } return result; } private void ApplyMoonDiscoveries(List discoveries, bool permanent) { if (discoveries == null || discoveries.Count == 0) { return; } foreach (LMUnlockable discovery in discoveries) { if (UseConstellationDiscovery) { discovery.SetDiscoveryState(discovered: true); } else { discovery.Discovered = true; if (!discovery.DiscoveredOnce) { discovery.DiscoveredOnce = true; discovery.NewDiscovery = true; } } if (permanent) { discovery.PermanentlyDiscovered = true; Logger.LogDebug(discovery.Name + ": Discovery is permanent"); } } if (UseConstellationDiscovery) { if (!permanent) { Plugin.ConstellationManager.AddLocalMoonDiscoveriesForCurrentConstellation(discoveries); } Plugin.ConstellationManager.ApplyCurrentConstellationVisibility(); } } private bool TryDiscoverConstellationForTrigger(string triggerKey, bool preferCheapestConstellation) { if (Plugin.ConstellationManager == null || !Plugin.ConstellationManager.TryDiscoverConstellation(preferCheapestConstellation, out var constellationName)) { return false; } string discoveryTriggerName = GetDiscoveryTriggerName(triggerKey); string constellationWord = Plugin.ConstellationManager.GetConstellationWord(); NotificationHelper.SendChatMessage(discoveryTriggerName + " granted " + constellationWord.ToLowerInvariant() + ":\n" + constellationName + ""); NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = discoveryTriggerName + "!", Text = "Received coordinates for constellation: " + constellationName, IsWarning = true, Key = "LMU_" + triggerKey + "ConstellationDiscovery" }); Logger.LogInfo(discoveryTriggerName + ": Discovered constellation '" + constellationName + "'."); return true; } private bool TryHandleDiscoveryTrigger(string triggerKey, List moonCandidates, Func, bool> moonDiscoveryAction, bool preferCheapestConstellation = false) { if (!UseConstellationDiscovery || Plugin.ConstellationManager == null) { return false; } string constellationDiscoveryTargetMode = GetConstellationDiscoveryTargetMode(triggerKey); bool flag = Plugin.ConstellationManager.HasUndiscoveredConstellations(); bool flag2 = Plugin.ConstellationManager.HasEligibleUndiscoveredConstellations(); int constellationDiscoveryChance = GetConstellationDiscoveryChance(triggerKey); bool flag3 = false; Logger.LogInfo(string.Format("{0}: Handling trigger with target mode '{1}', moon candidates={2}, undiscovered constellations={3}, eligible undiscovered constellations={4}, constellation chance={5}%.", GetDiscoveryTriggerName(triggerKey), constellationDiscoveryTargetMode, moonCandidates.Count, flag ? "yes" : "no", flag2 ? "yes" : "no", constellationDiscoveryChance)); switch (constellationDiscoveryTargetMode) { case "MoonsOnly": return moonCandidates.Count > 0 && moonDiscoveryAction(moonCandidates); case "MoonsAndConstellations": if (moonCandidates.Count > 0) { flag3 |= moonDiscoveryAction(moonCandidates); } if (flag2 && RandomHelper.Chance(constellationDiscoveryChance)) { flag3 |= TryDiscoverConstellationForTrigger(triggerKey, preferCheapestConstellation); } return flag3; case "ConstellationsOnly": if (!flag2) { Logger.LogInfo(GetDiscoveryTriggerName(triggerKey) + ": No eligible undiscovered constellations are available."); return false; } if (!RandomHelper.Chance(constellationDiscoveryChance)) { Logger.LogInfo($"{GetDiscoveryTriggerName(triggerKey)}: Constellation discovery chance missed ({constellationDiscoveryChance}%)."); return false; } return TryDiscoverConstellationForTrigger(triggerKey, preferCheapestConstellation); case "ConstellationsOnlyWithMoonFallback": if (flag) { if (!flag2) { Logger.LogInfo(GetDiscoveryTriggerName(triggerKey) + ": Undiscovered constellations remain story-locked or otherwise ineligible. Moon fallback is blocked."); return false; } if (!RandomHelper.Chance(constellationDiscoveryChance)) { Logger.LogInfo($"{GetDiscoveryTriggerName(triggerKey)}: Constellation discovery chance missed ({constellationDiscoveryChance}%). Moon fallback is blocked until all constellations are discovered."); return false; } return TryDiscoverConstellationForTrigger(triggerKey, preferCheapestConstellation); } return moonCandidates.Count > 0 && moonDiscoveryAction(moonCandidates); default: Logger.LogWarning(GetDiscoveryTriggerName(triggerKey) + ": Unknown target mode '" + constellationDiscoveryTargetMode + "'. Falling back to moon discoveries only."); return moonCandidates.Count > 0 && moonDiscoveryAction(moonCandidates); } } private bool QuotaDiscovery(List candidates) { List list = candidates; list = ((!ConfigManager.CheapMoonBiasQuotaDiscovery) ? RandomHelper.Select(list, ConfigManager.QuotaDiscoveryCount) : RandomHelper.SelectWeighted(RandomHelper.CalculateBiasedWeights(list, ConfigManager.CheapMoonBiasQuotaDiscoveryValue), ConfigManager.QuotaDiscoveryCount)); if (list.Count == 0) { Logger.LogInfo("No moons for Quota Discovery available!"); return false; } ApplyMoonDiscoveries(list, ConfigManager.QuotaDiscoveryPermanent); NotificationHelper.SendChatMessage(list.Count.SinglePluralWord("Discovery") + " granted:\n" + string.Join(", ", list.Select((LMUnlockable ndd) => ndd.Name)) + ""); NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = list.Count.SinglePluralWord("Discovery") + " granted!", Text = "Received coordinates for:\n" + string.Join(", ", list.Select((LMUnlockable unlock) => unlock.Name)), Key = "LMU_NewQuotaDiscovery", ExceptWhenKey = "LMU_NewQuotaDiscoveryGroup" }); Logger.LogInfo("New Quota Discoveries: " + string.Join(", ", list.Select((LMUnlockable unlock) => unlock.Name))); return true; } private bool QuotaDiscoveryGroup(List candidates) { List list = new List(); LMGroup lMGroup = new LMGroup(); foreach (LMUnlockable item in candidates.OrderBy((LMUnlockable c) => c.OriginalPrice)) { Logger.LogInfo("Got cheapest candidate: " + item.Name); Logger.LogInfo("Checking for groups.."); lMGroup = MatchMoonGroup(item, candidates, fallback: false); if (lMGroup.Members.Count > 0) { foreach (LMUnlockable member in lMGroup.Members) { if (!IsGloballyDiscovered(member)) { list.Add(member); } } break; } Logger.LogInfo("Candidate has no group matches. Try next.."); } if (lMGroup.Members.Count <= list.Count) { NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "NOTICE", Text = "Keep the company happy! Route to " + lMGroup.Name + " established.", Key = "LMU_NewQuotaDiscoveryGroup" }); } if (list.Count < 1 && ConfigManager.QuotaDiscoveryCheapestGroupFallback) { Logger.LogInfo("Couldn't match any moons for cheapest group. Fallback to all moons.."); list = candidates; lMGroup = new LMGroup(); } list = ((!ConfigManager.CheapMoonBiasQuotaDiscovery) ? RandomHelper.Select(list, ConfigManager.QuotaDiscoveryCount) : RandomHelper.SelectWeighted(RandomHelper.CalculateBiasedWeights(list, ConfigManager.CheapMoonBiasQuotaDiscoveryValue), ConfigManager.QuotaDiscoveryCount)); if (list.Count == 0) { Logger.LogInfo("No moons for Quota Discovery available!"); return false; } ApplyMoonDiscoveries(list, ConfigManager.QuotaDiscoveryPermanent); string text = string.Empty; if (lMGroup.Name != string.Empty) { text = " in " + lMGroup.Name + ""; } NotificationHelper.SendChatMessage(list.Count.SinglePluralWord("Discovery") + " granted" + text + ":\n" + string.Join(", ", list.Select((LMUnlockable qd) => qd.Name)) + ""); NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = candidates.Count.SinglePluralWord("Discovery") + " granted!", Text = "Received coordinates for:\n" + string.Join(", ", candidates.Select((LMUnlockable unlock) => unlock.Name)), Key = "LMU_NewQuotaDiscovery", ExceptWhenKey = "LMU_NewQuotaDiscoveryGroup" }); Logger.LogInfo("New Quota Discoveries: " + string.Join(", ", candidates.Select((LMUnlockable unlock) => unlock.Name))); return true; } private void QuotaUnlock() { List list = (from unlock in GetQuotaRewardMoonCandidates() where unlock.RoutePrice > 0 select unlock).ToList(); if (ConfigManager.DiscoveryMode) { list = list.Where(IsGloballyDiscovered).ToList(); } if (ConfigManager.QuotaUnlockMaxPrice > 0) { list = list.Where((LMUnlockable moon) => moon.RoutePrice <= ConfigManager.QuotaUnlockMaxPrice).ToList(); } list = ((!ConfigManager.CheapMoonBiasQuotaUnlock) ? RandomHelper.Select(list, ConfigManager.QuotaUnlockCount) : RandomHelper.SelectWeighted(RandomHelper.CalculateBiasedWeights(list, ConfigManager.CheapMoonBiasQuotaUnlockValue), ConfigManager.QuotaUnlockCount)); if (list.Count == 0) { Logger.LogInfo("No moons for Quota Unlock available!"); return; } foreach (LMUnlockable item in list) { item.BuyCount++; item.FreeVisitCount = 1; item.IterateState(); } QuotaUnlocksCount++; if (list.Count > 1) { NotificationHelper.SendChatMessage("New moons unlocked:\n" + string.Join(", ", list.Select((LMUnlockable unlock) => unlock.Name)) + ""); } else if (list.Count == 1) { NotificationHelper.SendChatMessage("New moon unlocked:\n" + list.FirstOrDefault()?.Name + ""); } NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = list.Count.SinglePluralWord("Unlock") + " granted!", Text = "You earned unlocks for:\n" + string.Join(", ", list.Select((LMUnlockable unlock) => unlock.Name)), Key = "LMU_NewQuotaUnlock" }); Logger.LogInfo("New Quota Unlocks: " + string.Join(", ", list.Select((LMUnlockable unlock) => unlock.Name))); } private void QuotaDiscount() { List list = (from unlock in GetQuotaRewardMoonCandidates() where unlock.RoutePrice > 0 select unlock).ToList(); if (ConfigManager.DiscoveryMode) { list = list.Where(IsGloballyDiscovered).ToList(); } if (ConfigManager.QuotaDiscountMaxPrice > 0) { list = list.Where((LMUnlockable moon) => moon.RoutePrice <= ConfigManager.QuotaDiscountMaxPrice).ToList(); } list = ((!ConfigManager.CheapMoonBiasQuotaDiscount) ? RandomHelper.Select(list, ConfigManager.QuotaDiscountCount) : RandomHelper.SelectWeighted(RandomHelper.CalculateBiasedWeights(list, ConfigManager.CheapMoonBiasQuotaDiscountValue), ConfigManager.QuotaDiscountCount)); if (list.Count == 0) { Logger.LogInfo("No moons for Quota Discount available!"); return; } foreach (LMUnlockable item in list) { item.BuyCount++; item.IterateState(); } QuotaDiscountsCount++; if (list.Count == 1) { NotificationHelper.SendChatMessage("Discount granted:\n" + list.First().Name + ""); } else if (list.Count > 1) { NotificationHelper.SendChatMessage("Discounts granted:\n" + string.Join(", ", list.Select((LMUnlockable unlock) => unlock.Name)) + ""); } NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = list.Count.SinglePluralWord("Discount") + " granted!", Text = "You earned discounts for:\n" + string.Join(", ", list.Select((LMUnlockable discount) => discount.Name + " " + Plugin.GetDiscountPercentOff(discount.BuyCount) + "%")), Key = "LMU_NewQuotaDiscount" }); Logger.LogInfo("New Quota Discounts: " + string.Join(", ", list.Select((LMUnlockable unlock) => unlock.Name))); } private void QuotaFullDiscount() { List list = (from unlock in GetQuotaRewardMoonCandidates() where unlock.RoutePrice > 0 select unlock).ToList(); if (ConfigManager.DiscoveryMode) { list = list.Where(IsGloballyDiscovered).ToList(); } if (ConfigManager.QuotaFullDiscountMaxPrice > 0) { list = list.Where((LMUnlockable moon) => moon.RoutePrice <= ConfigManager.QuotaFullDiscountMaxPrice).ToList(); } if (ConfigManager.Discounts[ConfigManager.Discounts.Count - 1] < 100) { list = list.Where((LMUnlockable unlock) => unlock.BuyCount < ConfigManager.DiscountsCount).ToList(); } list = ((!ConfigManager.CheapMoonBiasQuotaFullDiscount) ? RandomHelper.Select(list, ConfigManager.QuotaFullDiscountCount) : RandomHelper.SelectWeighted(RandomHelper.CalculateBiasedWeights(list, ConfigManager.CheapMoonBiasQuotaFullDiscountValue), ConfigManager.QuotaFullDiscountCount)); if (list.Count == 0) { Logger.LogInfo("No moons for Quota Full Discount available!"); return; } foreach (LMUnlockable item in list) { item.BuyCount = ConfigManager.DiscountsCount; item.FreeVisitCount = 1; item.IterateState(); } QuotaFullDiscountsCount++; if (list.Count == 1) { NotificationHelper.SendChatMessage("Full discount granted:\n" + list.First().Name + ""); } else if (list.Count > 1) { NotificationHelper.SendChatMessage("Full discounts granted:\n" + string.Join(", ", list.Select((LMUnlockable unlock) => unlock.Name)) + ""); } NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = " Full " + list.Count.SinglePluralWord("Discount") + " granted!", Text = "You earned full discounts for:\n" + string.Join(", ", list.Select((LMUnlockable unlock) => unlock.Name)), Key = "LMU_NewQuotaFullDiscount" }); Logger.LogInfo("New Quota Full Discounts: " + string.Join(", ", list.Select((LMUnlockable unlock) => unlock.Name))); } private bool NewDayDiscovery(List candidates) { Logger.LogInfo("New Day Discovery Candidates: " + string.Join(", ", candidates.Select((LMUnlockable unlock) => unlock.Name))); LMUnlockable lMUnlockable = Unlocks.FirstOrDefault((LMUnlockable unlock) => unlock.ExtendedLevel.NumberlessPlanetName == LevelManager.CurrentExtendedLevel.NumberlessPlanetName); List list = candidates; string text = "nearby"; if (ConfigManager.NewDayDiscoveryMatchGroup && lMUnlockable != null) { LMGroup lMGroup = MatchMoonGroup(lMUnlockable, candidates, ConfigManager.NewDayDiscoveryMatchGroupFallback); list = lMGroup.Members; if (!string.IsNullOrEmpty(lMGroup.Name)) { text = "in " + lMGroup.Name + ""; } } if (list.Count < 1) { Logger.LogInfo("No discoverable moons found!"); return false; } List list2 = ((!ConfigManager.CheapMoonBiasNewDayDiscovery) ? RandomHelper.Select(list, ConfigManager.NewDayDiscoveryCount) : RandomHelper.SelectWeighted(RandomHelper.CalculateBiasedWeights(list, ConfigManager.CheapMoonBiasNewDayDiscoveryValue), ConfigManager.NewDayDiscoveryCount)); ApplyMoonDiscoveries(list2, ConfigManager.NewDayDiscoveryPermanent); if (list2.Count == 1) { NotificationHelper.SendChatMessage("Autopilot discovered moon suitable for landing " + text + ":\n" + list2.First().Name + ""); Logger.LogInfo("New Day Discoveries: [ " + string.Join(", ", list2.Select((LMUnlockable discovery) => discovery.Name)) + " ]"); } if (list2.Count > 1) { NotificationHelper.SendChatMessage("Autopilot discovered moons suitable for landing " + text + ":\n" + string.Join(", ", list2.Select((LMUnlockable ndd) => ndd.Name)) + ""); Logger.LogInfo("New Day Discovery: [ " + string.Join(", ", list2.Select((LMUnlockable discovery) => discovery.Name)) + " ]"); } NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "New Day " + list2.Count.SinglePluralWord("Discovery") + "!", Text = "Autopilot discovered new " + list2.Count.SinglePluralWord("moon") + " " + text + ".\nMoon catalog updated!", Key = "LMU_NewDayDiscovery" }); Logger.LogInfo("New Day Discoveries: " + string.Join(", ", list2.Select((LMUnlockable unlock) => unlock.Name))); return true; } private bool TravelDiscovery(LMUnlockable unlock, List candidates) { Logger.LogInfo("Travel Discovery Candidates: " + string.Join(", ", candidates.Select((LMUnlockable candidate) => candidate.Name))); List list = candidates; string text = string.Empty; if (ConfigManager.TravelDiscoveryMatchGroup) { LMGroup lMGroup = MatchMoonGroup(unlock, candidates, ConfigManager.TravelDiscoveryMatchGroupFallback); list = lMGroup.Members; if (!string.IsNullOrEmpty(lMGroup.Name)) { text = " to " + lMGroup.Name + ""; } } if (list.Count < 1) { Logger.LogInfo("No discoverable moons found!"); return false; } List list2 = ((!ConfigManager.CheapMoonBiasTravelDiscovery) ? RandomHelper.Select(list, ConfigManager.TravelDiscoveryCount) : RandomHelper.SelectWeighted(RandomHelper.CalculateBiasedWeights(list, ConfigManager.CheapMoonBiasTravelDiscoveryValue), ConfigManager.TravelDiscoveryCount)); ApplyMoonDiscoveries(list2, ConfigManager.TravelDiscoveryPermanent); if (list2.Count > 1) { NotificationHelper.SendChatMessage("Discovered new moons on route" + text + ":\n" + string.Join(", ", list2.Select((LMUnlockable td) => td.Name)) + ""); } else if (list2.Count == 1) { NotificationHelper.SendChatMessage("Discovered new moon on route" + text + ":\n" + list2.First().Name + ""); } Logger.LogInfo("Travel Discovery: [ " + string.Join(", ", list2.Select((LMUnlockable discovery) => discovery.Name)) + " ]"); NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "Travel " + list2.Count.SinglePluralWord("Discovery") + "!", Text = "Autopilot discovered new " + list2.Count.SinglePluralWord("moon") + " during travel" + text + ".\nMoon catalog updated!", Key = "LMU_TravelDiscovery" }); Logger.LogInfo("Travel Discoveries: " + string.Join(", ", list2.Select((LMUnlockable u) => u.Name))); return true; } private LMGroup MatchMoonGroup(LMUnlockable matchingUnlock, List unlocksToMatch, bool fallback) { Logger.LogDebug("Matching moon " + matchingUnlock.Name + ": Matching against = [ " + string.Join(", ", unlocksToMatch.Select((LMUnlockable unlock) => unlock.Name)) + " ]"); switch (ConfigManager.MoonGroupMatchingMethod) { case "Price": { List list4 = new List(); foreach (LMUnlockable item in unlocksToMatch) { if (item.OriginalPrice == matchingUnlock.OriginalPrice) { list4.Add(item); } } if (list4.Count > 0) { Logger.LogInfo("Matching moon " + matchingUnlock.Name + ": Matches by price = [ " + string.Join(", ", list4.Select((LMUnlockable unlock) => unlock.Name)) + " ]"); LMGroup result = new LMGroup(); result.Members = list4; return result; } break; } case "PriceRange": { List list6 = new List(); foreach (LMUnlockable item2 in unlocksToMatch) { if (item2.OriginalPrice >= matchingUnlock.OriginalPrice - ConfigManager.MoonGroupMatchingPriceRange && item2.OriginalPrice <= matchingUnlock.OriginalPrice + ConfigManager.MoonGroupMatchingPriceRange) { list6.Add(item2); } } if (list6.Count > 0) { Logger.LogInfo("Matching moon " + matchingUnlock.Name + ": Matches by price range = [ " + string.Join(", ", list6.Select((LMUnlockable unlock) => unlock.Name)) + " ]"); LMGroup result = new LMGroup(); result.Members = list6; return result; } break; } case "PriceRangeUpper": { List list5 = new List(); foreach (LMUnlockable item3 in unlocksToMatch) { if (item3.OriginalPrice >= matchingUnlock.OriginalPrice && item3.OriginalPrice <= matchingUnlock.OriginalPrice + ConfigManager.MoonGroupMatchingPriceRange) { list5.Add(item3); } } if (list5.Count > 0) { Logger.LogInfo("Matching moon " + matchingUnlock.Name + ": Matches by price range = [ " + string.Join(", ", list5.Select((LMUnlockable unlock) => unlock.Name)) + " ]"); LMGroup result = new LMGroup(); result.Members = list5; return result; } break; } case "Tag": { List list3 = new List(); List contentTags = ((ExtendedContent)matchingUnlock.ExtendedLevel).ContentTags; ContentTag val = contentTags[RandomHelper.Range(0, contentTags.Count)]; foreach (LMUnlockable item4 in unlocksToMatch) { if (((ExtendedContent)item4.ExtendedLevel).ContentTags.Select((ContentTag tag) => tag.contentTagName.ToLower()).Contains(val.contentTagName.ToLower()) && !list3.Contains(item4)) { list3.Add(item4); } } if (list3.Count > 0) { Logger.LogInfo("Matching moon " + matchingUnlock.Name + ": Matches by LLL tags = [ " + string.Join(", ", list3.Select((LMUnlockable unlock) => unlock.Name)) + " ]"); LMGroup result = new LMGroup(); result.Members = list3; return result; } break; } case "Custom": { Dictionary> matchingCustomGroups = matchingUnlock.GetMatchingCustomGroups(); if (matchingCustomGroups == null || matchingCustomGroups.Count == 0) { break; } string text = matchingCustomGroups.Keys.ToList()[RandomHelper.Range(0, matchingCustomGroups.Count)]; if (matchingCustomGroups.Count > 1) { Logger.LogInfo("Matching moon " + matchingUnlock.Name + ": Moon is member of multiple groups. Selected " + text + " for matching."); } List list = matchingCustomGroups[text]; Logger.LogInfo("Matching moon " + matchingUnlock.Name + ": " + text + " members = [ " + string.Join(", ", list) + " ]"); List list2 = new List(); foreach (LMUnlockable item5 in unlocksToMatch) { if (list.Contains(item5.Name)) { list2.Add(item5); } } if (list2.Count <= 0) { break; } Logger.LogInfo("Matching moon " + matchingUnlock.Name + ": Matched by custom group [" + text + "]; Matches = [ " + string.Join(", ", list2.Select((LMUnlockable unlock) => unlock.Name)) + " ]"); LMGroup result = new LMGroup(); result.Name = text; result.Members = list2; return result; } default: Logger.LogError("Missing moon group matching method!"); break; } Logger.LogInfo("No matching moons found!"); if (fallback) { LMGroup result = new LMGroup(); result.Members = unlocksToMatch; return result; } return new LMGroup(); } private void InitializeNewGame() { Logger.LogInfo("New game initialization.."); InitializeBuiltInStoryLocks(); if (ConfigManager.EnableStoryProgression) { CollectStoryLockedMoons(); } if (ConfigManager.DiscoveryMode) { ShuffleDiscoverable(suppressNewDiscovery: true); } if (ConfigManager.Sales) { RefreshSales(); } } private void InitializeBuiltInStoryLocks() { if (ConfigManager.EnableStoryProgression) { if (ConfigManager.LMUStoryProgression) { ForceStoryLockAtStartup("Artifice"); ForceStoryLockAtStartup("Embrion"); } if (ConfigManager.GaletryStoryLock && AllLevels.Any((ExtendedLevel level) => level.NumberlessPlanetName == "Galetry")) { ForceStoryLockAtStartup("Galetry"); } } } private void ForceStoryLockAtStartup(string numberlessPlanetName) { LMUnlockable lMUnlockable = Unlocks.FirstOrDefault((LMUnlockable candidate) => candidate.Name == numberlessPlanetName); if (lMUnlockable == null) { Logger.LogWarning(numberlessPlanetName + ": Unable to force startup story lock because the moon was not found."); return; } lMUnlockable.ForceStoryLockAtStartup(); Logger.LogInfo(lMUnlockable.Name + ": Forced built-in story lock startup state (hidden + locked)."); } private void ShuffleDiscoverable(bool suppressNewDiscovery = false) { Logger.LogInfo("Shuffling discovered moon rotations.. "); foreach (LMUnlockable item in Unlocks.Where((LMUnlockable unlock) => !unlock.OriginallyHidden && !unlock.OriginallyLocked && !unlock.PermanentlyDiscovered)) { if (item.NewDiscovery) { item.NewDiscovery = false; } item.Discovered = false; } RefreshDiscoveryRotationCounts(); ApplyDiscoveryWhitelist(); if (UseConstellationDiscovery) { Plugin.ConstellationManager.ClearAllConstellationRotations(); Plugin.ConstellationManager.ClearLocalMoonDiscoveries(); Plugin.ConstellationManager.RegenerateAllConstellationRotations(); Logger.LogInfo("Regenerated stored LethalConstellations moon rotations."); Plugin.ConstellationManager.ApplyCurrentConstellationVisibility(suppressNewDiscovery); } else { AddFreeToRotation(DiscoveredFreeCount); AddDynamicFreeToRotation(DiscoveredDynamicFreeCount); AddPaidToRotation(DiscoveredPaidCount); } if (Unlocks.Any((LMUnlockable unlock) => unlock.Discovered)) { RerouteShipToFreeMoon(); } else { Logger.LogWarning("All moons would have been hidden from the terminal! Force discovering a free moon.."); LMUnlockable lMUnlockable = Unlocks.Where((LMUnlockable unlock) => unlock.RoutePrice == 0).FirstOrDefault(); if (lMUnlockable == null) { Logger.LogWarning("Can't find any free moon to display in moon catalog! You probably want at least one free moon available at all times.. Falling back to a paid moon!"); lMUnlockable = Unlocks.FirstOrDefault(); } if (lMUnlockable == null) { Logger.LogError("Can't find any moon! No unlockable data initialized. Please check your configs (LMU + LLL). If this persists report it on GitHub or Discord."); return; } if (UseConstellationDiscovery) { lMUnlockable.SetDiscoveryState(discovered: true, suppressNewDiscovery); } else { lMUnlockable.Discovered = true; if (suppressNewDiscovery) { lMUnlockable.DiscoveredOnce = true; lMUnlockable.NewDiscovery = false; } } RerouteShipToFreeMoon(); } if (DayCount > 0) { NotificationHelper.SendChatMessage("Moon catalog updated!"); NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "Moon catalog updated!", Text = "New moons available. Use the computer terminal to route the ship.", Key = "LMU_Shuffle" }); } } private void AddFreeToRotation(int amount) { List list = RandomHelper.Select(DiscoveryFreeCandidates, amount); Logger.LogInfo("New free rotation: [ " + string.Join(", ", list.Select((LMUnlockable moon) => moon.Name)) + " ]"); foreach (LMUnlockable item in list) { item.Discovered = true; } } private void AddDynamicFreeToRotation(int amount) { List list = RandomHelper.Select(DiscoveryDynamicFreeCandidates, amount); Logger.LogInfo("New dynamic free rotation: [ " + string.Join(", ", list.Select((LMUnlockable moon) => moon.Name)) + " ]"); foreach (LMUnlockable item in list) { item.Discovered = true; } } private void AddPaidToRotation(int amount) { List list = ((!ConfigManager.CheapMoonBiasPaidRotation) ? RandomHelper.Select(DiscoveryPaidCandidates, amount) : RandomHelper.SelectWeighted(RandomHelper.CalculateBiasedWeights(DiscoveryPaidCandidates, ConfigManager.CheapMoonBiasPaidRotationValue), amount)); Logger.LogInfo("New paid rotation: [ " + string.Join(", ", list.Select((LMUnlockable moon) => moon.Name)) + " ]"); foreach (LMUnlockable item in list) { item.Discovered = true; } } private void RerouteShipToFreeMoon() { Logger.LogInfo("After shuffling check if we have to reroute to a discovered safe destination.."); if (Unlocks.Any((LMUnlockable unlock) => (unlock.Discovered || unlock.PermanentlyDiscovered) && unlock.Name == LevelManager.CurrentExtendedLevel.NumberlessPlanetName) || LevelManager.CurrentExtendedLevel.NumberlessPlanetName == "Gordion") { Logger.LogInfo("Current moon is discovered. Not rerouting ship."); return; } ExtendedLevel rerouteDestination; if (UseConstellationDiscovery) { List list = (from unlock in Plugin.ConstellationManager.GetCurrentVisibleUnlocks() where !unlock.OriginallyLocked && !unlock.OriginallyHidden && (unlock.Discovered || unlock.PermanentlyDiscovered) && unlock.RoutePrice == 0 select unlock).ToList(); if (list.Count > 0) { rerouteDestination = list[RandomHelper.Range(0, list.Count)].ExtendedLevel; } else { if (!Plugin.ConstellationManager.TryGetCurrentDefaultMoon(out var defaultMoon) || !Object.op_Implicit((Object)(object)defaultMoon?.ExtendedLevel) || (!defaultMoon.Discovered && !defaultMoon.PermanentlyDiscovered)) { Logger.LogWarning("Can't find any free discovered moon in the current constellation, and no discovered default moon fallback is available. Abort auto routing ship!"); return; } rerouteDestination = defaultMoon.ExtendedLevel; Logger.LogInfo("No free discovered moon is available in the current constellation. Falling back to default moon '" + defaultMoon.Name + "'."); } } else { List list2 = DynamicFreeMoons.Where((LMUnlockable unlock) => !unlock.OriginallyLocked && !unlock.OriginallyHidden && (unlock.Discovered || unlock.PermanentlyDiscovered)).ToList(); if (list2.Count < 1) { Logger.LogWarning("Can't find any free and discovered moon! You probably want at least one free moon available at all times.. Abort auto routing ship!"); return; } rerouteDestination = list2[RandomHelper.Range(0, list2.Count)].ExtendedLevel; } Logger.LogInfo("Current moon is not discovered! Rerouting ship to " + rerouteDestination.NumberlessPlanetName + ".."); if (DayCount > 0) { NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "Dangerous conditions!", Text = "Conditions too dangerous to stay in orbit! Auto routing ship to a safe moon..", Key = "LMU_RerouteFree" }); } DelayHelper.Instance.ExecuteAfterDelay(delegate { StartOfRound.Instance.ChangeLevelServerRpc(rerouteDestination.SelectableLevel.levelID, Terminal.groupCredits); }, 3.5f); } private void ApplyDiscoveryWhitelist() { if (!ConfigManager.DiscoveryMode || ConfigManager.DiscoveryWhitelistMoons.Count <= 0) { return; } Logger.LogInfo("Whitelist: " + string.Join(", ", ConfigManager.DiscoveryWhitelistMoons)); foreach (string discoveryWhitelistMoon in ConfigManager.DiscoveryWhitelistMoons) { bool flag = false; foreach (LMUnlockable unlock in Unlocks) { if (unlock.Name.Contains(discoveryWhitelistMoon.Trim(), StringComparison.OrdinalIgnoreCase)) { flag = true; if (UseConstellationDiscovery) { unlock.PermanentlyDiscovered = true; unlock.DiscoveredOnce = true; unlock.NewDiscovery = false; Logger.LogDebug("Whitelist entry set to permanently discovered: " + discoveryWhitelistMoon); } else { unlock.Discovered = true; Logger.LogDebug("Whitelist entry set to discovered: " + discoveryWhitelistMoon); } break; } } if (!flag) { Logger.LogWarning("Couldn't match whitelist entry! Is this a valid moon name: " + discoveryWhitelistMoon + " ?"); } } } private bool ShouldForceDiscoveryShuffleOnLoad(bool loadSuccess) { if (!loadSuccess || !ConfigManager.DiscoveryMode) { return false; } if (UseConstellationDiscovery) { return false; } return Unlocks.All((LMUnlockable unlock) => !unlock.Discovered); } private void ScheduleFiredReset(FiredResetPreservedState preservedState = null) { ProgressionManager.Instance?.Reset(); Reset(); InitializeUnlocks(); DelayHelper.Instance.ExecuteAfterDelay(delegate { InitializeNewGame(); RestoreFiredResetPreservedState(preservedState); IterateUnlocks(); NetworkManager.Instance.ServerSendUnlockables(Unlocks, 0uL); }, 8f); } private FiredResetPreservedState CaptureFiredResetPreservedState() { FiredResetPreservedState firedResetPreservedState = new FiredResetPreservedState(); foreach (LMUnlockable item in Unlocks.Where((LMUnlockable unlock) => unlock != null && unlock.StoryUnlock && unlock.StoryIsUnlocked && !string.IsNullOrWhiteSpace(unlock.Name))) { firedResetPreservedState.StoryUnlockedMoons.Add(item.Name); } if (Plugin.LethalConstellationsPresent && Plugin.LethalConstellationsExtension != null) { foreach (KeyValuePair item2 in Plugin.LethalConstellationsExtension.ConstellationStates.Where((KeyValuePair constellation) => !string.IsNullOrWhiteSpace(constellation.Key) && (constellation.Value?.StoryIsUnlocked ?? false))) { firedResetPreservedState.StoryUnlockedConstellations.Add(item2.Key); } } Logger.LogInfo($"Captured before fired story state: StoryMoons={firedResetPreservedState.StoryUnlockedMoons.Count}, StoryConstellations={firedResetPreservedState.StoryUnlockedConstellations.Count}."); return firedResetPreservedState; } private void RestoreFiredResetPreservedState(FiredResetPreservedState preservedState) { if (preservedState == null || !preservedState.HasData()) { return; } int num = 0; bool flag = ConfigManager.DiscoveryMode && IsImmediateMoonStoryReleaseBehavior(); foreach (string moonName in preservedState.StoryUnlockedMoons) { LMUnlockable lMUnlockable = Unlocks.FirstOrDefault((LMUnlockable candidate) => string.Equals(candidate.Name, moonName, StringComparison.OrdinalIgnoreCase)); if (lMUnlockable == null) { Logger.LogWarning("Unable to restore before fired story state for missing moon '" + moonName + "'."); continue; } if (!lMUnlockable.StoryUnlock) { Logger.LogDebug(moonName + ": Skipping before fired story restore because the moon appears to not be story locked."); continue; } lMUnlockable.StoryIsUnlocked = true; if (flag) { lMUnlockable.SetDiscoveryState(discovered: true, suppressNewDiscovery: true); } num++; } int num2 = 0; bool restoreImmediateDiscovery = ConfigManager.DiscoveryMode && (Plugin.ConstellationManager?.IsImmediateDiscoveryStoryReleaseBehavior() ?? false); if (preservedState.StoryUnlockedConstellations.Count > 0 && Plugin.LethalConstellationsPresent && Plugin.ConstellationManager != null) { num2 = Plugin.ConstellationManager.RestoreStoryUnlockedConstellations(preservedState.StoryUnlockedConstellations, restoreImmediateDiscovery); } Logger.LogInfo($"Restored before fired story state: StoryMoons={num}, StoryConstellations={num2}."); } private bool IsGloballyDiscovered(LMUnlockable unlock) { if (unlock == null) { return false; } if (!UseConstellationDiscovery) { return unlock.Discovered || unlock.PermanentlyDiscovered; } return unlock.PermanentlyDiscovered || Plugin.ConstellationManager.IsDerivedVisibleAnywhere(unlock); } private void RefreshDiscoveryRotationCounts() { if (ConfigManager.DiscoveryFreeCountIncreaseBy > 0) { int num2 = (DiscoveredFreeCount = ConfigManager.DiscoveryFreeCountBase + (ConfigManager.DiscoveryShuffleEveryDay ? DayCount : QuotaCount) * ConfigManager.DiscoveryFreeCountIncreaseBy); Logger.LogInfo($"Increasing DiscoverFreeCount (Base = {ConfigManager.DiscoveryFreeCountBase}, Increase = {(ConfigManager.DiscoveryShuffleEveryDay ? DayCount : QuotaCount) * ConfigManager.DiscoveryFreeCountIncreaseBy}, Result = {num2}, Corrected = {DiscoveredFreeCount})"); } else { DiscoveredFreeCount = ConfigManager.DiscoveryFreeCountBase; Logger.LogInfo($"DiscoveredFreeCount (Config value = {ConfigManager.DiscoveryFreeCountBase}, Corrected = {DiscoveredFreeCount})"); } if (ConfigManager.DiscoveryDynamicFreeCountIncreaseBy > 0) { int num4 = (DiscoveredDynamicFreeCount = ConfigManager.DiscoveryDynamicFreeCountBase + (ConfigManager.DiscoveryShuffleEveryDay ? DayCount : QuotaCount) * ConfigManager.DiscoveryDynamicFreeCountIncreaseBy); Logger.LogInfo($"Increasing DiscoveredDynamicFreeCount (Base = {ConfigManager.DiscoveryDynamicFreeCountBase}, Increase = {(ConfigManager.DiscoveryShuffleEveryDay ? DayCount : QuotaCount) * ConfigManager.DiscoveryDynamicFreeCountIncreaseBy}, Result = {num4}, Corrected = {DiscoveredDynamicFreeCount})"); } else { DiscoveredDynamicFreeCount = ConfigManager.DiscoveryDynamicFreeCountBase; Logger.LogInfo($"DiscoveredDynamicFreeCount (Config value = {ConfigManager.DiscoveryDynamicFreeCountBase}, Corrected = {DiscoveredDynamicFreeCount})"); } if (ConfigManager.DiscoveryPaidCountIncreaseBy > 0) { int num6 = (DiscoveredPaidCount = ConfigManager.DiscoveryPaidCountBase + (ConfigManager.DiscoveryShuffleEveryDay ? DayCount : QuotaCount) * ConfigManager.DiscoveryPaidCountIncreaseBy); Logger.LogInfo($"Increasing DiscoveredPaidCount (Base = {ConfigManager.DiscoveryPaidCountBase}, Increase = {(ConfigManager.DiscoveryShuffleEveryDay ? DayCount : QuotaCount) * ConfigManager.DiscoveryPaidCountIncreaseBy}, Result = {num6}, Corrected = {DiscoveredPaidCount})"); } else { DiscoveredPaidCount = ConfigManager.DiscoveryPaidCountBase; Logger.LogInfo($"DiscoveredPaidCount (Config value = {ConfigManager.DiscoveryPaidCountBase}, Corrected = {DiscoveredPaidCount})"); } } private bool LoadAndImportSavaData() { Dictionary savedata = SaveManager.Savedata; ProgressionManager.Instance?.Reset(); if (savedata != null && (savedata.ContainsKey("LMU_Unlockables") || savedata.ContainsKey("LMU_QuotaCount") || savedata.ContainsKey("LMU_DayCount") || savedata.ContainsKey("LMU_QuotaUnlocksCount") || savedata.ContainsKey("LMU_QuotaDiscountsCount") || savedata.ContainsKey("LMU_QuotaFullDiscountsCount") || savedata.ContainsKey("LMU_Progression") || (savedata.ContainsKey("LMU_LethalConstellations") && Plugin.LethalConstellationsPresent && Plugin.LethalConstellationsExtension != null))) { Logger.LogInfo("LMU save data detected!"); Logger.LogInfo("Loading LMU data from save.."); if (savedata.ContainsKey("LMU_Unlockables")) { ImportUnlockableData((List)savedata["LMU_Unlockables"]); } if (savedata.ContainsKey("LMU_QuotaCount")) { QuotaCount = (int)savedata["LMU_QuotaCount"]; Logger.LogInfo($"Loading QuotaCount: {QuotaCount}."); } if (savedata.ContainsKey("LMU_DayCount")) { DayCount = (int)savedata["LMU_DayCount"]; Logger.LogInfo($"Loading DayCount: {DayCount}."); } if (savedata.ContainsKey("LMU_QuotaUnlocksCount")) { QuotaUnlocksCount = (int)savedata["LMU_QuotaUnlocksCount"]; Logger.LogInfo($"Loading QuotaUnlocksCount: {QuotaUnlocksCount}."); } if (savedata.ContainsKey("LMU_QuotaDiscountsCount")) { QuotaDiscountsCount = (int)savedata["LMU_QuotaDiscountsCount"]; Logger.LogInfo($"Loading QuotaDiscountsCount: {QuotaDiscountsCount}."); } if (savedata.ContainsKey("LMU_QuotaFullDiscountsCount")) { QuotaFullDiscountsCount = (int)savedata["LMU_QuotaFullDiscountsCount"]; Logger.LogInfo($"Loading QuotaFullDiscountsCount: {QuotaFullDiscountsCount}."); } if (savedata.ContainsKey("LMU_Progression")) { ProgressionManager.Instance?.LoadSaveData((ProgressionSaveData)savedata["LMU_Progression"]); Logger.LogInfo($"Loading ProgressionManager state: PaintingsSold={ProgressionManager.Instance?.PaintingsSold}, BestiaryReads={ProgressionManager.Instance?.ReadBestiaryEntries.Count ?? 0}, StoryLogReads={ProgressionManager.Instance?.ReadStoryLogs.Count ?? 0}."); } if (Plugin.LethalConstellationsPresent && Plugin.LethalConstellationsExtension != null) { if (savedata.ContainsKey("LMU_LethalConstellations")) { LethalConstellationsSaveData lethalConstellationsSaveData = (LethalConstellationsSaveData)savedata["LMU_LethalConstellations"]; Plugin.LethalConstellationsExtension.LoadSaveData(lethalConstellationsSaveData); Plugin.ConstellationManager?.ReplaceLocalMoonDiscoveries(lethalConstellationsSaveData?.LocalConstellationDiscoveries); Plugin.ConstellationManager?.ReplaceAllConstellationRotations(lethalConstellationsSaveData?.ConstellationRotationMoons); Plugin.ConstellationManager?.ApplyConstellationState(); Logger.LogInfo($"Loading LethalConstellations state: {(lethalConstellationsSaveData?.Constellations?.Count).GetValueOrDefault()} constellations, rotations={(lethalConstellationsSaveData?.ConstellationRotationMoons?.Count).GetValueOrDefault()}, locals={(lethalConstellationsSaveData?.LocalConstellationDiscoveries?.Count).GetValueOrDefault()}."); } else { Plugin.LethalConstellationsExtension.LoadSaveData(null); Plugin.ConstellationManager?.ReplaceLocalMoonDiscoveries(null); Plugin.ConstellationManager?.ReplaceAllConstellationRotations(null); Plugin.ConstellationManager?.ApplyConstellationState(); Logger.LogInfo("No saved LethalConstellations progression found. Reinitialized constellation definitions."); } } Logger.LogInfo("Finished loading LMU save data."); return true; } if (savedata != null && savedata.ContainsKey("LMU_UnlockedMoons")) { Logger.LogInfo("Legacy LMU save data detected! Migrating.."); Dictionary dictionary = (Dictionary)savedata["LMU_UnlockedMoons"]; foreach (KeyValuePair item in dictionary) { foreach (LMUnlockable unlock in Unlocks) { if (unlock.Name == item.Key) { unlock.BuyCount = item.Value; Logger.LogInfo($"Migrated unlock data for {item.Key}: {item.Value}."); } } } if (savedata.ContainsKey("LMU_QuotaCount")) { QuotaCount = (int)savedata["LMU_QuotaCount"]; Logger.LogInfo($"Migrating QuotaCount: {QuotaCount}."); } Logger.LogInfo("Finished migrating legacy LMU save data."); Logger.LogInfo("Loading done. Applying migrated data before new game init.."); return false; } if (savedata != null && savedata.ContainsKey("UnlockedMoons")) { Logger.LogInfo("Permanent Moons save data detected! Migrating.."); List list = (List)savedata["UnlockedMoons"]; foreach (string item2 in list) { foreach (LMUnlockable unlock2 in Unlocks) { if (item2.Contains(unlock2.Name, StringComparison.OrdinalIgnoreCase)) { unlock2.BuyCount = 1; Logger.LogInfo("Migrated PM unlock for " + unlock2.Name + "."); } } } if (savedata.ContainsKey("MoonQuotaNum")) { QuotaCount = (int)savedata["MoonQuotaNum"]; Logger.LogInfo($"Migrated PM MoonQuotaNum (QuotaCount): {QuotaCount}."); } Logger.LogInfo("Finished migrating Permanent Moons save data."); Logger.LogInfo("Loading done. Applying migrated data before new game init."); return false; } Logger.LogInfo("No save data found! New save.."); return false; } private void Reset() { foreach (LMUnlockable unlock in Unlocks) { unlock.RestoreOriginalState(); } Unlocks.Clear(); QuotaCount = 0; DayCount = 0; QuotaUnlocksCount = 0; QuotaDiscountsCount = 0; QuotaFullDiscountsCount = 0; if (Plugin.LethalConstellationsPresent) { Plugin.LethalConstellationsExtension.Reset(); } } private string ReplaceTerminalPreview(ExtendedLevel extendedLevel, PreviewInfoType infoType) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) if (ConfigManager.TerminalFontSizeOverride) { Terminal.screenText.textComponent.fontSize = ConfigManager.TerminalFontSize; } LMUnlockable lMUnlockable = Unlocks.Where((LMUnlockable unlock) => (Object)(object)unlock.ExtendedLevel == (Object)(object)extendedLevel).FirstOrDefault(); if (lMUnlockable == null) { Logger.LogError("Couldn't get unlock for Terminal preview text replacement!"); return string.Empty; } return lMUnlockable.GetMoonPreviewText(infoType); } } public static class PluginInfo { public const string PLUGIN_GUID = "LethalMoonUnlocks"; public const string PLUGIN_NAME = "LethalMoonUnlocks"; public const string PLUGIN_VERSION = "2.4.7"; } } namespace LethalMoonUnlocks.Util { internal class DelayHelper : MonoBehaviour { [CompilerGenerated] private sealed class d__6 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Action action; public float delay; public DelayHelper <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__6(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(delay); <>1__state = 1; return true; case 1: <>1__state = -1; action(); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } internal static DelayHelper Instance { get; private set; } private void Awake() { if ((Object)(object)Instance == (Object)null) { Instance = this; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); } else { Object.Destroy((Object)(object)((Component)this).gameObject); } } internal void ExecuteAfterDelay(Action action, float delay) { ((MonoBehaviour)this).StartCoroutine(DelayedExecution(action, delay)); } [IteratorStateMachine(typeof(d__6))] private IEnumerator DelayedExecution(Action action, float delay) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__6(0) { <>4__this = this, action = action, delay = delay }; } } [Serializable] public class Notification { [SerializeField] public string Header { get; init; } = ""; [SerializeField] public string Text { get; init; } = ""; [SerializeField] public bool IsWarning { get; init; } = false; [SerializeField] public bool UseSave { get; init; } = false; [SerializeField] public string Key { get; init; } = "LMU_"; [SerializeField] public string ExceptWhenKey { get; init; } = ""; } internal static class NotificationHelper { [CompilerGenerated] private sealed class d__3 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; private Notification 5__1; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__3(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__1 = null; <>1__state = -2; } private bool MoveNext() { //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; if (IsSending) { Logger.LogDebug("Trying to start queue but it's already sending.."); return false; } break; case 1: <>1__state = -1; 5__1 = null; break; } if (Queue.Count > 0) { IsSending = true; 5__1 = Queue.First(); Logger.LogDebug("Sending out alert: key = " + 5__1.Key); HUDManager.Instance.DisplayTip(5__1.Header, 5__1.Text, 5__1.IsWarning, 5__1.UseSave, 5__1.Key); Queue.Remove(5__1); <>2__current = (object)new WaitForSeconds(8f); <>1__state = 1; return true; } IsSending = false; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static readonly List Queue = new List(); private static bool IsSending = false; internal static void AddNotificationToQueue(Notification notification) { foreach (Notification item in Queue) { if (notification.ExceptWhenKey != string.Empty && item.Key.Contains(notification.ExceptWhenKey)) { return; } } Queue.Add(notification); Logger.LogDebug("Queued new alert: key = " + notification.Key); } [IteratorStateMachine(typeof(d__3))] internal static IEnumerator SendQueuedNotifications() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__3(0); } internal static void SendChatMessage(string message) { if (ConfigManager.ChatMessages) { HUDManager.Instance.AddTextToChatOnServer(message, -1); } } internal static string CountToText(this int count) { if (count <= 0) { return string.Empty; } if (count > 0) { int num = count % 10; if (num % 10 == 1) { return count + "st"; } return num switch { 2 => count + "nd", 3 => count + "rd", _ => count + "th", }; } return string.Empty; } internal static string NumberOfWords(this int number, string word) { if (number <= 0) { return string.Empty; } if (number == 1) { return "one " + word; } if (number == 2) { return "two " + word + "s"; } if (number == 3) { return "three " + word + "s"; } if (number > 3) { return number + " " + word + "s"; } return string.Empty; } internal static string SinglePluralWord(this int number, string word) { if (word.EndsWith('y')) { if (number > 1) { return word.Substring(0, word.Length - 1) + "ies"; } return word; } if (number > 1) { return word + "s"; } return word; } } } namespace LethalMoonUnlocks.Patches { [HarmonyPatch] internal class DawnLibMoonCataloguePatch { private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("MoonRegistrationHandler"); return AccessTools.Method(type, "FormatMoonEntry", (Type[])null, (Type[])null); } private static void Postfix(DawnMoonInfo moonInfo, TerminalPurchaseResult result, ref string __result) { if (!ConfigManager.DisplayTerminalTags) { return; } foreach (LMUnlockable unlock in UnlockManager.Instance.Unlocks) { if (unlock.Name == moonInfo.GetNumberlessPlanetName()) { string text = unlock.BuildTagString(); if (!string.IsNullOrEmpty(text)) { __result += text; } } } } } [HarmonyPatch] internal class DawnLibSaveDataPatch { private static MethodBase TargetMethod() { if (!Plugin.DawnLibPresent) { return null; } Type type = AccessTools.TypeByName("Dawn.Internal.DawnNetworker"); return AccessTools.Method(type, "SaveData", (Type[])null, (Type[])null); } private static void Prefix() { if (NetworkManager.Instance != null && NetworkManager.Instance.IsServer() && UnlockManager.Instance != null) { CollectionExtensions.Do((IEnumerable)UnlockManager.Instance.Unlocks, (Action)delegate(LMUnlockable unlock) { unlock.RestoreOriginalState(); }); } } private static void Postfix() { if (NetworkManager.Instance == null || !NetworkManager.Instance.IsServer() || UnlockManager.Instance == null) { return; } foreach (LMUnlockable unlock in UnlockManager.Instance.Unlocks) { unlock.RefreshCalculatedPrice(); unlock.ApplyState(); unlock.ApplyVisibility(); } } } [HarmonyPatch(typeof(DepositItemsDesk))] internal class DepositItemsDeskPatch { [HarmonyPatch("SellItemsOnServer")] [HarmonyPrefix] private static bool SellItemsOnServerPatch(ref DepositItemsDesk __instance) { if (NetworkManager.Instance == null || ProgressionManager.Instance == null || !NetworkManager.Instance.IsServer()) { return true; } if (!ConfigManager.GaletryStoryLock || ProgressionManager.Instance.PaintingsSold >= ConfigManager.GaletryStoryLockPaintingsAmount) { return true; } bool flag = false; foreach (GrabbableObject item in __instance.itemsOnCounter) { if (!((Object)(object)item == (Object)null) && !((Object)(object)item.itemProperties == (Object)null) && item.itemProperties.itemName.Contains("Painting")) { ProgressionManager.Instance.PaintingsSold++; flag = true; } } if (flag) { NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "Company Report", Text = $"Paintings sold: {ProgressionManager.Instance.PaintingsSold}/{ConfigManager.GaletryStoryLockPaintingsAmount}", IsWarning = (ProgressionManager.Instance.PaintingsSold >= ConfigManager.GaletryStoryLockPaintingsAmount), Key = "LMU_GaletryProgress" }); NetworkManager.Instance.ServerSendAlertQueueEvent(); if (ProgressionManager.Instance.PaintingsSold >= ConfigManager.GaletryStoryLockPaintingsAmount && UnlockManager.TryReleaseStoryLock("Galetry")) { NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "Art exhibition!", Text = "The art museum welcomes visitors. Step inside, stare at the art and regain intellectual sustenance.", Key = "LMU_GaletryProgress" }); NetworkManager.Instance.ServerSendAlertQueueEvent(); } } return true; } } [HarmonyPatch(typeof(GameNetworkManager))] internal class GameNetworkManagerPatch { [HarmonyPatch("Disconnect")] [HarmonyPostfix] private static void DisconnectPatch() { Logger.LogInfo("Disconnecting from lobby. Restoring original prices and clearing variables.."); UnlockManager.Instance.OnDisconnect(); } [HarmonyPatch("SaveGame")] [HarmonyPostfix] private static void SaveGameValuesPatch() { if (!NetworkManager.Instance.IsServer()) { return; } bool flag = (Object)(object)StartOfRound.Instance != (Object)null && StartOfRound.Instance.inShipPhase; bool groupCreditsSavingBandAid = ConfigManager.GroupCreditsSavingBandAid; Logger.LogDebug($"inShipPhase: {flag}"); if (!flag && !groupCreditsSavingBandAid) { Logger.LogInfo("Skipping LMU save because the game is not in ship phase. Mid-round LMU progression will roll back on load."); return; } try { Logger.LogInfo("Host is saving game.."); if (!flag) { Logger.LogWarning("Saving LMU data outside ship phase because the legacy compatibility band-aid is enabled."); } SaveManager.StoreSaveData(); } catch (Exception arg) { Logger.LogError($"Failed to save unlock data: {arg}"); } } [HarmonyPatch("ResetSavedGameValues")] [HarmonyPostfix] private static void ResetSavedGameValuesPatch() { Logger.LogInfo("You are fired!"); if (NetworkManager.Instance.IsServer() && ConfigManager.ResetWhenFired != ResetWhenFiredBehavior.Nothing) { UnlockManager.Instance.OnResetGame(); } } } [HarmonyPatch(typeof(HUDManager))] internal class HUDManagerPatch { private static readonly string GameAssemblyName = typeof(HUDManagerPatch).Assembly.GetName().Name; private static readonly string HarmonyAssemblyName = typeof(Harmony).Assembly.GetName().Name; [HarmonyPatch("DisplayTip")] [HarmonyPrefix] private static bool DisplayTipPatch(string headerText, string bodyText, bool isWarning, bool useSave, string prefsKey) { string text = prefsKey ?? string.Empty; if (!ConfigManager.AlertMessageQueueing) { return true; } if (!HUDManager.Instance.CanTipDisplay(isWarning, useSave, text)) { return false; } if (!string.IsNullOrEmpty(bodyText) && headerText == "Route Discovered!" && bodyText.StartsWith("Location: ")) { if (ConfigManager.DiscoveryMode && ConfigManager.MoonStoryReleaseBehavior == StoryReleaseBehavior.HiddenBacklog) { JLLCompatibility.ReplaceJLLAlertDiscovery(bodyText); } else { JLLCompatibility.ReplaceJLLAlert(bodyText); } return false; } if (!text.StartsWith("LMU_", StringComparison.Ordinal)) { string text2 = TryGetAlertCallerAssembly(); string text3 = (string.IsNullOrWhiteSpace(text2) ? "unknown" : text2); string text4 = TryResolveAlertCallerPlugin(text2); string text5 = (string.IsNullOrWhiteSpace(text4) ? "unresolved" : text4); if (!string.IsNullOrWhiteSpace(text4) && ConfigManager.AlertMessageQueueExcludedPlugins.Contains(text4)) { Logger.LogDebug("Bypassing alert queue for excluded plugin: plugin = " + text5 + ", source = " + text3 + ", key = " + text + "!"); return true; } Logger.LogDebug($"Intercepted alert: plugin = {text5}, source = {text3}, header = {headerText}, warning = {isWarning}, useSave = {useSave}, key = {text}!"); Logger.LogDebug("Intercepted alert: body = " + bodyText + "!"); NotificationHelper.AddNotificationToQueue(new Notification { Header = headerText, Text = bodyText, IsWarning = isWarning, UseSave = false, Key = "LMU_Intercept_" + text }); ((MonoBehaviour)DelayHelper.Instance).StartCoroutine(NotificationHelper.SendQueuedNotifications()); return false; } return true; } private static string TryGetAlertCallerAssembly() { StackFrame[] frames = new StackTrace(1, fNeedFileInfo: false).GetFrames(); if (frames == null) { return string.Empty; } StackFrame[] array = frames; foreach (StackFrame stackFrame in array) { Type type = stackFrame.GetMethod()?.DeclaringType; string text = type?.Assembly?.GetName().Name; if (!string.IsNullOrWhiteSpace(text) && !string.Equals(text, GameAssemblyName, StringComparison.Ordinal) && !string.Equals(text, HarmonyAssemblyName, StringComparison.Ordinal) && !string.Equals(text, "DynamicMethodsAssembly", StringComparison.Ordinal) && !(type == typeof(HUDManager))) { return text; } } return string.Empty; } private static string TryResolveAlertCallerPlugin(string callerAssemblyName) { if (string.IsNullOrWhiteSpace(callerAssemblyName)) { return null; } foreach (PluginInfo value in Chainloader.PluginInfos.Values) { if (value != null) { string a = ((object)value.Instance)?.GetType()?.Assembly?.GetName()?.Name; if (string.Equals(a, callerAssemblyName, StringComparison.OrdinalIgnoreCase)) { BepInPlugin metadata = value.Metadata; return (metadata != null) ? metadata.GUID : null; } string a2 = (string.IsNullOrWhiteSpace(value.Location) ? string.Empty : Path.GetFileNameWithoutExtension(value.Location)); if (string.Equals(a2, callerAssemblyName, StringComparison.OrdinalIgnoreCase)) { BepInPlugin metadata2 = value.Metadata; return (metadata2 != null) ? metadata2.GUID : null; } } } return null; } } [HarmonyPatch] internal class LethalConstellationsPatch { [HarmonyPatch] private static class TravelToNewConstellationPatch { private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("LethalConstellations.PluginCore.MenuStuff"); return AccessTools.Method(type, "TravelToNewConstellation", (Type[])null, (Type[])null); } [HarmonyPrefix] private static void Prefix(ref int getPrice, int indexNum) { if (indexNum >= 0 && indexNum < Collections.DisplayConstellations.Count) { ClassMapper val = Collections.DisplayConstellations[indexNum]; if (!string.IsNullOrWhiteSpace(val.consName)) { Plugin.LethalConstellationsExtension?.TryApplyPendingSaveData(); Plugin.LethalConstellationsExtension?.RecordPendingConstellationRoute(val.consName, getPrice); } } } } [HarmonyPatch] private static class GetConstPricePatch { private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("LethalConstellations.PluginCore.LevelStuff"); return AccessTools.Method(type, "GetConstPrice", (Type[])null, (Type[])null); } [HarmonyPostfix] private static void Postfix(ClassMapper item, ref int __result) { if (item == null || Plugin.LethalConstellationsExtension == null) { return; } string text = PositionalPriceModeField?.GetValue(item) as string; if (!(text != "SetPriceByDistance")) { Plugin.LethalConstellationsExtension.TryApplyPendingSaveData(); if (Plugin.LethalConstellationsExtension.TryGetConstellationState(item.consName, out var constellationState) && constellationState != null) { __result = constellationState.GetCalculatedPrice(__result); } } } } private static readonly FieldInfo PositionalPriceModeField = AccessTools.Field(typeof(ClassMapper), "positionalPriceMode"); private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("LethalConstellations.PluginCore.MenuStuff"); return AccessTools.Method(type, "MainMenuText", (Type[])null, (Type[])null); } } [HarmonyPatch] internal class LLLSaveManagerInitPatch { private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("LethalLevelLoader.SaveManager"); return AccessTools.Method(type, "InitializeSave", (Type[])null, (Type[])null); } private static void Prefix() { UnlockManager.Instance.InitializeUnlocks(); } } [HarmonyPatch] internal class LLLSaveManagerSavePatch { private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("LethalLevelLoader.SaveManager"); return AccessTools.Method(type, "SaveGameValues", (Type[])null, (Type[])null); } private static void Prefix() { CollectionExtensions.Do((IEnumerable)UnlockManager.Instance.Unlocks, (Action)delegate(LMUnlockable unlock) { unlock.RestoreOriginalState(); }); } private static void Postfix() { foreach (LMUnlockable unlock in UnlockManager.Instance.Unlocks) { unlock.RefreshCalculatedPrice(); unlock.ApplyState(); unlock.ApplyVisibility(); } } } [HarmonyPatch(typeof(PlayerControllerB), "ScrollMouse_performed", new Type[] { typeof(CallbackContext) })] internal class PlayerControllerBPatch { private static float _scrollAmount = 1f / 3f; private static string CurrentText { get; set; } = ""; private static void ScrollMouse_performed(Scrollbar scrollbar, float scrollDirection) { if ((Object)(object)UnlockManager.Instance.Terminal == (Object)null || ConfigManager.TerminalScrollAmount < 1) { scrollbar.value += scrollDirection * _scrollAmount; return; } if (string.CompareOrdinal(UnlockManager.Instance.Terminal.currentText, CurrentText) != 0) { CurrentText = UnlockManager.Instance.Terminal.currentText; int num = CurrentText.Count((char c) => c.Equals('\n')) + 1; float num2 = (float)num / 25f; _scrollAmount = 1f / (3f * num2 * (float)ConfigManager.TerminalScrollAmount); Logger.LogDebug($"Setting terminal scroll amount to '{_scrollAmount}'.."); } scrollbar.value += scrollDirection * _scrollAmount; } private static IEnumerable Transpiler(IEnumerable instructions) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Expected O, but got Unknown //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Expected O, but got Unknown //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Expected O, but got Unknown return new CodeMatcher(instructions, (ILGenerator)null).MatchForward(false, (CodeMatch[])(object)new CodeMatch[2] { new CodeMatch((OpCode?)OpCodes.Ldarg_0, (object)null, (string)null), new CodeMatch((OpCode?)OpCodes.Ldfld, (object)AccessTools.Field(typeof(PlayerControllerB), "terminalScrollVertical"), (string)null) }).Insert((CodeInstruction[])(object)new CodeInstruction[5] { new CodeInstruction(OpCodes.Ldarg_0, (object)null), new CodeInstruction(OpCodes.Ldfld, (object)AccessTools.Field(typeof(PlayerControllerB), "terminalScrollVertical")), new CodeInstruction(OpCodes.Ldloc_0, (object)null), new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(PlayerControllerBPatch), "ScrollMouse_performed", (Type[])null, (Type[])null)), new CodeInstruction(OpCodes.Ret, (object)null) }).InstructionEnumeration(); } } [HarmonyPatch(typeof(RoundManager))] internal class RoundManagerPatch { [HarmonyPatch("LoadNewLevel")] [HarmonyPostfix] private static void LoadNewLevelPatch(ref SelectableLevel newLevel) { Logger.LogInfo($"Landing on moon {newLevel.PlanetName} with id {newLevel.levelID}"); UnlockManager.Instance.OnLanding(newLevel); } } [HarmonyPatch(typeof(StartOfRound))] internal class StartOfRoundPatch { [HarmonyPrefix] [HarmonyAfter(new string[] { "evaisa.lethallib", "imabatby.lethallevelloader", "com.github.teamxiaolan.dawnlib" })] [HarmonyPatch(typeof(StartOfRound), "Start")] private static void StartOfRoundStartPrefix(StartOfRound __instance, bool __runOriginal) { if (Plugin.DawnLibPresent) { UnlockManager.Instance.InitializeUnlocksDawnLib(); } } [HarmonyPatch("PassTimeToNextDay")] [HarmonyPostfix] private static void PassTimeToNextDay() { if (NetworkManager.Instance.IsServer()) { UnlockManager.Instance.OnNewDay(); } } [HarmonyPatch("ArriveAtLevel")] [HarmonyPostfix] private static void ArriveAtLevelPatch() { if (NetworkManager.Instance.IsServer()) { Logger.LogInfo("After travel arriving at: " + LevelManager.CurrentExtendedLevel.NumberlessPlanetName); UnlockManager.Instance.OnArrive(); } } } [HarmonyPatch(typeof(Terminal))] internal class TerminalPatch { private static string buyMoon = string.Empty; private static int buyCredits = 0; [HarmonyPatch("Start")] [HarmonyPostfix] private static void TerminalStartPatch(ref Terminal __instance) { Logger.LogInfo("Terminal is booting up!"); UnlockManager.Instance.Terminal = __instance; if (NetworkManager.Instance.IsServer()) { UnlockManager.Instance.OnLobbyStart(); return; } ConfigManager.RefreshConfig(); UnlockManager.Instance.InitializeUnlocks(); NetworkManager.Instance.ClientRequestSync(); } [HarmonyPatch("LoadNewNodeIfAffordable")] [HarmonyPrefix] private static void TerminalLoadNewNodeIfAffordablePrefix(TerminalNode node) { if (node == null) { Logger.LogFatal("Terminal node in Terminal.LoadNewNodeIfAffordable is null!"); return; } Logger.LogDebug($"Loading new terminal node! Name: {((Object)node).name}, ID: {node.buyRerouteToMoon}"); foreach (LMUnlockable unlock in UnlockManager.Instance.Unlocks) { if (unlock.ExtendedLevel == null) { Logger.LogWarning("LMUnlockable " + unlock.Name + " has no ExtendedLevel! Skipping.."); continue; } Logger.LogDebug($"Checking node against moon {unlock.ExtendedLevel.NumberlessPlanetName} with ID {unlock.ExtendedLevel.SelectableLevel.levelID}"); if (unlock.ExtendedLevel.SelectableLevel.levelID != node.buyRerouteToMoon) { continue; } buyMoon = unlock.ExtendedLevel.NumberlessPlanetName; buyCredits = UnlockManager.Instance.Terminal.groupCredits; Logger.LogInfo($"Routing to moon {buyMoon} with ID {node.buyRerouteToMoon}!"); break; } } [HarmonyPatch("LoadNewNodeIfAffordable")] [HarmonyPostfix] private static void TerminalLoadNewNodeIfAffordablePostfix() { if (buyMoon != string.Empty) { if (buyCredits > UnlockManager.Instance.Terminal.groupCredits) { int num = buyCredits - UnlockManager.Instance.Terminal.groupCredits; Logger.LogInfo($"Route to {buyMoon} was paid ({num} credits)."); if (NetworkManager.Instance.IsServer()) { UnlockManager.Instance.BuyMoon(buyMoon); } else { NetworkManager.Instance.ClientBuyMoon(buyMoon); } } else { Logger.LogInfo("Route to " + buyMoon + " was free."); } } buyMoon = string.Empty; buyCredits = 0; } [HarmonyPatch("AttemptLoadCreatureFileNode")] [HarmonyPrefix] private static void AttemptLoadCreatureFileNodePrefix(TerminalNode node) { string text = ResolveBestiaryEntryName(node); Logger.LogDebug($"Loading bestiary node! Name: {text}, FileID: {node?.creatureFileID ?? (-1)}"); ReportTerminalRead(TerminalReadKind.Bestiary, text); } [HarmonyPatch("AttemptLoadStoryLogFileNode")] [HarmonyPrefix] private static void AttemptLoadStoryLogFileNodePrefix(TerminalNode node) { if (!Plugin.DawnLibPresent) { Logger.LogDebug($"Skipping story log terminal-read handling because DawnLib is not present. FileID='{node?.storyLogFileID ?? (-1)}'"); return; } string text = ResolveStoryLogEntryName(node); Logger.LogInfo($"Loading story log file node! Name='{text}', FileID='{node?.storyLogFileID ?? (-1)}'"); ReportTerminalRead(TerminalReadKind.StoryLog, text); } private static void ReportTerminalRead(TerminalReadKind readKind, string entryName) { string text = NormalizeEntryName(entryName); if (text.Length == 0) { Logger.LogDebug($"Skipping {readKind} terminal-read report with blank entry name."); } else if (ProgressionManager.Instance == null) { Logger.LogWarning($"Skipping {readKind} terminal-read report for '{text}' because ProgressionManager is unavailable."); } else if (NetworkManager.Instance.IsServer()) { if (1 == 0) { } bool flag = readKind switch { TerminalReadKind.Bestiary => ProgressionManager.Instance.RecordBestiaryRead(text), TerminalReadKind.StoryLog => ProgressionManager.Instance.RecordStoryLogRead(text), _ => false, }; if (1 == 0) { } if (flag) { UnlockManager.Instance?.HandleRecordedTerminalRead(readKind, text); } } else { NetworkManager.Instance.ClientReportTerminalRead(new TerminalReadSyncData(readKind, text)); } } private static string ResolveBestiaryEntryName(TerminalNode node) { return NormalizeEntryName(node?.creatureName); } private static string ResolveStoryLogEntryName(TerminalNode node) { if ((Object)(object)node == (Object)null || !Plugin.DawnLibPresent) { return string.Empty; } DawnStoryLogInfo val = LethalContent.StoryLogs.Values.FirstOrDefault((Func)((DawnStoryLogInfo log) => (Object)(object)((log != null) ? log.StoryLogTerminalNode : null) != (Object)null && log.StoryLogTerminalNode.storyLogFileID == node.storyLogFileID)); if (val != null) { return NormalizeEntryName(((Object)val.StoryLogTerminalNode).name); } Logger.LogDebug($"Could not resolve story log metadata for file ID {node.storyLogFileID}. Falling back to terminal node name."); return NormalizeEntryName(((Object)node).name); } private static string NormalizeEntryName(string entryName) { return string.IsNullOrWhiteSpace(entryName) ? string.Empty : entryName.Trim(); } } [HarmonyPatch(typeof(TimeOfDay))] internal class TimeOfDayPatch { [HarmonyPatch("SetNewProfitQuota")] [HarmonyPrefix] private static void SetNewProfitQuotaPatch() { if (NetworkManager.Instance.IsServer()) { UnlockManager.Instance.OnNewQuota(); } } } } namespace LethalMoonUnlocks.Compatibility { internal class JLLCompatibility { internal static void ReplaceJLLAlertDiscovery(string text) { if (NetworkManager.Instance != null && NetworkManager.Instance.IsServer()) { string text2 = text.Replace("Location: ", string.Empty); NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "New Coordinates!", Text = "Establishing route to " + text2 + "...", IsWarning = false, UseSave = false, Key = "LMU_JLL_Discovery_1" }); NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "Route unavailable!", Text = "Adding " + text2 + " to backlog...", IsWarning = true, UseSave = false, Key = "LMU_JLL_Discovery_2" }); NetworkManager.Instance.ServerSendAlertQueueEvent(); } } internal static void ReplaceJLLAlert(string text) { if (NetworkManager.Instance != null && NetworkManager.Instance.IsServer()) { string text2 = text.Replace("Location: ", string.Empty); NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "New Coordinates!", Text = "Adding " + text2 + " to moons catalog...", IsWarning = false, UseSave = false, Key = "LMU_JLL_1" }); NetworkManager.Instance.ServerSendAlertQueueEvent(); } } } internal sealed class ConstellationUnlockConditionsConfig { private const string SectionPrefix = "Constellation: "; private readonly string _configPath; private readonly ConfigFile _configFile; private readonly Dictionary _ruleDefinitions = new Dictionary(StringComparer.OrdinalIgnoreCase); internal IReadOnlyDictionary RuleDefinitions => _ruleDefinitions; internal ConstellationUnlockConditionsConfig() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown _configPath = Path.Combine(Paths.ConfigPath, "LethalMoonUnlocks - Constellations.cfg"); EnsureConfigFileExists(); _configFile = new ConfigFile(_configPath, false); RefreshDefinitions(); } internal bool TryGetRuleDefinition(string constellationName, out ConstellationUnlockRuleDefinition ruleDefinition) { ruleDefinition = null; if (string.IsNullOrWhiteSpace(constellationName)) { return false; } return _ruleDefinitions.TryGetValue(constellationName, out ruleDefinition) && ruleDefinition != null; } internal void RefreshDefinitions(bool saveChanges = true) { _ruleDefinitions.Clear(); EnsureConfigFileExists(); _configFile.Reload(); foreach (ClassMapper item in Collections.ConstellationStuff) { if (!string.IsNullOrWhiteSpace(item.consName)) { ConstellationUnlockRuleDefinition constellationUnlockRuleDefinition = BindRuleDefinition(item.consName); if (constellationUnlockRuleDefinition != null) { _ruleDefinitions[item.consName] = constellationUnlockRuleDefinition; } } } if (saveChanges) { _configFile.Save(); } } private void EnsureConfigFileExists() { string directoryName = Path.GetDirectoryName(_configPath); if (!string.IsNullOrWhiteSpace(directoryName)) { Directory.CreateDirectory(directoryName); } if (!File.Exists(_configPath)) { File.WriteAllText(_configPath, string.Empty); } } private ConstellationUnlockRuleDefinition BindRuleDefinition(string constellationName) { string section = "Constellation: " + constellationName; bool enabled = BindValue(section, "Enabled", defaultValue: false, "Enable custom unlock conditions for this constellation."); string value = BindValue(section, "MatchMode", ConstellationUnlockMatchMode.Any.ToString(), "How active conditions are combined. Any requires one active condition to pass. All requires every active condition to pass.", (AcceptableValueBase)(object)new AcceptableValueList(new string[2] { ConstellationUnlockMatchMode.Any.ToString(), ConstellationUnlockMatchMode.All.ToString() })); int requiredQuotaCount = BindValue(section, "RequiredQuotaCount", 0, "Unlock when the quota count reaches this value. Set to 0 to disable this condition.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 1000)); string value2 = BindValue(section, "RequiredVisitedMoons", string.Empty, "Unlock when every moon in this comma-separated list has been visited at least once. Leave empty to disable this condition."); int requiredUniqueMoonVisits = BindValue(section, "RequiredUniqueMoonVisits", 0, "Unlock when at least this many different moons have been visited. Set to 0 to disable this condition.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 1000)); string value3 = BindValue(section, "RequiredBestiaryEntries", string.Empty, "Requires DawnLib. Unlock when every bestiary entry in this comma-separated list has been read at least once. Leave empty to disable this condition. Without DawnLib this condition will never pass."); string value4 = BindValue(section, "RequiredStoryLogs", string.Empty, "Requires DawnLib. Unlock when every story log in this comma-separated list has been read at least once. Leave empty to disable this condition. Without DawnLib this condition will never pass."); bool ignoreDefaultMoonStoryLock = BindValue(section, "IgnoreDefaultMoonStoryLock", defaultValue: false, "When enabled, the constellation may unlock even if its default moon is still story-locked by another source."); return new ConstellationUnlockRuleDefinition(constellationName, enabled, ParseMatchMode(value), requiredQuotaCount, ParseCommaSeparatedList(value2), requiredUniqueMoonVisits, ParseCommaSeparatedList(value3), ParseCommaSeparatedList(value4), ignoreDefaultMoonStoryLock); } private T BindValue(string section, string key, T defaultValue, string description) { return _configFile.Bind(section, key, defaultValue, description).Value; } private T BindValue(string section, string key, T defaultValue, string description, AcceptableValueBase acceptableValues) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown return _configFile.Bind(section, key, defaultValue, new ConfigDescription(description, acceptableValues, Array.Empty())).Value; } private static ConstellationUnlockMatchMode ParseMatchMode(string value) { if (Enum.TryParse(value, ignoreCase: true, out var result)) { return result; } return ConstellationUnlockMatchMode.Any; } private static string[] ParseCommaSeparatedList(string value) { if (string.IsNullOrWhiteSpace(value)) { return Array.Empty(); } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); List list = new List(); string[] array = value.Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i]?.Trim() ?? string.Empty; if (!string.IsNullOrWhiteSpace(text) && hashSet.Add(text)) { list.Add(text); } } return list.ToArray(); } } internal enum ConstellationUnlockMatchMode { Any, All } internal sealed class ConstellationUnlockRuleDefinition { internal string ConstellationName { get; } internal bool Enabled { get; } internal ConstellationUnlockMatchMode MatchMode { get; } internal int RequiredQuotaCount { get; } internal IReadOnlyList RequiredVisitedMoons { get; } internal int RequiredUniqueMoonVisits { get; } internal IReadOnlyList RequiredBestiaryEntries { get; } internal IReadOnlyList RequiredStoryLogs { get; } internal bool IgnoreDefaultMoonStoryLock { get; } internal bool HasActiveConditions => RequiredQuotaCount > 0 || RequiredVisitedMoons.Count > 0 || RequiredUniqueMoonVisits > 0 || RequiredBestiaryEntries.Count > 0 || RequiredStoryLogs.Count > 0; internal bool IsEnabled => Enabled && HasActiveConditions; internal ConstellationUnlockRuleDefinition(string constellationName, bool enabled, ConstellationUnlockMatchMode matchMode, int requiredQuotaCount, IReadOnlyList requiredVisitedMoons, int requiredUniqueMoonVisits, IReadOnlyList requiredBestiaryEntries, IReadOnlyList requiredStoryLogs, bool ignoreDefaultMoonStoryLock) { ConstellationName = constellationName ?? string.Empty; Enabled = enabled; MatchMode = matchMode; RequiredQuotaCount = requiredQuotaCount; RequiredVisitedMoons = requiredVisitedMoons?.ToArray() ?? Array.Empty(); RequiredUniqueMoonVisits = requiredUniqueMoonVisits; RequiredBestiaryEntries = requiredBestiaryEntries?.ToArray() ?? Array.Empty(); RequiredStoryLogs = requiredStoryLogs?.ToArray() ?? Array.Empty(); IgnoreDefaultMoonStoryLock = ignoreDefaultMoonStoryLock; } } public class LethalConstellationsExtension { private readonly LethalConstellationsSaveData _constellationSaveData = new LethalConstellationsSaveData(); private LethalConstellationsSaveData _pendingSaveData; private LethalConstellationsManager _constellationManager = null; private readonly Dictionary _pendingConstellationRoutePrices = new Dictionary(StringComparer.OrdinalIgnoreCase); internal IReadOnlyDictionary ConstellationStates => _constellationSaveData.Constellations; public LethalConstellationsExtension() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown NewEvents.RouteConstellationSuccess.AddListener(new Event(OnConstellationBought)); } internal void SetManager(LethalConstellationsManager manager) { _constellationManager = manager; } internal int PruneDuplicateConstellations() { if (Collections.ConstellationStuff.Count < 2) { return 0; } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); List list = new List(); int num = 0; for (int num2 = Collections.ConstellationStuff.Count - 1; num2 >= 0; num2--) { ClassMapper val = Collections.ConstellationStuff[num2]; if (!string.IsNullOrWhiteSpace(val.consName) && !hashSet.Add(val.consName)) { Collections.ConstellationStuff.RemoveAt(num2); num++; list?.Add(val.consName); } } if (num > 0) { string arg = string.Join(", ", list.Distinct(StringComparer.OrdinalIgnoreCase).OrderBy((string name) => name, StringComparer.OrdinalIgnoreCase)); Logger.LogWarning($"LethalConstellationsExtension: Removed {num} duplicate constellations. Keeping newest definitions for: {arg}"); } return num; } public void ApplyUnlocks() { TryApplyPendingSaveData(); if (PruneDuplicateConstellations() > 0) { _constellationManager.ReindexDefinitions(logProblems: false); } if (!HasConstellationDefinitions()) { Logger.LogWarning("LethalConstellationsExtension: Constellation definitions are unavailable. Skipping unlock application."); return; } if (!_constellationManager.Bootstrap()) { Logger.LogError("LethalConstellations bootstrap failed; skipping constellation-local unlock application."); return; } ApplyVisibility(); if (ConfigManager.DiscoveryMode && UnlockManager.Instance != null && UnlockManager.Instance.UseConstellationDiscovery) { _constellationManager.ApplyCurrentConstellationVisibility(); HideUnlocksNotInCurrentConstellation(); ShowUnlocksInCurrentConstellation(); } else { HideUnlocksNotInCurrentConstellation(); ShowUnlocksInCurrentConstellation(); } _constellationManager.ApplyConstellationState(); AddDiscoveryCount(); foreach (ClassMapper item in Collections.ConstellationStuff) { Logger.LogDebug($"LethalConstellationsExtension: Constellation {item.consName}: {item.constelMoons.Count} moons, hidden state {item.isHidden}, locked state {item.isLocked}, default moon {item.defaultMoon}, price {item.constelPrice}, optional params: {item.optionalParams}"); } } internal LethalConstellationsSaveData GetSaveData() { if (_pendingSaveData != null) { return _pendingSaveData.Copy(); } return _constellationSaveData.Copy(); } internal void LoadSaveData(LethalConstellationsSaveData saveData) { if (!HasConstellationDefinitions()) { _constellationSaveData.Clear(); _pendingSaveData = saveData?.Copy(); _constellationManager.SetHasLoadedPersistedState(saveData != null && (saveData.Constellations?.Values.Any((LMConstellationUnlockable state) => state?.HasData() ?? false)).GetValueOrDefault()); if (saveData != null) { Logger.LogWarning("LethalConstellationsExtension: Constellation definitions are unavailable while trying to load save data. Skipping now. Will attempt again."); } } else { ApplySaveData(saveData); } } private void ApplySaveData(LethalConstellationsSaveData saveData, bool refreshDefinitions = true) { _pendingSaveData = null; _constellationSaveData.Clear(); SyncConstellationStates(); if (saveData?.Constellations != null) { foreach (KeyValuePair constellation2 in saveData.Constellations) { if (string.IsNullOrWhiteSpace(constellation2.Key) || constellation2.Value == null) { Logger.LogWarning("LethalConstellationsExtension: Dropping invalid saved constellation entry (no valid name or payload)."); continue; } if (!_constellationSaveData.TryGet(constellation2.Key, out var constellationState) || constellationState == null) { Logger.LogError("LethalConstellationsExtension: Dropping orphaned saved constellation '" + constellation2.Key + "' because no constellation definition with that name exists."); continue; } constellationState.OverrideData(constellation2.Value); if (_constellationManager.TryGetConstellation(constellation2.Key, out var constellation)) { constellationState.UpdateFromConstellation(constellation); } } } _constellationManager.SetHasLoadedPersistedState(_constellationSaveData.Constellations.Values.Any((LMConstellationUnlockable state) => state?.HasData() ?? false)); if (refreshDefinitions) { _constellationManager.RefreshDefinitions(logProblems: false); } } internal void TryApplyPendingSaveData(bool refreshDefinitions = true) { if (_pendingSaveData != null && HasConstellationDefinitions()) { ApplySaveData(_pendingSaveData, refreshDefinitions); } } internal bool TryGetConstellationState(string constellationName, out LMConstellationUnlockable constellationState) { return _constellationSaveData.TryGet(constellationName, out constellationState); } internal LMConstellationUnlockable GetOrCreateConstellationState(string constellationName) { return _constellationSaveData.GetOrCreate(constellationName); } internal void SyncConstellationStates() { foreach (ClassMapper item in Collections.ConstellationStuff) { if (!string.IsNullOrWhiteSpace(item.consName)) { GetOrCreateConstellationState(item.consName)?.UpdateFromConstellation(item); } } } public string GetConstellationName(LMUnlockable unlock) { return _constellationManager.GetConstellationName(unlock); } private void OnConstellationBought() { if (!HasConstellationDefinitions()) { Logger.LogError("LethalConstellationsExtension: Route success fired before constellation definitions were available. Something is most likely broken."); } else { if (!TryGetCurrentConstellationDefinition(out var currentConstellation)) { return; } if (!TryConsumePendingConstellationRoute(currentConstellation.consName, out var chargedPrice)) { if (!TryResolveFallbackRoutePrice(currentConstellation.consName, out chargedPrice)) { Logger.LogError("LethalConstellationsExtension: Missing captured route price for constellation '" + currentConstellation.consName + "' when the route success event fired, and no fallback price could be resolved either."); return; } Logger.LogWarning($"LethalConstellationsExtension: Missing captured route price for constellation '{currentConstellation.consName}' when the route success event fired. Falling back to constellation price from definition: {chargedPrice}."); } if (NetworkManager.Instance.IsServer()) { HandleConstellationRoute(currentConstellation.consName, chargedPrice); } else { NetworkManager.Instance.ClientRouteConstellation(currentConstellation.consName, chargedPrice); } } } internal void RecordPendingConstellationRoute(string constellationName, int chargedPrice) { if (!string.IsNullOrWhiteSpace(constellationName)) { _pendingConstellationRoutePrices[constellationName] = chargedPrice; } } private bool TryConsumePendingConstellationRoute(string constellationName, out int chargedPrice) { chargedPrice = 0; if (string.IsNullOrWhiteSpace(constellationName) || !_pendingConstellationRoutePrices.TryGetValue(constellationName, out chargedPrice)) { return false; } _pendingConstellationRoutePrices.Remove(constellationName); return true; } private bool TryResolveFallbackRoutePrice(string constellationName, out int chargedPrice) { chargedPrice = 0; if (string.IsNullOrWhiteSpace(constellationName)) { return false; } if (_constellationManager.TryGetConstellationEconomyTarget(constellationName, out var target) && target != null) { chargedPrice = target.EffectivePrice; return true; } ClassMapper val = Collections.ConstellationStuff?.FirstOrDefault((Func)((ClassMapper c) => string.Equals(c.consName, constellationName, StringComparison.OrdinalIgnoreCase))); if (val == null) { return false; } chargedPrice = val.constelPrice; return true; } internal void HandleConstellationRoute(string constellationName, int chargedPrice) { if (string.IsNullOrWhiteSpace(constellationName) || UnlockManager.Instance == null) { return; } if (!HasConstellationDefinitions()) { Logger.LogError("LethalConstellationsExtension: Unable to handle route for '" + constellationName + "' because constellation definitions are unavailable."); return; } if (!_constellationManager.TryGetConstellation(constellationName, out var constellation)) { Logger.LogError("LethalConstellationsExtension: Unable to handle route for missing constellation '" + constellationName + "'."); return; } Collections.CurrentConstellation = constellation.consName; Collections.CurrentConstellationCM = constellation; bool wasPaid = chargedPrice > 0; _constellationManager.TryGetDefaultMoon(constellation.consName, out var defaultMoon); if (defaultMoon != null) { Logger.LogInfo($"Routing to constellation {constellation.consName} -> default moon {defaultMoon.Name} with charged price {chargedPrice} and ID {defaultMoon.ExtendedLevel.SelectableLevel.levelID}!"); } _constellationManager.HandleConstellationRoute(constellation.consName, chargedPrice); if (ConfigManager.LethalConstellationsMirrorDefaultMoonRoute && defaultMoon != null) { Logger.LogInfo("Mirroring constellation route '" + constellation.consName + "' onto default moon '" + defaultMoon.Name + "'."); UnlockManager.Instance.ApplyMoonRouteProgression(defaultMoon, wasPaid, allowTravelDiscovery: true, broadcastState: false); } UnlockManager.Instance.IterateUnlocks(); NetworkManager.Instance.ServerSendUnlockables(UnlockManager.Instance.Unlocks, 0uL); DelayHelper.Instance.ExecuteAfterDelay(NetworkManager.Instance.ServerSendAlertQueueEvent, 2f); } private void ApplyVisibility() { foreach (ClassMapper item in Collections.ConstellationStuff) { bool flag = false; if (TryGetConstellationState(item.consName, out var constellationState)) { flag = (ConfigManager.DiscoveryMode ? constellationState.Discovered : (constellationState.StoryIsUnlocked || constellationState.Discovered)); } if (flag) { item.isHidden = false; item.isLocked = false; Logger.LogDebug("Constellation " + item.consName + " is discovered and routable."); } else { item.isHidden = true; item.isLocked = true; item.optionalParams = string.Empty; Logger.LogDebug("Constellation " + item.consName + " is hidden and locked."); } } } private void AddDiscoveryCount() { foreach (ClassMapper constellation in Collections.ConstellationStuff) { if (constellation.isHidden) { constellation.optionalParams = string.Empty; continue; } string text = string.Empty; if (ConfigManager.DiscoveryMode) { List list = ((UnlockManager.Instance != null && UnlockManager.Instance.UseConstellationDiscovery) ? _constellationManager.GetVisibleConstellationUnlocks(constellation.consName) : UnlockManager.Instance?.Unlocks.Where((LMUnlockable unlock) => (unlock.Discovered || unlock.PermanentlyDiscovered) && _constellationManager.IsMoonInConstellation(unlock, constellation.consName)).ToList()); text = $"\nMoons discovered: {list?.Count}"; if (list?.Count == constellation.constelMoons.Count) { text = "\nAll moons discovered!"; } } if (TryGetConstellationState(constellation.consName, out var constellationState)) { text += constellationState.BuildAdditionalInfoString(); } constellation.optionalParams = text; } } private void HideUnlocksNotInCurrentConstellation() { if (!TryGetCurrentConstellationDefinition(out var currentConstellation)) { return; } foreach (LMUnlockable unlock in UnlockManager.Instance.Unlocks) { if (!_constellationManager.IsMoonInConstellation(unlock, currentConstellation.consName)) { unlock.LockAndHide(); unlock.ApplyVisibility(); } } } private void ShowUnlocksInCurrentConstellation() { if (!TryGetCurrentConstellationDefinition(out var currentConstellation)) { return; } foreach (LMUnlockable unlock in UnlockManager.Instance.Unlocks) { if (_constellationManager.IsMoonInConstellation(unlock, currentConstellation.consName)) { unlock.ApplyState(); unlock.ApplyVisibility(); } } } internal void Reset() { _pendingConstellationRoutePrices.Clear(); _pendingSaveData = null; _constellationManager.Reset(); _constellationSaveData.Clear(); } private static bool HasConstellationDefinitions() { return Collections.ConstellationStuff.Count > 0; } private bool TryGetCurrentConstellationDefinition(out ClassMapper currentConstellation) { currentConstellation = null; return !string.IsNullOrWhiteSpace(Collections.CurrentConstellation) && _constellationManager.TryGetConstellation(Collections.CurrentConstellation, out currentConstellation); } } internal sealed class LethalConstellationsManager { internal sealed class StoryReleaseResult { internal List AffectedConstellations { get; } = new List(); internal bool ImmediateDiscovery { get; set; } internal bool AnyAffected => AffectedConstellations.Count > 0; } internal sealed class ConstellationEconomyTarget { internal ClassMapper Constellation { get; set; } internal LMConstellationUnlockable State { get; set; } internal LMUnlockable DefaultMoon { get; set; } internal int EffectivePrice { get; set; } internal int EffectiveOriginalPrice { get; set; } internal string Name => State?.Name ?? Constellation?.consName ?? string.Empty; } private sealed class RecoveryConstellationCandidate { internal ClassMapper Constellation { get; } internal int EffectivePrice { get; } internal RecoveryConstellationCandidate(ClassMapper constellation, int effectivePrice) { Constellation = constellation; EffectivePrice = effectivePrice; } } private const string StartingConstellationPolicyRandom = "Random"; private readonly LethalConstellationsExtension _extension; private readonly Dictionary _constellationLookup = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _moonNameToLevelIdLookup = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _unlockByLevelIdLookup = new Dictionary(); private readonly Dictionary _moonToConstellationLookup = new Dictionary(); private readonly Dictionary> _constellationMoonLevelLookup = new Dictionary>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _defaultMoonLevelLookup = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary> _rotationLookup = new Dictionary>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary> _localDiscoveryLookup = new Dictionary>(StringComparer.OrdinalIgnoreCase); private readonly HashSet _invalidVisitedMoonRuleWarnings = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly HashSet _terminalReadConditionDawnWarnings = new HashSet(StringComparer.OrdinalIgnoreCase); private Dictionary> _pendingRotationLoad; private Dictionary> _pendingLocalDiscoveryLoad; private bool _bootstrapped; private bool _hasLoadedPersistedState; internal static LethalConstellationsManager Instance { get; private set; } internal LethalConstellationsManager(LethalConstellationsExtension extension) { _extension = extension ?? throw new ArgumentNullException("extension"); Instance = this; } internal void Reset() { _constellationLookup.Clear(); _moonNameToLevelIdLookup.Clear(); _unlockByLevelIdLookup.Clear(); _moonToConstellationLookup.Clear(); _constellationMoonLevelLookup.Clear(); _defaultMoonLevelLookup.Clear(); _rotationLookup.Clear(); _localDiscoveryLookup.Clear(); _invalidVisitedMoonRuleWarnings.Clear(); _terminalReadConditionDawnWarnings.Clear(); _pendingRotationLoad = null; _pendingLocalDiscoveryLoad = null; _bootstrapped = false; _hasLoadedPersistedState = false; } internal void SetHasLoadedPersistedState(bool value) { _hasLoadedPersistedState = value; } internal void RefreshDefinitions(bool logProblems) { _extension.PruneDuplicateConstellations(); ReindexDefinitions(logProblems); } internal void ReindexDefinitions(bool logProblems) { _constellationLookup.Clear(); _moonNameToLevelIdLookup.Clear(); _unlockByLevelIdLookup.Clear(); _moonToConstellationLookup.Clear(); _constellationMoonLevelLookup.Clear(); _defaultMoonLevelLookup.Clear(); Plugin.ConstellationUnlockConditions?.RefreshDefinitions(logProblems); if (Collections.ConstellationStuff.Count == 0) { if (logProblems) { Logger.LogError("LethalConstellationsManager: No constellations are available to index."); } return; } _extension.TryApplyPendingSaveData(refreshDefinitions: false); _extension.SyncConstellationStates(); if (UnlockManager.Instance?.Unlocks != null) { foreach (LMUnlockable unlock in UnlockManager.Instance.Unlocks) { if (TryGetLevelId(unlock, out var levelId)) { _unlockByLevelIdLookup[levelId] = unlock; if (!_moonNameToLevelIdLookup.ContainsKey(unlock.Name)) { _moonNameToLevelIdLookup[unlock.Name] = levelId; } } } } string value3 = default(string); foreach (ClassMapper item in Collections.ConstellationStuff) { if (string.IsNullOrWhiteSpace(item.consName)) { if (logProblems) { Logger.LogError("LethalConstellationsManager: Encountered a constellation with no name while indexing."); } continue; } if (!_constellationLookup.TryAdd(item.consName, item) && logProblems) { Logger.LogWarning("LethalConstellationsManager: Duplicate constellation name '" + item.consName + "' encountered. Keeping the first definition."); } if (!_constellationMoonLevelLookup.TryGetValue(item.consName, out var value)) { value = new HashSet(); _constellationMoonLevelLookup[item.consName] = value; } foreach (string constelMoon in item.constelMoons) { if (!string.IsNullOrWhiteSpace(constelMoon) && _moonNameToLevelIdLookup.TryGetValue(constelMoon, out var value2)) { value.Add(value2); if (!_moonToConstellationLookup.TryAdd(value2, item.consName) && _moonToConstellationLookup.TryGetValue(value2, out value3) && !string.Equals(value3, item.consName, StringComparison.OrdinalIgnoreCase) && logProblems) { Logger.LogWarning("LethalConstellationsManager: Moon '" + constelMoon + "' is present in multiple constellations. Keeping the first mapping to '" + value3 + "'."); } } } } foreach (ClassMapper value4 in _constellationLookup.Values) { LMConstellationUnlockable orCreateConstellationState = _extension.GetOrCreateConstellationState(value4.consName); if (orCreateConstellationState == null) { if (logProblems) { Logger.LogError("LethalConstellationsManager: Failed to create state for constellation '" + value4.consName + "' while indexing."); } continue; } if (!TryGetDefaultMoon(value4.consName, out var defaultMoon)) { if (logProblems) { Logger.LogError("LethalConstellationsManager: Constellation '" + value4.consName + "' has no valid configured default moon."); } continue; } if (TryGetLevelId(defaultMoon, out var levelId2)) { _defaultMoonLevelLookup[value4.consName] = levelId2; } SyncConstellationStateFromDefaultMoon(value4, orCreateConstellationState, defaultMoon); } if (logProblems) { foreach (string key in _extension.ConstellationStates.Keys) { if (!_constellationLookup.ContainsKey(key)) { Logger.LogError("LethalConstellationsManager: Saved constellation state '" + key + "' has no matching constellation definition."); } } } TryApplyPendingLocalMoonDiscoveries(); TryApplyPendingConstellationRotation(); EnsureRotationsForDiscoveredConstellations(); } internal bool Bootstrap() { if (_bootstrapped) { return true; } RefreshDefinitions(logProblems: true); ApplyWhitelist(); if (_hasLoadedPersistedState || HasDiscoveredConstellations()) { if (!TryEnsureCurrentConstellation()) { Logger.LogError("LethalConstellationsManager: Failed to recover a current constellation when constellation discovery progression data is available."); return false; } EnsureRotationsForDiscoveredConstellations(); _bootstrapped = true; return true; } if (!TryResolveBootstrapConstellation(out var bootstrapConstellation)) { return false; } EnsureCurrentConstellationIsValid(bootstrapConstellation); EnsureRotationsForDiscoveredConstellations(); _bootstrapped = true; return true; } internal StoryReleaseResult ReleaseStoryLockForMoon(string moonName) { StoryReleaseResult storyReleaseResult = new StoryReleaseResult(); if (string.IsNullOrWhiteSpace(moonName) || Collections.ConstellationStuff.Count == 0) { return storyReleaseResult; } bool immediateDiscovery = IsImmediateDiscoveryStoryReleaseBehavior(); storyReleaseResult.ImmediateDiscovery = immediateDiscovery; foreach (ClassMapper item in Collections.ConstellationStuff.Where((ClassMapper constellation) => !string.IsNullOrWhiteSpace(constellation.defaultMoon) && string.Equals(constellation.defaultMoon, moonName, StringComparison.OrdinalIgnoreCase))) { LMConstellationUnlockable orCreateConstellationState = _extension.GetOrCreateConstellationState(item.consName); if (orCreateConstellationState != null) { bool appliedImmediateDiscovery; if (!TryGetDefaultMoon(item.consName, out var defaultMoon)) { Logger.LogError("LethalConstellationsManager: Unable to resolve the configured default moon for constellation '" + item.consName + "' during story release."); } else if (TryApplyConstellationStoryRelease(item, orCreateConstellationState, defaultMoon, "story release of constellation '" + item.consName + "' via moon '" + moonName + "'", out appliedImmediateDiscovery)) { storyReleaseResult.AffectedConstellations.Add(item.consName); Logger.LogInfo("LethalConstellationsManager: Released story lock for constellation '" + item.consName + "' via moon '" + moonName + "' (" + (appliedImmediateDiscovery ? "immediate discovery" : "hidden backlog") + ")."); } else { Logger.LogInfo("LethalConstellationsManager: Released moon-side story lock for constellation '" + item.consName + "' via moon '" + moonName + "', but additional custom unlock conditions are still required."); } } } if (storyReleaseResult.AffectedConstellations.Any((string name) => string.Equals(name, Collections.CurrentConstellation, StringComparison.OrdinalIgnoreCase))) { ApplyCurrentConstellationVisibility(); } return storyReleaseResult; } internal int RestoreStoryUnlockedConstellations(IEnumerable constellationNames, bool restoreImmediateDiscovery) { if (constellationNames == null || Collections.ConstellationStuff.Count == 0) { return 0; } EnsureIndexed(); int num = 0; foreach (string item in constellationNames.Where((string name) => !string.IsNullOrWhiteSpace(name)).Distinct(StringComparer.OrdinalIgnoreCase)) { if (!_constellationLookup.TryGetValue(item, out var value)) { Logger.LogWarning("LethalConstellationsManager: Unable to restore fired-reset story state for missing constellation '" + item + "'."); continue; } LMConstellationUnlockable orCreateConstellationState = _extension.GetOrCreateConstellationState(value.consName); if (orCreateConstellationState == null) { Logger.LogWarning("LethalConstellationsManager: Unable to restore fired-reset story state for constellation '" + value.consName + "' because no state object could be created."); continue; } if (ConfigManager.DiscoveryMode && restoreImmediateDiscovery) { if (!ForceDiscoverConstellation(value, orCreateConstellationState, "restored fired-reset story state for constellation '" + value.consName + "'")) { continue; } } else { orCreateConstellationState.StoryIsUnlocked = true; orCreateConstellationState.Discovered = false; orCreateConstellationState.DiscoveredOnce = false; orCreateConstellationState.NewDiscovery = false; } num++; } if (num < 1) { return 0; } ApplyConstellationState(); if (ConfigManager.DiscoveryMode && UnlockManager.Instance != null && UnlockManager.Instance.UseConstellationDiscovery) { ApplyCurrentConstellationVisibility(suppressNewDiscovery: true); } return num; } internal bool EvaluateCustomUnlockConditions() { if (Plugin.ConstellationUnlockConditions == null || Collections.ConstellationStuff.Count == 0) { return false; } Plugin.ConstellationUnlockConditions.RefreshDefinitions(saveChanges: false); EnsureIndexed(); bool result = false; foreach (ClassMapper value in _constellationLookup.Values) { if (value == null || string.IsNullOrWhiteSpace(value.consName)) { continue; } LMConstellationUnlockable orCreateConstellationState = _extension.GetOrCreateConstellationState(value.consName); if (orCreateConstellationState != null) { LMUnlockable defaultMoon; if (TryUnlockCustomCondition(value, orCreateConstellationState)) { result = true; } else if (TryGetDefaultMoon(value.consName, out defaultMoon)) { SyncConstellationStateFromDefaultMoon(value, orCreateConstellationState, defaultMoon); } } } return result; } internal bool TryGetConstellation(string constellationName, out ClassMapper constellation) { EnsureIndexed(); constellation = null; if (string.IsNullOrWhiteSpace(constellationName)) { return false; } return _constellationLookup.TryGetValue(constellationName, out constellation) && constellation != null; } internal string GetConstellationName(LMUnlockable unlock) { if (!TryGetLevelId(unlock, out var levelId)) { return string.Empty; } EnsureIndexed(); string value; return _moonToConstellationLookup.TryGetValue(levelId, out value) ? value : string.Empty; } internal bool TryGetDefaultMoon(string constellationName, out LMUnlockable defaultMoon) { defaultMoon = null; EnsureIndexed(); if (!TryGetConstellation(constellationName, out var constellation) || string.IsNullOrWhiteSpace(constellation.defaultMoon)) { return false; } if (_defaultMoonLevelLookup.TryGetValue(constellationName, out var value) && _unlockByLevelIdLookup.TryGetValue(value, out defaultMoon) && defaultMoon != null) { if (!defaultMoon.StoryUnlock && (defaultMoon.OriginallyHidden || defaultMoon.OriginallyLocked)) { Logger.LogWarning("LethalConstellationsManager: Configured default moon '" + constellation.defaultMoon + "' for constellation '" + constellation.consName + "' is hidden or locked without a story gate. Ignoring the constellation's default moon."); _defaultMoonLevelLookup.Remove(constellationName); defaultMoon = null; return false; } return true; } if (!_moonNameToLevelIdLookup.TryGetValue(constellation.defaultMoon, out var value2) || !_unlockByLevelIdLookup.TryGetValue(value2, out defaultMoon) || defaultMoon == null) { return false; } if (!defaultMoon.StoryUnlock && (defaultMoon.OriginallyHidden || defaultMoon.OriginallyLocked)) { Logger.LogWarning("LethalConstellationsManager: Configured default moon '" + constellation.defaultMoon + "' for constellation '" + constellation.consName + "' is hidden or locked without a story gate. Ignoring the constellation's default moon."); defaultMoon = null; return false; } _defaultMoonLevelLookup[constellationName] = value2; return true; } internal bool TryGetCurrentDefaultMoon(out LMUnlockable defaultMoon) { defaultMoon = null; return !string.IsNullOrWhiteSpace(Collections.CurrentConstellation) && TryGetDefaultMoon(Collections.CurrentConstellation, out defaultMoon); } internal string GetCurrentConstellationName() { return Collections.CurrentConstellation ?? string.Empty; } internal string GetConstellationWord() { return Configuration.ConstellationWord.Value; } internal bool IsMoonInConstellation(LMUnlockable unlock, string constellationName) { if (!TryGetLevelId(unlock, out var levelId) || string.IsNullOrWhiteSpace(constellationName)) { return false; } EnsureIndexed(); HashSet value; return _constellationMoonLevelLookup.TryGetValue(constellationName, out value) && value.Contains(levelId); } internal bool TryGetConstellationEconomyTarget(string constellationName, out ConstellationEconomyTarget target) { EnsureIndexed(); target = null; if (string.IsNullOrWhiteSpace(constellationName) || !_constellationLookup.TryGetValue(constellationName, out var value) || value == null || !TryGetConstellationState(value.consName, out var constellationState)) { return false; } target = CreateEconomyTarget(value, constellationState); return target != null; } internal bool TryGetCurrentConstellationEconomyTarget(out ConstellationEconomyTarget target) { return TryGetConstellationEconomyTarget(Collections.CurrentConstellation, out target); } internal List GetQuotaRewardMoonTargets() { EnsureIndexed(); if (UnlockManager.Instance == null || !UnlockManager.Instance.UseConstellationDiscovery) { return new List(); } if (string.Equals(ConfigManager.LethalConstellationsQuotaRewardScope, "CurrentConstellationOnly", StringComparison.OrdinalIgnoreCase)) { return GetCurrentVisibleUnlocks(); } HashSet visibleMoonIds = GetAllVisibleMoonIds(); int levelId; return UnlockManager.Instance.Unlocks.Where((LMUnlockable unlock) => TryGetLevelId(unlock, out levelId) && visibleMoonIds.Contains(levelId)).ToList(); } internal void RefreshConstellationSales() { EnsureIndexed(); foreach (ClassMapper value in _constellationLookup.Values) { if (TryGetConstellationState(value.consName, out var constellationState)) { constellationState.UpdateFromConstellation(value); TryGetDefaultMoon(value.consName, out var defaultMoon); int effectiveOriginalPrice = GetEffectiveOriginalPrice(constellationState, defaultMoon); constellationState.IterateState(effectiveOriginalPrice); if (IsConstellationAvailable(constellationState)) { constellationState.RefreshSale(effectiveOriginalPrice); } else { constellationState.OnSale = false; constellationState.SalesRate = 0; } constellationState.IterateState(effectiveOriginalPrice); } } ApplyConstellationState(); } internal void ApplyConstellationState() { EnsureIndexed(); foreach (ClassMapper value2 in _constellationLookup.Values) { if (!TryGetConstellationState(value2.consName, out var constellationState)) { continue; } constellationState.UpdateFromConstellation(value2); LMUnlockable defaultMoon; bool flag = TryGetDefaultMoon(value2.consName, out defaultMoon); int effectiveOriginalPrice = GetEffectiveOriginalPrice(constellationState, defaultMoon); constellationState.IterateState(effectiveOriginalPrice); ApplyConstellationState(value2, constellationState); if (flag) { if (TryGetLevelId(defaultMoon, out var levelId)) { _defaultMoonLevelLookup[value2.consName] = levelId; } value2.defaultMoonLevel = defaultMoon.ExtendedLevel; } value2.constelPrice = constellationState.RoutePrice; value2.oneTimePurchase = ConfigManager.UnlockMode && !ConfigManager.DiscountMode && value2.constelPrice <= 0; } if (!string.IsNullOrWhiteSpace(Collections.CurrentConstellation) && _constellationLookup.TryGetValue(Collections.CurrentConstellation, out var value)) { Collections.CurrentConstellationCM = value; } } internal bool IsImmediateDiscoveryStoryReleaseBehavior() { return ConfigManager.LCStoryReleaseBehavior == StoryReleaseBehavior.ImmediateDiscovery; } internal List GetVisibleConstellationUnlocks(string constellationName) { List result = new List(); if (UnlockManager.Instance == null || !UnlockManager.Instance.UseConstellationDiscovery) { return result; } if (!Bootstrap() || !TryGetConstellation(constellationName, out var constellation) || !TryGetConstellationState(constellationName, out var constellationState)) { return result; } if (!constellationState.Discovered) { return result; } List constellationUnlocks = GetConstellationUnlocks(constellation); if (constellationUnlocks.Count == 0) { return result; } EnsureConstellationRotation(constellationName); HashSet visibleMoonIds = new HashSet(); if (TryGetDefaultMoon(constellationName, out var defaultMoon)) { AddVisibleMoon(defaultMoon); } foreach (LMUnlockable item in constellationUnlocks.Where((LMUnlockable unlock) => unlock.PermanentlyDiscovered)) { AddVisibleMoon(item); } if (_rotationLookup.TryGetValue(constellationName, out var rotationMoons)) { int levelId4; foreach (LMUnlockable item2 in constellationUnlocks.Where((LMUnlockable unlock) => TryGetLevelId(unlock, out levelId4) && rotationMoons.Contains(levelId4))) { AddVisibleMoon(item2); } } if (_localDiscoveryLookup.TryGetValue(constellationName, out var localDiscoveries)) { int levelId3; foreach (LMUnlockable item3 in constellationUnlocks.Where((LMUnlockable unlock) => TryGetLevelId(unlock, out levelId3) && localDiscoveries.Contains(levelId3))) { AddVisibleMoon(item3); } } int levelId2; return constellationUnlocks.Where((LMUnlockable unlock) => TryGetLevelId(unlock, out levelId2) && visibleMoonIds.Contains(levelId2)).ToList(); void AddVisibleMoon(LMUnlockable unlock) { if (TryGetLevelId(unlock, out var levelId)) { visibleMoonIds.Add(levelId); } } } internal bool ApplyCurrentConstellationVisibility(bool suppressNewDiscovery = false) { if (UnlockManager.Instance == null || !UnlockManager.Instance.UseConstellationDiscovery) { return false; } if (!Bootstrap()) { Logger.LogError("LethalConstellationsManager: Unable to apply moon visibility because initialization failed."); return false; } if (!TryEnsureCurrentConstellation()) { Logger.LogError("LethalConstellationsManager: Failed applying visibility status! No current constellation is available. This should not happen at runtime."); return false; } string currentConstellation = Collections.CurrentConstellation; if (string.IsNullOrWhiteSpace(currentConstellation)) { Logger.LogError("LethalConstellationsManager: Failed applying visibility status! No current constellation is available. This should not happen at runtime."); return false; } List visibleConstellationUnlocks = GetVisibleConstellationUnlocks(currentConstellation); int levelId3; HashSet hashSet = new HashSet(from unlock in visibleConstellationUnlocks select TryGetLevelId(unlock, out levelId3) ? levelId3 : (-1) into levelId where levelId >= 0 select levelId); foreach (LMUnlockable unlock in UnlockManager.Instance.Unlocks) { unlock.SetDiscoveryState(TryGetLevelId(unlock, out var levelId2) && hashSet.Contains(levelId2), suppressNewDiscovery); } Logger.LogInfo("LethalConstellationsManager: Applied moon visibility for '" + currentConstellation + "' -> [ " + string.Join(", ", visibleConstellationUnlocks.Select((LMUnlockable unlock) => unlock.Name)) + " ]"); return true; } internal List GetCurrentVisibleUnlocks() { return GetVisibleConstellationUnlocks(Collections.CurrentConstellation); } internal List GetCurrentConstellationDiscoveryCandidates() { if (!Bootstrap()) { return new List(); } string currentConstellation = Collections.CurrentConstellation; if (string.IsNullOrWhiteSpace(currentConstellation) || !TryGetConstellation(currentConstellation, out var constellation)) { Logger.LogError("LethalConstellationsManager: No valid current constellation is available for moon discovery selection."); return new List(); } int levelId3; HashSet visibleMoonIds = new HashSet(from unlock in GetVisibleConstellationUnlocks(currentConstellation) select TryGetLevelId(unlock, out levelId3) ? levelId3 : (-1) into levelId where levelId >= 0 select levelId); int levelId2; return (from unlock in GetConstellationUnlocks(constellation) where IsEligibleForLocalRotation(unlock) && (!TryGetLevelId(unlock, out levelId2) || !visibleMoonIds.Contains(levelId2)) select unlock).ToList(); } internal bool HasUndiscoveredConstellations() { return GetUndiscoveredConstellations(includeStoryLocked: true, requireResolvableDefaultMoon: false).Count > 0; } internal bool HasEligibleUndiscoveredConstellations() { return GetUndiscoveredConstellations(includeStoryLocked: false, requireResolvableDefaultMoon: true).Count > 0; } internal bool TryDiscoverConstellation(bool preferCheapest, out string constellationName) { constellationName = string.Empty; List undiscoveredConstellations = GetUndiscoveredConstellations(includeStoryLocked: false, requireResolvableDefaultMoon: true); if (undiscoveredConstellations.Count == 0) { return false; } ClassMapper val = (ClassMapper)(preferCheapest ? ((object)undiscoveredConstellations.OrderBy((ClassMapper candidate) => candidate.constelPrice).ThenBy((ClassMapper candidate) => candidate.consName, StringComparer.OrdinalIgnoreCase).FirstOrDefault()) : ((object)undiscoveredConstellations[RandomHelper.Range(0, undiscoveredConstellations.Count)])); if (val == null) { return false; } LMConstellationUnlockable orCreateConstellationState = _extension.GetOrCreateConstellationState(val.consName); if (orCreateConstellationState == null) { Logger.LogError("LethalConstellationsManager: Failed to create state while discovering constellation '" + val.consName + "'."); return false; } if (!TryGetDefaultMoon(val.consName, out var defaultMoon)) { Logger.LogError("LethalConstellationsManager: Unable to resolve the configured default moon while discovering constellation '" + val.consName + "'."); return false; } if (!DiscoverConstellation(val, orCreateConstellationState, "trigger discovery of constellation '" + val.consName + "'")) { return false; } constellationName = val.consName; Logger.LogInfo("LethalConstellationsManager: Discovered constellation '" + constellationName + "' via trigger progression. Marked discovered and permanently available for this run; default moon '" + defaultMoon.Name + "' forced available."); return true; } internal bool HandleConstellationRoute(string constellationName, int chargedPrice) { if (string.IsNullOrWhiteSpace(constellationName) || !TryGetConstellation(constellationName, out var constellation)) { Logger.LogError("LethalConstellationsManager: Unable to handle route progression for missing constellation '" + constellationName + "'."); return false; } LMConstellationUnlockable orCreateConstellationState = _extension.GetOrCreateConstellationState(constellation.consName); if (orCreateConstellationState == null) { Logger.LogError("LethalConstellationsManager: Failed to create state for routed constellation '" + constellation.consName + "'."); return false; } orCreateConstellationState.UpdateFromConstellation(constellation); TryGetDefaultMoon(constellation.consName, out var defaultMoon); int effectiveOriginalPrice = GetEffectiveOriginalPrice(orCreateConstellationState, defaultMoon); orCreateConstellationState.IterateState(effectiveOriginalPrice); bool flag = chargedPrice > 0; int buyCount = orCreateConstellationState.BuyCount; int freeVisitCount = orCreateConstellationState.FreeVisitCount; if (flag) { orCreateConstellationState.AdvanceBuyProgression(); Logger.LogInfo($"Constellation {constellation.consName}: Set buy count to {orCreateConstellationState.BuyCount}"); } orCreateConstellationState.IterateState(effectiveOriginalPrice); orCreateConstellationState.VisitRoute(effectiveOriginalPrice); orCreateConstellationState.IterateState(effectiveOriginalPrice); ApplyConstellationState(); if (ConfigManager.UnlockMode && !ConfigManager.DiscountMode && ConfigManager.UnlocksResetAfterVisits > 0) { if (freeVisitCount > 0 && orCreateConstellationState.FreeVisitCount == 0 && orCreateConstellationState.BuyCount == 0) { NotificationHelper.SendChatMessage("Unlock expired:\n" + constellation.consName + ""); NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "Unlock expired!", Text = "Your unlock for constellation " + constellation.consName + " has been used " + freeVisitCount.NumberOfWords("time") + " and expired.", IsWarning = true, Key = "LMU_LethalConstellationsUnlockExpired" }); } else if (orCreateConstellationState.FreeVisitCount > freeVisitCount && orCreateConstellationState.FreeVisitCount > 1) { NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = constellation.consName, Text = "Unlock redeemed! " + (orCreateConstellationState.FreeVisitCount - 1).CountToText() + " use.\nYou have " + (ConfigManager.UnlocksResetAfterVisits - orCreateConstellationState.FreeVisitCount + 1).NumberOfWords("use") + " left.", Key = "LMU_LethalConstellationsUnlockUsed" }); } } if (ConfigManager.DiscountMode && ConfigManager.DiscountsResetAfterVisits > 0) { if (freeVisitCount > 0 && orCreateConstellationState.FreeVisitCount == 0 && buyCount > 0 && orCreateConstellationState.BuyCount == 0) { NotificationHelper.SendChatMessage("Discount expired:\n" + constellation.consName + ""); NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "Discount expired!", Text = "Your discount for constellation " + constellation.consName + " has been used " + freeVisitCount.NumberOfWords("time") + " and expired.", IsWarning = true, Key = "LMU_LethalConstellationsDiscountExpired" }); } else if (orCreateConstellationState.FreeVisitCount > freeVisitCount && orCreateConstellationState.FreeVisitCount > 1) { NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "Discount: " + constellation.consName, Text = "Discount redeemed! " + (orCreateConstellationState.FreeVisitCount - 1).CountToText() + " use.\nYou have " + (ConfigManager.DiscountsResetAfterVisits - orCreateConstellationState.FreeVisitCount + 1).NumberOfWords("use") + " left.", Key = "LMU_LethalConstellationsDiscountUsed" }); } } return flag || orCreateConstellationState.FreeVisitCount != freeVisitCount || orCreateConstellationState.BuyCount != buyCount; } internal void ClearAllConstellationRotations() { _rotationLookup.Clear(); _pendingRotationLoad = null; } internal void RegenerateAllConstellationRotations() { if (!Bootstrap()) { return; } _rotationLookup.Clear(); foreach (ClassMapper value in _constellationLookup.Values) { if (TryGetConstellationState(value.consName, out var constellationState) && constellationState.Discovered) { RegenerateConstellationRotation(value); } } } internal void ClearLocalMoonDiscoveries() { _localDiscoveryLookup.Clear(); } internal void AddLocalMoonDiscoveries(string constellationName, IEnumerable moons) { if (string.IsNullOrWhiteSpace(constellationName) || moons == null) { return; } if (!_localDiscoveryLookup.TryGetValue(constellationName, out var value)) { value = new HashSet(); _localDiscoveryLookup[constellationName] = value; } foreach (LMUnlockable moon in moons) { if (TryGetLevelId(moon, out var levelId)) { value.Add(levelId); } } } internal void AddLocalMoonDiscoveriesForCurrentConstellation(IEnumerable moons) { AddLocalMoonDiscoveries(Collections.CurrentConstellation, moons); } internal void SetLocalMoonDiscoveries(string constellationName, IEnumerable moonNames) { if (string.IsNullOrWhiteSpace(constellationName)) { return; } HashSet hashSet = new HashSet(); if (moonNames != null) { foreach (string moonName in moonNames) { if (!string.IsNullOrWhiteSpace(moonName) && _moonNameToLevelIdLookup.TryGetValue(moonName, out var value)) { hashSet.Add(value); } } } _localDiscoveryLookup[constellationName] = hashSet; } internal Dictionary> GetAllConstellationRotations() { if (_pendingRotationLoad != null) { return CopyConstellationRotations(_pendingRotationLoad); } Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair> item in _rotationLookup) { dictionary[item.Key] = ResolveMoonNames(item.Value); } return dictionary; } internal Dictionary> GetAllLocalMoonDiscoveries() { if (_pendingLocalDiscoveryLoad != null) { return CopyLocalMoonDiscoveries(_pendingLocalDiscoveryLoad); } Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair> item in _localDiscoveryLookup) { dictionary[item.Key] = ResolveMoonNames(item.Value); } return dictionary; } internal void ReplaceAllConstellationRotations(Dictionary> rotationSelections) { _rotationLookup.Clear(); _pendingRotationLoad = null; if (rotationSelections != null) { if (Collections.ConstellationStuff.Count == 0) { _pendingRotationLoad = CopyConstellationRotations(rotationSelections); Logger.LogWarning("LethalConstellationsManager: Constellation definitions are unavailable. Deferring rotation restore."); } else { EnsureIndexed(); ApplyValidatedConstellationRotations(rotationSelections); EnsureRotationsForDiscoveredConstellations(); } } } internal void ReplaceLocalMoonDiscoveries(Dictionary> localDiscoveries) { _localDiscoveryLookup.Clear(); _pendingLocalDiscoveryLoad = null; if (localDiscoveries != null) { if (Collections.ConstellationStuff.Count == 0) { _pendingLocalDiscoveryLoad = CopyLocalMoonDiscoveries(localDiscoveries); Logger.LogWarning("LethalConstellationsManager: Constellation definitions are unavailable. Deferring local discovery restore."); } else { EnsureIndexed(); ApplyValidatedLocalMoonDiscoveries(localDiscoveries); EnsureRotationsForDiscoveredConstellations(); } } } private void ApplyValidatedConstellationRotations(Dictionary> rotationSelections) { if (rotationSelections == null) { return; } foreach (KeyValuePair> rotationSelection in rotationSelections) { if (string.IsNullOrWhiteSpace(rotationSelection.Key)) { Logger.LogWarning("LethalConstellationsManager: Dropping rotation entry with no constellation name."); continue; } if (!_constellationLookup.TryGetValue(rotationSelection.Key, out var value)) { Logger.LogError("LethalConstellationsManager: Dropping orphaned rotation state for missing constellation '" + rotationSelection.Key + "'."); continue; } if (!_constellationMoonLevelLookup.TryGetValue(value.consName, out var value2)) { SetConstellationRotation(rotationSelection.Key, Array.Empty()); continue; } List list = new List(); if (rotationSelection.Value != null) { foreach (string item in rotationSelection.Value) { if (!string.IsNullOrWhiteSpace(item) && _moonNameToLevelIdLookup.TryGetValue(item, out var value3) && value2.Contains(value3)) { list.Add(value3); } } } SetConstellationRotation(rotationSelection.Key, list); } } private void ApplyValidatedLocalMoonDiscoveries(Dictionary> localDiscoveries) { if (localDiscoveries == null) { return; } foreach (KeyValuePair> localDiscovery in localDiscoveries) { if (string.IsNullOrWhiteSpace(localDiscovery.Key)) { Logger.LogWarning("LethalConstellationsManager: Dropping local discovery entry with no constellation name."); } else if (!_constellationLookup.ContainsKey(localDiscovery.Key)) { Logger.LogError("LethalConstellationsManager: Dropping orphaned local discovery state for missing constellation '" + localDiscovery.Key + "'."); } else { SetLocalMoonDiscoveries(localDiscovery.Key, localDiscovery.Value); } } } private void TryApplyPendingConstellationRotation() { if (_pendingRotationLoad != null && _constellationLookup.Count != 0) { Dictionary> pendingRotationLoad = _pendingRotationLoad; _pendingRotationLoad = null; _rotationLookup.Clear(); ApplyValidatedConstellationRotations(pendingRotationLoad); } } private void TryApplyPendingLocalMoonDiscoveries() { if (_pendingLocalDiscoveryLoad != null && _constellationLookup.Count != 0) { Dictionary> pendingLocalDiscoveryLoad = _pendingLocalDiscoveryLoad; _pendingLocalDiscoveryLoad = null; _localDiscoveryLookup.Clear(); ApplyValidatedLocalMoonDiscoveries(pendingLocalDiscoveryLoad); } } private static Dictionary> CopyConstellationRotations(Dictionary> rotationSelections) { Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); if (rotationSelections == null) { return dictionary; } foreach (KeyValuePair> rotationSelection in rotationSelections) { if (!string.IsNullOrWhiteSpace(rotationSelection.Key)) { dictionary[rotationSelection.Key] = ((rotationSelection.Value == null) ? new List() : rotationSelection.Value.Where((string name) => !string.IsNullOrWhiteSpace(name)).Distinct(StringComparer.OrdinalIgnoreCase).ToList()); } } return dictionary; } private static Dictionary> CopyLocalMoonDiscoveries(Dictionary> localDiscoveries) { Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); if (localDiscoveries == null) { return dictionary; } foreach (KeyValuePair> localDiscovery in localDiscoveries) { if (!string.IsNullOrWhiteSpace(localDiscovery.Key)) { dictionary[localDiscovery.Key] = ((localDiscovery.Value == null) ? new List() : localDiscovery.Value.Where((string name) => !string.IsNullOrWhiteSpace(name)).Distinct(StringComparer.OrdinalIgnoreCase).ToList()); } } return dictionary; } internal bool IsDerivedVisibleAnywhere(LMUnlockable unlock) { if (!TryGetLevelId(unlock, out var levelId)) { return false; } return GetAllVisibleMoonIds().Contains(levelId); } private void EnsureIndexed() { if (_constellationLookup.Count <= 0) { RefreshDefinitions(logProblems: false); } } private List GetUndiscoveredConstellations(bool includeStoryLocked, bool requireResolvableDefaultMoon) { if (!Bootstrap()) { return new List(); } List list = new List(); foreach (ClassMapper value in _constellationLookup.Values) { if (TryGetConstellationState(value.consName, out var constellationState) && !constellationState.Discovered && (includeStoryLocked || constellationState.StoryIsUnlocked) && (!requireResolvableDefaultMoon || TryGetDefaultMoon(value.consName, out var _))) { list.Add(value); } } return list; } private void EnsureRotationsForDiscoveredConstellations() { foreach (ClassMapper value in _constellationLookup.Values) { if (TryGetConstellationState(value.consName, out var constellationState) && constellationState.Discovered) { EnsureConstellationRotation(value.consName); } } } private void EnsureConstellationRotation(string constellationName) { if (!string.IsNullOrWhiteSpace(constellationName) && !_rotationLookup.ContainsKey(constellationName) && _constellationLookup.TryGetValue(constellationName, out var value) && TryGetConstellationState(value.consName, out var constellationState) && constellationState.Discovered) { RegenerateConstellationRotation(value); } } private void RegenerateConstellationRotation(ClassMapper constellation) { if (constellation == null || UnlockManager.Instance == null) { return; } if (!TryGetConstellationState(constellation.consName, out var constellationState) || !constellationState.Discovered) { _rotationLookup.Remove(constellation?.consName ?? string.Empty); return; } List constellationUnlocks = GetConstellationUnlocks(constellation); HashSet reservedMoonIds = new HashSet(); if (TryGetDefaultMoon(constellation.consName, out var defaultMoon) && TryGetLevelId(defaultMoon, out var levelId)) { reservedMoonIds.Add(levelId); } foreach (LMUnlockable item in constellationUnlocks.Where((LMUnlockable unlock) => unlock.PermanentlyDiscovered)) { if (TryGetLevelId(item, out var levelId2)) { reservedMoonIds.Add(levelId2); } } if (_localDiscoveryLookup.TryGetValue(constellation.consName, out var value)) { foreach (int item2 in value) { reservedMoonIds.Add(item2); } } List rotationSelection = new List(); int levelId6; List objects = constellationUnlocks.Where((LMUnlockable unlock) => IsEligibleForLocalRotation(unlock) && (!TryGetLevelId(unlock, out levelId6) || !reservedMoonIds.Contains(levelId6)) && unlock.OriginalPrice == 0).ToList(); AddSelection(RandomHelper.Select(objects, UnlockManager.Instance.DiscoveredFreeCount)); int levelId5; List objects2 = constellationUnlocks.Where((LMUnlockable unlock) => IsEligibleForLocalRotation(unlock) && (!TryGetLevelId(unlock, out levelId5) || !reservedMoonIds.Contains(levelId5)) && unlock.RoutePrice == 0).ToList(); AddSelection(RandomHelper.Select(objects2, UnlockManager.Instance.DiscoveredDynamicFreeCount)); int levelId4; List list = constellationUnlocks.Where((LMUnlockable unlock) => IsEligibleForLocalRotation(unlock) && (!TryGetLevelId(unlock, out levelId4) || !reservedMoonIds.Contains(levelId4)) && unlock.RoutePrice > 0).ToList(); AddSelection(ConfigManager.CheapMoonBiasPaidRotation ? RandomHelper.SelectWeighted(RandomHelper.CalculateBiasedWeights(list, ConfigManager.CheapMoonBiasPaidRotationValue), UnlockManager.Instance.DiscoveredPaidCount) : RandomHelper.Select(list, UnlockManager.Instance.DiscoveredPaidCount)); SetConstellationRotation(constellation.consName, rotationSelection); Logger.LogInfo("LethalConstellationsManager: Generated stored moon rotation for '" + constellation.consName + "' -> [ " + string.Join(", ", ResolveMoonNames(rotationSelection)) + " ]"); void AddSelection(IEnumerable selection) { if (selection == null) { return; } foreach (LMUnlockable item3 in selection) { if (TryGetLevelId(item3, out var levelId3) && reservedMoonIds.Add(levelId3)) { rotationSelection.Add(levelId3); } } } } private void SetConstellationRotation(string constellationName, IEnumerable moonLevelIds) { if (string.IsNullOrWhiteSpace(constellationName)) { return; } HashSet hashSet = new HashSet(); if (moonLevelIds != null) { foreach (int moonLevelId in moonLevelIds) { hashSet.Add(moonLevelId); } } _rotationLookup[constellationName] = hashSet; } private void ApplyWhitelist() { if (ConfigManager.LethalConstellationsWhitelist.Count == 0) { return; } foreach (string item in ConfigManager.LethalConstellationsWhitelist) { if (!TryGetConstellation(item, out var constellation)) { Logger.LogError("LethalConstellationsManager: Whitelisted constellation '" + item + "' does not exist."); continue; } LMConstellationUnlockable orCreateConstellationState = _extension.GetOrCreateConstellationState(constellation.consName); if (orCreateConstellationState == null) { Logger.LogError("LethalConstellationsManager: Failed to create state for whitelisted constellation '" + constellation.consName + "'."); } else { ForceDiscoverConstellation(constellation, orCreateConstellationState, "whitelisted constellation '" + constellation.consName + "'"); } } } private bool TryResolveBootstrapConstellation(out ClassMapper bootstrapConstellation) { bootstrapConstellation = null; List bootstrapCandidates = GetBootstrapCandidates(); if (bootstrapCandidates.Count == 0) { Logger.LogError("LethalConstellationsManager: No eligible constellations exist for startup bootstrap."); return false; } bootstrapConstellation = SelectCandidate(bootstrapCandidates); if (bootstrapConstellation == null) { Logger.LogError("LethalConstellationsManager: Failed to select a startup constellation."); return false; } return ApplyBootstrapState(bootstrapConstellation, "startup constellation '" + bootstrapConstellation.consName + "'"); } private bool ApplyBootstrapState(ClassMapper constellation, string reason) { if (constellation == null) { return false; } LMConstellationUnlockable orCreateConstellationState = _extension.GetOrCreateConstellationState(constellation.consName); if (orCreateConstellationState == null) { Logger.LogError("LethalConstellationsManager: Failed to create state for " + reason + "."); return false; } if (!ForceDiscoverConstellation(constellation, orCreateConstellationState, reason)) { return false; } ApplySilentStartupProgression(constellation, orCreateConstellationState); return true; } private List GetBootstrapCandidates() { List list = new List(); List acceptableStartingConstellations = ConfigManager.AcceptableStartingConstellations; foreach (ClassMapper constellation in _constellationLookup.Values) { if (TryGetConstellationState(constellation.consName, out var constellationState) && constellationState.StoryIsUnlocked && TryGetDefaultMoon(constellation.consName, out var _) && (acceptableStartingConstellations.Count <= 0 || acceptableStartingConstellations.Any((string name) => string.Equals(name, constellation.consName, StringComparison.OrdinalIgnoreCase)))) { list.Add(constellation); } } return list; } private ClassMapper SelectCandidate(List candidates) { if (candidates == null || candidates.Count == 0) { return null; } if (string.Equals(ConfigManager.LCStartingConstellationSelectionPolicy, "Random", StringComparison.OrdinalIgnoreCase)) { return candidates[RandomHelper.Range(0, candidates.Count)]; } return candidates.OrderBy((ClassMapper candidate) => candidate.constelPrice).ThenBy((ClassMapper candidate) => candidate.consName, StringComparer.OrdinalIgnoreCase).FirstOrDefault(); } private bool TryEnsureCurrentConstellation() { string currentConstellation = Collections.CurrentConstellation; if (!string.IsNullOrWhiteSpace(currentConstellation) && _constellationLookup.TryGetValue(currentConstellation, out var value) && value != null) { if (TryGetConstellationState(value.consName, out var constellationState) && IsConstellationAvailable(constellationState) && TryGetDefaultMoon(value.consName, out var _)) { Collections.CurrentConstellationCM = value; return true; } Logger.LogError("LethalConstellationsManager: Current constellation '" + currentConstellation + "' is no longer available."); } else if (string.IsNullOrWhiteSpace(currentConstellation)) { Logger.LogError("LethalConstellationsManager: No current constellation exists even though constellation discovery progression data is available. This should not happen at runtime."); } else { Logger.LogError("LethalConstellationsManager: Current constellation '" + currentConstellation + "' no longer exists even though constellation discovery progression data is available."); } if (!TryResolveRecoveryConstellation(out var recoveryConstellation)) { Collections.CurrentConstellationCM = null; return false; } Logger.LogWarning("LethalConstellationsManager: Recovering missing current constellation with '" + recoveryConstellation.consName + "'."); Collections.CurrentConstellation = recoveryConstellation.consName; Collections.CurrentConstellationCM = recoveryConstellation; return true; } private bool TryResolveRecoveryConstellation(out ClassMapper recoveryConstellation) { recoveryConstellation = (from candidate in (from constellation in _constellationLookup.Values select CreateRecoveryCandidate(constellation) into candidate where candidate != null orderby candidate.EffectivePrice select candidate).ThenBy((RecoveryConstellationCandidate candidate) => candidate.Constellation.consName, StringComparer.OrdinalIgnoreCase) select candidate.Constellation).FirstOrDefault(); return recoveryConstellation != null; } private RecoveryConstellationCandidate CreateRecoveryCandidate(ClassMapper constellation) { if (constellation == null || !TryGetConstellationState(constellation.consName, out var constellationState) || !IsConstellationAvailable(constellationState)) { return null; } if (!TryGetDefaultMoon(constellation.consName, out var _)) { return null; } ConstellationEconomyTarget constellationEconomyTarget = CreateEconomyTarget(constellation, constellationState); return (constellationEconomyTarget == null) ? null : new RecoveryConstellationCandidate(constellation, constellationEconomyTarget.EffectivePrice); } private void EnsureCurrentConstellationIsValid(ClassMapper bootstrapConstellation) { if (bootstrapConstellation != null) { string currentConstellation = Collections.CurrentConstellation; if (!string.IsNullOrWhiteSpace(currentConstellation) && !string.Equals(currentConstellation, bootstrapConstellation.consName, StringComparison.OrdinalIgnoreCase)) { Logger.LogWarning("LethalConstellationsManager: Replacing existing current constellation '" + currentConstellation + "' with startup constellation '" + bootstrapConstellation.consName + "'."); } Collections.CurrentConstellation = bootstrapConstellation.consName; Collections.CurrentConstellationCM = bootstrapConstellation; } } private void EnsureDefaultMoonAvailable(ClassMapper constellation, bool warnOnStoryLockOverride, string reason) { if (constellation == null) { return; } if (!TryGetDefaultMoon(constellation.consName, out var defaultMoon)) { Logger.LogError("LethalConstellationsManager: Unable to resolve the configured default moon for " + reason + "."); return; } if (defaultMoon.StoryUnlock && !defaultMoon.StoryIsUnlocked && warnOnStoryLockOverride) { Logger.LogWarning("LethalConstellationsManager: " + reason + " requires default moon '" + defaultMoon.Name + "' to be available, overriding its story lock."); } if (defaultMoon.StoryUnlock) { defaultMoon.StoryIsUnlocked = true; } } private bool DiscoverConstellation(ClassMapper constellation, LMConstellationUnlockable constellationState, string reason) { if (constellation == null || constellationState == null) { return false; } if (!TryGetDefaultMoon(constellation.consName, out var defaultMoon)) { Logger.LogError("LethalConstellationsManager: Unable to resolve the configured default moon for " + reason + "."); return false; } bool discovered = constellationState.Discovered; constellationState.StoryIsUnlocked = true; constellationState.Discovered = true; constellationState.DiscoveredOnce = true; constellationState.NewDiscovery = !discovered; EnsureDefaultMoonAvailable(constellation, warnOnStoryLockOverride: true, reason); SyncConstellationStateFromDefaultMoon(constellation, constellationState, defaultMoon, allowDefaultMoonNewDiscovery: true); EnsureConstellationRotation(constellation.consName); if (!discovered) { SendConstellationDiscoveredAlert(constellation.consName); } return true; } private bool ForceDiscoverConstellation(ClassMapper constellation, LMConstellationUnlockable constellationState, string reason) { if (constellation == null || constellationState == null) { return false; } if (!TryGetDefaultMoon(constellation.consName, out var defaultMoon)) { Logger.LogError("LethalConstellationsManager: Unable to resolve the configured default moon for " + reason + "."); return false; } constellationState.StoryIsUnlocked = true; constellationState.Discovered = true; constellationState.DiscoveredOnce = true; constellationState.NewDiscovery = false; EnsureDefaultMoonAvailable(constellation, warnOnStoryLockOverride: true, reason); SyncConstellationStateFromDefaultMoon(constellation, constellationState, defaultMoon); EnsureConstellationRotation(constellation.consName); return true; } private void SendConstellationDiscoveredAlert(string constellationName) { if (ConfigManager.DiscoveryMode && !string.IsNullOrWhiteSpace(constellationName) && NetworkManager.Instance != null && NetworkManager.Instance.IsServer()) { string value = Configuration.ConstellationWord.Value; NotificationHelper.SendChatMessage("Autopilot discovered new " + value.ToLowerInvariant() + ":\n" + constellationName + ""); NetworkManager.Instance.ServerSendAlertMessage(new Notification { Header = "New Discovery!", Text = Configuration.ConstellationWord.Value + " " + constellationName + " available for routing.", IsWarning = true, Key = "LMU_ConstellationDiscovered", ExceptWhenKey = "LMU_NewQuotaDiscoveryGroup" }); NetworkManager.Instance.ServerSendAlertQueueEvent(); } } private void ApplySilentStartupProgression(ClassMapper constellation, LMConstellationUnlockable constellationState) { if (constellation != null && constellationState != null) { constellationState.UpdateFromConstellation(constellation); TryGetDefaultMoon(constellation.consName, out var defaultMoon); int effectiveOriginalPrice = GetEffectiveOriginalPrice(constellationState, defaultMoon); constellationState.IterateState(effectiveOriginalPrice); constellationState.AdvanceBuyProgression(); constellationState.IterateState(effectiveOriginalPrice); constellationState.VisitRoute(effectiveOriginalPrice); constellationState.IterateState(effectiveOriginalPrice); ApplyConstellationState(constellation, constellationState); } } private bool TryUnlockCustomCondition(ClassMapper constellation, LMConstellationUnlockable constellationState) { if (constellation == null || constellationState == null) { return false; } if (!TryGetActiveCustomUnlockRule(constellation.consName, out var ruleDefinition)) { return false; } if (!TryGetDefaultMoon(constellation.consName, out var defaultMoon)) { Logger.LogError("LethalConstellationsManager: Unable to resolve the configured default moon for constellation '" + constellation.consName + "' while evaluating custom unlock conditions."); return false; } bool flag = false; if (!constellationState.CustomConditionUnlocked) { if (!AreCustomUnlockConditionsSatisfied(constellation.consName, ruleDefinition)) { return false; } constellationState.CustomConditionUnlocked = true; flag = true; } if (constellationState.StoryIsUnlocked && (!IsImmediateDiscoveryStoryReleaseBehavior() || constellationState.Discovered)) { return flag; } if (TryApplyConstellationStoryRelease(constellation, constellationState, defaultMoon, "custom unlock conditions for constellation '" + constellation.consName + "'", out var appliedImmediateDiscovery)) { if (flag) { Logger.LogInfo("LethalConstellationsManager: Custom unlock conditions satisfied for constellation '" + constellation.consName + "', releasing constellation story lock (" + (appliedImmediateDiscovery ? "immediate discovery" : "hidden backlog") + ")."); } else { Logger.LogInfo("LethalConstellationsManager: Applied pending constellation story release for previously satisfied custom unlock conditions on '" + constellation.consName + "' (" + (appliedImmediateDiscovery ? "immediate discovery" : "hidden backlog") + ")."); } } else if (flag) { Logger.LogInfo("LethalConstellationsManager: Custom unlock conditions satisfied for constellation '" + constellation.consName + "', but its default moon story lock is still closed."); } return true; } private bool TryGetActiveCustomUnlockRule(string constellationName, out ConstellationUnlockRuleDefinition ruleDefinition) { ruleDefinition = null; return Plugin.ConstellationUnlockConditions != null && Plugin.ConstellationUnlockConditions.TryGetRuleDefinition(constellationName, out ruleDefinition) && ruleDefinition != null && ruleDefinition.IsEnabled; } private bool AreCustomUnlockConditionsSatisfied(string constellationName, ConstellationUnlockRuleDefinition ruleDefinition) { if (ruleDefinition == null || !ruleDefinition.IsEnabled) { return false; } List list = new List(); if (ruleDefinition.RequiredQuotaCount > 0) { list.Add(UnlockManager.Instance != null && UnlockManager.Instance.QuotaCount >= ruleDefinition.RequiredQuotaCount); } if (ruleDefinition.RequiredVisitedMoons.Count > 0) { list.Add(HaveVisitedAllMoons(constellationName, ruleDefinition.RequiredVisitedMoons)); } if (ruleDefinition.RequiredUniqueMoonVisits > 0) { list.Add(CountUniqueVisitedMoons() >= ruleDefinition.RequiredUniqueMoonVisits); } if (ruleDefinition.RequiredBestiaryEntries.Count > 0) { list.Add(HaveReadRequiredBestiaryEntries(constellationName, ruleDefinition.RequiredBestiaryEntries)); } if (ruleDefinition.RequiredStoryLogs.Count > 0) { list.Add(HaveReadRequiredStoryLogs(constellationName, ruleDefinition.RequiredStoryLogs)); } if (list.Count == 0) { return false; } return (ruleDefinition.MatchMode == ConstellationUnlockMatchMode.All) ? list.All((bool result) => result) : list.Any((bool result) => result); } private bool HaveReadRequiredBestiaryEntries(string constellationName, IReadOnlyList requiredEntries) { if (!Plugin.DawnLibPresent) { WarnTerminalReadConditionRequiresDawnLib(constellationName, "RequiredBestiaryEntries"); return false; } return ProgressionManager.Instance != null && requiredEntries != null && requiredEntries.All(ProgressionManager.Instance.HasReadBestiaryEntry); } private bool HaveReadRequiredStoryLogs(string constellationName, IReadOnlyList requiredEntries) { if (!Plugin.DawnLibPresent) { WarnTerminalReadConditionRequiresDawnLib(constellationName, "RequiredStoryLogs"); return false; } return ProgressionManager.Instance != null && requiredEntries != null && requiredEntries.All(ProgressionManager.Instance.HasReadStoryLog); } private void WarnTerminalReadConditionRequiresDawnLib(string constellationName, string conditionName) { string item = constellationName + "|" + conditionName; if (_terminalReadConditionDawnWarnings.Add(item)) { Logger.LogWarning("LethalConstellationsManager: '" + conditionName + "' for constellation '" + constellationName + "' requires DawnLib. The condition will remain locked without DawnLib present."); } } private static bool IsDefaultMoonStoryGateOpen(LMUnlockable defaultMoon) { return defaultMoon != null && (!defaultMoon.StoryUnlock || defaultMoon.StoryIsUnlocked); } private bool TryApplyConstellationStoryRelease(ClassMapper constellation, LMConstellationUnlockable constellationState, LMUnlockable defaultMoon, string reason, out bool appliedImmediateDiscovery) { appliedImmediateDiscovery = false; if (constellation == null || constellationState == null || defaultMoon == null) { return false; } if (!IsConstellationStoryGateOpen(constellation.consName, constellationState, defaultMoon, out var _)) { SyncConstellationStateFromDefaultMoon(constellation, constellationState, defaultMoon); return false; } if (IsImmediateDiscoveryStoryReleaseBehavior()) { appliedImmediateDiscovery = true; return DiscoverConstellation(constellation, constellationState, reason); } constellationState.StoryIsUnlocked = true; SyncConstellationStateFromDefaultMoon(constellation, constellationState, defaultMoon); return true; } private bool TryGetCustomConditionGateState(string constellationName, LMConstellationUnlockable constellationState, out bool customGateOpen, out bool ignoreDefaultMoonStoryLock) { customGateOpen = true; ignoreDefaultMoonStoryLock = false; if (constellationState == null) { return false; } bool customConditionUnlocked = constellationState.CustomConditionUnlocked; if (!TryGetActiveCustomUnlockRule(constellationName, out var ruleDefinition)) { customGateOpen = customConditionUnlocked; return customConditionUnlocked; } customGateOpen = constellationState.CustomConditionUnlocked; ignoreDefaultMoonStoryLock = ruleDefinition.IgnoreDefaultMoonStoryLock; return true; } private bool IsConstellationStoryGateOpen(string constellationName, LMConstellationUnlockable constellationState, LMUnlockable defaultMoon, out bool ignoreDefaultMoonStoryLock) { bool flag = IsDefaultMoonStoryGateOpen(defaultMoon); if (!TryGetCustomConditionGateState(constellationName, constellationState, out var customGateOpen, out ignoreDefaultMoonStoryLock)) { return flag; } return ignoreDefaultMoonStoryLock ? customGateOpen : (flag && customGateOpen); } private bool HaveVisitedAllMoons(string constellationName, IReadOnlyList moonNames) { return moonNames != null && moonNames.Count > 0 && moonNames.All((string moonName) => HasVisitedMoon(constellationName, moonName)); } private bool HasVisitedMoon(string constellationName, string moonName) { if (!_moonNameToLevelIdLookup.ContainsKey(moonName)) { string item = constellationName + "|" + moonName; if (_invalidVisitedMoonRuleWarnings.Add(item)) { Logger.LogWarning("LethalConstellationsManager: Custom unlock condition for constellation '" + constellationName + "' references unknown moon '" + moonName + "'."); } return false; } return UnlockManager.Instance?.Unlocks.Any((LMUnlockable unlock) => string.Equals(unlock.Name, moonName, StringComparison.OrdinalIgnoreCase) && unlock.VisitCount > 0) ?? false; } private static int CountUniqueVisitedMoons() { if (UnlockManager.Instance?.Unlocks == null) { return 0; } return UnlockManager.Instance.Unlocks.Count((LMUnlockable unlock) => unlock.VisitCount > 0); } private void SyncConstellationStateFromDefaultMoon(ClassMapper constellation, LMConstellationUnlockable constellationState, LMUnlockable defaultMoon, bool allowDefaultMoonNewDiscovery = false) { if (constellation != null && constellationState != null && defaultMoon != null) { bool ignoreDefaultMoonStoryLock; bool flag = IsConstellationStoryGateOpen(constellation.consName, constellationState, defaultMoon, out ignoreDefaultMoonStoryLock); bool discovered = constellationState.Discovered; constellationState.StoryIsUnlocked = flag || discovered; ApplyConstellationState(constellation, constellationState); SyncDefaultMoonAvailability(constellationState, defaultMoon, allowDefaultMoonNewDiscovery, defaultMoon.StoryUnlock && (!ignoreDefaultMoonStoryLock || defaultMoon.StoryIsUnlocked)); } } private void ApplyConstellationState(ClassMapper constellation, LMConstellationUnlockable constellationState) { if (constellation != null && constellationState != null) { bool flag = IsConstellationAvailable(constellationState); constellation.isHidden = !flag; constellation.isLocked = !flag; } } private ConstellationEconomyTarget CreateEconomyTarget(ClassMapper constellation, LMConstellationUnlockable state) { if (constellation == null || state == null) { return null; } state.UpdateFromConstellation(constellation); TryGetDefaultMoon(constellation.consName, out var defaultMoon); int effectiveOriginalPrice = GetEffectiveOriginalPrice(state, defaultMoon); state.IterateState(effectiveOriginalPrice); return new ConstellationEconomyTarget { Constellation = constellation, State = state, DefaultMoon = defaultMoon, EffectivePrice = state.RoutePrice, EffectiveOriginalPrice = effectiveOriginalPrice }; } private static bool IsConstellationDiscovered(LMConstellationUnlockable state) { return state?.Discovered ?? false; } private static bool IsConstellationAvailable(LMConstellationUnlockable state) { if (state == null) { return false; } if (!ConfigManager.DiscoveryMode) { return state.StoryIsUnlocked || state.Discovered; } return IsConstellationDiscovered(state); } private bool HasDiscoveredConstellations() { foreach (ClassMapper value in _constellationLookup.Values) { if (TryGetConstellationState(value.consName, out var constellationState) && IsConstellationAvailable(constellationState)) { return true; } } return false; } private static int GetEffectiveOriginalPrice(LMConstellationUnlockable state, LMUnlockable defaultMoon) { if (ConfigManager.LethalConstellationsOverridePrice && defaultMoon != null) { return defaultMoon.OriginalPrice; } return state?.OriginalPrice ?? 0; } private void SyncDefaultMoonAvailability(LMConstellationUnlockable constellationState, LMUnlockable defaultMoon, bool allowDefaultMoonNewDiscovery, bool releaseDefaultMoonStoryLock = true) { if (constellationState == null || defaultMoon == null || !constellationState.Discovered) { return; } if (releaseDefaultMoonStoryLock && defaultMoon.StoryUnlock) { defaultMoon.StoryIsUnlocked = true; } if (UnlockManager.Instance != null && UnlockManager.Instance.UseConstellationDiscovery) { defaultMoon.SetDiscoveryState(discovered: true, !allowDefaultMoonNewDiscovery); return; } defaultMoon.Discovered = true; if (!defaultMoon.DiscoveredOnce) { defaultMoon.DiscoveredOnce = true; defaultMoon.NewDiscovery = allowDefaultMoonNewDiscovery; } } private bool TryGetConstellationState(string constellationName, out LMConstellationUnlockable constellationState) { return _extension.TryGetConstellationState(constellationName, out constellationState); } private HashSet GetAllVisibleMoonIds() { HashSet hashSet = new HashSet(); if (UnlockManager.Instance == null || !UnlockManager.Instance.UseConstellationDiscovery || !Bootstrap()) { return hashSet; } foreach (ClassMapper value in _constellationLookup.Values) { if (!TryGetConstellationState(value.consName, out var constellationState) || !constellationState.Discovered) { continue; } foreach (LMUnlockable visibleConstellationUnlock in GetVisibleConstellationUnlocks(value.consName)) { if (TryGetLevelId(visibleConstellationUnlock, out var levelId)) { hashSet.Add(levelId); } } } return hashSet; } private List GetConstellationUnlocks(ClassMapper constellation) { List list = new List(); if (constellation == null || !_constellationMoonLevelLookup.TryGetValue(constellation.consName, out var value)) { return list; } foreach (int item in value) { if (_unlockByLevelIdLookup.TryGetValue(item, out var value2) && value2 != null) { list.Add(value2); } } return list; } private List ResolveMoonNames(IEnumerable moonLevelIds) { List list = new List(); if (moonLevelIds == null) { return list; } foreach (int moonLevelId in moonLevelIds) { if (TryResolveMoonName(moonLevelId, out var moonName)) { list.Add(moonName); } } return list.Distinct(StringComparer.OrdinalIgnoreCase).OrderBy((string name) => name, StringComparer.OrdinalIgnoreCase).ToList(); } private bool TryResolveMoonName(int levelId, out string moonName) { moonName = string.Empty; if (!_unlockByLevelIdLookup.TryGetValue(levelId, out var value) || value == null || string.IsNullOrWhiteSpace(value.Name)) { return false; } moonName = value.Name; return true; } private static bool TryGetLevelId(LMUnlockable unlock, out int levelId) { levelId = -1; object obj; if (unlock == null) { obj = null; } else { ExtendedLevel extendedLevel = unlock.ExtendedLevel; obj = ((extendedLevel != null) ? extendedLevel.SelectableLevel : null); } if ((Object)obj == (Object)null) { return false; } levelId = unlock.ExtendedLevel.SelectableLevel.levelID; return levelId >= 0; } private static bool IsEligibleForLocalRotation(LMUnlockable unlock) { if (unlock == null || unlock.PermanentlyDiscovered) { return false; } return (!unlock.OriginallyLocked && !unlock.OriginallyHidden && !unlock.StoryUnlock) || (unlock.StoryUnlock && unlock.StoryIsUnlocked); } } [Serializable] [ES3Serializable] internal class LethalConstellationsSaveData { [ES3Serializable] internal Dictionary Constellations { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); [ES3Serializable] internal Dictionary> ConstellationRotationMoons { get; set; } = new Dictionary>(StringComparer.OrdinalIgnoreCase); [ES3Serializable] internal Dictionary> LocalConstellationDiscoveries { get; set; } = new Dictionary>(StringComparer.OrdinalIgnoreCase); internal LMConstellationUnlockable GetOrCreate(string constellationName) { if (string.IsNullOrWhiteSpace(constellationName)) { return null; } if (Constellations == null) { Dictionary dictionary2 = (Constellations = new Dictionary(StringComparer.OrdinalIgnoreCase)); } if (!Constellations.TryGetValue(constellationName, out var value) || value == null) { value = new LMConstellationUnlockable(constellationName); Constellations[constellationName] = value; } return value; } internal bool TryGet(string constellationName, out LMConstellationUnlockable constellationState) { constellationState = null; if (string.IsNullOrWhiteSpace(constellationName)) { return false; } if (Constellations == null) { Dictionary dictionary2 = (Constellations = new Dictionary(StringComparer.OrdinalIgnoreCase)); } return Constellations.TryGetValue(constellationName, out constellationState) && constellationState != null; } internal void Set(LMConstellationUnlockable constellationState) { if (constellationState != null && !string.IsNullOrWhiteSpace(constellationState.Name)) { if (Constellations == null) { Dictionary dictionary2 = (Constellations = new Dictionary(StringComparer.OrdinalIgnoreCase)); } Constellations[constellationState.Name] = constellationState; } } internal void SynchronizeDefinitions(IEnumerable constellations) { if (constellations == null) { return; } foreach (ClassMapper constellation in constellations) { if (constellation != null && !string.IsNullOrWhiteSpace(constellation.consName)) { GetOrCreate(constellation.consName)?.UpdateFromConstellation(constellation); } } } internal bool HasData() { Dictionary constellations = Constellations; int result; if (constellations == null || !constellations.Values.Any((LMConstellationUnlockable constellation) => constellation?.HasData() ?? false)) { Dictionary> constellationRotationMoons = ConstellationRotationMoons; if (constellationRotationMoons == null || !constellationRotationMoons.Any((KeyValuePair> entry) => !string.IsNullOrWhiteSpace(entry.Key) && entry.Value != null && entry.Value.Count > 0)) { result = ((LocalConstellationDiscoveries?.Any((KeyValuePair> entry) => !string.IsNullOrWhiteSpace(entry.Key) && entry.Value != null && entry.Value.Count > 0) ?? false) ? 1 : 0); goto IL_00a0; } } result = 1; goto IL_00a0; IL_00a0: return (byte)result != 0; } internal LethalConstellationsSaveData Copy() { LethalConstellationsSaveData lethalConstellationsSaveData = new LethalConstellationsSaveData(); if (Constellations != null) { foreach (KeyValuePair constellation in Constellations) { if (constellation.Value != null) { lethalConstellationsSaveData.Constellations[constellation.Key] = constellation.Value.Clone(); } } } if (ConstellationRotationMoons != null) { foreach (KeyValuePair> constellationRotationMoon in ConstellationRotationMoons) { if (!string.IsNullOrWhiteSpace(constellationRotationMoon.Key)) { lethalConstellationsSaveData.ConstellationRotationMoons[constellationRotationMoon.Key] = ((constellationRotationMoon.Value == null) ? new List() : constellationRotationMoon.Value.Where((string name) => !string.IsNullOrWhiteSpace(name)).Distinct(StringComparer.OrdinalIgnoreCase).ToList()); } } } if (LocalConstellationDiscoveries != null) { foreach (KeyValuePair> localConstellationDiscovery in LocalConstellationDiscoveries) { if (!string.IsNullOrWhiteSpace(localConstellationDiscovery.Key)) { lethalConstellationsSaveData.LocalConstellationDiscoveries[localConstellationDiscovery.Key] = ((localConstellationDiscovery.Value == null) ? new List() : localConstellationDiscovery.Value.Where((string name) => !string.IsNullOrWhiteSpace(name)).Distinct(StringComparer.OrdinalIgnoreCase).ToList()); } } } return lethalConstellationsSaveData; } internal void Clear() { Constellations?.Clear(); ConstellationRotationMoons?.Clear(); LocalConstellationDiscoveries?.Clear(); } } [Serializable] internal sealed class LethalConstellationsSyncData { public List constellations = new List(); public List constellationRotationMoons = new List(); public List localConstellationDiscoveries = new List(); internal static LethalConstellationsSyncData FromSaveData(LethalConstellationsSaveData saveData) { LethalConstellationsSyncData lethalConstellationsSyncData = new LethalConstellationsSyncData(); if (saveData == null) { return lethalConstellationsSyncData; } if (saveData.Constellations != null) { foreach (KeyValuePair item in saveData.Constellations.Where((KeyValuePair entry) => !string.IsNullOrWhiteSpace(entry.Key) && entry.Value != null).OrderBy, string>((KeyValuePair entry) => entry.Key, StringComparer.OrdinalIgnoreCase)) { LMConstellationUnlockableSyncData lMConstellationUnlockableSyncData = item.Value.BuildSyncData(); lMConstellationUnlockableSyncData.name = item.Key; lethalConstellationsSyncData.constellations.Add(lMConstellationUnlockableSyncData); } } lethalConstellationsSyncData.constellationRotationMoons = BuildNamedLists(saveData.ConstellationRotationMoons); lethalConstellationsSyncData.localConstellationDiscoveries = BuildNamedLists(saveData.LocalConstellationDiscoveries); return lethalConstellationsSyncData; } internal LethalConstellationsSaveData ToSaveData() { LethalConstellationsSaveData lethalConstellationsSaveData = new LethalConstellationsSaveData(); foreach (LMConstellationUnlockableSyncData item in constellations ?? new List()) { if (item != null && !string.IsNullOrWhiteSpace(item.name)) { LMConstellationUnlockable lMConstellationUnlockable = new LMConstellationUnlockable(item.name); lMConstellationUnlockable.ApplySyncData(item); lethalConstellationsSaveData.Constellations[item.name] = lMConstellationUnlockable; } } CopyNamedLists(constellationRotationMoons, lethalConstellationsSaveData.ConstellationRotationMoons); CopyNamedLists(localConstellationDiscoveries, lethalConstellationsSaveData.LocalConstellationDiscoveries); return lethalConstellationsSaveData; } private static List BuildNamedLists(Dictionary> source) { List list = new List(); if (source == null) { return list; } foreach (KeyValuePair> item in source.Where((KeyValuePair> entry) => !string.IsNullOrWhiteSpace(entry.Key)).OrderBy>, string>((KeyValuePair> entry) => entry.Key, StringComparer.OrdinalIgnoreCase)) { list.Add(new NamedStringListSyncData { name = item.Key, values = ((item.Value == null) ? new List() : item.Value.Where((string value) => !string.IsNullOrWhiteSpace(value)).Distinct(StringComparer.OrdinalIgnoreCase).ToList()) }); } return list; } private static void CopyNamedLists(IEnumerable source, Dictionary> target) { if (source == null || target == null) { return; } foreach (NamedStringListSyncData item in source) { if (item != null && !string.IsNullOrWhiteSpace(item.name)) { target[item.name] = ((item.values == null) ? new List() : item.values.Where((string value) => !string.IsNullOrWhiteSpace(value)).Distinct(StringComparer.OrdinalIgnoreCase).ToList()); } } } } [Serializable] [ES3Serializable] internal class LMConstellationUnlockable { [ES3Serializable] internal string Name { get; set; } [ES3Serializable] internal bool StoryIsUnlocked { get; set; } [ES3Serializable] internal bool CustomConditionUnlocked { get; set; } [ES3Serializable] internal bool Discovered { get; set; } [ES3Serializable] internal bool DiscoveredOnce { get; set; } [ES3Serializable] internal bool NewDiscovery { get; set; } [ES3Serializable] internal int BuyCount { get; set; } [ES3Serializable] internal int VisitCount { get; set; } [ES3Serializable] internal int FreeVisitCount { get; set; } [ES3NonSerializable] internal int RoutePrice { get; set; } [ES3NonSerializable] internal int OriginalPrice { get; set; } [ES3NonSerializable] internal bool HasOriginalPriceSnapshot { get; set; } [ES3Serializable] internal bool OnSale { get; set; } [ES3Serializable] internal int SalesRate { get; set; } public LMConstellationUnlockable() { } internal LMConstellationUnlockable(string constellationName) { Name = constellationName; } internal LMConstellationUnlockable(ClassMapper constellation) { UpdateFromConstellation(constellation); } internal void UpdateFromConstellation(ClassMapper constellation) { if (constellation != null) { Name = constellation.consName; if (!HasOriginalPriceSnapshot) { OriginalPrice = constellation.constelPrice; HasOriginalPriceSnapshot = true; } } } internal void OverrideData(LMConstellationUnlockable other) { if (other != null) { Name = other.Name; StoryIsUnlocked = other.StoryIsUnlocked; CustomConditionUnlocked = other.CustomConditionUnlocked; Discovered = other.Discovered; DiscoveredOnce = other.DiscoveredOnce; NewDiscovery = other.NewDiscovery; BuyCount = other.BuyCount; VisitCount = other.VisitCount; FreeVisitCount = other.FreeVisitCount; RoutePrice = other.RoutePrice; OriginalPrice = other.OriginalPrice; HasOriginalPriceSnapshot = other.HasOriginalPriceSnapshot; OnSale = other.OnSale; SalesRate = other.SalesRate; } } internal LMConstellationUnlockableSyncData BuildSyncData() { return new LMConstellationUnlockableSyncData { name = Name, storyIsUnlocked = StoryIsUnlocked, customConditionUnlocked = CustomConditionUnlocked, discovered = Discovered, discoveredOnce = DiscoveredOnce, newDiscovery = NewDiscovery, buyCount = BuyCount, visitCount = VisitCount, freeVisitCount = FreeVisitCount, routePrice = RoutePrice, originalPrice = OriginalPrice, hasOriginalPriceSnapshot = HasOriginalPriceSnapshot, onSale = OnSale, salesRate = SalesRate }; } internal void ApplySyncData(LMConstellationUnlockableSyncData syncData) { if (syncData != null) { Name = syncData.name; StoryIsUnlocked = syncData.storyIsUnlocked; CustomConditionUnlocked = syncData.customConditionUnlocked; Discovered = syncData.discovered; DiscoveredOnce = syncData.discoveredOnce; NewDiscovery = syncData.newDiscovery; BuyCount = syncData.buyCount; VisitCount = syncData.visitCount; FreeVisitCount = syncData.freeVisitCount; RoutePrice = syncData.routePrice; OriginalPrice = syncData.originalPrice; HasOriginalPriceSnapshot = syncData.hasOriginalPriceSnapshot; OnSale = syncData.onSale; SalesRate = syncData.salesRate; } } internal LMConstellationUnlockable Clone() { LMConstellationUnlockable lMConstellationUnlockable = new LMConstellationUnlockable(); lMConstellationUnlockable.OverrideData(this); return lMConstellationUnlockable; } internal bool HasData() { return StoryIsUnlocked || CustomConditionUnlocked || Discovered || DiscoveredOnce || NewDiscovery || BuyCount > 0 || VisitCount > 0 || FreeVisitCount > 0 || OnSale || SalesRate > 0; } internal void IterateState(int? originalPriceOverride = null) { CalculatePrice(originalPriceOverride ?? OriginalPrice); } internal void AdvanceBuyProgression() { if (ConfigManager.DiscountMode) { if (BuyCount < ConfigManager.DiscountsCount) { BuyCount++; } } else { BuyCount++; } } internal void VisitRoute(int? originalPriceOverride = null) { VisitCount++; int num = originalPriceOverride ?? OriginalPrice; if ((RoutePrice == 0 || (ConfigManager.DiscountMode && BuyCount == ConfigManager.DiscountsCount)) && num != RoutePrice) { FreeVisitCount++; if (ConfigManager.UnlockMode && !ConfigManager.DiscountMode && ConfigManager.UnlocksResetAfterVisits > 0 && FreeVisitCount > ConfigManager.UnlocksResetAfterVisits) { BuyCount = 0; FreeVisitCount = 0; } else if (ConfigManager.DiscountMode && ConfigManager.DiscountsResetAfterVisits > 0 && FreeVisitCount > ConfigManager.DiscountsResetAfterVisits) { BuyCount = 0; FreeVisitCount = 0; } } } internal int CalculatePrice(bool includeSale = true) { return CalculatePrice(OriginalPrice, includeSale); } internal int GetCalculatedPrice(int originalPrice, bool includeSale = true) { int num = originalPrice; if (BuyCount > 0) { if (ConfigManager.DiscountMode) { float discountRate = Plugin.GetDiscountRate(BuyCount); num = (int)((float)num * discountRate); if (num <= 0 && discountRate > 0f) { num = 1; } } else if (ConfigManager.UnlockMode) { num = 0; } } if (includeSale && OnSale && num > 0) { num = (int)((float)(num * (100 - SalesRate)) / 100f); if (num <= 0) { num = 1; } } return num; } internal int CalculatePrice(int originalPrice, bool includeSale = true) { RoutePrice = GetCalculatedPrice(originalPrice, includeSale); return RoutePrice; } internal void RefreshSale(int? originalPriceOverride = null) { int originalPrice = originalPriceOverride ?? OriginalPrice; if (RandomHelper.Chance(ConfigManager.SalesChance) && CalculatePrice(originalPrice, includeSale: false) > 0) { OnSale = true; SalesRate = ConfigManager.SalesRate; } else { OnSale = false; SalesRate = 0; } } internal string BuildAdditionalInfoString() { if (!ConfigManager.DisplayTerminalTags) { return string.Empty; } string text = string.Empty; if (NewDiscovery && ConfigManager.DiscoveryMode && ConfigManager.ShowTagNewDiscovery) { text = AddTag("[NEW]", text); } if (VisitCount > 0 && ConfigManager.ShowTagExplored) { text = AddTag($"[VISITS:{VisitCount}]", text); } if (FreeVisitCount > 0 && ConfigManager.UnlockMode && !ConfigManager.DiscountMode && ConfigManager.UnlocksResetAfterVisits > 0 && ConfigManager.ShowTagUnlockDiscount) { text = AddTag($"[UNLOCK EXPIRES:{ConfigManager.UnlocksResetAfterVisits - FreeVisitCount + 1}]", text); } else if (FreeVisitCount > 0 && ConfigManager.DiscountMode && ConfigManager.DiscountsResetAfterVisits > 0 && ConfigManager.ShowTagUnlockDiscount) { text = AddTag($"[DISCOUNT EXPIRES:{ConfigManager.DiscountsResetAfterVisits - FreeVisitCount + 1}]", text); } else if (ConfigManager.UnlockMode && !ConfigManager.DiscountMode && BuyCount > 0 && ConfigManager.ShowTagUnlockDiscount) { text = AddTag("[UNLOCKED]", text); } else if (ConfigManager.DiscountMode && BuyCount > 0 && ConfigManager.ShowTagUnlockDiscount) { int discountPercentOff = Plugin.GetDiscountPercentOff(BuyCount); text = AddTag((discountPercentOff != 100) ? $"[DISCOUNT {discountPercentOff}%]" : "[FULL DISCOUNT]", text); } if (OnSale && SalesRate > 0 && RoutePrice > 0 && ConfigManager.Sales && ConfigManager.ShowTagSale) { text = AddTag($"[SALE {SalesRate}%]", text); } return string.IsNullOrEmpty(text) ? string.Empty : ("\nInfo: " + text); } private static string AddTag(string tag, string tags) { if (string.IsNullOrWhiteSpace(tag)) { return tags; } if (string.IsNullOrWhiteSpace(tags)) { return tag; } return tags + " " + tag; } } [Serializable] internal sealed class LMConstellationUnlockableSyncData { public string name = string.Empty; public bool storyIsUnlocked; public bool customConditionUnlocked; public bool discovered; public bool discoveredOnce; public bool newDiscovery; public int buyCount; public int visitCount; public int freeVisitCount; public int routePrice; public int originalPrice; public bool hasOriginalPriceSnapshot; public bool onSale; public int salesRate; } internal static class LQCompatibility { internal static string GetLQRiskLevel(LMUnlockable unlock) { Type type = ((object)Plugin.INSTANCE).GetType(); FieldInfo field = type.GetField("presets", BindingFlags.Instance | BindingFlags.NonPublic); Dictionary dictionary = (Dictionary)field.GetValue(Plugin.INSTANCE); LevelPreset value; LevelPreset val = (dictionary.TryGetValue(SelectableLevelCache.getGuid(unlock.ExtendedLevel.SelectableLevel), out value) ? value : null); if (val != null) { return val.riskLevel.value; } return string.Empty; } } [HarmonyPatch] internal class MalfunctionsCompatibility { private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("Malfunctions.Patches.StartOfRoundPatches"); return AccessTools.FirstMethod(type, (Func)((MethodInfo method) => method.Name.Contains("HandleRollNavigation"))); } [HarmonyPostfix] private static void HandleRollNavigationPostfix(bool result, int level) { if (result && !(((Object)StartOfRound.Instance.currentLevel).name == "CompanyBuildingLevel") && TimeOfDay.Instance.daysUntilDeadline >= 2 && ConfigManager.MalfunctionsNavigation) { LMUnlockable lMUnlockable = UnlockManager.Instance.Unlocks.Where((LMUnlockable unlock) => unlock.ExtendedLevel.SelectableLevel.levelID == level).FirstOrDefault(); if (lMUnlockable != null && NetworkManager.Instance.IsServer() && lMUnlockable.ExtendedLevel.RoutePrice > 0) { Logger.LogInfo("Interpreting navigation malfunction as buying the moon.."); UnlockManager.Instance.BuyMoon(lMUnlockable.Name); } } } } [Serializable] internal sealed class NamedStringListSyncData { public string name = string.Empty; public List values = new List(); } internal static class TerminalStuffCompatibility { private static readonly FieldInfo LevelField = AccessTools.Field(typeof(MoonInfo), "Level"); private static readonly FieldInfo PurchaseNodeField = AccessTools.Field(typeof(MoonInfo), "PurchaseNode"); private static readonly PropertyInfo DisplayPriceProperty = AccessTools.Property(typeof(MoonInfo), "DisplayPrice"); private static int _groupCredits; private static bool _shouldHandleDirectMoonsPlusPurchase; private static int _selectedMoonDisplayPrice; internal static void OnUpdateMoonsDisplayed(List moons) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown foreach (MoonInfo moon in moons) { if (!ConfigManager.DisplayTerminalTags) { moon.AdditionalInfo = string.Empty; continue; } try { SelectableLevel level = (SelectableLevel)LevelField.GetValue(moon); LMUnlockable lMUnlockable = UnlockManager.Instance.Unlocks.FirstOrDefault((LMUnlockable x) => (Object)(object)x.ExtendedLevel.SelectableLevel == (Object)(object)level); if (lMUnlockable != null) { moon.AdditionalInfo = lMUnlockable.BuildAdditionalInfoString(); continue; } if (level.PlanetName.Contains("Gordion")) { moon.AdditionalInfo = string.Empty + "\n"; continue; } throw new Exception("TerminalStuffCompatibility: Moon not found in UnlockManager!"); } catch (Exception ex) { Logger.LogError("TerminalStuffCompatibility: Failed to get MoonInfo.Level!"); Logger.LogError(ex.Message); } } } [HarmonyPatch(typeof(MoonInfo), "SelectThisMoon")] [HarmonyPrefix] private static void SelectThisMoonPrefix(MoonInfo __instance) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown _groupCredits = UnlockManager.Instance.Terminal.groupCredits; _selectedMoonDisplayPrice = 0; _shouldHandleDirectMoonsPlusPurchase = false; if (__instance != null && !((Object)(object)UnlockManager.Instance?.Terminal == (Object)null) && !((Object)(object)StartOfRound.Instance == (Object)null)) { SelectableLevel val = (SelectableLevel)(LevelField?.GetValue(__instance)); if (!((Object)(object)val == (Object)null)) { int displayPrice = GetDisplayPrice(__instance); bool flag = MoonsPlusConfig.UseVanillaPurchaseNodes.Value && PurchaseNodeField?.GetValue(__instance) is TerminalNode; _selectedMoonDisplayPrice = displayPrice; _shouldHandleDirectMoonsPlusPurchase = !StartOfRound.Instance.travellingToNewLevel && StartOfRound.Instance.inShipPhase && (Object)(object)StartOfRound.Instance.currentLevel != (Object)(object)val && displayPrice <= _groupCredits && !flag; } } } [HarmonyPatch(typeof(MoonInfo), "SelectThisMoon")] [HarmonyPostfix] private static void SelectThisMoonPostfix(MoonInfo __instance) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown if (!_shouldHandleDirectMoonsPlusPurchase) { return; } SelectableLevel level = (SelectableLevel)LevelField.GetValue(__instance); if ((Object)(object)level == (Object)null) { Logger.LogWarning("TerminalStuffCompatibility: Failed to resolve selected moon level after MoonsPlus route."); return; } LMUnlockable lMUnlockable = UnlockManager.Instance.Unlocks.FirstOrDefault((LMUnlockable x) => (Object)(object)x.ExtendedLevel.SelectableLevel == (Object)(object)level); if (lMUnlockable == null) { Logger.LogWarning("TerminalStuffCompatibility: Failed to resolve LMUnlockable for MoonsPlus route '" + level.PlanetName + "'."); return; } if (_selectedMoonDisplayPrice <= 0) { Logger.LogInfo("Route to " + lMUnlockable.ExtendedLevel.SelectableLevel.PlanetName + " was free (routed via MoonsPlus)."); return; } Logger.LogInfo($"Route to {lMUnlockable.ExtendedLevel.SelectableLevel.PlanetName} was paid ({_selectedMoonDisplayPrice} credits) (routed via MoonsPlus)."); if (NetworkManager.Instance.IsServer()) { UnlockManager.Instance.BuyMoon(lMUnlockable.Name); } else { NetworkManager.Instance.ClientBuyMoon(lMUnlockable.Name); } } private static int GetDisplayPrice(MoonInfo moonInfo) { if (moonInfo == null || DisplayPriceProperty == null) { return 0; } try { return (DisplayPriceProperty.GetValue(moonInfo) is int num) ? num : 0; } catch (Exception ex) { Logger.LogWarning("TerminalStuffCompatibility: Failed to resolve DisplayPrice for MoonsPlus route. " + ex.Message); return 0; } } } [HarmonyPatch] internal static class WTCompatibility { internal static Dictionary WTWeathers = new Dictionary(); private static MethodBase TargetMethod() { Assembly assembly = typeof(Plugin).Assembly; Type type = assembly.GetType("WeatherTweaks.Variables"); return AccessTools.FirstMethod(type, (Func)((MethodInfo method) => method.Name.Contains("GetPlanetCurrentWeather"))); } [HarmonyPostfix] private static void GetPlanetCurrentWeatherPostfix(SelectableLevel level, bool uncertain, ref string __result) { WTWeathers[level] = __result; } internal static string GetWeatherTweaksWeather(LMUnlockable unlock) { string value; string text = (WTWeathers.TryGetValue(unlock.ExtendedLevel.SelectableLevel, out value) ? value : null); if (text != null) { return text; } return string.Empty; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } internal static class IsExternalInit { } }