using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.UI;
[assembly: AssemblyInformationalVersion("0.5.1")]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: CompilationRelaxations(8)]
[assembly: AssemblyProduct("Planetary Anomalies")]
[assembly: AssemblyFileVersion("0.5.1.0")]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyTitle("Planetary Anomalies")]
[assembly: AssemblyVersion("0.5.1.0")]
namespace PlanetaryAnomalies;
internal enum AnomalyVisibility
{
None,
Marker,
Full
}
internal enum UnresearchedDisplay
{
Hide,
Marker,
Show
}
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 DefaultRuleset = 1;
internal const int LatestRuleset = 2;
internal const int OriginalRuleset = 1;
internal const int DensityMinPercent = 25;
internal const int DensityMaxPercent = 75;
private const int TooltipMaxNamed = 3;
private static int _outputMultiplier = 10;
private static int _ruleset = 1;
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 HashSet<string> _exclusions;
private static HashSet<string> _exclusionsMatched;
private static bool _versionLogged;
private static string _waitReason;
private static readonly HashSet<int> _withheldLogged = new HashSet<int>();
private static bool _densityWarned;
private static readonly List<RecipeProto> _outsideRuleset = new List<RecipeProto>();
internal static int OutputMultiplier
{
get
{
if (!_galaxyKnown)
{
return ConfiguredMultiplier();
}
return _outputMultiplier;
}
}
internal static int AnomalySystemVersion => AnomalyMath.GeneratorVersionFor(_ruleset);
internal static int DensityPercent => _densityPercent;
private static bool HomeIsProtected
{
get
{
if (Plugin.HomePlanetNeverAnomalous != null)
{
return Plugin.HomePlanetNeverAnomalous.Value;
}
return true;
}
}
private static int ConfiguredMultiplier()
{
if (Plugin.OutputMultiplier == null)
{
return 10;
}
return Plugin.OutputMultiplier.Value;
}
private static int ResolveRuleVersion(out string reason)
{
string text = ((Plugin.AnomalyRules != null) ? Plugin.AnomalyRules.Value : null);
string text2 = ((text != null) ? text.Trim() : "");
if (string.Equals(text2, "latest", StringComparison.OrdinalIgnoreCase))
{
reason = "AnomalyRules = Latest, the newest this build knows" + PoolNote(2);
return 2;
}
if (string.Equals(text2, "pinned", StringComparison.OrdinalIgnoreCase))
{
Plugin.AnomalyRules.Value = 1.ToString();
reason = "AnomalyRules = Pinned, from an early 0.5 build; rewritten as " + 1;
return 1;
}
string s = (text2.StartsWith("v", StringComparison.OrdinalIgnoreCase) ? text2.Substring(1) : text2);
if (text2.Length == 0 || !int.TryParse(s, out var result))
{
Plugin.Log.LogWarning((object)("AnomalyRules = '" + text + "' was not understood. Use a ruleset number (" + 1 + " to " + 2 + ") or Latest. Keeping ruleset " + 1 + ", to be safe."));
reason = "AnomalyRules = '" + text + "' not understood; kept on ruleset " + 1 + " to be safe";
return 1;
}
if (result < 1 || result > 2)
{
Plugin.Log.LogWarning((object)("AnomalyRules = " + result + " is not a ruleset this build knows (" + 1 + " to " + 2 + "). Keeping ruleset " + 1 + ", to be safe."));
reason = "AnomalyRules = " + result + " is outside " + 1 + ".." + 2 + "; kept on ruleset " + 1 + " to be safe";
return 1;
}
reason = "AnomalyRules = " + result + PoolNote(result);
return result;
}
private static string PoolNote(int ruleset)
{
if (!AnomalyMath.HasFixedPool(ruleset))
{
return ", recipe list follows the game";
}
return ", fixed recipe list";
}
private static int ResolveOutputMultiplier(GameDesc desc, out string reason)
{
int result = ConfiguredMultiplier();
if (Plugin.MultiplierFromCombatSettings == null || !Plugin.MultiplierFromCombatSettings.Value)
{
reason = "from config";
return result;
}
if (desc == null)
{
reason = "from config; no game description to read combat settings from";
return result;
}
bool isPeaceMode = desc.isPeaceMode;
bool isEnemyPassive = ((CombatSettings)(ref desc.combatSettings)).isEnemyPassive;
if (!isPeaceMode && !isEnemyPassive)
{
reason = "from config; the Dark Fog is hostile here, so the experimental derivation leaves it alone";
return result;
}
int result2 = ((Plugin.PeacefulOutputMultiplier != null) ? Plugin.PeacefulOutputMultiplier.Value : 3);
reason = (isPeaceMode ? "peace mode" : "enemies are passive") + ", so PeacefulOutputMultiplier applies (EXPERIMENTAL)";
return result2;
}
internal static void Reset()
{
_ruleset = 1;
_outsideRuleset.Clear();
_galaxySeed = 0;
_birthPlanetId = -1;
_galaxyKnown = false;
_outputMultiplier = 10;
_byPlanet.Clear();
_withheldLogged.Clear();
_eligible = null;
_exclusions = null;
_exclusionsMatched = 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)
{
return AnomalyFor(planetId, log: true);
}
private static PlanetAnomaly AnomalyFor(int planetId, bool log)
{
if (!EnsureGalaxy())
{
return null;
}
if (_byPlanet.TryGetValue(planetId, out var value))
{
return value;
}
PlanetAnomaly planetAnomaly = Derive(planetId);
_byPlanet[planetId] = planetAnomaly;
if (log)
{
if (planetAnomaly != null)
{
LogAnomaly(planetAnomaly, planetId);
}
else
{
LogNoAnomaly(planetId);
}
}
return planetAnomaly;
}
internal static AnomalyVisibility VisibilityFor(int planetId)
{
PlanetAnomaly planetAnomaly = AnomalyFor(planetId);
if (planetAnomaly == null)
{
return AnomalyVisibility.None;
}
AnomalyVisibility anomalyVisibility = VisibilityOfRecipe(planetAnomaly.RecipeId);
if (anomalyVisibility == AnomalyVisibility.None && !_withheldLogged.Contains(planetId))
{
_withheldLogged.Add(planetId);
string text = UnlockingTechName(planetAnomaly.RecipeId);
Plugin.Log.LogInfo((object)("Withheld on " + PlanetName(planetId) + " (planet id " + planetId + "): recipe " + planetAnomaly.RecipeId + " is not researched" + ((text != null) ? (", unlocked by " + text) : "") + ". Set UnresearchedAnomalies = Marker or Show to reveal it."));
}
return anomalyVisibility;
}
private static AnomalyVisibility VisibilityOfRecipe(int recipeId)
{
if (IsRecipeResearched(recipeId))
{
return AnomalyVisibility.Full;
}
return ((Plugin.UnresearchedAnomalies != null) ? Plugin.UnresearchedAnomalies.Value : UnresearchedDisplay.Hide) switch
{
UnresearchedDisplay.Show => AnomalyVisibility.Full,
UnresearchedDisplay.Marker => AnomalyVisibility.Marker,
_ => AnomalyVisibility.None,
};
}
private static string UnlockingTechName(int recipeId)
{
RecipeProtoSet recipes = LDB.recipes;
if ((Object)(object)recipes == (Object)null || !((ProtoSet<RecipeProto>)(object)recipes).Exist(recipeId))
{
return null;
}
RecipeProto val = ((ProtoSet<RecipeProto>)(object)recipes).Select(recipeId);
if (val == null || val.preTech == null)
{
return null;
}
string name = ((Proto)val.preTech).name;
if (!string.IsNullOrEmpty(name))
{
return name;
}
return null;
}
private static string TechSuffix(int recipeId)
{
string text = UnlockingTechName(recipeId);
if (text == null)
{
return "";
}
return " (" + text + ")";
}
private static bool IsRecipeResearched(int recipeId)
{
GameHistoryData history = GameMain.history;
if (history == null)
{
return true;
}
return history.RecipeUnlocked(recipeId);
}
private static bool EnsureGalaxy()
{
if (DSPGame.IsMenuDemo)
{
Waiting("the main menu demo galaxy is showing; the mod does nothing there.");
return false;
}
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();
_withheldLogged.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;
}
_ruleset = ResolveRuleVersion(out var reason);
LoadExclusions();
_eligible = BuildEligibleRecipes(recipes);
ReportExclusions();
ReportRulesetPool(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);
_outputMultiplier = ResolveOutputMultiplier(data.gameDesc, out var reason2);
_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)") + ", output x" + _outputMultiplier + " (" + reason2 + "), ruleset " + _ruleset + " (" + reason + ")."));
LogPoolIds();
if (Plugin.LogEveryAnomaly != null && Plugin.LogEveryAnomaly.Value)
{
SurveyGalaxy(galaxy);
}
return true;
}
private static void SurveyGalaxy(GalaxyData galaxy)
{
if (galaxy.stars == null)
{
return;
}
int num = 0;
int num2 = 0;
Plugin.Log.LogInfo((object)"=== GALAXY SURVEY (spoils discovery; LogEveryAnomaly is on) ===");
LogEligiblePool();
for (int i = 0; i < galaxy.stars.Length; i++)
{
StarData val = galaxy.stars[i];
if (val == null || val.planets == null)
{
continue;
}
for (int j = 0; j < val.planets.Length; j++)
{
PlanetData val2 = val.planets[j];
if (val2 == null)
{
continue;
}
num++;
PlanetAnomaly planetAnomaly = AnomalyFor(val2.id);
if (planetAnomaly != null)
{
num2++;
bool scanned = val2.scanned;
bool flag = IsRecipeResearched(planetAnomaly.RecipeId);
Plugin.Log.LogInfo((object)(" SURVEY " + val.displayName + " / " + val2.displayName + " (planet id " + val2.id + "): " + ShortDescribeForPlanet(val2.id) + " [" + (scanned ? "scanned" : "NOT scanned") + ", " + (flag ? "researched" : ("NOT researched" + TechSuffix(planetAnomaly.RecipeId))) + " -> " + ((scanned && flag) ? "SHOWN" : "HIDDEN") + "]"));
}
else
{
string text = WouldBeRecipeName(val2.id);
if (text != null)
{
Plugin.Log.LogInfo((object)(" LATENT " + val.displayName + " / " + val2.displayName + " (planet id " + val2.id + "): " + text + " [needs density > " + PresenceRoll(val2.id) + "%]"));
}
}
}
}
Plugin.Log.LogInfo((object)("=== SURVEY END: " + num2 + " anomalous of " + num + " planets (" + ((num > 0) ? (num2 * 100 / num) : 0) + "%) ==="));
}
private static void LogEligiblePool()
{
//IL_0081: Unknown result type (might be due to invalid IL or missing references)
if (_eligible == null)
{
return;
}
string text = "";
int num = 0;
for (int i = 0; i < _eligible.Length; i++)
{
RecipeProto val = _eligible[i];
string text2 = ((val.Results != null && val.Results.Length > 0) ? PlayerFacingItemName(val.Results[0]) : ((Proto)val).name);
object obj = text;
text = string.Concat(obj, (num > 0) ? ", " : "", text2, " [", val.Type, ", recipe ", ((Proto)val).ID, "]");
num++;
if (num == 6)
{
Plugin.Log.LogInfo((object)(" POOL " + text));
text = "";
num = 0;
}
}
if (num > 0)
{
Plugin.Log.LogInfo((object)(" POOL " + text));
}
Plugin.Log.LogInfo((object)(" POOL (" + _eligible.Length + " eligible recipes)"));
}
private static string WouldBeRecipeName(int planetId)
{
if ((planetId == _birthPlanetId && HomeIsProtected) || IsUnbuildable(planetId) || _eligible == null)
{
return null;
}
int[] array = new int[_eligible.Length];
for (int i = 0; i < _eligible.Length; i++)
{
array[i] = ((Proto)_eligible[i]).ID;
}
int num = AnomalyMath.ChooseRecipeId(_galaxySeed, planetId, AnomalySystemVersion, array);
for (int j = 0; j < _eligible.Length; j++)
{
if (((Proto)_eligible[j]).ID == num && _eligible[j].Results != null && _eligible[j].Results.Length != 0)
{
return PlayerFacingItemName(_eligible[j].Results[0]);
}
}
return null;
}
private static int PresenceRoll(int planetId)
{
return (int)(AnomalyMath.Hash(_galaxySeed, planetId, AnomalySystemVersion, 2654435769u) % 100);
}
private static bool IsUnbuildable(int planetId)
{
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Invalid comparison between Unknown and I4
GameData data = GameMain.data;
if (data == null || data.galaxy == null)
{
return false;
}
PlanetData val = data.galaxy.PlanetById(planetId);
if (val != null)
{
return (int)val.type == 5;
}
return false;
}
private static int ResolveDensity(int seed)
{
if (TryForcedDensity(out var percent))
{
return percent;
}
return AnomalyMath.DensityFor(seed, AnomalySystemVersion);
}
internal static bool TryForcedDensity(out int percent)
{
percent = -1;
string text = ((Plugin.AnomalyChancePercent != null) ? Plugin.AnomalyChancePercent.Value : null);
string text2 = ((text != null) ? text.Trim() : "");
if (text2.Length == 0 || string.Equals(text2, "seed", StringComparison.OrdinalIgnoreCase) || text2 == "-1")
{
return false;
}
if (!int.TryParse(text2.TrimEnd('%'), out var result) || result < 0 || result > 100)
{
if (!_densityWarned)
{
_densityWarned = true;
Plugin.Log.LogWarning((object)("AnomalyChancePercent = '" + text + "' was not understood. Use Seed, or a number from 0 to 100. Deriving density from the seed."));
}
return false;
}
percent = result;
return true;
}
private static void LogPoolIds()
{
if (_eligible != null)
{
string[] array = new string[_eligible.Length];
for (int i = 0; i < _eligible.Length; i++)
{
array[i] = ((Proto)_eligible[i]).ID.ToString();
}
Plugin.Log.LogInfo((object)("Eligible pool (" + _eligible.Length + " recipes, by id): " + string.Join(",", array)));
}
}
private static bool IsDensityOverridden()
{
int percent;
return TryForcedDensity(out percent);
}
private static PlanetAnomaly Derive(int planetId)
{
if (planetId == _birthPlanetId && HomeIsProtected)
{
return null;
}
if (IsUnbuildable(planetId))
{
return null;
}
if (!AnomalyMath.IsAnomalous(_galaxySeed, planetId, AnomalySystemVersion, _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 RecipeProto ChooseRecipe(int planetId)
{
int[] array = new int[_eligible.Length];
for (int i = 0; i < _eligible.Length; i++)
{
array[i] = ((Proto)_eligible[i]).ID;
}
int num = AnomalyMath.ChooseRecipeId(_galaxySeed, planetId, AnomalySystemVersion, array);
if (num < 0)
{
return null;
}
for (int j = 0; j < _eligible.Length; j++)
{
if (((Proto)_eligible[j]).ID == num)
{
return _eligible[j];
}
}
return null;
}
private static RecipeProto[] BuildEligibleRecipes(RecipeProtoSet recipes)
{
List<RecipeProto> list = new List<RecipeProto>();
RecipeProto[] dataArray = ((ProtoSet<RecipeProto>)(object)recipes).dataArray;
_outsideRuleset.Clear();
for (int i = 0; i < dataArray.Length; i++)
{
if (IsEligible(dataArray[i]))
{
if (!AnomalyMath.InRulesetPool(_ruleset, ((Proto)dataArray[i]).ID))
{
_outsideRuleset.Add(dataArray[i]);
}
else
{
list.Add(dataArray[i]);
}
}
}
list.Sort((RecipeProto a, RecipeProto b) => ((Proto)a).ID.CompareTo(((Proto)b).ID));
return list.ToArray();
}
private static void ReportRulesetPool(RecipeProtoSet recipes)
{
if (_outsideRuleset.Count > 0)
{
string text = "";
for (int i = 0; i < _outsideRuleset.Count; i++)
{
RecipeProto val = _outsideRuleset[i];
object obj = text;
text = string.Concat(obj, (i > 0) ? ", " : "", PlayerFacingRecipeName(val), " (recipe ", ((Proto)val).ID, ")");
}
Plugin.Log.LogInfo((object)("Ruleset " + _ruleset + " draws from its own fixed list, so " + _outsideRuleset.Count + " newer recipe(s) in this game are not drawn and move no planet: " + text + ". Set AnomalyRules = " + 2 + " to include them; that moves only the planets they take."));
}
if (!AnomalyMath.HasFixedPool(_ruleset))
{
return;
}
string text2 = "";
int num = 0;
for (int j = 0; j < AnomalyMath.RulesetOnePool.Length; j++)
{
int num2 = AnomalyMath.RulesetOnePool[j];
RecipeProto val2 = (((ProtoSet<RecipeProto>)(object)recipes).Exist(num2) ? ((ProtoSet<RecipeProto>)(object)recipes).Select(num2) : null);
if (val2 == null || (!IsEligible(val2) && !IsExcludedByPlayer(val2)))
{
text2 = text2 + ((num > 0) ? "," : "") + num2;
num++;
}
}
if (num > 0)
{
Plugin.Log.LogWarning((object)("Ruleset " + _ruleset + " draws from " + AnomalyMath.RulesetOnePool.Length + " recipes, and " + num + " of them are missing from this game or no longer eligible (recipe " + text2 + "). Planets that carried them have moved to other recipes. Please report this, with your game version."));
}
}
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
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
//IL_0027: Invalid comparison between Unknown and I4
//IL_002a: Unknown result type (might be due to invalid IL or missing references)
//IL_0030: Invalid comparison between Unknown and I4
if (recipe == null)
{
return false;
}
if ((int)recipe.Type != 1 && (int)recipe.Type != 4 && (int)recipe.Type != 2 && (int)recipe.Type != 3 && (int)recipe.Type != 5)
{
return false;
}
if (IsExcludedByPlayer(recipe))
{
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 bool IsExcludedByPlayer(RecipeProto recipe)
{
if (_exclusions == null || _exclusions.Count == 0)
{
return false;
}
if (_exclusions.Contains(((Proto)recipe).ID.ToString()))
{
_exclusionsMatched.Add(((Proto)recipe).ID.ToString());
return true;
}
string[] array = new string[3]
{
((Proto)recipe).name,
((Proto)recipe).Name,
PlayerFacingRecipeName(recipe)
};
for (int i = 0; i < array.Length; i++)
{
if (!string.IsNullOrEmpty(array[i]))
{
string item = array[i].Trim().ToLowerInvariant();
if (_exclusions.Contains(item))
{
_exclusionsMatched.Add(item);
return true;
}
}
}
return false;
}
private static void LoadExclusions()
{
_exclusions = new HashSet<string>();
_exclusionsMatched = new HashSet<string>();
string text = ((Plugin.ExcludedRecipes != null) ? Plugin.ExcludedRecipes.Value : null);
if (string.IsNullOrEmpty(text))
{
return;
}
string[] array = text.Split(',');
for (int i = 0; i < array.Length; i++)
{
string text2 = array[i].Trim();
if (text2.Length > 0)
{
_exclusions.Add(text2.ToLowerInvariant());
}
}
}
private static void ReportExclusions()
{
if (_exclusions == null || _exclusions.Count == 0)
{
return;
}
Plugin.Log.LogInfo((object)("Excluded by config: " + _exclusionsMatched.Count + " of " + _exclusions.Count + " entries matched a recipe."));
foreach (string exclusion in _exclusions)
{
if (!_exclusionsMatched.Contains(exclusion))
{
Plugin.Log.LogWarning((object)("ExcludedRecipes entry \"" + exclusion + "\" matched no recipe. Check the spelling: use the item name as it appears in game, or the recipe's numeric id."));
}
}
}
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, PlayerFacingRecipeName(val), ": ", num, " → ", num * planetAnomaly.OutputMultiplier);
}
if (text.Length == 0)
{
return null;
}
return "ANOMALY\n" + text;
}
internal static string ShortDescribeForPlanet(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.Results.Length == 0)
{
return null;
}
return PlayerFacingRecipeName(val) + " ×" + planetAnomaly.OutputMultiplier;
}
internal static string AnomalousItemName(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.Results.Length == 0)
{
return null;
}
return PlayerFacingRecipeName(val);
}
private static string PlayerFacingRecipeName(RecipeProto recipe)
{
if (recipe == null)
{
return null;
}
string text = CleanRecipeName(((Proto)recipe).name);
if (text != null)
{
return text;
}
if (recipe.Results != null && recipe.Results.Length > 0)
{
return PlayerFacingItemName(recipe.Results[0]);
}
return null;
}
private static string CleanRecipeName(string name)
{
if (string.IsNullOrEmpty(name))
{
return null;
}
int i;
for (i = 0; i < name.Length && (char.IsWhiteSpace(name[i]) || name[i] == '-' || name[i] == '–' || name[i] == '—' || name[i] == '·'); i++)
{
}
string text = name.Substring(i).TrimEnd();
if (text.Length <= 0)
{
return null;
}
return 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));
}
}
internal static string TooltipLinesForItem(int itemId)
{
ItemProtoSet items = LDB.items;
if ((Object)(object)items == (Object)null || !((ProtoSet<ItemProto>)(object)items).Exist(itemId))
{
return null;
}
ItemProto val = ((ProtoSet<ItemProto>)(object)items).Select(itemId);
if (val == null || val.recipes == null)
{
return null;
}
List<string> list = null;
for (int i = 0; i < val.recipes.Count; i++)
{
string text = TooltipLineForRecipe(val.recipes[i]);
if (text != null)
{
if (list == null)
{
list = new List<string>();
}
list.Add(text);
}
}
if (list == null)
{
return null;
}
return string.Join("\n", list.ToArray());
}
internal static string TooltipLinesForRecipe(int recipeId)
{
RecipeProtoSet recipes = LDB.recipes;
if ((Object)(object)recipes == (Object)null || !((ProtoSet<RecipeProto>)(object)recipes).Exist(recipeId))
{
return null;
}
return TooltipLineForRecipe(((ProtoSet<RecipeProto>)(object)recipes).Select(recipeId));
}
private static string TooltipLineForRecipe(RecipeProto recipe)
{
if (recipe == null || !EnsureGalaxy())
{
return null;
}
AnomalyVisibility anomalyVisibility = VisibilityOfRecipe(((Proto)recipe).ID);
if (anomalyVisibility == AnomalyVisibility.None)
{
return null;
}
GameData data = GameMain.data;
if (data == null || data.galaxy == null || data.galaxy.stars == null)
{
return null;
}
string text = "";
int num = 0;
int num2 = 0;
for (int i = 0; i < data.galaxy.stars.Length; i++)
{
StarData val = data.galaxy.stars[i];
if (val == null || val.planets == null)
{
continue;
}
for (int j = 0; j < val.planets.Length; j++)
{
PlanetData val2 = val.planets[j];
if (val2 == null || !val2.scanned)
{
continue;
}
PlanetAnomaly planetAnomaly = AnomalyFor(val2.id, log: false);
if (planetAnomaly != null && planetAnomaly.RecipeId == ((Proto)recipe).ID)
{
num2++;
if (num < 3)
{
text = text + ((num > 0) ? ", " : "") + val2.displayName;
num++;
}
}
}
}
if (num2 == 0)
{
return null;
}
if (anomalyVisibility == AnomalyVisibility.Marker)
{
return "Anomaly: " + ((num2 == 1) ? "a world you have scanned makes this" : (num2 + " worlds you have scanned make this")) + " ×" + OutputMultiplier + ", on a recipe you have not researched yet.";
}
if (num2 > num)
{
object obj = text;
text = string.Concat(obj, " and ", num2 - num, " more");
}
string text2 = PlayerFacingRecipeName(recipe);
return "Anomaly: " + (string.IsNullOrEmpty(text2) ? "" : (text2 + " ")) + "×" + OutputMultiplier + " on " + text;
}
internal static string RecipeLabel(int recipeId)
{
RecipeProtoSet recipes = LDB.recipes;
if ((Object)(object)recipes == (Object)null || !((ProtoSet<RecipeProto>)(object)recipes).Exist(recipeId))
{
return null;
}
string text = PlayerFacingRecipeName(((ProtoSet<RecipeProto>)(object)recipes).Select(recipeId));
if (string.IsNullOrEmpty(text))
{
return null;
}
return text + " ×" + OutputMultiplier;
}
internal static string KnownPlanetsWithRecipe(int recipeId, int maxNamed, out int total)
{
total = 0;
GameData data = GameMain.data;
if (data == null || data.galaxy == null || data.galaxy.stars == null)
{
return null;
}
string text = "";
int num = 0;
for (int i = 0; i < data.galaxy.stars.Length; i++)
{
StarData val = data.galaxy.stars[i];
if (val == null || val.planets == null)
{
continue;
}
for (int j = 0; j < val.planets.Length; j++)
{
PlanetData val2 = val.planets[j];
if (val2 == null || !val2.scanned)
{
continue;
}
PlanetAnomaly planetAnomaly = AnomalyFor(val2.id, log: false);
if (planetAnomaly != null && planetAnomaly.RecipeId == recipeId)
{
total++;
if (num < maxNamed)
{
text = text + ((num > 0) ? ", " : "") + val2.displayName;
num++;
}
}
}
}
if (total == 0)
{
return null;
}
if (total > num)
{
object obj = text;
text = string.Concat(obj, " and ", total - num, " more");
}
return text;
}
internal static bool AnyUnknownPlanetWithRecipe(int recipeId)
{
GameData data = GameMain.data;
if (data == null || data.galaxy == null || data.galaxy.stars == null)
{
return false;
}
for (int i = 0; i < data.galaxy.stars.Length; i++)
{
StarData val = data.galaxy.stars[i];
if (val == null || val.planets == null)
{
continue;
}
for (int j = 0; j < val.planets.Length; j++)
{
PlanetData val2 = val.planets[j];
if (val2 != null && !val2.scanned)
{
PlanetAnomaly planetAnomaly = AnomalyFor(val2.id, log: false);
if (planetAnomaly != null && planetAnomaly.RecipeId == recipeId)
{
return true;
}
}
}
}
return false;
}
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 = "";
if (planetId == _birthPlanetId && HomeIsProtected)
{
text = " (home planet -- never anomalous)";
}
else if (IsUnbuildable(planetId))
{
text = " (gas giant -- cannot host machines)";
}
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 + "]";
}
}
internal static class AnomalyMath
{
internal const int DensityMinPercent = 25;
internal const int DensityMaxPercent = 75;
internal const uint SaltPresence = 2654435769u;
internal const uint SaltRecipe = 2246822507u;
internal const uint SaltDensity = 3266489909u;
internal const int LatestRuleset = 2;
internal static readonly int[] RulesetOnePool = new int[150]
{
1, 2, 3, 4, 5, 6, 7, 8, 10, 11,
12, 13, 14, 15, 17, 19, 20, 21, 22, 23,
24, 25, 26, 28, 29, 30, 31, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
46, 47, 48, 49, 50, 51, 52, 53, 54, 56,
57, 59, 60, 61, 62, 63, 64, 65, 66, 67,
68, 69, 70, 71, 72, 73, 76, 77, 78, 79,
80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
100, 101, 103, 104, 105, 106, 107, 108, 109, 110,
111, 112, 113, 114, 116, 117, 118, 119, 120, 121,
122, 123, 124, 125, 126, 127, 128, 129, 130, 131,
132, 133, 134, 135, 136, 137, 138, 139, 140, 141,
142, 143, 144, 145, 146, 147, 148, 149, 150, 151,
152, 153, 154, 155, 156, 157, 158, 159, 160, 161
};
internal static int GeneratorVersionFor(int ruleset)
{
return 1;
}
internal static bool HasFixedPool(int ruleset)
{
return ruleset == 1;
}
internal static bool InRulesetPool(int ruleset, int recipeId)
{
if (!HasFixedPool(ruleset))
{
return true;
}
return Array.IndexOf(RulesetOnePool, recipeId) >= 0;
}
internal static int DensityFor(int seed, int version)
{
uint num = 51u;
return (int)(25 + Hash(seed, 0, version, 3266489909u) % num);
}
internal static bool IsAnomalous(int seed, int planetId, int version, int densityPercent)
{
return Hash(seed, planetId, version, 2654435769u) % 100 < (uint)densityPercent;
}
internal static int ChooseRecipeId(int seed, int planetId, int version, int[] eligibleRecipeIds)
{
if (eligibleRecipeIds == null || eligibleRecipeIds.Length == 0)
{
return -1;
}
int num = -1;
uint num2 = 0u;
foreach (int num3 in eligibleRecipeIds)
{
uint num4 = RecipeWeight(seed, planetId, version, num3);
if (num < 0 || num4 > num2 || (num4 == num2 && num3 < num))
{
num = num3;
num2 = num4;
}
}
return num;
}
internal 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);
return Avalanche(h);
}
internal 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);
return Avalanche(h);
}
private static uint Avalanche(uint h)
{
h ^= h >> 16;
h *= 2246822507u;
h ^= h >> 13;
h *= 3266489909u;
h ^= h >> 16;
return h;
}
private static uint MixBytes(uint h, uint value)
{
for (int i = 0; i < 4; i++)
{
h ^= (value >> i * 8) & 0xFF;
h *= 16777619;
}
return h;
}
}
[BepInPlugin("com.planetaryanomalies.dsp", "Planetary Anomalies", "0.5.1")]
public class Plugin : BaseUnityPlugin
{
public const string PluginGuid = "com.planetaryanomalies.dsp";
public const string PluginName = "Planetary Anomalies";
public const string PluginVersion = "0.5.1";
internal static ManualLogSource Log;
internal static ConfigEntry<string> AnomalyChancePercent;
internal static ConfigEntry<bool> HomePlanetNeverAnomalous;
internal static ConfigEntry<int> OutputMultiplier;
internal static ConfigEntry<StarmapLabelMode> StarmapLabel;
internal static ConfigEntry<UnresearchedDisplay> UnresearchedAnomalies;
internal static ConfigEntry<bool> AnnounceOnResearch;
internal static ConfigEntry<bool> MultiplierFromCombatSettings;
internal static ConfigEntry<int> PeacefulOutputMultiplier;
internal static ConfigEntry<bool> LegacyHideUnresearched;
internal static ConfigEntry<bool> LogEveryAnomaly;
internal static ConfigEntry<bool> TestAnnouncement;
internal static ConfigEntry<string> ExcludedRecipes;
internal static ConfigEntry<string> AnomalyRules;
private Harmony _harmony;
private void Awake()
{
//IL_00be: Unknown result type (might be due to invalid IL or missing references)
//IL_00c8: Expected O, but got Unknown
Log = ((BaseUnityPlugin)this).Logger;
TechUnlockPatch.CaptureMainThread();
BindConfig();
Log.LogInfo((object)"Planetary Anomalies v0.5.1 loaded");
Log.LogInfo((object)("Anomalies derived from the galaxy seed; output x" + OutputMultiplier.Value + (AnomalyManager.TryForcedDensity(out var percent) ? (". Density forced to " + percent + "% by config.") : ". Density drawn per galaxy, 25-75%.") + (MultiplierFromCombatSettings.Value ? (" EXPERIMENTAL: peaceful galaxies use x" + PeacefulOutputMultiplier.Value + " instead; resolved when a save loads.") : "")));
_harmony = new Harmony("com.planetaryanomalies.dsp");
if (!TryPatch(typeof(PlanetFactoryBeforeGameTickPatch), "production (PlanetFactory.BeforeGameTick)"))
{
Log.LogError((object)"Planetary Anomalies is off: production could not be patched, so nothing is labelled either. This usually means the game has updated; look for a newer version of the mod.");
return;
}
Type[] array = new Type[6]
{
typeof(UIPlanetDetailPatch),
typeof(UIAssemblerWindowPatch),
typeof(UIStarmapPlanetPatch),
typeof(UIStarmapStarPatch),
typeof(ItemTipPatch),
typeof(TechUnlockPatch)
};
string[] array2 = new string[6] { "the planet panel", "the machine window", "star map planet labels", "star map star labels", "item tooltips", "research announcements" };
string text = "";
string text2 = "";
for (int i = 0; i < array.Length; i++)
{
if (TryPatch(array[i], array2[i]))
{
text = text + ((text.Length > 0) ? ", " : "") + array2[i];
}
else
{
text2 = text2 + ((text2.Length > 0) ? ", " : "") + array2[i];
}
}
Log.LogInfo((object)("Patched PlanetFactory.BeforeGameTick() for production. Anomalies are disclosed in: " + ((text.Length > 0) ? text : "nothing") + "." + ((text2.Length > 0) ? (" NOT working: " + text2 + " -- see the errors above.") : "") + " Production is idle until a planet has a factory (i.e. until something is built)."));
}
private bool TryPatch(Type patchClass, string what)
{
try
{
_harmony.PatchAll(patchClass);
return true;
}
catch (Exception ex)
{
Log.LogError((object)("Could not patch " + what + ", so that part of the mod is off. This usually means the game has updated. " + ex));
return false;
}
}
private void BindConfig()
{
//IL_006c: Unknown result type (might be due to invalid IL or missing references)
//IL_0076: Expected O, but got Unknown
//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
//IL_01ca: Expected O, but got Unknown
HomePlanetNeverAnomalous = ((BaseUnityPlugin)this).Config.Bind<bool>("Generation", "HomePlanetNeverAnomalous", true, "Keeps the world you start on ordinary.\nOn by default, deliberately: you would meet an anomaly before the star map exists to\nexplain it, and anomalies are meant to be a reason to look outward rather than a\nproperty of the ground you are standing on.\nSet to false and your home planet is drawn like any other -- most of the time it will\nstill have nothing, since it takes the same chance as everywhere else.\nThis affects only the home planet. Every other world keeps exactly the anomaly it\nalready had, because presence is an independent draw per planet.");
AnomalyChancePercent = ((BaseUnityPlugin)this).Config.Bind<string>("Generation", "AnomalyChancePercent", "Seed", "How many non-home planets carry an anomaly.\nSeed: derived from the galaxy seed, between 25% and 75%, so galaxies differ from one\n another -- some anomaly-rich, some sparse. The default, and the intended behaviour.\nA number from 0 to 100 forces that percentage instead. Useful when a galaxy rolls\n sparser than you want to play; it costs nothing on a galaxy nobody has built on.\nChanging this re-rolls which planets are anomalous, though not which recipe each\nanomalous planet gets. (-1 still means Seed, for configs written by earlier versions.)");
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]));
StarmapLabel = ((BaseUnityPlugin)this).Config.Bind<StarmapLabelMode>("Display", "StarmapLabel", StarmapLabelMode.Detail, "What star map labels show, for planets you have scanned.\nDetail: a star lists the affected items in its system, and a planet names its item\n and multiplier, e.g. \"Titanium Crystal x10\".\nMarker: a star shows how many anomalous planets it has, and a planet shows just a\n symbol -- a less crowded galaxy view.\nOff: no star map labels at all; anomalies remain visible in the planet panel.\nUnscanned planets never show anything, whichever setting is used.");
AnomalyRules = ((BaseUnityPlugin)this).Config.Bind<string>("Generation", "AnomalyRules", 1.ToString(), "Which ruleset this install uses: 1, 2, or Latest.\n1 -- the pre-1.0 ruleset, and the default. Every 0.x release uses it. It draws from a\n fixed list of 150 recipes, so a game update that adds recipes never moves a planet.\n2 -- ruleset 1 plus every recipe the game has gained since: Dark Fog Lens from game\n version 0.10.35, and whatever comes next. Switching from 1 moves only the few planets\n those recipes take -- about one anomalous planet in 150 per recipe -- and a later game\n update can move a planet or two again. Recipes added by other mods count here too.\nLatest -- the newest ruleset this version of the mod knows. 1.0 will add ruleset 3,\n which draws anomalies differently: a galaxy under 3 is a different galaxy.\nThis line is written once, when the mod first runs, and the mod never changes it, so an\nupgraded install keeps what it has. It applies to every galaxy on this install, old and\nnew alike. Nothing is written to saves or anywhere else; this is the record.");
ExcludedRecipes = ((BaseUnityPlugin)this).Config.Bind<string>("Generation", "ExcludedRecipes", "", "Recipes that should never receive an anomaly, comma separated. Empty by default.\nUse the item name as it appears in game, or a recipe's numeric id:\n ExcludedRecipes = Water Pump, Wind Turbine, 106\nThe mod deliberately holds no opinion about which anomalies are worth having --\nwhich recipes matter depends on how you play, and a list that is useless to one\nplayer is exactly what another builds by the thousand. So this is yours to set.\nEntries that match no recipe are reported in the log rather than ignored quietly.\nExcluding a recipe only changes the planets that currently carry it; the rest of\nyour galaxy is untouched. It does mean your galaxy differs from another player's\nwith the same seed.");
UnresearchedAnomalies = ((BaseUnityPlugin)this).Config.Bind<UnresearchedDisplay>("Display", "UnresearchedAnomalies", UnresearchedDisplay.Hide, "What an anomaly says about itself before you have researched the recipe it\naffects. Applies everywhere: the planet panel, planet and star labels, the system\ncounts, and the line in item tooltips.\nHide: nothing at all. The planet reads as ordinary until the research lands.\nMarker: the symbol without the name -- you know something is there and worth\n coming back for, but not yet what. Existence is cheap information; the name is\n the part that means nothing before you can build it.\nShow: everything, as in 0.3 and earlier.\nDisplay only: this changes nothing about which planets are anomalous.");
AnnounceOnResearch = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "AnnounceOnResearch", true, "When you finish a technology, says whether a world you have already scanned has an\nanomaly on one of its recipes. Shown in gold just under the game's own\n\"Research complete\" notice for about six seconds -- several from one technology\nappear in turn -- and always written to the log.\nThis is the other half of hiding unresearched anomalies: the mod stays quiet while\na recipe means nothing to you, and speaks at the moment it starts to. Only planets\nyou have already found are named -- it will not point at worlds you have not\nvisited, which would be a spoiler rather than a reminder.");
LegacyHideUnresearched = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "HideUnresearchedAnomalies", true, "Superseded by UnresearchedAnomalies. Kept only so a 0.4 setting is not ignored:\nif this is false and UnresearchedAnomalies is still at its default, it is read as\nUnresearchedAnomalies = Show, once, and reported in the log.");
if (!LegacyHideUnresearched.Value && UnresearchedAnomalies.Value == UnresearchedDisplay.Hide)
{
UnresearchedAnomalies.Value = UnresearchedDisplay.Show;
Log.LogInfo((object)"HideUnresearchedAnomalies = false is superseded by UnresearchedAnomalies = Show, and has been migrated. You can delete the old setting; Marker is the middle option if you want the symbol without the name.");
}
MultiplierFromCombatSettings = ((BaseUnityPlugin)this).Config.Bind<bool>("Experimental", "MultiplierFromCombatSettings", false, "EXPERIMENTAL. Off by default.\nWhen on, a galaxy with the Dark Fog disabled or set to passive uses\nPeacefulOutputMultiplier instead of OutputMultiplier. Hostile galaxies are unchanged.\nThe reasoning: the multiplier is a return on the cost of using an anomaly, and the\nlargest cost is clearing a world and then holding it. Without the Dark Fog that\ncost is gone and only hauling remains, so the same x10 that is fair against a\ngarrison is a giveaway without one.\nResolved once per galaxy when a save is loaded, and the choice is written to the\nlog. Changes neither which planets are anomalous nor which recipe each carries.");
PeacefulOutputMultiplier = ((BaseUnityPlugin)this).Config.Bind<int>("Experimental", "PeacefulOutputMultiplier", 3, new ConfigDescription("EXPERIMENTAL. Only used when MultiplierFromCombatSettings is on and the galaxy\nis peaceful or its enemies passive. 3 is what players who found x10 too high\nhave settled on.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(2, 1000), new object[0]));
LogEveryAnomaly = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "LogEveryAnomaly", false, "Writes every anomaly in the galaxy to the BepInEx log when a save is loaded, including planets you have never scanned. This spoils discovery on purpose. It exists for development and for answering \"does this galaxy contain X anywhere?\". It changes nothing in game and shows nothing on screen -- it only writes to the log.");
TestAnnouncement = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "TestAnnouncement", false, "Shows one test announcement about eight seconds after a save loads, through exactly the path a\nreal one takes. A quick way to check announcements work on your setup without\nwaiting for research to finish. The log records whether it reached the screen.\nTurn it off again afterwards.");
}
private void OnDestroy()
{
if (_harmony != null)
{
_harmony.UnpatchSelf();
_harmony = null;
}
AnomalyManager.Reset();
PlanetFactoryBeforeGameTickPatch.Reset();
TechUnlockPatch.Reset();
ItemTipPatch.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));
}
}
}
}
internal static class ItemTipPatch
{
private const float Spacing = 6f;
private const float BottomPadding = 10f;
private const string LabelColour = "#DBBA75";
private const string Label = "Anomaly:";
private const int PruneAbove = 8;
private static bool _errorLogged;
private static bool _geometryLogged;
private static bool _recipeEntryLogged;
private static bool _styleLogged;
private static readonly Dictionary<UIItemTip, Text> _lines = new Dictionary<UIItemTip, Text>();
private static readonly List<UIItemTip> _pruneScratch = new List<UIItemTip>();
internal static void Reset()
{
_lines.Clear();
_pruneScratch.Clear();
}
[HarmonyPostfix]
[HarmonyPatch(typeof(UIItemTip), "SetTip")]
internal static void AfterSetTip(UIItemTip __instance, int itemId, bool isRecipe)
{
//IL_011e: Unknown result type (might be due to invalid IL or missing references)
//IL_0135: Unknown result type (might be due to invalid IL or missing references)
//IL_014a: Unknown result type (might be due to invalid IL or missing references)
//IL_014f: Unknown result type (might be due to invalid IL or missing references)
//IL_0155: Unknown result type (might be due to invalid IL or missing references)
//IL_017b: Unknown result type (might be due to invalid IL or missing references)
//IL_0183: Unknown result type (might be due to invalid IL or missing references)
//IL_0188: Unknown result type (might be due to invalid IL or missing references)
//IL_019d: Unknown result type (might be due to invalid IL or missing references)
//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
//IL_0220: Unknown result type (might be due to invalid IL or missing references)
//IL_023a: Unknown result type (might be due to invalid IL or missing references)
//IL_0255: Unknown result type (might be due to invalid IL or missing references)
//IL_0270: Unknown result type (might be due to invalid IL or missing references)
//IL_028a: Unknown result type (might be due to invalid IL or missing references)
try
{
if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.trans == (Object)null || (Object)(object)__instance.descText == (Object)null)
{
return;
}
string text = ((itemId < 0) ? AnomalyManager.TooltipLinesForRecipe(-itemId) : AnomalyManager.TooltipLinesForItem(itemId));
if (itemId < 0 && !_recipeEntryLogged)
{
_recipeEntryLogged = true;
Plugin.Log.LogInfo((object)("Recipe-entry tooltip (once): recipe " + -itemId + " (passed as " + itemId + "), " + (string.IsNullOrEmpty(text) ? "no anomaly line" : "anomaly line shown") + "."));
}
Text val = LineFor(__instance);
if (string.IsNullOrEmpty(text))
{
if ((Object)(object)val != (Object)null)
{
((Component)val).gameObject.SetActive(false);
}
}
else if (!((Object)(object)val == (Object)null))
{
RectTransform trans = __instance.trans;
RectTransform rectTransform = ((Graphic)val).rectTransform;
RectTransform rectTransform2 = ((Graphic)__instance.descText).rectTransform;
val.text = Decorate(text);
((Graphic)val).color = ((Graphic)__instance.descText).color;
((Component)val).gameObject.SetActive(true);
float y = trans.sizeDelta.y;
float preferredHeight = val.preferredHeight;
Vector2 anchoredPosition = rectTransform.anchoredPosition;
anchoredPosition.x = rectTransform2.anchoredPosition.x;
anchoredPosition.y = 0f - y + 10f - 6f;
rectTransform.anchoredPosition = anchoredPosition;
Vector2 sizeDelta = trans.sizeDelta;
sizeDelta.y = y + 6f + preferredHeight;
trans.sizeDelta = sizeDelta;
Clamp(trans);
if (!_geometryLogged)
{
_geometryLogged = true;
Plugin.Log.LogInfo((object)string.Concat("Tooltip geometry (once): panel ", trans.sizeDelta.x, "x", y, " -> ", sizeDelta.y, "; desc anchorMin ", rectTransform2.anchorMin, " pivot ", rectTransform2.pivot, " pos ", rectTransform2.anchoredPosition, " size ", rectTransform2.sizeDelta, "; line pos ", rectTransform.anchoredPosition, " height ", preferredHeight, "."));
}
}
}
catch (Exception ex)
{
if (!_errorLogged)
{
_errorLogged = true;
Plugin.Log.LogError((object)("Failed to add the anomaly line to an item tooltip: " + ex));
}
}
}
private static Text LineFor(UIItemTip tip)
{
if (_lines.TryGetValue(tip, out var value) && (Object)(object)value != (Object)null)
{
return value;
}
Text descText = tip.descText;
GameObject val = Object.Instantiate<GameObject>(((Component)descText).gameObject, (Transform)(object)tip.trans);
((Object)val).name = "PlanetaryAnomalies-AnomalyLine";
value = val.GetComponent<Text>();
if ((Object)(object)value == (Object)null)
{
Object.Destroy((Object)(object)val);
return null;
}
Component[] components = val.GetComponents<Component>();
foreach (Component val2 in components)
{
if ((Object)(object)val2 != (Object)null && ((object)val2).GetType().Name.IndexOf("Locali", StringComparison.OrdinalIgnoreCase) >= 0)
{
Object.Destroy((Object)(object)val2);
}
}
value.supportRichText = true;
((Graphic)value).raycastTarget = false;
LogStyleOnce(descText);
Prune();
_lines[tip] = value;
return value;
}
private static void Prune()
{
if (_lines.Count < 8)
{
return;
}
_pruneScratch.Clear();
foreach (KeyValuePair<UIItemTip, Text> line in _lines)
{
if ((Object)(object)line.Key == (Object)null)
{
_pruneScratch.Add(line.Key);
}
}
for (int i = 0; i < _pruneScratch.Count; i++)
{
_lines.Remove(_pruneScratch[i]);
}
_pruneScratch.Clear();
}
private static string Decorate(string body)
{
return body.Replace("Anomaly:", "<color=#DBBA75>Anomaly:</color>");
}
private static void LogStyleOnce(Text source)
{
//IL_0057: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Unknown result type (might be due to invalid IL or missing references)
//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
if (!_styleLogged)
{
_styleLogged = true;
string text = "";
Shadow[] components = ((Component)source).GetComponents<Shadow>();
for (int i = 0; i < components.Length; i++)
{
object obj = text;
text = string.Concat(obj, "; ", ((object)components[i]).GetType().Name, " colour ", components[i].effectColor, " distance ", components[i].effectDistance);
}
Plugin.Log.LogInfo((object)string.Concat("Tooltip description style (once): colour ", ((Graphic)source).color, ", size ", source.fontSize, " ", source.fontStyle, ", font ", ((Object)(object)source.font != (Object)null) ? ((Object)source.font).name : "?", ", material ", ((Object)(object)((Graphic)source).material != (Object)null) ? ((Object)((Graphic)source).material).name : "?", text, "."));
}
}
private static void Clamp(RectTransform panel)
{
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
//IL_004c: Unknown result type (might be due to invalid IL or missing references)
//IL_0057: Unknown result type (might be due to invalid IL or missing references)
//IL_0064: Unknown result type (might be due to invalid IL or missing references)
//IL_007e: Unknown result type (might be due to invalid IL or missing references)
//IL_008b: Unknown result type (might be due to invalid IL or missing references)
//IL_009c: Unknown result type (might be due to invalid IL or missing references)
//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
//IL_0111: Unknown result type (might be due to invalid IL or missing references)
//IL_0113: Unknown result type (might be due to invalid IL or missing references)
//IL_0121: Unknown result type (might be due to invalid IL or missing references)
//IL_0126: Unknown result type (might be due to invalid IL or missing references)
//IL_0128: Unknown result type (might be due to invalid IL or missing references)
UIRoot instance = UIRoot.instance;
if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance.itemTipTransform == (Object)null))
{
Rect rect = instance.itemTipTransform.rect;
float num = Mathf.RoundToInt(((Rect)(ref rect)).width);
float num2 = Mathf.RoundToInt(((Rect)(ref rect)).height);
Rect rect2 = panel.rect;
((Rect)(ref rect2)).x = ((Rect)(ref rect2)).x + (panel.anchorMin.x * num + panel.anchoredPosition.x);
((Rect)(ref rect2)).y = ((Rect)(ref rect2)).y + (panel.anchorMin.y * num2 + panel.anchoredPosition.y);
Vector2 zero = Vector2.zero;
if (((Rect)(ref rect2)).xMin < 0f)
{
zero.x = 0f - ((Rect)(ref rect2)).xMin;
}
if (((Rect)(ref rect2)).yMin < 0f)
{
zero.y = 0f - ((Rect)(ref rect2)).yMin;
}
if (((Rect)(ref rect2)).xMax > num)
{
zero.x = num - ((Rect)(ref rect2)).xMax;
}
if (((Rect)(ref rect2)).yMax > num2)
{
zero.y = num2 - ((Rect)(ref rect2)).yMax;
}
if (zero != Vector2.zero)
{
panel.anchoredPosition += zero;
}
}
}
}
[HarmonyPatch(typeof(UIPlanetDetail), "OnPlanetDataSet")]
internal static class UIPlanetDetailPatch
{
private static bool _errorLogged;
[HarmonyPostfix]
internal static void Postfix(UIPlanetDetail __instance)
{
//IL_008e: Unknown result type (might be due to invalid IL or missing references)
//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
try
{
PlanetData planet = __instance.planet;
if (planet == null || !planet.scanned)
{
return;
}
Text planetBrief = __instance.planetBrief;
if ((Object)(object)planetBrief == (Object)null)
{
return;
}
object obj;
switch (AnomalyManager.VisibilityFor(planet.id))
{
case AnomalyVisibility.None:
return;
default:
obj = "ANOMALY\nOn a recipe you have not researched yet.";
break;
case AnomalyVisibility.Full:
obj = AnomalyManager.DescribeForPlanet(planet.id);
break;
}
string text = (string)obj;
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));
}
}
}
}
internal static class StarmapLabel
{
private const string Open = " <color=#FFC454>";
private const string Close = "</color>";
private const int RefreshIntervalFrames = 30;
internal static bool DueForRefresh()
{
return Time.frameCount % 30 == 0;
}
internal static void Set(Text label, string body)
{
if ((Object)(object)label == (Object)null)
{
return;
}
string text = ((label.text != null) ? label.text : "");
int num = text.IndexOf(" <color=#FFC454>", StringComparison.Ordinal);
string text2 = ((num >= 0) ? text.Substring(0, num) : text);
string text3 = (string.IsNullOrEmpty(body) ? text2 : (text2 + " <color=#FFC454>" + body + "</color>"));
if (!string.Equals(text, text3, StringComparison.Ordinal))
{
if (!string.IsNullOrEmpty(body) && !label.supportRichText)
{
label.supportRichText = true;
}
label.text = text3;
}
}
}
internal static class UIStarmapPlanetPatch
{
private static bool _errorLogged;
[HarmonyPostfix]
[HarmonyPatch(typeof(UIStarmapPlanet), "_OnInit")]
internal static void AfterInit(UIStarmapPlanet __instance)
{
Apply(__instance);
}
[HarmonyPatch(typeof(UIStarmapPlanet), "OnPlanetDisplayNameChange")]
[HarmonyPostfix]
internal static void AfterRename(UIStarmapPlanet __instance)
{
Apply(__instance);
}
[HarmonyPatch(typeof(UIStarmapPlanet), "_OnLateUpdate")]
[HarmonyPostfix]
internal static void AfterLateUpdate(UIStarmapPlanet __instance)
{
if (StarmapLabel.DueForRefresh())
{
Apply(__instance);
}
}
private static void Apply(UIStarmapPlanet instance)
{
try
{
if ((Object)(object)instance == (Object)null)
{
return;
}
PlanetData planet = instance.planet;
Text nameText = instance.nameText;
if (planet == null || (Object)(object)nameText == (Object)null)
{
return;
}
StarmapLabelMode starmapLabelMode = ((Plugin.StarmapLabel != null) ? Plugin.StarmapLabel.Value : StarmapLabelMode.Detail);
string body = null;
if (planet.scanned && starmapLabelMode != StarmapLabelMode.Off)
{
AnomalyVisibility anomalyVisibility = AnomalyManager.VisibilityFor(planet.id);
if (anomalyVisibility != AnomalyVisibility.None)
{
body = "Å";
if (starmapLabelMode == StarmapLabelMode.Detail && anomalyVisibility == AnomalyVisibility.Full)
{
string text = AnomalyManager.ShortDescribeForPlanet(planet.id);
if (!string.IsNullOrEmpty(text))
{
body = "Å " + text;
}
}
}
}
StarmapLabel.Set(nameText, body);
}
catch (Exception ex)
{
if (!_errorLogged)
{
_errorLogged = true;
Plugin.Log.LogError((object)("Failed to mark a star map planet: " + ex));
}
}
}
}
internal enum StarmapLabelMode
{
Off,
Marker,
Detail
}
internal static class UIStarmapStarPatch
{
private const string Symbol = "Å";
private static bool _errorLogged;
[HarmonyPostfix]
[HarmonyPatch(typeof(UIStarmapStar), "_OnInit")]
internal static void AfterInit(UIStarmapStar __instance)
{
Apply(__instance);
}
[HarmonyPatch(typeof(UIStarmapStar), "OnStarDisplayNameChange")]
[HarmonyPostfix]
internal static void AfterRename(UIStarmapStar __instance)
{
Apply(__instance);
}
[HarmonyPostfix]
[HarmonyPatch(typeof(UIStarmapStar), "_OnLateUpdate")]
internal static void AfterLateUpdate(UIStarmapStar __instance)
{
if (StarmapLabel.DueForRefresh())
{
Apply(__instance);
}
}
private static void Apply(UIStarmapStar instance)
{
try
{
if ((Object)(object)instance == (Object)null)
{
return;
}
Text nameText = instance.nameText;
StarData star = instance.star;
if ((Object)(object)nameText == (Object)null || star == null || star.planets == null)
{
return;
}
StarmapLabelMode starmapLabelMode = ((Plugin.StarmapLabel != null) ? Plugin.StarmapLabel.Value : StarmapLabelMode.Detail);
int num = 0;
string text = "";
int num2 = 0;
int num3 = 0;
for (int i = 0; i < star.planets.Length; i++)
{
PlanetData val = star.planets[i];
if (val == null || !val.scanned)
{
continue;
}
AnomalyVisibility anomalyVisibility = AnomalyManager.VisibilityFor(val.id);
if (anomalyVisibility == AnomalyVisibility.None)
{
continue;
}
num++;
if (anomalyVisibility == AnomalyVisibility.Full)
{
string text2 = AnomalyManager.AnomalousItemName(val.id);
if (!string.IsNullOrEmpty(text2))
{
text = text + ((num2 > 0) ? ", " : "") + text2;
num2++;
continue;
}
}
num3++;
}
string body = null;
if (num > 0 && starmapLabelMode != StarmapLabelMode.Off)
{
body = ((starmapLabelMode != StarmapLabelMode.Detail || num2 <= 0) ? ((num == 1) ? "Å" : ("Å " + num)) : ("Å " + text + ((num3 > 0) ? (", +" + num3) : "")));
}
StarmapLabel.Set(nameText, body);
}
catch (Exception ex)
{
if (!_errorLogged)
{
_errorLogged = true;
Plugin.Log.LogError((object)("Failed to mark a star map star: " + ex));
}
}
}
}
internal static class TechUnlockPatch
{
private struct Completed
{
internal int TechId;
internal bool OnMainThread;
internal GameHistoryData History;
}
private sealed class TipStyle
{
internal int FontSize;
internal Color Colour;
internal TextAnchor Alignment;
internal Vector2 Pivot;
internal string Message;
internal Vector2 Requested;
internal float ShownAt;
internal float HoldUntil;
internal bool GeometryLogged;
}
private const int MaxQueued = 5;
private const int MaxNamed = 3;
private const float TestDelaySeconds = 8f;
private const float ReadSeconds = 6f;
private const float HoldLifetime = 0.8f;
private const float FullAlphaFloor = 1f / 9f;
private const float LifeDecayPerSecond = 0.6666666f;
private const float HoldSeconds = 4.9666667f;
private const float GapSeconds = 0.5f;
private const float Drift = 0f;
private const float BelowNoticePixels = 44f;
private const float FallbackHeightFraction = 0.82f;
private const int FontBoost = 8;
private static bool _errorLogged;
private static bool _handoffErrorLogged;
private static readonly object _handoffLock = new object();
private static readonly List<Completed> _completed = new List<Completed>();
private static int _mainThreadId = -1;
private static readonly HashSet<int> _handled = new HashSet<int>();
private static GameHistoryData _history;
private static readonly Queue<string> _pending = new Queue<string>();
private static int _overflow;
private static float _nextShowTime;
private static bool _testQueued;
private static float _testReadyAt = -1f;
private static readonly Color AnnouncementColour = new Color(1f, 0.769f, 0.329f, 1f);
private static FieldInfo _realtimeTipsField;
private static FieldInfo _lifeTimeField;
private static readonly Dictionary<UIRealtimeTip, TipStyle> _styled = new Dictionary<UIRealtimeTip, TipStyle>();
private static readonly List<UIRealtimeTip> _restoreScratch = new List<UIRealtimeTip>();
private static bool _geometryLogged;
internal static void CaptureMainThread()
{
_mainThreadId = Thread.CurrentThread.ManagedThreadId;
}
internal static void Reset()
{
lock (_handoffLock)
{
_completed.Clear();
}
_history = null;
_geometryLogged = false;
ForgetGame();
}
private static void ForgetGame()
{
_handled.Clear();
_pending.Clear();
_overflow = 0;
_nextShowTime = 0f;
_testQueued = false;
_testReadyAt = -1f;
}
private static void NoticeNewGame()
{
GameHistoryData history = GameMain.history;
if (!object.ReferenceEquals(history, _history))
{
_history = history;
ForgetGame();
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(GameHistoryData), "NotifyTechUnlock")]
internal static void AfterTechUnlock(GameHistoryData __instance, int _techId)
{
try
{
if (DSPGame.IsMenuDemo)
{
return;
}
Completed item = new Completed
{
TechId = _techId,
History = __instance,
OnMainThread = (Thread.CurrentThread.ManagedThreadId == _mainThreadId)
};
lock (_handoffLock)
{
_completed.Add(item);
}
}
catch (Exception ex)
{
if (!_handoffErrorLogged)
{
_handoffErrorLogged = true;
Plugin.Log.LogError((object)("Failed to record a completed technology: " + ex));
}
}
}
[HarmonyPatch(typeof(UIGeneralTips), "_OnUpdate")]
[HarmonyPostfix]
internal static void AfterTipsUpdate()
{
try
{
if (_mainThreadId < 0)
{
_mainThreadId = Thread.CurrentThread.ManagedThreadId;
}
RestoreReusedTips();
HoldTips();
NoticeNewGame();
DrainCompleted();
QueueTestIfAsked();
if ((_pending.Count == 0 && _overflow == 0) || !CanShow())
{
return;
}
float realtimeSinceStartup = Time.realtimeSinceStartup;
if (!(realtimeSinceStartup < _nextShowTime))
{
string message;
if (_pending.Count > 0)
{
message = _pending.Dequeue();
}
else
{
message = "ANOMALY: " + _overflow + " more you can now use -- see the log.";
_overflow = 0;
}
Show(message);
_nextShowTime = realtimeSinceStartup + 6f + 0.5f;
}
}
catch (Exception ex)
{
if (!_errorLogged)
{
_errorLogged = true;
Plugin.Log.LogError((object)("Failed to process research announcements: " + ex));
}
}
}
private static void DrainCompleted()
{
Completed[] array;
lock (_handoffLock)
{
if (_completed.Count == 0)
{
return;
}
array = _completed.ToArray();
_completed.Clear();
}
for (int i = 0; i < array.Length; i++)
{
if (!object.ReferenceEquals(array[i].History, _history))
{
Plugin.Log.LogInfo((object)("Ignored a completed technology (id " + array[i].TechId + ") recorded against a game that is no longer the loaded one."));
}
else
{
Process(array[i]);
}
}
}
private static void Process(Completed entry)
{
if (_handled.Contains(entry.TechId))
{
return;
}
_handled.Add(entry.TechId);
TechProtoSet techs = LDB.techs;
if ((Object)(object)techs == (Object)null || !((ProtoSet<TechProto>)(object)techs).Exist(entry.TechId))
{
return;
}
TechProto val = ((ProtoSet<TechProto>)(object)techs).Select(entry.TechId);
if (val == null)
{
return;
}
string text = (string.IsNullOrEmpty(((Proto)val).name) ? ("technology " + entry.TechId) : ((Proto)val).name);
int num = ((val.UnlockRecipes != null) ? val.UnlockRecipes.Length : 0);
bool flag = Plugin.AnnounceOnResearch == null || Plugin.AnnounceOnResearch.Value;
int num2 = 0;
if (flag && val.UnlockRecipes != null)
{
for (int i = 0; i < val.UnlockRecipes.Length; i++)
{
string text2 = MessageFor(val.UnlockRecipes[i]);
if (text2 != null)
{
Plugin.Log.LogInfo((object)("Announced on research: " + text2));
Enqueue(text2);
num2++;
}
}
}
Plugin.Log.LogInfo((object)("Research completed: " + text + " (tech " + entry.TechId + ") -- " + num + " recipe(s), " + (flag ? (num2 + " announced") : "announcements off") + " (reported on the " + (entry.OnMainThread ? "main" : "a worker") + " thread)."));
}
private static string MessageFor(int recipeId)
{
string text = AnomalyManager.RecipeLabel(recipeId);
if (string.IsNullOrEmpty(text))
{
return null;
}
int total;
string text2 = AnomalyManager.KnownPlanetsWithRecipe(recipeId, 3, out total);
if (text2 != null)
{
return "ANOMALY: " + text + " on " + text2;
}
if (AnomalyManager.AnyUnknownPlanetWithRecipe(recipeId))
{
return "ANOMALY: " + text + " exists on a world you have not found.";
}
return null;
}
private static void Enqueue(string message)
{
if (_pending.Count < 5)
{
_pending.Enqueue(message);
}
else
{
_overflow++;
}
}
private static void QueueTestIfAsked()
{
if (!_testQueued && Plugin.TestAnnouncement != null && Plugin.TestAnnouncement.Value && CanShow())
{
float realtimeSinceStartup = Time.realtimeSinceStartup;
if (_testReadyAt < 0f)
{
_testReadyAt = realtimeSinceStartup + 8f;
}
else if (!(realtimeSinceStartup < _testReadyAt))
{
_testQueued = true;
Enqueue("ANOMALY: test announcement. If you can read this, announcements work.");
Plugin.Log.LogInfo((object)"TestAnnouncement is on: queued a test announcement.");
}
}
}
private static bool CanShow()
{
if (!GameMain.isRunning || GameMain.isPaused || GameMain.isLoading)
{
return false;
}
UIRoot instance = UIRoot.instance;
if ((Object)(object)instance != (Object)null && (Object)(object)instance.uiGame != (Object)null && ((ManualBehaviour)instance.uiGame).active)
{
return (Object)(object)instance.uiGame.generalTips != (Object)null;
}
return false;
}
private static void Show(string message)
{
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
//IL_0052: Unknown result type (might be due to invalid IL or missing references)
//IL_0055: Unknown result type (might be due to invalid IL or missing references)
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
try
{
UIRoot instance = UIRoot.instance;
UIGeneralTips val = (((Object)(object)instance != (Object)null && (Object)(object)instance.uiGame != (Object)null) ? instance.uiGame.generalTips : null);
if ((Object)(object)val == (Object)null)
{
Plugin.Log.LogWarning((object)("No tip layer to show an announcement on: " + message));
return;
}
Vector2 val2 = AnnouncementPosition(val);
val.InvokeRealtimeTip(message, val2, 0f, 0f);
if (Linger(val, message, val2))
{
Plugin.Log.LogInfo((object)("Shown on screen: " + message));
}
else
{
Plugin.Log.LogWarning((object)("Raised a tip, but the game did not draw one: " + message));
}
}
catch (Exception ex)
{
if (!_errorLogged)
{
_errorLogged = true;
Plugin.Log.LogError((object)("Could not show a tip; the announcement is in the log only: " + ex));
}
}
}
private static Vector2 AnnouncementPosition(UIGeneralTips tips)
{
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
//IL_004c: Unknown result type (might be due to invalid IL or missing references)
//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
RectTransform tipPanelRect = tips.tipPanelRect;
if ((Object)(object)tipPanelRect == (Object)null)
{
return Vector2.zero;
}
Rect rect = tipPanelRect.rect;
Text researchCompleteText = tips.researchCompleteText;
if ((Object)(object)researchCompleteText != (Object)null && (Object)(object)((Graphic)researchCompleteText).rectTransform != (Object)null)
{
Vector3 val = ((Transform)tipPanelRect).InverseTransformPoint(((Transform)((Graphic)researchCompleteText).rectTransform).position);
float num = val.x - ((Rect)(ref rect)).xMin;
float num2 = val.y - ((Rect)(ref rect)).yMin - 44f;
if (num > 0f && num < ((Rect)(ref rect)).width && num2 > 0f && num2 < ((Rect)(ref rect)).height)
{
return new Vector2(num, num2);
}
}
return new Vector2(((Rect)(ref rect)).width * 0.5f, ((Rect)(ref rect)).height * 0.82f);
}
private static bool Linger(UIGeneralTips tips, string message, Vector2 requested)
{
//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
if (_realtimeTipsField == null)
{
_realtimeTipsField = AccessTools.Field(typeof(UIGeneralTips), "realtimeTips");
}
if (_lifeTimeField == null)
{
_lifeTimeField = AccessTools.Field(typeof(UIRealtimeTip), "lifeTime");
}
if (_realtimeTipsField == null || _lifeTimeField == null)
{
return false;
}
if (!(_realtimeTipsField.GetValue(tips) is List<UIRealtimeTip> list))
{
return false;
}
for (int num = list.Count - 1; num >= 0; num--)
{
UIRealtimeTip val = list[num];
if (!((Object)(object)val == (Object)null) && !((Object)(object)val.textComp == (Object)null) && ((Component)val).gameObject.activeSelf && !(val.textComp.text != message))
{
_lifeTimeField.SetValue(val, 0.8f);
Style(val, tips.realtimeTipPrefab, message, requested);
return true;
}
}
return false;
}
private static void Style(UIRealtimeTip tip, UIRealtimeTip prefab, string message, Vector2 requested)
{
//IL_0092: Unknown result type (might be due to invalid IL or missing references)
//IL_0093: Unknown result type (might be due to invalid IL or missing references)
//IL_002d: Unknown result type (might be due to invalid IL or missing references)
//IL_0032: Unknown result type (might be due to invalid IL or missing references)
//IL_003e: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_006e: Unknown result type (might be due to invalid IL or missing references)
//IL_0061: Unknown result type (might be due to invalid IL or missing references)
//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
//IL_010f: Unknown result type (might be due to invalid IL or missing references)
//IL_0073: Unknown result type (might be due to invalid IL or missing references)
//IL_013f: Unknown result type (might be due to invalid IL or missing references)
if (!_styled.TryGetValue(tip, out var value))
{
value = new TipStyle();
value.FontSize = tip.textComp.fontSize;
value.Colour = ((Graphic)tip.textComp).color;
value.Alignment = tip.textComp.alignment;
value.Pivot = (Vector2)(((Object)(object)tip.rectTrans != (Object)null) ? tip.rectTrans.pivot : new Vector2(0.5f, 0.5f));
_styled[tip] = value;
}
value.Message = message;
float realtimeSinceStartup = Time.realtimeSinceStartup;
value.Requested = requested;
value.ShownAt = realtimeSinceStartup;
value.HoldUntil = realtimeSinceStartup + 4.9666667f;
value.GeometryLogged = false;
int num = (((Object)(object)prefab != (Object)null && (Object)(object)prefab.textComp != (Object)null) ? prefab.textComp.fontSize : value.FontSize);
tip.textComp.fontSize = num + 8;
Color announcementColour = AnnouncementColour;
announcementColour.a = ((Graphic)tip.textComp).color.a;
((Graphic)tip.textComp).color = announcementColour;
tip.textComp.alignment = (TextAnchor)4;
if ((Object)(object)tip.rectTrans != (Object)null)
{
tip.rectTrans.pivot = new Vector2(0.5f, 0.5f);
}
}
private static void HoldTips()
{
if (_styled.Count == 0 || _lifeTimeField == null)
{
return;
}
float realtimeSinceStartup = Time.realtimeSinceStartup;
foreach (KeyValuePair<UIRealtimeTip, TipStyle> item in _styled)
{
UIRealtimeTip key = item.Key;
TipStyle value = item.Value;
if (!((Object)(object)key == (Object)null) && !((Object)(object)key.textComp == (Object)null) && ((Component)key).gameObject.activeSelf)
{
if (realtimeSinceStartup < value.HoldUntil)
{
_lifeTimeField.SetValue(key, 0.8f);
}
if (!value.GeometryLogged && !_geometryLogged && realtimeSinceStartup >= value.ShownAt + 0.5f)
{
value.GeometryLogged = true;
_geometryLogged = true;
LogGeometry(key, value);
}
}
}
}
private static void LogGeometry(UIRealtimeTip tip, TipStyle style)
{
//IL_0044: Unknown result type (might be due to invalid IL or missing references)
//IL_0070: Unknown result type (might be due to invalid IL or missing references)
//IL_0075: Unknown result type (might be due to invalid IL or missing references)
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
//IL_0086: Unknown result type (might be due to invalid IL or missing references)
//IL_008b: Unknown result type (might be due to invalid IL or missing references)
//IL_0090: Unknown result type (might be due to invalid IL or missing references)
//IL_01af: Unknown result type (might be due to invalid IL or missing references)
//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
//IL_01df: Unknown result type (might be due to invalid IL or missing references)
//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
//IL_0200: Unknown result type (might be due to invalid IL or missing references)
//IL_0219: Unknown result type (might be due to invalid IL or missing references)
//IL_0233: Unknown result type (might be due to invalid IL or missing references)
//IL_024d: Unknown result type (might be due to invalid IL or missing references)
//IL_0266: Unknown result type (might be due to invalid IL or missing references)
//IL_027c: Unknown result type (might be due to invalid IL or missing references)
//IL_0111: Unknown result type (might be due to invalid IL or missing references)
//IL_0116: Unknown result type (might be due to invalid IL or missing references)
//IL_011b: Unknown result type (might be due to invalid IL or missing references)
//IL_0122: Unknown result type (might be due to invalid IL or missing references)
//IL_0311: Unknown result type (might be due to invalid IL or missing references)
//IL_0167: Unknown result type (might be due to invalid IL or missing references)
//IL_016c: Unknown result type (might be due to invalid IL or missing references)
//IL_0170: Unknown result type (might be due to invalid IL or missing references)
try
{
UIRoot instance = UIRoot.instance;
UIGeneralTips val = (((Object)(object)instance != (Object)null && (Object)(object)instance.uiGame != (Object)null) ? instance.uiGame.generalTips : null);
RectTransform rectTrans = tip.rectTrans;
Canvas componentInParent = ((Component)tip).GetComponentInParent<Canvas>();
Camera val2 = (((Object)(object)componentInParent != (Object)null && (int)componentInParent.renderMode != 0) ? componentInParent.worldCamera : null);
Vector3[] array = (Vector3[])(object)new Vector3[4];
rectTrans.GetWorldCorners(array);
Vector2 val3 = RectTransformUtility.WorldToScreenPoint(val2, array[0]);
Vector2 val4 = RectTransformUtility.WorldToScreenPoint(val2, array[2]);
int width = Screen.width;
int height = Screen.height;
bool flag = val4.x > 0f && val3.x < (float)width && val4.y > 0f && val3.y < (float)height;
string text = "no notice";
string text2 = "no panel";
bool flag2 = false;
if ((Object)(object)val != (Object)null)
{
if ((Object)(object)val.researchCompleteText != (Object)null)
{
Vector2 val5 = RectTransformUtility.WorldToScreenPoint(val2, ((Transform)((Graphic)val.researchCompleteText).rectTransform).position);
text = string.Concat("notice at ", val5, ((Component)val.researchCompleteText).gameObject.activeInHierarchy ? " (showing)" : " (not showing)");
}
if ((Object)(object)val.tipPanelRect != (Object)null)
{
Rect rect = val.tipPanelRect.rect;
text2 = "tip panel " + ((Rect)(ref rect)).size;
flag2 = (Object)(object)((Transform)rectTrans).parent == (Object)(object)val.tipPanelRect;
}
}
ManualLogSource log = Plugin.Log;
object[] array2 = new object[34]
{
"Announcement geometry (once): asked for ",
style.Requested,
", tip is at ",
rectTrans.anchoredPosition,
", scale ",
((Transform)rectTrans).localScale,
", size ",
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null
};
Rect rect2 = rectTrans.rect;
array2[7] = ((Rect)(ref rect2)).size;
array2[8] = ", pivot ";
array2[9] = rectTrans.pivot;
array2[10] = ", anchors ";
array2[11] = rectTrans.anchorMin;
array2[12] = " to ";
array2[13] = rectTrans.anchorMax;
array2[14] = "; on screen from ";
array2[15] = val3;
array2[16] = " to ";
array2[17] = val4;
array2[18] = " of ";
array2[19] = width;
array2[20] = "x";
array2[21] = height;
array2[22] = (flag ? " -- INSIDE the screen" : " -- OUTSIDE the screen");
array2[23] = "; ";
array2[24] = text;
array2[25] = "; ";
array2[26] = text2;
array2[27] = "; parent is the tip panel: ";
array2[28] = flag2;
array2[29] = "; text alpha ";
array2[30] = ((Graphic)tip.textComp).color.a;
array2[31] = ", size ";
array2[32] = tip.textComp.fontSize;
array2[33] = ".";
log.LogInfo((object)string.Concat(array2));
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not measure the announcement on screen: " + ex.Message));
}
}
private static void RestoreReusedTips()
{
//IL_0107: Unknown result type (might be due to invalid IL or missing references)
//IL_010c: Unknown result type (might be due to invalid IL or missing references)
//IL_0117: Unknown result type (might be due to invalid IL or missing references)
//IL_012d: Unknown result type (might be due to invalid IL or missing references)
//IL_013d: Unknown result type (might be due to invalid IL or missing references)
//IL_015f: Unknown result type (might be due to invalid IL or missing references)
if (_styled.Count == 0)
{
return;
}
_restoreScratch.Clear();
foreach (KeyValuePair<UIRealtimeTip, TipStyle> item in _styled)
{
UIRealtimeTip key = item.Key;
if ((Object)(object)key == (Object)null || (Object)(object)key.textComp == (Object)null || !((Component)key).gameObject.activeSelf || !(key.textComp.text == item.Value.Message))
{
_restoreScratch.Add(key);
}
}
for (int i = 0; i < _restoreScratch.Count; i++)
{
UIRealtimeTip val = _restoreScratch[i];
TipStyle tipStyle = _styled[val];
_styled.Remove(val);
if (!((Object)(object)val == (Object)null) && !((Object)(object)val.textComp == (Object)null))
{
val.textComp.fontSize = tipStyle.FontSize;
Color colour = tipStyle.Colour;
colour.a = ((Graphic)val.textComp).color.a;
((Graphic)val.textComp).color = colour;
val.textComp.alignment = tipStyle.Alignment;
if ((Object)(object)val.rectTrans != (Object)null)
{
val.rectTrans.pivot = tipStyle.Pivot;
}
}
}
}
}