Decompiled source of DSPQualitySystem v0.17.0
BepInEx/plugins/DSPQualitySystem/DSPQualitySystem.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; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using DSPQualitySystem.Diag; using DSPQualitySystem.Patches; using DSPQualitySystem.Save; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Events; using UnityEngine.Rendering; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("DSPQualitySystem")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("DSP Quality System - linear inc tables, product inc pool, build quality")] [assembly: AssemblyFileVersion("0.17.0.0")] [assembly: AssemblyInformationalVersion("0.17.0")] [assembly: AssemblyProduct("DSPQualitySystem")] [assembly: AssemblyTitle("DSPQualitySystem")] [assembly: AssemblyVersion("0.17.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace DSPQualitySystem { public static class BuildQualityStash { private struct Entry { public int itemId; public int perItem; } private const int MaxEntries = 1024; private static Entry[] _entries = new Entry[32]; private static int _count; private static bool _capturing; private static bool _warnedMismatch; public static bool Capturing => _capturing; public static void BeginBatch() { _count = 0; _capturing = true; } public static void EndBatch() { _capturing = false; _count = 0; } public static void Push(int itemId, int perItem) { if (!_capturing || itemId <= 0) { return; } if (perItem < 0) { perItem = 0; } else if (perItem > 10) { perItem = 10; } if (_count < 1024) { if (_count == _entries.Length) { Entry[] array = new Entry[_entries.Length * 2]; Array.Copy(_entries, array, _entries.Length); _entries = array; } _entries[_count].itemId = itemId; _entries[_count].perItem = perItem; _count++; } } public static bool TryPopFor(int itemId, out int perItem) { perItem = 0; if (_count <= 0) { return false; } Entry entry = _entries[0]; if (entry.itemId != itemId) { if (!_warnedMismatch) { _warnedMismatch = true; if (Plugin.Log != null) { Plugin.Log.LogWarning((object)("[QSystem][build] stash item mismatch: preview=" + itemId + " queueHead=" + entry.itemId + "; dropping batch to avoid wrong quality")); } } _count = 0; return false; } perItem = entry.perItem; for (int i = 0; i < _count - 1; i++) { _entries[i] = _entries[i + 1]; } _count--; return true; } } [BepInPlugin("dsp.qualitysystem", "DSP Quality System", "0.17.0")] public class Plugin : BaseUnityPlugin { public const string GUID = "dsp.qualitysystem"; public const string VERSION = "0.17.0"; internal static ManualLogSource Log; private bool _startupReported; private int _pollTicks; private void Awake() { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; try { QualityConfig.Init(((BaseUnityPlugin)this).Config); Log.LogInfo((object)"[QSystem] === DSP Quality System v0.17.0 ==="); LogBuildInfo(); LogConfig(); Log.LogInfo((object)("[QSystem] rule: miner rolls = floor(points/2) (multi-point ore, cap = half); extra upgrade above=" + QualityConfig.ExtraUpgradeAboveClamped + " guaranteed +2 at=" + QualityConfig.DoubleUpgradeAtClamped)); P0_Tables.Apply("Awake"); } catch (Exception ex) { Log.LogError((object)("[QSystem] Awake (config/tables) failed: " + ex)); } try { Harmony val = new Harmony("dsp.qualitysystem"); val.PatchAll(typeof(Plugin).Assembly); int num = 0; StringBuilder stringBuilder = new StringBuilder(); foreach (MethodBase patchedMethod in val.GetPatchedMethods()) { num++; if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append((patchedMethod.DeclaringType == null) ? "?" : patchedMethod.DeclaringType.Name).Append('.').Append(patchedMethod.Name); } Log.LogInfo((object)("[QSystem] Harmony PatchAll OK, harmony=" + typeof(Harmony).Assembly.GetName().Version?.ToString() + " patchedMethods=" + num)); Log.LogInfo((object)("[QSystem] patched: " + stringBuilder)); P3_Speed.Apply(val); Log.LogInfo((object)("[QSystem][speed] " + P3_Speed.Describe())); P3_StationCharge.Apply(val); Log.LogInfo((object)("[QSystem][charge] " + P3_StationCharge.Describe())); P3_Dismantle.Apply(val); Log.LogInfo((object)("[QSystem][dismantle] " + P3_Dismantle.Describe())); P4_Isolation.Apply(val); Log.LogInfo((object)("[QSystem][isolation] " + P4_Isolation.Describe())); P4_ConsumePriority.Apply(val); Log.LogInfo((object)("[QSystem][consume] " + P4_ConsumePriority.Describe())); P5_LabSpeed.Apply(val); Log.LogInfo((object)("[QSystem][lab] " + P5_LabSpeed.Describe())); P5_TurretFireRate.Apply(val); Log.LogInfo((object)("[QSystem][turret] " + P5_TurretFireRate.Describe())); P5_PowerGen.Apply(val); Log.LogInfo((object)("[QSystem][powergen] " + P5_PowerGen.Describe())); P5_Launcher.Apply(val); Log.LogInfo((object)("[QSystem][launcher] " + P5_Launcher.Describe())); P5_Sprayer.Apply(val); Log.LogInfo((object)("[QSystem][sprayer] " + P5_Sprayer.Describe())); P4_StorageGrids.Apply(val); Log.LogInfo((object)("[QSystem][storage] " + P4_StorageGrids.Describe())); P5_SplitterFilter.Apply(val); Log.LogInfo((object)("[QSystem][splitterfilter] " + P5_SplitterFilter.Describe())); P5_QualityFilterPicker.Apply(val); Log.LogInfo((object)("[QSystem][filterpicker] " + P5_QualityFilterPicker.Describe())); P5_SorterFilter.Apply(val); Log.LogInfo((object)("[QSystem][sorterfilter] " + P5_SorterFilter.Describe())); P5_SorterFilterUi.Apply(val); Log.LogInfo((object)("[QSystem][sorterfilter] " + P5_SorterFilterUi.Describe())); P6_ItemQualityBadges.Apply(val); Log.LogInfo((object)("[QSystem][badges] " + P6_ItemQualityBadges.Describe())); InstantBuildCapture.Apply(val); Log.LogInfo((object)("[QSystem][build] " + InstantBuildCapture.Describe())); P5_TenPointHalo.Install(((Component)this).gameObject); Log.LogInfo((object)("[QSystem][halo] " + P5_TenPointHalo.Describe())); } catch (Exception ex2) { Log.LogError((object)("[QSystem] Harmony PatchAll failed: " + ex2)); } } private void Update() { try { if (!_startupReported) { PollStartup(); } HandleHotkeys(); P5_TenPointHalo.Tick(); StorageGrids.Tick(); } catch (Exception e) { QualityEnv.WarnOnce("plugin.update", e); } } private void PollStartup() { _pollTicks++; if (_pollTicks % 30 != 0) { return; } try { if (!((Object)(object)LDB.items == (Object)null)) { ItemProto[] dataArray = ((ProtoSet<ItemProto>)(object)LDB.items).dataArray; if (dataArray != null && dataArray.Length >= 100) { _startupReported = true; Log.LogInfo((object)("[QSystem] LDB ready (items=" + dataArray.Length + "), running startup report.")); P0_Tables.Apply("LDB ready"); StartupAudit.Run(); Log.LogInfo((object)"[QSystem] === startup report complete ==="); } } } catch (Exception ex) { _startupReported = true; Log.LogError((object)("[QSystem] startup report failed: " + ex)); } } private void HandleHotkeys() { if (QualityConfig.DevMode) { if (Input.GetKeyDown((KeyCode)286)) { Probes.DumpAssemblers(); } if (Input.GetKeyDown((KeyCode)288)) { Probes.DumpPicks(); } if (Input.GetKeyDown((KeyCode)289)) { Probes.DumpTables(); } if (Input.GetKeyDown((KeyCode)290)) { Probes.DumpBuildingInc(); } if (Input.GetKeyDown((KeyCode)291)) { Probes.InjectHandItemQuality(); } if (Input.GetKeyDown((KeyCode)292)) { Probes.InjectAimedBuildingQuality(); } if (Input.GetKeyDown((KeyCode)293)) { Probes.DumpMinerStats(); } if (Input.GetKeyDown((KeyCode)287)) { Probes.DumpAimedMiner(); } } } private void LogBuildInfo() { try { string location = Assembly.GetExecutingAssembly().Location; string text = "-"; if (!string.IsNullOrEmpty(location) && File.Exists(location)) { text = File.GetLastWriteTime(location).ToString("yyyy-MM-dd HH:mm:ss"); } Log.LogInfo((object)("[QSystem] build: dll=" + location)); Log.LogInfo((object)("[QSystem] build: mtime=" + text + " assembly=" + typeof(Plugin).Assembly.GetName().Version)); Log.LogInfo((object)("[QSystem] env: game=" + Application.version + " unity=" + Application.unityVersion)); } catch (Exception ex) { Log.LogWarning((object)("[QSystem] build info failed: " + ex.Message)); } } private void LogConfig() { Log.LogInfo((object)("[QSystem] cfg isolateBuildingStacks=" + QualityConfig.IsolateBuildingStacks.Value + " isolateStorageContainers=" + QualityConfig.IsolateStorageContainers.Value + " | speedBonus=" + QualityConfig.SpeedBonus.Value + " chargeBonus=" + QualityConfig.ChargeBonus.Value + " labSpeedBonus=" + QualityConfig.LabSpeedBonus.Value + " turretFireRateBonus=" + QualityConfig.TurretFireRateBonus.Value + " powerGenBonus=" + QualityConfig.PowerGenBonus.Value + " launcherSpeedBonus=" + QualityConfig.LauncherSpeedBonus.Value + " sprayerIncBonus=" + QualityConfig.SprayerIncBonus.Value + " | itemQualityBadges=" + QualityConfig.ItemQualityBadges.Value + " itemQualityBadgeStyle=" + QualityConfig.ItemQualityBadgeStyleNormalized + " itemQualityBadgeMinPoints=" + QualityConfig.ItemQualityBadgeMinPointsClamped + " tenPointMarkerStyle=" + QualityConfig.MarkerStyleName + " tenPointHaloSize=" + QualityConfig.TenPointHaloSizeClamped.ToString("F2") + " tenPointHaloPulse=" + QualityConfig.HaloPulseEnabled + " tenPointHaloSpin=" + QualityConfig.HaloSpinClamped.ToString("F1"))); Log.LogInfo((object)("[QSystem] cfg builtin=(internal constants, no longer user-editable) enabled=" + true + " incPerPoint=" + 100 + " powerPerPoint=" + 250 + " buildingChancePerPoint=" + 1.5f.ToString("F2") + " minerChancePerPoint=" + 2f.ToString("F2") + " maxUpgradeChance=" + 60 + " maxQuality=" + 10 + " giveDismantleInc=" + true + " speedPerPoint=" + 2f.ToString("F2") + " maxSpeedBonus=" + 50f.ToString("F2") + " chargePerPoint=" + 2f.ToString("F2") + " maxChargeBonus=" + 50f.ToString("F2") + " speedBonusMode=curve extraUpgradeAbove=" + 6 + " doubleUpgradeAt=" + 10 + " preferHighQualityConsume=" + true + " tenPointHaloColor=FFD24A tenPointHaloLift=" + 1.2f.ToString("F2") + " tenPointHaloMax=" + 64 + " storageQualityGrids=" + true + " storageQualityGridsPerPoint=" + 0.5f.ToString("F2") + " storageQualityGridsMax=" + 20 + " splitterQualityFilter=" + true + " sorterQualityFilter=" + true + " debugProbes=" + QualityConfig.DevMode + " (env DSPQS_DEV=1 enables debug hotkeys/probes)")); } } public static class QualityBlueprintPlan { public const int Magic = 1364414721; public const int MaxParamCount = 32765; public static int[] Append(int[] parameters, int inc) { if (inc < 0) { inc = 0; } if (inc == 0) { return parameters; } if (parameters == null) { parameters = new int[0]; } int num = parameters.Length; if (num >= 32765) { return null; } if (num >= 2 && parameters[num - 2] == 1364414721) { if (parameters[num - 1] == inc) { return parameters; } int[] obj = (int[])parameters.Clone(); obj[num - 1] = inc; return obj; } int[] array = new int[num + 2]; Array.Copy(parameters, array, num); array[num] = 1364414721; array[num + 1] = inc; return array; } public static bool TryRead(int[] parameters, int count, out int inc) { inc = 0; try { if (parameters == null) { return false; } int num = count; if (num <= 0 || num > parameters.Length) { num = parameters.Length; } if (num < 2 || parameters[num - 2] != 1364414721) { return false; } int num2 = parameters[num - 1]; if (num2 < 0) { return false; } inc = num2; return true; } catch (Exception) { inc = 0; return false; } } } public sealed class QualityFixedBool { private readonly bool _value; public bool Value => _value; public QualityFixedBool(bool value) { _value = value; } } public sealed class QualityFixedInt { private readonly int _value; public int Value => _value; public QualityFixedInt(int value) { _value = value; } } public sealed class QualityFixedFloat { private readonly float _value; public float Value => _value; public QualityFixedFloat(float value) { _value = value; } } public sealed class QualityFixedString { private readonly string _value; public string Value => _value; public QualityFixedString(string value) { _value = value; } } public sealed class QualityDevSwitch { public const string EnvVar = "DSPQS_DEV"; public bool Value { get { try { return string.Equals(Environment.GetEnvironmentVariable("DSPQS_DEV"), "1", StringComparison.Ordinal); } catch (Exception) { return false; } } } } public static class QualityConfig { public const string Section = "quality"; public const bool EnabledValue = true; public const int IncPerPointValue = 100; public const int PowerPerPointValue = 250; public const float BuildingChancePerPointValue = 1.5f; public const float MinerChancePerPointValue = 2f; public const int MaxUpgradeChanceValue = 60; public const int MaxQualityValue = 10; public const bool GiveDismantleIncValue = true; public const int DebugInjectPointsValue = 4; public const string SpeedBonusModeValue = "curve"; public const float SpeedPerPointValue = 2f; public const float MaxSpeedBonusValue = 50f; public const float ChargePerPointValue = 2f; public const float MaxChargeBonusValue = 50f; public const int ExtraUpgradeAboveValue = 6; public const int DoubleUpgradeAtValue = 10; public const bool PreferHighQualityConsumeValue = true; public const string TenPointHaloColorValue = "FFD24A"; public const float TenPointHaloLiftValue = 1.2f; public const int TenPointHaloMaxValue = 64; public const bool TenPointHaloValue = true; public const bool StorageQualityGridsValue = true; public const float StorageQualityGridsPerPointValue = 0.5f; public const int StorageQualityGridsMaxValue = 20; public const bool SplitterQualityFilterValue = true; public const bool SorterQualityFilterValue = true; public static readonly QualityFixedBool GiveDismantleInc = new QualityFixedBool(value: true); public static readonly QualityFixedBool PreferHighQualityConsume = new QualityFixedBool(value: true); public static readonly QualityFixedBool TenPointHalo = new QualityFixedBool(value: true); public static readonly QualityFixedString TenPointHaloColor = new QualityFixedString("FFD24A"); public static readonly QualityFixedFloat TenPointHaloLift = new QualityFixedFloat(1.2f); public static readonly QualityFixedInt TenPointHaloMax = new QualityFixedInt(64); public static readonly QualityFixedBool StorageQualityGrids = new QualityFixedBool(value: true); public static readonly QualityFixedFloat StorageQualityGridsPerPoint = new QualityFixedFloat(0.5f); public static readonly QualityFixedInt StorageQualityGridsMax = new QualityFixedInt(20); public static readonly QualityFixedBool SplitterQualityFilter = new QualityFixedBool(value: true); public static readonly QualityFixedBool SorterQualityFilter = new QualityFixedBool(value: true); public static readonly QualityFixedBool Enabled = new QualityFixedBool(value: true); public static readonly QualityFixedInt IncPerPoint = new QualityFixedInt(100); public static readonly QualityFixedInt PowerPerPoint = new QualityFixedInt(250); public static readonly QualityFixedFloat BuildingChancePerPoint = new QualityFixedFloat(1.5f); public static readonly QualityFixedFloat MinerChancePerPoint = new QualityFixedFloat(2f); public static readonly QualityFixedInt MaxUpgradeChance = new QualityFixedInt(60); public static readonly QualityFixedInt MaxQuality = new QualityFixedInt(10); public static readonly QualityFixedInt DebugInjectPoints = new QualityFixedInt(4); public static readonly QualityFixedString SpeedBonusMode = new QualityFixedString("curve"); public static readonly QualityFixedFloat SpeedPerPoint = new QualityFixedFloat(2f); public static readonly QualityFixedFloat MaxSpeedBonus = new QualityFixedFloat(50f); public static readonly QualityFixedFloat ChargePerPoint = new QualityFixedFloat(2f); public static readonly QualityFixedFloat MaxChargeBonus = new QualityFixedFloat(50f); public static readonly QualityFixedInt ExtraUpgradeAbove = new QualityFixedInt(6); public static readonly QualityFixedInt DoubleUpgradeAt = new QualityFixedInt(10); public static readonly QualityDevSwitch DebugProbes = new QualityDevSwitch(); public static ConfigEntry<bool> IsolateBuildingStacks; public static ConfigEntry<bool> IsolateStorageContainers; public static ConfigEntry<bool> SpeedBonus; public static ConfigEntry<bool> ChargeBonus; public static ConfigEntry<bool> LabSpeedBonus; public static ConfigEntry<bool> TurretFireRateBonus; public static ConfigEntry<bool> PowerGenBonus; public static ConfigEntry<bool> LauncherSpeedBonus; public static ConfigEntry<bool> SprayerIncBonus; public static ConfigEntry<bool> ItemQualityBadges; public static ConfigEntry<string> ItemQualityBadgeStyle; public static ConfigEntry<int> ItemQualityBadgeMinPoints; public static ConfigEntry<string> TenPointMarkerStyle; public static ConfigEntry<float> TenPointHaloSize; public static ConfigEntry<bool> TenPointHaloPulse; public static ConfigEntry<float> TenPointHaloSpin; public static bool DevMode => DebugProbes.Value; public static float SpeedPerPointClamped { get { float num = ((SpeedPerPoint == null) ? 2f : SpeedPerPoint.Value); if (num < 0f) { return 0f; } if (num > 100f) { return 100f; } return num; } } public static float MaxSpeedBonusClamped { get { float num = ((MaxSpeedBonus == null) ? 50f : MaxSpeedBonus.Value); if (num < 0f) { return 0f; } if (num > 500f) { return 500f; } return num; } } public static bool SpeedBonusIsCurve { get { if (SpeedBonusMode != null) { return !string.Equals(SpeedBonusMode.Value, "linear", StringComparison.OrdinalIgnoreCase); } return true; } } public static float ChargePerPointClamped { get { float num = ((ChargePerPoint == null) ? 2f : ChargePerPoint.Value); if (num < 0f) { return 0f; } if (num > 100f) { return 100f; } return num; } } public static float MaxChargeBonusClamped { get { float num = ((MaxChargeBonus == null) ? 50f : MaxChargeBonus.Value); if (num < 0f) { return 0f; } if (num > 500f) { return 500f; } return num; } } public static int ExtraUpgradeAboveClamped { get { int num = ((ExtraUpgradeAbove == null) ? 6 : ExtraUpgradeAbove.Value); if (num < 0) { return 0; } if (num > 10) { return 10; } return num; } } public static int DoubleUpgradeAtClamped { get { int num = ((DoubleUpgradeAt == null) ? 10 : DoubleUpgradeAt.Value); if (num < 0) { return 0; } if (num > 10) { return 10; } return num; } } public static int DebugInjectPointsClamped { get { int num = ((DebugInjectPoints == null) ? 4 : DebugInjectPoints.Value); if (num < 0) { return 0; } int maxLevel = MaxLevel; if (num > maxLevel) { return maxLevel; } return num; } } public static float TenPointHaloLiftClamped { get { float num = ((TenPointHaloLift == null) ? 1.2f : TenPointHaloLift.Value); if (num < 0f) { return 0f; } if (num > 6f) { return 6f; } return num; } } public static float TenPointHaloSizeClamped { get { float num = ((TenPointHaloSize == null) ? 0.3f : TenPointHaloSize.Value); if (num < 0.3f) { return 0.3f; } if (num > 3f) { return 3f; } return num; } } public static int TenPointHaloMaxClamped { get { int num = ((TenPointHaloMax == null) ? 64 : TenPointHaloMax.Value); if (num < 0) { return 0; } if (num > 256) { return 256; } return num; } } public static string MarkerStyleName { get { string text = ((TenPointMarkerStyle == null) ? null : TenPointMarkerStyle.Value); if (string.IsNullOrEmpty(text)) { return "auto"; } text = text.Trim().ToLowerInvariant(); if (text == "halo" || text == "none") { return text; } return "auto"; } } public static bool HaloArmed { get { QualityMath.ResolveMarkerStyle(MarkerStyleName, out var haloOn); if (haloOn && TenPointHalo != null) { return TenPointHalo.Value; } return false; } } public static string HaloColorHex { get { string text = ((TenPointHaloColor == null) ? null : TenPointHaloColor.Value); if (!string.IsNullOrEmpty(text)) { return text; } return "FFD24A"; } } public static bool HaloPulseEnabled { get { if (TenPointHaloPulse != null) { return TenPointHaloPulse.Value; } return true; } } public static float HaloSpinClamped { get { float num = ((TenPointHaloSpin == null) ? 1f : TenPointHaloSpin.Value); if (float.IsNaN(num) || num < 0f) { return 0f; } if (num > 5f) { return 5f; } return num; } } public static bool StorageGridsEnabled { get { if (Enabled != null && Enabled.Value && StorageQualityGrids != null) { return StorageQualityGrids.Value; } return false; } } public static float StorageGridsPerPointClamped { get { float num = ((StorageQualityGridsPerPoint == null) ? 0.5f : StorageQualityGridsPerPoint.Value); if (float.IsNaN(num) || float.IsInfinity(num) || num < 0f) { return 0f; } if (num > 2f) { return 2f; } return num; } } public static int StorageGridsMaxClamped { get { int num = ((StorageQualityGridsMax == null) ? 20 : StorageQualityGridsMax.Value); if (num < 0) { return 0; } if (num > 200) { return 200; } return num; } } public static bool SplitterQualityFilterEnabled { get { if (Enabled != null && Enabled.Value && SplitterQualityFilter != null) { return SplitterQualityFilter.Value; } return false; } } public static bool SorterQualityFilterEnabled { get { if (Enabled != null && Enabled.Value && SorterQualityFilter != null) { return SorterQualityFilter.Value; } return false; } } public static bool ItemQualityBadgesEnabled { get { if (Enabled != null && Enabled.Value && ItemQualityBadges != null) { return ItemQualityBadges.Value; } return false; } } public static int ItemQualityBadgeMinPointsClamped { get { int num = ((ItemQualityBadgeMinPoints == null) ? 1 : ItemQualityBadgeMinPoints.Value); if (num < 1) { return 1; } if (num > 10) { return 10; } return num; } } public static string ItemQualityBadgeStyleNormalized => QualityMath.BadgeStyleNormalized((ItemQualityBadgeStyle == null) ? null : ItemQualityBadgeStyle.Value); public static int MaxLevel { get { int value = MaxQuality.Value; if (value < 0) { return 0; } if (value > 10) { return 10; } return value; } } public static void Init(ConfigFile cfg) { IsolateBuildingStacks = cfg.Bind<bool>("quality", "isolateBuildingStacks", true, QualityConfigText.DescriptionOf("isolateBuildingStacks")); IsolateStorageContainers = cfg.Bind<bool>("quality", "isolateStorageContainers", true, QualityConfigText.DescriptionOf("isolateStorageContainers")); SpeedBonus = cfg.Bind<bool>("quality", "speedBonus", true, QualityConfigText.DescriptionOf("speedBonus")); ChargeBonus = cfg.Bind<bool>("quality", "chargeBonus", true, QualityConfigText.DescriptionOf("chargeBonus")); LabSpeedBonus = cfg.Bind<bool>("quality", "labSpeedBonus", true, QualityConfigText.DescriptionOf("labSpeedBonus")); TurretFireRateBonus = cfg.Bind<bool>("quality", "turretFireRateBonus", true, QualityConfigText.DescriptionOf("turretFireRateBonus")); PowerGenBonus = cfg.Bind<bool>("quality", "powerGenBonus", true, QualityConfigText.DescriptionOf("powerGenBonus")); LauncherSpeedBonus = cfg.Bind<bool>("quality", "launcherSpeedBonus", true, QualityConfigText.DescriptionOf("launcherSpeedBonus")); SprayerIncBonus = cfg.Bind<bool>("quality", "sprayerIncBonus", true, QualityConfigText.DescriptionOf("sprayerIncBonus")); ItemQualityBadges = cfg.Bind<bool>("quality", "itemQualityBadges", true, QualityConfigText.DescriptionOf("itemQualityBadges")); ItemQualityBadgeStyle = cfg.Bind<string>("quality", "itemQualityBadgeStyle", "number", QualityConfigText.DescriptionOf("itemQualityBadgeStyle")); ItemQualityBadgeMinPoints = cfg.Bind<int>("quality", "itemQualityBadgeMinPoints", 1, QualityConfigText.DescriptionOf("itemQualityBadgeMinPoints")); TenPointMarkerStyle = cfg.Bind<string>("quality", "tenPointMarkerStyle", "auto", QualityConfigText.DescriptionOf("tenPointMarkerStyle")); TenPointHaloSize = cfg.Bind<float>("quality", "tenPointHaloSize", 0.3f, QualityConfigText.DescriptionOf("tenPointHaloSize")); TenPointHaloPulse = cfg.Bind<bool>("quality", "tenPointHaloPulse", true, QualityConfigText.DescriptionOf("tenPointHaloPulse")); TenPointHaloSpin = cfg.Bind<float>("quality", "tenPointHaloSpin", 1f, QualityConfigText.DescriptionOf("tenPointHaloSpin")); MigrateCfgLayout(cfg); } private static void MigrateCfgLayout(ConfigFile cfg) { try { if (cfg == null) { return; } string configFilePath = cfg.ConfigFilePath; if (string.IsNullOrEmpty(configFilePath) || !File.Exists(configFilePath)) { return; } Dictionary<string, string> dictionary = QualityConfigText.ParseKeyValues(File.ReadAllText(configFilePath, Encoding.UTF8)); if (QualityConfigText.IsPlayerKeySet(dictionary.Keys)) { return; } int num = QualityConfigText.CountLegacyKeys(dictionary.Keys); bool saveOnConfigSet = cfg.SaveOnConfigSet; cfg.SaveOnConfigSet = false; try { for (int i = 0; i < QualityConfigText.PlayerEntries.Length; i++) { QualityConfigText.CfgEntry cfgEntry = QualityConfigText.PlayerEntries[i]; ConfigEntryBase val = FindEntry(cfgEntry.Key); string value = null; if (val != null && dictionary.TryGetValue(cfgEntry.Key, out value)) { ApplyParsedValue(val, value); } } } finally { cfg.SaveOnConfigSet = saveOnConfigSet; } List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(); for (int j = 0; j < QualityConfigText.PlayerEntries.Length; j++) { QualityConfigText.CfgEntry cfgEntry2 = QualityConfigText.PlayerEntries[j]; ConfigEntryBase val2 = FindEntry(cfgEntry2.Key); list.Add(new KeyValuePair<string, string>(cfgEntry2.Key, (val2 == null) ? cfgEntry2.DefaultText : FormatEntryValue(val2))); } File.WriteAllText(configFilePath, QualityConfigText.BuildCfgText("0.17.0", list), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); ClearOrphanedEntries(cfg); Plugin.Log.LogInfo((object)("[QSystem][config] rewrote cfg with cleaned Chinese layout (" + QualityConfigText.PlayerEntries.Length + " keys, dropped " + num + " legacy keys)")); } catch (Exception e) { QualityEnv.WarnOnce("config.chinese", e); } } private static ConfigEntryBase FindEntry(string key) { return (ConfigEntryBase)(key switch { "isolateBuildingStacks" => IsolateBuildingStacks, "isolateStorageContainers" => IsolateStorageContainers, "speedBonus" => SpeedBonus, "chargeBonus" => ChargeBonus, "labSpeedBonus" => LabSpeedBonus, "turretFireRateBonus" => TurretFireRateBonus, "powerGenBonus" => PowerGenBonus, "launcherSpeedBonus" => LauncherSpeedBonus, "sprayerIncBonus" => SprayerIncBonus, "itemQualityBadges" => ItemQualityBadges, "itemQualityBadgeStyle" => ItemQualityBadgeStyle, "itemQualityBadgeMinPoints" => ItemQualityBadgeMinPoints, "tenPointMarkerStyle" => TenPointMarkerStyle, "tenPointHaloSize" => TenPointHaloSize, "tenPointHaloPulse" => TenPointHaloPulse, "tenPointHaloSpin" => TenPointHaloSpin, _ => null, }); } private static void ApplyParsedValue(ConfigEntryBase entry, string raw) { object boxedValue = entry.BoxedValue; if (boxedValue is bool) { if (bool.TryParse(raw, out var result) && result != (bool)boxedValue) { entry.BoxedValue = result; } } else if (boxedValue is int) { if (int.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) && result2 != (int)boxedValue) { entry.BoxedValue = result2; } } else if (boxedValue is float) { if (float.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out var result3) && !float.IsNaN(result3) && !float.IsInfinity(result3) && result3 != (float)boxedValue) { entry.BoxedValue = result3; } } else if (boxedValue is string) { string text = ((raw == null) ? string.Empty : raw.Trim()); if (text.Length > 0 && !string.Equals(text, (string)boxedValue, StringComparison.Ordinal)) { entry.BoxedValue = text; } } } private static string FormatEntryValue(ConfigEntryBase entry) { object boxedValue = entry.BoxedValue; if (boxedValue is bool) { if (!(bool)boxedValue) { return "false"; } return "true"; } if (boxedValue is int num) { return num.ToString(CultureInfo.InvariantCulture); } if (boxedValue is float f) { if (float.IsNaN(f) || float.IsInfinity(f)) { f = 0f; } return f.ToString("R", CultureInfo.InvariantCulture); } return (boxedValue as string) ?? string.Empty; } private static void ClearOrphanedEntries(ConfigFile cfg) { try { IDictionary dictionary = null; PropertyInfo property = typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { dictionary = property.GetValue(cfg, null) as IDictionary; } if (dictionary == null) { FieldInfo field = typeof(ConfigFile).GetField("orphanedEntries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { dictionary = field.GetValue(cfg) as IDictionary; } } dictionary?.Clear(); } catch (Exception) { } } public static int ClampChance(float chance) { int num = MaxUpgradeChance.Value; if (num < 0) { num = 0; } if (num > 100) { num = 100; } int num2 = (int)chance; if (num2 < 0) { return 0; } if (num2 > num) { return num; } return num2; } } public static class QualityConfigText { public sealed class CfgEntry { public readonly string Key; public readonly string TypeName; public readonly string DefaultText; public readonly string Description; public CfgEntry(string key, string typeName, string defaultText, string description) { Key = key; TypeName = typeName; DefaultText = defaultText; Description = description; } } public const string PluginName = "DSP Quality System"; public const string PluginGuid = "dsp.qualitysystem"; public const string SectionName = "quality"; public static readonly CfgEntry[] PlayerEntries = new CfgEntry[16] { new CfgEntry("isolateBuildingStacks", "Boolean", "true", "【DSP 品质系统 v0.15.0】本文件只开放 16 个玩家开关,其余平衡参数已内置;调试探针需环境变量 DSPQS_DEV=1。|隔离-背包建筑分格:true = 玩家背包里的同类建筑按每件品质点数分格存放(0 点与 10 点不会合并成一格);false = 原版合并行为。非建筑物品始终按原版合并。"), new CfgEntry("isolateStorageContainers", "Boolean", "true", "隔离-储物容器分格:true = 在 isolateBuildingStacks=true 的前提下,储物仓、战场分析基站等通用储物容器里的建筑物品也按每件品质点数分格存放;false = 只隔离玩家背包。机甲燃料/弹药/炸弹/战斗机格、过滤容器、物流塔内部、储液罐永远不隔离。代价:同一物品可能占更多格位。"), new CfgEntry("speedBonus", "Boolean", "true", "品质加成-制造与采集速度:true = 建筑品质点数提高制造台/采矿机的工作速度(内置分段曲线,10 点 = 2.00×);false = 原版速度。"), new CfgEntry("chargeBonus", "Boolean", "true", "品质加成-物流塔充电上限:true = 建筑品质点数提高物流塔每 tick 可充入的电量上限(内置分段曲线,10 点 = 2.00×,总电量上限不变、单位时间取电上升);false = 原版充电速度。"), new CfgEntry("labSpeedBonus", "Boolean", "true", "品质加成-研究站研究速度:true = 建筑品质点数提高研究站的研究吞吐(内置分段曲线,10 点 = 2.00×);false = 原版研究速度。"), new CfgEntry("turretFireRateBonus", "Boolean", "true", "品质加成-防御塔射速:true = 建筑品质点数提高防御塔开火速度(高斯/激光/加农/等离子/导弹/近程等离子,内置分段曲线,10 点 = 2.00×);false = 原版射速。伤害、射程、弹药消耗不变。"), new CfgEntry("powerGenBonus", "Boolean", "true", "品质加成-发电机功率:true = 建筑品质点数提高发电机每 tick 发电功率(风力/光伏/地热/火力/射线接收站,内置分段曲线,10 点 = 2.00×);false = 原版功率。火力发电机按比例多烧燃料、射线接收站按比例多产。"), new CfgEntry("launcherSpeedBonus", "Boolean", "true", "品质加成-弹射器与发射井:true = 建筑品质点数提高电磁轨道弹射器和垂直发射井的发射速度(内置分段曲线,10 点 = 2.00×);false = 原版发射速度。"), new CfgEntry("sprayerIncBonus", "Boolean", "true", "品质加成-喷涂机额外品质点:true = 喷涂机按自身建筑品质点数,在喷到的物品上额外 +1 品质点(同一曲线当概率:0 点 0%、5 点 15%、10 点 100%,已达 10 点上限不加);false = 原版喷涂结果。"), new CfgEntry("itemQualityBadges", "Boolean", "true", "可视化-物品品质角标:true = 在物品图标角上显示每件品质点数(0–10,按 总点数/堆叠数 向下取整),覆盖玩家背包、打开的储物仓面板、手持物品提示;纯显示、不写存档;false = 不注册补丁、不创建任何角标(原版等价)。"), new CfgEntry("itemQualityBadgeStyle", "String", "number", "可视化-角标样式:number = 只显示数字(1–10);dot = 只显示一个按档位着色的圆点;both = 圆点 + 数字。无法识别时按 number 处理。"), new CfgEntry("itemQualityBadgeMinPoints", "Int32", "1", "可视化-角标显示阈值:每件品质点数达到该值才显示角标(自动夹到 1–10)。默认 1 = 有品质点就显示;设 10 = 只标出满品物品。"), new CfgEntry("tenPointMarkerStyle", "String", "auto", "可视化-满品建筑标记样式:auto = 画金色光环(默认);halo = 只画金色光环;none = 不画任何标记。屏幕描边功能已删除,旧值 outline/both 及无法识别的值都会按 auto(画光环)处理。"), new CfgEntry("tenPointHaloSize", "Single", "0.3", "可视化-光环大小倍率:光环半径 = clamp(max(占地X,占地Z) × 0.42, 1.0, 3.2) × 本值,使用时夹到 0.3–3.0。默认 0.3(比首版 1.0 小 70%,不会比建筑本身还大);想要更醒目可调到 1.0 左右。"), new CfgEntry("tenPointHaloPulse", "Boolean", "true", "可视化-光环呼吸:true = 光环做 alpha 呼吸(约 2 秒一轮,0.6–1.0);false = 恒定亮度 0.85,长时间看不刺眼。"), new CfgEntry("tenPointHaloSpin", "Single", "1", "可视化-光环自转速度:倍率制,1 = 基础约 20°/秒,0 = 不自转,2 = 两倍;使用时夹到 0–5。") }; public static readonly string[] PlayerKeys = BuildKeyList(); private static string[] BuildKeyList() { string[] array = new string[PlayerEntries.Length]; for (int i = 0; i < PlayerEntries.Length; i++) { array[i] = PlayerEntries[i].Key; } return array; } public static string DescriptionOf(string key) { CfgEntry cfgEntry = Find(key); if (cfgEntry != null) { return cfgEntry.Description; } return string.Empty; } public static CfgEntry Find(string key) { if (string.IsNullOrEmpty(key)) { return null; } for (int i = 0; i < PlayerEntries.Length; i++) { if (string.Equals(PlayerEntries[i].Key, key, StringComparison.Ordinal)) { return PlayerEntries[i]; } } return null; } public static Dictionary<string, string> ParseKeyValues(string text) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(text)) { return dictionary; } string[] array = text.Replace("\r\n", "\n").Replace('\r', '\n').Split(new char[1] { '\n' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length == 0 || text2[0] == '#' || text2[0] == ';' || text2[0] == '[') { continue; } int num = text2.IndexOf('='); if (num <= 0) { continue; } string text3 = text2.Substring(0, num).Trim(); if (text3.Length != 0 && IsIdentifier(text3)) { string value = text2.Substring(num + 1).Trim(); if (!dictionary.ContainsKey(text3)) { dictionary.Add(text3, value); } } } return dictionary; } private static bool IsIdentifier(string key) { for (int i = 0; i < key.Length; i++) { char c = key[i]; if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (i <= 0 || c < '0' || c > '9') && c != '_') { return false; } } return true; } public static bool IsPlayerKeySet(IEnumerable<string> keys) { if (keys == null) { return false; } int num = 0; foreach (string key in keys) { num++; if (FindIgnoreCase(key) == null) { return false; } } return num == PlayerEntries.Length; } private static CfgEntry FindIgnoreCase(string key) { if (string.IsNullOrEmpty(key)) { return null; } for (int i = 0; i < PlayerEntries.Length; i++) { if (string.Equals(PlayerEntries[i].Key, key, StringComparison.OrdinalIgnoreCase)) { return PlayerEntries[i]; } } return null; } public static int CountLegacyKeys(IEnumerable<string> keys) { int num = 0; if (keys == null) { return 0; } foreach (string key in keys) { if (FindIgnoreCase(key) == null) { num++; } } return num; } public static string BuildCfgText(string version, IList<KeyValuePair<string, string>> values) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); if (values != null) { for (int i = 0; i < values.Count; i++) { KeyValuePair<string, string> keyValuePair = values[i]; if (!string.IsNullOrEmpty(keyValuePair.Key)) { dictionary[keyValuePair.Key] = keyValuePair.Value ?? string.Empty; } } } StringBuilder stringBuilder = new StringBuilder(4096); stringBuilder.Append("## Settings file was created by plugin ").Append("DSP Quality System").Append(" v") .Append(string.IsNullOrEmpty(version) ? "0.0.0" : version) .Append("\r\n"); stringBuilder.Append("## Plugin GUID: ").Append("dsp.qualitysystem").Append("\r\n"); stringBuilder.Append("\r\n"); stringBuilder.Append('[').Append("quality").Append(']') .Append("\r\n"); stringBuilder.Append("\r\n"); for (int j = 0; j < PlayerEntries.Length; j++) { CfgEntry cfgEntry = PlayerEntries[j]; if (!dictionary.TryGetValue(cfgEntry.Key, out var value) || value == null) { value = cfgEntry.DefaultText; } stringBuilder.Append("## ").Append(cfgEntry.Description).Append("\r\n"); stringBuilder.Append("# Setting type: ").Append(cfgEntry.TypeName).Append("\r\n"); stringBuilder.Append("# Default value: ").Append(cfgEntry.DefaultText).Append("\r\n"); stringBuilder.Append(cfgEntry.Key).Append(" = ").Append(value) .Append("\r\n"); stringBuilder.Append("\r\n"); } return stringBuilder.ToString(); } } public class PlanetQuality { public int planetId; public byte[] buildingInc = new byte[0]; public int[] poolInc = new int[0]; public int[] poolCount = new int[0]; public short[] inputLevel = new short[0]; public short[] pendingLevel = new short[0]; public byte[] wasReplicating = new byte[0]; public int[] prevProduced = new int[0]; public int[] pickedSinceTick = new int[0]; public int[] storageBase = new int[0]; public int Capacity => buildingInc.Length; public void EnsureCapacity(int capacity) { if (capacity > buildingInc.Length) { int num = ((buildingInc.Length < 64) ? 64 : (buildingInc.Length * 2)); if (num < capacity) { num = capacity; } int num2 = ((inputLevel != null) ? inputLevel.Length : 0); buildingInc = Grow(buildingInc, num); poolInc = Grow(poolInc, num); poolCount = Grow(poolCount, num); inputLevel = Grow(inputLevel, num); for (int i = num2; i < inputLevel.Length; i++) { inputLevel[i] = -1; } int num3 = ((pendingLevel != null) ? pendingLevel.Length : 0); pendingLevel = Grow(pendingLevel, num); for (int j = num3; j < pendingLevel.Length; j++) { pendingLevel[j] = -1; } wasReplicating = Grow(wasReplicating, num); storageBase = Grow(storageBase, num); pickedSinceTick = Grow(pickedSinceTick, num); int num4 = ((prevProduced != null) ? prevProduced.Length : 0); prevProduced = Grow(prevProduced, num * 4); for (int k = num4; k < prevProduced.Length; k++) { prevProduced[k] = -1; } } } private static T[] Grow<T>(T[] source, int length) { T[] array = new T[length]; if (source != null && source.Length != 0) { Array.Copy(source, array, source.Length); } return array; } } public static class QualityData { internal sealed class PlanetFilterTable { internal int[] splitterRules = new int[0]; internal byte[] sorterMin = new byte[0]; internal int splitterRuleCount; internal int sorterRuleCount; internal int capacity; internal int EntityCapacity => capacity; internal void EnsureCapacity(int entityId) { int num = entityId + 1; if (num <= capacity) { return; } int num2; for (num2 = ((capacity < 64) ? 64 : capacity); num2 < num; num2 *= 2) { } if (num2 > 1048576) { num2 = 1048576; } if (num2 > capacity) { int[] destinationArray = new int[num2 * 4]; byte[] destinationArray2 = new byte[num2]; if (splitterRules.Length != 0) { Array.Copy(splitterRules, destinationArray, splitterRules.Length); } if (sorterMin.Length != 0) { Array.Copy(sorterMin, destinationArray2, sorterMin.Length); } splitterRules = destinationArray; sorterMin = destinationArray2; capacity = num2; } } internal int GetSplitterRule(int entityId, int port) { int num = entityId * 4 + port; if (entityId < 0 || port < 0 || port >= 4 || num >= splitterRules.Length) { return 0; } return splitterRules[num]; } internal bool HasAnySplitterRule(int entityId) { if (entityId < 0) { return false; } int num = entityId * 4; if (num + 4 > splitterRules.Length) { return false; } for (int i = 0; i < 4; i++) { if (splitterRules[num + i] != 0) { return true; } } return false; } internal int GetSorterMin(int entityId) { if (entityId < 0 || entityId >= sorterMin.Length) { return 0; } return sorterMin[entityId]; } } public const int ProducedSlots = 4; public const int MaxEntityId = 1048576; public static readonly Dictionary<int, PlanetQuality> planets = new Dictionary<int, PlanetQuality>(); public static readonly Dictionary<int, byte> prebuildInc = new Dictionary<int, byte>(); private static int _cachedPlanetId = int.MinValue; private static PlanetQuality _cached; private static int _dismantleDebugLogged; private static int _isolationDebugCount; internal const int FilterMaxEntityId = 1048576; private static readonly Dictionary<int, PlanetFilterTable> filterTables = new Dictionary<int, PlanetFilterTable>(); private static int filterRuleTotal; private static int filterEpoch; [ThreadStatic] private static PlanetFilterTable cachedFilterTable; [ThreadStatic] private static int cachedFilterPlanetId; [ThreadStatic] private static int cachedFilterEpoch; public static Action<int, int> BuildingIncChanged; internal static bool HasAnyFilterRules => filterRuleTotal > 0; internal static int TotalFilterRuleCount => filterRuleTotal; private static void InvalidateCache() { _cachedPlanetId = int.MinValue; _cached = null; } public static PlanetQuality Get(int planetId) { if (_cachedPlanetId == planetId) { return _cached; } planets.TryGetValue(planetId, out var value); _cachedPlanetId = planetId; _cached = value; return value; } public static PlanetQuality GetOrCreate(int planetId) { if (planetId == _cachedPlanetId && _cached != null) { return _cached; } if (!planets.TryGetValue(planetId, out var value)) { value = new PlanetQuality(); value.planetId = planetId; planets[planetId] = value; } _cachedPlanetId = planetId; _cached = value; return value; } public static void EnsureCapacity(PlanetQuality quality, int capacity) { quality?.EnsureCapacity(capacity); } public static void RemovePlanet(int planetId) { planets.Remove(planetId); ClearFiltersForPlanet(planetId); InvalidateCache(); } public static void ClearAll() { planets.Clear(); prebuildInc.Clear(); ClearAllFilters(); InvalidateCache(); } public static byte GetBuildingInc(int planetId, int entityId) { PlanetQuality planetQuality = Get(planetId); if (planetQuality == null || entityId < 0 || entityId >= planetQuality.buildingInc.Length) { return 0; } return planetQuality.buildingInc[entityId]; } public static void SetBuildingInc(int planetId, int entityId, byte inc) { if (entityId >= 0 && entityId <= 1048576) { PlanetQuality orCreate = GetOrCreate(planetId); orCreate.EnsureCapacity(entityId + 1); orCreate.buildingInc[entityId] = (byte)((inc > 10) ? 10 : inc); BuildingIncChanged?.Invoke(planetId, entityId); } } public static short GetInputLevel(int planetId, int entityId) { PlanetQuality planetQuality = Get(planetId); if (planetQuality == null || entityId < 0 || entityId >= planetQuality.inputLevel.Length) { return -1; } return planetQuality.inputLevel[entityId]; } public static void SetInputLevel(int planetId, int entityId, short level) { if (entityId >= 0 && entityId <= 1048576) { PlanetQuality orCreate = GetOrCreate(planetId); orCreate.EnsureCapacity(entityId + 1); orCreate.inputLevel[entityId] = level; } } public static void AddPool(int planetId, int entityId, int incPoints, int productCount) { if (entityId >= 0 && entityId <= 1048576 && productCount > 0) { PlanetQuality orCreate = GetOrCreate(planetId); orCreate.EnsureCapacity(entityId + 1); QualityMath.AddPool(orCreate.poolInc[entityId], orCreate.poolCount[entityId], incPoints, productCount, out var newPoolInc, out var newPoolCount); orCreate.poolInc[entityId] = newPoolInc; orCreate.poolCount[entityId] = newPoolCount; } } public static int TakeOneProductInc(int planetId, int entityId) { PlanetQuality planetQuality = Get(planetId); if (planetQuality == null || entityId < 0 || entityId >= planetQuality.poolCount.Length) { return 0; } int newPoolInc; int newPoolCount; int result = QualityMath.TakeOne(planetQuality.poolInc[entityId], planetQuality.poolCount[entityId], 10, out newPoolInc, out newPoolCount); planetQuality.poolInc[entityId] = newPoolInc; planetQuality.poolCount[entityId] = newPoolCount; return result; } public static int GetPoolInc(int planetId, int entityId) { PlanetQuality planetQuality = Get(planetId); if (planetQuality == null || entityId < 0 || entityId >= planetQuality.poolInc.Length) { return 0; } return planetQuality.poolInc[entityId]; } public static int GetPoolCount(int planetId, int entityId) { PlanetQuality planetQuality = Get(planetId); if (planetQuality == null || entityId < 0 || entityId >= planetQuality.poolCount.Length) { return 0; } return planetQuality.poolCount[entityId]; } public static int GetPrevProduced(int planetId, int entityId, int slot) { PlanetQuality planetQuality = Get(planetId); if (planetQuality == null || entityId < 0 || slot < 0 || slot >= 4) { return -1; } int num = entityId * 4 + slot; if (num >= planetQuality.prevProduced.Length) { return -1; } return planetQuality.prevProduced[num]; } public static void SetPrevProduced(int planetId, int entityId, int slot, int value) { if (entityId >= 0 && entityId <= 1048576 && slot >= 0 && slot < 4) { PlanetQuality orCreate = GetOrCreate(planetId); orCreate.EnsureCapacity(entityId + 1); int num = entityId * 4 + slot; if (num < orCreate.prevProduced.Length) { orCreate.prevProduced[num] = value; } } } public static void NoteProductPicked(int planetId, int entityId) { if (entityId >= 0 && entityId <= 1048576) { PlanetQuality orCreate = GetOrCreate(planetId); orCreate.EnsureCapacity(entityId + 1); if (entityId < orCreate.pickedSinceTick.Length && orCreate.pickedSinceTick[entityId] < int.MaxValue) { orCreate.pickedSinceTick[entityId]++; } } } public static int ConsumeProductPicked(int planetId, int entityId) { PlanetQuality planetQuality = Get(planetId); if (planetQuality == null || entityId < 0 || entityId >= planetQuality.pickedSinceTick.Length) { return 0; } int result = planetQuality.pickedSinceTick[entityId]; planetQuality.pickedSinceTick[entityId] = 0; return result; } public static short GetPendingLevel(int planetId, int entityId) { PlanetQuality planetQuality = Get(planetId); if (planetQuality == null || entityId < 0 || entityId >= planetQuality.pendingLevel.Length) { return -1; } return planetQuality.pendingLevel[entityId]; } public static void SetPendingLevel(int planetId, int entityId, short level) { if (entityId >= 0 && entityId <= 1048576) { PlanetQuality orCreate = GetOrCreate(planetId); orCreate.EnsureCapacity(entityId + 1); orCreate.pendingLevel[entityId] = level; } } public static byte GetWasReplicating(int planetId, int entityId) { PlanetQuality planetQuality = Get(planetId); if (planetQuality == null || entityId < 0 || entityId >= planetQuality.wasReplicating.Length) { return 0; } return planetQuality.wasReplicating[entityId]; } public static void SetWasReplicating(int planetId, int entityId, byte value) { if (entityId >= 0 && entityId <= 1048576) { PlanetQuality orCreate = GetOrCreate(planetId); orCreate.EnsureCapacity(entityId + 1); orCreate.wasReplicating[entityId] = value; } } public static void SetPrebuildInc(int prebuildId, int perItem) { if (prebuildId <= 0) { return; } if (perItem <= 0) { prebuildInc.Remove(prebuildId); return; } if (perItem > 10) { perItem = 10; } prebuildInc[prebuildId] = (byte)perItem; } public static int TakePrebuildInc(int prebuildId) { if (prebuildId <= 0) { return 0; } if (!prebuildInc.TryGetValue(prebuildId, out var value)) { return 0; } prebuildInc.Remove(prebuildId); return value; } public static void ClearPrebuild(int prebuildId) { if (prebuildId > 0) { prebuildInc.Remove(prebuildId); } } public static void EnsureCapacity(int planetId, int capacity) { if (capacity > 0) { GetOrCreate(planetId).EnsureCapacity(capacity); } } public static void SetPool(int planetId, int entityId, int poolInc, int poolCount) { if (entityId >= 0 && entityId <= 1048576) { PlanetQuality orCreate = GetOrCreate(planetId); orCreate.EnsureCapacity(entityId + 1); orCreate.poolInc[entityId] = ((poolInc >= 0) ? poolInc : 0); orCreate.poolCount[entityId] = ((poolCount >= 0) ? poolCount : 0); } } public static int MinerOutputInc(PlanetFactory factory, int entityId) { try { if (!QualityConfig.Enabled.Value || factory == null || factory.planet == null) { return 0; } int num = QualityRules.RollMinerQuality(factory.planet.id, entityId); Probes.RecordMinerCall(entityId, num); if (num > 0) { Probes.RecordMinerHit(entityId, num); } return num; } catch (Exception e) { QualityEnv.WarnOnce("miner.roll", e); return 0; } } public static int MinerCollectorIncLevel(PlanetFactory factory, int entityId, int transferCount) { try { if (!QualityConfig.Enabled.Value || factory == null || factory.planet == null || entityId <= 0 || transferCount <= 0) { return 0; } int num = QualityRules.RollMinerQuality(factory.planet.id, entityId); Probes.RecordMinerCall(entityId, num); if (num > 0) { Probes.RecordMinerHit(entityId, num); } return num; } catch (Exception e) { QualityEnv.WarnOnce("miner.collector.roll", e); return 0; } } public static void ClearEntity(int planetId, int entityId) { PlanetQuality planetQuality = Get(planetId); if (planetQuality == null || entityId < 0) { return; } if (entityId < planetQuality.inputLevel.Length) { planetQuality.inputLevel[entityId] = -1; } if (entityId < planetQuality.pendingLevel.Length) { planetQuality.pendingLevel[entityId] = -1; } if (entityId < planetQuality.wasReplicating.Length) { planetQuality.wasReplicating[entityId] = 0; } if (entityId < planetQuality.buildingInc.Length) { planetQuality.buildingInc[entityId] = 0; } if (entityId < planetQuality.storageBase.Length) { planetQuality.storageBase[entityId] = 0; } ClearFilterEntity(planetId, entityId); if (entityId < planetQuality.poolCount.Length) { planetQuality.poolInc[entityId] = 0; planetQuality.poolCount[entityId] = 0; } if (entityId < planetQuality.pickedSinceTick.Length) { planetQuality.pickedSinceTick[entityId] = 0; } if (entityId * 4 < planetQuality.prevProduced.Length) { for (int i = 0; i < 4; i++) { planetQuality.prevProduced[entityId * 4 + i] = -1; } } } public static int DismantleInc(PlanetFactory factory, int entityId) { try { if (!QualityConfig.Enabled.Value || QualityConfig.GiveDismantleInc == null || !QualityConfig.GiveDismantleInc.Value) { return 0; } if (factory == null || factory.planet == null || entityId <= 0) { return 0; } int id = factory.planet.id; if (id < 0) { return 0; } byte buildingInc = GetBuildingInc(id, entityId); int num = QualityMath.DismantleIncFrom(buildingInc, 10, giveDismantleInc: true); if (num > 0 && QualityConfig.DebugProbes.Value && _dismantleDebugLogged < 20) { _dismantleDebugLogged++; if (Plugin.Log != null) { Plugin.Log.LogInfo((object)("[QSystem][dismantle] inc returned eid=" + entityId + " points=" + buildingInc + " inc=" + num)); } } return num; } catch (Exception e) { QualityEnv.WarnOnce("dismantle.inc", e); return 0; } } public static float LabSpeedFactor(int planetId, int entityId) { return FeatureFactor(planetId, entityId, QualityConfig.LabSpeedBonus != null && QualityConfig.LabSpeedBonus.Value); } public static float TurretFireFactor(int planetId, int entityId) { return FeatureFactor(planetId, entityId, QualityConfig.TurretFireRateBonus != null && QualityConfig.TurretFireRateBonus.Value); } public static float PowerGenFactor(int planetId, int entityId) { return FeatureFactor(planetId, entityId, QualityConfig.PowerGenBonus != null && QualityConfig.PowerGenBonus.Value); } public static float LauncherSpeedFactor(int planetId, int entityId) { return FeatureFactor(planetId, entityId, QualityConfig.LauncherSpeedBonus != null && QualityConfig.LauncherSpeedBonus.Value); } private static float FeatureFactor(int planetId, int entityId, bool enabled) { try { if (!enabled || QualityConfig.Enabled == null || !QualityConfig.Enabled.Value) { return 1f; } if (planetId < 0 || entityId < 0) { return 1f; } return QualityMath.FeatureFactorFrom(GetBuildingInc(planetId, entityId), enabled, QualityConfig.SpeedBonusIsCurve, QualityConfig.SpeedPerPointClamped, QualityConfig.MaxSpeedBonusClamped); } catch (Exception) { return 1f; } } public static int SprayBonusPoints(int sprayedInc, int entityId) { try { if (sprayedInc <= 0 || entityId < 0) { return sprayedInc; } if (QualityConfig.Enabled == null || !QualityConfig.Enabled.Value || QualityConfig.SprayerIncBonus == null || !QualityConfig.SprayerIncBonus.Value) { return sprayedInc; } int currentPlanetId = QualityEnv.CurrentPlanetId; if (currentPlanetId < 0) { return sprayedInc; } int buildingInc = GetBuildingInc(currentPlanetId, entityId); int num = QualityMath.SprayBonus(sprayedInc, buildingInc, QualityRules.RollPercent, 10); P5_Sprayer.NoteSpray(sprayedInc, num); return num; } catch (Exception) { return sprayedInc; } } public static int FractionatorProduceInc(PlanetFactory factory, int entityId, int fluidInputInc, int fluidInputCount) { try { if (!QualityConfig.Enabled.Value || factory == null || entityId < 0) { return 0; } int planetId = factory.planetId; if (planetId < 0) { return 0; } int inputLevel = QualityMath.FractionatorInputLevel(fluidInputInc, fluidInputCount, QualityConfig.MaxLevel); int num = QualityRules.RollCycleQuality(planetId, entityId, inputLevel, allowExtraUpgrade: false); AddPool(planetId, entityId, num, 1); return num; } catch (Exception e) { QualityEnv.WarnOnce("fractionator.produce", e); return 0; } } public static bool TryInsertFractionatorProduct(CargoTraffic traffic, int beltId, int productId, PlanetFactory factory, int entityId) { if (traffic == null) { return false; } bool flag = false; int num = -1; int num2 = 0; try { if (!QualityConfig.Enabled.Value || factory == null || entityId < 0) { return traffic.TryInsertItemAtHead(beltId, productId, (byte)1, (byte)0); } num = factory.planetId; if (num < 0) { return traffic.TryInsertItemAtHead(beltId, productId, (byte)1, (byte)0); } num2 = TakeOneProductInc(num, entityId); flag = num2 > 0; bool num3 = traffic.TryInsertItemAtHead(beltId, productId, (byte)1, (byte)num2); if (!num3 && flag) { AddPool(num, entityId, num2, 1); } return num3; } catch (Exception e) { if (flag) { try { AddPool(num, entityId, num2, 1); } catch (Exception) { } } QualityEnv.WarnOnce("fractionator.ship", e); return false; } } public static void ClearPool(int planetId, int entityId) { PlanetQuality planetQuality = Get(planetId); if (planetQuality != null && entityId >= 0 && entityId < planetQuality.poolCount.Length) { planetQuality.poolInc[entityId] = 0; planetQuality.poolCount[entityId] = 0; } } public static bool IsBuildingItem(int itemId) { if (itemId <= 0) { return false; } try { ItemProtoSet items = LDB.items; if ((Object)(object)items == (Object)null) { return false; } return ((ProtoSet<ItemProto>)(object)items).Select(itemId)?.CanBuild ?? false; } catch (Exception) { return false; } } public static int CountBuildingItems() { try { ItemProtoSet items = LDB.items; ItemProto[] array = (((Object)(object)items == (Object)null) ? null : ((ProtoSet<ItemProto>)(object)items).dataArray); if (array == null) { return 0; } int num = 0; for (int i = 0; i < array.Length; i++) { if (array[i] != null && array[i].CanBuild) { num++; } } return num; } catch (Exception) { return 0; } } public static int AddIsolatedStacked(StorageComponent storage, int itemId, int count, int inc, out int remainInc) { remainInc = inc; if (storage == null || itemId <= 0 || count <= 0) { return 0; } GRID[] grids = storage.grids; if (grids == null) { return 0; } int num = storage.size; if (num > grids.Length) { num = grids.Length; } if (num <= 0) { return 0; } int remaining = count; int remainingInc = inc; int stackSize = 0; int num2 = 0; for (int i = 0; i < num; i++) { if (remaining <= 0) { break; } if (grids[i].itemId == itemId) { if (!QualityMath.SharesStack(itemId, grids[i].itemId, grids[i].count, grids[i].inc, count, inc, isBuilding: true, isPlayerInventory: true)) { num2++; } else if (!MergeIntoGrid(ref grids[i], ref stackSize, ref remaining, ref remainingInc)) { num2++; } } } if (remaining > 0) { for (int j = 0; j < num; j++) { if (remaining <= 0) { break; } if (grids[j].itemId == 0) { if (stackSize == 0) { stackSize = StackSizeOf(itemId); } grids[j].itemId = itemId; if (grids[j].filter == 0) { grids[j].stackSize = stackSize; } } if (grids[j].itemId == itemId) { if (!QualityMath.SharesStack(itemId, grids[j].itemId, grids[j].count, grids[j].inc, count, inc, isBuilding: true, isPlayerInventory: true)) { num2++; } else if (!MergeIntoGrid(ref grids[j], ref stackSize, ref remaining, ref remainingInc)) { num2++; } } } } int num3 = count - remaining; if (num3 > 0) { storage.searchStart = 0; storage.lastEmptyItem = -1; storage.NotifyStorageChange(); LogIsolationSplit(itemId, inc, num2, ContainerLabel(storage)); } remainInc = remainingInc; return num3; } private static bool MergeIntoGrid(ref GRID grid, ref int stackSize, ref int remaining, ref int remainingInc) { if (stackSize == 0) { stackSize = grid.stackSize; } int num = stackSize - grid.count; if (num <= 0) { return false; } if (remaining <= num) { grid.count += remaining; grid.inc += remainingInc; remaining = 0; remainingInc = 0; return true; } grid.count = stackSize; grid.inc += QualityMath.SplitInc(ref remaining, ref remainingInc, num); return true; } private static int StackSizeOf(int itemId) { try { int[] itemStackCount = StorageComponent.itemStackCount; if (itemStackCount != null && itemId > 0 && itemId < itemStackCount.Length && itemStackCount[itemId] > 0) { return itemStackCount[itemId]; } } catch (Exception) { } return 1000; } private static string ContainerLabel(StorageComponent storage) { try { return storage.isPlayerInventory ? "player" : ((object)Unsafe.As<EStorageType, EStorageType>(ref storage.type)/*cast due to .constrained prefix*/).ToString(); } catch (Exception) { return "unknown"; } } private static void LogIsolationSplit(int itemId, int inc, int skippedGrids, string container) { if (skippedGrids <= 0 || _isolationDebugCount >= 20) { return; } try { if (QualityConfig.DebugProbes.Value && Plugin.Log != null) { _isolationDebugCount++; Plugin.Log.LogInfo((object)("[QSystem][isolation] split item=" + itemId + " inc=" + inc + " grids=" + skippedGrids + " container=" + container)); } } catch (Exception) { } } public static int CollectFullQualityEntities(int planetId, int minLevel, List<int> entityIds) { if (entityIds == null) { return 0; } entityIds.Clear(); try { PlanetQuality planetQuality = Get(planetId); if (planetQuality == null || planetQuality.buildingInc == null) { return 0; } int num = ((minLevel <= 0) ? 1 : minLevel); if (minLevel <= 0) { return 0; } byte[] buildingInc = planetQuality.buildingInc; for (int i = 1; i < buildingInc.Length; i++) { if (buildingInc[i] >= num) { entityIds.Add(i); } } } catch (Exception e) { QualityEnv.WarnOnce("outline.collect", e); } return entityIds.Count; } internal static PlanetFilterTable FiltersForRead(int planetId) { if (filterRuleTotal <= 0) { return null; } if (cachedFilterTable != null && cachedFilterPlanetId == planetId && cachedFilterEpoch == filterEpoch) { return cachedFilterTable; } filterTables.TryGetValue(planetId, out var value); cachedFilterPlanetId = planetId; cachedFilterTable = value; cachedFilterEpoch = filterEpoch; return value; } internal static int GetSplitterRule(int planetId, int entityId, int port) { if (!filterTables.TryGetValue(planetId, out var value) || value == null) { return 0; } return value.GetSplitterRule(entityId, port); } internal static bool EntityHasSplitterRule(int planetId, int entityId) { if (!filterTables.TryGetValue(planetId, out var value) || value == null) { return false; } return value.HasAnySplitterRule(entityId); } internal static int SplitterRuleCount(int planetId) { if (!filterTables.TryGetValue(planetId, out var value) || value == null) { return 0; } return value.splitterRuleCount; } internal static void SetSplitterRule(int planetId, int entityId, int port, int packed) { if (entityId < 0 || entityId >= 1048576 || port < 0 || port >= 4) { return; } int num = QualityMath.PackSplitterRule(QualityMath.SplitterRuleItem(packed), QualityMath.SplitterRuleMin(packed)); PlanetFilterTable orCreateFilterTable = GetOrCreateFilterTable(planetId); orCreateFilterTable.EnsureCapacity(entityId); int num2 = entityId * 4 + port; int num3 = orCreateFilterTable.splitterRules[num2]; if (num3 != num) { orCreateFilterTable.splitterRules[num2] = num; if (num3 == 0 && num != 0) { orCreateFilterTable.splitterRuleCount++; filterRuleTotal++; } else if (num3 != 0 && num == 0) { orCreateFilterTable.splitterRuleCount--; filterRuleTotal--; } TouchFilters(); DropFilterTableIfEmpty(planetId, orCreateFilterTable); } } internal static void ClearSplitterRules(int planetId, int entityId) { if (!filterTables.TryGetValue(planetId, out var value) || value == null || entityId < 0 || entityId * 4 + 4 > value.splitterRules.Length) { return; } int num = entityId * 4; for (int i = 0; i < 4; i++) { if (value.splitterRules[num + i] != 0) { value.splitterRules[num + i] = 0; value.splitterRuleCount--; filterRuleTotal--; } } TouchFilters(); DropFilterTableIfEmpty(planetId, value); } internal static int GetSorterMin(int planetId, int entityId) { if (!filterTables.TryGetValue(planetId, out var value) || value == null) { return 0; } return value.GetSorterMin(entityId); } internal static int SorterRuleCount(int planetId) { if (!filterTables.TryGetValue(planetId, out var value) || value == null) { return 0; } return value.sorterRuleCount; } internal static void SetSorterMin(int planetId, int entityId, int minPoints) { if (entityId < 0 || entityId >= 1048576) { return; } int num = QualityMath.ClampQualityPoints(minPoints); PlanetFilterTable orCreateFilterTable = GetOrCreateFilterTable(planetId); orCreateFilterTable.EnsureCapacity(entityId); int num2 = orCreateFilterTable.sorterMin[entityId]; if (num2 != num) { orCreateFilterTable.sorterMin[entityId] = (byte)num; if (num2 == 0 && num != 0) { orCreateFilterTable.sorterRuleCount++; filterRuleTotal++; } else if (num2 != 0 && num == 0) { orCreateFilterTable.sorterRuleCount--; filterRuleTotal--; } TouchFilters(); DropFilterTableIfEmpty(planetId, orCreateFilterTable); } } internal static void ClearSorterMin(int planetId, int entityId) { SetSorterMin(planetId, entityId, 0); } internal static void ClearFilterEntity(int planetId, int entityId) { ClearSplitterRules(planetId, entityId); ClearSorterMin(planetId, entityId); } internal static void ClearFiltersForPlanet(int planetId) { if (filterTables.TryGetValue(planetId, out var value) && value != null) { filterRuleTotal -= value.splitterRuleCount; filterRuleTotal -= value.sorterRuleCount; if (filterRuleTotal < 0) { filterRuleTotal = 0; } filterTables.Remove(planetId); TouchFilters(); if (cachedFilterPlanetId == planetId) { cachedFilterTable = null; } } } internal static void ClearAllFilters() { filterTables.Clear(); filterRuleTotal = 0; cachedFilterTable = null; cachedFilterPlanetId = 0; TouchFilters(); } internal static PlanetFilterTable FiltersForSave(int planetId) { filterTables.TryGetValue(planetId, out var value); return value; } internal static List<int> PlanetIdsWithRules() { List<int> list = new List<int>(); foreach (KeyValuePair<int, PlanetFilterTable> filterTable in filterTables) { PlanetFilterTable value = filterTable.Value; if (value != null && (value.splitterRuleCount > 0 || value.sorterRuleCount > 0)) { list.Add(filterTable.Key); } } list.Sort(); return list; } private static PlanetFilterTable GetOrCreateFilterTable(int planetId) { if (filterTables.TryGetValue(planetId, out var value) && value != null) { return value; } value = new PlanetFilterTable(); filterTables[planetId] = value; TouchFilters(); return value; } private static void TouchFilters() { filterEpoch++; } private static void DropFilterTableIfEmpty(int planetId, PlanetFilterTable table) { if (table.splitterRuleCount == 0 && table.sorterRuleCount == 0) { filterTables.Remove(planetId); TouchFilters(); } } public static int GetStorageBase(int planetId, int entityId) { PlanetQuality planetQuality = Get(planetId); if (planetQuality == null || entityId < 0 || entityId >= planetQuality.storageBase.Length) { return 0; } return planetQuality.storageBase[entityId]; } public static void SetStorageBase(int planetId, int entityId, int baseGrids) { if (entityId >= 0 && entityId <= 1048576) { PlanetQuality orCreate = GetOrCreate(planetId); orCreate.EnsureCapacity(entityId + 1); orCreate.storageBase[entityId] = ((baseGrids >= 0) ? baseGrids : 0); } } public static void ClearStorageBase(int planetId, int entityId) { PlanetQuality planetQuality = Get(planetId); if (planetQuality != null && entityId >= 0 && entityId < planetQuality.storageBase.Length) { planetQuality.storageBase[entityId] = 0; } } public static bool HasAnyStorageBase(int planetId) { PlanetQuality planetQuality = Get(planetId); if (planetQuality == null) { return false; } int[] storageBase = planetQuality.storageBase; for (int i = 0; i < storageBase.Length; i++) { if (storageBase[i] > 0) { return true; } } return false; } } public static class QualityEnv { private static int _planetId = -1; private static int _lastPlanetId = -1; private static readonly HashSet<string> _warned = new HashSet<string>(); public static int AmbientPlanetId => _planetId; public static int CurrentPlanetId { get { if (_planetId > 0) { return _planetId; } if (_lastPlanetId > 0) { return _lastPlanetId; } try { PlanetData localPlanet = GameMain.localPlanet; if (localPlanet != null) { _lastPlanetId = localPlanet.id; return localPlanet.id; } } catch (Exception) { } return -1; } } public static void Enter(FactorySystem system) { if (system == null || system.planet == null) { _planetId = -1; return; } _planetId = system.planet.id; if (_planetId > 0) { _lastPlanetId = _planetId; } } public static void Exit() { _planetId = -1; } public static void WarnOnce(string key, Exception e) { try { if (_warned.Add(key)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[QSystem][patch] " + key + " failed: " + e)); } } } catch (Exception) { } } public static void WarnOnce(string key, string message) { try { if (_warned.Add(key)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[QSystem] " + message)); } } } catch (Exception) { } } public static void LogInfo(string message) { try { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)message); } } catch (Exception) { } } } public static class QualityMath { public const int BadgeMaxPoints = 10; public const int BadgeDefaultMinPoints = 1; public const string BadgeStyleNumber = "number"; public const string BadgeStyleDot = "dot"; public const string BadgeStyleBoth = "both"; public const string BadgeDotGlyph = "●"; public const int BadgeTierCount = 4; public const int RollResolution = 100; public const int IncPerItemMax = 10; public const int CurveMaxPoints = 10; private static readonly double[] CurveBonusPercent = new double[11] { 0.0, 2.5, 5.0, 7.5, 10.0, 15.0, 20.0, 30.0, 40.0, 60.0, 100.0 }; public const float HaloInnerRatio = 0.55f; public const float HaloOuterRatio = 1f; public const float HaloPulseMinAlpha = 0.6f; public const float HaloPulseMaxAlpha = 1f; public const float HaloPulsePeriod = 2f; public const float HaloBobAmplitude = 0.12f; public const float HaloBobPeriod = 2.5f; public const float HaloSpinDegreesPerSecond = 20f; public const float HaloRadiusFactor = 0.42f; public const float HaloMinRadius = 1f; public const float HaloMaxRadius = 3.2f; public const float HaloHoleQ = 0.55f; public const float HaloRingInnerQ = 0.58f; public const float HaloRingOuterQ = 0.82f; public const float HaloGlowEndQ = 1f; public const float HaloGlowStrength = 0.45f; public const float HaloSteadyAlpha = 0.85f; public const string HaloDefaultColorHex = "FFD24A"; public const float HaloMaxDrawDistance = 120f; public const int SplitterPortCount = 4; public const int SplitterRuleNone = 0; public const int SplitterRuleAnyItem = 0; public const int SplitterRuleItemMask = 65535; public const int SplitterRuleMinShift = 16; public const int SplitterRuleMinMask = 255; public const int QualityPointsMax = 10; public const int SorterMinNone = 0; public static int BadgePointsFromGrid(int totalInc, int count) { if (count <= 0 || totalInc <= 0) { return 0; } int num = totalInc / count; if (num < 0) { return 0; } if (num > 10) { return 10; } return num; } public static int BadgeMinPointsClamped(int value) { if (value < 1) { return 1; } if (value > 10) { return 10; } return value; } public static string BadgeStyleNormalized(string raw) { if (string.IsNullOrEmpty(raw)) { return "number"; } string a = raw.Trim(); if (string.Equals(a, "dot", StringComparison.OrdinalIgnoreCase)) { return "dot"; } if (string.Equals(a, "both", StringComparison.OrdinalIgnoreCase)) { return "both"; } return "number"; } public static bool BadgeVisible(int points, bool featureEnabled, int minPoints) { if (!featureEnabled) { return false; } if (points <= 0 || points > 10) { return false; } return points >= BadgeMinPointsClamped(minPoints); } public static string BadgeLabelText(int points, string style) { if (points <= 0 || points > 10) { return string.Empty; } string a = BadgeStyleNormalized(style); if (string.Equals(a, "dot", StringComparison.Ordinal)) { return "●"; } if (string.Equals(a, "both", StringComparison.Ordinal)) { return "●" + points; } return points.ToString(); } public static int BadgeTier(int points) { switch (points) { default: return 0; case 1: case 2: return 1; case 3: case 4: return 2; case 5: case 6: case 7: return 3; case 8: case 9: case 10: return 4; } } public static int BadgeColorRgb(int points) { return BadgeTier(points) switch { 1 => 9674926, 2 => 3114670, 3 => 8018864, 4 => 13081130, _ => 0, }; } public static int PerItemPoints(int totalInc, int count) { if (count <= 0 || totalInc <= 0) { return 0; } return totalInc / count; } public static int ChooseBestMatchGrid(int[] itemIds, int[] counts, int[] incs, int itemId) { if (itemIds == null || counts == null || incs == null || itemId <= 0) { return -1; } int num = itemIds.Length; if (counts.Length < num) { num = counts.Length; } if (incs.Length < num) { num = incs.Length; } int num2 = -1; int num3 = 0; int num4 = 0; for (int i = 0; i < num; i++) { if (itemIds[i] != itemId || counts[i] <= 0) { continue; } int num5 = ((incs[i] >= 0) ? incs[i] : 0); if (num2 < 0) { num2 = i; num3 = num5; num4 = counts[i]; continue; } long num6 = (long)num5 * (long)num4; long num7 = (long)num3 * (long)counts[i]; if (num6 > num7) { num2 = i; num3 = num5; num4 = counts[i]; } } return num2; } public static bool ShouldSwapHand(int handInc, int handCount, int bestInc, int bestCount) { if (handCount <= 0 || bestCount <= 0) { return false; } long num = (long)((handInc >= 0) ? handInc : 0) * (long)bestCount; return (long)((bestInc >= 0) ? bestInc : 0) * (long)handCount > num; } public static void AddPool(int poolInc, int poolCount, int incPoints, int productCount, out int newPoolInc, out int newPoolCount) { if (productCount <= 0) { newPoolInc = poolInc; newPoolCount = poolCount; return; } if (incPoints < 0) { incPoints = 0; } newPoolInc = poolInc + incPoints; newPoolCount = poolCount + productCount; } public static int TakeOne(int poolInc, int poolCount, int maxLevel, out int newPoolInc, out int newPoolCount) { if (poolCount <= 0) { newPoolInc = poolInc; newPoolCount = poolCount; return 0; } int num = poolInc / poolCount; if (num < 0) { num = 0; } else if (num > maxLevel) { num = maxLevel; } newPoolInc = poolInc - num; newPoolCount = poolCount - 1; return num; } public static int ClampLevel(int level, int maxLevel) { if (maxLevel < 0) { maxLevel = 0; } if (level < 0) { return 0; } if (level <= maxLevel) { return level; } return maxLevel; } public static float ClampPercent(float percent) { if (percent < 0f) { return 0f; } if (!(percent > 100f)) { return percent; } return 100f; } public static float UpgradeChance(int buildingInc, float chancePerPoint, float maxChance) { if (buildingInc <= 0 || chancePerPoint <= 0f) { return 0f; } float num = ClampPercent(maxChance); float num2 = (float)buildingInc * chancePerPoint; if (!(num2 > num)) { return num2; } return num; } public static float MinerChance(int buildingInc, float chancePerPoint) { if (buildingInc <= 0 || chancePerPoint <= 0f) { return 0f; } return ClampPercent((float)buildingInc * chancePerPoint); } public static int MinerRollsOf(int buildingInc) { if (buildingInc <= 0) { return 0; } return buildingInc / 2; } public static int MinerMaxInc(int buildingInc) { return MinerRollsOf(buildingInc); } public static int StationIncAfterTransfer(int currentInc, int addCount, int perItem, int totalCount) { int num = ((currentInc > 0) ? currentInc : 0); if (addCount <= 0 || perItem <= 0 || totalCount <= 0) { return num; } if (perItem > 10) { perItem = 10; } long num2 = (long)totalCount * 10L; long num3 = num + (long)addCount * (long)perItem; if (num3 > num2) { num3 = num2; } if (num3 > int.MaxValue) { num3 = 2147483647L; } return (int)num3; } public static int ProducedDelta(int netProduced, int picks) { long num = netProduced; if (picks > 0) { num += picks; } if (num <= 0) { return 0; } if (num <= int.MaxValue) { return (int)num; } return int.MaxValue; } public static int InstantBuildPerItem(int totalInc, int count) { if (count <= 0 || totalInc <= 0) { return 0; } return ClampLevel(totalInc / count, 10); } public static int ReceiptPerItem(int perItemHundredths) { if (perItemHundredths <= 0) { return 0; } return ClampLevel(perItemHundredths / 100, 10); } public static bool ReceiptItemMatches(int receiptItemId, int expectedItemId) { if (receiptItemId <= 0 || expectedItemId <= 0) { return true; } return receiptItemId == expectedItemId; } public static bool ConsumeWindowActive(bool inPaste, bool inHandBuild, bool instantWindow) { return inPaste || inHandBuild || instantWindow; } public static int PreferBindingValue(int receiptPerItem, int recordPerItem) { if (receiptPerItem > 0) { return ClampLevel(receiptPerItem, 10); } if (recordPerItem > 0) { return ClampLevel(recordPerItem, 10); } return 0; } public static int ExtraUpgradeMode(int buildingInc, int doubleAt, int extraAbove) { if (buildingInc <= 0) { return 0; } if (doubleAt > 0 && buildingInc >= doubleAt) { return 2; } if (extraAbove > 0 && buildingInc > extraAbove) { return 1; } return 0; } public static int ApplyCycleUpgrade(int inputLevel, int mode, bool baseRollHit, bool extraRollHit, int maxLevel) { int num = ((inputLevel >= 0) ? inputLevel : 0); if (maxLevel < 0) { maxLevel = 0; } if (num > maxLevel) { num = maxLevel; } if (num >= maxLevel) { return num; } if (mode >= 2) { num += 2; } else { if (baseRollHit) { num++; } if (mode >= 1 && num < maxLevel && extraRollHit) { num++; } } if (num > maxLevel) { num = maxLevel; } if (num < 0) { num = 0; } return num; } public static int RollCycleLevel(int inputLevel, int buildingInc, bool allowExtraUpgrade, float chancePerPoint, float maxChance, int doubleUpgradeAt, int extraAbove, int maxLevel, Func<float, bool> rollPercent, out int mode, out bool baseRollHit, out bool extraRollHit) { int num = ((inputLevel >= 0) ? inputLevel : 0); if (maxLevel < 0) { maxLevel = 0; } if (num > maxLevel) { num = maxLevel; } mode = 0; baseRollHit = false; extraRollHit = false; if (num < maxLevel && buildingInc > 0) { mode = (allowExtraUpgrade ? ExtraUpgradeMode(buildingInc, doubleUpgradeAt, extraAbove) : 0); if (mode < 2) { float num2 = UpgradeChance(buildingInc, chancePerPoint, maxChance); baseRollHit = num2 > 0f && rollPercent != null && rollPercent(num2); if (mode >= 1) { extraRollHit = num2 > 0f && rollPercent != null && rollPercent(num2); } } num = ApplyCycleUpgrade(num, mode, baseRollHit, extraRollHit, maxLevel); } if (num > maxLevel) { num = maxLevel; } if (num < 0) { num = 0; } return num; } public static int FractionatorInputLevel(int fluidInputInc, int fluidInputCount, int maxLevel) { if (fluidInputInc <= 0 || fluidInputCount <= 0) { return 0; } return ClampLevel(fluidInputInc / fluidInputCount, maxLevel); } public static float SpeedFactor(int buildingInc, float percentPerPoint, float maxBonusPercent) { if (buildingInc <= 0 || percentPerPoint <= 0f || maxBonusPercent <= 0f) { return 1f; } float num = (float)buildingInc * percentPerPoint; if (num > maxBonusPercent) { num = maxBonusPercent; } return 1f + num / 100f; } public static long ChargeCapFrom(long energyPerTick, long energy, long energyMax, int buildingInc, float percentPerPoint, float maxBonusPercent) { return ChargeCapFrom(energyPerTick, energy, energyMax, buildingInc, curve: false, percentPerPoint, maxBonusPercent); } public static long ChargeCapFrom(long energyPerTick, long energy, long energyMax, int buildingInc, bool curve, float percentPerPoint, float maxBonusPercent) { if (energyPerTick <= 0 || energyMax <= 0 || energy >= energyMax - 1) { return 0L; } float num = SpeedFactorFor(buildingInc, curve, percentPerPoint, maxBonusPercent); if (num <= 1f || float.IsNaN(num) || float.IsInfinity(num)) { return 0L; } long num2 = (long)((double)energyPerTick * (double)num); if (num2 <= energyPerTick) { return 0L; } return num2; } public static int DismantleIncFrom(int buildingInc, int kIncLevelMax, bool giveDismantleInc) { if (!giveDismantleInc || buildingInc <= 0) { return 0; } return ClampLevel(buildingInc, kIncLevelMax); } public static bool SharesStack(int itemId, int gridItemId, int gridCount, int gridInc, int incomingCount, int incomingInc, bool isBuilding, bool isPlayerInventory) { if (gridItemId != itemId) { return false; } if (!isBuilding || !isPlayerInventory) { return true; } if (gridCount <= 0 || incomingCount <= 0) { return true; } return (long)gridInc * (long)incomingCount == (long)incomingInc * (long)gridCount; } public static bool ShouldIsolateStorage(bool isPlayerInventory, int storageType, int storageSize, bool containersEnabled) { if (storageSize <= 0) { return false; } if (isPlayerInventory) { return true; } if (!containersEnabled) { return false; } return storageType == 0; } public static int SplitInc(ref int n, ref int m, int p) { if (n <= 0 || p <= 0) { return 0; } int num = m / n; int num2 = m - num * n; n -= p; num2 -= n; num = ((num2 > 0) ? (num * p + num2) : (num * p)); m -= num; return num; } public static int PlanSortedLayout(int[] itemIds, int[] counts, int[] incs, int[] stackSizes, bool[] isBuilding, int[] protoIndexes, out int[] outIds, out int[] outCounts, out int[] outIncs) { int num = ((itemIds != null) ? itemIds.Length : 0); outIds = new int[num]; outCounts = new int[num]; outIncs = new int[num]; if (num == 0 || counts == null || incs == null || stackSizes == null || isBuilding == null) { return 0; } int[] array = new int[num]; int[] array2 = new int[num]; long[] array3 = new long[num]; int[] array4 = new int[num]; int[] array5 = new int[num]; int[] array6 = new int[num]; int num2 = 0; for (int i = 0; i < num; i++) { int num3 = itemIds[i]; int num4 = counts[i]; int num5 = incs[i]; if (num3 <= 0 || num4 <= 0) { continue; } bool flag = isBuilding[i]; int num6 = -1; for (int j = 0; j < num2; j++) { if (array[j] == num3) { if (!flag) { num6 = j; break; } if (SharesStack(num3, array[j], array2[j], (int)array3[j], num4, num5, isBuilding: true, isPlayerInventory: true)) { num6 = j; break; } } } if (num6 < 0) { num6 = num2; num2++; array[num6] = num3; array2[num6] = 0; array3[num6] = 0L; array4[num6] = ((stackSizes[i] > 0) ? stackSizes[i] : 1000); int num7 = ((protoIndexes != null && i < protoIndexes.Length) ? protoIndexes[i] : (-1)); array5[num6] = ((num7 >= 0) ? num7 : (10000 + num3)); array6[num6] = i; } array2[num6] += num4; array3[num6] += num5; } int[] array7 = new int[num2]; for (int k = 0; k < num2; k++) { array7[k] = k; } for (int l = 1; l < num2; l++) { int num8 = array7[l]; int num9 = l - 1; while (num9 >= 0 && CompareSortedGroups(num8, array7[num9], array2, array3, array5, array6) < 0) { array7[num9 + 1] = array7[num9]; num9--; } array7[num9 + 1] = num8; } int num10 = 0; for (int m = 0; m < num2; m++) { if (num10 >= num) { break; } int num11 = array7[m]; int num12 = array[num11]; int n = array2[num11]; long num13 = array3[num11]; int m2 = (int)((num13 > int.MaxValue) ? int.MaxValue : ((num13 >= 0) ? num13 : 0)); int num14 = ((array4[num11] <= 0) ? 1000 : array4[num11]); while (n > 0 && num10 < num) { outIds[num10] = num12; if (n <= num14) { outCounts[num10] = n; outIncs[num10] = m2; n = 0; m2 = 0; } else { outIncs[num10] = SplitInc(ref n, ref m2, num14); outCounts[num10] = num14; } num10++; } } return num10; } private static int CompareSortedGroups(int a, int b, int[] groupCounts, long[] groupIncs, int[] groupProto, int[] groupFirst) { if (groupProto[a] != groupProto[b]) { if (groupProto[a] >= groupProto[b]) { return 1; } return -1; } int num = groupCounts[a]; int num2 = groupCounts[b]; if (num > 0 && num2 > 0) { long num3 = groupIncs[a] * num2; long num4 = groupIncs[b] * num; if (num3 != num4) { if (num3 >= num4) { return 1; } return -1; } } if (groupFirst[a] != groupFirst[b]) { if (groupFirst[a] >= groupFirst[b]) { return 1; } return -1; } return 0; } public static bool RollPercent(float percent, Random rng) { if (percent <= 0f) { return false; } if (percent >= 100f) { return true; } if (rng == null) { return false; } int num = (int)(percent * 100f + 0.5f); if (num > 10000) { num = 10000; } return rng.Next(10000) < num; } public static double SpeedBonusFrom(int points) { if (points <= 0) { return 0.0; } if (points >= 10) { return CurveBonusPercent[10]; } return CurveBonusPercent[points]; } public static float SpeedFactorFor(int buildingInc, bool curve, float percentPerPoint, float maxBonusPercent) { if (curve) { double num = SpeedBonusFrom(buildingInc); if (!(num <= 0.0)) { return (float)(1.0 + num / 100.0); } return 1f; } return SpeedFactor(buildingInc, percentPerPoint, maxBonusPercent); } public static float FeatureFactorFrom(int points, bool enabled, bool curve, float percentPerPoint, float maxBonusPercent) { if (!enabled || points <= 0) { return 1f; } return SpeedFactorFor(points, curve, percentPerPoint, maxBonusPercent); } public static float SprayChancePercent(int points) { if (points <= 0) { return 0f; } double num = SpeedBonusFrom(points); if (num < 0.0) { return 0f; } if (num > 100.0) { return 100f; } return (float)num; } public static int SprayBonus(int sprayedInc, int points, Func<float, bool> rollPercent, int maxInc) { if (sprayedInc <= 0 || (maxInc > 0 && sprayedInc >= maxInc)) { return sprayedInc; } float num = SprayChancePercent(points); if (num <= 0f || rollPercent == null) { return sprayedInc; } if (!rollPercent(num)) { return sprayedInc; } return sprayedInc + 1; } public static float HaloAlphaAt(float q) { if (float.IsNaN(q) || q < 0f) { return 0f; } if (q < 0.55f) { return 0f; } if (q < 0.58f) { return (q - 0.55f) / 0.029999971f; } if (q <= 0.82f) { return 1f; } if (q >= 1f) { return 0f; } float num = 1f - (q - 0.82f) / 0.18f; if (num < 0f) { num = 0f; } return num * 0.45f; } public static float HaloRadius(float selectSizeX, float selectSizeZ, float sizeScale) { if (float.IsNaN(selectSizeX) || selectSizeX < 0f) { selectSizeX = 0f; } if (float.IsNaN(selectSizeZ) || selectSizeZ < 0f) { selectSizeZ = 0f; } float num = ((selectSizeX > selectSizeZ) ? selectSizeX : selectSizeZ) * 0.42f; if (num < 1f) { num = 1f; } else if (num > 3.2f) { num = 3.2f; } if (float.IsNaN(sizeScale) || sizeScale <= 0f) { return num; } return num * sizeScale; } public static float HaloPulseAlpha(float timeSeconds) { if (float.IsNaN(timeSeconds) || float.IsInfinity(timeSeconds) || timeSeconds < 0f) { timeSeconds = 0f; } double d = Math.PI * 2.0 * (double)(timeSeconds / 2f); float num = (float)(0.5 - 0.5 * Math.Cos(d)); if (num < 0f) { num = 0f; } else if (num > 1f) { num = 1f; } return 0.6f + 0.39999998f * num; } public static float HaloBobOffset(float timeSeconds) { if (float.IsNaN(timeSeconds) || float.IsInfinity(timeSeconds) || timeSeconds < 0f) { timeSeconds = 0f; } return (float)Math.Sin(Math.PI * 2.0 * (double)(timeSeconds / 2.5f)) * 0.12f; } public static float HaloSpinDegrees(float timeSeconds) { if (float.IsNaN(timeSeconds) || float.IsInfinity(timeSeconds) || timeSeconds < 0f) { timeSeconds = 0f; } return timeSeconds * 20f % 360f; } public static float HaloPixelRadius(float radius, float distance, float screenHeight, float fieldOfViewDegrees) { if (radius <= 0f || distance <= 0.01f || screenHeight <= 0f) { return 0f; } if (fieldOfViewDegrees < 1f || fieldOfViewDegrees > 179f) { fieldOfViewDegrees = 60f; } double num = Math.Tan((double)fieldOfViewDegrees * 0.5 * Math.PI / 180.0) * (double)distance; if (num <= 1E-06) { return 0f; } float num2 = (float)((double)radius / num) * (screenHeight * 0.5f); if (float.IsNaN(num2) || num2 < 0f) { return 0f; } return num2; } public static void HaloQuadLayout(out float[] vertices, out float[] uvs, out int[] triangles) { vertices = new float[12] { -0.5f, -0.5f, 0f, 0.5f, -0.5f, 0f, 0.5f, 0.5f, 0f, -0.5f, 0.5f, 0f }; uvs = new float[8] { 0f, 0f, 1f, 0f, 1f, 1f, 0f, 1f }; triangles = new int[6] { 0, 1, 2, 2, 3, 0 }; } public static bool TryParseHexColor(string text, out float r, out float g, out float b, out float a) { r = 1f; g = 0.8235294f; b = 0.2901961f; a = 1f; if (string.IsNullOrEmpty(text)) { return false; } string text2 = text.Trim(); if (text2.Length > 0 && text2[0] == '#') { text2 = text2.Substring(1); } if (text2.Length != 6 && text2.Length != 8) { return false; } int num = 0; for (int i = 0; i < text2.Length; i++) { int num2 = HexDigit(text2[i]); if (num2 < 0) { return false; } num = (num << 4) | num2; } if (text2.Length == 6) { r = (float)((num >> 16) & 0xFF) / 255f; g = (float)((num >> 8) & 0xFF) / 255f; b = (float)(num & 0xFF) / 255f; a = 1f; } else { r = (float)((num >> 24) & 0xFF) / 255f; g = (float)((num >> 16) & 0xFF) / 255f; b = (float)((num >> 8) & 0xFF) / 255f; a = (float)(num & 0xFF) / 255f; } return true; } private static int HexDigit(char c) { if (c >= '0' && c <= '9') { return c - 48; } if (c >= 'a' && c <= 'f') { return c - 97 + 10; } if (c >= 'A' && c <= 'F') { return c - 65 + 10; } return -1; } public static void ResolveMarkerStyle(string style, out bool haloOn) { haloOn = true; if (string.IsNullOrEmpty(style)) { return; } string text = style.Trim().ToLowerInvariant(); if (text.Length != 0) { switch (text) { case "none": case "off": case "hidden": haloOn = false; break; } } } public static int SelectHaloTargets(float[] distances, int count, int maxCount, int[] outIndices) { if (outIndices == null || outIndices.Length == 0 || maxCount <= 0) { return 0; } int num = maxCount; if (num > outIndices.Length) { num = outIndices.Length; } int num2 = count; if (distances == null) { num2 = 0; } if (num2 > distances.Length) { num2 = distances.Length; } if (num2 <= 0) { return 0; } int num3 = 0; for (int i = 0; i < num; i++) { int num4 = -1; float num5 = float.MaxValue; for (int j = 0; j < num2; j++) { float num6 = distances[j]; if (float.IsNaN(num6) || num6 < 0f || num6 > 120f) { continue; } bool flag = false; for (int k = 0; k < num3; k++) { if (outIndices[k] == j) { flag = true; break; } } if (!flag && num6 < num5) { num5 = num6; num4 = j; } } if (num4 < 0) { break; } outIndices[num3] = num4; num3++; } return num3; } public static float HaloSpinDegrees(float timeSeconds, float spinScale) { if (float.IsNaN(spinScale) || spinScale <= 0f) { return 0f; } return HaloSpinDegrees(timeSeconds) * spinScale; } public static float HaloPulseAlpha(float timeSeconds, bool pulseEnabled) { if (!pulseEnabled) { return 0.85f; } return HaloPulseAlpha(timeSeconds); } public static void CameraPixelsToScreen(float pixelsX, float pixelsY, int pixelWidth, int pixelHeight, int screenWidth, int screenHeight, out float screenX, out float screenY) { screenX = pixelsX; screenY = pixelsY; if (pixelWidth > 0 && screenWidth > 0 && pixelWidth != screenWidth) { screenX = pixelsX * (float)screenWidth / (float)pixelWidth; } if (pixelHeight > 0 && screenHeight > 0 && pixelHeight != screenHeight) { screenY = pixelsY * (float)screenHeight / (float)pixelHeight; } } public static int ClampQualityPoints(int points) { if (points <= 0) { return 0; } if (points < 10) { return points; } return 10; } public static int PackSplitterRule(int itemId, int minPoints) { if (itemId < 0 || itemId > 65535) { return 0; } int num = ClampQualityPoints(minPoints); int num2 = itemId & 0xFFFF; if (num2 == 0 && num == 0) { return 0; } return num2 | (num << 16); } public static int SplitterRuleItem(int packed) { return packed & 0xFFFF; } public static int SplitterRuleMin(int packed) { return (packed >> 16) & 0xFF; } public static bool S