Decompiled source of RoguelikeMode v1.1.0

plugins/RoguelikeMode.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Logging;
using DM;
using GamepadUI.StateManager.Core;
using HarmonyLib;
using InControl;
using Landfall.TABS;
using Landfall.TABS.Budget;
using Landfall.TABS.GameMode;
using Landfall.TABS.GameState;
using Landfall.TABS.UI;
using Landfall.TABS.UnitPlacement;
using Landfall.TABS.Workshop;
using Landfall.TABS_Input;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RoguelikeMode.Roguelike;
using RoguelikeMode.Roguelike.Patches;
using RoguelikeMode.Roguelike.UI;
using TFBGames;
using TMPro;
using UIStateManager;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
using UnityEngine.TextCore;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("RoguelikeMode")]
[assembly: AssemblyDescription("Roguelike Mode for Totally Accurate Battle Simulator")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Krafft Media")]
[assembly: AssemblyProduct("Roguelike Mode")]
[assembly: AssemblyCopyright("Copyright © Krafft Media 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("7d2f4c1e-9b3a-4e6f-8c2d-5a1b0e7f3c9d")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.1.0.0")]
namespace RoguelikeMode
{
	[BepInPlugin("krafftmedia.tabs.roguelikemode", "Roguelike Mode", "1.1.0")]
	public class Launcher : BaseUnityPlugin
	{
		public const string Guid = "krafftmedia.tabs.roguelikemode";

		public const string Name = "Roguelike Mode";

		public const string Version = "1.1.0";

		private void Awake()
		{
			Log.Init(((BaseUnityPlugin)this).Logger);
			Log.Info("Roguelike Mode 1.1.0 (release build); Unity " + Application.unityVersion + ", BepInEx " + typeof(Paths).Assembly.GetName().Version?.ToString() + ", Harmony " + typeof(Harmony).Assembly.GetName().Version);
			RoguelikeMod.Initialize();
		}
	}
	public static class BuildInfo
	{
		public const bool DevMode = false;
	}
	public static class GameSurface
	{
		private struct Probe
		{
			public readonly Type Type;

			public readonly string Name;

			public readonly bool IsMethod;

			public readonly bool Critical;

			public Probe(Type type, string name, bool critical, bool isMethod = false)
			{
				Type = type;
				Name = name;
				Critical = critical;
				IsMethod = isMethod;
			}
		}

		private static readonly Probe[] Probes = new Probe[14]
		{
			new Probe(typeof(CampaignGameMode), "m_playerWonMatch", critical: true),
			new Probe(typeof(PlacementUI), "m_unitIDs", critical: true),
			new Probe(typeof(PlacementUI), "unitLayoutGroup", critical: true),
			new Probe(typeof(BaseGameMode), "RestartMatch", critical: true, isMethod: true),
			new Probe(typeof(UnitButton), "m_unitName", critical: false),
			new Probe(typeof(UnitButton), "m_cost", critical: false),
			new Probe(typeof(UnitButton), "m_customUnitCost", critical: false),
			new Probe(typeof(UnitInfoBar), "unitCost", critical: false),
			new Probe(typeof(BattleRadialButton), "costLabel", critical: false),
			new Probe(typeof(MusicHandler), "m_currentSong", critical: false),
			new Probe(typeof(MusicHandler), "sources", critical: false),
			new Probe(typeof(GlyphService), "m_pcIcons", critical: false),
			new Probe(typeof(MainMenuButtons), "RebuildNavigation", critical: false, isMethod: true),
			new Probe(typeof(LevelSelector), "m_templateLevelButton", critical: false)
		};

		private static readonly Dictionary<string, FieldInfo> fields = new Dictionary<string, FieldInfo>();

		private static readonly Dictionary<string, MethodInfo> methods = new Dictionary<string, MethodInfo>();

		private static readonly List<string> missing = new List<string>();

		private static volatile bool validated;

		public static IList<string> Missing
		{
			get
			{
				lock (missing)
				{
					return missing.ToArray();
				}
			}
		}

		public static FieldInfo Field(Type type, string name)
		{
			string key = type.FullName + "." + name;
			lock (fields)
			{
				if (fields.TryGetValue(key, out var value))
				{
					return value;
				}
				value = (Simulated(name) ? null : AccessTools.Field(type, name));
				fields[key] = value;
				if (value == null)
				{
					NoteMissing(key, "field");
				}
				return value;
			}
		}

		public static MethodInfo Method(Type type, string name)
		{
			string key = type.FullName + "." + name;
			lock (methods)
			{
				if (methods.TryGetValue(key, out var value))
				{
					return value;
				}
				value = (Simulated(name) ? null : AccessTools.Method(type, name, (Type[])null, (Type[])null));
				methods[key] = value;
				if (value == null)
				{
					NoteMissing(key, "method");
				}
				return value;
			}
		}

		public static List<string> Validate()
		{
			List<string> list = new List<string>();
			int num = 0;
			Probe[] probes = Probes;
			for (int i = 0; i < probes.Length; i++)
			{
				Probe probe = probes[i];
				if (probe.IsMethod ? (Method(probe.Type, probe.Name) != null) : (Field(probe.Type, probe.Name) != null))
				{
					num++;
				}
				else if (probe.Critical)
				{
					list.Add(probe.Type.Name + "." + probe.Name);
				}
			}
			string text = "Game members resolved: " + num + "/" + Probes.Length;
			IList<string> list2 = Missing;
			if (list2.Count == 0)
			{
				Log.Info(text);
			}
			else
			{
				Log.Warn(text + "; features using these are off, check for a mod update: " + string.Join(", ", list2.ToArray()));
			}
			validated = true;
			return list;
		}

		private static void NoteMissing(string key, string kind)
		{
			lock (missing)
			{
				missing.Add(key);
			}
			if (validated)
			{
				Log.Warn("Game " + kind + " not found: " + key + "; the feature using it is off, check for a mod update.");
			}
		}

		private static bool Simulated(string name)
		{
			return false;
		}
	}
	public sealed class JsonStore<T> where T : class
	{
		private readonly string stem;

		private readonly string label;

		private readonly string fallback;

		private readonly int currentVersion;

		private readonly Func<T, string> normalize;

		private readonly Func<JObject, int, bool> migrate;

		private bool dirty;

		public string SavePath => Path.Combine(SaveIO.SaveDirectory, stem + ".json");

		public string PreviousPath => Path.Combine(SaveIO.SaveDirectory, stem + ".prev.json");

		public JsonStore(string stem, string label, string fallback, int currentVersion, Func<T, string> normalize, Func<JObject, int, bool> migrate = null)
		{
			this.stem = stem;
			this.label = label;
			this.fallback = fallback;
			this.currentVersion = currentVersion;
			this.normalize = normalize;
			this.migrate = migrate;
		}

		public T Load()
		{
			if (!File.Exists(SavePath))
			{
				return null;
			}
			T val = TryLoad(SavePath, out var why);
			if (val != null)
			{
				return val;
			}
			string text = SaveIO.Quarantine(SavePath, stem);
			Log.Warn(stem + ".json unreadable (" + why + ")" + ((text != null) ? ("; moved to " + Path.GetFileName(text)) : "") + ".");
			if (!File.Exists(PreviousPath))
			{
				return null;
			}
			val = TryLoad(PreviousPath, out var why2);
			if (val != null)
			{
				Log.Warn(label + " restored from " + stem + ".prev.json.");
				try
				{
					SaveIO.WriteAtomic(SavePath, Serialize(val), PreviousPath);
				}
				catch (Exception ex)
				{
					Log.Error("Failed to re-create " + stem + ".json: " + ex.Message);
				}
				return val;
			}
			Log.Warn(stem + ".prev.json unreadable too (" + why2 + "); quarantining it and " + fallback + ".");
			SaveIO.Quarantine(PreviousPath, stem + ".prev");
			return null;
		}

		public void Save(T value)
		{
			dirty = false;
			if (value == null)
			{
				return;
			}
			try
			{
				SaveIO.WriteAtomic(SavePath, Serialize(value), PreviousPath);
			}
			catch (Exception ex)
			{
				Log.Error("Failed to save " + stem + ": " + ex.Message);
			}
		}

		public void MarkDirty()
		{
			dirty = true;
		}

		public void SaveIfDirty(T value)
		{
			if (dirty)
			{
				Save(value);
			}
		}

		private static string Serialize(T value)
		{
			return JsonConvert.SerializeObject((object)value, (Formatting)1);
		}

		private T TryLoad(string path, out string why)
		{
			why = null;
			try
			{
				JObject val = JObject.Parse(File.ReadAllText(path));
				JToken val2 = val["Version"];
				int num = ((val2 != null) ? Extensions.Value<int>((IEnumerable<JToken>)val2) : 0);
				if (num > currentVersion)
				{
					string[] obj = new string[5]
					{
						"saved by a newer Roguelike Mode (format ",
						num.ToString(),
						", this build reads ",
						null,
						null
					};
					int num2 = currentVersion;
					obj[3] = num2.ToString();
					obj[4] = ")";
					why = string.Concat(obj);
					return null;
				}
				if (num < currentVersion && migrate != null && !migrate(val, num))
				{
					string text = num.ToString();
					int num2 = currentVersion;
					why = "format " + text + " cannot be upgraded to " + num2;
					return null;
				}
				T val3 = ((JToken)val).ToObject<T>();
				if (val3 == null)
				{
					why = "empty";
					return null;
				}
				why = ((normalize != null) ? normalize(val3) : null);
				return (why == null) ? val3 : null;
			}
			catch (Exception ex)
			{
				why = ex.Message;
				return null;
			}
		}
	}
	public static class Log
	{
		private static ManualLogSource source;

		public static ManualLogSource Source
		{
			get
			{
				if (source == null)
				{
					source = Logger.CreateLogSource("Roguelike Mode");
				}
				return source;
			}
		}

		public static void Init(ManualLogSource pluginSource)
		{
			if (pluginSource != null)
			{
				source = pluginSource;
			}
		}

		public static void Debug(string message)
		{
			Source.LogDebug((object)message);
		}

		public static void Info(string message)
		{
			Source.LogInfo((object)message);
		}

		public static void Warn(string message)
		{
			Source.LogWarning((object)message);
		}

		public static void Error(string message)
		{
			Source.LogError((object)message);
		}
	}
	public static class PluginPaths
	{
		public static readonly string Root = Directory.GetParent(Assembly.GetExecutingAssembly().Location)?.ToString();
	}
	public static class SaveIO
	{
		public static string SaveDirectory => Path.Combine(Application.persistentDataPath, "RoguelikeMode");

		public static void WriteAtomic(string path, string text, string prevPath)
		{
			string directoryName = Path.GetDirectoryName(path);
			if (!string.IsNullOrEmpty(directoryName))
			{
				Directory.CreateDirectory(directoryName);
			}
			string text2 = path + ".tmp";
			if (File.Exists(text2))
			{
				File.Delete(text2);
			}
			using (FileStream fileStream = new FileStream(text2, FileMode.Create, FileAccess.Write, FileShare.None))
			{
				using StreamWriter streamWriter = new StreamWriter(fileStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
				streamWriter.Write(text);
				streamWriter.Flush();
				fileStream.Flush(flushToDisk: true);
			}
			if (!File.Exists(path))
			{
				File.Move(text2, path);
				return;
			}
			try
			{
				File.Replace(text2, path, prevPath);
			}
			catch (Exception)
			{
				if (!string.IsNullOrEmpty(prevPath))
				{
					File.Copy(path, prevPath, overwrite: true);
				}
				File.Delete(path);
				File.Move(text2, path);
			}
		}

		public static string Quarantine(string path, string stem)
		{
			try
			{
				if (!File.Exists(path))
				{
					return null;
				}
				string text = Path.Combine(Path.GetDirectoryName(path) ?? "", stem + ".bad." + DateTime.Now.ToString("yyyyMMdd-HHmmss") + ".json");
				if (File.Exists(text))
				{
					File.Delete(text);
				}
				File.Move(path, text);
				return text;
			}
			catch (Exception ex)
			{
				Log.Warn("Could not quarantine " + path + ": " + ex.Message);
				return null;
			}
		}

		public static void Prune(string dir, string pattern, int keep)
		{
			try
			{
				if (Directory.Exists(dir))
				{
					List<FileInfo> list = (from f in new DirectoryInfo(dir).GetFiles(pattern)
						orderby f.LastWriteTimeUtc descending
						select f).ToList();
					for (int num = keep; num < list.Count; num++)
					{
						list[num].Delete();
					}
				}
			}
			catch (Exception ex)
			{
				Log.Warn("Could not prune " + dir + ": " + ex.Message);
			}
		}
	}
	public static class VanillaLookup
	{
		private static readonly string[] sanitizationFilter;

		private static readonly Dictionary<string, string> language;

		private static LandfallContentDatabase Database => ContentDatabase.Instance().LandfallContentDatabase;

		static VanillaLookup()
		{
			sanitizationFilter = new string[33]
			{
				"Icon_Legacy_", "Icons_128x128_", "Icons_", "Icon_", "CP_", "P_", "E_", " Prefabs_VB", " Effects_VB", "_1",
				"_2", "_3", " (1)", " (2)", " (3)", " (4)", " (5)", " (6)", "B_", "Move_",
				"Leg_", "1_", "12_", "15_", "6__", "7_", "14_", "2_", "8_", "10_",
				"16_", "5_", "3_"
			};
			language = new Dictionary<string, string>();
			try
			{
				language = new Dictionary<string, string>(Localizer.GetLanguage((Language)0));
				List<string> source = (from z in (from z in language
						group z by z.Value into z
						where z.Count() > 1
						select z).SelectMany((IGrouping<string, KeyValuePair<string, string>> z) => z)
					select z.Key).ToList();
				Dictionary<string, int> dictionary = new Dictionary<string, int>();
				foreach (string item in source.Where((string v) => language[v] != null && language[v] != string.Empty))
				{
					if (!dictionary.ContainsKey(language[item]))
					{
						dictionary.Add(language[item], 1);
						continue;
					}
					dictionary[language[item]]++;
					language[item] = language[item] + " " + dictionary[language[item]];
				}
			}
			catch (Exception ex)
			{
				Log.Warn("English localisation table unavailable for name lookups: " + ex);
			}
		}

		public static T FindVanillaObject<T>(IEnumerable<T> source, string name, Func<T, string> getName)
		{
			return source.FirstOrDefault((T o) => name == GetProperName(getName(o)));
		}

		public static string GetProperName(string name)
		{
			if (name == null)
			{
				return null;
			}
			string text;
			if (language.ContainsKey(name))
			{
				text = language[name];
			}
			else
			{
				text = name;
				string[] array = sanitizationFilter;
				foreach (string oldValue in array)
				{
					text = text.Replace(oldValue, "");
				}
			}
			return text;
		}

		public static GameObject GetWeapon(string name)
		{
			return FindVanillaObject(Database.GetWeapons(), name, (GameObject w) => ((CharacterItem)w.GetComponentInChildren<WeaponItem>()).Entity.Name);
		}

		public static GameObject GetProjectile(string name)
		{
			GameObject val = FindVanillaObject(Database.GetProjectiles(), name, (GameObject p) => p.GetComponent<ProjectileEntity>().Entity.Name);
			if (!((Object)(object)val != (Object)null))
			{
				return null;
			}
			return val.gameObject;
		}
	}
}
namespace RoguelikeMode.Roguelike
{
	public static class BattleLauncher
	{
		private static bool launchPending;

		private static TABSCampaignLevelAsset battleLevel;

		private static TABSCampaignAsset battleCampaign;

		public static void CompleteBattle()
		{
			RoguelikeSession current = RoguelikeState.Current;
			if (current == null)
			{
				return;
			}
			if (!string.IsNullOrEmpty(current.PendingLevelId))
			{
				current.PlayedLevelIds.Add(current.PendingLevelId);
				current.PendingLevelId = null;
			}
			if (TimeBonusTimer.Active)
			{
				float chancePercent = TimeBonusTimer.ChancePercent;
				float num = (float)new Random().NextDouble();
				bool flag = num * 100f < chancePercent;
				RoguelikeState.PendingTimeBonusRoll = new TimeBonusRoll
				{
					ChancePercent = chancePercent,
					Value01 = num,
					Success = flag
				};
				if (flag)
				{
					current.Events.Insert(current.CurrentIndex + 1, new FreeCardsEvent
					{
						CardCount = 1,
						TimeBonus = true
					});
				}
				Log.Debug("Time bonus roll " + (flag ? "won" : "lost") + " at " + chancePercent.ToString("0.#") + "% (rolled " + (num * 100f).ToString("0.#") + ").");
			}
			current.CurrentIndex++;
			RoguelikeFlow.EnsureOffers(current);
			SessionStore.Save(current);
		}

		public static void ResetLaunch()
		{
			launchPending = false;
		}

		private static void DestroyBattleClones()
		{
			if ((Object)(object)battleLevel != (Object)null)
			{
				Object.Destroy((Object)(object)battleLevel);
			}
			if ((Object)(object)battleCampaign != (Object)null)
			{
				Object.Destroy((Object)(object)battleCampaign);
			}
			battleLevel = null;
			battleCampaign = null;
		}

		public static void LaunchBattle()
		{
			if (!RoguelikeState.BattleActive && !launchPending && !((Object)(object)PendingBattleLevel() == (Object)null))
			{
				launchPending = true;
				TransitionCover.Raise(delegate
				{
					launchPending = false;
					StartBattle();
				});
			}
		}

		private static TABSCampaignLevelAsset PendingBattleLevel()
		{
			RoguelikeSession current = RoguelikeState.Current;
			if (current == null || !(current.CurrentEvent is BattleEvent))
			{
				Log.Warn("LaunchBattle called without a pending Battle event.");
				return null;
			}
			if (string.IsNullOrEmpty(current.PendingLevelId))
			{
				Log.Warn("No pending level id; cannot launch battle.");
				return null;
			}
			TABSCampaignLevelAsset level = CardPools.GetLevel(current.PendingLevelId);
			if ((Object)(object)level == (Object)null)
			{
				Log.Error("Pending level not found: " + current.PendingLevelId);
			}
			return level;
		}

		private static void StartBattle()
		{
			//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f8: Invalid comparison between Unknown and I4
			if (RoguelikeState.BattleActive)
			{
				return;
			}
			RoguelikeSession current = RoguelikeState.Current;
			TABSCampaignLevelAsset val = PendingBattleLevel();
			if ((Object)(object)val == (Object)null)
			{
				TransitionCover.DropSoon();
				return;
			}
			DestroyBattleClones();
			TABSCampaignLevelAsset val2 = Object.Instantiate<TABSCampaignLevelAsset>(val);
			int num = FactionBonusRules.GoldBudgetBonus(current);
			val2.m_budget = Mathf.Max(1, current.Money + num);
			UnitBlueprint[] array = (val2.AllowedUnits = (from kv in current.UnitCounts
				where kv.Value > 0
				select CardPools.GetUnit(kv.Key) into u
				where (Object)(object)u != (Object)null
				select u).ToArray());
			val2.AllowedFactions = FactionLookup.FactionsForUnits(array);
			TABSCampaignAsset val3 = ScriptableObject.CreateInstance<TABSCampaignAsset>();
			val3.SetData("Roguelike", (TABSCampaignLevelAsset[])(object)new TABSCampaignLevelAsset[1] { val2 });
			battleLevel = val2;
			battleCampaign = val3;
			RoguelikeState.CurrentLevelAsset = val2;
			RoguelikeState.CurrentLevelFactionId = FactionLookup.IdOf(FactionLookup.DominantFaction(val));
			RoguelikeState.BattleActive = true;
			CollectionStore.RecordLevel(current.PendingLevelId);
			CollectionStore.SaveIfDirty();
			RoguelikeState.LastResultWin = false;
			RoguelikeState.PendingTimeBonusRoll = null;
			Log.Info("Launching battle: " + val.Entity.Name + " budget=" + val2.m_budget + " (money=" + current.Money + " goldBonus=" + num + ") roster=" + array.Length + " factions=" + val2.AllowedFactions.Length);
			RoguelikeFlow.CloseAllScreens();
			CampaignPlayerDataHolder.StartedPlayingNewCampaign(val3, 0, false, false);
			GameStateManager service = ServiceLocator.GetService<GameStateManager>();
			if (service != null && (int)service.GameState != 2)
			{
				service.EnterNoneState();
			}
			TABSSceneManager.LoadCampaign();
		}
	}
	public static class CardChoices
	{
		private static readonly Color LockedFrameColor = Color.black;

		public const string CollectionBonusFactionName = "FACTION_SECRET";

		private const float ZeroGoldAlpha = 0.55f;

		public static CardChoice BuildUnitChoice(string id, UnitBlueprint unit, Dictionary<string, int> counts, bool includeVoice, bool counters = true)
		{
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)unit == (Object)null)
			{
				return new CardChoice
				{
					Label = id,
					Localized = false,
					DarkCard = true
				};
			}
			Faction val = FactionLookup.PrimaryFactionForUnit(unit);
			int value = 0;
			counts?.TryGetValue(id, out value);
			RunGrants.FactionUniqueCounts(val, counts, out var owned, out var total);
			bool milestone = value == 0 && owned >= 0 && total > 0 && RunGrants.UnclaimedCrossings(RoguelikeState.Current, FactionLookup.IdOf(val), owned, owned + 1, total).Count > 0;
			CardChoice cardChoice = new CardChoice
			{
				Label = unit.Entity.Name,
				Localized = true,
				Badge = CostRules.DisplayCost(unit).ToString(),
				Milestone = milestone,
				DarkCard = true,
				IconLoader = IconLoader(unit),
				Use3DCard = true,
				FactionColor = (((Object)(object)val != (Object)null) ? new Color?(val.m_FactionColor) : ((Color?)null)),
				FactionIconLoader = IconLoader(val),
				FactionLabel = RosterLabel(counters, owned, total),
				BottomLabel = (counters ? (value + " in deck") : null)
			};
			if (includeVoice)
			{
				cardChoice.OnPickStart = delegate
				{
					MenuAudio.PlayUnitVoice(unit);
				};
			}
			return cardChoice;
		}

		public static CardChoice BuildBonusChoice(FactionBonusDef def, Faction faction, Dictionary<string, int> counts, bool counters = true)
		{
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			if (def == null)
			{
				return new CardChoice
				{
					Label = "?",
					Localized = false,
					DarkCard = true
				};
			}
			RunGrants.FactionUniqueCounts(faction, counts, out var owned, out var total);
			Action<Action<Sprite>> action = IconLoader(faction);
			CardChoice obj = new CardChoice
			{
				Label = def.Title,
				Localized = false,
				Badge = null,
				Description = FactionBonusCatalog.Describe(def, FactionBonusRules.RosterCount(FactionLookup.IdOf(faction))),
				DarkCard = true,
				Use3DCard = true,
				MultilineLabel = false,
				FactionColor = (((Object)(object)faction != (Object)null) ? new Color?(faction.m_FactionColor) : ((Color?)null)),
				FactionIconLoader = action,
				IconLoader = action,
				FactionLabel = RosterLabel(counters, owned, total)
			};
			MouseGlyph.ApplyRmbHint(obj);
			return obj;
		}

		public static CardChoice BuildBonusChoice(FactionBonusPick pick)
		{
			Dictionary<string, int> counts = ((RoguelikeState.Current != null) ? RoguelikeState.Current.UnitCounts : null);
			return BuildBonusChoice(FactionBonusCatalog.ById(pick.BonusId), FactionLookup.Find(pick.FactionId), counts, counters: false);
		}

		private static Action<Action<Sprite>> QuestionLoader()
		{
			Sprite question = GlyphBackdrop.QuestionSprite;
			return delegate(Action<Sprite> cb)
			{
				if ((Object)(object)question != (Object)null)
				{
					cb(question);
				}
			};
		}

		private static Action<Action<Sprite>> IconLoader(Faction faction)
		{
			if ((Object)(object)faction == (Object)null)
			{
				return null;
			}
			return delegate(Action<Sprite> cb)
			{
				faction.Entity.GetSpriteIconAsync((Action<Sprite>)delegate(Sprite sprite)
				{
					cb(sprite);
				});
			};
		}

		private static Action<Action<Sprite>> IconLoader(UnitBlueprint unit)
		{
			if ((Object)(object)unit == (Object)null || unit.Entity == null)
			{
				return null;
			}
			return delegate(Action<Sprite> cb)
			{
				unit.Entity.GetSpriteIconAsync((Action<Sprite>)delegate(Sprite sprite)
				{
					cb(sprite);
				});
			};
		}

		private static Action<Action<Sprite>> LevelIconLoader(TABSCampaignLevelAsset level)
		{
			return delegate(Action<Sprite> cb)
			{
				Sprite levelCellSpriteIcon = level.Entity.LevelCellSpriteIcon;
				if ((Object)(object)levelCellSpriteIcon != (Object)null)
				{
					cb(levelCellSpriteIcon);
				}
				else
				{
					level.Entity.GetSpriteIconAsync((Action<Sprite>)delegate(Sprite s2)
					{
						cb(s2);
					});
				}
			};
		}

		private static string RosterLabel(bool counters, int owned, int total)
		{
			if (!counters || owned < 0 || total < 0)
			{
				return null;
			}
			return owned + " / " + total;
		}

		public static CardChoice BuildCollectionUnitChoice(string id, UnitBlueprint unit)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			bool num = CollectionStore.HasUnit(id);
			CardChoice cardChoice = BuildUnitChoice(id, unit, null, includeVoice: false, counters: false);
			if (num || (Object)(object)unit == (Object)null)
			{
				return cardChoice;
			}
			cardChoice.FactionColor = LockedFrameColor;
			cardChoice.IconLoader = QuestionLoader();
			cardChoice.Milestone = false;
			return cardChoice;
		}

		public static CardChoice BuildCollectionBonusChoice(FactionBonusDef def)
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			bool flag = CollectionStore.HasBonus(def.Id);
			Faction val = FactionLookup.PlayableFactions().FirstOrDefault((Func<Faction, bool>)((Faction f) => f.Entity.Name == "FACTION_SECRET"));
			Action<Action<Sprite>> action = IconLoader(val);
			CardChoice obj = new CardChoice
			{
				Label = def.Title,
				Localized = false,
				Description = FactionBonusCatalog.DescribeGeneric(def),
				DarkCard = true,
				Use3DCard = true,
				MultilineLabel = false,
				FactionColor = ((!flag) ? new Color?(LockedFrameColor) : (((Object)(object)val != (Object)null) ? new Color?(val.m_FactionColor) : ((Color?)null))),
				IconLoader = (flag ? action : QuestionLoader()),
				FactionIconLoader = action
			};
			MouseGlyph.ApplyRmbHint(obj);
			return obj;
		}

		public static CardChoice BuildDeckUnitChoice(string id, UnitBlueprint unit, bool sellable)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<string, int> counts = ((RoguelikeState.Current != null) ? RoguelikeState.Current.UnitCounts : null);
			CardChoice cardChoice = BuildUnitChoice(id, unit, counts, includeVoice: false, counters: false);
			if (sellable)
			{
				cardChoice.BottomLabel = "Sell? " + CoinHud.GainAmount("+" + RunEconomy.SellValue(RoguelikeState.Current, id));
				cardChoice.BottomLabelColor = null;
				cardChoice.BottomLabelIcon = CoinHud.CoinSprite;
				cardChoice.BottomLabelIconColor = CoinHud.IconColor;
			}
			return cardChoice;
		}

		public static CardChoice BuildCollectionLevelChoice(TABSCampaignLevelAsset level)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)level == (Object)null || level.Entity == null)
			{
				return new CardChoice
				{
					Label = "?",
					Localized = false,
					DarkCard = true
				};
			}
			bool num = CollectionStore.HasLevel(CardPools.IdToString(level.Entity.GUID));
			Faction val = FactionLookup.DominantFaction(level);
			CardChoice cardChoice = new CardChoice
			{
				Label = level.Entity.Name,
				Localized = true,
				Badge = LevelPool.ArmyValueOf(level).ToString(),
				DarkCard = true,
				Use3DCard = true,
				MultilineLabel = true,
				FactionColor = (((Object)(object)val != (Object)null) ? new Color?(val.m_FactionColor) : ((Color?)null)),
				FactionIconLoader = IconLoader(val)
			};
			if (num)
			{
				cardChoice.WideIcon = true;
				cardChoice.IconLoader = LevelIconLoader(level);
			}
			else
			{
				cardChoice.FactionColor = LockedFrameColor;
				cardChoice.IconLoader = QuestionLoader();
			}
			return cardChoice;
		}

		internal static CardChoice[] BuildUnitCards(SelectUnitEvent ev)
		{
			Dictionary<string, int> counts = ((RoguelikeState.Current != null) ? RoguelikeState.Current.UnitCounts : null);
			return ev.OfferedIds.Select((string id) => BuildUnitChoice(id, CardPools.GetUnit(id), counts, includeVoice: true)).ToArray();
		}

		public static ItemChoice BuildItemChoice(ItemDef def, bool locked = false)
		{
			//IL_0062: 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)
			if (def == null)
			{
				return new ItemChoice
				{
					Label = "?",
					Locked = true
				};
			}
			ItemChoice obj = new ItemChoice
			{
				Label = def.Name,
				Description = def.Description,
				Locked = locked,
				ModelEuler = def.ModelEuler,
				ModelPrefab = delegate
				{
					GameObject val = null;
					try
					{
						if (!string.IsNullOrEmpty(def.ModelBundlePath))
						{
							val = CardAssets.Load<GameObject>(def.ModelBundlePath);
						}
					}
					catch (Exception ex)
					{
						Log.Debug("Item model bundle asset '" + def.ModelBundlePath + "': " + ex.Message);
					}
					if ((Object)(object)val != (Object)null && (Object)(object)val.GetComponentInChildren<Renderer>(true) != (Object)null)
					{
						return val;
					}
					val = null;
					try
					{
						if (!string.IsNullOrEmpty(def.ModelWeapon))
						{
							val = VanillaLookup.GetWeapon(def.ModelWeapon);
						}
					}
					catch (Exception ex2)
					{
						Log.Debug("Item model weapon '" + def.ModelWeapon + "': " + ex2.Message);
					}
					if ((Object)(object)val == (Object)null || (Object)(object)val.GetComponentInChildren<Renderer>(true) == (Object)null)
					{
						try
						{
							if (!string.IsNullOrEmpty(def.ModelProjectile))
							{
								val = VanillaLookup.GetProjectile(def.ModelProjectile);
							}
						}
						catch (Exception ex3)
						{
							Log.Debug("Item model projectile '" + def.ModelProjectile + "': " + ex3.Message);
						}
					}
					return val;
				}
			};
			MouseGlyph.ApplyRmbHint(obj);
			return obj;
		}

		public static ItemChoice BuildCollectionItemChoice(ItemDef def)
		{
			return BuildItemChoice(def, def == null || !CollectionStore.HasItem(def.Id));
		}

		public static CardChoice BuildMysteryChoice()
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			return new CardChoice
			{
				Label = "",
				Localized = false,
				DarkCard = true,
				Use3DCard = true,
				FactionColor = LockedFrameColor,
				IconLoader = QuestionLoader()
			};
		}

		public static CardChoice BuildFreeCardChoice(FreeCardsEvent ev, int slot)
		{
			if (ev == null || ev.OfferedIds == null || slot < 0 || slot >= ev.OfferedIds.Count)
			{
				return null;
			}
			string id = ev.OfferedIds[slot];
			return BuildUnitChoice(counts: (RoguelikeState.Current != null) ? RoguelikeState.Current.UnitCounts : null, id: id, unit: CardPools.GetUnit(id), includeVoice: true);
		}

		internal static CardChoice[] BuildFreeCards(FreeCardsEvent ev)
		{
			CardChoice[] array = new CardChoice[(ev.OfferedIds != null) ? ev.OfferedIds.Count : 0];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = ((ev.RevealedSlots != null && ev.RevealedSlots.Contains(i)) ? BuildFreeCardChoice(ev, i) : BuildMysteryChoice());
			}
			return array;
		}

		public static ShopOffer[] BuildShopOffers(ShopEvent ev)
		{
			RoguelikeSession current = RoguelikeState.Current;
			Dictionary<string, int> counts = current?.UnitCounts;
			int num = current?.Money ?? 0;
			RunTuning t = RunTuning.Of(current);
			ShopOffer[] array = new ShopOffer[(ev.OfferedIds != null) ? ev.OfferedIds.Count : 0];
			for (int i = 0; i < array.Length; i++)
			{
				string offerId = ev.OfferedIds[i];
				ShopOffer shopOffer = new ShopOffer
				{
					Slot = i,
					OfferId = offerId,
					Price = Shop.PriceOf(t, offerId),
					Sold = (ev.SoldSlots != null && ev.SoldSlots.Contains(i))
				};
				if (Shop.IsItem(offerId))
				{
					shopOffer.Kind = ShopOfferKind.Item;
					shopOffer.Item = BuildItemChoice(ItemCatalog.ById(Shop.BareId(offerId)));
				}
				else
				{
					string id = Shop.BareId(offerId);
					shopOffer.Kind = ShopOfferKind.Card;
					shopOffer.Card = BuildUnitChoice(id, CardPools.GetUnit(id), counts, includeVoice: true);
				}
				shopOffer.Affordable = !shopOffer.Sold && num >= shopOffer.Price;
				array[i] = shopOffer;
			}
			return array;
		}

		internal static CardChoice[] BuildBonusCards(SelectFactionBonusEvent ev, Faction faction)
		{
			Dictionary<string, int> counts = ((RoguelikeState.Current != null) ? RoguelikeState.Current.UnitCounts : null);
			return ev.OfferedIds.Select((string id) => BuildBonusChoice(FactionBonusCatalog.ById(id), faction, counts)).ToArray();
		}

		internal static CardChoice[] BuildLevelCards(SelectLevelEvent ev)
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: 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_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: 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_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: 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_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: Unknown result type (might be due to invalid IL or missing references)
			CardChoice[] array = new CardChoice[ev.OfferedIds.Count];
			for (int i = 0; i < array.Length; i++)
			{
				string text = ev.OfferedIds[i];
				TABSCampaignLevelAsset level = CardPools.GetLevel(text);
				if ((Object)(object)level == (Object)null)
				{
					array[i] = new CardChoice
					{
						Label = text,
						Localized = false
					};
					continue;
				}
				Faction val = FactionLookup.DominantFaction(level);
				int num = RunEconomy.LevelPickGold(RoguelikeState.Current, level);
				Color gainColor = CoinHud.GainColor;
				Color value = (Color)((num > 0) ? gainColor : new Color(gainColor.r, gainColor.g, gainColor.b, 0.55f));
				array[i] = new CardChoice
				{
					Label = level.Entity.Name,
					Localized = true,
					Badge = LevelPool.ArmyValueOf(level).ToString(),
					CostLabel = "+" + num,
					CostLabelColor = value,
					CostLabelIcon = CoinHud.CoinSprite,
					CostLabelIconColor = CoinHud.IconColor,
					Badge2 = "+" + num,
					Badge2Color = value,
					FactionLabel = null,
					DarkCard = true,
					Use3DCard = true,
					WideIcon = true,
					MultilineLabel = true,
					FactionColor = (((Object)(object)val != (Object)null) ? new Color?(val.m_FactionColor) : ((Color?)null)),
					FactionIconLoader = IconLoader(val),
					IconLoader = LevelIconLoader(level),
					BackLines = LevelBackLines(level)
				};
				MouseGlyph.ApplyRmbHint(array[i], "Details");
			}
			return array;
		}

		private static List<CardBackLine> LevelBackLines(TABSCampaignLevelAsset level)
		{
			List<CardBackLine> list = new List<CardBackLine>();
			list.Add(new CardBackLine
			{
				Text = "Difficulty: " + LevelPool.DifficultyPercentOf(level) + "%",
				Header = true
			});
			list.Add(CardBackLine.Spacer());
			AddArmyLines(list, "Enemy Army:", LevelPool.LevelRoster(level, enemy: true));
			List<LevelPool.RosterEntry> list2 = LevelPool.LevelRoster(level, enemy: false);
			if (list2.Count > 0)
			{
				list.Add(CardBackLine.Spacer());
				AddArmyLines(list, "Player Army:", list2);
			}
			return list;
		}

		private static void AddArmyLines(List<CardBackLine> lines, string header, List<LevelPool.RosterEntry> roster)
		{
			//IL_0053: 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)
			lines.Add(new CardBackLine
			{
				Text = header,
				Header = true
			});
			lines.Add(new CardBackLine
			{
				Text = roster.Sum((LevelPool.RosterEntry e) => e.TotalCost).ToString(),
				Color = CoinHud.InfoColor,
				Icon = CoinHud.CoinSprite,
				IconColor = CoinHud.IconColor
			});
			foreach (LevelPool.RosterEntry item in roster)
			{
				UnitBlueprint unit = item.Unit;
				string text = ((unit.Entity != null) ? CardGrid.SafeLocalize(unit.Entity.Name) : ((Object)unit).name);
				lines.Add(new CardBackLine
				{
					Text = text + " x " + item.Count,
					IconLoader = IconLoader(unit)
				});
			}
		}
	}
	public static class CardPools
	{
		private static readonly HashSet<string> SpecialFactions = new HashSet<string> { "FACTION_SECRET", "FACTION_LEGACY", "FACTION_FANTASYGOOD", "FACTION_FANTASYEVIL" };

		public const float DefaultSpecialRarity = 0.25f;

		private static Dictionary<DatabaseID, UnitBlueprint> rosterIndex;

		private static readonly HashSet<DatabaseID> rosterMisses = new HashSet<DatabaseID>();

		private static bool rarityFallbackWarned;

		private const double UnitWeightScale = 1000.0;

		private const uint UnitWeightMinCost = 50u;

		private const double UnitWeightExponent = 0.75;

		public static bool IsSpecialFaction(Faction faction)
		{
			if ((Object)(object)faction != (Object)null && faction.Entity != null && faction.Entity.Name != null)
			{
				return SpecialFactions.Contains(faction.Entity.Name);
			}
			return false;
		}

		public static float DefaultFactionRarity(Faction faction)
		{
			if (!IsSpecialFaction(faction))
			{
				return 1f;
			}
			return 0.25f;
		}

		public static string IdToString(DatabaseID id)
		{
			return id.m_modID + ":" + id.m_ID;
		}

		public static bool TryParseId(string id, out DatabaseID result)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			result = default(DatabaseID);
			if (string.IsNullOrEmpty(id))
			{
				return false;
			}
			string[] array = id.Split(new char[1] { ':' });
			if (array.Length != 2 || !int.TryParse(array[0], out var result2) || !int.TryParse(array[1], out var result3))
			{
				return false;
			}
			result = new DatabaseID(result2, result3);
			return true;
		}

		public static UnitBlueprint GetUnit(DatabaseID guid)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return GetUnit(IdToString(guid));
		}

		public static UnitBlueprint GetUnit(string id)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			if (!TryParseId(id, out var result))
			{
				return null;
			}
			UnitBlueprint unitBlueprint = ContentDatabase.Instance().GetUnitBlueprint(result);
			if ((Object)(object)unitBlueprint != (Object)null)
			{
				return unitBlueprint;
			}
			return GetUnitFromRosters(result);
		}

		private static UnitBlueprint GetUnitFromRosters(DatabaseID guid)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: 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)
			if (rosterIndex != null && rosterIndex.TryGetValue(guid, out var value))
			{
				return value;
			}
			if (rosterMisses.Contains(guid))
			{
				return null;
			}
			rosterIndex = new Dictionary<DatabaseID, UnitBlueprint>();
			foreach (Faction faction in ContentDatabase.Instance().LandfallContentDatabase.GetFactions())
			{
				if ((Object)(object)faction == (Object)null || faction.Units == null)
				{
					continue;
				}
				UnitBlueprint[] units = faction.Units;
				foreach (UnitBlueprint val in units)
				{
					if ((Object)(object)val != (Object)null && val.Entity != null && !rosterIndex.ContainsKey(val.Entity.GUID))
					{
						rosterIndex[val.Entity.GUID] = val;
					}
				}
			}
			if (rosterIndex.TryGetValue(guid, out value))
			{
				return value;
			}
			rosterMisses.Add(guid);
			return null;
		}

		public static void InvalidateCaches()
		{
			rosterIndex = null;
			rosterMisses.Clear();
			rarityFallbackWarned = false;
		}

		public static TABSCampaignLevelAsset GetLevel(string id)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			if (!TryParseId(id, out var result))
			{
				return null;
			}
			return ContentDatabase.Instance().LandfallContentDatabase.GetCampaignLevel(result);
		}

		internal static IEnumerable<UnitBlueprint> EligibleUnits(Faction faction, ISaveLoaderService save, HashSet<DatabaseID> seen)
		{
			if ((Object)(object)faction == (Object)null || faction.Units == null)
			{
				yield break;
			}
			UnitBlueprint[] units = faction.Units;
			foreach (UnitBlueprint val in units)
			{
				if (!((Object)(object)val == (Object)null) && val.Entity != null && !seen.Contains(val.Entity.GUID))
				{
					seen.Add(val.Entity.GUID);
					if (val.GetUnitCost(true) != 0 && (!val.IsSecret || save == null || save.HasUnlockedSecret(val.Entity.UnlockKey)))
					{
						yield return val;
					}
				}
			}
		}

		public static List<KeyValuePair<UnitBlueprint, float>> WeightedUnitPool()
		{
			RunTuning tuning = RunTuning.Of(RoguelikeState.Current);
			List<KeyValuePair<UnitBlueprint, float>> list = BuildUnitPool(tuning, applyRarity: true);
			if (list.Count > 0)
			{
				return list;
			}
			List<KeyValuePair<UnitBlueprint, float>> list2 = BuildUnitPool(tuning, applyRarity: false);
			if (list2.Count == 0)
			{
				return list;
			}
			if (!rarityFallbackWarned)
			{
				rarityFallbackWarned = true;
				Log.Warn("Faction rarities exclude every unit; drawing with the default rarities instead.");
			}
			return list2;
		}

		private static List<KeyValuePair<UnitBlueprint, float>> BuildUnitPool(RunTuning tuning, bool applyRarity)
		{
			ISaveLoaderService service = ServiceLocator.GetService<ISaveLoaderService>();
			HashSet<DatabaseID> seen = new HashSet<DatabaseID>();
			List<KeyValuePair<UnitBlueprint, float>> list = new List<KeyValuePair<UnitBlueprint, float>>();
			foreach (Faction item in FactionLookup.PlayableFactions())
			{
				foreach (UnitBlueprint item2 in EligibleUnits(item, service, seen))
				{
					float num = (float)Math.Pow(1000.0 / (double)Math.Max(50u, item2.GetUnitCost(true)), 0.75);
					Faction faction = FactionLookup.PrimaryFactionForUnit(item2);
					string factionId = FactionLookup.IdOf(faction);
					num = ((!applyRarity) ? (num * DefaultFactionRarity(faction)) : (num * tuning.FactionRarityOf(factionId, DefaultFactionRarity(faction))));
					num *= FactionBonusRules.LuckWeightMultiplier(RoguelikeState.Current, factionId);
					if (!(num <= 0f))
					{
						list.Add(new KeyValuePair<UnitBlueprint, float>(item2, num));
					}
				}
			}
			return list;
		}

		public static List<UnitBlueprint> DrawUnits(Random rand, int count, ICollection<string> exclude = null)
		{
			List<KeyValuePair<UnitBlueprint, float>> list = WeightedUnitPool();
			if (exclude != null && exclude.Count > 0)
			{
				list.RemoveAll((KeyValuePair<UnitBlueprint, float> p) => exclude.Contains(IdToString(p.Key.Entity.GUID)));
			}
			List<UnitBlueprint> list2 = new List<UnitBlueprint>();
			for (int num = 0; num < count; num++)
			{
				if (list.Count <= 0)
				{
					break;
				}
				float num2 = list.Sum((KeyValuePair<UnitBlueprint, float> p) => p.Value);
				double num3 = rand.NextDouble() * (double)num2;
				int index = list.Count - 1;
				double num4 = 0.0;
				for (int num5 = 0; num5 < list.Count; num5++)
				{
					num4 += (double)list[num5].Value;
					if (num3 <= num4)
					{
						index = num5;
						break;
					}
				}
				list2.Add(list[index].Key);
				list.RemoveAt(index);
			}
			return list2;
		}
	}
	[Serializable]
	public class CollectionData
	{
		public const int CurrentVersion = 1;

		public int Version = 1;

		public List<string> Units = new List<string>();

		public List<string> Bonuses = new List<string>();

		public List<string> Levels = new List<string>();

		public List<string> Items = new List<string>();

		public int Wins;

		public int CrownUnlocked;

		public int CrownLevel;

		public void Normalize()
		{
			if (Units == null)
			{
				Units = new List<string>();
			}
			if (Bonuses == null)
			{
				Bonuses = new List<string>();
			}
			if (Levels == null)
			{
				Levels = new List<string>();
			}
			if (Items == null)
			{
				Items = new List<string>();
			}
			Version = 1;
		}
	}
	public static class CollectionStore
	{
		private static readonly JsonStore<CollectionData> store = new JsonStore<CollectionData>("collection", "Collection", "starting empty", 1, delegate(CollectionData d)
		{
			d.Normalize();
			return (string)null;
		});

		private static CollectionData data;

		public static string SavePath => store.SavePath;

		public static CollectionData Data
		{
			get
			{
				if (data == null)
				{
					data = store.Load() ?? new CollectionData();
				}
				return data;
			}
		}

		public static int Wins => Mathf.Max(0, Data.Wins);

		public static int CrownUnlocked => Mathf.Max(0, Data.CrownUnlocked);

		public static int CrownLevel => Mathf.Clamp(Data.CrownLevel, 0, CrownUnlocked);

		public static bool HasUnit(string unitId)
		{
			if (!string.IsNullOrEmpty(unitId))
			{
				return Data.Units.Contains(unitId);
			}
			return false;
		}

		public static bool HasBonus(string bonusId)
		{
			if (!string.IsNullOrEmpty(bonusId))
			{
				return Data.Bonuses.Contains(bonusId);
			}
			return false;
		}

		public static bool HasLevel(string levelId)
		{
			if (!string.IsNullOrEmpty(levelId))
			{
				return Data.Levels.Contains(levelId);
			}
			return false;
		}

		public static bool HasItem(string itemId)
		{
			if (!string.IsNullOrEmpty(itemId))
			{
				return Data.Items.Contains(itemId);
			}
			return false;
		}

		public static void RecordUnit(string unitId)
		{
			Record(Data.Units, unitId);
		}

		public static void RecordBonus(string bonusId)
		{
			Record(Data.Bonuses, bonusId);
		}

		public static void RecordLevel(string levelId)
		{
			Record(Data.Levels, levelId);
		}

		public static void RecordItem(string itemId)
		{
			Record(Data.Items, itemId);
		}

		public static void RecordWin(int crownLevel)
		{
			Data.Wins = Wins + 1;
			Data.CrownUnlocked = Mathf.Max(CrownUnlocked, Mathf.Max(0, crownLevel) + 1);
			Save();
		}

		public static int SetCrownLevel(int level)
		{
			Data.CrownLevel = Mathf.Clamp(level, 0, CrownUnlocked);
			Save();
			return Data.CrownLevel;
		}

		private static void Record(List<string> list, string id)
		{
			if (!string.IsNullOrEmpty(id) && !list.Contains(id))
			{
				list.Add(id);
				store.MarkDirty();
			}
		}

		public static void SaveIfDirty()
		{
			store.SaveIfDirty(data);
		}

		public static void Save()
		{
			store.Save(Data);
		}
	}
	public abstract class ConfigEntry
	{
		public string Id;

		public string Category;

		public string Section;

		public string Name;

		public abstract bool IsDefault(ConfigData d);

		public abstract void Reset(ConfigData d);

		public abstract string Display(ConfigData d);

		public abstract bool TryApplyText(ConfigData d, string text);

		public abstract void Sanitize(ConfigData d);

		public virtual void Snapshot(ConfigData d, RunTuning t)
		{
		}
	}
	public sealed class IntConfigEntry : ConfigEntry
	{
		public int Default;

		public int Min;

		public int Max = 1000000;

		public Func<ConfigData, int> Get;

		public Action<ConfigData, int> Set;

		public Action<RunTuning, int> Tune;

		public int Clamp(int value)
		{
			return Mathf.Clamp(value, Min, Max);
		}

		public override void Snapshot(ConfigData d, RunTuning t)
		{
			if (Tune != null)
			{
				Tune(t, Clamp(Get(d)));
			}
		}

		public override bool IsDefault(ConfigData d)
		{
			return Get(d) == Default;
		}

		public override void Reset(ConfigData d)
		{
			Set(d, Default);
		}

		public override string Display(ConfigData d)
		{
			return Get(d).ToString(CultureInfo.InvariantCulture);
		}

		public override bool TryApplyText(ConfigData d, string text)
		{
			if (text == null || !int.TryParse(text.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
			{
				return false;
			}
			Set(d, Clamp(result));
			return true;
		}

		public void Apply(ConfigData d, int value)
		{
			Set(d, Clamp(value));
		}

		public override void Sanitize(ConfigData d)
		{
			Set(d, Clamp(Get(d)));
		}
	}
	public sealed class FloatConfigEntry : ConfigEntry
	{
		public float Default;

		public float Min;

		public float Max = 1f;

		public int Decimals = 3;

		public bool Percent;

		public Func<ConfigData, float> Get;

		public Action<ConfigData, float> Set;

		public Action<RunTuning, float> Tune;

		public float Clamp(float value)
		{
			return Mathf.Clamp(value, Min, Max);
		}

		public override void Snapshot(ConfigData d, RunTuning t)
		{
			if (Tune != null)
			{
				Tune(t, Clamp(Get(d)));
			}
		}

		public float RoundToDecimals(float value)
		{
			float num = Mathf.Pow(10f, (float)Decimals);
			return Mathf.Round(value * num) / num;
		}

		public bool Matches(float a, float b)
		{
			return Mathf.Abs(a - b) < 0.5f * Mathf.Pow(10f, (float)(-Decimals));
		}

		public string Format(float value)
		{
			if (!Percent)
			{
				return value.ToString("0." + new string('#', Mathf.Max(0, Decimals)), CultureInfo.InvariantCulture);
			}
			return (value * 100f).ToString("0." + new string('#', Mathf.Max(0, Decimals - 2)), CultureInfo.InvariantCulture) + "%";
		}

		public override bool IsDefault(ConfigData d)
		{
			return Matches(Get(d), Default);
		}

		public override void Reset(ConfigData d)
		{
			Set(d, Default);
		}

		public override string Display(ConfigData d)
		{
			return Format(Get(d));
		}

		public override bool TryApplyText(ConfigData d, string text)
		{
			if (text == null)
			{
				return false;
			}
			string text2 = text.Trim().Replace(',', '.');
			if (Percent && text2.EndsWith("%"))
			{
				text2 = text2.Substring(0, text2.Length - 1).TrimEnd(Array.Empty<char>());
			}
			if (!float.TryParse(text2, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				return false;
			}
			if (float.IsNaN(result) || float.IsInfinity(result))
			{
				return false;
			}
			if (Percent)
			{
				result /= 100f;
			}
			Set(d, Clamp(RoundToDecimals(result)));
			return true;
		}

		public override void Sanitize(ConfigData d)
		{
			Set(d, Clamp(Get(d)));
		}
	}
	public sealed class ColorConfigEntry : ConfigEntry
	{
		public string Default;

		public Func<ConfigData, string> Get;

		public Action<ConfigData, string> Set;

		public static bool TryParse(string text, out Color color, out string normalized)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: 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)
			color = Color.white;
			normalized = null;
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			string text2 = text.Trim();
			if (!text2.StartsWith("#"))
			{
				text2 = "#" + text2;
			}
			if (!ColorUtility.TryParseHtmlString(text2, ref color))
			{
				return false;
			}
			color.a = 1f;
			normalized = "#" + ColorUtility.ToHtmlStringRGB(color);
			return true;
		}

		public Color ColorOf(ConfigData d)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			if (d != null && TryParse(Get(d), out var color, out var normalized))
			{
				return color;
			}
			TryParse(Default, out color, out normalized);
			return color;
		}

		public override bool IsDefault(ConfigData d)
		{
			if (TryParse(Get(d), out var _, out var normalized))
			{
				return string.Equals(normalized, Default, StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		public override void Reset(ConfigData d)
		{
			Set(d, Default);
		}

		public override string Display(ConfigData d)
		{
			return Get(d) ?? Default;
		}

		public override bool TryApplyText(ConfigData d, string text)
		{
			if (!TryParse(text, out var _, out var normalized))
			{
				return false;
			}
			Set(d, normalized);
			return true;
		}

		public override void Sanitize(ConfigData d)
		{
			Set(d, TryParse(Get(d), out var _, out var normalized) ? normalized : Default);
		}
	}
	public class ConfigCategory
	{
		public string Id;

		public string Name;
	}
	public class ConfigSection
	{
		public string Id;

		public string Category;

		public string Name;

		public bool StartsExpanded;
	}
	public static class ConfigCatalog
	{
		public const string General = "general";

		public const string StartingItems = "starting_items";

		public const string StartingUnits = "starting_units";

		public const string StartingBonuses = "starting_bonuses";

		public const string EventOrder = "event_order";

		public const string ShaderPreview = "shader_preview";

		public const string Appearance = "appearance";

		public static readonly ConfigCategory[] UserCategories = new ConfigCategory[4]
		{
			new ConfigCategory
			{
				Id = "general",
				Name = "General"
			},
			new ConfigCategory
			{
				Id = "starting_items",
				Name = "Starting Items"
			},
			new ConfigCategory
			{
				Id = "starting_units",
				Name = "Starting Units"
			},
			new ConfigCategory
			{
				Id = "starting_bonuses",
				Name = "Starting Bonuses"
			}
		};

		public static readonly ConfigCategory[] DevCategories = new ConfigCategory[0];

		public static readonly ConfigCategory[] Categories = UserCategories.Concat(DevCategories).ToArray();

		public const string Economy = "economy";

		public const string CardChoices = "card_choices";

		public const string ShopSection = "shop";

		public const string TimeBonus = "time_bonus";

		public const string FactionRarity = "faction_rarity";

		public const string ItemRarity = "item_rarity";

		public static readonly ConfigSection[] Sections = new ConfigSection[6]
		{
			new ConfigSection
			{
				Id = "economy",
				Category = "general",
				Name = "Economy",
				StartsExpanded = true
			},
			new ConfigSection
			{
				Id = "card_choices",
				Category = "general",
				Name = "Card Choices",
				StartsExpanded = true
			},
			new ConfigSection
			{
				Id = "shop",
				Category = "general",
				Name = "Shop",
				StartsExpanded = true
			},
			new ConfigSection
			{
				Id = "time_bonus",
				Category = "general",
				Name = "Time Bonus",
				StartsExpanded = true
			},
			new ConfigSection
			{
				Id = "faction_rarity",
				Category = "general",
				Name = "Faction Rarity"
			},
			new ConfigSection
			{
				Id = "item_rarity",
				Category = "general",
				Name = "Item Rarity"
			}
		};

		public const float MaxRarity = 100f;

		public const int MaxOptionCap = 12;

		public const int MaxGold = 1000000;

		public static readonly IntConfigEntry StartingBudget = new IntConfigEntry
		{
			Id = "starting_budget",
			Category = "general",
			Section = "economy",
			Name = "Starting Budget",
			Default = 1000,
			Min = 0,
			Max = 1000000,
			Get = (ConfigData d) => d.StartingBudget,
			Set = delegate(ConfigData d, int v)
			{
				d.StartingBudget = v;
			}
		};

		public static readonly IntConfigEntry ForfeitBonus = new IntConfigEntry
		{
			Id = "forfeit_bonus",
			Category = "general",
			Section = "economy",
			Name = "Forfeit Bonus",
			Default = 20,
			Min = 0,
			Max = 1000000,
			Get = (ConfigData d) => d.ForfeitBonus,
			Set = delegate(ConfigData d, int v)
			{
				d.ForfeitBonus = v;
			},
			Tune = delegate(RunTuning t, int v)
			{
				t.ForfeitBonus = v;
			}
		};

		public static readonly IntConfigEntry LevelRewardMinimum = new IntConfigEntry
		{
			Id = "level_reward_minimum",
			Category = "general",
			Section = "economy",
			Name = "Level Reward Minimum",
			Default = 50,
			Min = 0,
			Max = 1000000,
			Get = (ConfigData d) => d.LevelRewardMinimum,
			Set = delegate(ConfigData d, int v)
			{
				d.LevelRewardMinimum = v;
			},
			Tune = delegate(RunTuning t, int v)
			{
				t.LevelRewardMinimum = v;
			}
		};

		public static readonly FloatConfigEntry LevelRewardValue = new FloatConfigEntry
		{
			Id = "level_reward_value",
			Category = "general",
			Section = "economy",
			Name = "Level Reward Value",
			Default = 0.15f,
			Min = 0f,
			Max = 100f,
			Percent = true,
			Get = (ConfigData d) => d.LevelRewardValue,
			Set = delegate(ConfigData d, float v)
			{
				d.LevelRewardValue = v;
			},
			Tune = delegate(RunTuning t, float v)
			{
				t.LevelRewardValue = v;
			}
		};

		public static readonly IntConfigEntry RefreshBaseCost = new IntConfigEntry
		{
			Id = "refresh_base_cost",
			Category = "general",
			Section = "card_choices",
			Name = "Refresh Base Cost",
			Default = 10,
			Min = 0,
			Max = 1000000,
			Get = (ConfigData d) => d.RefreshBaseCost,
			Set = delegate(ConfigData d, int v)
			{
				d.RefreshBaseCost = v;
			},
			Tune = delegate(RunTuning t, int v)
			{
				t.RefreshBaseCost = v;
			}
		};

		public static readonly IntConfigEntry RefreshCostScaling = new IntConfigEntry
		{
			Id = "refresh_cost_scaling",
			Category = "general",
			Section = "card_choices",
			Name = "Refresh Cost Scaling",
			Default = 10,
			Min = 0,
			Max = 1000000,
			Get = (ConfigData d) => d.RefreshCostScaling,
			Set = delegate(ConfigData d, int v)
			{
				d.RefreshCostScaling = v;
			},
			Tune = delegate(RunTuning t, int v)
			{
				t.RefreshCostScaling = v;
			}
		};

		public static readonly IntConfigEntry BaseOptions = new IntConfigEntry
		{
			Id = "base_options",
			Category = "general",
			Section = "card_choices",
			Name = "Card Options",
			Default = 3,
			Min = 1,
			Max = 12,
			Get = (ConfigData d) => d.BaseOptions,
			Set = delegate(ConfigData d, int v)
			{
				d.BaseOptions = v;
			},
			Tune = delegate(RunTuning t, int v)
			{
				t.BaseOptions = v;
			}
		};

		public static readonly IntConfigEntry MaxOptions = new IntConfigEntry
		{
			Id = "max_options",
			Category = "general",
			Section = "card_choices",
			Name = "Max Card Options",
			Default = 6,
			Min = 1,
			Max = 12,
			Get = (ConfigData d) => d.MaxOptions,
			Set = delegate(ConfigData d, int v)
			{
				d.MaxOptions = v;
			},
			Tune = delegate(RunTuning t, int v)
			{
				t.MaxOptions = v;
			}
		};

		public static readonly IntConfigEntry TimeBonusCountdown = new IntConfigEntry
		{
			Id = "time_bonus_countdown",
			Category = "general",
			Section = "time_bonus",
			Name = "Time Bonus Countdown",
			Default = 10,
			Min = 1,
			Max = 3600,
			Get = (ConfigData d) => d.TimeBonusCountdown,
			Set = delegate(ConfigData d, int v)
			{
				d.TimeBonusCountdown = v;
			},
			Tune = delegate(RunTuning t, int v)
			{
				t.TimeBonusCountdown = v;
			}
		};

		public static readonly FloatConfigEntry TimeBonusDecreaseRate = new FloatConfigEntry
		{
			Id = "time_bonus_decrease_rate",
			Category = "general",
			Section = "time_bonus",
			Name = "Time Bonus Decrease Rate",
			Default = 0.75f,
			Min = 0f,
			Max = 1f,
			Percent = true,
			Get = (ConfigData d) => d.TimeBonusDecreaseRate,
			Set = delegate(ConfigData d, float v)
			{
				d.TimeBonusDecreaseRate = v;
			},
			Tune = delegate(RunTuning t, float v)
			{
				t.TimeBonusDecreaseRate = v;
			}
		};

		public static readonly IntConfigEntry ShopItemCost = new IntConfigEntry
		{
			Id = "shop_item_cost",
			Category = "general",
			Section = "shop",
			Name = "Shop Item Cost",
			Default = 100,
			Min = 0,
			Max = 1000000,
			Get = (ConfigData d) => d.ShopItemCost,
			Set = delegate(ConfigData d, int v)
			{
				d.ShopItemCost = v;
			},
			Tune = delegate(RunTuning t, int v)
			{
				t.ShopItemCost = v;
			}
		};

		public static readonly FloatConfigEntry ShopItemRatio = new FloatConfigEntry
		{
			Id = "shop_item_ratio",
			Category = "general",
			Section = "shop",
			Name = "Shop Item Ratio",
			Default = 1f / 3f,
			Min = 0f,
			Max = 1f,
			Percent = true,
			Get = (ConfigData d) => d.ShopItemRatio,
			Set = delegate(ConfigData d, float v)
			{
				d.ShopItemRatio = v;
			},
			Tune = delegate(RunTuning t, float v)
			{
				t.ShopItemRatio = v;
			}
		};

		public static readonly FloatConfigEntry ShopUnitCost = new FloatConfigEntry
		{
			Id = "shop_unit_cost",
			Category = "general",
			Section = "shop",
			Name = "Shop Unit Cost",
			Default = 0.2f,
			Min = 0f,
			Max = 100f,
			Percent = true,
			Get = (ConfigData d) => d.ShopUnitCost,
			Set = delegate(ConfigData d, float v)
			{
				d.ShopUnitCost = v;
			},
			Tune = delegate(RunTuning t, float v)
			{
				t.ShopUnitCost = v;
			}
		};

		public static readonly FloatConfigEntry CardSellValue = new FloatConfigEntry
		{
			Id = "card_sell_value",
			Category = "general",
			Section = "economy",
			Name = "Card Sell Value",
			Default = 0f,
			Min = 0f,
			Max = 100f,
			Percent = true,
			Get = (ConfigData d) => d.CardSellValue,
			Set = delegate(ConfigData d, float v)
			{
				d.CardSellValue = v;
			},
			Tune = delegate(RunTuning t, float v)
			{
				t.CardSellValue = v;
			}
		};

		public static readonly IntConfigEntry MinimumSellValue = new IntConfigEntry
		{
			Id = "minimum_sell_value",
			Category = "general",
			Section = "economy",
			Name = "Minimum Sell Value",
			Default = 10,
			Min = 0,
			Max = 1000000,
			Get = (ConfigData d) => d.MinimumSellValue,
			Set = delegate(ConfigData d, int v)
			{
				d.MinimumSellValue = v;
			},
			Tune = delegate(RunTuning t, int v)
			{
				t.MinimumSellValue = v;
			}
		};

		public static readonly ColorConfigEntry CustomRunColor = new ColorConfigEntry
		{
			Id = "custom_run_color",
			Category = "appearance",
			Name = "Custom Run Color",
			Default = "#ECC700",
			Get = (ConfigData d) => d.CustomRunColor,
			Set = delegate(ConfigData d, string v)
			{
				d.CustomRunColor = v;
			}
		};

		public const string ItemEntryPrefix = "item_";

		public const int MaxStartingCopies = 99;

		public const int MaxStartingBonusCopies = 9;

		public static readonly IntConfigEntry[] StartingItemEntries = BuildStartingItemEntries();

		public const string ItemRarityEntryPrefix = "item_rarity_";

		public static readonly FloatConfigEntry[] ItemRarityEntries = BuildItemRarityEntries();

		public static readonly ConfigEntry[] All = BuildAll();

		public const string UnitEntryPrefix = "unit_";

		private static readonly Dictionary<string, IntConfigEntry> unitEntries = new Dictionary<string, IntConfigEntry>();

		public const string FactionRarityEntryPrefix = "faction_rarity_";

		private static readonly Dictionary<string, FloatConfigEntry> factionRarityEntries = new Dictionary<string, FloatConfigEntry>();

		public const string BonusEntryPrefix = "bonus_";

		private static readonly Dictionary<string, IntConfigEntry> bonusEntries = new Dictionary<string, IntConfigEntry>();

		public static bool IsDevCategory(string id)
		{
			ConfigCategory[] devCategories = DevCategories;
			for (int i = 0; i < devCategories.Length; i++)
			{
				if (devCategories[i].Id == id)
				{
					return true;
				}
			}
			return false;
		}

		private static IntConfigEntry[] BuildStartingItemEntries()
		{
			IList<ItemDef> all = ItemCatalog.All;
			IntConfigEntry[] array = new IntConfigEntry[all.Count];
			for (int i = 0; i < all.Count; i++)
			{
				string itemId = all[i].Id;
				array[i] = new IntConfigEntry
				{
					Id = "item_" + itemId,
					Category = "starting_items",
					Name = all[i].Name,
					Default = 0,
					Min = 0,
					Max = 99,
					Get = (ConfigData d) => d.StartingItemCount(itemId),
					Set = delegate(ConfigData d, int v)
					{
						d.SetStartingItemCount(itemId, v);
					}
				};
			}
			return array;
		}

		private static FloatConfigEntry[] BuildItemRarityEntries()
		{
			IList<ItemDef> all = ItemCatalog.All;
			FloatConfigEntry[] array = new FloatConfigEntry[all.Count];
			for (int i = 0; i < all.Count; i++)
			{
				string itemId = all[i].Id;
				FloatConfigEntry entry = null;
				entry = new FloatConfigEntry
				{
					Id = "item_rarity_" + itemId,
					Category = "general",
					Section = "item_rarity",
					Name = all[i].Name,
					Default = 1f,
					Min = 0f,
					Max = 100f,
					Get = (ConfigData d) => d.ItemRarityOf(itemId),
					Set = delegate(ConfigData d, float v)
					{
						d.SetItemRarity(itemId, v, entry.Matches(v, entry.Default));
					}
				};
				array[i] = entry;
			}
			return array;
		}

		private static ConfigEntry[] BuildAll()
		{
			List<ConfigEntry> list = new List<ConfigEntry>();
			list.Add(StartingBudget);
			list.Add(ForfeitBonus);
			list.Add(LevelRewardMinimum);
			list.Add(LevelRewardValue);
			list.Add(CardSellValue);
			list.Add(MinimumSellValue);
			list.Add(BaseOptions);
			list.Add(MaxOptions);
			list.Add(RefreshBaseCost);
			list.Add(RefreshCostScaling);
			list.Add(ShopItemCost);
			list.Add(ShopUnitCost);
			list.Add(ShopItemRatio);
			list.Add(TimeBonusCountdown);
			list.Add(TimeBonusDecreaseRate);
			list.AddRange(StartingItemEntries);
			list.AddRange(ItemRarityEntries);
			return list.ToArray();
		}

		public static string ItemIdOf(ConfigEntry entry)
		{
			if (entry == null || !(entry.Category == "starting_items"))
			{
				return null;
			}
			return StripPrefix(entry.Id, "item_");
		}

		public static string RarityItemIdOf(ConfigEntry entry)
		{
			if (entry == null || !(entry.Section == "item_rarity"))
			{
				return null;
			}
			return StripPrefix(entry.Id, "item_rarity_");
		}

		public static IntConfigEntry UnitEntry(string unitId)
		{
			if (string.IsNullOrEmpty(unitId))
			{
				return null;
			}
			if (unitEntries.TryGetValue(unitId, out var value))
			{
				return value;
			}
			value = new IntConfigEntry
			{
				Id = "unit_" + unitId,
				Category = "starting_units",
				Name = unitId,
				Default = 0,
				Min = 0,
				Max = 99,
				Get = (ConfigData d) => d.StartingUnitCount(unitId),
				Set = delegate(ConfigData d, int v)
				{
					d.SetStartingUnitCount(unitId, v);
				}
			};
			unitEntries[unitId] = value;
			return value;
		}

		public static string UnitIdOf(ConfigEntry entry)
		{
			if (entry == null || !(entry.Category == "starting_units"))
			{
				return null;
			}
			return UnitIdOfEntryId(entry.Id);
		}

		private static string UnitIdOfEntryId(string entryId)
		{
			return StripPrefix(entryId, "unit_");
		}

		public static List<IntConfigEntry> StoredUnitEntries(ConfigData d)
		{
			List<IntConfigEntry> list = new List<IntConfigEntry>();
			if (d == null || d.StartingUnits == null)
			{
				return list;
			}
			foreach (string key in d.StartingUnits.Keys)
			{
				IntConfigEntry intConfigEntry = UnitEntry(key);
				if (intConfigEntry != null)
				{
					list.Add(intConfigEntry);
				}
			}
			return list;
		}

		public static FloatConfigEntry FactionRarityEntry(string factionId)
		{
			if (string.IsNullOrEmpty(factionId))
			{
				return null;
			}
			if (factionRarityEntries.TryGetValue(factionId, out var value))
			{
				return value;
			}
			Faction val = null;
			try
			{
				val = FactionLookup.Find(factionId);
			}
			catch (Exception)
			{
			}
			string id = factionId;
			float fallback = (((Object)(object)val != (Object)null) ? CardPools.DefaultFactionRarity(val) : 1f);
			string name = (((Object)(object)val != (Object)null && val.Entity != null && !string.IsNullOrEmpty(val.Entity.Name)) ? val.Entity.Name : factionId);
			FloatConfigEntry entry = null;
			entry = new FloatConfigEntry
			{
				Id = "faction_rarity_" + id,
				Category = "general",
				Section = "faction_rarity",
				Name = name,
				Default = fallback,
				Min = 0f,
				Max = 100f,
				Get = (ConfigData d) => d.FactionRarityOf(id, fallback),
				Set = delegate(ConfigData d, float v)
				{
					d.SetFactionRarity(id, v, entry.Matches(v, fallback));
				}
			};
			if ((Object)(object)val != (Object)null)
			{
				factionRarityEntries[factionId] = entry;
			}
			return entry;
		}

		public static string FactionIdOf(ConfigEntry entry)
		{
			if (entry == null)
			{
				return null;
			}
			return StripPrefix(entry.Id, "faction_rarity_");
		}

		public static List<FloatConfigEntry> StoredFactionRarityEntries(ConfigData d)
		{
			List<FloatConfigEntry> list = new List<FloatConfigEntry>();
			if (d == null || d.FactionRarity == null)
			{
				return list;
			}
			foreach (string key in d.FactionRarity.Keys)
			{
				FloatConfigEntry floatConfigEntry = FactionRarityEntry(key);
				if (floatConfigEntry != null)
				{
					list.Add(floatConfigEntry);
				}
			}
			return list;
		}

		public static IntConfigEntry BonusEntry(string factionId, string bonusId)
		{
			if (string.IsNullOrEmpty(factionId) || string.IsNullOrEmpty(bonusId))
			{
				return null;
			}
			string key = ConfigData.BonusKey(factionId, bonusId);
			if (bonusEntries.TryGetValue(key, out var value))
			{
				return value;
			}
			FactionBonusDef factionBonusDef = FactionBonusCatalog.ById(bonusId);
			value = new IntConfigEntry
			{
				Id = "bonus_" + key,
				Category = "starting_bonuses",
				Name = ((factionBonusDef != null) ? factionBonusDef.Title : bonusId),
				Default = 0,
				Min = 0,
				Max = 9,
				Get = (ConfigData d) => d.StartingBonusCount(key),
				Set = delegate(ConfigData d, int v)
				{
					d.SetStartingBonusCount(key, v);
				}
			};
			bonusEntries[key] = value;
			return value;
		}

		private static string BonusKeyOfEntryId(string entryId)
		{
			return StripPrefix(entryId, "bonus_");
		}

		private static string StripPrefix(string id, string prefix)
		{
			if (id == null || !id.StartsWith(prefix) || id.Length == prefix.Length)
			{
				return null;
			}
			return id.Substring(prefix.Length);
		}

		public static List<IntConfigEntry> StoredBonusEntries(ConfigData d)
		{
			List<IntConfigEntry> list = new List<IntConfigEntry>();
			if (d == null || d.StartingBonuses == null)
			{
				return list;
			}
			foreach (string key in d.StartingBonuses.Keys)
			{
				if (ConfigData.SplitBonusKey(key, out var factionId, out var bonusId))
				{
					IntConfigEntry intConfigEntry = BonusEntry(factionId, bonusId);
					if (intConfigEntry != null)
					{
						list.Add(intConfigEntry);
					}
				}
			}
			return list;
		}

		public static List<ConfigEntry> EntriesFor(ConfigData d)
		{
			List<ConfigEntry> list = new List<ConfigEntry>(All);
			list.AddRange(StoredUnitEntries(d));
			list.AddRange(StoredBonusEntries(d));
			list.AddRange(StoredFactionRarityEntries(d));
			return list;
		}

		public static ConfigEntry ById(string id)
		{
			ConfigEntry[] all = All;
			foreach (ConfigEntry configEntry in all)
			{
				if (configEntry.Id == id)
				{
					return configEntry;
				}
			}
			if (ConfigData.SplitBonusKey(BonusKeyOfEntryId(id), out var factionId, out var bonusId))
			{
				return BonusEntry(factionId, bonusId);
			}
			string text = StripPrefix(id, "faction_rarity_");
			if (text != null)
			{
				return FactionRarityEntry(text);
			}
			string text2 = UnitIdOfEntryId(id);
			if (text2 == null)
			{
				return null;
			}
			return UnitEntry(text2);
		}

		public static ConfigCategory CategoryById(string id)
		{
			ConfigCategory[] categories = Categories;
			foreach (ConfigCategory configCategory in categories)
			{
				if (configCategory.Id == id)
				{
					return configCategory;
				}
			}
			return null;
		}

		public static List<ConfigEntry> InCategory(string categoryId, ConfigData d = null)
		{
			List<ConfigEntry> list = new List<ConfigEntry>();
			ConfigEntry[] all = All;
			foreach (ConfigEntry configEntry in all)
			{
				if (configEntry.Category == categoryId)
				{
					list.Add(configEntry);
				}
			}
			switch (categoryId)
			{
			case "starting_units":
				list.AddRange(StoredUnitEntries(d));
				break;
			case "starting_bonuses":
				list.AddRange(StoredBonusEntries(d));
				break;
			case "general":
				list.AddRange(StoredFactionRarityEntries(d));
				break;
			}
			return list;
		}

		public static List<ConfigEntry> InSection(string sectionId, ConfigData d = null)
		{
			List<ConfigEntry> list = new List<ConfigEntry>();
			if (sectionId == "faction_rarity")
			{
				HashSet<string> hashSet = new HashSet<string>();
				try
				{
					foreach (Faction item in from f in FactionLookup.PlayableFactions()
						orderby f.index
						select f)
					{
						string text = FactionLookup.IdOf(item);
						if (text != null && !hashSet.Contains(text))
						{
							hashSet.Add(text);
							FloatConfigEntry floatConfigEntry = FactionRarityEntry(text);
							if (floatConfigEntry != null)
							{
								list.Add(floatConfigEntry);
							}
						}
					}
				}
				catch (Exception ex)
				{
					Log.Warn("Faction list unavailable for the rarity rows: " + ex.Message);
				}
				{
					foreach (FloatConfigEntry item2 in StoredFactionRarityEntries(d))
					{
						string text2 = FactionIdOf(item2);
						if (text2 != null && !hashSet.Contains(text2))
						{
							hashSet.Add(text2);
							list.Add(item2);
						}
					}
					return list;
				}
			}
			ConfigEntry[] all = All;
			foreach (ConfigEntry configEntry in all)
			{
				if (configEntry.Section == sectionId)
				{
					list.Add(configEntry);
				}
			}
			return list;
		}

		public static List<ConfigEntry> StoredInSection(ConfigData d, string sectionId)
		{
			List<ConfigEntry> list = new List<ConfigEntry>();
			ConfigEntry[] all = All;
			foreach (ConfigEntry configEntry in all)
			{
				if (configEntry.Section == sectionId)
				{
					list.Add(configEntry);
				}
			}
			if (sectionId == "faction_rarity")
			{
				list.AddRange(StoredFactionRarityEntries(d));
			}
			return list;
		}

		public static bool IsCategoryModified(ConfigData d, string categoryId)
		{
			foreach (ConfigEntry item in InCategory(categoryId, d))
			{
				if (!item.IsDefault(d))
				{
					return true;
				}
			}
			return false;
		}

		public static bool IsCustom(ConfigData d)
		{
			ConfigCategory[] userCategories = UserCategories;
			foreach (ConfigCategory configCategory in userCategories)
			{
				if (IsCategoryModified(d, configCategory.Id))
				{
					return true;
				}
			}
			return false;
		}

		public static bool IsSectionModified(ConfigData d, string sectionId)
		{
			foreach (ConfigEntry item in StoredInSection(d, sectionId))
			{
				if (!item.IsDefault(d))
				{
					return true;
				}
			}
			return false;
		}
	}
	[Serializable]
	public class ConfigData
	{
		public const int CurrentVersion = 2;

		public int Version = 2;

		public int StartingBudget = 1000;

		public int ForfeitBonus = 20;

		public int LevelRewardMinimum = 50;

		public float LevelRewardValue = 0.15f;

		public int RefreshBaseCost = 10;

		public int RefreshCostScaling = 10;

		public int BaseOptions = 3;

		public int MaxOptions = 6;

		public int TimeBonusCountdown = 10;

		public float TimeBonusDecreaseRate = 0.75f;

		public int ShopItemCost = 100;

		public float ShopItemRatio = 1f / 3f;

		public float ShopUnitCost = 0.2f;

		public float CardSellValue;

		public int MinimumSellValue = 10;

		public Dictionary<string, int> StartingItems = new Dictionary<string, int>();

		public Dictionary<string, int> StartingUnits = new Dictionary<string, int>();

		public Dictionary<string, float> FactionRarity = new Dictionary<string, float>();

		public Dictionary<string, float> ItemRarity = new Dictionary<string, float>();

		public Dictionary<string, int> StartingBonuses = new Dictionary<string, int>();

		public string CustomRunColor = "#ECC700";

		public int StartingItemCount(string itemId)
		{
			return StoredMap.Get(StartingItems, itemId, 0);
		}

		public void SetStartingItemCount(string itemId, int count)
		{
			StoredMap.Set(ref StartingItems, itemId, count, count == 0);
		}

		public int StartingUnitCount(string unitId)
		{
			return StoredMap.Get(StartingUnits, unitId, 0);
		}

		public void SetStartingUnitCount(string unitId, int count)
		{
			StoredMap.Set(ref StartingUnits, unitId, count, count == 0);
		}

		public float FactionRarityOf(string factionId, float fallback)
		{
			return StoredMap.Get(FactionRarity, factionId, fallback);
		}

		public void SetFactionRarity(string factionId, float value, bool isDefault)
		{
			StoredMap.Set(ref FactionRarity, factionId, value, isDefault);
		}

		public float ItemRarityOf(string itemId)
		{
			return StoredMap.Get(ItemRarity, itemId, 1f);
		}

		public void SetItemRarity(string itemId, float value, bool isDefault)
		{
			StoredMap.Set(ref ItemRarity, itemId, value, isDefault);
		}

		public static string BonusKey(string factionId, string bonusId)
		{
			return factionId + "|" + bonusId;
		}

		public static bool SplitBonusKey(string key, out string factionId, out string bonusId)
		{
			factionId = null;
			bonusId = null;
			if (string.IsNullOrEmpty(key))
			{
				return false;
			}
			int num = key.IndexOf('|');
			if (num <= 0 || num >= key.Length - 1)
			{
				return false;
			}
			factionId = key.Substring(0, num);
			bonusId = key.Substring(num + 1);
			return true;
		}

		public int StartingBonusCount(string key)
		{
			return StoredMap.Get(StartingBonuses, key, 0);
		}

		public void SetStartingBonusCount(string key, int count)
		{
			StoredMap.Set(ref StartingBonuses, key, count, count == 0);
		}

		public void Normalize()
		{
			Version = 2;
			if (StartingItems == null)
			{
				StartingItems = new Dictionary<string, int>();
			}
			foreach (string item in new List<string>(StartingItems.Keys))
			{
				if (ItemCatalog.ById(item) == null)
				{
					Log.Warn("config.json names an unknown item '" + item + "'; ignored.");
					StartingItems.Remove(item);
				}
			}
			if (StartingUnits == null)
			{
				StartingUnits = new Dictionary<string, int>();
			}
			if (FactionRarity == null)
			{
				FactionRarity = new Dictionary<string, float>();
			}
			if (ItemRarity == null)
			{
				ItemRarity = new Dictionary<string, float>();
			}
			if (StartingBonuses == null)
			{
				StartingBonuses = new Dictionary<string, int>();
			}
			DropEmptyKeys(StartingUnits, "starting unit");
			DropEmptyKeys(FactionRarity, "faction weight");
			DropEmptyKeys(ItemRarity, "item weight");
			DropEmptyKeys(StartingBonuses, "starting bonus");
			foreach (string item2 in new List<string>(ItemRarity.Keys))
			{
				if (ItemCatalog.ById(item2) == null)
				{
					Log.Warn("config.json weighs an unknown item '" + item2 + "'; ignored.");
					ItemRarity.Remove(item2);
				}
			}
			foreach (string item3 in new List<string>(StartingBonuses.Keys))
			{
				if (!SplitBonusKey(item3, out var _, out var bonusId) || FactionBonusCatalog.ById(bonusId) == null)
				{
					Log.Warn("config.json names an unknown starting bonus '" + item3 + "'; ignored.");
					StartingBonuses.Remove(item3);
				}
			}
			foreach (ConfigEntry item4 in ConfigCatalog.EntriesFor(this))
			{
				item4.Sanitize(this);
			}
			ConfigCatalog.CustomRunColor.Sanitize(this);
		}

		private static void DropEmptyKeys<T>(Dictionary<string, T> map, string what)
		{
			foreach (string item in new List<string>(map.Keys))
			{
				if (string.IsNullOrEmpty(item))
				{
					Log.Warn("config.json holds a nameless " + what + " (an empty key); ignored.");
					map.Remove(item);
				}
			}
		}
	}
	internal static class StoredMap
	{
		public static T Get<T>(Dictionary<string, T> map, string id, T fallback)
		{
			if (map == null || id == null || !map.TryGetValue(id, out var value))
			{
				return fallback;
			}
			return value;
		}

		public static void Set<T>(ref Dictionary<string, T> map, string id, T value, bool drop)
		{
			if (id != null)
			{
				if (map == null)
				{
					map = new Dictionary<string, T>();
				}
				if (drop)
				{
					map.Remove(id);
				}
				else
				{
					map[id] = value;
				}
			}
		}
	}
	public static class ConfigStore
	{
		private static readonly JsonStore<ConfigData> store = new JsonStore<ConfigData>("config", "Config", "using the defaults", 2, delegate(ConfigData d)
		{
			d.Normalize();
			return (string)null;
		}, Migrate);

		private static ConfigData data;

		public const string DefaultCustomRunColor = "#ECC700";

		public static string SavePath => store.SavePath;

		public static ConfigData Data
		{
			get
			{
				if (data == null)
				{
					data = store.Load() ?? new ConfigData();
				}
				return data;
			}
		}

		public static int StartingBudget => ConfigCatalog.StartingBudget.Clamp(Data.StartingBudget);

		public static Color CustomRunColor => ConfigCatalog.CustomRunColor.ColorOf(Data);

		public static List<KeyValuePair<string, float>> FactionRarities
		{
			get
			{
				List<KeyValuePair<string, float>> list = new List<KeyValuePair<string, float>>();
				foreach (FloatConfigEntry item in ConfigCatalog.StoredFactionRarityEntries(Data))
				{
					string text = ConfigCatalog.FactionIdOf(item);
					if (text != null)
					{
						list.Add(new KeyValuePair<string, float>(text, item.Clamp(item.Get(Data))));
					}
				}
				return list;
			}
		}

		public static List<KeyValuePair<string, float>> ItemRarities
		{
			get
			{
				List<KeyValuePair<string, float>> list = new List<KeyValuePair<string, float>>();
				FloatConfigEntry[] itemRarityEntries = ConfigCatalog.ItemRarityEntries;
				foreach (FloatConfigEntry floatConfigEntry in itemRarityEntries)
				{
					float num = floatConfigEntry.Clamp(floatConfigEntry.Get(Data));
					if (!floatConfigEntry.Matches(num, floatConfigEntry.Default))
					{
						list.Add(new KeyValuePair<string, float>(ConfigCatalog.RarityItemIdOf(floatConfigEntry), num));
					}
				}
				return list;
			}
		}

		public static List<KeyValuePair<string, int>> StartingBonuses
		{
			get
			{
				List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>();
				foreach (IntConfigEntry item in ConfigCatalog.StoredBonusEntries(Data))
				{
					int num = item.Clamp(item.Get(Data));
					if (num > 0)
					{
						string key = item.Id.Substring("bonus_".Length);
						list.Add(new KeyValuePair<string, int>(key, num));
					}
				}
				return list;
			}
		}

		public static List<KeyValuePair<string, int>> StartingItems
		{
			get
			{
				List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>();
				IntConfigEntry[] startingItemEntries = ConfigCatalog.StartingItemEntries;
				foreach (IntConfigEntry intConfigEntry in startingItemEntries)
				{
					int num = intConfigEntry.Clamp(intConfigEntry.Get(Data));
					if (num > 0)
					{
						list.Add(new KeyValuePair<string, int>(ConfigCatalog.ItemIdOf(intConfigEntry), num));
					}
				}
				return list;
			}
		}

		public static List<KeyValuePair<string, int>> StartingUnits
		{
			get
			{
				List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>();
				foreach (IntConfigEntry item in ConfigCatalog.StoredUnitEntries(Data))
				{
					int num = item.Clamp(item.Get(Data));
					if (num > 0)
					{
						list.Add(new KeyValuePair<string, int>(ConfigCatalog.UnitIdOf(item), num));
					}
				}
				return list;
			}
		}

		private static bool Migrate(JObject jo, int version)
		{
			if (version < 2 && jo.Remove("StartingBudget"))
			{
				Log.Info("config.json: Starting Budget reset to the new default (" + 1000 + ").");
			}
			return true;
		}

		public static bool ApplyText(ConfigEntry entry, string text)
		{
			if (entry == null || !entry.TryApplyText(Data, text))
			{
				return false;
			}
			Save();
			return true;
		}

		public static void ApplyInt(IntConfigEntry entry, int value)
		{
			if (entry != null)
			{
				entry.Apply(Data, value);
				Save();
			}
		}

		public static void Reset(ConfigEntry entry)
		{
			if (entry != null)
			{
				entry.Reset(Data);
				Save();
			}
		}

		public static void ResetCategory(string categoryId)
		{
			foreach (ConfigEntry item in ConfigCatalog.InCategory(categoryId, Data))
			{
				item.Reset(Data);
			}
			Save();
		}

		public static void ResetSection(string sectionId)
		{
			foreach (ConfigEntry item in ConfigCatalog.StoredInSection(Data, sectionId))
			{
				item.Reset(Data);
			}
			Save();
		}

		public static void Save()
		{
			store.Save(Data);
		}
	}
	public static class CostRules
	{
		internal static UnitBlueprint PendingCostBlueprint;

		public static int ScaleCost(Team team, int amount)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			if (!RoguelikeState.BattleActive || (int)team != 0)
			{
				return amount;
			}
			UnitBlueprint pendingCostBlueprint = PendingCostBlueprint;
			if ((Object)(object)pendingCostBlueprint == (Object)null)
			{
				return amount;
			}
			try
			{
				return DiscountAmount(pendingCostBlueprint, amount);
			}
			catch (Exception e)
			{
				PatchGuard.Report("ScaleCost", e);
				return amount;
			}
		}

		public static int DiscountAmount(UnitBlueprint blueprint, int amount)
		{
			RoguelikeSession current = RoguelikeState.Current;
			if (current == null)
			{
				return amount;
			}
			string factionId = null;
			if (current.FactionBonuses != null && current.FactionBonuses.Count > 0)
			{
				factionId = RunEffects.FactionIdOf(blueprint);
			}
			float num = RunEffects.CostFactor(current, factionId);
			if (num == 1f)
			{
				return amount;
			}
			int num2 = Mathf.RoundToInt((float)amount * Mathf.Max(0f, num));
			if (amount <= 0)
			{
				return num2;
			}
			return Mathf.Max(1, num2);
		}

		public static int DisplayCost(UnitBlueprint blueprint)
		{
			if (!((Object)(object)blueprint == (Object)null))
			{
				return DiscountAmount(blueprint, (int)blueprint.GetUnitCost(true));
			}
			return 0;
		}

		public static bool IsDiscounted(UnitBlueprint blueprint)
		{
			if ((Object)(object)blueprint != (Object)null)
			{
				return DisplayCost(blueprint) != (int)blueprint.GetUnitCost(true);
			}
			return false;
		}
	}
	public enum FactionBonusKind
	{
		Damage,
		Health,
		Speed,
		Cost,
		Haste,
		Gold,
		HomeField,
		Crowd,
		Range,
		Refresh,
		Mitosis,
		Luck,
		Superiority
	}
	public enum BonusScaling
	{
		None,
		Positive,
		Negative
	}
	public class FactionBonusDef
	{
		public FactionBonusKind Kind;

		public string Id;

		public string Title;

		public string DescriptionFormat;

		public BonusScaling Scaling;

		public bool OneOff;
	}
	public class FactionBonusPick
	{
		public string FactionId;

		public string BonusId;

		public int Milestone;
	}
	public static class FactionBonusCatalog
	{
		public const int PositivePercentPerUnit = 3;

		public const int NegativePercentPerUnit = 2;

		public const int PercentRounding = 5;

		public const int MinPositivePercent = 5;

		public const int MinNegativePercent = 4;

		public const int MaxNegativePercent = 95;

		public const int GoldPerOwnedUnit = 10;

		public const int CrowdPercentPerMember = 1;

		private static readonly FactionBonusDef[] all = new FactionBonusDef[13]
		{
			new FactionBonusDef
			{
				Kind = FactionBonusKind.Damage,
				Id = "damage",
				Title = "DAMAGE",
				Scaling = BonusScaling.Positive,
				DescriptionFormat = "Units of this faction deal +{0}% damage"
			},
			new FactionBonusDef
			{
				Kind = FactionBonusKind.Health,
				Id = "health",
				Title = "HEALTH",
				Scaling = BonusScaling.Positive,
				DescriptionFormat = "Units of this faction have +{0}% health"
			},
			new FactionBonusDef
			{
				Kind = FactionBonusKind.Speed,
				Id = "speed",
				Title = "SPEED",
				Scaling = BonusScaling.Positive,
				DescriptionFormat = "Units of this faction move +{0}% faster"
			},
			new FactionBonusDef
			{
				Kind = FactionBonusKind.Cost,
				Id = "cost",
				Title = "COST",
				Scaling = BonusScaling.Negative,
				DescriptionFormat = "Units of this faction cost {0}% less to place"
			},
			new FactionBonusDef
			{
				Kind = FactionBonusKind.Haste,
				Id = "haste",
				Title = "HASTE",
				Scaling = BonusScaling.Positive,
				DescriptionFormat = "Units of this faction attack +{0}% faster"
			},
			new FactionBonusDef
			{
				Kind = FactionBonusKind.Gold,
				Id = "gold",
				Title = "GOLD",
				Scaling = BonusScaling.None,
				DescriptionFormat = "+" + 10 + " battle gold per unit of this faction in your deck"
			},
			new FactionBonusDef
			{
				Kind = FactionBonusKind.HomeField,
				Id = "homefield",
				Title = "HOME FIELD",
				Scaling = BonusScaling.Positive,
				DescriptionFormat = "Units of this faction get +{0}% stats on this faction's levels"
			},
			new FactionBonusDef
			{
				Kind = FactionBonusKind.Crowd,
				Id = "crowd",
				Title = "CROWD",
				Scaling = BonusScaling.None,
				DescriptionFormat = "Units of this faction get +" + 1 + "% stats per living member"
			},
			new FactionBonusDef
			{
				Kind = FactionBonusKind.Range,
				Id = "range",
				Title = "RANGE",
				Scaling = BonusScaling.Positive,
				DescriptionFormat = "Ranged units of this faction get +{0}% range"
			},
			new FactionBonusDef
			{
				Kind = FactionBonusKind.Refresh,
				Id = "refresh",
				Title = "REFRESH",
				Scaling = BonusScaling.None,
				OneOff = true,
				DescriptionFormat = "Gain 1 free refresh per unit of this faction in your deck"
			},
			new FactionBonusDef
			{
				Kind = FactionBonusKind.Mitosis,
				Id = "mitosis",
				Title = "MITOSIS",
				Scaling = BonusScaling.None,
				OneOff = true,
				DescriptionFormat = "Duplicate every card of this faction in your deck"
			},
			new FactionBonusDef
			{
				Kind = FactionBonusKind.Luck,
				Id = "luck",
				Title = "LUCK",
				Scaling = BonusScaling.Positive,
				DescriptionFormat = "Units of this faction are +{0}% more likely to be offered"
			},
			new FactionBonusDef
			{
				Kind = FactionBonusKind.Superiority,
				Id = "superiority",
				Title = "SUPERIORITY",
				Scaling = BonusScaling.Negative,
				DescriptionFormat = "Enemy units of this faction get -{0}% stats"
			}
		};

		public static IList<FactionBonusDef> All => all;

		public static FactionBonusDef ById(string id)
		{
			if (string.IsNullOrEmpty(id))
			{
				return null;
			}
			for (int i = 0; i < all.Length; i++)
			{
				if (all[i].Id == id)
				{
					return all[i];
				}
			}
			return null;
		}

		public static FactionBonusDef ByKind(FactionBonusKind kind)
		{
			for (int i = 0; i < all.Length; i++)
			{
				if (all[i].Kind == kind)
				{
					return all[i];
				}
			}
			return null;
		}

		public static int Percent(FactionBonusDef def, int rosterCount)
		{
			if (def == null)
			{
				return 0;
			}
			int num = Math.Max(0, rosterCount);
			switch (def.Scaling)
			{
			case BonusScaling.Positive:
				return Math.Max(5, RoundTo(3 * num, 5));
			case BonusScaling.Negative:
			{
				double num2 = Math.Pow(0.98, num);
				int val = RoundTo((1.0 - num2) * 100.0, 5);
				return Math.Min(95, Math.Max(4, val));
			}
			default:
				return 0;
			}
		}

		private static int RoundTo(double value, int step)
		{
			return (int)Math.Round(value / (double)step, MidpointRounding.AwayFromZero) * step;
		}

		public static string Describe(FactionBonusDef def, int rosterCount)
		{
			if (def == null)
			{
				return "";
			}
			return string.Format(def.DescriptionFormat, Percent(def, rosterCount));
		}

		public static string DescribeGeneric(FactionBonusDef def)
		{
			if (def == null)
			{
				return "";
			}
			return string.Format(def.DescriptionFormat, "X");
		}
	}
	public static class FactionBonusRules
	{
		private static readonly Dictionary<string, int> rosterCountCache = new Dictionary<string, int>();

		public static List<FactionBonusPick> PicksFor(RoguelikeSession session, string factionId)
		{
			List<FactionBonusPick> list = new List<FactionBonusPick>();
			if (session == null || session.FactionBonuses == null)
			{
				return list;
			}
			foreach (FactionBonusPick factionBonuse in session.FactionBonuses)
			{
				if (factionBonuse != null && !string.IsNullOrEmpty(factionBonuse.BonusId) && (string.IsNullOrEmpty(factionId) || !(factionBonuse.FactionId != factionId)))
				{
					list.Add(factionBonuse);
				}
			}
			return list;
		}

		public static int CountOwned(RoguelikeSession session, string factionId, FactionBonusKind kind)
		{
			int num = 0;
			if (session == null || session.FactionBonuses == null)
			{
				return 0;
			}
			foreach (FactionBonusPick factionBonuse in session.FactionBonuses)
			{
				if (factionBonuse != null && !string.IsNullOrEmpty(factionBonuse.BonusId) && (string.IsNullOrEmpty(factionId) || !(factionBonuse.FactionId != factionId)))
				{
					FactionBonusDef factionBonusDef = FactionBonusCatalog.ById(factionBonuse.BonusId);
					if (factionBonusDef != null && factionBonusDef.Kind == kind)
					{
						num++;
					}
				}
			}
			return num;
		}

		public static bool Owns(RoguelikeSession session, string factionId, FactionBonusKind kind)
		{
			return CountOwned(session, factionId, kind) > 0;
		}

		public static void InvalidateCaches()
		{
			rosterCountCache.Clear();
		}

		public static int RosterCount(string factionId)
		{
			if (string.IsNullOrEmpty(factionId))
			{
				return 0;
			}
			if (rosterCountCache.TryGetValue(factionId, out var value))
			{
				return value;
			}
			Faction val = FactionLookup.Find(factionId);
			int num = (((Object)(object)val != (Object)null) ? FactionLookup.RosterUnitIds(val).Count : 0);
			rosterCountCache[factionId] = num;
			return num;
		}

		public static int Percent(string factionId, FactionBonusKind kind)
		{
			return FactionBonusCatalog.Percent(FactionBonusCatalog.ByKind(kind), RosterCount(factionId));
		}

		public static float Multiplier(RoguelikeSession session, string factionId, FactionBonusKind kind)
		{
			int num = CountOwned(session, factionId, kind);
			if (num <= 0)
			{
				return 1f;
			}
			FactionBonusDef factionBonusDef = FactionBonusCatalog.ByKind(kind);
			if (factionBonusDef == null || factionBonusDef.Scaling == BonusScaling.None)
			{
				return 1f;
			}
			float num2 = (float)FactionBonusCatalog.Percent(factionBonusDef, RosterCount(factionId)) / 100f;
			float num3 = ((factionBonusDef.Scaling == BonusScaling.Positive) ? (1f + num2) : (1f - num2));
			float num4 = 1f;
			for (int i = 0; i < num; i++)
			{
				num4 *= num3;
			}
			return num4;
		}

		public static float HomeFieldMultiplier(RoguelikeSession session, string factionId)
		{
			if (string.IsNullOrEmpty(factionId) || RoguelikeState.CurrentLevelFactionId != factionId)
			{
				return 1f;
			}
			return Multiplier(session, factionId, FactionBonusKind.HomeField);
		}

		public static float SuperiorityMultiplier(RoguelikeSession session, string factionId)
		{
			return Multiplier(session, factionId, FactionBonusKind.Superiority);
		}

		public static float LuckWeightMultiplier(RoguelikeSession session, string factionId)
		{
			return Multiplier(session, factionId, FactionBonusKind.Luck);
		}

		public static float CrowdMultiplier(RoguelikeSession session, string factionId, int livingMembers)
		{
			if (!Owns(session, factionId, FactionBonusKind.Crowd))
			{
				return 1f;
			}
			return 1f + (float)Math.Max(0, livingMembers) / 100f;
		}

		public static List<string> RemainingIds(RoguelikeSession session, string factionId)
		{
			HashSet<string> hashSet = new HashSet<string>(from p in PicksFor(session, factionId)
				select p.BonusId);
			List<string> list = new List<string>();
			foreach (FactionBonusDef item in FactionBonusCatalog.All)
			{
				if (!hashSet.Contains(item.Id))
				{
					list.Add(item.Id);
				}
			}
			return list;
		}

		public static List<string> DrawOffers(Random rand, RoguelikeSession session, string factionId, int count, ICollection<string> exclude = null)
		{
			List<string> list = RemainingIds(session, factionId);
			if (exclude != null && exclude.Count > 0)
			{
				list.RemoveAll(exclude.Contains);
			}
			List<string> list2 = new List<string>();
			while (list2.Count < count && list.Count > 0)
			{
				int index = rand?.Next(list.Count) ?? 0;
				list2.Add(list[index]);
				list.RemoveAt(index);
			}
			return list2;
		}

		public static HashSet<string> FactionIdsWithBonuses(RoguelikeSession session)
		{
			HashSet<string> hashSet = new HashSet<string>();
			if (session == null || session.FactionBonuses == null)
			{
				return hashSet;
			}
			foreach (FactionBonusPick factionBonuse in session.FactionBonuses)
			{
				if (factionBonuse != null && !string.IsNullOrEmpty(factionBonuse.FactionId))
				{
					hashSet.Add(factionBonuse.FactionId);
				}
			}
			return hashSet;
		}

		public static int UnitCopiesInFaction(RoguelikeSession session, string factionId)
		{
			if (session == null || session.UnitCounts == null || string.IsNullOrEmpty(factionId))
			{
				return 0;
			}
			int num = 0;
			foreach (KeyValuePair<string, int> unitCount in session.UnitCounts)
			{
				if (unitCount.Value > 0 && !(FactionLookup.FactionIdOfUnit(unitCount.Key) != factionId))
				{
					num += unitCount.Value;
				}
			}
			return num;
		}

		public static int GoldBudgetBonus(RoguelikeSession session)
		{
			if (session == null || session.FactionBonuses == null)
			{
				return 0;
			}
			int num = 0;
			foreach (FactionBonusPick factionBonuse in session.FactionBonuses)
			{
				if (factionBonuse != null)
				{
					FactionBonusDef factionBonusDef = FactionBonusCatalog.ById(factionBonuse.BonusId);
					if (factionBonusDef != null && factionBonusDef.Kind == FactionBonusKind.Gold)
					{
						num += 10 * UnitCopiesInFaction(session, factionBonuse.FactionId);
					}
				}
			}
			return num;
		}
	}
	public static class FactionLookup
	{
		private static readonly Dictionary<DatabaseID, List<string>> rosterIdsCache = new Dictionary<DatabaseID, List<string>>();

		private static readonly Dictionary<DatabaseID, Faction> dominantFactionCache = new Dictionary<DatabaseID, Faction>();

		private static readonly Dictionary<DatabaseID, Faction> primaryFactionCache = new Dictionary<DatabaseID, Faction>();

		private static readonly Dictionary<string, Faction> factionById = new Dictionary<string, Faction>();

		public static void InvalidateCaches()
		{
			factionById.Clear();
			dominantFactionCache.Clear();
			primaryFactionCache.Clear();
			rosterIdsCache.Clear();
		}

		public static bool IsModFaction(Faction faction)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)faction != (Object)null && faction.Entity != null)
			{
				return faction.Entity.GUID.m_modID != 0;
			}
			return false;
		}

		private static bool IsPlayableFaction(Faction faction)
		{
			if ((Object)(object)faction == (Object)null || faction.Entity == null)
			{
				return false;
			}
			if (faction.Entity.Name == null || !faction.Entity.Name.StartsWith("FACTION_"))
			{
				return IsModFaction(faction);
			}
			return true;
		}

		public static IEnumerable<Faction> PlayableFactions()
		{
			return from f in ContentDatabase.Instance().LandfallContentDatabase.GetFactions()
				where (Object)(object)f != (Object)null && f.Entity != null && f.m_displayFaction && IsPlayableFaction(f)
				select f;
		}

		public static List<string> RosterUnitIds(Faction faction)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			bool flag = (Object)(object)faction != (Object)null && faction.Entity != null;
			if (flag && rosterIdsCache.TryGetValue(faction.Entity.GUID, out var value))
			{
				return value;
			}
			value = new List<string>();
			foreach (UnitBlueprint item in CardPools.EligibleUnits(faction, null, new HashSet<DatabaseID>()))
			{
				value.Add(CardPools.IdToString(item.Entity.GUID));
			}
			if (flag)
			{
				rosterIdsCache[faction.Entity.GUID] = value;
			}
			return value;
		}

		public static Faction DominantFaction(TABSCampaignLevelAsset level)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)level == (Object)null || level.Entity == null)
			{
				return null;
			}
			if (dominantFactionCache.TryGetValue(level.Entity.GUID, out var value))
			{
				return value;
			}
			Dictionary<DatabaseID, long> totals = new Dictionary<DatabaseID, long>();
			Dictionary<DatabaseID, Faction> dictionary = new Dictionary<DatabaseID, Faction>();
			if (level.BlueUnits != null)
			{
				TABSLayoutUnit[] blueUnits = level.BlueUnits;
				foreach (TABSLayoutUnit val in blueUnits)
				{
					if (val != null && !((Object)(object)val.m_unitBlueprint == (Object)null))
					{
						Faction val2 = PrimaryFactionForUnit(val.m_unitBlueprint);
						if (!((Object)(object)val2 == (Object)null) && val2.Entity != null)
						{
							DatabaseID gUID = val2.Entity.GUID;
							dictionary[gUID] = val2;
							totals.TryGetValue(gUID, out var value2);
							totals[gUID] = value2 + val.m_unitBlueprint.GetUnitCost(true);
						}
					}
				}
			}
			Faction val3 = (from f in dictionary.Values
				orderby totals[f.Entity.GUID] descending, f.index
				select f).FirstOrDefault();
			dominantFactionCache[level.Entity.GUID] = val3;
			return val3;
		}

		public static Faction