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<int, PlanetAnomaly> _byPlanet = new Dictionary<int, PlanetAnomaly>();
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<RecipeProto>)(object)recipes).dataArray == null || ((ProtoSet<RecipeProto>)(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<RecipeProto> list = new List<RecipeProto>();
RecipeProto[] dataArray = ((ProtoSet<RecipeProto>)(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<RecipeProto>)(object)recipes).Exist(planetAnomaly.RecipeId))
{
return null;
}
RecipeProto val = ((ProtoSet<RecipeProto>)(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<ItemProto>)(object)items).Exist(itemId))
{
ItemProto val = ((ProtoSet<ItemProto>)(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 "<unknown>";
}
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<RecipeProto>)(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 : "<unknown>");
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<ItemProto>)(object)items).Exist(itemId))
{
ItemProto val = ((ProtoSet<ItemProto>)(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<int> AnomalyChancePercent;
internal static ConfigEntry<int> 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<int>("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<int>(-1, 100), new object[0]));
OutputMultiplier = ((BaseUnityPlugin)this).Config.Bind<int>("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<int>(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<int, int> _countdown = new Dictionary<int, int>();
private static readonly Dictionary<int, int> _lastCursor = new Dictionary<int, int>();
private static readonly HashSet<int> _attachLogged = new HashSet<int>();
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));
}
}
}
}