Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of LethalRPG v0.6.0
LethalRPG.dll
Decompiled 3 hours 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.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using TMPro; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Cucumis")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) 2026 Cucumis")] [assembly: AssemblyFileVersion("0.6.0.0")] [assembly: AssemblyInformationalVersion("0.6.0")] [assembly: AssemblyProduct("LethalRPG")] [assembly: AssemblyTitle("LethalRPG")] [assembly: InternalsVisibleTo("PatchCheck")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.6.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LethalRPG { public enum Balancing { Standard, Generous, Strict } public static class BalancingPresets { private sealed class Rule { public UpgradeType Type; public string Key; public float Generous; public float Strict; public bool Add; } public const int LevelLimit = 20; private const double GenerousLevels = 3.0; private const double StrictLevels = 0.6; private const float CrouchSpeedGap = 0.5f; private static readonly Dictionary<UpgradeType, int> GenerousCeiling = new Dictionary<UpgradeType, int> { { UpgradeType.Agility, 10 }, { UpgradeType.Jump, 6 }, { UpgradeType.LongReach, 8 }, { UpgradeType.Climber, 7 } }; private static readonly Rule[] Rules = new Rule[8] { Scale(UpgradeType.Lockpicker, "Seconds", 0.5f, 1.5f), Scale(UpgradeType.FieldHacker, "Seconds", 0.5f, 1.5f), Scale(UpgradeType.Medic, "RegenDelaySeconds", 0.5f, 1.5f), Scale(UpgradeType.ConcussiveBlows, "StunTime", 1.5f, 0.75f), Scale(UpgradeType.ConcussiveBlows, "CooldownSeconds", 0.5f, 1.5f), Scale(UpgradeType.BlastShield, "Damage", 0.5f, 1.3333334f), Scale(UpgradeType.RallyingPresence, "Range", 1.5f, 0.75f), new Rule { Type = UpgradeType.SecondWind, Key = "ChargesPerDay", Generous = 1f, Strict = 0f, Add = true } }; private static Rule Scale(UpgradeType type, string key, float generous, float strict) { return new Rule { Type = type, Key = key, Generous = generous, Strict = strict }; } public static void Apply(RpgSettings settings) { if (settings.Balancing == Balancing.Standard) { return; } UpgradeDef[] all = Upgrades.All; foreach (UpgradeDef upgradeDef in all) { if (!upgradeDef.Unique) { ScaleLevels(settings, upgradeDef.Type); } } Rule[] rules = Rules; foreach (Rule rule in rules) { ApplyRule(settings, rule); } } private static void ScaleLevels(RpgSettings settings, UpgradeType type) { UpgradeSettings upgradeSettings = settings.For(type); int maxLevel = upgradeSettings.MaxLevel; if (settings.Balancing == Balancing.Strict) { upgradeSettings.MaxLevel = Math.Max(1, (int)Math.Round((double)maxLevel * 0.6, MidpointRounding.AwayFromZero)); return; } int num = Math.Min((int)Math.Round((double)maxLevel * 3.0), UsefulLevels(settings, type)); if (GenerousCeiling.TryGetValue(type, out var value)) { num = Math.Min(num, value); } if (num > maxLevel) { upgradeSettings.CostMultiplier = ((maxLevel <= 1) ? 1f : ((float)Math.Pow(upgradeSettings.CostMultiplier, ((double)maxLevel - 1.0) / ((double)num - 1.0)))); upgradeSettings.MaxLevel = num; } } public static int UsefulLevels(RpgSettings settings, UpgradeType type) { float num = settings.EffectPerLevel(type); int num2 = 20; if (num <= 0f) { return num2; } if (settings.For(type).Params.TryGetValue("MaxReduction", out var value)) { num2 = Math.Min(num2, LevelsFor(value / num)); } if (type == UpgradeType.Inventory) { num2 = Math.Min(num2, LevelsFor(16f / Math.Max(1f, (float)Math.Round(num)))); } if (type == UpgradeType.CrouchWalk) { num2 = Math.Min(num2, LevelsFor(0.5f / num)); } return Math.Max(1, num2); } private static int LevelsFor(float levels) { return (int)Math.Ceiling(levels - 0.001f); } private static void ApplyRule(RpgSettings settings, Rule rule) { UpgradeSettings upgradeSettings = settings.For(rule.Type); SkillParam skillParam = Array.Find(Upgrades.Get(rule.Type).Params, (SkillParam p) => p.Key == rule.Key); if (skillParam != null && upgradeSettings.Params != null && upgradeSettings.Params.TryGetValue(rule.Key, out var value)) { float num = ((settings.Balancing == Balancing.Generous) ? rule.Generous : rule.Strict); value = (rule.Add ? (value + num) : (value * num)); if (skillParam.IsInt) { value = (float)Math.Round(value, MidpointRounding.AwayFromZero); } upgradeSettings.Params[rule.Key] = Math.Max(skillParam.Min, Math.Min(skillParam.Max, value)); } } public static string Describe(Balancing balancing) { return balancing switch { Balancing.Generous => "Balancing: Generous (up to 3x the levels with slower rising prices, faster lockpicking and hacking, stronger skills).", Balancing.Strict => "Balancing: Strict (40% fewer levels, slower lockpicking and hacking, weaker skills).", _ => null, }; } } public static class Builds { private static UpgradeDef Leveled(string id) { return Upgrades.All.FirstOrDefault((UpgradeDef u) => u.Id == id && !u.Unique); } public static List<string> CurrentBuild(PlayerProgress progress) { List<string> list = new List<string>(); Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (string item in progress.BuildOrder ?? new List<string>()) { UpgradeDef upgradeDef = Leveled(item); if (upgradeDef != null) { dictionary.TryGetValue(item, out var value); if (value < progress.GetLevel(upgradeDef.Type)) { list.Add(item); dictionary[item] = value + 1; } } } foreach (UpgradeDef item2 in Upgrades.All.Where((UpgradeDef u) => !u.Unique)) { dictionary.TryGetValue(item2.Id, out var value2); for (int num = value2; num < progress.GetLevel(item2.Type); num++) { list.Add(item2.Id); } } return list; } public static List<UpgradeType> MissingSteps(PlayerProgress progress, IList<string> build, RpgSettings settings = null) { List<UpgradeType> list = new List<UpgradeType>(); Dictionary<string, int> dictionary = new Dictionary<string, int>(); foreach (string item in build ?? new List<string>()) { UpgradeDef upgradeDef = Leveled(item); if (upgradeDef != null) { dictionary.TryGetValue(item, out var value); value = (dictionary[item] = value + 1); if ((settings == null || (RpgState.IsEnabled(upgradeDef.Type) && value <= settings.For(upgradeDef.Type).MaxLevel)) && progress.GetLevel(upgradeDef.Type) < value) { list.Add(upgradeDef.Type); } } } return list; } public static long CostOf(RpgSettings settings, PlayerProgress progress, IEnumerable<UpgradeType> steps) { Dictionary<UpgradeType, int> dictionary = new Dictionary<UpgradeType, int>(); long num = 0L; foreach (UpgradeType step in steps) { if (!dictionary.TryGetValue(step, out var value)) { value = progress.GetLevel(step); } num += settings.CostForLevel(step, value); dictionary[step] = value + 1; } return num; } public static string Totals(IEnumerable<string> build) { return string.Join(", ", from d in build.Select(Leveled) where d != null group d by d.Type into g select $"{g.First().DisplayName} {g.Count()}"); } public static string Increments(IEnumerable<UpgradeType> steps) { return string.Join(", ", from t in steps group t by t into g select $"{Upgrades.Get(g.Key).DisplayName} +{g.Count()}"); } } internal sealed class HudLine { private readonly string name; private HUDManager hud; private TextMeshProUGUI text; private Color defaultColor; private int line = -1; public bool Visible { get { if ((Object)(object)text != (Object)null) { return ((TMP_Text)text).text.Length > 0; } return false; } } public HudLine(string name) { this.name = name; } public int Show(string value, int lineIndex, Color? color = null) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) Set(value, lineIndex); if ((Object)(object)text != (Object)null && !string.IsNullOrEmpty(value)) { Color val = (Color)(((??)color) ?? defaultColor); if (((Graphic)text).color != val) { ((Graphic)text).color = val; } } if (!Visible) { return lineIndex; } return lineIndex + 1; } public void Set(string value, int lineIndex) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(value)) { Clear(); } else if (Ensure()) { if (lineIndex != line) { line = lineIndex; RectTransform rectTransform = ((TMP_Text)hud.weightCounter).rectTransform; float num = ((TMP_Text)hud.weightCounter).fontSize * 1.35f; ((RectTransform)((TMP_Text)text).transform).anchoredPosition = rectTransform.anchoredPosition - new Vector2(0f, num * (float)lineIndex); } if (((TMP_Text)text).text != value) { ((TMP_Text)text).text = value; } } } public void Clear() { if ((Object)(object)text != (Object)null && ((TMP_Text)text).text.Length > 0) { ((TMP_Text)text).text = ""; } } private bool Ensure() { //IL_0088: 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) HUDManager instance = HUDManager.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.weightCounter == (Object)null) { return false; } if ((Object)(object)instance == (Object)(object)hud && (Object)(object)text != (Object)null) { return true; } hud = instance; line = -1; TextMeshProUGUI weightCounter = instance.weightCounter; GameObject val = Object.Instantiate<GameObject>(((Component)weightCounter).gameObject, ((TMP_Text)weightCounter).transform.parent); ((Object)val).name = name; text = val.GetComponent<TextMeshProUGUI>(); defaultColor = ((Graphic)text).color; ((TMP_Text)text).richText = false; ((TMP_Text)text).enableWordWrapping = false; ((TMP_Text)text).text = ""; return true; } } internal static class CompassHud { private const float SearchInterval = 1f; private static readonly HudLine Line = new HudLine("LethalRPG Compass"); private static EntranceTeleport[] exits = (EntranceTeleport[])(object)new EntranceTeleport[0]; private static float nextSearch; private static int lastMeters = -1; private static int lastDirection; private static string lastText; public static int Tick(PlayerControllerB player, int line) { //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) if (!player.isInsideFactory || player.isPlayerDead || !RpgState.Has(player, UpgradeType.Compass)) { Line.Clear(); return line; } if (Time.time >= nextSearch) { nextSearch = Time.time + 1f; exits = Object.FindObjectsOfType<EntranceTeleport>(); } EntranceTeleport val = null; float num = float.MaxValue; EntranceTeleport[] array = exits; foreach (EntranceTeleport val2 in array) { if (!((Object)(object)val2 == (Object)null) && !val2.isEntranceToBuilding) { float num2 = Vector3.Distance(((Component)player).transform.position, ((Component)val2).transform.position); if (num2 < num) { num = num2; val = val2; } } } if ((Object)(object)val == (Object)null) { Line.Clear(); return line; } Vector3 val3 = ((Component)val).transform.position - ((Component)player).transform.position; val3.y = 0f; Vector3 forward = ((Component)player).transform.forward; forward.y = 0f; float angle = Vector3.SignedAngle(forward, val3, Vector3.up); int num3 = Mathf.RoundToInt(num); int num4 = Direction(angle); if (num3 != lastMeters || num4 != lastDirection || lastText == null) { lastMeters = num3; lastDirection = num4; lastText = Format(num4, num3); } return Line.Show(lastText, line); } private static int Direction(float angle) { float num = Mathf.Abs(angle); if (num <= 20f) { return 0; } if (num >= 150f) { return 3; } int num2 = ((!(num > 90f)) ? 1 : 2); if (!(angle < 0f)) { return num2; } return -num2; } private static string Format(int direction, int meters) { string text = $"EXIT {meters}m"; return direction switch { 0 => "^ " + text + " ^", 3 => "v " + text + " v", -2 => "<< " + text, -1 => "< " + text, 1 => text + " >", _ => text + " >>", }; } } internal static class StatusHud { private const float Interval = 0.1f; private static readonly HudLine Line = new HudLine("LethalRPG Status"); private static readonly List<string> parts = new List<string>(); private static string flash; private static float flashUntil; private static float nextUpdate; private static string currentText = ""; public static void Flash(string text, float seconds = 4f) { flash = text; flashUntil = Time.time + seconds; nextUpdate = 0f; } public static void ResetSession() { flash = null; flashUntil = 0f; } public static int Tick(PlayerControllerB player, int line) { if (Time.time < nextUpdate) { if (!Line.Visible) { return line; } return Line.Show(currentText, line); } nextUpdate = Time.time + 0.1f; if (!Plugin.Cfg.ShowStatusHud.Value) { Line.Clear(); return line; } parts.Clear(); if (!player.isPlayerDead) { int num = RpgState.MaxHealth(player); if (num > 100) { parts.Add($"HP {player.health}/{num}"); } if (RpgState.Has(player, UpgradeType.SecondWind)) { parts.Add($"2ND WIND {LocalSkills.SecondWindLeft}/{LocalSkills.SecondWindChargesPerDay}"); } if (RpgState.AdrenalineActive(player)) { parts.Add("ADRENALINE"); } if (LightningRodActive(player)) { parts.Add("ROD ACTIVE"); } if (RpgState.RallyNearby(player)) { parts.Add("RALLIED"); } } if (flash != null && Time.time < flashUntil) { parts.Add(flash); } currentText = string.Join(" ", parts); return Line.Show(currentText, line); } private static bool LightningRodActive(PlayerControllerB player) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 if (player.isInsideFactory || (Object)(object)TimeOfDay.Instance == (Object)null || (int)TimeOfDay.Instance.currentLevelWeather != 2 || !RpgState.Has(player, UpgradeType.LightningRod) || player.ItemSlots == null) { return false; } GrabbableObject[] itemSlots = player.ItemSlots; foreach (GrabbableObject val in itemSlots) { if ((Object)(object)val != (Object)null && (Object)(object)val.itemProperties != (Object)null && val.itemProperties.isConductiveMetal) { return true; } } return false; } } internal static class DangerHud { private const float Interval = 0.25f; private const float MaxDistance = 40f; private static readonly HudLine Line = new HudLine("LethalRPG Danger"); private static readonly Color Alarm = new Color(1f, 0.15f, 0.1f); private static float nextUpdate; private static string current = ""; private static string reported = ""; private static float reportedUntil; public static void ReportFromHost(string enemyName) { reported = enemyName ?? ""; reportedUntil = Time.time + 1.2f; nextUpdate = 0f; } public static void ResetSession() { reported = ""; reportedUntil = 0f; } public static int Tick(PlayerControllerB player, int line) { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) if (Time.time >= nextUpdate) { nextUpdate = Time.time + 0.25f; string text = ""; if (!player.isPlayerDead && RpgState.Has(player, UpgradeType.DangerSense)) { EnemyAI val = Hunter(player, ownedOnly: true); text = (((Object)(object)val != (Object)null) ? Reports.EnemyName(val) : ((Time.time < reportedUntil) ? reported : "")); } current = ((text.Length > 0) ? ("!! HUNTED BY " + text.ToUpperInvariant() + " !!") : ""); } if (current.Length == 0) { Line.Clear(); return line; } return Line.Show(current, line, Alarm); } public static bool IsHunting(EnemyAI enemy, PlayerControllerB player) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)enemy != (Object)null && !enemy.isEnemyDead && (Object)(object)enemy.targetPlayer == (Object)(object)player && (Object)(object)player != (Object)null && (enemy.movingTowardsTargetPlayer || enemy.currentBehaviourStateIndex != 0)) { return Vector3.Distance(((Component)enemy).transform.position, ((Component)player).transform.position) < 40f; } return false; } public static EnemyAI Hunter(PlayerControllerB player, bool ownedOnly) { //IL_005f: 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) RoundManager instance = RoundManager.Instance; if ((Object)(object)instance == (Object)null || instance.SpawnedEnemies == null) { return null; } EnemyAI result = null; float num = float.MaxValue; foreach (EnemyAI spawnedEnemy in instance.SpawnedEnemies) { if ((!ownedOnly || (!((Object)(object)spawnedEnemy == (Object)null) && ((NetworkBehaviour)spawnedEnemy).IsOwner)) && IsHunting(spawnedEnemy, player)) { float num2 = Vector3.Distance(((Component)spawnedEnemy).transform.position, ((Component)player).transform.position); if (num2 < num) { num = num2; result = spawnedEnemy; } } } return result; } } internal static class ProspectorHud { private const float Interval = 2f; private static readonly HudLine Line = new HudLine("LethalRPG Prospector"); private static float nextUpdate; private static string current = ""; public static int Tick(PlayerControllerB player, int line) { if (Time.time >= nextUpdate) { nextUpdate = Time.time + 2f; current = (Active(player) ? Count() : ""); } if (current.Length == 0) { Line.Clear(); return line; } return Line.Show(current, line); } private static bool Active(PlayerControllerB player) { StartOfRound instance = StartOfRound.Instance; if (!player.isPlayerDead && RpgState.Has(player, UpgradeType.Prospector) && (Object)(object)instance != (Object)null && !instance.inShipPhase && (Object)(object)instance.currentLevel != (Object)null) { return instance.currentLevel.planetHasTime; } return false; } private static string Count() { int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; GrabbableObject[] array = Object.FindObjectsOfType<GrabbableObject>(); foreach (GrabbableObject val in array) { if (!((Object)(object)val == (Object)null) && !((Object)(object)val.itemProperties == (Object)null) && val.itemProperties.isScrap && val.scrapValue > 0 && !val.deactivated && !val.isHeld && !val.isInShipRoom && !val.isInElevator) { if (val.isInFactory) { num++; num2 += val.scrapValue; } else { num3++; num4 += val.scrapValue; } } } if (num + num3 != 0) { return $"SCRAP INSIDE {num} (${num2}) OUTSIDE {num3} (${num4})"; } return "SCRAP LEFT: NONE"; } } internal static class InfectionHud { private const float SearchInterval = 1f; private const int BarLength = 10; private static readonly HudLine Line = new HudLine("LethalRPG Infection"); private static readonly Color Critical = new Color(1f, 0.15f, 0.1f); private static CadaverGrowthAI growth; private static float nextSearch; private static int lastPercent = -1; private static bool lastBursting; private static bool lastHealing; private static string lastText; public static void ResetSession() { growth = null; nextSearch = 0f; } public static int Tick(PlayerControllerB player, int line) { //IL_0132: Unknown result type (might be due to invalid IL or missing references) PlayerInfection val = ((player.isPlayerDead || !RpgState.Has(player, UpgradeType.BioMonitor)) ? null : InfectionOf(player)); if (val == null) { Line.Clear(); return line; } if (!val.infected) { return Line.Show("BIO-SCAN: CLEAN", line); } bool flag = val.infectionMeter >= 1f; int num = Percent(flag ? val.burstMeter : val.infectionMeter); bool flag2 = !flag && val.healing > 0; if (num != lastPercent || flag != lastBursting || flag2 != lastHealing || lastText == null) { lastPercent = num; lastBursting = flag; lastHealing = flag2; lastText = (flag ? $"BURSTING {num}% {Bar(val.burstMeter)} USE WEED KILLER" : ($"INFECTED {num}% {Bar(val.infectionMeter)}" + (flag2 ? " HEALING" : ""))); } return Line.Show(lastText, line, (flag || val.infectionMeter >= 0.8f) ? new Color?(Critical) : ((Color?)null)); } private static PlayerInfection InfectionOf(PlayerControllerB player) { if ((Object)(object)growth == (Object)null && Time.time >= nextSearch) { nextSearch = Time.time + 1f; growth = Object.FindObjectOfType<CadaverGrowthAI>(); } if ((Object)(object)growth == (Object)null || growth.playerInfections == null || player.playerClientId >= (ulong)growth.playerInfections.Length) { return null; } return growth.playerInfections[player.playerClientId]; } private static int Percent(float value) { return Mathf.Clamp(Mathf.FloorToInt(value * 100f), 0, 100); } private static string Bar(float value) { int num = Mathf.Clamp(Mathf.RoundToInt(value * 10f), 0, 10); return "[" + new string('#', num) + new string('-', 10 - num) + "]"; } } internal static class Compat { private static readonly Dictionary<string, string> InventoryMods = new Dictionary<string, string> { { "FlipMods.ReservedItemSlotCore", "ReservedItemSlotCore" }, { "FlipMods.HotbarPlus", "HotbarPlus" }, { "com.potatoepet.AdvancedCompany", "AdvancedCompany" }, { "Stoneman.LethalProgression", "LethalProgression" } }; private static readonly Dictionary<string, string> OverlappingMods = new Dictionary<string, string> { { "com.malco.lethalcompany.moreshipupgrades", "LateGameUpgrades" }, { "Stoneman.LethalProgression", "LethalProgression" }, { "ShaosilGaming.GeneralImprovements", "GeneralImprovements" } }; private static bool checkedInventory; private static string inventoryConflict; private static bool logged; public static string InventoryConflict { get { if (!checkedInventory) { checkedInventory = true; try { inventoryConflict = (from m in InventoryMods where Chainloader.PluginInfos.ContainsKey(m.Key) select m.Value).FirstOrDefault() ?? (Chainloader.PluginInfos.Keys.Any((string guid) => guid.Contains("ReservedItem")) ? "ReservedItemSlotCore" : null); } catch (TypeInitializationException) { inventoryConflict = null; } } return inventoryConflict; } } public static bool InventoryUpgradeAllowed => InventoryConflict == null; public static void LogSummary() { if (logged) { return; } logged = true; if (InventoryConflict != null) { Plugin.Log.LogWarning((object)(InventoryConflict + " manages inventory slots itself: the Inventory upgrade is disabled.")); } foreach (KeyValuePair<string, string> item in OverlappingMods.Where((KeyValuePair<string, string> m) => Chainloader.PluginInfos.ContainsKey(m.Key))) { Plugin.Log.LogInfo((object)(item.Value + " detected: similar upgrades from both mods stack.")); } } } public enum GainKind { Xp, Budget, Note, TeamBudget } internal static class DayReport { private static readonly XpSource[] Order = new XpSource[5] { XpSource.Rank, XpSource.Scrap, XpSource.Kill, XpSource.Survival, XpSource.Quota }; private static readonly Dictionary<XpSource, int> xp = new Dictionary<XpSource, int>(); private static readonly List<string> notes = new List<string>(); private static int budget; private static int teamBudget; public static void Reset() { xp.Clear(); notes.Clear(); budget = 0; teamBudget = 0; } public static void Add(GainKind kind, int amount, XpSource source, string text) { switch (kind) { case GainKind.Xp: if (source != XpSource.Debug) { xp.TryGetValue(source, out var value); xp[source] = value + amount; if (source == XpSource.Kill) { StatusHud.Flash($"+{amount} XP" + ((text != null) ? (" (" + text + ")") : "")); } } break; case GainKind.Budget: budget += amount; break; case GainKind.TeamBudget: teamBudget += amount; break; case GainKind.Note: if (!string.IsNullOrEmpty(text)) { notes.Add(text); } break; } } public static void OnShipReady() { if (xp.Count != 0 || budget != 0 || teamBudget != 0 || notes.Count != 0) { if (!Plugin.Cfg.ShowDayReport.Value || (Object)(object)StatApplier.Instance == (Object)null) { Reset(); } else { StatApplier.Instance.ShowDelayed("Day report", TakeText, 5f); } } } private static string TakeText() { List<string> list = new List<string>(); int num = xp.Values.Sum(); if (num > 0) { IEnumerable<string> values = from s in Order.Where(xp.ContainsKey) select $"{Label(s)} {xp[s]}"; list.Add(string.Format("+{0} XP ({1})", num, string.Join(", ", values))); } if (budget > 0) { list.Add($"+${budget} upgrade budget"); } if (teamBudget > 0) { list.Add($"+${teamBudget} team budget"); } list.AddRange(notes); List<string> list2 = Reports.Affordable(GameNetworkManager.Instance?.localPlayerController, 2); if (list2.Count > 0) { list.Add("Can buy: " + string.Join(", ", list2)); } Reset(); return string.Join("\n", list); } private static string Label(XpSource source) { return source switch { XpSource.Rank => "rank", XpSource.Scrap => "scrap", XpSource.Kill => "kills", XpSource.Survival => "survived", XpSource.Quota => "quota", _ => source.ToString().ToLowerInvariant(), }; } } internal static class IlTools { public static IEnumerable<CodeInstruction> WrapFieldReads(IEnumerable<CodeInstruction> instructions, FieldInfo field, MethodInfo adjust, MethodInfo replaceOther, string context) { int wrapped = 0; CodeInstruction val = null; foreach (CodeInstruction instruction in instructions) { if (CodeInstructionExtensions.LoadsField(instruction, field, false)) { if (val != null && val.opcode == OpCodes.Ldarg_0) { yield return instruction; yield return new CodeInstruction(OpCodes.Ldarg_0, (object)null); yield return new CodeInstruction(OpCodes.Call, (object)adjust); wrapped++; } else if (replaceOther != null) { CodeInstruction val2 = new CodeInstruction(OpCodes.Call, (object)replaceOther); val2.labels.AddRange(instruction.labels); val2.blocks.AddRange(instruction.blocks); yield return val2; wrapped++; } else { yield return instruction; } } else { yield return instruction; } val = instruction; } if (wrapped == 0) { Plugin.Log.LogWarning((object)(context + ": no reads of " + field.Name + " found; the game or another mod may have changed this code.")); } } } internal static class InventoryHud { private static HUDManager hud; private static int originalCount; private static Transform slotParent; private static Vector3[] originalFramePositions; private static Vector3 originalCenter; private static Vector3 step; private static float frameWidth; private static float frameHeight; public const int SlotsPerRow = 8; private const float RowSpacing = 1.3f; private static Vector3 iconOffsetInFrame; private static bool iconsAreChildren; private static int lastVisible = -1; private static Transform utilityFrame; private static Transform utilityIcon; private static Vector3 utilityOffset; private static Vector3 utilityIconOffset; private static bool utilityOnRight; private static bool Initialize() { //IL_0088: 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) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: 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_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) HUDManager instance = HUDManager.Instance; if ((Object)(object)instance == (Object)null || instance.itemSlotIconFrames == null || instance.itemSlotIconFrames.Length < 2) { return false; } if ((Object)(object)hud == (Object)(object)instance) { return true; } hud = instance; lastVisible = -1; Image[] itemSlotIconFrames = hud.itemSlotIconFrames; originalCount = itemSlotIconFrames.Length; slotParent = ((Component)itemSlotIconFrames[0]).transform.parent; originalFramePositions = (Vector3[])(object)new Vector3[originalCount]; for (int i = 0; i < originalCount; i++) { originalFramePositions[i] = ((Component)itemSlotIconFrames[i]).transform.localPosition; } originalCenter = (originalFramePositions[0] + originalFramePositions[originalCount - 1]) / 2f; step = (originalFramePositions[originalCount - 1] - originalFramePositions[0]) / (float)(originalCount - 1); frameWidth = float.MaxValue; frameHeight = float.MaxValue; for (int j = 0; j < originalCount; j++) { frameWidth = Mathf.Min(frameWidth, WidthInParent(((Graphic)itemSlotIconFrames[j]).rectTransform)); frameHeight = Mathf.Min(frameHeight, HeightInParent(((Graphic)itemSlotIconFrames[j]).rectTransform)); } Transform transform = ((Component)itemSlotIconFrames[0]).transform; Transform transform2 = ((Component)hud.itemSlotIcons[0]).transform; iconsAreChildren = transform2.IsChildOf(transform); iconOffsetInFrame = transform.InverseTransformPoint(transform2.position); utilityFrame = null; utilityIcon = null; if ((Object)(object)hud.itemOnlySlotIconFrame != (Object)null) { utilityFrame = ((Component)hud.itemOnlySlotIconFrame).transform; Vector3 val = ToParent(utilityFrame.position); utilityOnRight = val.x > originalCenter.x; Vector3 val2 = (utilityOnRight ? originalFramePositions[originalCount - 1] : originalFramePositions[0]); utilityOffset = val - val2; if ((Object)(object)hud.itemOnlySlotIcon != (Object)null && !((Component)hud.itemOnlySlotIcon).transform.IsChildOf(utilityFrame)) { utilityIcon = ((Component)hud.itemOnlySlotIcon).transform; utilityIconOffset = ToParent(utilityIcon.position) - val; } } return true; } public static void Refresh(int capacity, int visible) { if (Initialize()) { EnsureCapacity(capacity); if (visible != lastVisible) { Layout(visible); lastVisible = visible; } } } public static void EnsureCapacity(int capacity) { if (!Initialize()) { return; } capacity = Mathf.Min(capacity, 20); Image[] itemSlotIconFrames = hud.itemSlotIconFrames; Image[] itemSlotIcons = hud.itemSlotIcons; if (itemSlotIconFrames.Length >= capacity) { return; } List<Image> list = new List<Image>(itemSlotIconFrames); List<Image> list2 = new List<Image>(itemSlotIcons); Image val = itemSlotIconFrames[originalCount - 1]; Image val2 = itemSlotIcons[originalCount - 1]; while (list.Count < capacity) { int count = list.Count; GameObject val3 = Object.Instantiate<GameObject>(((Component)val).gameObject, ((Component)val).transform.parent); ((Object)val3).name = $"{((Object)((Component)val).gameObject).name} (RPG {count})"; Image component = val3.GetComponent<Image>(); Image component2; if (iconsAreChildren) { string text = RelativePath(((Component)val2).transform, ((Component)val).transform); component2 = ((Component)val3.transform.Find(text)).GetComponent<Image>(); } else { GameObject obj = Object.Instantiate<GameObject>(((Component)val2).gameObject, ((Component)val2).transform.parent); ((Object)obj).name = $"{((Object)((Component)val2).gameObject).name} (RPG {count})"; component2 = obj.GetComponent<Image>(); } ((Behaviour)component2).enabled = false; component2.sprite = null; Animator component3 = val3.GetComponent<Animator>(); if ((Object)(object)component3 != (Object)null) { component3.SetBool("selectedSlot", false); } list.Add(component); list2.Add(component2); } hud.itemSlotIconFrames = list.ToArray(); hud.itemSlotIcons = list2.ToArray(); lastVisible = -1; } private static void Layout(int visible) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_0340: Unknown result type (might be due to invalid IL or missing references) //IL_0378: Unknown result type (might be due to invalid IL or missing references) //IL_0384: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_02bf: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Unknown result type (might be due to invalid IL or missing references) //IL_041a: Unknown result type (might be due to invalid IL or missing references) //IL_0416: Unknown result type (might be due to invalid IL or missing references) //IL_041c: Unknown result type (might be due to invalid IL or missing references) //IL_0421: Unknown result type (might be due to invalid IL or missing references) //IL_0426: Unknown result type (might be due to invalid IL or missing references) //IL_042d: Unknown result type (might be due to invalid IL or missing references) //IL_042f: Unknown result type (might be due to invalid IL or missing references) //IL_044b: Unknown result type (might be due to invalid IL or missing references) //IL_044d: Unknown result type (might be due to invalid IL or missing references) //IL_0452: Unknown result type (might be due to invalid IL or missing references) //IL_0457: Unknown result type (might be due to invalid IL or missing references) Image[] itemSlotIconFrames = hud.itemSlotIconFrames; Image[] itemSlotIcons = hud.itemSlotIcons; visible = Mathf.Clamp(visible, originalCount, itemSlotIconFrames.Length); int num = Mathf.Min(visible, 8); Vector3 val = step; float num2 = (((Object)(object)utilityFrame != (Object)null) ? Mathf.Abs(utilityOffset.x) : 0f); float left; float right; bool flag = TryGetScreenBounds(out left, out right); if (flag && num > 1) { float num3 = frameWidth * 0.25f; float num4 = right - left - 2f * num3; if (Mathf.Abs(val.x) * (float)(num - 1) + num2 + frameWidth > num4) { float num5 = frameWidth * 1.05f; float num6 = (num4 - num2 - frameWidth) / (float)(num - 1); val *= Mathf.Max(num5, num6) / Mathf.Abs(val.x); } } Vector3 val2 = originalCenter - val * ((float)(num - 1) / 2f); Vector3 val3 = val2 + val * (float)(num - 1); if (flag) { float num7 = frameWidth * 0.25f; float num8 = Mathf.Min(val2.x, val3.x) - frameWidth / 2f; float num9 = Mathf.Max(val2.x, val3.x) + frameWidth / 2f; if ((Object)(object)utilityFrame != (Object)null) { Vector3 val4 = (utilityOnRight ? val3 : val2) + utilityOffset; num8 = Mathf.Min(num8, val4.x - frameWidth / 2f); num9 = Mathf.Max(num9, val4.x + frameWidth / 2f); } float num10 = 0f; if (num9 > right - num7) { num10 = right - num7 - num9; } if (num8 + num10 < left + num7) { num10 = left + num7 - num8; } val2.x += num10; val3.x += num10; } string info; Vector3 val5 = RowOffset(val2, visible, out info); Vector3 val6 = (val2 + val3) / 2f; for (int i = 0; i < itemSlotIconFrames.Length; i++) { bool flag2 = i < visible; if (flag2) { int num11 = i / 8; int num12 = i % 8; int num13 = Mathf.Min(8, visible - num11 * 8); Vector3 val7 = val6 - val * ((float)(num13 - 1) / 2f) + val5 * (float)num11; ((Component)itemSlotIconFrames[i]).transform.localPosition = val7 + val * (float)num12; if (!iconsAreChildren) { ((Component)itemSlotIcons[i]).transform.position = ((Component)itemSlotIconFrames[i]).transform.TransformPoint(iconOffsetInFrame); } } if (i >= originalCount) { SetVisible(((Component)itemSlotIconFrames[i]).gameObject, flag2); if (!iconsAreChildren) { SetVisible(((Component)itemSlotIcons[i]).gameObject, flag2); } } } Plugin.Log.LogDebug((object)($"HUD layout: {visible} slots in {(visible + 8 - 1) / 8} row(s), step {val.x:0.#} (vanilla {step.x:0.#}), frame {frameWidth:0.#}, " + string.Format("x {0:0.#}..{1:0.#}, screen {2}, ", val2.x, val3.x, flag ? $"{left:0.#}..{right:0.#}" : "unknown") + "utility " + (((Object)(object)utilityFrame == (Object)null) ? "none" : (utilityOnRight ? "right" : "left")) + ", " + info)); if ((Object)(object)utilityFrame != (Object)null) { Vector3 val8 = (utilityOnRight ? val3 : val2) + utilityOffset; utilityFrame.position = FromParent(val8); if ((Object)(object)utilityIcon != (Object)null) { utilityIcon.position = FromParent(val8 + utilityIconOffset); } } } private static bool TryGetScreenBounds(out float left, out float right) { //IL_0057: 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_0069: 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) left = (right = 0f); Canvas canvas = ((Graphic)hud.itemSlotIconFrames[0]).canvas; if ((Object)(object)canvas == (Object)null) { return false; } Transform transform = ((Component)canvas.rootCanvas).transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); if ((Object)(object)val == (Object)null) { return false; } Vector3[] array = (Vector3[])(object)new Vector3[4]; val.GetWorldCorners(array); float x = ToParent(array[0]).x; float x2 = ToParent(array[2]).x; left = Mathf.Min(x, x2); right = Mathf.Max(x, x2); return right - left > frameWidth; } private static Vector3 RowOffset(Vector3 bottomRowPosition, int visible, out string info) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Max(frameHeight, frameWidth) * 1.3f; float num2 = 1f; float num3 = float.PositiveInfinity; Canvas canvas = ((Graphic)hud.itemSlotIconFrames[0]).canvas; RectTransform val = (RectTransform)(((Object)(object)canvas != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null); if ((Object)(object)val != (Object)null) { Vector3[] array = (Vector3[])(object)new Vector3[4]; val.GetWorldCorners(array); float y = ToParent(array[0]).y; float y2 = ToParent(array[1]).y; num2 = ((y2 >= y) ? 1f : (-1f)); num3 = Mathf.Abs(y2 - bottomRowPosition.y) - num * 0.5f; } int num4 = (visible - 1) / 8; if (num4 > 0 && num * (float)num4 > num3 && num3 > 0f) { num = num3 / (float)num4; } info = $"frame height {frameHeight:0.#}, row offset {num * num2:0.#}"; return new Vector3(0f, num * num2, 0f); } private static float HeightInParent(RectTransform rect) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) Vector3[] array = (Vector3[])(object)new Vector3[4]; rect.GetWorldCorners(array); return Mathf.Abs(ToParent(array[1]).y - ToParent(array[0]).y); } private static float WidthInParent(RectTransform rect) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) Vector3[] array = (Vector3[])(object)new Vector3[4]; rect.GetWorldCorners(array); return Mathf.Abs(ToParent(array[2]).x - ToParent(array[0]).x); } private static Vector3 ToParent(Vector3 world) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)slotParent != (Object)null)) { return world; } return slotParent.InverseTransformPoint(world); } private static Vector3 FromParent(Vector3 local) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)slotParent != (Object)null)) { return local; } return slotParent.TransformPoint(local); } private static void SetVisible(GameObject target, bool visible) { CanvasGroup val = target.GetComponent<CanvasGroup>(); if ((Object)(object)val == (Object)null) { val = target.AddComponent<CanvasGroup>(); } val.alpha = (visible ? 1f : 0f); val.blocksRaycasts = false; val.interactable = false; } private static string RelativePath(Transform child, Transform root) { string text = ((Object)child).name; Transform parent = child.parent; while ((Object)(object)parent != (Object)null && (Object)(object)parent != (Object)(object)root) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } return text; } } public class PlayerProfile { public int Xp; public int SpentXp; public Dictionary<string, int> UniqueSkills = new Dictionary<string, int>(); public List<string> LastBuild; public bool AutoRebuy; public static PlayerProfile From(PlayerProgress progress) { return new PlayerProfile { Xp = progress.Xp, SpentXp = progress.SpentXp, UniqueSkills = Upgrades.All.Where((UpgradeDef u) => u.Unique && progress.GetLevel(u.Type) > 0).ToDictionary((UpgradeDef u) => u.Id, (UpgradeDef u) => progress.GetLevel(u.Type)), LastBuild = new List<string>(progress.LastBuild ?? new List<string>()), AutoRebuy = progress.AutoRebuy }; } public void ApplyTo(PlayerProgress progress) { progress.Xp = Math.Max(0, Xp); progress.SpentXp = Math.Max(0, SpentXp); foreach (UpgradeDef item in Upgrades.All.Where((UpgradeDef u) => u.Unique)) { progress.SetLevel(item.Type, (UniqueSkills != null && UniqueSkills.TryGetValue(item.Id, out var value)) ? value : 0); } if (LastBuild != null) { progress.LastBuild = new List<string>(LastBuild); progress.AutoRebuy = AutoRebuy; } } public static void CopyPermanent(PlayerProgress from, PlayerProgress to) { From(from).ApplyTo(to); to.Name = from.Name; to.ProfileLoaded = from.ProfileLoaded; } } internal static class LocalProfile { private const string File = "LethalRPG_Profile"; private const string Key = "Profile"; private const float RetryInterval = 2f; private static PlayerProfile profile; private static bool loaded; private static bool submitted; private static bool unreadable; private static string lastSaved; private static float nextSubmit; private static PlayerProfile Loaded { get { if (loaded) { return profile; } loaded = true; try { lastSaved = (ES3.KeyExists("Profile", "LethalRPG_Profile") ? ES3.Load<string>("Profile", "LethalRPG_Profile") : null); profile = (string.IsNullOrEmpty(lastSaved) ? null : JsonConvert.DeserializeObject<PlayerProfile>(lastSaved)); } catch (Exception arg) { Plugin.Log.LogError((object)$"Could not read the LethalRPG profile, it will not be changed this session: {arg}"); profile = null; unreadable = true; } return profile; } } public static void ResetSession() { profile = null; loaded = false; unreadable = false; submitted = false; lastSaved = null; nextSubmit = 0f; } public static void Tick() { PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); if (!((Object)(object)val == (Object)null) && RpgState.KeyOf(val) != null && !RpgState.ProgressOf(val).ProfileLoaded && !(Time.time < nextSubmit)) { nextSubmit = Time.time + 2f; submitted = true; if (RpgState.IsServer) { RpgServer.ApplyProfile(val, Loaded); } else { RpgNetwork.SubmitProfile(JsonConvert.SerializeObject((object)Loaded)); } } } public static void OnDataChanged() { PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); if ((Object)(object)val == (Object)null) { return; } PlayerProgress playerProgress = RpgState.ProgressOf(val); if (!playerProgress.ProfileLoaded || !submitted || unreadable) { return; } profile = PlayerProfile.From(playerProgress); string text = JsonConvert.SerializeObject((object)profile); if (text == lastSaved) { return; } try { ES3.Save<string>("Profile", text, "LethalRPG_Profile"); lastSaved = text; } catch (Exception arg) { Plugin.Log.LogError((object)$"Could not save the LethalRPG profile: {arg}"); } } } internal static class LocalSkills { private const int MedicMinimumHealth = 20; private static readonly FieldRef<PlayerControllerB, float> TimeSinceSwitchingSlots = AccessTools.FieldRefAccess<PlayerControllerB, float>("timeSinceSwitchingSlots"); private static int lastHealth = -1; private static float lastDamageTime; private static float healAccumulator; private static int secondWindUsed; private const float SecondWindGrace = 1.5f; private static float graceUntil; private static DoorLock pickingDoor; private static float pickProgress; private static InputAction interactAction; private static readonly FieldRef<TerminalAccessibleObject, bool> InCooldown = AccessTools.FieldRefAccess<TerminalAccessibleObject, bool>("inCooldown"); private const float HackRange = 4f; private const float HackAngle = 30f; private const float HackSearchInterval = 2f; private static TerminalAccessibleObject[] hackables = (TerminalAccessibleObject[])(object)new TerminalAccessibleObject[0]; private static float nextHackSearch; private static TerminalAccessibleObject hacking; private static float hackProgress; private static bool waitForRelease; private static string ownTip; private static bool tipSetThisFrame; private static readonly HashSet<CauseOfDeath> SurvivableKills = new HashSet<CauseOfDeath> { (CauseOfDeath)12, (CauseOfDeath)7, (CauseOfDeath)3, (CauseOfDeath)8, (CauseOfDeath)16 }; private const int KeyRaycastMask = 2816; private const float KeyRaycastDistance = 3f; private static int SecondWindCharges => Mathf.RoundToInt(RpgState.Param(UpgradeType.SecondWind, "ChargesPerDay")); public static int SecondWindChargesPerDay => SecondWindCharges; public static int SecondWindLeft => Mathf.Max(0, SecondWindCharges - secondWindUsed); private static int SecondWindHealthLeft => Mathf.Max(1, Mathf.RoundToInt(RpgState.Param(UpgradeType.SecondWind, "HealthLeft"))); public static void ResetSession() { lastHealth = -1; healAccumulator = 0f; secondWindUsed = 0; graceUntil = 0f; pickingDoor = null; pickProgress = 0f; interactAction = null; hackables = (TerminalAccessibleObject[])(object)new TerminalAccessibleObject[0]; nextHackSearch = 0f; hacking = null; waitForRelease = false; ownTip = null; tipSetThisFrame = false; } public static void ResetDaily() { secondWindUsed = 0; } public static void Tick(PlayerControllerB player) { if (player.isPlayerDead) { lastHealth = -1; StopPicking(); StopHacking(player); } else { EndCriticalInjury(player); TickMedic(player); TickFastHands(player); if (!TickLockpicker(player)) { TickFieldHacker(player); } TickWristwatch(player); } if (!tipSetThisFrame && ownTip != null && (Object)(object)player.cursorTip != (Object)null && ((TMP_Text)player.cursorTip).text == ownTip) { ((TMP_Text)player.cursorTip).text = ""; ownTip = null; } tipSetThisFrame = false; int line = DangerHud.Tick(player, 1); line = InfectionHud.Tick(player, line); line = CompassHud.Tick(player, line); line = ProspectorHud.Tick(player, line); StatusHud.Tick(player, line); } private static void EndCriticalInjury(PlayerControllerB player) { if (player.criticallyInjured && player.health >= 20) { player.MakeCriticallyInjured(false); } } private static void TickWristwatch(PlayerControllerB player) { HUDManager instance = HUDManager.Instance; if ((Object)(object)instance != (Object)null && instance.Clock != null && player.isInsideFactory && instance.Clock.targetAlpha < 1f && RpgState.Has(player, UpgradeType.Wristwatch)) { instance.SetClockVisible(true); } } private static void TickFieldHacker(PlayerControllerB player) { bool flag = InteractHeld() && !player.isTypingChat && !player.inTerminalMenu && !player.inSpecialInteractAnimation; if (!flag) { waitForRelease = false; } TerminalAccessibleObject val = ((RpgState.Has(player, UpgradeType.FieldHacker) && TipIsFree(player)) ? HackTarget(player) : null); if ((Object)(object)val == (Object)null) { StopHacking(player); return; } string text = HackableName(val); string text2 = ((val.isBigDoor && text == "door") ? "Override" : "Hack"); if (InCooldown.Invoke(val)) { StopHacking(player); SetTip(player, text2 + " " + text + " : rebooting..."); return; } if (!flag || waitForRelease) { StopHacking(player); SetTip(player, text2 + " " + text + " : [Hold E]"); return; } if ((Object)(object)val != (Object)(object)hacking) { hacking = val; hackProgress = 0f; } float num = RpgState.Param(UpgradeType.FieldHacker, "Seconds") / RpgState.HoldInteractionMultiplier(player); hackProgress += Time.deltaTime; SetTip(player, $"Hacking {text}... {Mathf.CeilToInt(Mathf.Max(0f, num - hackProgress))}s"); ShowHoldBar(hackProgress / num); if (hackProgress >= num) { val.CallFunctionFromTerminal(); Plugin.Log.LogInfo((object)("Field Hacker: " + text + " " + val.objectCode + " triggered")); StopHacking(player); waitForRelease = true; } } private static TerminalAccessibleObject HackTarget(PlayerControllerB player) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007c: 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_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) if (Time.time >= nextHackSearch) { nextHackSearch = Time.time + 2f; hackables = Object.FindObjectsOfType<TerminalAccessibleObject>(); } if ((Object)(object)player.gameplayCamera == (Object)null) { return null; } Transform transform = ((Component)player.gameplayCamera).transform; TerminalAccessibleObject result = null; float num = 30f; TerminalAccessibleObject[] array = hackables; foreach (TerminalAccessibleObject val in array) { if ((Object)(object)val == (Object)null || !((Behaviour)val).isActiveAndEnabled) { continue; } Vector3 position = ((Component)val).transform.position; Vector3 val2 = position - transform.position; if (!(((Vector3)(ref val2)).magnitude > 4f)) { float num2 = Vector3.Angle(transform.forward, val2); if (!(num2 >= num) && !IsExplodedMine(val) && InSight(transform.position, position, val)) { num = num2; result = val; } } } return result; } private static bool IsExplodedMine(TerminalAccessibleObject target) { Landmine val = ((Component)target).GetComponentInChildren<Landmine>() ?? ((Component)target).GetComponentInParent<Landmine>(); if ((Object)(object)val != (Object)null) { return val.hasExploded; } return false; } private static bool InSight(Vector3 eye, Vector3 target, TerminalAccessibleObject hackable) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) RaycastHit val = default(RaycastHit); if ((Object)(object)StartOfRound.Instance == (Object)null || !Physics.Linecast(eye, target, ref val, StartOfRound.Instance.collidersAndRoomMask, (QueryTriggerInteraction)1)) { return true; } if (!((Object)(object)((Component)((RaycastHit)(ref val)).collider).GetComponentInParent<TerminalAccessibleObject>() == (Object)(object)hackable)) { return Vector3.Distance(((RaycastHit)(ref val)).point, target) < 1f; } return true; } private static bool Has<T>(Component target) where T : Component { if (!((Object)(object)target.GetComponentInParent<T>() != (Object)null)) { return (Object)(object)target.GetComponentInChildren<T>() != (Object)null; } return true; } private static string HackableName(TerminalAccessibleObject target) { if (!Has<Turret>((Component)(object)target)) { if (!Has<Landmine>((Component)(object)target)) { if (!Has<SpikeRoofTrap>((Component)(object)target)) { if (!target.isBigDoor) { return "system"; } return "door"; } return "spike trap"; } return "mine"; } return "turret"; } private static bool TipIsFree(PlayerControllerB player) { if ((Object)(object)player.cursorTip == (Object)null || player.inSpecialMenu || ((Object)(object)player.quickMenuManager != (Object)null && player.quickMenuManager.isMenuOpen)) { return false; } string text = ((TMP_Text)player.cursorTip).text; if (string.IsNullOrEmpty(text) || text == ownTip) { return true; } InteractTrigger hoveringOverTrigger = player.hoveringOverTrigger; if ((Object)(object)hoveringOverTrigger != (Object)null && !hoveringOverTrigger.interactable) { return text == hoveringOverTrigger.disabledHoverTip; } return false; } private static void SetTip(PlayerControllerB player, string text) { ownTip = text; tipSetThisFrame = true; ((TMP_Text)player.cursorTip).text = text; } private static void ShowHoldBar(float fill) { HUDManager instance = HUDManager.Instance; if ((Object)(object)instance != (Object)null) { instance.holdInteractionFillAmount.fillAmount = Mathf.Clamp01(fill); if ((Object)(object)instance.holdInteractionCanvasGroup != (Object)null) { instance.holdInteractionCanvasGroup.alpha = 1f; } } } private static void StopHacking(PlayerControllerB player) { if ((Object)(object)hacking != (Object)null && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.holdInteractionFillAmount.fillAmount = 0f; } hacking = null; hackProgress = 0f; } private static void TickMedic(PlayerControllerB player) { if (lastHealth >= 0 && player.health < lastHealth) { lastDamageTime = Time.time; healAccumulator = 0f; } lastHealth = player.health; float num = RpgState.HealthRegenPerSecond(player); int num2 = RpgState.MaxHealth(player); if (num <= 0f || player.criticallyInjured || player.health < 20 || player.health >= num2 || Time.time - lastDamageTime < RpgState.Param(UpgradeType.Medic, "RegenDelaySeconds")) { healAccumulator = 0f; return; } healAccumulator += num * Time.deltaTime; if (healAccumulator >= 1f) { int num3 = Mathf.FloorToInt(healAccumulator); healAccumulator -= num3; player.health = Mathf.Min(num2, player.health + num3); lastHealth = player.health; if ((Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.UpdateHealthUI(player.health, false); } } } private static bool SecondWindReady(PlayerControllerB player) { if (secondWindUsed < SecondWindCharges && ((NetworkBehaviour)player).IsOwner && !player.isPlayerDead && player.AllowPlayerDeath()) { return RpgState.Has(player, UpgradeType.SecondWind); } return false; } private static bool InGrace(PlayerControllerB player) { if (Time.time < graceUntil && ((NetworkBehaviour)player).IsOwner) { return !player.isPlayerDead; } return false; } private static void ConsumeSecondWind(string what) { secondWindUsed++; graceUntil = Time.time + 1.5f; int secondWindCharges = SecondWindCharges; Plugin.Log.LogInfo((object)$"Second Wind saved the local player from {what} ({secondWindUsed}/{secondWindCharges} today)"); if ((Object)(object)HUDManager.Instance != (Object)null) { int num = secondWindCharges - secondWindUsed; HUDManager.Instance.DisplayTip("Second Wind", (num > 0) ? $"You survived a lethal hit! {num} more today." : "You survived a lethal hit! Available again tomorrow.", true, false, "LC_Tip1"); } } public static void ApplySecondWind(PlayerControllerB player, ref int damage) { if (damage > 0 && player.health - damage <= 0) { if (InGrace(player)) { damage = Mathf.Max(0, player.health - SecondWindHealthLeft); } else if (SecondWindReady(player) && (player.criticallyInjured || damage >= 50)) { ConsumeSecondWind("lethal damage"); damage = player.health - SecondWindHealthLeft; } } } public unsafe static bool TrySecondWindOnKill(PlayerControllerB player, CauseOfDeath cause, bool spawnBody) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: 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) if (!SurvivableKills.Contains(cause) || !spawnBody || player.health <= 0 || player.inSpecialInteractAnimation || (Object)(object)player.inAnimationWithEnemy != (Object)null) { return false; } if (InGrace(player)) { return true; } if (!SecondWindReady(player)) { return false; } ConsumeSecondWind(((object)(*(CauseOfDeath*)(&cause))/*cast due to .constrained prefix*/).ToString()); player.DamagePlayer(player.health - SecondWindHealthLeft, true, true, cause, 0, false, default(Vector3)); return true; } private static void TickFastHands(PlayerControllerB player) { if (RpgState.Has(player, UpgradeType.FastHands)) { TimeSinceSwitchingSlots.Invoke(player) += Time.deltaTime * (RpgState.Param(UpgradeType.FastHands, "SwitchSpeedMultiplier") - 1f); } } public static void ApplyFastGrab(PlayerControllerB player) { if (player.isGrabbingObjectAnimation && RpgState.Has(player, UpgradeType.FastHands)) { float num = RpgState.Param(UpgradeType.FastHands, "GrabTimeFactor"); player.grabObjectAnimationTime = Mathf.Max(0.2f, player.grabObjectAnimationTime * num); } } private static bool TickLockpicker(PlayerControllerB player) { DoorLock val = ((RpgState.Has(player, UpgradeType.Lockpicker) && TipIsFree(player)) ? LockedDoorInFocus(player) : null); if ((Object)(object)val == (Object)null) { StopPicking(); return false; } if (!InteractHeld() || player.isTypingChat || player.inTerminalMenu || player.inSpecialInteractAnimation) { StopPicking(); SetTip(player, "Pick lock : [Hold E]"); return true; } if ((Object)(object)val != (Object)(object)pickingDoor) { pickingDoor = val; pickProgress = 0f; } float num = RpgState.Param(UpgradeType.Lockpicker, "Seconds") / RpgState.HoldInteractionMultiplier(player); pickProgress += Time.deltaTime; SetTip(player, $"Picking lock... {Mathf.CeilToInt(Mathf.Max(0f, num - pickProgress))}s"); ShowHoldBar(pickProgress / num); if (pickProgress >= num) { val.UnlockDoorSyncWithServer(); if ((Object)(object)val.twinDoor != (Object)null && val.twinDoor.isLocked) { val.twinDoor.UnlockDoorSyncWithServer(); } Plugin.Log.LogInfo((object)"Lockpicker opened a door"); StopPicking(); } return true; } private static DoorLock LockedDoorInFocus(PlayerControllerB player) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) GrabbableObject currentlyHeldObjectServer = player.currentlyHeldObjectServer; if ((Object)(object)currentlyHeldObjectServer != (Object)null && (Object)(object)currentlyHeldObjectServer.itemProperties != (Object)null && currentlyHeldObjectServer.itemProperties.itemId == 14) { return null; } RaycastHit val = default(RaycastHit); if ((Object)(object)player.gameplayCamera == (Object)null || !Physics.Raycast(new Ray(((Component)player.gameplayCamera).transform.position, ((Component)player.gameplayCamera).transform.forward), ref val, 3f, 2816)) { return null; } DoorLock val2 = DoorOf(((RaycastHit)(ref val)).transform); if (!((Object)(object)val2 != (Object)null) || !val2.isLocked) { return null; } return val2; } private static DoorLock DoorOf(Transform target) { DoorLock component = ((Component)target).GetComponent<DoorLock>(); if ((Object)(object)component != (Object)null) { return component; } TriggerPointToDoor component2 = ((Component)target).GetComponent<TriggerPointToDoor>(); if ((Object)(object)component2 != (Object)null && (Object)(object)component2.pointToDoor != (Object)null) { return component2.pointToDoor; } return ((Component)target).GetComponentInParent<DoorLock>(); } private static bool InteractHeld() { if (interactAction == null && (Object)(object)InputSystem.actions != (Object)null) { interactAction = InputSystem.actions.FindAction("Interact", false); } if (interactAction != null) { return interactAction.IsPressed(); } return false; } private static void StopPicking() { if ((Object)(object)pickingDoor != (Object)null && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.holdInteractionFillAmount.fillAmount = 0f; } pickingDoor = null; pickProgress = 0f; } } public static class Ownership { private static IEnumerable<UpgradeDef> MoneyUpgrades => Upgrades.All.Where((UpgradeDef u) => !u.Unique); public static void ToTeam(RpgData data) { PlayerProgress team = data.Team; bool fullShareBudgets = data.FullShareBudgets; Dictionary<UpgradeType, int> dictionary = new Dictionary<UpgradeType, int>(); foreach (UpgradeDef def in MoneyUpgrades) { dictionary[def.Type] = data.Players.Values.Select((PlayerProgress p) => p.GetLevel(def.Type)).DefaultIfEmpty(0).Max(); } team.Levels = new Dictionary<string, int>(); foreach (KeyValuePair<UpgradeType, int> item in dictionary.Where((KeyValuePair<UpgradeType, int> e) => e.Value > 0)) { team.SetLevel(item.Key, item.Value); } team.Budget = (fullShareBudgets ? OneShareLeft(data.Players.Values) : 0); team.SpentMoney = 0; foreach (PlayerProgress value in data.Players.Values) { if (!fullShareBudgets) { team.Budget += value.Budget; } value.Budget = 0; foreach (UpgradeDef moneyUpgrade in MoneyUpgrades) { value.Levels.Remove(moneyUpgrade.Id); } value.BuildOrder = new List<string>(); value.SpentMoney = 0; } team.BuildOrder = new List<string>(); team.BuildOrder = Builds.CurrentBuild(team); data.FullShareBudgets = false; data.ActiveOwnership = UpgradeOwnership.Team; } private static int OneShareLeft(ICollection<PlayerProgress> players) { long num = players.Select((PlayerProgress p) => (long)Math.Max(0, p.Budget) + (long)Math.Max(0, p.SpentMoney)).DefaultIfEmpty(0L).Max(); long num2 = ((IEnumerable<PlayerProgress>)players).Sum((Func<PlayerProgress, long>)((PlayerProgress p) => Math.Max(0, p.SpentMoney))); return (int)Math.Max(0L, Math.Min(2147483647L, num - num2)); } public static int ToIndividual(RpgData data, IList<string> present, bool fullShare = false) { PlayerProgress team = data.Team; foreach (PlayerProgress value in data.Players.Values) { foreach (UpgradeDef moneyUpgrade in MoneyUpgrades) { value.SetLevel(moneyUpgrade.Type, team.GetLevel(moneyUpgrade.Type)); } value.BuildOrder = new List<string>(team.BuildOrder); value.SpentMoney = 0; } List<string> list = (present ?? new List<string>()).Where(data.Players.ContainsKey).Distinct().ToList(); if (list.Count == 0) { list = data.Players.Keys.ToList(); } int num = 0; if (list.Count > 0) { num = (fullShare ? team.Budget : (team.Budget / list.Count)); int num2 = ((!fullShare) ? (team.Budget - num * list.Count) : 0); for (int i = 0; i < list.Count; i++) { data.Players[list[i]].Budget += num + ((i == 0) ? num2 : 0); } } team.Levels = new Dictionary<string, int>(); team.BuildOrder = new List<string>(); team.Budget = 0; team.SpentMoney = 0; data.FullShareBudgets = fullShare; data.ActiveOwnership = UpgradeOwnership.Individual; return num; } } [BepInPlugin("cucumis.lethalrpg", "LethalRPG", "0.6.0")] public class Plugin : BaseUnityPlugin { public const string Guid = "cucumis.lethalrpg"; public const string Name = "LethalRPG"; public const string Version = "0.6.0"; internal static ManualLogSource Log; internal static RpgConfig Cfg; internal static readonly List<string> FailedPatches = new List<string>(); private readonly Harmony harmony = new Harmony("cucumis.lethalrpg"); private void Awake() { Log = ((BaseUnityPlugin)this).Logger; for (int i = 0; i < Upgrades.All.Length; i++) { if (Upgrades.All[i].Type != (UpgradeType)i) { Log.LogError((object)$"Upgrade table out of order at index {i} ({Upgrades.All[i].Id}); settings sync would break."); } } Cfg = new RpgConfig(((BaseUnityPlugin)this).Config); Cfg.Changed += OnConfigChanged; RpgState.DataChanged += LocalProfile.OnDataChanged; ApplyPatches(); Log.LogInfo((object)("LethalRPG 0.6.0 loaded" + ((FailedPatches.Count > 0) ? string.Format(", {0} patch group(s) failed: {1}", FailedPatches.Count, string.Join(", ", FailedPatches)) : ""))); } private void ApplyPatches() { Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(Plugin).Assembly); foreach (Type type in typesFromAssembly) { try { harmony.CreateClassProcessor(type).Patch(); } catch (Exception arg) { FailedPatches.Add(type.Name); Log.LogError((object)$"Could not apply {type.Name}; the skills it handles will not work. {arg}"); } } } private static void OnConfigChanged() { RpgState.InvalidateLocalSettings(); if (RpgState.IsServer) { RpgServer.SyncOwnership(); } RpgNetwork.BroadcastSync(); RpgState.RaiseDataChanged(); } } internal static class Reports { private const int LineWidth = 48; private static Terminal terminal; public const string NotEnoughMoney = "not enough money"; public const string NotEnoughXp = "not enough XP"; public static int GroupCredits() { if ((Object)(object)terminal == (Object)null) { terminal = Object.FindObjectOfType<Terminal>(); } if (!((Object)(object)terminal != (Object)null)) { return 0; } return terminal.groupCredits; } public static string EnemyName(EnemyAI enemy) { ScanNodeProperties componentInChildren = ((Component)enemy).GetComponentInChildren<ScanNodeProperties>(true); if ((Object)(object)componentInChildren != (Object)null && !string.IsNullOrEmpty(componentInChildren.headerText)) { return componentInChildren.headerText; } if (!((Object)(object)enemy.enemyType != (Object)null)) { return "monster"; } return enemy.enemyType.enemyName; } public static int Money(PlayerProgress progress) { if (RpgState.Settings.Currency != CurrencyMode.GroupCredits) { return RpgState.Settings.Wallet(progress).Budget; } return GroupCredits(); } public static string MoneyLabel(PlayerProgress progress) { if (RpgState.Settings.Currency != CurrencyMode.GroupCredits) { if (!RpgState.Settings.TeamMode) { return $"Budget ${progress.Budget}"; } return $"Team budget ${RpgState.Data.Team.Budget}"; } return $"Credits ${GroupCredits()}"; } public static string ParamText(UpgradeDef def) { return string.Join(", ", def.Params.Select((SkillParam p) => p.Label + ": " + FormatParam(p, RpgState.Param(def.Type, p.Key)))); } private static string FormatParam(SkillParam param, float value) { if (param.IsInt) { if (!(param.Key == "MaxTwoHandedItems") || !(value <= 0f)) { return Mathf.RoundToInt(value).ToString(); } return "unlimited"; } return value.ToString("0.##", CultureInfo.InvariantCulture); } public static string BlockedReason(PlayerControllerB player, UpgradeType type) { RpgSettings settings = RpgState.Settings; PlayerProgress playerProgress = RpgState.ProgressOf(player); UpgradeDef upgradeDef = Upgrades.Get(type); string text = RpgState.DisabledReason(type); if (text != null) { return text; } int num = settings.LevelOf(playerProgress, type); if (num >= settings.For(type).MaxLevel) { if (!upgradeDef.Unique) { return "max level"; } return "owned"; } (UpgradeType, int)? tuple = settings.MissingPrerequisite(type, playerProgress); if (tuple.HasValue) { return "needs " + Upgrades.Describe(tuple.Value.Item1, tuple.Value.Item2); } if (upgradeDef.Unique && !settings.PaysWithXp(type) && !RpgServer.GameSavesNow) { return "in orbit only"; } if (settings.PaysWithXp(type)) { if (playerProgress.Xp < settings.XpPrice(type)) { return "not enough XP"; } return null; } if (Money(playerProgress) < settings.CostForLevel(type, num)) { return "not enough money"; } return null; } public static string PriceText(UpgradeType type, int level) { RpgSettings settings = RpgState.Settings; if (!settings.PaysWithXp(type)) { return "$" + settings.CostForLevel(type, level); } return $"{settings.XpPrice(type)} XP"; } public static List<string> Affordable(PlayerControllerB player, int max) { if ((Object)(object)player == (Object)null) { return new List<string>(); } RpgSettings settings = RpgState.Settings; PlayerProgress progress = RpgState.ProgressOf(player); return (from o in (from o in Upgrades.All.Where((UpgradeDef def) => BlockedReason(player, def.Type) == null).Select(delegate(UpgradeDef def) { int num = settings.LevelOf(progress, def.Type); return (sortCost: settings.PaysWithXp(def.Type) ? (settings.XpPrice(def.Type) * 5) : settings.CostForLevel(def.Type, num), text: Upgrades.Describe(def.Type, num + 1) + " (" + PriceText(def.Type, num) + ")"); }) orderby o.sortCost select o).Take(max) select o.text).ToList(); } public static string Stats(PlayerControllerB player) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(StatsSummary(player, withLists: false)); stringBuilder.AppendLine(); stringBuilder.Append(UpgradeTotals(player)); AppendSkillLists(stringBuilder, player); AppendPresets(stringBuilder); return stringBuilder.ToString(); } private static void AppendPresets(StringBuilder sb) { RpgSettings settings = RpgState.Settings; List<string> list = new List<string>(); if (settings.Difficulty != Difficulty.Normal) { list.Add($"Difficulty: {settings.Difficulty}"); } if (settings.Balancing != Balancing.Standard) { list.Add($"Balancing: {settings.Balancing}"); } if (list.Count > 0) { sb.AppendLine(); sb.AppendLine(string.Join(" ", list)); } } public static string StatsSummary(PlayerControllerB player, bool withLists) { PlayerProgress playerProgress = RpgState.ProgressOf(player); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("YOUR STATS"); stringBuilder.AppendLine(); int num = Mathf.RoundToInt(Mathf.Clamp(player.carryWeight - 1f, 0f, 100f) * 105f); int num2 = Mathf.RoundToInt(Mathf.Clamp(RpgState.EffectiveCarryWeight(player) - 1f, 0f, 100f) * 105f); object arg = ((!player.isPlayerDead) ? player.health : 0); object arg2 = RpgState.MaxHealth(player); GrabbableObject[] itemSlots = player.ItemSlots; stringBuilder.AppendLine($"Health {arg}/{arg2} Slots {((itemSlots != null) ? itemSlots.Length : 4)}"); stringBuilder.AppendLine((num > 0 && num2 != num) ? $"Load {num} lb (feels like {num2} lb)" : $"Load {num} lb"); stringBuilder.AppendLine($"Sprint about {player.sprintTime:0.#} s without load"); stringBuilder.AppendLine($"{MoneyLabel(playerProgress)} XP {playerProgress.Xp} (spent {playerProgress.SpentXp})"); if (RpgState.Has(player, UpgradeType.SecondWind)) { stringBuilder.AppendLine($"Second Wind: {LocalSkills.SecondWindLeft}/{LocalSkills.SecondWindChargesPerDay} left today"); } if (withLists) { stringBuilder.AppendLine(); AppendSkillLists(stringBuilder, player); AppendPresets(stringBuilder); } return stringBuilder.ToString(); } public static string UpgradeTotals(PlayerControllerB player) { RpgSettings settings = RpgState.Settings; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(settings.TeamMode ? "UPGRADES (shared by the crew)" : "UPGRADES"); bool flag = false; foreach (UpgradeDef item in Upgrades.All.Where((UpgradeDef u) => !u.Unique)) { int num = RpgState.Level(player, item.Type); if (num > 0) { flag = true; stringBuilder.AppendLine($" {item.DisplayName,-13} Lv {num,-2} {item.FormatTotal(settings.EffectPerLevel(item.Type), num)}"); } } if (!flag) { stringBuilder.AppendLine(" none yet"); } return stringBuilder.ToString(); } private static void AppendSkillLists(StringBuilder sb, PlayerControllerB player) { AppendWrapped(sb, "SKILLS: ", from u in Upgrades.All where u.Category == UpgradeCategory.Unique && RpgState.Has(player, u.Type) select u.DisplayName); AppendWrapped(sb, "ULTIMATES: ", from u in Upgrades.All where u.IsUltimate && RpgState.Has(player, u.Type) select u.DisplayName); } public static string Crew() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("CREW"); if (RpgState.Settings.TeamMode) { stringBuilder.AppendLine(); stringBuilder.Append(TeamUpgrades()); } foreach (PlayerControllerB item in CrewPlayers()) { stringBuilder.AppendLine(); stringBuilder.Append(CrewMember(item)); } return stringBuilder.ToString(); } public static List<PlayerControllerB> CrewPlayers() { return (from p in RpgState.ConnectedPlayers() where RpgState.KeyOf(p) != null select p).ToList(); } public static string TeamUpgrades() { StringBuilder stringBuilder = new StringBuilder(); PlayerProgress team = RpgState.Data.Team; int num = Upgrades.All.Where((UpgradeDef u) => !u.Unique).Sum((UpgradeDef u) => team.GetLevel(u.Type)); string arg = ((RpgState.Settings.Currency == CurrencyMode.GroupCredits) ? "" : $" ${team.Budget}"); stringBuilder.AppendLine($"TEAM UPGRADES{arg} {num} levels"); AppendWrapped(stringBuilder, " ", from u in Upgrades.All where !u.Unique && team.GetLevel(u.Type) > 0 select $"{u.DisplayName} {team.GetLevel(u.Type)}"); return stringBuilder.ToString(); } public static string CrewMember(PlayerControllerB player, string indent = " ") { StringBuilder stringBuilder = new StringBuilder(); PlayerProgress playerProgress = RpgState.ProgressOf(player); if (RpgState.Settings.TeamMode) { stringBuilder.AppendLine($"{player.playerUsername} {playerProgress.Xp} XP"); } else { int num = Upgrades.All.Where((UpgradeDef u) => !u.Unique).Sum((UpgradeDef u) => RpgState.Level(player, u.Type)); string text = ((RpgState.Settings.Currency == CurrencyMode.GroupCredits) ? "" : $" ${playerProgress.Budget}"); stringBuilder.AppendLine($"{player.playerUsername}{text} {playerProgress.Xp} XP {num} levels"); AppendWrapped(stringBuilder, indent, from u in Upgrades.All where !u.Unique && RpgState.Level(player, u.Type) > 0 select $"{u.DisplayName} {RpgState.Level(player, u.Type)}"); } AppendWrapped(stringBuilder, indent + "Skills: ", from u in Upgrades.All where u.Category == UpgradeCategory.Unique && RpgState.Has(player, u.Type) select u.DisplayName); AppendWrapped(stringBuilder, indent + "Ult: ", from u in Upgrades.All where u.IsUltimate && RpgState.Has(player, u.Type) select u.DisplayName); return stringBuilder.ToString(); } public static string Build(PlayerControllerB player, bool withCommands = true) { RpgSettings settings = RpgState.Settings; PlayerProgress playerProgress = settings.Wallet(RpgState.ProgressOf(player)); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(settings.TeamMode ? "SAVED TEAM BUILD" : "SAVED BUILD"); stringBuilder.AppendLine(); if (playerProgress.LastBuild == null || playerProgress.LastBuild.Count == 0) { stringBuilder.AppendLine("No build saved yet. Your money upgrades are saved automatically when a run ends."); } else { AppendWrapped(stringBuilder, "", Builds.Totals(playerProgress.LastBuild).Split(new string[1] { ", " }, StringSplitOptions.None)); List<UpgradeType> list = Builds.MissingSteps(playerProgress, playerProgress.LastBuild, settings); stringBuilder.AppendLine((list.Count > 0) ? $"Missing: {list.Count} levels for ${Builds.CostOf(settings, playerProgress, list)}" : ((Builds.MissingSteps(playerProgress, playerProgress.LastBuild).Count > 0) ? "You have all of it that this ship offers." : "You have all of it.")); } stringBuilder.AppendLine(); stringBuilder.AppendLine("Auto rebuy: " + (playerProgress.AutoRebuy ? "ON" : "off") + ((settings.Currency == CurrencyMode.GroupCredits) ? " (only with personal budgets)" : "")); if (!withCommands) { return stringBuilder.ToString(); } stringBuilder.AppendLine(); stringBuilder.AppendLine("rpg rebuy buy the missing levels now"); stringBuilder.AppendLine("rpg rebuy auto toggle auto rebuy"); stringBuilder.AppendLine("rpg build save save your current upgrades"); return stringBuilder.ToString(); } private static void AppendWrapped(StringBuilder sb, string prefix, IEnumerable<string> items) { List<string> list = items.ToList(); if (list.Count == 0) { return; } string text = new string(' ', Mathf.Min(prefix.Length, 4)); StringBuilder stringBuilder = new StringBuilder(prefix); for (int i = 0; i < list.Count; i++) { string text2 = list[i] + ((i < list.Count - 1) ? "," : ""); if (stringBuilder.Length > text.Length && stringBuilder.Length + 1 + text2.Length > 48) { sb.AppendLine(stringBuilder.ToString().TrimEnd()); stringBuilder.Clear().Append(text); } if (stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] != ' ') { stringBuilder.Append(' '); } stringBuilder.Append(text2); } sb.AppendLine(stringBuilder.ToString().TrimEnd()); } public static void ResetSession() { terminal = null; } } public enum CurrencyMode { SurplusShare, GroupCredits } public enum ShareSplit { Even, ByContribution, FullShareEach } public enum UniqueSkillCurrency { Xp, Money } public enum Difficulty { Normal, Easy, Hard } public enum UpgradeOwnership { Individual, Team } public enum XpSource { Rank, Scrap, Kill, Survival, Quota, Debug } public class UpgradeSettings { public bool Enabled; public int MaxLevel; public int BaseCost; public float CostMultiplier; public float EffectPerLevel; public int XpPrice; public int RequiredLevel; public int RequiredLevel2; public Dictionary<string, float> Params = new Dictionary<string, float>(); } public class RpgSettings { public CurrencyMode Currency; public int SurplusSharePercent; public bool TakeShareFromGroupCredits; public ShareSplit Split; public UniqueSkillCurrency UniqueCurrency; public bool RequirePrerequisites; public int RefundPercent; public bool ResetOnGameOver; public bool DebugCommands; public Difficulty Difficulty; public Balancing Balancing; public UpgradeOwnership Ownership; public float XpRankMultiplier; public float XpPer100Scrap; public float XpPerKill; public int XpPerSurvivedDay; public int XpPerQuota; public UpgradeSettings[] Upgrades; public string InventoryBlockedBy; public bool TeamMode { get { if (RpgState.Data != null) { return RpgState.Data.ActiveOwnership == UpgradeOwnership.Team; } return false; } } public float PriceFactor { get { if (Difficulty != Difficulty.Easy) { if (Difficulty != Difficulty.Hard) { return 1f; } return 1.4f; } return 0.7f; } } public float XpGainFactor { get { if (Difficulty != Difficulty.Easy) { if (Difficulty != Difficulty.Hard) { return 1f; } return 0.75f; } return 1.5f; } } public float EffectFactor { get { if (Difficulty != Difficulty.Easy) { if (Difficulty != Difficulty.Hard) { return 1f; } return 0.9f; } return 1.2f; } } public UpgradeSettings For(UpgradeType type) { return Upgrades[(int)type]; } public PlayerProgress LevelRecord(PlayerProgress personal, UpgradeType type) { if (!TeamMode || LethalRPG.Upgrades.Get(type).Unique) { return personal; } return RpgState.Data.Team; } public int LevelOf(PlayerProgress personal, UpgradeType type) { return LevelRecord(personal, type).GetLevel(type); } public PlayerProgress Wallet(PlayerProgress personal) { if (!TeamMode) { return personal; } return RpgState.Data.Team; } public int CostForLevel(UpgradeType type, int currentLevel) { UpgradeSettings upgradeSettings = For(type); double num = Math.Round((double)upgradeSettings.BaseCost * Math.Pow(upgradeSettings.CostMultiplier, currentLevel) * (double)PriceFactor); if (!(num >= 2147483647.0)) { return (int)Math.Max(0.0, num); } return int.MaxValue; } public int XpPrice(UpgradeType type) { return (int)Math.Round((float)For(type).XpPrice * PriceFactor); } public float EffectPerLevel(UpgradeType type) { if (type != UpgradeType.Inventory) { return For(type).EffectPerLevel * EffectFactor; } return For(type).EffectPerLevel; } public bool PaysWithXp(UpgradeType type) { if (LethalRPG.Upgrades.Get(type).Unique) { return UniqueCurrency == UniqueSkillCurrency.Xp; } return false; } public int RequiredLevel(UpgradeType type) { UpgradeDef upgradeDef = LethalRPG.Upgrades.Get(type); if (!RequirePrerequisites || !upgradeDef.Unique || !RpgState.IsEnabled(upgradeDef.Requires)) { return 0; } return Math.Min(For(type).RequiredLevel, For(upgradeDef.Requires).MaxLevel); } public int RequiredLevel2(UpgradeType type) { UpgradeDef upgradeDef = LethalRPG.Upgrades.Get(type); if (!RequirePrerequisites || !upgradeDef.IsUltimate || !RpgState.IsEnabled(upgradeDef.Requires2)) { return 0; } return Math.Min(For(type).RequiredLevel2, For(upgradeDef.Requires2).MaxLevel); } public (UpgradeType Type, int Level)? MissingPrerequisite(UpgradeType type, PlayerProgress progress) { UpgradeDef upgradeDef = LethalRPG.Upgrades.Get(type); int num = RequiredLevel(type); if (num > 0 && LevelOf(progress, upgradeDef.Requires) < num) { return (upgradeDef.Requires, num); } int num2 = RequiredLevel2(type); if (num2 > 0 && LevelOf(progress, upgradeDef.Requires2) < num2) { return (upgradeDef.Requires2, num2); } return null; } } public class RpgConfig { private const string GeneralSection = "0. General (host decides)"; private const string XpSection = "0. XP sources (host decides)"; private const string InterfaceSection = "5. Interface (this PC)"; private const string DebugSection = "9. Debug"; public readonly ConfigEntry<CurrencyMode> Currency; public readonly ConfigEntry<int> SurplusSharePercent; public readonly ConfigEntry<bool> TakeShareFromGroupCredits; public readonly ConfigEntry<ShareSplit> Split; public readonly ConfigEntry<UniqueSkillCurrency> UniqueCurrency; public readonly ConfigEntry<bool> RequirePrerequisites; public readonly ConfigEntry<int> RefundPercent; public readonly ConfigEntry<bool> ResetOnGameOver; public readonly ConfigEntry<Difficulty> Difficulty; public readonly ConfigEntry<Balancing> Balancing; public readonly ConfigEntry<UpgradeOwnership> Ownership; public readonly ConfigEntry<float> XpRankMultiplier; public readonly ConfigEntry<float> XpPer100Scrap; public readonly ConfigEntry<float> XpPerKill; public readonly ConfigEntry<int> XpPerSurvivedDay; public readonly ConfigEntry<int> XpPerQuota; public readonly ConfigEntry<bool> DebugCommands; public readonly ConfigEntry<Key> MenuKey; public readonly ConfigEntry<bool> ShowStatusHud; public readonly ConfigEntry<bool> ShowDayReport; private readonly ConfigEntry<bool>[] enabled; private readonly ConfigEntry<int>[] maxLevel; private readonly ConfigEntry<int>[] baseCost; private readonly ConfigEntry<float>[] costMultiplier; private readonly ConfigEntry<float>[] effectPerLevel; private readonly ConfigEntry<int>[] xpPrice; private readonly ConfigEntry<int>[] requiredLevel; private readonly ConfigEntry<int>[] requiredLevel2; private readonly Dictionary<string, Func<float>>[] paramValues; public event Action Changed; public RpgConfig(ConfigFile config) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Expected O, but got Unknown //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Expected O, but got Unknown //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Expected O, but got Unknown //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Expected O, but got Unknown //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Expected O, but got Unknown //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Expected O, but got Unknown //IL_05c4: Unknown result type (might be due to invalid IL or missing references) //IL_05ce: Expected O, but got Unknown //IL_05f9: Unknown result type (might be due to invalid IL or missing references) //IL_0603: Expected O, but got Unknown //IL_0632: Unknown result type (might be due to invalid IL or missing references) //IL_063c: Expected O, but got Unknown //IL_066e: Unknown result type (might be due to invalid IL or missing references) //IL_0678: Expected O, but got Unknown //IL_0433: Unknown result type (might be due to invalid IL or missing references) //IL_043d: Expected O, but got Unknown //IL_0468: Unknown result type (might be due to invalid IL or missing references) //IL_0472: Expected O, but got Unknown //IL_04d8: Unknown result type (might be due to invalid IL or missing references) //IL_04e2: Expected O, but got Unknown //IL_0530: Unknown result type (might be due to invalid IL or missing references) //IL_053a: Expected O, but got Unknown Currency = config.Bind<CurrencyMode>("0. General (host decides)", "Currency", CurrencyMode.SurplusShare, "How upgrades are paid for.\nSurplusShare: when a quota is completed, a share of the surplus (money above the quota) goes into the players' personal upgrade budgets.\nGroupCredits: upgrades are bought directly with the group credits, the same money used in the shop."); SurplusSharePercent = config.Bind<int>("0. General (host decides)", "SurplusSharePercent", 50, new ConfigDescription("SurplusShare only: percentage of the quota surplus that becomes upgrade budget. ShareSplit decides whether the players split it or each of them gets all of it (FullShareEach); in team mode it goes into the team budget.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); TakeShareFromGroupCredits = config.Bind<bool>("0. General (host decides)", "TakeShareFromGroupCredits", true, "SurplusShare only: if enabled, the share is taken out of the group credits (it is the same money you would spend in the shop), only once per quota even with ShareSplit = FullShareEach. If disabled, the budget is an extra bonus on top."); Split = config.Bind<ShareSplit>("0. General (host decides)", "ShareSplit", ShareSplit.ByContribution, "SurplusShare with personal upgrades only: who gets the quota share. Even: the share is split evenly between the players. ByContribution: the share is split by the scrap value each player brought to the ship during the quota (evenly if nobody brought anything). FullShareEach: every player gets the whole share for their own upgrades (e.g. $187 each instead of $187 split between three), but the group credits pay it only once (if TakeShareFromGroupCredits is on). Everyone then progresses as fast as in team mode, where one upgrade counts for every player, bu