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 CrewUpgrades v0.1.0
BepInEx/plugins/Serenta-CrewUpgrades/CrewUpgrades.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("CrewUpgrades")] [assembly: AssemblyDescription("Configurable, team-shared terminal upgrades for Lethal Company.")] [assembly: AssemblyCompany("Serenta")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyVersion("0.1.0.0")] namespace CrewUpgrades; public sealed class PurchaseResult { public bool Success; public int Cost; public int NewLevel; public string Reason; } [Serializable] public sealed class TeamProgression { public int[] Levels = new int[7]; public int Revision; public int QuotaMarker = -1; public int ExplorationsCompleted; public bool CompanyRolled; public bool CompanyActive; public string[] CompletedTripIds = Array.Empty<string>(); public int Level(UpgradeKind kind) { if (!UpgradeSettings.Known(kind) || Levels == null || (int)kind >= Levels.Length) { return 0; } return Math.Min(Math.Max(0, Levels[(int)kind]), (kind == UpgradeKind.CompanyRelations) ? 1 : 5); } public TeamProgression ValidatedCopy(UpgradeSettings settings) { UpgradeSettings upgradeSettings = (settings ?? new UpgradeSettings()).ValidatedCopy(); TeamProgression teamProgression = new TeamProgression { Revision = Math.Max(0, Revision), QuotaMarker = Math.Max(-1, QuotaMarker), ExplorationsCompleted = Math.Max(0, ExplorationsCompleted), CompanyRolled = CompanyRolled }; for (int i = 0; i < 7; i++) { teamProgression.Levels[i] = Math.Min(Level((UpgradeKind)i), upgradeSettings.MaxLevel((UpgradeKind)i)); } HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); List<string> list = new List<string>(); if (CompletedTripIds != null) { string[] completedTripIds = CompletedTripIds; for (int j = 0; j < completedTripIds.Length; j++) { string text = ValidTripId(completedTripIds[j]); if (text != null && hashSet.Add(text)) { list.Add(text); } } } teamProgression.CompletedTripIds = list.ToArray(); if (teamProgression.ExplorationsCompleted >= upgradeSettings.CompanyExplorationCount) { teamProgression.CompanyRolled = true; } teamProgression.CompanyActive = CompanyActive && teamProgression.CompanyRolled && teamProgression.Level(UpgradeKind.CompanyRelations) > 0; return teamProgression; } public PurchaseResult TryPurchase(UpgradeKind kind, int expectedLevel, ref int credits, UpgradeSettings settings) { lock (this) { UpgradeSettings upgradeSettings = (settings ?? new UpgradeSettings()).ValidatedCopy(); int num = Level(kind); if (!UpgradeSettings.Known(kind)) { return Failure("unknown-upgrade", num); } if (!upgradeSettings.IsEnabled(kind)) { return Failure("disabled", num); } if (num != expectedLevel) { return Failure("stale-level", num); } if (num >= upgradeSettings.MaxLevel(kind)) { return Failure("max-level", num); } int num2 = upgradeSettings.Price(kind, num + 1); if (num2 < 0) { return Failure("invalid-price", num); } if (credits < 0 || credits < num2) { return Failure("insufficient-credits", num); } int[] array = new int[7]; for (int i = 0; i < 7; i++) { array[i] = Level((UpgradeKind)i); } array[(int)kind] = num + 1; credits -= num2; Levels = array; AdvanceRevision(); return new PurchaseResult { Success = true, Cost = num2, NewLevel = num + 1, Reason = "ok" }; } } private static PurchaseResult Failure(string reason, int level) { return new PurchaseResult { Success = false, Cost = 0, NewLevel = level, Reason = reason }; } public bool CompleteExploration(string tripId, double draw, UpgradeSettings settings) { lock (this) { string text = ValidTripId(tripId); if (text == null || double.IsNaN(draw) || double.IsInfinity(draw) || draw < 0.0 || draw >= 1.0) { return false; } UpgradeSettings upgradeSettings = (settings ?? new UpgradeSettings()).ValidatedCopy(); if (!upgradeSettings.Enabled) { return false; } List<string> list = new List<string>(CompletedTripIds ?? Array.Empty<string>()); if (list.Contains(text)) { return false; } list.Add(text); int num = Math.Max(0, ExplorationsCompleted); CompletedTripIds = list.ToArray(); ExplorationsCompleted = ((num < int.MaxValue) ? (num + 1) : int.MaxValue); bool result = false; if (!CompanyRolled && ExplorationsCompleted >= upgradeSettings.CompanyExplorationCount) { CompanyRolled = true; result = (CompanyActive = num < upgradeSettings.CompanyExplorationCount && upgradeSettings.IsEnabled(UpgradeKind.CompanyRelations) && Level(UpgradeKind.CompanyRelations) > 0 && draw < (double)upgradeSettings.CompanyChance); } AdvanceRevision(); return result; } } public void NewQuota(int marker) { if (marker < 0) { throw new ArgumentOutOfRangeException("marker"); } lock (this) { if (QuotaMarker != marker) { QuotaMarker = marker; ExplorationsCompleted = 0; CompanyRolled = (CompanyActive = false); CompletedTripIds = Array.Empty<string>(); AdvanceRevision(); } } } public void ResetAll() { lock (this) { Levels = new int[7]; QuotaMarker = -1; ExplorationsCompleted = 0; CompanyRolled = (CompanyActive = false); CompletedTripIds = Array.Empty<string>(); AdvanceRevision(); } } private void AdvanceRevision() { Revision = ((Revision < int.MaxValue) ? (Math.Max(0, Revision) + 1) : int.MaxValue); } private static string ValidTripId(string id) { if (string.IsNullOrWhiteSpace(id) || id.Length > 256) { return null; } return id.Trim(); } } public enum UpgradeKind { Stamina, ActionTraining, Strength, CarryWeight, Interaction, Equipment, CompanyRelations } [Serializable] public sealed class UpgradeSettings { public bool Enabled = true; public bool ResetOnFired = true; public bool RequireAllPlayers = true; public bool[] UpgradeEnabled = new bool[7] { true, true, true, true, true, true, true }; public int[] StaminaPrices = new int[5] { 500, 1000, 2000, 4000, 8000 }; public int[] ActionPrices = new int[5] { 200, 400, 800, 1600, 3200 }; public int[] StrengthPrices = new int[5] { 500, 1000, 5000, 10000, 20000 }; public int[] CarryPrices = new int[5] { 500, 1000, 2000, 4000, 8000 }; public int[] InteractionPrices = new int[5] { 200, 400, 800, 1600, 3200 }; public int[] EquipmentPrices = new int[5] { 500, 1000, 2000, 4000, 8000 }; public int CompanyPrice = 1000; public float[] StaminaCapacity = new float[6] { 100f, 120f, 140f, 160f, 180f, 200f }; public float[] SprintSpeed = new float[6] { 1f, 1.02f, 1.04f, 1.06f, 1.08f, 1.1f }; public float[] JumpFraction = new float[6] { 0.08f, 0.07f, 0.06f, 0.05f, 0.04f, 0.03f }; public float[] StandingRecoverySeconds = new float[6] { 15f, 13.5f, 12f, 10.5f, 9f, 7.5f }; public float[] WalkingRecoverySeconds = new float[6] { 20f, 18.5f, 17f, 15.5f, 14f, 12.5f }; public float BaseSprintSeconds = 11f; public float SprintStopFraction = 0.1f; public float[] FootstepRange = new float[6] { 1f, 0.9f, 0.8f, 0.7f, 0.6f, 0.5f }; public float[] CrouchMultiplier = new float[6] { 1f, 1.1f, 1.2f, 1.3f, 1.4f, 1.5f }; public float[] LadderMultiplier = new float[6] { 1f, 1.1f, 1.2f, 1.3f, 1.4f, 1.5f }; public float[] UphillPenalty = new float[6] { 1f, 0.8f, 0.6f, 0.4f, 0.2f, 0f }; public float[] CritChance = new float[6] { 0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f }; public int[] MeleeBonus = new int[6] { 0, 0, 0, 1, 1, 1 }; public float[] CritMultiplier = new float[6] { 2f, 2f, 2f, 2f, 2f, 2f }; public float[] WeightFraction = new float[6] { 1f, 0.85f, 0.7f, 0.55f, 0.4f, 0.25f }; public float[] HoldDuration = new float[6] { 1f, 0.9f, 0.8f, 0.7f, 0.6f, 0.5f }; public float[] ReachBonus = new float[6] { 0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f }; public float[] BatteryLifetime = new float[6] { 1f, 1.2f, 1.4f, 1.6f, 1.8f, 2f }; public float[] JetAcceleration = new float[6] { 1f, 1.1f, 1.2f, 1.3f, 1.4f, 1.5f }; public float[] JetCap = new float[6] { 1f, 1.05f, 1.1f, 1.15f, 1.2f, 1.25f }; public float[] NormalTeleportWait = new float[6] { 3f, 2.5f, 2f, 1.5f, 1f, 0.5f }; public float[] InverseTeleportWait = new float[6] { 5f, 4.5f, 4f, 3.5f, 3f, 2.5f }; public bool[] HoldCategories = new bool[10] { true, true, true, true, true, true, true, true, true, true }; public int CompanyExplorationCount = 3; public float CompanyChance = 0.1f; public float CompanyMultiplier = 2f; public bool CompanyNotify = true; public int MaxLevel(UpgradeKind kind) { if (Known(kind)) { if (kind != UpgradeKind.CompanyRelations) { return 5; } return 1; } return 0; } public bool IsEnabled(UpgradeKind kind) { if (Enabled && Known(kind) && UpgradeEnabled != null && UpgradeEnabled.Length == 7) { return UpgradeEnabled[(int)kind]; } return false; } public int Price(UpgradeKind kind, int nextLevel) { if (nextLevel < 1 || nextLevel > MaxLevel(kind)) { return -1; } int[] array; switch (kind) { case UpgradeKind.CompanyRelations: return CompanyPrice; case UpgradeKind.Stamina: array = StaminaPrices; break; case UpgradeKind.ActionTraining: array = ActionPrices; break; case UpgradeKind.Strength: array = StrengthPrices; break; case UpgradeKind.CarryWeight: array = CarryPrices; break; case UpgradeKind.Interaction: array = InteractionPrices; break; case UpgradeKind.Equipment: array = EquipmentPrices; break; default: return -1; } if (array == null || array.Length != 5) { return -1; } return array[nextLevel - 1]; } internal static bool Known(UpgradeKind kind) { if (kind >= UpgradeKind.Stamina) { return kind < (UpgradeKind)7; } return false; } private static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private static float Scalar(float value, float fallback, float min, float max) { if (!Finite(value) || !(value >= min) || !(value <= max)) { return fallback; } return value; } private static int Scalar(int value, int fallback, int min, int max) { if (value < min || value > max) { return fallback; } return value; } private static float[] Values(float[] values, float[] defaults, float min, float max) { float[] array = (float[])defaults.Clone(); if (values == null || values.Length != array.Length) { return array; } for (int i = 0; i < array.Length; i++) { array[i] = Scalar(values[i], defaults[i], min, max); } return array; } private static int[] Values(int[] values, int[] defaults, int min, int max) { int[] array = (int[])defaults.Clone(); if (values == null || values.Length != array.Length) { return array; } for (int i = 0; i < array.Length; i++) { array[i] = Scalar(values[i], defaults[i], min, max); } return array; } private static bool[] Values(bool[] values, bool[] defaults) { if (values == null || values.Length != defaults.Length) { return (bool[])defaults.Clone(); } return (bool[])values.Clone(); } public UpgradeSettings ValidatedCopy() { UpgradeSettings upgradeSettings = new UpgradeSettings(); return new UpgradeSettings { Enabled = Enabled, ResetOnFired = ResetOnFired, RequireAllPlayers = RequireAllPlayers, UpgradeEnabled = Values(UpgradeEnabled, upgradeSettings.UpgradeEnabled), StaminaPrices = Values(StaminaPrices, upgradeSettings.StaminaPrices, 0, int.MaxValue), ActionPrices = Values(ActionPrices, upgradeSettings.ActionPrices, 0, int.MaxValue), StrengthPrices = Values(StrengthPrices, upgradeSettings.StrengthPrices, 0, int.MaxValue), CarryPrices = Values(CarryPrices, upgradeSettings.CarryPrices, 0, int.MaxValue), InteractionPrices = Values(InteractionPrices, upgradeSettings.InteractionPrices, 0, int.MaxValue), EquipmentPrices = Values(EquipmentPrices, upgradeSettings.EquipmentPrices, 0, int.MaxValue), CompanyPrice = Scalar(CompanyPrice, upgradeSettings.CompanyPrice, 0, int.MaxValue), StaminaCapacity = Values(StaminaCapacity, upgradeSettings.StaminaCapacity, 1f, 1000000f), SprintSpeed = Values(SprintSpeed, upgradeSettings.SprintSpeed, 0.01f, 100f), JumpFraction = Values(JumpFraction, upgradeSettings.JumpFraction, 0f, 1f), StandingRecoverySeconds = Values(StandingRecoverySeconds, upgradeSettings.StandingRecoverySeconds, 0.01f, 86400f), WalkingRecoverySeconds = Values(WalkingRecoverySeconds, upgradeSettings.WalkingRecoverySeconds, 0.01f, 86400f), BaseSprintSeconds = Scalar(BaseSprintSeconds, upgradeSettings.BaseSprintSeconds, 0.01f, 86400f), SprintStopFraction = Scalar(SprintStopFraction, upgradeSettings.SprintStopFraction, 0f, 0.99f), FootstepRange = Values(FootstepRange, upgradeSettings.FootstepRange, 0f, 10f), CrouchMultiplier = Values(CrouchMultiplier, upgradeSettings.CrouchMultiplier, 0.01f, 100f), LadderMultiplier = Values(LadderMultiplier, upgradeSettings.LadderMultiplier, 0.01f, 100f), UphillPenalty = Values(UphillPenalty, upgradeSettings.UphillPenalty, 0f, 1f), CritChance = Values(CritChance, upgradeSettings.CritChance, 0f, 1f), MeleeBonus = Values(MeleeBonus, upgradeSettings.MeleeBonus, 0, 1000), CritMultiplier = Values(CritMultiplier, upgradeSettings.CritMultiplier, 1f, 100f), WeightFraction = Values(WeightFraction, upgradeSettings.WeightFraction, 0f, 1f), HoldDuration = Values(HoldDuration, upgradeSettings.HoldDuration, 0.01f, 100f), ReachBonus = Values(ReachBonus, upgradeSettings.ReachBonus, 0f, 100f), BatteryLifetime = Values(BatteryLifetime, upgradeSettings.BatteryLifetime, 0.01f, 100f), JetAcceleration = Values(JetAcceleration, upgradeSettings.JetAcceleration, 0.01f, 100f), JetCap = Values(JetCap, upgradeSettings.JetCap, 0.01f, 100f), NormalTeleportWait = Values(NormalTeleportWait, upgradeSettings.NormalTeleportWait, 0f, 3600f), InverseTeleportWait = Values(InverseTeleportWait, upgradeSettings.InverseTeleportWait, 0f, 3600f), HoldCategories = Values(HoldCategories, upgradeSettings.HoldCategories), CompanyExplorationCount = Scalar(CompanyExplorationCount, upgradeSettings.CompanyExplorationCount, 1, 1000), CompanyChance = Scalar(CompanyChance, upgradeSettings.CompanyChance, 0f, 1f), CompanyMultiplier = Scalar(CompanyMultiplier, upgradeSettings.CompanyMultiplier, 1f, 1000f), CompanyNotify = CompanyNotify }; } } [BepInPlugin("Serenta.CrewUpgrades", "Crew Upgrades", "0.1.0")] public sealed class CrewPlugin : BaseUnityPlugin { public const string Guid = "Serenta.CrewUpgrades"; public const string Version = "0.1.0"; private void Awake() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown Harmony val = null; try { ConfigFile val2 = new ConfigFile(Path.Combine(Paths.ConfigPath, "CrewUpgrades.cfg"), true); UpgradeRuntime.Initialize(((BaseUnityPlugin)this).Logger, new UpgradeConfig(val2).Read()); UpgradeTerminal.Chinese = val2.Bind<bool>("Local UI", "Chinese", true, "Chinese terminal text. Local preference; not synchronized. Set false for English.").Value; val = new Harmony("Serenta.CrewUpgrades"); val.PatchAll(typeof(CrewPlugin).Assembly); if (PlayerUpgrades.HasPatchFailures || EquipmentUpgrades.HasPatchFailures) { val.UnpatchSelf(); throw new InvalidOperationException("Game IL compatibility checks failed. See the exact player/equipment patch errors above."); } StrengthUpgrades.Initialize(val); CrewRuntimeHost.Initialize(val); ((BaseUnityPlugin)this).Logger.LogInfo((object)"CrewUpgrades 0.1.0 initialized. Config: CrewUpgrades.cfg. Terminal: upgrades."); } catch (Exception ex) { if (val != null) { val.UnpatchSelf(); } ((BaseUnityPlugin)this).Logger.LogError((object)("CrewUpgrades initialization failed: " + ex)); ((Behaviour)this).enabled = false; } } } internal static class CrewRuntimeHost { private static bool initialized; private static bool quitting; private static Harmony harmony; private static CrewDriver driver; private static bool optionalHooksReady; private static float optionalHooksAfter; internal static void Initialize(Harmony patches) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown if (!initialized) { initialized = true; harmony = patches; optionalHooksAfter = Time.realtimeSinceStartup + 4f; SceneManager.sceneLoaded += SceneLoaded; Application.onBeforeRender += new UnityAction(Ensure); Application.quitting += Shutdown; Ensure(); } } private static void SceneLoaded(Scene scene, LoadSceneMode mode) { Ensure(); } private static void Ensure() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown if (!quitting && !((Object)(object)driver != (Object)null)) { GameObject val = new GameObject("CrewUpgrades.Runtime"); Object.DontDestroyOnLoad((Object)val); driver = val.AddComponent<CrewDriver>(); } } internal static void TickOptionalHooks() { if (!optionalHooksReady && !(Time.realtimeSinceStartup < optionalHooksAfter)) { optionalHooksReady = true; StrengthUpgrades.Initialize(harmony); } } private static void Shutdown() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown quitting = true; SceneManager.sceneLoaded -= SceneLoaded; Application.onBeforeRender -= new UnityAction(Ensure); Application.quitting -= Shutdown; UpgradeRuntime.Shutdown(); Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } } public sealed class CrewDriver : MonoBehaviour { private void Update() { CrewRuntimeHost.TickOptionalHooks(); UpgradeRuntime.Tick(); } } internal static class EquipmentUpgrades { private static readonly List<string> failures = new List<string>(); internal static string[] PatchFailures { get { lock (failures) { return failures.ToArray(); } } } internal static bool HasPatchFailures { get { lock (failures) { return failures.Count > 0; } } } internal static void Failure(string patch, string reason) { string text = patch + ": " + reason + ". Original instructions retained."; lock (failures) { failures.Add(text); } ManualLogSource log = UpgradeRuntime.Log; if (log != null) { log.LogError((object)("Equipment IL compatibility failure: " + text)); } } private static float At(float[] values, int level, float fallback) { if (values == null || level < 0 || level >= values.Length) { return fallback; } float num = values[level]; if (!float.IsNaN(num) && !float.IsInfinity(num)) { return num; } return fallback; } private static int Level() { return UpgradeRuntime.Level(UpgradeKind.Equipment); } internal static float ContinuousBatteryDuration(float original) { int num = Level(); if (num != 0) { return original * At(UpgradeRuntime.Settings.BatteryLifetime, num, 1f); } return original; } internal static float TriggerBatteryCost(float original) { int num = Level(); if (num != 0) { return original / Math.Max(0.01f, At(UpgradeRuntime.Settings.BatteryLifetime, num, 1f)); } return original; } internal static float Acceleration(float original) { int num = Level(); if (num != 0) { return original * At(UpgradeRuntime.Settings.JetAcceleration, num, 1f); } return original; } internal static float PropulsionCap(float original) { int num = Level(); if (num != 0) { return original * At(UpgradeRuntime.Settings.JetCap, num, 1f); } return original; } internal static float PowerCap(float original) { int num = Level(); if (num == 0) { return original; } return Math.Max(original, 400f * At(UpgradeRuntime.Settings.JetCap, num, 1f)); } internal static float NormalTeleportWait(float original) { int num = Level(); if (num != 0) { return At(UpgradeRuntime.Settings.NormalTeleportWait, num, original); } return original; } internal static float InverseTeleportStep(float original) { int num = Level(); if (num != 0) { return At(UpgradeRuntime.Settings.InverseTeleportWait, num, original * 5f) / 5f; } return original; } internal static bool Field(CodeInstruction code, Type type, string name) { if (code.opcode == OpCodes.Ldfld && code.operand is FieldInfo fieldInfo && fieldInfo.DeclaringType == type) { return fieldInfo.Name == name; } return false; } internal static bool Float(CodeInstruction code, float value) { if (code.opcode == OpCodes.Ldc_R4 && code.operand is float num) { return Math.Abs(num - value) < 1E-06f; } return false; } internal static bool Method(CodeInstruction code, Type type, string name) { if ((code.opcode == OpCodes.Call || code.opcode == OpCodes.Callvirt) && code.operand is MethodInfo methodInfo && methodInfo.DeclaringType == type) { return methodInfo.Name == name; } return false; } internal static bool WaitConstructor(CodeInstruction code) { if (code.opcode == OpCodes.Newobj && code.operand is ConstructorInfo constructorInfo && constructorInfo.DeclaringType == typeof(WaitForSeconds) && constructorInfo.GetParameters().Length == 1) { return constructorInfo.GetParameters()[0].ParameterType == typeof(float); } return false; } internal static List<CodeInstruction> Copy(IEnumerable<CodeInstruction> source) { return source.Select((Func<CodeInstruction, CodeInstruction>)((CodeInstruction c) => new CodeInstruction(c))).ToList(); } internal static CodeInstruction Call(string name) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown return new CodeInstruction(OpCodes.Call, (object)typeof(EquipmentUpgrades).GetMethod(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); } internal static IEnumerable<CodeInstruction> Battery(IEnumerable<CodeInstruction> source, bool trigger) { List<CodeInstruction> list = Copy(source); List<int> list2 = new List<int>(); for (int i = 1; i + 1 < list.Count; i++) { if (Field(list[i - 1], typeof(GrabbableObject), "itemProperties") && Field(list[i], typeof(Item), "batteryUsage") && list[i + 1].opcode == (trigger ? OpCodes.Sub : OpCodes.Div)) { list2.Add(i); } } if (list2.Count != 1) { Failure(trigger ? "UseItemBatteries" : "GrabbableObject.Update", "expected one original battery usage calculation, found " + list2.Count); return list; } list.Insert(list2[0] + 1, Call(trigger ? "TriggerBatteryCost" : "ContinuousBatteryDuration")); return list; } internal static IEnumerable<CodeInstruction> Jetpack(IEnumerable<CodeInstruction> source) { List<CodeInstruction> list = Copy(source); List<int> list2 = new List<int>(); List<int> list3 = new List<int>(); List<int> list4 = new List<int>(); for (int i = 0; i + 1 < list.Count; i++) { if (Field(list[i], typeof(JetpackItem), "jetpackAcceleration") && list[i + 1].opcode == OpCodes.Mul) { list2.Add(i); } if (Float(list[i], 500f) && Method(list[i + 1], typeof(Mathf), "Clamp")) { list3.Add(i); } if (Float(list[i], 400f) && Method(list[i + 1], typeof(Vector3), "ClampMagnitude")) { list4.Add(i); } } if (list2.Count != 1 || list3.Count != 1 || list4.Count != 1) { Failure("JetpackItem.Update", "expected acceleration/power/output sites 1/1/1, found " + list2.Count + "/" + list3.Count + "/" + list4.Count); return list; } foreach (KeyValuePair<int, string> item in new KeyValuePair<int, string>[3] { new KeyValuePair<int, string>(list2[0], "Acceleration"), new KeyValuePair<int, string>(list3[0], "PowerCap"), new KeyValuePair<int, string>(list4[0], "PropulsionCap") }.OrderByDescending((KeyValuePair<int, string> x) => x.Key)) { list.Insert(item.Key + 1, Call(item.Value)); } return list; } internal static IEnumerable<CodeInstruction> NormalTeleport(IEnumerable<CodeInstruction> source) { List<CodeInstruction> list = Copy(source); List<int> list2 = new List<int>(); for (int i = 0; i + 1 < list.Count; i++) { if (!Float(list[i], 3f) || !WaitConstructor(list[i + 1])) { continue; } for (int j = Math.Max(0, i - 8); j < i; j++) { if (list[j].opcode == OpCodes.Ldstr && object.Equals(list[j].operand, "Teleport A")) { list2.Add(i); break; } } } if (list2.Count != 1) { Failure("ShipTeleporter.beamUpPlayer.MoveNext", "expected one Teleport A wait, found " + list2.Count); return list; } list.Insert(list2[0] + 1, Call("NormalTeleportWait")); return list; } internal static IEnumerable<CodeInstruction> InverseTeleport(IEnumerable<CodeInstruction> source) { List<CodeInstruction> list = Copy(source); List<int> list2 = new List<int>(); bool flag = false; for (int i = 0; i + 1 < list.Count; i++) { if (Float(list[i], 1f) && WaitConstructor(list[i + 1])) { list2.Add(i); } if (list[i].opcode == OpCodes.Ldc_I4_5 && (list[i + 1].opcode == OpCodes.Blt || list[i + 1].opcode == OpCodes.Blt_S)) { flag = true; } } if (list2.Count != 1 || !flag) { Failure("ShipTeleporter.beamOutPlayer.MoveNext", "expected one1-second wait in the unchanged five-pass loop"); return list; } list.Insert(list2[0] + 1, Call("InverseTeleportStep")); return list; } } [HarmonyPatch(typeof(GrabbableObject), "Update")] internal static class EquipmentContinuousBatteryPatch { [HarmonyTranspiler] [HarmonyPriority(0)] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return EquipmentUpgrades.Battery(instructions, trigger: false); } } [HarmonyPatch(typeof(GrabbableObject), "UseItemBatteries")] internal static class EquipmentTriggerBatteryPatch { [HarmonyTranspiler] [HarmonyPriority(0)] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return EquipmentUpgrades.Battery(instructions, trigger: true); } } [HarmonyPatch(typeof(JetpackItem), "Update")] internal static class EquipmentJetpackPatch { [HarmonyTranspiler] [HarmonyPriority(0)] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return EquipmentUpgrades.Jetpack(instructions); } } [HarmonyPatch] internal static class EquipmentNormalTeleporterPatch { private static MethodBase TargetMethod() { return AccessTools.EnumeratorMoveNext((MethodBase)AccessTools.DeclaredMethod(typeof(ShipTeleporter), "beamUpPlayer", (Type[])null, (Type[])null)); } [HarmonyTranspiler] [HarmonyPriority(0)] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return EquipmentUpgrades.NormalTeleport(instructions); } } [HarmonyPatch] internal static class EquipmentInverseTeleporterPatch { private static MethodBase TargetMethod() { return AccessTools.EnumeratorMoveNext((MethodBase)AccessTools.DeclaredMethod(typeof(ShipTeleporter), "beamOutPlayer", (Type[])null, (Type[])null)); } [HarmonyTranspiler] [HarmonyPriority(0)] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { return EquipmentUpgrades.InverseTeleport(instructions); } } internal static class InteractionUpgrades { [HarmonyPatch] private static class ReachPatch { private static IEnumerable<MethodBase> TargetMethods() { yield return AccessTools.Method(typeof(PlayerControllerB), "BeginGrabObject", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(PlayerControllerB), "SetHoverTipAndCurrentInteractTrigger", (Type[])null, (Type[])null); } [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod) { List<CodeInstruction> list = PlayerUpgradeIl.Copy(instructions); Dictionary<int, List<CodeInstruction>> edits = new Dictionary<int, List<CodeInstruction>>(); int num = 0; for (int i = 0; i < list.Count; i++) { if (PlayerUpgradeIl.Field(list[i], typeof(PlayerControllerB), "grabDistance")) { PlayerUpgradeIl.After(edits, i, PlayerUpgradeIl.Call(typeof(InteractionUpgrades), "Reach")); num++; } } if (num != 1) { PlayerUpgrades.PatchFailure(__originalMethod.Name, "expected one ordinary reach read, found " + num); return list; } return PlayerUpgradeIl.Apply(list, edits); } } [HarmonyPatch(typeof(PlayerControllerB), "ClickHoldInteraction")] private static class HoldPatch { [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown List<CodeInstruction> list = PlayerUpgradeIl.Copy(instructions); Dictionary<int, List<CodeInstruction>> edits = new Dictionary<int, List<CodeInstruction>>(); int num = 0; for (int i = 0; i < list.Count; i++) { if (PlayerUpgradeIl.Field(list[i], typeof(InteractTrigger), "timeToHold")) { PlayerUpgradeIl.After(edits, i, new CodeInstruction(OpCodes.Ldarg_0, (object)null), new CodeInstruction(OpCodes.Ldfld, (object)AccessTools.Field(typeof(PlayerControllerB), "hoveringOverTrigger")), PlayerUpgradeIl.Call(typeof(InteractionUpgrades), "HoldDuration")); num++; } } if (num != 1) { PlayerUpgrades.PatchFailure("PlayerControllerB.ClickHoldInteraction", "expected one hold duration read, found " + num); return list; } return PlayerUpgradeIl.Apply(list, edits); } } internal static float Reach(float original) { int num = PlayerUpgrades.ActiveLevel(UpgradeKind.Interaction); if (num != 0) { return original + Mathf.Max(0f, PlayerUpgrades.At(UpgradeRuntime.Settings.ReachBonus, num, 0f)); } return original; } internal static float HoldDuration(float original, InteractTrigger trigger) { int num = PlayerUpgrades.ActiveLevel(UpgradeKind.Interaction); if (num == 0 || original <= 0f || (Object)(object)trigger == (Object)null) { return original; } int num2 = AllowedCategory(trigger); bool[] holdCategories = UpgradeRuntime.Settings.HoldCategories; if (num2 < 0 || holdCategories == null || num2 >= holdCategories.Length || !holdCategories[num2]) { return original; } return original * Mathf.Max(0.01f, PlayerUpgrades.At(UpgradeRuntime.Settings.HoldDuration, num, 1f)); } internal static int AllowedCategory(InteractTrigger trigger) { int num = EventCategory((UnityEventBase)(object)trigger.onInteract); if (num < 0) { return EventCategory((UnityEventBase)(object)trigger.onInteractEarly); } return num; } private static int EventCategory(UnityEventBase action) { if (action == null) { return -1; } for (int i = 0; i < action.GetPersistentEventCount(); i++) { Object persistentTarget = action.GetPersistentTarget(i); string persistentMethodName = action.GetPersistentMethodName(i); if (persistentTarget is EntranceTeleport && persistentMethodName == "TeleportPlayer") { return 0; } DoorLock val = (DoorLock)(object)((persistentTarget is DoorLock) ? persistentTarget : null); if (val != null && persistentMethodName == "OpenOrCloseDoor") { if (!val.isLocked) { return 1; } continue; } if (persistentTarget is SteamValveHazard && persistentMethodName == "FixValve") { return 2; } if (persistentTarget is StartMatchLever && persistentMethodName == "PullLever") { return 3; } if (persistentTarget is UnlockableSuit && persistentMethodName == "SwitchSuitToThis") { return 4; } if (persistentTarget is PlaceableObjectsSurface && persistentMethodName == "PlaceObject") { return 5; } if (persistentTarget is BeltBagItem && persistentMethodName == "TryCheckBagContents") { return 6; } if (persistentTarget is VehicleController) { switch (persistentMethodName) { case "TakeControlOfVehicle": case "SetPassengerInCar": return 7; case "RemoveKeyFromIgnition": return 8; case "ToggleHoodOpenLocalClient": return 9; } } } return -1; } } internal static class PlayerUpgrades { [HarmonyPatch(typeof(PlayerControllerB), "Update")] private static class MovementPatch { [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Expected O, but got Unknown //IL_034e: Unknown result type (might be due to invalid IL or missing references) //IL_0354: Expected O, but got Unknown List<CodeInstruction> list = PlayerUpgradeIl.Copy(instructions); Dictionary<int, List<CodeInstruction>> edits = new Dictionary<int, List<CodeInstruction>>(); int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; for (int i = 0; i < list.Count; i++) { if (i > 0 && PlayerUpgradeIl.Field(list[i], typeof(PlayerControllerB), "carryWeight") && list[i - 1].opcode == OpCodes.Ldarg_0) { PlayerUpgradeIl.After(edits, i, PlayerUpgradeIl.Call(typeof(PlayerUpgrades), "EffectiveWeight")); num++; } if (i + 1 < list.Count && PlayerUpgradeIl.Field(list[i], typeof(PlayerControllerB), "sprintMultiplier") && PlayerUpgradeIl.Method(list[i + 1], typeof(Vector3), "op_Multiply")) { PlayerUpgradeIl.After(edits, i, new CodeInstruction(OpCodes.Ldarg_0, (object)null), new CodeInstruction(OpCodes.Ldfld, (object)AccessTools.Field(typeof(PlayerControllerB), "isSprinting")), PlayerUpgradeIl.Call(typeof(PlayerUpgrades), "SprintVelocity")); num4++; } if (i > 0 && PlayerUpgradeIl.Float(list[i], 0.1f) && PlayerUpgradeIl.Field(list[i - 1], typeof(PlayerControllerB), "sprintMeter")) { PlayerUpgradeIl.After(edits, i, PlayerUpgradeIl.Call(typeof(PlayerUpgrades), "SprintStop")); num6++; } if (i >= 3 && i + 2 < list.Count && PlayerUpgradeIl.Float(list[i], 1.5f) && PlayerUpgradeIl.Field(list[i - 3], typeof(PlayerControllerB), "isCrouching") && list[i + 1].opcode == OpCodes.Div) { PlayerUpgradeIl.After(edits, i, PlayerUpgradeIl.Call(typeof(PlayerUpgrades), "CrouchDivisor")); num2++; } if (PlayerUpgradeIl.Field(list[i], typeof(PlayerControllerB), "climbSpeed")) { PlayerUpgradeIl.After(edits, i, PlayerUpgradeIl.Call(typeof(PlayerUpgrades), "LadderSpeed")); num3++; } if (i >= 10 && PlayerUpgradeIl.Method(list[i], typeof(Mathf), "Max") && PlayerUpgradeIl.Float(list[i - 9], 0.8f) && PlayerUpgradeIl.Field(list[i - 5], typeof(PlayerControllerB), "slopeIntensity") && PlayerUpgradeIl.Field(list[i - 3], typeof(PlayerControllerB), "slopeModifier") && PlayerUpgradeIl.IsLocalLoad(list[i - 10]) && list[i - 1].opcode == OpCodes.Add) { PlayerUpgradeIl.After(edits, i, new CodeInstruction(list[i - 10].opcode, list[i - 10].operand), PlayerUpgradeIl.Call(typeof(PlayerUpgrades), "UphillResult")); num5++; } } if (num != 3 || num2 != 1 || num3 != 1 || num4 != 1 || num5 != 1 || num6 != 2) { PatchFailure("PlayerControllerB.Update", $"expected weight/crouch/ladder/sprint/slope/stop=3/1/1/1/1/2, found {num}/{num2}/{num3}/{num4}/{num5}/{num6}"); return list; } return PlayerUpgradeIl.Apply(list, edits); } } [HarmonyPatch(typeof(PlayerControllerB), "LateUpdate")] private static class StaminaPatch { [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = PlayerUpgradeIl.Copy(instructions); Dictionary<int, List<CodeInstruction>> edits = new Dictionary<int, List<CodeInstruction>>(); int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; for (int i = 0; i < list.Count; i++) { if (PlayerUpgradeIl.Field(list[i], typeof(PlayerControllerB), "carryWeight")) { PlayerUpgradeIl.After(edits, i, PlayerUpgradeIl.Call(typeof(PlayerUpgrades), "EffectiveWeight")); num4++; } if (!PlayerUpgradeIl.Field(list[i], typeof(PlayerControllerB), "sprintTime") || i + 1 >= list.Count) { continue; } if (list[i + 1].opcode == OpCodes.Div) { PlayerUpgradeIl.After(edits, i, PlayerUpgradeIl.Call(typeof(PlayerUpgrades), "CapacitySprintSeconds")); num++; } else if (i + 3 < list.Count && list[i + 2].opcode == OpCodes.Add && list[i + 3].opcode == OpCodes.Div) { if (PlayerUpgradeIl.Float(list[i + 1], 4f)) { PlayerUpgradeIl.After(edits, i + 2, PlayerUpgradeIl.Call(typeof(PlayerUpgrades), "StandingRecovery")); num2++; } else if (PlayerUpgradeIl.Float(list[i + 1], 9f)) { PlayerUpgradeIl.After(edits, i + 2, PlayerUpgradeIl.Call(typeof(PlayerUpgrades), "WalkingRecovery")); num3++; } } } if (num != 2 || num2 != 1 || num3 != 1 || num4 != 1) { PatchFailure("PlayerControllerB.LateUpdate", $"expected drain/standing/walking/weight=2/1/1/1, found {num}/{num2}/{num3}/{num4}"); return list; } return PlayerUpgradeIl.Apply(list, edits); } } [HarmonyPatch(typeof(PlayerControllerB), "Jump_performed")] private static class JumpPatch { [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> list = PlayerUpgradeIl.Copy(instructions); Dictionary<int, List<CodeInstruction>> edits = new Dictionary<int, List<CodeInstruction>>(); int num = 0; for (int i = 1; i + 1 < list.Count; i++) { if (PlayerUpgradeIl.Float(list[i], 0.08f) && PlayerUpgradeIl.Field(list[i - 1], typeof(PlayerControllerB), "sprintMeter") && !(list[i + 1].opcode != OpCodes.Sub)) { PlayerUpgradeIl.After(edits, i, PlayerUpgradeIl.Call(typeof(PlayerUpgrades), "JumpCost")); num++; } } if (num != 1) { PatchFailure("PlayerControllerB.Jump_performed", "expected one fixed jump cost, found " + num); return list; } return PlayerUpgradeIl.Apply(list, edits); } } [HarmonyPatch] private static class FootstepPatch { private static IEnumerable<MethodBase> TargetMethods() { yield return AccessTools.Method(typeof(PlayerControllerB), "PlayFootstepLocal", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(PlayerControllerB), "PlayFootstepServer", (Type[])null, (Type[])null); } [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod) { List<CodeInstruction> list = PlayerUpgradeIl.Copy(instructions); Dictionary<int, List<CodeInstruction>> edits = new Dictionary<int, List<CodeInstruction>>(); int num = 0; for (int i = 0; i + 5 < list.Count; i++) { if ((PlayerUpgradeIl.Float(list[i], 22f) || PlayerUpgradeIl.Float(list[i], 17f)) && PlayerUpgradeIl.Method(list[i + 5], typeof(RoundManager), "PlayAudibleNoise")) { PlayerUpgradeIl.After(edits, i, PlayerUpgradeIl.Call(typeof(PlayerUpgrades), "FootstepDistance")); num++; } } if (num != 2) { PatchFailure(__originalMethod.Name, "expected two movement footstep ranges, found " + num); return list; } return PlayerUpgradeIl.Apply(list, edits); } } private static readonly List<string> failures = new List<string>(); public static string[] PatchFailures { get { lock (failures) { return failures.ToArray(); } } } public static bool HasPatchFailures { get { lock (failures) { return failures.Count != 0; } } } internal static void PatchFailure(string method, string detail) { string text = method + ": " + detail + ". This patch was left unchanged."; lock (failures) { failures.Add(text); } ManualLogSource log = UpgradeRuntime.Log; if (log != null) { log.LogError((object)("Player/interaction IL compatibility failure: " + text)); } } internal static int ActiveLevel(UpgradeKind kind) { if (!UpgradeRuntime.Ready) { return 0; } return UpgradeRuntime.Level(kind); } internal static float At(float[] values, int level, float fallback) { if (values == null || level < 0 || level >= values.Length) { return fallback; } float num = values[level]; if (!float.IsNaN(num) && !float.IsInfinity(num)) { return num; } return fallback; } internal static float EffectiveWeight(float actual) { int num = ActiveLevel(UpgradeKind.CarryWeight); if (num == 0) { return actual; } float num2 = Mathf.Clamp01(At(UpgradeRuntime.Settings.WeightFraction, num, 1f)); if (!(actual > 1f)) { return actual; } return 1f + (actual - 1f) * num2; } internal static float CapacitySprintSeconds(float original) { int num = ActiveLevel(UpgradeKind.Stamina); if (num == 0) { return original; } UpgradeSettings settings = UpgradeRuntime.Settings; float num2 = Mathf.Max(1f, At(settings.StaminaCapacity, 0, 100f)); float num3 = Mathf.Max(1f, At(settings.StaminaCapacity, num, num2)); float num4 = settings.BaseSprintSeconds; if (float.IsNaN(num4) || float.IsInfinity(num4) || num4 <= 0f) { num4 = 11f; } return original * (num4 / 11f) * (num3 / num2); } internal static float StandingRecovery(float original) { int num = ActiveLevel(UpgradeKind.Stamina); if (num != 0) { return Mathf.Max(0.01f, At(UpgradeRuntime.Settings.StandingRecoverySeconds, num, original)); } return original; } internal static float WalkingRecovery(float original) { int num = ActiveLevel(UpgradeKind.Stamina); if (num != 0) { return Mathf.Max(0.01f, At(UpgradeRuntime.Settings.WalkingRecoverySeconds, num, original)); } return original; } internal static float JumpCost(float original) { int num = ActiveLevel(UpgradeKind.Stamina); if (num != 0) { return Mathf.Clamp01(At(UpgradeRuntime.Settings.JumpFraction, num, original)); } return original; } internal static float SprintVelocity(float original, bool isSprinting) { int num = ActiveLevel(UpgradeKind.Stamina); if (num != 0 && isSprinting) { return original * Mathf.Max(0.01f, At(UpgradeRuntime.Settings.SprintSpeed, num, 1f)); } return original; } internal static float SprintStop(float original) { if (ActiveLevel(UpgradeKind.Stamina) == 0) { return original; } float sprintStopFraction = UpgradeRuntime.Settings.SprintStopFraction; if (!float.IsNaN(sprintStopFraction) && !float.IsInfinity(sprintStopFraction)) { return Mathf.Clamp(sprintStopFraction, 0f, 0.99f); } return original; } internal static float CrouchDivisor(float original) { int num = ActiveLevel(UpgradeKind.ActionTraining); if (num != 0) { return original / Mathf.Max(0.01f, At(UpgradeRuntime.Settings.CrouchMultiplier, num, 1f)); } return original; } internal static float LadderSpeed(float original) { int num = ActiveLevel(UpgradeKind.ActionTraining); if (num != 0) { return original * Mathf.Max(0.01f, At(UpgradeRuntime.Settings.LadderMultiplier, num, 1f)); } return original; } internal static float UphillResult(float result, float beforeSlope) { int num = ActiveLevel(UpgradeKind.ActionTraining); if (num == 0 || result >= beforeSlope) { return result; } float num2 = Mathf.Clamp01(At(UpgradeRuntime.Settings.UphillPenalty, num, 1f)); return beforeSlope + (result - beforeSlope) * num2; } internal static float FootstepDistance(float original) { int num = ActiveLevel(UpgradeKind.ActionTraining); if (num != 0) { return original * Mathf.Max(0f, At(UpgradeRuntime.Settings.FootstepRange, num, 1f)); } return original; } } internal static class PlayerUpgradeIl { private static readonly Dictionary<OpCode, OpCode> LongBranches = new Dictionary<OpCode, OpCode> { [OpCodes.Br_S] = OpCodes.Br, [OpCodes.Brfalse_S] = OpCodes.Brfalse, [OpCodes.Brtrue_S] = OpCodes.Brtrue, [OpCodes.Beq_S] = OpCodes.Beq, [OpCodes.Bge_S] = OpCodes.Bge, [OpCodes.Bge_Un_S] = OpCodes.Bge_Un, [OpCodes.Bgt_S] = OpCodes.Bgt, [OpCodes.Bgt_Un_S] = OpCodes.Bgt_Un, [OpCodes.Ble_S] = OpCodes.Ble, [OpCodes.Ble_Un_S] = OpCodes.Ble_Un, [OpCodes.Blt_S] = OpCodes.Blt, [OpCodes.Blt_Un_S] = OpCodes.Blt_Un, [OpCodes.Bne_Un_S] = OpCodes.Bne_Un, [OpCodes.Leave_S] = OpCodes.Leave }; internal static List<CodeInstruction> Copy(IEnumerable<CodeInstruction> instructions) { return instructions.Select((Func<CodeInstruction, CodeInstruction>)((CodeInstruction i) => new CodeInstruction(i))).ToList(); } internal static bool Field(CodeInstruction i, Type owner, string name) { if (i.opcode == OpCodes.Ldfld && i.operand is FieldInfo fieldInfo && fieldInfo.DeclaringType == owner) { return fieldInfo.Name == name; } return false; } internal static bool Float(CodeInstruction i, float value) { if (i.opcode == OpCodes.Ldc_R4 && i.operand is float num) { return Math.Abs(num - value) < 1E-06f; } return false; } internal static bool Method(CodeInstruction i, Type owner, string name) { if ((i.opcode == OpCodes.Call || i.opcode == OpCodes.Callvirt) && i.operand is MethodInfo methodInfo && methodInfo.DeclaringType == owner) { return methodInfo.Name == name; } return false; } internal static bool IsLocalLoad(CodeInstruction i) { if (!(i.opcode == OpCodes.Ldloc) && !(i.opcode == OpCodes.Ldloc_S) && !(i.opcode == OpCodes.Ldloc_0) && !(i.opcode == OpCodes.Ldloc_1) && !(i.opcode == OpCodes.Ldloc_2)) { return i.opcode == OpCodes.Ldloc_3; } return true; } internal static CodeInstruction Call(Type owner, string name) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown return new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(owner, name, (Type[])null, (Type[])null)); } internal static void After(Dictionary<int, List<CodeInstruction>> edits, int index, params CodeInstruction[] inserted) { if (!edits.TryGetValue(index, out var value)) { value = (edits[index] = new List<CodeInstruction>()); } value.AddRange(inserted); } internal static IEnumerable<CodeInstruction> Apply(List<CodeInstruction> code, Dictionary<int, List<CodeInstruction>> edits) { List<CodeInstruction> list = new List<CodeInstruction>(code.Count + edits.Sum((KeyValuePair<int, List<CodeInstruction>> x) => x.Value.Count)); for (int num = 0; num < code.Count; num++) { list.Add(code[num]); if (edits.TryGetValue(num, out var value)) { list.AddRange(value); } } foreach (CodeInstruction item in list) { if (LongBranches.TryGetValue(item.opcode, out var value2)) { item.opcode = value2; } } return list; } } public static class StrengthUpgrades { internal sealed class AttackScope { internal readonly AttackScope Parent; internal readonly GrabbableObject Weapon; internal readonly UpgradeSettings Settings; internal readonly int Level; internal readonly Dictionary<EnemyAI, HitEntry> Hits = new Dictionary<EnemyAI, HitEntry>(EnemyReferenceComparer.Instance); internal AttackScope(AttackScope parent, GrabbableObject weapon, UpgradeSettings settings, int level) { Parent = parent; Weapon = weapon; Settings = settings; Level = level; } } internal sealed class HitEntry { internal int Force; internal bool Accepted; } private sealed class EnemyReferenceComparer : IEqualityComparer<EnemyAI> { internal static readonly EnemyReferenceComparer Instance = new EnemyReferenceComparer(); public bool Equals(EnemyAI first, EnemyAI second) { return first == second; } public int GetHashCode(EnemyAI value) { return RuntimeHelpers.GetHashCode(value); } } private static readonly MethodInfo InterfaceHit = AccessTools.Method(typeof(IHittable), "Hit", new Type[5] { typeof(int), typeof(Vector3), typeof(PlayerControllerB), typeof(bool), typeof(int) }, (Type[])null); private static readonly MethodInfo EnemyHit = AccessTools.Method(typeof(EnemyAI), "HitEnemyOnLocalClient", new Type[5] { typeof(int), typeof(Vector3), typeof(PlayerControllerB), typeof(bool), typeof(int) }, (Type[])null); private static readonly MethodInfo InterfaceWrapper = AccessTools.Method(typeof(StrengthUpgrades), "HitTarget", (Type[])null, (Type[])null); private static readonly MethodInfo EnemyWrapper = AccessTools.Method(typeof(StrengthUpgrades), "HitGloveEnemy", (Type[])null, (Type[])null); private static readonly HashSet<MethodBase> Installed = new HashSet<MethodBase>(); private static readonly object RandomGate = new object(); private static readonly Random Random = new Random(); [ThreadStatic] private static AttackScope current; internal static Func<double> Draw = NextDraw; public static void Initialize(Harmony harmony) { if (harmony == null) { throw new ArgumentNullException("harmony"); } Install(harmony, typeof(Shovel), "HitShovel", typeof(bool), glove: false, required: true); Install(harmony, typeof(KnifeItem), "HitKnife", typeof(bool), glove: false, required: true); InstallOptional(harmony, "PiggyVarietyMod", "PiggyVarietyMod.Patches.AxeItem", "HitAxe"); InstallOptional(harmony, "PiggysVarietyMod", "PiggyVarietyMod.Patches.AxeItem", "HitAxe"); string[] array = new string[6] { "AntiqueSwordScript", "CandyDispenserScript", "CrowbarScript", "FireAxeScript", "SizableScissorsScript", "SledgehammerScript" }; foreach (string text in array) { InstallOptional(harmony, "UsualScrap", "UsualScrap.Behaviors." + text, "HitMeleeWeapon"); } Type type = FindType("LCGoldScrapMod", "GoldenGloveScript"); if (type != null) { Install(harmony, type, "CollideWhileExtending", typeof(RaycastHit), glove: true, required: false); } } public static void Reset() { current = null; } private static void InstallOptional(Harmony harmony, string assembly, string type, string method) { Type type2 = FindType(assembly, type); if (type2 != null) { Install(harmony, type2, method, typeof(bool), glove: false, required: false); } } private static Type FindType(string assemblyName, string typeName) { return (from a in AppDomain.CurrentDomain.GetAssemblies() where string.Equals(a.GetName().Name, assemblyName, StringComparison.Ordinal) select a.GetType(typeName, throwOnError: false)).FirstOrDefault((Type t) => t != null); } private static void Install(Harmony harmony, Type type, string name, Type argument, bool glove, bool required) { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Expected O, but got Unknown //IL_0114: Expected O, but got Unknown //IL_0114: Expected O, but got Unknown MethodInfo method = type.GetMethod(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { argument }, null); if (method == null || method.ReturnType != typeof(void) || !typeof(GrabbableObject).IsAssignableFrom(type)) { string text = "Melee upgrade hook does not match " + type.FullName + "." + name; if (required) { throw new InvalidOperationException(text); } ManualLogSource log = UpgradeRuntime.Log; if (log != null) { log.LogWarning((object)(text + "; this optional weapon remains unmodified.")); } return; } lock (Installed) { if (Installed.Contains(method)) { return; } try { harmony.Patch((MethodBase)method, new HarmonyMethod(AccessTools.Method(typeof(StrengthUpgrades), "BeginAttack", (Type[])null, (Type[])null)), (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(StrengthUpgrades), glove ? "RewriteGloveContact" : "RewriteMeleeContact", (Type[])null, (Type[])null)) { priority = 0 }, new HarmonyMethod(AccessTools.Method(typeof(StrengthUpgrades), "EndAttack", (Type[])null, (Type[])null)), (HarmonyMethod)null); Installed.Add(method); ManualLogSource log2 = UpgradeRuntime.Log; if (log2 != null) { log2.LogInfo((object)("Strength contact hook: " + type.FullName + "." + name)); } } catch (Exception ex) { if (required) { throw; } ManualLogSource log3 = UpgradeRuntime.Log; if (log3 != null) { log3.LogError((object)("Strength contact hook failed for " + type.FullName + "." + name + "; upgrade support for this weapon is unavailable: " + ex)); } } } } internal static void BeginAttack(GrabbableObject __instance, out AttackScope __state) { __state = null; if (!((Object)(object)__instance == (Object)null)) { int level = (UpgradeRuntime.Ready ? UpgradeRuntime.Level(UpgradeKind.Strength) : 0); UpgradeSettings settings = UpgradeRuntime.Settings; __state = new AttackScope(current, __instance, settings, level); current = __state; } } internal static Exception EndAttack(Exception __exception, AttackScope __state) { if (__state != null) { current = __state.Parent; } return __exception; } internal static IEnumerable<CodeInstruction> RewriteMeleeContact(IEnumerable<CodeInstruction> instructions) { return RewriteContacts(instructions, glove: false); } internal static IEnumerable<CodeInstruction> RewriteGloveContact(IEnumerable<CodeInstruction> instructions) { return RewriteContacts(instructions, glove: true); } private static IEnumerable<CodeInstruction> RewriteContacts(IEnumerable<CodeInstruction> instructions, bool glove) { List<CodeInstruction> list = instructions.Select((Func<CodeInstruction, CodeInstruction>)((CodeInstruction i) => new CodeInstruction(i))).ToList(); int num = 0; int num2 = 0; foreach (CodeInstruction item in list) { if (CodeInstructionExtensions.Calls(item, InterfaceHit)) { item.opcode = OpCodes.Call; item.operand = InterfaceWrapper; num++; } else if (glove && CodeInstructionExtensions.Calls(item, EnemyHit)) { item.opcode = OpCodes.Call; item.operand = EnemyWrapper; num2++; } } if ((!glove && num == 0) || (glove && num2 != 1)) { throw new InvalidOperationException("Audited melee contact call site changed; refusing a broad damage patch."); } return list; } public static bool HitTarget(IHittable target, int force, Vector3 direction, PlayerControllerB playerWhoHit, bool playHitSFX, int hitID) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) EnemyAICollisionDetect val = (EnemyAICollisionDetect)(object)((target is EnemyAICollisionDetect) ? target : null); if ((Object)(object)val == (Object)null || (val.onlyCollideWhenGrounded && !val.alwaysAllowHitting) || !CanUpgrade(val.mainScript, force, playerWhoHit)) { return target.Hit(force, direction, playerWhoHit, playHitSFX, hitID); } HitEntry hitEntry = Contact(val.mainScript, force); if (hitEntry.Accepted) { return true; } return hitEntry.Accepted = target.Hit(hitEntry.Force, direction, playerWhoHit, playHitSFX, hitID); } public static void HitGloveEnemy(EnemyAI target, int force, Vector3 direction, PlayerControllerB playerWhoHit, bool playHitSFX, int hitID) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (!CanUpgrade(target, force, playerWhoHit)) { target.HitEnemyOnLocalClient(force, direction, playerWhoHit, playHitSFX, hitID); return; } HitEntry hitEntry = Contact(target, force); if (!hitEntry.Accepted) { target.HitEnemyOnLocalClient(hitEntry.Force, direction, playerWhoHit, playHitSFX, hitID); hitEntry.Accepted = true; } } private static bool CanUpgrade(EnemyAI target, int force, PlayerControllerB attacker) { if (current == null || current.Level <= 0 || current.Settings == null || (Object)(object)target == (Object)null || target.isEnemyDead || force <= 0 || (Object)(object)attacker == (Object)null || !UpgradeRuntime.Ready) { return false; } GameNetworkManager instance = GameNetworkManager.Instance; if ((Object)(object)instance != (Object)null && (Object)(object)instance.localPlayerController == (Object)(object)attacker) { return !attacker.isPlayerDead; } return false; } private static HitEntry Contact(EnemyAI target, int originalForce) { if (!current.Hits.TryGetValue(target, out var value)) { value = new HitEntry { Force = ComputeForce(originalForce, current.Settings, current.Level, Draw) }; current.Hits.Add(target, value); } return value; } internal static int ComputeForce(int force, UpgradeSettings settings, int level, Func<double> draw) { if (force <= 0 || settings == null || level <= 0) { return force; } int num = Math.Max(0, At(settings.MeleeBonus, level, 0)); double num2 = (double)force + (double)num; float num3 = At(settings.CritChance, level, 0f); if (float.IsNaN(num3) || float.IsInfinity(num3)) { num3 = 0f; } num3 = Math.Max(0f, Math.Min(1f, num3)); if (num3 > 0f && draw() < (double)num3) { float num4 = At(settings.CritMultiplier, level, 2f); if (float.IsNaN(num4) || float.IsInfinity(num4) || num4 < 1f) { num4 = 2f; } num2 *= (double)num4; } if (!(num2 >= 2147483647.0)) { return Math.Max(1, (int)Math.Round(num2, MidpointRounding.AwayFromZero)); } return int.MaxValue; } private static T At<T>(T[] values, int level, T fallback) { if (values == null || level < 0 || level >= values.Length) { return fallback; } return values[level]; } private static double NextDraw() { lock (RandomGate) { return Random.NextDouble(); } } } public sealed class UpgradeConfig { private readonly Dictionary<string, ConfigEntry<bool>> flags = new Dictionary<string, ConfigEntry<bool>>(); private readonly Dictionary<string, ConfigEntry<int>> integers = new Dictionary<string, ConfigEntry<int>>(); private readonly Dictionary<string, ConfigEntry<float>> numbers = new Dictionary<string, ConfigEntry<float>>(); private readonly Dictionary<string, ConfigEntry<string>> lists = new Dictionary<string, ConfigEntry<string>>(); private readonly UpgradeSettings defaults = new UpgradeSettings(); private static readonly string[] Sections = new string[7] { "01 - Stamina", "02 - Action Training", "03 - Strength", "04 - Carry Weight", "05 - Interaction", "06 - Equipment", "07 - Company Relations" }; public UpgradeConfig(ConfigFile file) { if (file == null) { throw new ArgumentNullException("file"); } Flag(file, "General", "Enabled", d: true, "Enable the shared upgrade system. All gameplay settings are controlled by the host and synchronized to peers."); Flag(file, "General", "Reset On Fired", d: true, "Clear purchased upgrades when the team is fired. Quota success always retains purchased levels."); Flag(file, "General", "Require All Players", d: true, "Require every connected player to have a compatible CrewUpgrades version before allowing gameplay or purchases."); for (int i = 0; i < Sections.Length; i++) { Flag(file, Sections[i], "Enabled", d: true, "Allow this upgrade to be purchased and applied to the whole team."); if (i != 6) { List(file, Sections[i], "Purchase Prices", Prices((UpgradeKind)i), "Five comma-separated integer prices for buying levels 1, 2, 3, 4 and 5. Each is the incremental team cost; 0 allows a free level. Negative values fall back to the default."); } } Number(file, Sections[0], "Base Sprint Seconds", defaults.BaseSprintSeconds, "Baseline sprint stamina drain parameter in seconds. Vanilla v81 uses 11; ordinary sprint stops before zero stamina."); Number(file, Sections[0], "Sprint Stop Fraction", defaults.SprintStopFraction, "Remaining stamina fraction where normal sprint ends. Vanilla is 0.1 (10 percent). Valid range 0 to 0.99."); Effect(file, 0, "Stamina Capacity", defaults.StaminaCapacity, "Stamina capacity in points, using vanilla 100 points as the baseline."); Effect(file, 0, "Sprint Speed Multiplier", defaults.SprintSpeed, "Multiplier of ordinary vanilla sprint movement speed; 1.1 means 10 percent faster."); Effect(file, 0, "Jump Cost Fraction", defaults.JumpFraction, "Fraction of CURRENT stamina capacity spent on a normal jump; 0.08 means 8 percent."); Effect(file, 0, "Standing Recovery Seconds", defaults.StandingRecoverySeconds, "Seconds to recover from empty to full while standing under normal conditions. Independent of stamina capacity."); Effect(file, 0, "Walking Recovery Seconds", defaults.WalkingRecoverySeconds, "Seconds to recover from empty to full while normally walking. Independent of stamina capacity."); Effect(file, 1, "Footstep Noise Range Multiplier", defaults.FootstepRange, "Multiplier of walking/running footstep AI noise propagation range, not a universal enemy detection radius."); Effect(file, 1, "Crouch Speed Multiplier", defaults.CrouchMultiplier, "Multiplier of vanilla crouch speed. Default level 5 is 1.5, matching ordinary walking speed."); Effect(file, 1, "Ladder Speed Multiplier", defaults.LadderMultiplier, "Multiplier of the instance's original ladder climbing speed."); Effect(file, 1, "Remaining Uphill Penalty", defaults.UphillPenalty, "Remaining fraction of vanilla's extra uphill movement penalty. 1 keeps the penalty; 0 removes it. Does not change climbable slope limits."); Effect(file, 2, "Critical Chance", defaults.CritChance, "Probability from 0 to 1 of a critical eligible melee hit against a monster. Fists, ranged damage and friendly fire are excluded."); List(file, Sections[2], "Melee Damage Bonus", defaults.MeleeBonus, "Six comma-separated nonnegative integer melee force bonuses for levels 0 through 5. Added before the critical multiplier. Not player HP damage."); Effect(file, 2, "Critical Damage Multiplier", defaults.CritMultiplier, "Critical eligible melee hit multiplier, applied after the flat bonus. Must be at least 1."); Effect(file, 3, "Effective Weight Fraction", defaults.WeightFraction, "Fraction of actual item weight used for movement slowdown and additional sprint stamina cost. Effective carryWeight = 1 + (actual carryWeight - 1) * fraction. 0.25 means 25 percent item-weight penalty."); Effect(file, 4, "Hold Duration Multiplier", defaults.HoldDuration, "Duration multiplier for explicitly allowed native hold interactions only. 0.5 halves the hold duration. Does not change QTE, safe decoder, reload, lockpick or pickup animations."); Effect(file, 4, "Additional Reach", defaults.ReachBonus, "Extra Unity units added to the instance's original ordinary pickup/interaction reach. Vanilla v81 baseline is 3 units."); string[] array = new string[10] { "Facility Entrances", "Ordinary Doors", "Steam Valves", "Ship Landing Lever", "Suit Changing", "Storage Placement", "Belt Bag Inspection", "Cruiser Seat Entry", "Cruiser Key Removal", "Cruiser Hood Opening" }; for (int j = 0; j < array.Length; j++) { Flag(file, "05 - Interaction Allowlist", array[j], d: true, "Allow hold-duration upgrades for this operation category. Unlisted interactions remain unaffected."); } Effect(file, 5, "Battery Lifetime Multiplier", defaults.BatteryLifetime, "Divide normal battery consumption by this multiplier. 2 doubles lifetime, not infinite battery. Custom item-specific logic needs separate compatibility support."); Effect(file, 5, "Jetpack Acceleration Multiplier", defaults.JetAcceleration, "Multiplier of original jetpack propulsion acceleration."); Effect(file, 5, "Jetpack Propulsion Cap Multiplier", defaults.JetCap, "Multiplier of the vanilla jetpack propulsion vector cap. This does not guarantee the same proportional increase to total flight speed."); Effect(file, 5, "Normal Teleport Wait Seconds", defaults.NormalTeleportWait, "Execution delay for an ordinary living-player teleport in seconds. Does not modify the teleporter cooldown."); Effect(file, 5, "Inverse Teleport Wait Seconds", defaults.InverseTeleportWait, "Execution delay for inverse teleport in seconds. Does not modify the inverse teleporter cooldown."); Integer(file, Sections[6], "Purchase Price", defaults.CompanyPrice, "Single team purchase price. The unlock remains across quota cycles until the configured fired reset."); Integer(file, Sections[6], "Explorations Before Draw", defaults.CompanyExplorationCount, "Draw once after this many non-company explorations return to orbit in each quota cycle. Company visits do not count. Buying after this scheduled draw waits until the next cycle. Valid range 1 to 1000."); Number(file, Sections[6], "Activation Chance", defaults.CompanyChance, "Chance from 0 to 1 for the cycle's single company bonus draw; 0.1 means 10 percent. It is not a per-item roll."); Number(file, Sections[6], "Sale Multiplier", defaults.CompanyMultiplier, "Multiplier of the normal company buying rate while this cycle's bonus is active. 2 doubles subsequent company sale proceeds. Minimum 1."); Flag(file, Sections[6], "Notify Players", defaults.CompanyNotify, "Show all players the activated company bonus and send the current active state to late joiners."); } private int[] Prices(UpgradeKind kind) { int[] array = new int[5]; for (int i = 0; i < array.Length; i++) { array[i] = defaults.Price(kind, i + 1); } return array; } private static string Id(string section, string key) { return section + "\n" + key; } private void Flag(ConfigFile f, string s, string k, bool d, string help) { flags[Id(s, k)] = f.Bind<bool>(s, k, d, help); } private void Integer(ConfigFile f, string s, string k, int d, string help) { integers[Id(s, k)] = f.Bind<int>(s, k, d, help); } private void Number(ConfigFile f, string s, string k, float d, string help) { numbers[Id(s, k)] = f.Bind<float>(s, k, d, help); } private void List(ConfigFile f, string s, string k, int[] d, string help) { lists[Id(s, k)] = f.Bind<string>(s, k, string.Join(",", Array.ConvertAll(d, (int x) => x.ToString(CultureInfo.InvariantCulture))), help); } private void Effect(ConfigFile f, int section, string key, float[] d, string help) { lists[Id(Sections[section], key)] = f.Bind<string>(Sections[section], key, string.Join(",", Array.ConvertAll(d, (float x) => x.ToString("R", CultureInfo.InvariantCulture))), "Six comma-separated values for levels 0, 1, 2, 3, 4 and 5. Level 0 documents the unupgraded baseline. " + help); } private bool Flag(string s, string k) { return flags[Id(s, k)].Value; } private int Integer(string s, string k) { return integers[Id(s, k)].Value; } private float Number(string s, string k) { return numbers[Id(s, k)].Value; } private float[] Effect(int section, string key, float[] fallback) { string[] array = (lists[Id(Sections[section], key)].Value ?? "").Split(','); float[] array2 = (float[])fallback.Clone(); if (array.Length != array2.Length) { return array2; } for (int i = 0; i < array2.Length; i++) { if (float.TryParse(array[i].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { array2[i] = result; } } return array2; } private int[] List(int section, string key, int[] fallback) { string[] array = (lists[Id(Sections[section], key)].Value ?? "").Split(','); int[] array2 = (int[])fallback.Clone(); if (array.Length != array2.Length) { return array2; } for (int i = 0; i < array2.Length; i++) { if (int.TryParse(array[i].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { array2[i] = result; } } return array2; } public UpgradeSettings Read() { UpgradeSettings upgradeSettings = new UpgradeSettings { Enabled = Flag("General", "Enabled"), ResetOnFired = Flag("General", "Reset On Fired"), RequireAllPlayers = Flag("General", "Require All Players"), StaminaPrices = List(0, "Purchase Prices", defaults.StaminaPrices), ActionPrices = List(1, "Purchase Prices", defaults.ActionPrices), StrengthPrices = List(2, "Purchase Prices", defaults.StrengthPrices), CarryPrices = List(3, "Purchase Prices", defaults.CarryPrices), InteractionPrices = List(4, "Purchase Prices", defaults.InteractionPrices), EquipmentPrices = List(5, "Purchase Prices", defaults.EquipmentPrices), BaseSprintSeconds = Number(Sections[0], "Base Sprint Seconds"), SprintStopFraction = Number(Sections[0], "Sprint Stop Fraction"), StaminaCapacity = Effect(0, "Stamina Capacity", defaults.StaminaCapacity), SprintSpeed = Effect(0, "Sprint Speed Multiplier", defaults.SprintSpeed), JumpFraction = Effect(0, "Jump Cost Fraction", defaults.JumpFraction), StandingRecoverySeconds = Effect(0, "Standing Recovery Seconds", defaults.StandingRecoverySeconds), WalkingRecoverySeconds = Effect(0, "Walking Recovery Seconds", defaults.WalkingRecoverySeconds), FootstepRange = Effect(1, "Footstep Noise Range Multiplier", defaults.FootstepRange), CrouchMultiplier = Effect(1, "Crouch Speed Multiplier", defaults.CrouchMultiplier), LadderMultiplier = Effect(1, "Ladder Speed Multiplier", defaults.LadderMultiplier), UphillPenalty = Effect(1, "Remaining Uphill Penalty", defaults.UphillPenalty), CritChance = Effect(2, "Critical Chance", defaults.CritChance), MeleeBonus = List(2, "Melee Damage Bonus", defaults.MeleeBonus), CritMultiplier = Effect(2, "Critical Damage Multiplier", defaults.CritMultiplier), WeightFraction = Effect(3, "Effective Weight Fraction", defaults.WeightFraction), HoldDuration = Effect(4, "Hold Duration Multiplier", defaults.HoldDuration), ReachBonus = Effect(4, "Additional Reach", defaults.ReachBonus), BatteryLifetime = Effect(5, "Battery Lifetime Multiplier", defaults.BatteryLifetime), JetAcceleration = Effect(5, "Jetpack Acceleration Multiplier", defaults.JetAcceleration), JetCap = Effect(5, "Jetpack Propulsion Cap Multiplier", defaults.JetCap), NormalTeleportWait = Effect(5, "Normal Teleport Wait Seconds", defaults.NormalTeleportWait), InverseTeleportWait = Effect(5, "Inverse Teleport Wait Seconds", defaults.InverseTeleportWait), CompanyPrice = Integer(Sections[6], "Purchase Price"), CompanyExplorationCount = Integer(Sections[6], "Explorations Before Draw"), CompanyChance = Number(Sections[6], "Activation Chance"), CompanyMultiplier = Number(Sections[6], "Sale Multiplier"), CompanyNotify = Flag(Sections[6], "Notify Players") }; for (int i = 0; i < Sections.Length; i++) { upgradeSettings.UpgradeEnabled[i] = Flag(Sections[i], "Enabled"); } string[] array = new string[10] { "Facility Entrances", "Ordinary Doors", "Steam Valves", "Ship Landing Lever", "Suit Changing", "Storage Placement", "Belt Bag Inspection", "Cruiser Seat Entry", "Cruiser Key Removal", "Cruiser Hood Opening" }; for (int j = 0; j < array.Length; j++) { upgradeSettings.HoldCategories[j] = Flag("05 - Interaction Allowlist", array[j]); } return upgradeSettings.ValidatedCopy(); } } internal static class UpgradeNetwork { [Serializable] private sealed class Packet { public UpgradeSettings Settings; public TeamProgression State; public string Result; public bool Usable; } private const string Hello = "Serenta.CrewUpgrades.Hello.1"; private const string Snapshot = "Serenta.CrewUpgrades.State.1"; private const string Buy = "Serenta.CrewUpgrades.Buy.1"; private const byte Protocol = 1; private static NetworkManager manager; private static CustomMessagingManager messages; private static ulong clientNonce; private static ulong serverNonce; private static ulong sequence; private static ulong pending; private static float nextHello; private static float pendingUntil; private static readonly Dictionary<ulong, ulong> peers = new Dictionary<ulong, ulong>(); private static readonly Dictionary<ulong, ulong> lastRequest = new Dictionary<ulong, ulong>(); private static readonly Dictionary<ulong, float> nextCommand = new Dictionary<ulong, float>(); private static Action<ulong> disconnected; internal static bool Pending => pending != 0; internal static bool AllPeersReady { get { if ((Object)(object)manager == (Object)null || !manager.IsServer) { return false; } foreach (ulong connectedClientsId in manager.ConnectedClientsIds) { if (connectedClientsId != 0L && !peers.ContainsKey(connectedClientsId)) { return false; } } return true; } } internal static void Tick() { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsListening) { if (manager != null) { Shutdown(); } return; } if (singleton != manager) { Bind(singleton); } if (!manager.IsServer && Time.realtimeSinceStartup >= nextHello) { nextHello = Time.realtimeSinceStartup + (float)(UpgradeRuntime.Ready ? 30 : 3); SendHello(); } if (pending != 0L && Time.realtimeSinceStartup > pendingUntil) { pending = 0uL; UpgradeTerminal.Feedback("timeout"); } } private static void Bind(NetworkManager current) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown Shutdown(); manager = current; messages = manager.CustomMessagingManager; clientNonce = Nonce(); serverNonce = (manager.IsServer ? Nonce() : 0); sequence = (pending = 0uL); nextHello = 0f; UpgradeRuntime.NewConnection(); NetworkManager captured = manager; messages.RegisterNamedMessageHandler("Serenta.CrewUpgrades.Hello.1", (HandleNamedMessageDelegate)delegate(ulong sender, FastBufferReader reader) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (manager == captured) { Guard(delegate { //IL_0007: Unknown result type (might be due to invalid IL or missing references) OnHello(sender, reader); }); } }); messages.RegisterNamedMessageHandler("Serenta.CrewUpgrades.State.1", (HandleNamedMessageDelegate)delegate(ulong sender, FastBufferReader reader) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (manager == captured) { Guard(delegate { //IL_0007: Unknown result type (might be due to invalid IL or missing references) OnSnapshot(sender, reader); }); } }); messages.RegisterNamedMessageHandler("Serenta.CrewUpgrades.Buy.1", (HandleNamedMessageDelegate)delegate(ulong sender, FastBufferReader reader) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (manager == captured) { Guard(delegate { //IL_0007: Unknown result type (might be due to invalid IL or missing references) OnBuy(sender, reader); }); } }); disconnected = delegate(ulong id) { if (manager == captured) { peers.Remove(id); lastRequest.Remove(id); nextCommand.Remove(id); if (id == manager.LocalClientId) { Shutdown(); } } }; manager.OnClientDisconnectCallback += disconnected; } private static ulong Nonce() { return BitConverter.ToUInt64(Guid.NewGuid().ToByteArray(), 0) | 1; } private unsafe static void SendHello() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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) FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(32, (Allocator)2, -1); try { byte b = 1; ((FastBufferWriter)(ref val)).WriteValueSafe<byte>(ref b, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref clientNonce, default(ForPrimitives)); messages.SendNamedMessage("Serenta.CrewUpgrades.Hello.1", 0uL, val, (NetworkDelivery)3); } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } private static void OnHello(ulong sender, FastBufferReader reader) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (!manager.IsServer || !manager.ConnectedClients.ContainsKey(sender)) { return; } byte b = default(byte); ((FastBufferReader)(ref reader)).ReadValueSafe<byte>(ref b, default(ForPrimitives)); ulong num = default(ulong); ((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref num, default(ForPrimitives)); if (b == 1 && num != 0L) { if (peers.TryGetValue(sender, out var value) && value != num) { lastRequest.Remove(sender); } peers[sender] = num; if (UpgradeRuntime.Ready) { SendState(sender, 0uL, null); } } } internal static void Publish() { if ((Object)(object)manager == (Object)null || !manager.IsServer) { return; } foreach (ulong peer in new List<ulong>(peers.Keys)) { Guard(delegate { SendState(peer, 0uL, null); }); } UpgradeTerminal.Refresh(); } private unsafe static void SendState(ulong target, ulong reply, string result) { //IL_0089: 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) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: 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_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: 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) if (!peers.TryGetValue(target, out var value)) { return; } TeamProgression teamProgression = UpgradeRuntime.State.ValidatedCopy(UpgradeRuntime.Settings); teamProgression.CompletedTripIds = Array.Empty<string>(); string text = JsonUtility.ToJson((object)new Packet { Settings = UpgradeRuntime.Settings, State = teamProgression, Result = result, Usable = UpgradeRuntime.Ready }); if (text.Length > 24000) { throw new InvalidOperationException("Crew state exceeds protocol bound."); } FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(65536, (Allocator)2, -1); try { byte b = 1; ((FastBufferWriter)(ref val)).WriteValueSafe<byte>(ref b, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref value, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref serverNonce, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref reply, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe(text, false); messages.SendNamedMessage("Serenta.CrewUpgrades.State.1", target, val, (NetworkDelivery)4); } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } } private static void OnSnapshot(ulong sender, FastBufferReader reader) { //IL_0024: 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_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: 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) //IL_0063: Unknown result type (might be due to invalid IL or missing references) if (manager.IsServer || sender != 0L || ((FastBufferReader)(ref reader)).Length > 65536) { return; } byte b = default(byte); ((FastBufferReader)(ref reader)).ReadValueSafe<byte>(ref b, default(ForPrimitives)); ulong num = default(ulong); ((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref num, default(ForPrimitives)); ulong num2 = default(ulong); ((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref num2, default(ForPrimitives)); ulong num3 = default(ulong); ((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref num3, default(ForPrimitives)); if (b != 1 || num != clientNonce || num2 == 0L || (serverNonce != 0L && num2 != serverNonce)) { return; } string text = default(string); ((FastBufferReader)(ref reader)).ReadValueSafe(ref text, false); if (text == null || text.Length > 24000) { return; } Packet packet = JsonUtility.FromJson<Packet>(text); if (packet?.Settings != null && packet.State != null && (!UpgradeRuntime.Ready || packet.State.Revision >= UpgradeRuntime.State.Revision)) { serverNonce = num2; UpgradeRuntime.AcceptState(packet.Settings, packet.State, packet.Usable); if (num3 != 0L && num3 == pending) { pending = 0uL; UpgradeTerminal.Feedback(packet.Result ?? "not-ready"); } } } internal unsafe static void Request(UpgradeKind kind, int expected) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: 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_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: 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) if ((Object)(object)manager == (Object)null || !UpgradeRuntime.Ready) { UpgradeTerminal.Feedback("not-ready"); } else { if (Pending) { return; } if (manager.IsServer) { UpgradeTerminal.Feedback(UpgradeRuntime.Purchase(manager.LocalClientId, kind, expected)); return; } pending = ++sequence; pendingUntil = Time.realtimeSinceStartup + 10f; FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(64, (Allocator)2, -1); try { byte b = 1; ((FastBufferWriter)(ref val)).WriteValueSafe<byte>(ref b, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref clientNonce, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref serverNonce, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref pending, default(ForPrimitives)); int num = (int)kind; ((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref num, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref expected, default(ForPrimitives)); messages.SendNamedMessage("Serenta.CrewUpgrades.Buy.1", 0uL, val, (NetworkDelivery)3); } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } UpgradeTerminal.Refresh(); } } private static void OnBuy(ulong sender, FastBufferReader reader) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0065: 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_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) if (!manager.IsServer || !UpgradeRuntime.Ready || !manager.ConnectedClients.ContainsKey(sender)) { return; } byte b = default(byte); ((FastBufferReader)(ref reader)).ReadValueSafe<byte>(ref b, default(ForPrimitives)); ulong num = default(ulong); ((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref num, default(ForPrimitives)); ulong num2 = default(ulong); ((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref num2, default(ForPrimitives)); ulong num3 = default(ulong); ((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref num3, default(ForPrimitives)); if (b != 1 || num2 != serverNonce || !peers.TryGetValue(sender, out var value) || num != value || num3 == 0L) { return; } int num4 = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num4, default(ForPrimitives)); int expectedLevel = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref expectedLevel, default(ForPrimitives)); if (lastRequest.TryGetValue(sender, out var value2) && num3 <= value2) { SendState(sender, num3, "stale-level"); return; } lastRequest[sender] = num3; if (nextCommand.TryGetValue(sender, out var value3) && Time.realtimeSinceStartup < value3) { SendState(sender, num3, "busy"); return; } nextCommand[sender] = Time.realtimeSinceStartup + 0.25f; string result = ((num4 < 0 || num4 > 6) ? "unknown-upgrade" : UpgradeRuntime.Purchase(sender, (UpgradeKind)num4, expectedLevel)); SendState(sender, num3, result); } internal static void Shutdown() { if (manager == null) { return; } UpgradeRuntime.EndConnection(); try { if (disconnected != null) { manager.OnClientDisconnectCallback -= disconnected; } CustomMessagingManager obj = messages; if (obj != null) { obj.UnregisterNamedMessageHandler("Serenta.CrewUpgrades.Hello.1"); } CustomMessagingManager obj2 = messages; if (obj2 != null) { obj2.UnregisterNamedMessageHandler("Serenta.CrewUpgrades.State.1"); } CustomMessagingManager obj3 = messages; if (obj3 != null) { obj3.UnregisterNamedMessageHandler("Serenta.CrewUpgrades.Buy.1"); } } catch (Exception ex) { ManualLogSource log = UpgradeRuntime.Log; if (log != null) { log.LogDebug((object)ex.Message); } } manager = null; messages = null; disconnected = null; peers.Clear(); lastRequest.Clear(); nextCommand.Clear(); pending = 0uL; } private static void Guard(Action action) { try { action(); } catch (Exception ex) { ManualLogSource log = UpgradeRuntime.Log; if (log != null) { log.LogWarning((object)("Crew network message rejected: " + ex.Message)); } } } } public static class UpgradeRuntime { internal static TeamProgression State = new TeamProgression(); private static UpgradeSettings localSettings; private static bool loaded; private static bool saveBlocked; private static StartOfRound initializedRound; private static string saveFile; private static string pendingTrip; private static bool pendingExploration; private static bool pendingBonusNotice; private static float nextRetry; private static float nextTip; public static UpgradeSettings Settings { get; private set; } = new UpgradeSettings().ValidatedCopy(); public static ManualLogSource Log { get; private set; } public static bool Ready { get; private set; } internal static bool IsServer { get { if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsListening) { return NetworkManager.Singleton.IsServer; } return false; } } public static int Level(UpgradeKind kind) { if (!Ready || !Settings.IsEnabled(kind)) { return 0; } return State.Level(kind); } internal static void Initialize(ManualLogSource log, UpgradeSettings settings) { Log = log; Settings = (localSettings = settings.ValidatedCopy()); } internal static void Tick() { try { UpgradeNetwork.Tick(); TryShowBonusNotice(); if (!IsServer || loaded || (Object)(object)StartOfRound.Instance == (Object)null || initializedRound != StartOfRound.Instance || (Object)(object)TimeOfDay.Instance == (Object)null || Time.realtimeSinceStartup < nextRetry) { return; } nextRetry = Time.realtimeSinceStartup + 2f; saveFile = GameNetworkManager.Instance?.currentSaveFileName; if (!string.IsNullOrEmpty(saveFile)) { Settings = localSettings; State = UpgradeSave.Load(saveFile, Settings); pendingTrip = null; pendingExploration = false; State.NewQuota(TimeOfDay.Instance.timesFulfilledQuota); loaded = (Ready = true); Log.LogInfo((object)("Crew progression loaded: " + saveFile + ", revision " + State.Revision)); RefreshBuyingRate(); UpgradeNetwork.Publish(); if (State.CompanyActive && Settings.CompanyNotify) { NotifyBonus(); } } } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogError((object)("Crew runtime: " + ex)); } } } internal static void NewConnection() { Ready = (loaded = (saveBlocked = false)); State = new TeamProgression(); Settings = localSettings; pendingTrip = null; pendingExploration = false; nextRetry = 0f; pendingBonusNotice = false; UpgradeTerminal.Close(); StrengthUpgrades.Reset(); } internal static void MarkRoundStarted(StartOfRound round) { initializedRound = round; } internal static void EndConnection() { Save(); Ready = (loaded = false); State = new TeamProgression(); Settings = localSettings; pendingTrip = null; pendingExploration = false; saveFile = null; pendingBonusNotice = false; UpgradeTerminal.Close(); StrengthUpgrades.Reset(); } internal static void AcceptState(UpgradeSettings settings, TeamProgression state, bool usable = true) { bool flag = (!Ready || !State.CompanyActive) && state.CompanyActive; Settings = settings.ValidatedCopy(); State = state.ValidatedCopy(Settings); Ready = usable; if (!State.CompanyActive) { pendingBonusNotice = false; } RefreshBuyingRate(); if (usable && flag && Settings.CompanyNotify) { NotifyBonus(); } UpgradeTerminal.Refresh(); } internal static bool Save() { if (!IsServer || !loaded || saveBlocked || string.IsNullOrEmpty(saveFile)) { return false; } if (UpgradeSave.Store(saveFile, State)) { return true; } saveBlocked = true; Ready = false; RefreshBuyingRate(); UpgradeNetwork.Publish(); return false; } internal static string Purchase(ulong sender, UpgradeKind kind, int expectedLevel) { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) if (!IsServer || !Ready) { return "not-ready"; } if (Settings.RequireAllPlayers && !UpgradeNetwork.AllPeersReady) { return "not-ready"; } Terminal val = Object.FindObjectOfType<Terminal>(); StartOfRound instance = StartOfRound.Instance; if ((Object)(object)val == (Object)null || (Object)(object)instance == (Object)null) { return "not-ready"; } PlayerControllerB val2 = null; PlayerControllerB[] allPlayerScripts = instance.allPlayerScripts; foreach (PlayerControllerB val3 in allPlayerScripts) { if ((Object)(object)val3 != (Object)null && val3.actualClientId == sender && val3.isPlayerControlled) { val2 = val3; break; } } if ((Object)(object)val2 == (Object)null || val2.isPlayerDead || !val2.isInHangarShipRoom || Vector3.Distance(((Component)val2).transform.position, ((Component)val).transform.position) > 8f || (Object)(object)val.placeableObject == (Object)null || !val.placeableObject.inUse) { return "terminal-required"; } int credits = val.groupCredits; TeamProgression state = State.ValidatedCopy(Settings); PurchaseResult purchaseResult = State.TryPurchase(kind, expectedLevel, ref credits, Settings); if (!purchaseResult.Success) { return purchaseResult.Reason; } switch (UpgradeSave.StorePurchase(saveFile, State, credits)) { case PurchaseCommit.NotCommitted: State = state; return "save-failed"; case PurchaseCommit.Unknown: saveBlocked = true; Ready = false; val.groupCredits = credits; try { val.SyncGroupCreditsClientRpc(credits, -1); } catch (Exception ex2) { Log.LogError((object)("Could not broadcast the provisional balance: " + ex2)); } RefreshBuyingRate(); UpgradeNetwork.Publish(); return "save-uncertain"; default: val.groupCredits = credits; try { val.SyncGroupCreditsClientRpc(credits, -1); } catch (Exception ex) { Log.LogError((object)("Purchase committed, but the native credit broadcast failed: " + ex)); } Log.LogInfo((object)$"Upgrade bought by {sender}: {kind} level {purchaseResult.NewLevel}, cost {purchaseResult.Cost}, credits {credits}."); UpgradeNetwork.Publish(); return "ok"; } } internal static bool CanLaunch() { if (!IsServer || !Settings.Enabled || (Ready && (!Settings.RequireAllPlayers || UpgradeNetwork.AllPeersReady))) { return true; } if (Time.realtimeSinceStartup >= nextTip) { nextTip = Time.realtimeSinceStartup + 4f; HUDManager instance = HUDManager.Instance; if (instance != null) { instance.DisplayTip("Crew Upgrades", UpgradeTerminal.Chinese ? "有玩家尚未同步 CrewUpgrades,请确认全员安装相同版本。" : "Waiting for CrewUpgrades on every player. Install the same version on all clients.", true, false, "LC_Tip1"); } } StartMatchLever obj = Object.FindObjectOfType<StartMatchLever>(); if (obj != null) { obj.CancelStartGameClientRpc(); } return false; } internal static void BeginTrip(StartOfRound round) { if (IsServer && Ready) { pendingExploration = (Object)(object)round.currentLevel != (Object)null && round.currentLevel.planetHasTime && round.currentLevelID != 3; pendingTrip = (pendingExploration ? (State.QuotaMarker + ":" + round.gameStats.daysSpent + ":" + round.randomMapSeed) : null); } } internal static void CompleteTrip() { if (IsServer && Ready && pendingExploration && pendingTrip != null) { string tripId = pendingTrip; pendingTrip = null; pendingExploration = false; bool flag = State.CompleteExploration(tripId, NextDraw(), Settings); Save(); RefreshBuyingRate(); UpgradeNetwork.Publish(); if (Ready && flag && Settings.CompanyNotify) { NotifyBonus(); } Log.LogInfo((object)$"Exploration completed: {State.ExplorationsCompleted}; company drawn={State.CompanyRolled}, bonus={State.CompanyActive}."); } } private static double NextDraw() { return (double)(BitConverter.ToUInt64(Guid.NewGuid().ToByteArray(), 0) >> 11) * 1.1102230246251565E-16; } internal static void NewQuota() { if (IsServer && Ready && !((Object)(object)TimeOfDay.Instance == (Object)null)) { State.NewQuota(TimeOfDay.Instance.timesFulfilledQuota); pendingBonusNotice = false; pendingTrip = null; pendingExploration = false; Save(); RefreshBuyingRate(); UpgradeNetwork.Publish(); } } internal static void Fired() { if (IsServer && Ready) { if (Settings.ResetOnFired) { State.ResetAll(); } State.QuotaMarker = -1; State.NewQuota(0); pendingBonusNotice = false; pendingTrip = null; pendingExploration = false; Save(); RefreshBuyingRate(); UpgradeNetwork.Publish(); } } internal static void RefreshBuyingRate() { if ((Object)(object)TimeOfDay.Instance != (Object)null && (Object)(object)StartOfRound.Instance != (Object)null) { TimeOfDay.Instance.SetBuyingRateForDay(); } } internal static void MultiplyBuyingRate() { if (Ready && Settings.IsEnabled(UpgradeKind.CompanyRelations) && State.CompanyActive && (Object)(object)StartOfRound.Instance != (Object)null) { StartOfRound instance = StartOfRound.Instance; instance.companyBuyingRate *= Settings.CompanyMultiplier; } } internal static void NotifyBonus() { pendingBonusNotice = true; TryShowBonusNotice(); } private static void TryShowBonusNotice() { if (pendingBonusNotice && Ready && State.CompanyActive && !((Object)(object)HUDManager.Instance == (Object)null)) { pendingBonusNotice = false; HUDManager.Instance.DisplayTip(UpgradeTerminal.Chinese ? "公司收购加价" : "Company bonus", UpgradeTerminal.Chinese ? $"本配额周期收购价 ×{Settings.CompanyMultiplier:0.##},可前往公司交货。" : $"Company buying rate x{Settings.CompanyMultiplier:0.##} for this quota. Ready for delivery.", false, false, "LC_Tip1"); } } internal static void Shutdown() { Save(); UpgradeNetwork.Shutdown(); } } [HarmonyPatch] internal static class ProgressionPatches { [HarmonyPrefix] [HarmonyPatch(typeof(StartOfRound), "StartGame")] private static bool BeforeLaunch(StartOfRound __instance, out bool __state) { bool flag = UpgradeRuntime.CanLaunch(); __state = flag && UpgradeRuntime.IsServer && UpgradeRuntime.Ready && __instance.inShipPhase && !__instance.beganLoadingNewLevel; return flag; } [HarmonyPostfix] [HarmonyPatch(typeof(StartOfRound), "StartGame")] private static void AfterLaunch(StartOfRound __instance, bool __state, bool __runOriginal) { if (__state && __runOriginal && __instance.beganLoadingNewLevel) { UpgradeRuntime.BeginTrip(__instance); } } [HarmonyPostfix] [HarmonyPatch(typeof(StartOfRound), "Start")] private static void Started(StartOfRound __instance) { UpgradeRuntime.MarkRoundStarted(__instance); } [HarmonyPostfix] [HarmonyPatch(typeof(StartOfRound), "SetShipReadyToLand")] private static void Orbit() { UpgradeRuntime.CompleteTrip(); } [HarmonyPostfix] [HarmonyPatch(typeof(TimeOfDay), "SetNewProfitQuota")] private static void Quota() { UpgradeRuntime.NewQuota(); } [HarmonyPostfix] [HarmonyPriority(0)] [HarmonyPatch(typeof(TimeOfDay), "SetBuyingRateForDay")] private static void Rate() { UpgradeRuntime.MultiplyBuyingRate(); } [HarmonyPostfix] [HarmonyPatch(typeof(StartOfRound), "ResetShip")] private static void Fired() { UpgradeRuntime.Fired(); } [HarmonyPostfix] [HarmonyPatch(typeof(GameNetworkManager), "SaveGameValues")] private static void Save() { UpgradeRuntime.Save(); } } internal enum PurchaseCommit { NotCommitted, Committed, Unknown } internal static class UpgradeSave { private const string Key = "Serenta.CrewUpgrades.v1"; internal static TeamProgression Load(string file, UpgradeSettings settings) { if (!ES3.KeyExists("Serenta.CrewUpgrades.v1", file)) { return new TeamProgression(); } string text = ES3.Load<string>("Serenta.CrewUpgrades.v1", file); if (string.IsNullOrEmpty(text) || text.Length > 65536) { throw new InvalidOperationException("Invalid CrewUpgrades save