using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; using UnityEngine.UI; [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace PlanetaryAnomalies; internal sealed class PlanetAnomaly { internal readonly int PlanetId; internal readonly int RecipeId; internal readonly int OutputMultiplier; internal readonly RecipeExecuteData AnomalousExecuteData; internal PlanetAnomaly(int planetId, int recipeId, int outputMultiplier, RecipeExecuteData anomalousExecuteData) { PlanetId = planetId; RecipeId = recipeId; OutputMultiplier = outputMultiplier; AnomalousExecuteData = anomalousExecuteData; } } internal static class AnomalyManager { internal const int AnomalySystemVersion = 1; internal const int DensityMinPercent = 25; internal const int DensityMaxPercent = 75; private const uint SaltPresence = 2654435769u; private const uint SaltRecipe = 2246822507u; private const uint SaltDensity = 3266489909u; private static int _densityPercent = 75; private static int _galaxySeed; private static int _birthPlanetId = -1; private static bool _galaxyKnown; private static readonly Dictionary _byPlanet = new Dictionary(); private static RecipeProto[] _eligible; private static bool _versionLogged; private static string _waitReason; internal static int OutputMultiplier { get { if (Plugin.OutputMultiplier == null) { return 10; } return Plugin.OutputMultiplier.Value; } } internal static int DensityPercent => _densityPercent; internal static void Reset() { _galaxySeed = 0; _birthPlanetId = -1; _galaxyKnown = false; _byPlanet.Clear(); _eligible = null; _versionLogged = false; _waitReason = null; } private static void Waiting(string reason) { if (!(_waitReason == reason)) { _waitReason = reason; Plugin.Log.LogInfo((object)("Waiting: " + reason)); } } internal static PlanetAnomaly AnomalyFor(int planetId) { if (!EnsureGalaxy()) { return null; } if (_byPlanet.TryGetValue(planetId, out var value)) { return value; } PlanetAnomaly planetAnomaly = Derive(planetId); _byPlanet[planetId] = planetAnomaly; if (planetAnomaly != null) { LogAnomaly(planetAnomaly, planetId); } else { LogNoAnomaly(planetId); } return planetAnomaly; } private static bool EnsureGalaxy() { GameData data = GameMain.data; if (data == null) { Waiting("no game data yet (no save loaded)."); return false; } GalaxyData galaxy = data.galaxy; if (galaxy == null) { Waiting("game data exists but the galaxy is not generated yet."); return false; } int birthPlanetId = galaxy.birthPlanetId; if (birthPlanetId <= 0) { Waiting("galaxy exists but birthPlanetId is not set yet."); return false; } int num = ((data.gameDesc != null) ? data.gameDesc.galaxySeed : 0); if (_galaxyKnown && _galaxySeed == num && _birthPlanetId == birthPlanetId) { return _eligible != null; } _byPlanet.Clear(); _eligible = null; _galaxySeed = num; _birthPlanetId = birthPlanetId; RecipeProtoSet recipes = LDB.recipes; if ((Object)(object)recipes == (Object)null || ((ProtoSet)(object)recipes).dataArray == null || ((ProtoSet)(object)recipes).dataArray.Length == 0) { Waiting("the recipe database (LDB.recipes) is not loaded yet."); return false; } if (RecipeProto.recipeExecuteData == null) { Waiting("RecipeProto.recipeExecuteData is null (InitRecipeItems has not run)."); return false; } _eligible = BuildEligibleRecipes(recipes); if (_eligible.Length == 0) { Plugin.Log.LogError((object)"No eligible recipes in this build; no planet will be anomalous."); return false; } _densityPercent = ResolveDensity(num); _galaxyKnown = true; LogGameVersionOnce(); Plugin.Log.LogInfo((object)("Galaxy seed " + num + ": " + _eligible.Length + " eligible recipes, " + _densityPercent + "% of non-home planets anomalous" + (IsDensityOverridden() ? " (forced by config)" : " (derived from the seed)") + ", anomaly system v" + 1 + ".")); return true; } private static int ResolveDensity(int seed) { if (IsDensityOverridden()) { return Plugin.AnomalyChancePercent.Value; } uint num = 51u; return (int)(25 + Hash(seed, 0, 1, 3266489909u) % num); } private static bool IsDensityOverridden() { if (Plugin.AnomalyChancePercent != null) { return Plugin.AnomalyChancePercent.Value >= 0; } return false; } private static PlanetAnomaly Derive(int planetId) { if (planetId == _birthPlanetId) { return null; } uint num = Hash(_galaxySeed, planetId, 1, 2654435769u); if (num % 100 >= (uint)_densityPercent) { return null; } RecipeProto val = ChooseRecipe(planetId); if (val == null) { return null; } if (!RecipeProto.recipeExecuteData.TryGetValue(((Proto)val).ID, out var value) || value == null) { Waiting("no execute data cached for recipe " + ((Proto)val).ID + " yet."); return null; } if (value.products == null || value.productCounts == null || value.requires == null || value.requireCounts == null) { Plugin.Log.LogError((object)("Recipe " + ((Proto)val).ID + " has incomplete execute data; refusing to modify it.")); return null; } return new PlanetAnomaly(planetId, ((Proto)val).ID, OutputMultiplier, BuildAnomalousExecuteData(value)); } private static uint Hash(int seed, int planetId, int version, uint salt) { uint h = 2166136261u; h = MixBytes(h, (uint)seed); h = MixBytes(h, (uint)planetId); h = MixBytes(h, (uint)version); h = MixBytes(h, salt); h ^= h >> 16; h *= 2246822507u; h ^= h >> 13; h *= 3266489909u; return h ^ (h >> 16); } private static RecipeProto ChooseRecipe(int planetId) { RecipeProto val = null; uint num = 0u; for (int i = 0; i < _eligible.Length; i++) { RecipeProto val2 = _eligible[i]; uint num2 = RecipeWeight(_galaxySeed, planetId, 1, ((Proto)val2).ID); if (val == null || num2 > num || (num2 == num && ((Proto)val2).ID < ((Proto)val).ID)) { val = val2; num = num2; } } return val; } private static uint RecipeWeight(int seed, int planetId, int version, int recipeId) { uint h = 2166136261u; h = MixBytes(h, (uint)seed); h = MixBytes(h, (uint)planetId); h = MixBytes(h, (uint)version); h = MixBytes(h, 2246822507u); h = MixBytes(h, (uint)recipeId); h ^= h >> 16; h *= 2246822507u; h ^= h >> 13; h *= 3266489909u; return h ^ (h >> 16); } private static uint MixBytes(uint h, uint value) { for (int i = 0; i < 4; i++) { h ^= (value >> i * 8) & 0xFF; h *= 16777619; } return h; } private static RecipeProto[] BuildEligibleRecipes(RecipeProtoSet recipes) { List list = new List(); RecipeProto[] dataArray = ((ProtoSet)(object)recipes).dataArray; for (int i = 0; i < dataArray.Length; i++) { if (IsEligible(dataArray[i])) { list.Add(dataArray[i]); } } list.Sort((RecipeProto a, RecipeProto b) => ((Proto)a).ID.CompareTo(((Proto)b).ID)); return list.ToArray(); } private static bool IsEligible(RecipeProto recipe) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Invalid comparison between Unknown and I4 if (recipe == null) { return false; } if ((int)recipe.Type != 1 && (int)recipe.Type != 4 && (int)recipe.Type != 2) { return false; } if (recipe.Items != null && recipe.Items.Length >= 1 && recipe.ItemCounts != null && recipe.ItemCounts.Length == recipe.Items.Length && recipe.Results != null && recipe.Results.Length == 1 && recipe.ResultCounts != null && recipe.ResultCounts.Length == 1) { return recipe.ResultCounts[0] > 0; } return false; } private static RecipeExecuteData BuildAnomalousExecuteData(RecipeExecuteData shared) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown int[] array = new int[shared.productCounts.Length]; for (int i = 0; i < array.Length; i++) { array[i] = shared.productCounts[i] * OutputMultiplier; } return new RecipeExecuteData((int[])shared.requires.Clone(), (int[])shared.requireCounts.Clone(), (int[])shared.products.Clone(), array, shared.timeSpend, shared.extraTimeSpend, shared.productive); } internal static string DescribeForPlanet(int planetId) { PlanetAnomaly planetAnomaly = AnomalyFor(planetId); if (planetAnomaly == null) { return null; } RecipeProtoSet recipes = LDB.recipes; if ((Object)(object)recipes == (Object)null || !((ProtoSet)(object)recipes).Exist(planetAnomaly.RecipeId)) { return null; } RecipeProto val = ((ProtoSet)(object)recipes).Select(planetAnomaly.RecipeId); if (val == null || val.Results == null || val.ResultCounts == null) { return null; } string text = ""; for (int i = 0; i < val.Results.Length && i < val.ResultCounts.Length; i++) { if (i > 0) { text += "\n"; } int num = val.ResultCounts[i]; object obj = text; text = string.Concat(obj, PlayerFacingItemName(val.Results[i]), ": ", num, " → ", num * planetAnomaly.OutputMultiplier); } if (text.Length == 0) { return null; } return "ANOMALY\n" + text; } private static string PlayerFacingItemName(int itemId) { ItemProtoSet items = LDB.items; if ((Object)(object)items != (Object)null && ((ProtoSet)(object)items).Exist(itemId)) { ItemProto val = ((ProtoSet)(object)items).Select(itemId); if (val != null && !string.IsNullOrEmpty(((Proto)val).name)) { return ((Proto)val).name; } } return "item " + itemId; } private static void LogGameVersionOnce() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (_versionLogged) { return; } _versionLogged = true; try { Version gameVersion = GameConfig.gameVersion; Plugin.Log.LogInfo((object)("Game version: " + ((Version)(ref gameVersion)).ToFullString())); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not read the game version: " + ex.Message)); } } private static string PlanetName(int planetId) { GameData data = GameMain.data; if (data != null && data.galaxy != null) { PlanetData val = data.galaxy.PlanetById(planetId); if (val != null) { return val.displayName; } } return ""; } private static void LogNoAnomaly(int planetId) { string text = ((planetId == _birthPlanetId) ? " (home planet -- never anomalous)" : ""); Plugin.Log.LogInfo((object)("No anomaly: " + PlanetName(planetId) + " (planet id " + planetId + ")" + text + ".")); } private static void LogAnomaly(PlanetAnomaly anomaly, int planetId) { //IL_00aa: Unknown result type (might be due to invalid IL or missing references) RecipeProto val = ((ProtoSet)(object)LDB.recipes).Select(anomaly.RecipeId); RecipeProto.recipeExecuteData.TryGetValue(anomaly.RecipeId, out var value); Plugin.Log.LogInfo((object)"ANOMALY"); Plugin.Log.LogInfo((object)(" Planet: " + PlanetName(planetId) + " (id " + planetId + ")")); Plugin.Log.LogInfo((object)(" Recipe: " + DescribeProto(((Proto)val).name, ((Proto)val).Name, ((Proto)val).ID))); Plugin.Log.LogInfo((object)(" Recipe type: " + val.Type)); if (value != null) { RecipeExecuteData anomalousExecuteData = anomaly.AnomalousExecuteData; Plugin.Log.LogInfo((object)(" Normally: " + DescribeSide(value.requires, value.requireCounts) + " -> " + DescribeSide(value.products, value.productCounts))); Plugin.Log.LogInfo((object)(" Here: " + DescribeSide(anomalousExecuteData.requires, anomalousExecuteData.requireCounts) + " -> " + DescribeSide(anomalousExecuteData.products, anomalousExecuteData.productCounts))); } Plugin.Log.LogInfo((object)(" Effect: output x" + OutputMultiplier + " on this planet only")); } internal static void NoteApplied(PlanetData planet, int machineCount) { string text = ((planet != null) ? planet.displayName : ""); int num = planet?.id ?? (-1); Plugin.Log.LogInfo((object)("Anomaly attached to " + machineCount + ((machineCount == 1) ? " machine on " : " machines on ") + text + " (planet id " + num + "). " + ((machineCount == 1) ? "Its" : "Their") + " output is now x" + OutputMultiplier + ".")); } private static string DescribeSide(int[] itemIds, int[] counts) { string text = ""; for (int i = 0; i < itemIds.Length; i++) { if (i > 0) { text += " + "; } object obj = text; text = string.Concat(obj, counts[i], " x ", DescribeItem(itemIds[i])); } return text; } private static string DescribeItem(int itemId) { ItemProtoSet items = LDB.items; if ((Object)(object)items != (Object)null && ((ProtoSet)(object)items).Exist(itemId)) { ItemProto val = ((ProtoSet)(object)items).Select(itemId); if (val != null) { return DescribeProto(((Proto)val).name, ((Proto)val).Name, itemId); } } return "item " + itemId; } private static string DescribeProto(string localizedName, string rawName, int id) { string text = ((!string.IsNullOrEmpty(localizedName)) ? localizedName : rawName); if (string.IsNullOrEmpty(text)) { return "id " + id; } if (!string.IsNullOrEmpty(rawName) && rawName != text) { return text + " [" + rawName + ", id " + id + "]"; } return text + " [id " + id + "]"; } } [BepInPlugin("com.planetaryanomalies.dsp", "Planetary Anomalies", "0.1.0")] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.planetaryanomalies.dsp"; public const string PluginName = "Planetary Anomalies"; public const string PluginVersion = "0.1.0"; internal static ManualLogSource Log; internal static ConfigEntry AnomalyChancePercent; internal static ConfigEntry OutputMultiplier; private Harmony _harmony; private void Awake() { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; BindConfig(); Log.LogInfo((object)"Planetary Anomalies v0.1.0 loaded"); Log.LogInfo((object)("Anomalies derived from the galaxy seed; output x" + OutputMultiplier.Value + ((AnomalyChancePercent.Value >= 0) ? (". Density forced to " + AnomalyChancePercent.Value + "% by config.") : ". Density drawn per galaxy, 25-75%."))); _harmony = new Harmony("com.planetaryanomalies.dsp"); _harmony.PatchAll(typeof(PlanetFactoryBeforeGameTickPatch)); _harmony.PatchAll(typeof(UIPlanetDetailPatch)); _harmony.PatchAll(typeof(UIAssemblerWindowPatch)); Log.LogInfo((object)"Patched PlanetFactory.BeforeGameTick() for production, and UIPlanetDetail.OnPlanetDataSet() and UIAssemblerWindow._OnUpdate() to disclose anomalies in the planet panel and on the machine. Idle until a planet has a factory (i.e. until something is built)."); } private void BindConfig() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown AnomalyChancePercent = ((BaseUnityPlugin)this).Config.Bind("Generation", "AnomalyChancePercent", -1, new ConfigDescription("Playtesting override for how many non-home planets carry an anomaly. -1, the default, derives the density from the galaxy seed, between 25% and 75%, so galaxies differ from one another: some are anomaly-rich, some sparse. That is the intended behaviour. Any value from 0 to 100 forces that percentage instead, which is useful for testing but makes every galaxy the same density. Changing this re-rolls which planets are anomalous, though not which recipe each anomalous planet gets.", (AcceptableValueBase)(object)new AcceptableValueRange(-1, 100), new object[0])); OutputMultiplier = ((BaseUnityPlugin)this).Config.Bind("Effect", "OutputMultiplier", 10, new ConfigDescription("How much more an anomalous recipe produces. 10 is deliberately unmistakable at a glance. Changing this affects neither which planets are anomalous nor which recipe each one affects.", (AcceptableValueBase)(object)new AcceptableValueRange(2, 1000), new object[0])); } private void OnDestroy() { if (_harmony != null) { _harmony.UnpatchSelf(); _harmony = null; } AnomalyManager.Reset(); PlanetFactoryBeforeGameTickPatch.Reset(); } } [HarmonyPatch(typeof(PlanetFactory), "BeforeGameTick")] internal static class PlanetFactoryBeforeGameTickPatch { private const int SweepIntervalTicks = 30; private static bool _hookProven; private static readonly Dictionary _countdown = new Dictionary(); private static readonly Dictionary _lastCursor = new Dictionary(); private static readonly HashSet _attachLogged = new HashSet(); internal static void Reset() { _hookProven = false; _countdown.Clear(); _lastCursor.Clear(); _attachLogged.Clear(); } [HarmonyPrefix] internal static void Prefix(PlanetFactory __instance) { PlanetData planet = __instance.planet; if (planet == null) { return; } if (!_hookProven) { _hookProven = true; Plugin.Log.LogInfo((object)("PlanetFactory.BeforeGameTick prefix is running (first seen on " + planet.displayName + ", planet id " + planet.id + ").")); } PlanetAnomaly planetAnomaly = AnomalyManager.AnomalyFor(planet.id); if (planetAnomaly == null) { return; } FactorySystem factorySystem = __instance.factorySystem; if (factorySystem == null || factorySystem.assemblerPool == null) { return; } int num = factorySystem.assemblerCursor; if (!DueForSweep(planet.id, num)) { return; } AssemblerComponent[] assemblerPool = factorySystem.assemblerPool; if (num > assemblerPool.Length) { num = assemblerPool.Length; } RecipeExecuteData anomalousExecuteData = planetAnomaly.AnomalousExecuteData; int recipeId = planetAnomaly.RecipeId; int num2 = 0; for (int i = 1; i < num; i++) { if (assemblerPool[i].id == i && assemblerPool[i].recipeId == recipeId && !object.ReferenceEquals(assemblerPool[i].recipeExecuteData, anomalousExecuteData)) { assemblerPool[i].recipeExecuteData = anomalousExecuteData; num2++; } } if (num2 > 0 && _attachLogged.Add(planet.id)) { AnomalyManager.NoteApplied(planet, num2); } } private static bool DueForSweep(int planetId, int cursor) { if (!_lastCursor.TryGetValue(planetId, out var value) || value != cursor) { _lastCursor[planetId] = cursor; _countdown[planetId] = 30; return true; } if (!_countdown.TryGetValue(planetId, out var value2)) { _countdown[planetId] = 30; return true; } if (value2 <= 0) { _countdown[planetId] = 30; return true; } _countdown[planetId] = value2 - 1; return false; } } [HarmonyPatch(typeof(UIAssemblerWindow), "_OnUpdate")] internal static class UIAssemblerWindowPatch { private static bool _errorLogged; private static FieldInfo _assemblerIdField; [HarmonyPostfix] internal static void Postfix(UIAssemblerWindow __instance) { try { Text stateText = __instance.stateText; if ((Object)(object)stateText == (Object)null) { return; } PlanetFactory factory = __instance.factory; if (factory == null || factory.planet == null) { return; } PlanetAnomaly planetAnomaly = AnomalyManager.AnomalyFor(factory.planet.id); if (planetAnomaly == null) { return; } FactorySystem factorySystem = __instance.factorySystem; if (factorySystem == null || factorySystem.assemblerPool == null) { return; } if (_assemblerIdField == null) { _assemblerIdField = AccessTools.Field(typeof(UIAssemblerWindow), "_assemblerId"); if (_assemblerIdField == null) { return; } } int num = (int)_assemblerIdField.GetValue(__instance); if (num > 0 && num < factorySystem.assemblerPool.Length && factorySystem.assemblerPool[num].id == num && factorySystem.assemblerPool[num].recipeId == planetAnomaly.RecipeId) { string text = " · ANOMALY ×" + planetAnomaly.OutputMultiplier; if (stateText.text == null || !stateText.text.EndsWith(text, StringComparison.Ordinal)) { stateText.text += text; } } } catch (Exception ex) { if (!_errorLogged) { _errorLogged = true; Plugin.Log.LogError((object)("Failed to mark the assembler window: " + ex)); } } } } [HarmonyPatch(typeof(UIPlanetDetail), "OnPlanetDataSet")] internal static class UIPlanetDetailPatch { private static bool _errorLogged; [HarmonyPostfix] internal static void Postfix(UIPlanetDetail __instance) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) try { PlanetData planet = __instance.planet; if (planet == null || !planet.scanned) { return; } Text planetBrief = __instance.planetBrief; if ((Object)(object)planetBrief == (Object)null) { return; } string text = AnomalyManager.DescribeForPlanet(planet.id); if (!string.IsNullOrEmpty(text)) { planetBrief.text = planetBrief.text + "\n\n" + text; RectTransform briefContentRect = __instance.briefContentRect; if ((Object)(object)briefContentRect != (Object)null) { briefContentRect.sizeDelta = new Vector2(Mathf.Round(briefContentRect.sizeDelta.x), Mathf.Round(planetBrief.preferredHeight)); } } } catch (Exception ex) { if (!_errorLogged) { _errorLogged = true; Plugin.Log.LogError((object)("Failed to show the anomaly in the planet panel: " + ex)); } } } }