Decompiled source of RoguelikeMode v1.1.0
plugins/RoguelikeMode.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
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