diff --git a/.gitignore b/.gitignore index d92e57b..7d8e1a0 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,12 @@ !.editorconfig !.gitignore !*.md +!*.ps1 +!*.psm1 +!NuGet.Config bin/ obj/ +tmp_release +.idea +.vs diff --git a/BetterStats/BetterStats.cs b/BetterStats/BetterStats.cs index 9f75253..4f0007c 100644 --- a/BetterStats/BetterStats.cs +++ b/BetterStats/BetterStats.cs @@ -6,11 +6,12 @@ using UnityEngine; using UnityEngine.UI; using System.Globalization; +using BepInEx.Logging; +using DefaultNamespace; namespace BetterStats { - // TODO: button to next producer/consumer - [BepInPlugin("com.brokenmass.plugin.DSP.BetterStats", "BetterStats", "1.3.2")] + [BepInPlugin("com.brokenmass.plugin.DSP.BetterStats", PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)] public class BetterStats : BaseUnityPlugin { public class EnhancedUIProductEntryElements @@ -28,12 +29,16 @@ public class EnhancedUIProductEntryElements public Text counterConsumptionLabel; public Text counterConsumptionValue; + public ProliferatorOperationSetting proliferatorOperationSetting; } + Harmony harmony; private static ConfigEntry lackOfProductionRatioTrigger; private static ConfigEntry consumptionToProductionRatioTrigger; private static ConfigEntry displayPerSecond; - private static Dictionary counter = new Dictionary(); + public static ConfigEntry disableProliferatorCalc; + public static ConfigEntry disableStackingCalc; + private static Dictionary counter = new(); private static GameObject txtGO, chxGO, filterGO; private static Texture2D texOff = Resources.Load("ui/textures/sprites/icons/checkbox-off"); private static Texture2D texOn = Resources.Load("ui/textures/sprites/icons/checkbox-on"); @@ -54,19 +59,23 @@ public class EnhancedUIProductEntryElements private static Dictionary enhancements = new Dictionary(); private static UIStatisticsWindow statWindow; + public static ManualLogSource Log; internal void Awake() { + Log = Logger; InitConfig(); harmony = new Harmony("com.brokenmass.plugin.DSP.BetterStats"); try { harmony.PatchAll(typeof(BetterStats)); + ProliferatorOperationSetting.Init(); } catch (Exception e) { - Console.WriteLine(e.ToString()); + Log.LogWarning(e.ToString()); } + Log.LogInfo($"{PluginInfo.PLUGIN_NAME} {PluginInfo.PLUGIN_VERSION} Loaded"); } internal void InitConfig() @@ -79,6 +88,10 @@ internal void InitConfig() " (e.g. if set to '1.5' then you will be warned if your max consumption is more than 150% of your max production)"); displayPerSecond = Config.Bind("General", "displayPerSecond", false, "Used by UI to persist the last selected value for checkbox"); + disableProliferatorCalc = Config.Bind("General", "Disable Proliferator Calculation", false, + "Tells mod to ignore proliferator points completely. Can cause production rates to exceed theoretical max values"); + disableStackingCalc = Config.Bind("General", "Disable Stacking Calculation", false, + "Tells mod to ignore unlocked tech for stacking items on belts. By default uses same 'Tech Limit' value as stations use"); } internal void OnDestroy() @@ -99,6 +112,7 @@ internal void OnDestroy() } ClearEnhancedUIProductEntries(); + ProliferatorOperationSetting.Unload(); harmony.UnpatchSelf(); } @@ -155,7 +169,6 @@ private static void EnsureId(ref Dictionary dict, int id) if (!dict.ContainsKey(id)) { ItemProto itemProto = LDB.items.Select(id); - dict.Add(id, new ProductMetrics() { itemProto = itemProto @@ -301,7 +314,7 @@ private static EnhancedUIProductEntryElements EnhanceUIProductEntry(UIProductEnt var counterConsumptionValue = CopyText(__instance.consumeText, new Vector2(-initialXOffset, 0)); counterConsumptionValue.GetComponent().sizeDelta = new Vector2(60, 40); counterConsumptionValue.text = "0"; - + var proliferatorOpSetting = ProliferatorOperationSetting.ForProductEntry(__instance); var enhancement = new EnhancedUIProductEntryElements { maxProductionLabel = maxProductionLabel, @@ -317,6 +330,7 @@ private static EnhancedUIProductEntryElements EnhanceUIProductEntry(UIProductEnt counterConsumptionLabel = counterConsumptionLabel, counterConsumptionValue = counterConsumptionValue, + proliferatorOperationSetting = proliferatorOpSetting }; enhancements.Add(__instance, enhancement); @@ -514,8 +528,8 @@ public static void UIProductEntry__OnUpdate_Postfix(UIProductEntry __instance) if (!isTotalTimeWindow) { - originalProductValue = originalProductValue / divider; - originalConsumeValue = originalConsumeValue / divider; + originalProductValue /= divider; + originalConsumeValue /= divider; originalProductText = $"{FormatMetric(originalProductValue)}"; @@ -563,12 +577,13 @@ public static void UIProductEntry__OnUpdate_Postfix(UIProductEntry __instance) if (warnOnHighMaxConsumption && !isTotalTimeWindow) enhancement.maxConsumptionValue.color = new Color(1f, 1f, .25f, .5f); + enhancement.proliferatorOperationSetting?.UpdateItemId(__instance.entryData.itemId); } [HarmonyPrefix, HarmonyPatch(typeof(UIStatisticsWindow), "ComputeDisplayEntries")] public static void UIProductionStatWindow_ComputeDisplayEntries_Prefix(UIStatisticsWindow __instance) { - if (Time.frameCount % 10 != 0) + if (Time.frameCount % 10 != 0 && lastStatTimer == __instance.timeLevel) { return; } @@ -615,6 +630,8 @@ public static void AddPlanetFactoryData(PlanetFactory planetFactory) var transport = planetFactory.transport; var veinPool = planetFactory.planet.factory.veinPool; var miningSpeedScale = (double)GameMain.history.miningSpeedScale; + var maxProductivityIncrease = ResearchTechHelper.GetMaxProductivityIncrease(); + var maxSpeedIncrease = ResearchTechHelper.GetMaxSpeedIncrease(); for (int i = 1; i < factorySystem.minerCursor; i++) { @@ -650,11 +667,21 @@ public static void AddPlanetFactoryData(PlanetFactory planetFactory) { production = frequency * speed * (float)((double)veinPool[veinId].amount * (double)VeinData.oilSpeedMultiplier); } + + // flag to tell us if it's one of the advanced miners they added in the 20-Jan-2022 release + var isAdvancedMiner = false; if (factorySystem.minerPool[i].type == EMinerType.Vein) { production = frequency * speed * miner.veinCount; + var minerEntity = factorySystem.factory.entityPool[miner.entityId]; + isAdvancedMiner = minerEntity.stationId > 0 && minerEntity.minerId > 0; + } + + // advanced miners aren't limited by belts + if (!isAdvancedMiner) + { + production = Math.Min(BELT_MAX_ITEMS_PER_MINUTE, production); } - production = Math.Min(BELT_MAX_ITEMS_PER_MINUTE, production); counter[productId].production += production; counter[productId].producers++; @@ -664,15 +691,49 @@ public static void AddPlanetFactoryData(PlanetFactory planetFactory) var assembler = factorySystem.assemblerPool[i]; if (assembler.id != i || assembler.recipeId == 0) continue; - var frequency = 60f / (float)((double)assembler.timeSpend / 600000.0); + var isNonProductiveRecipe = LDB.recipes.Select(assembler.recipeId).NonProductive; + var baseFrequency = 60f / (float)(assembler.timeSpend / 600000.0); + var productionFrequency = baseFrequency; var speed = (float)(0.0001 * (double)assembler.speed); + var runtimeSetting = + disableProliferatorCalc.Value ? ItemCalculationRuntimeSetting.None : ProliferatorOperationSetting.ForRecipe(assembler.recipeId); + + // forceAccMode is 'Production Speedup' mode. It just adds a straight increase to both production and consumption rate + if (runtimeSetting.Enabled) + { + if (runtimeSetting.Mode == ItemCalculationMode.Normal) + { + // let assembler decide + if (assembler.forceAccMode || isNonProductiveRecipe) + { + speed += speed * maxSpeedIncrease; + } + else + { + productionFrequency += productionFrequency * maxProductivityIncrease; + } + } + else if (runtimeSetting.Mode == ItemCalculationMode.ForceSpeed) + { + speed += speed * maxSpeedIncrease; + } + else if (runtimeSetting.Mode == ItemCalculationMode.ForceProductivity) + { + productionFrequency += productionFrequency * maxProductivityIncrease; + } + else + { + Log.LogWarning($"unexpected runtime setting ${JsonUtility.ToJson(runtimeSetting)}"); + } + } + for (int j = 0; j < assembler.requires.Length; j++) { var productId = assembler.requires[j]; EnsureId(ref counter, productId); - counter[productId].consumption += frequency * speed * assembler.requireCounts[j]; + counter[productId].consumption += baseFrequency * speed * assembler.requireCounts[j]; counter[productId].consumers++; } @@ -681,7 +742,7 @@ public static void AddPlanetFactoryData(PlanetFactory planetFactory) var productId = assembler.products[j]; EnsureId(ref counter, productId); - counter[productId].production += frequency * speed * assembler.productCounts[j]; + counter[productId].production += productionFrequency * speed * assembler.productCounts[j]; counter[productId].producers++; } } @@ -733,7 +794,7 @@ public static void AddPlanetFactoryData(PlanetFactory planetFactory) { var lab = factorySystem.labPool[i]; if (lab.id != i) continue; - float frequency = 60f / (float)((double)lab.timeSpend / 600000.0); + (float baseFrequency, float productionFrequency) = DetermineLabFrequencies(ref lab, maxProductivityIncrease, maxSpeedIncrease); if (lab.matrixMode) { @@ -742,7 +803,7 @@ public static void AddPlanetFactoryData(PlanetFactory planetFactory) var productId = lab.requires[j]; EnsureId(ref counter, productId); - counter[productId].consumption += frequency * lab.requireCounts[j]; + counter[productId].consumption += baseFrequency * lab.requireCounts[j]; counter[productId].consumers++; } @@ -751,7 +812,7 @@ public static void AddPlanetFactoryData(PlanetFactory planetFactory) var productId = lab.products[j]; EnsureId(ref counter, productId); - counter[productId].production += frequency * lab.productCounts[j]; + counter[productId].production += productionFrequency * lab.productCounts[j]; counter[productId].producers++; } } @@ -763,13 +824,13 @@ public static void AddPlanetFactoryData(PlanetFactory planetFactory) if (techProto == null) continue; TechState techState = GameMain.history.TechState(techProto.ID); + float hashPerMinute = (float)(60.0f * (GameMain.data.history.techSpeed * (1.0 + (double) maxProductivityIncrease / 6.0f))); + for (int index = 0; index < techProto.itemArray.Length; ++index) { var item = techProto.Items[index]; - var cubesNeeded = techProto.GetHashNeeded(techState.curLevel) * techProto.ItemPoints[index] / 3600L; - var researchRate = GameMain.history.techSpeed * 60.0f; - var hashesPerCube = (float) techState.hashNeeded / cubesNeeded; - var researchFreq = hashesPerCube / researchRate; + var researchRateSec = (float) GameMain.history.techSpeed * GameMain.tickPerSec; + var researchFreq = (float) (techState.uPointPerHash * hashPerMinute / researchRateSec); EnsureId(ref counter, item); counter[item].consumers++; counter[item].consumption += researchFreq * GameMain.history.techSpeed; @@ -832,6 +893,81 @@ public static void AddPlanetFactoryData(PlanetFactory planetFactory) } } } + + var cargoTraffic = planetFactory.cargoTraffic; + for (int i = 0; i < planetFactory.cargoTraffic.spraycoaterCursor; i++) + { + var sprayCoater = cargoTraffic.spraycoaterPool[i]; + if (sprayCoater.id != i || sprayCoater.incItemId < 1) + continue; + ItemProto itemProto = LDB.items.Select(sprayCoater.incItemId); + var beltComponent = cargoTraffic.beltPool[sprayCoater.cargoBeltId]; + // Belt running at 6 / s transports 360 cargos in 1 minute + // Tooltip for spray lvl 1 shows: "Numbers of sprays = 12", which means that + // each spray covers 12 cargos so 360 / 12 = 30 items are covered per minute + // (HpMax from proto == Numbers of Sprays) + // For now, since we're computing max consumption of the sprays, don't worry about sprays + // that are themselves sprayed since that would lead to lower consumption + var numbersOfSprays = itemProto.HpMax; + + // beltspeed is 1,2,5 so must be multiplied by 6 to get 6,12,30 + var beltRatePerMin = 6 * beltComponent.speed * 60; + int beltMaxStack = ResearchTechHelper.GetMaxPilerStackingUnlocked(); + var frequency = beltMaxStack * beltRatePerMin / (float) numbersOfSprays; + var productId = sprayCoater.incItemId; + EnsureId(ref counter, productId); + + counter[productId].consumption += frequency; + counter[productId].consumers++; + } + } + + private static (float, float) DetermineLabFrequencies(ref LabComponent lab, float maxProductivityIncrease, float maxSpeedIncrease) + { + // lab timeSpend is in game ticks, here we are figuring out the same number shown in lab window, example: 2.5 / m + // when we are in Production Speedup mode `speedOverride` is increased. + float baseFrequency = 0f, productionFrequency = 0; + + var runtimeSetting = disableProliferatorCalc.Value ? ItemCalculationRuntimeSetting.None : ProliferatorOperationSetting.ForRecipe(lab.recipeId); + + if (runtimeSetting != null && runtimeSetting.Enabled) + { + if (runtimeSetting.Mode == ItemCalculationMode.Normal) + { + // use whatever setting the lab has decide + if (!lab.forceAccMode) + { + // productivity bonuses are in Cargo table in the incTableMilli array + baseFrequency = (float)(1f / (lab.timeSpend / GameMain.tickPerSec / (60f * lab.speed))); + productionFrequency = baseFrequency + baseFrequency * maxProductivityIncrease; + } + else + { + var labSpeed = lab.speed * (1.0 + maxSpeedIncrease) + 0.1; + baseFrequency = (float)(1f / (lab.timeSpend / GameMain.tickPerSec / (60f * labSpeed))); + productionFrequency = baseFrequency; + } + } + else if (runtimeSetting.Mode == ItemCalculationMode.ForceSpeed) + { + var labSpeed = lab.speed * (1.0 + maxSpeedIncrease) + 0.1; + baseFrequency = (float)(1f / (lab.timeSpend / GameMain.tickPerSec / (60f * labSpeed))); + productionFrequency = baseFrequency; + } + else if (runtimeSetting.Mode == ItemCalculationMode.ForceProductivity) + { + baseFrequency = (float)(1f / (lab.timeSpend / GameMain.tickPerSec / (60f * lab.speed))); + productionFrequency = baseFrequency + baseFrequency * maxProductivityIncrease; + } + } + else + { + // regular calculation + baseFrequency = (float)(1f / (lab.timeSpend / GameMain.tickPerSec / (60f * lab.speed))); + productionFrequency = baseFrequency; + } + + return (baseFrequency, productionFrequency); } } } diff --git a/BetterStats/BetterStats.csproj b/BetterStats/BetterStats.csproj index 23399cf..ee897b6 100644 --- a/BetterStats/BetterStats.csproj +++ b/BetterStats/BetterStats.csproj @@ -1,114 +1,26 @@ - - - - - Debug - AnyCPU - {F415C8D1-768D-407C-86AE-4D2EEAC179F1} - Library - DSM.BetterStats - BetterStats - v4.0 - 512 - true - true - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.2.0 - false - false - true - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - False - Microsoft .NET Framework 4.8 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - - - - - C:\Program Files (x86)\Steam\steamapps\common\Dyson Sphere Program\BepInEx\core\0Harmony.dll - - - False - C:\Program Files (x86)\Steam\steamapps\common\Dyson Sphere Program\DSPGAME_Data\Managed\publicized_assemblies\Assembly-CSharp_publicized.dll - - - C:\Program Files (x86)\Steam\steamapps\common\Dyson Sphere Program\BepInEx\core\BepInEx.dll - - - C:\Program Files (x86)\Steam\steamapps\common\Dyson Sphere Program\BepInEx\core\BepInEx.Harmony.dll - - - C:\Program Files (x86)\Steam\steamapps\common\Dyson Sphere Program\BepInEx\core\Mono.Cecil.dll - - - C:\Program Files (x86)\Steam\steamapps\common\Dyson Sphere Program\DSPGAME_Data\Managed\netstandard.dll - - - C:\Program Files (x86)\Steam\steamapps\common\Dyson Sphere Program\BepInEx\DowngradedAssemblies\UnityEngine.dll - - - C:\Program Files (x86)\Steam\steamapps\common\Dyson Sphere Program\DSPGAME_Data\Managed\UnityEngine.CoreModule.dll - - - C:\Program Files (x86)\Steam\steamapps\common\Dyson Sphere Program\DSPGAME_Data\Managed\UnityEngine.JSONSerializeModule.dll - - - C:\Program Files (x86)\Steam\steamapps\common\Dyson Sphere Program\DSPGAME_Data\Managed\UnityEngine.TextRenderingModule.dll - - - C:\Program Files (x86)\Steam\steamapps\common\Dyson Sphere Program\DSPGAME_Data\Managed\UnityEngine.UI.dll - - - C:\Program Files (x86)\Steam\steamapps\common\Dyson Sphere Program\DSPGAME_Data\Managed\UnityEngine.UIModule.dll - - - - - copy $(TargetPath) "C:\Program Files (x86)\Steam\steamapps\common\Dyson Sphere Program\BepInEx\scripts\$(TargetFileName)" - - + + + netstandard2.0 + Add more information to the production stats panel + BetterStats + true + 1.3.3 + 9 + + + + + + + + + + + + + + + + + + diff --git a/BetterStats/ItemCalculationMode.cs b/BetterStats/ItemCalculationMode.cs new file mode 100644 index 0000000..464e2bb --- /dev/null +++ b/BetterStats/ItemCalculationMode.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BepInEx; +using BepInEx.Configuration; +using UnityEngine; +using static BetterStats.BetterStats; + +namespace BetterStats +{ + public enum ItemCalculationMode + { + None, + Normal, + ForceSpeed, + ForceProductivity + } + + /// + /// Manages currently selected proliferator calculation options for each item + /// + public class ItemCalculationRuntimeSetting + { + public static readonly ItemCalculationRuntimeSetting None = new(0) + { + _enabled = false, + _mode = ItemCalculationMode.None, + }; + private ItemCalculationMode _mode = ItemCalculationMode.Normal; + private bool _enabled; + + public readonly int productId; + + private ConfigEntry _configEntry; + private static readonly Dictionary> ConfigEntries = new(); + private static readonly Dictionary Pool = new(); + private static ConfigFile configFile; + private readonly ItemProto _itemProto; + private ItemCalculationRuntimeSetting(int productId) + { + this.productId = productId; + var proto = LDB.items.Select(productId); + if (proto != null) + { + _itemProto = proto; + } + } + + public ItemCalculationMode Mode + { + get => _mode; + set + { + _mode = value; + Pool[productId]._mode = value; + Save(); + } + } + + public bool Enabled + { + get => _enabled; + set + { + _enabled = value; + Pool[productId]._enabled = value; + Save(); + } + } + + public bool SpeedSupported => _itemProto is { recipes: { Count: > 0 } }; + + public bool ProductivitySupported + { + get { return SpeedSupported && _itemProto.recipes.Any(r => r.productive); } + } + + private void Save() + { + _configEntry.Value = Serialize(); + Log.LogDebug($"saved {productId} entry {_configEntry.Value}"); + } + + private static ItemCalculationRuntimeSetting Deserialize(string strVal) + { + var serializableRuntimeState = JsonUtility.FromJson(strVal); + + return new ItemCalculationRuntimeSetting(serializableRuntimeState.productId) + { + _enabled = serializableRuntimeState.enabled, + _mode = (ItemCalculationMode)serializableRuntimeState.mode, + }; + } + + private string Serialize() + { + return JsonUtility.ToJson(SerializableRuntimeState.From(this)); + } + + public static void InitConfig() + { + configFile = new ConfigFile($"{Paths.ConfigPath}/{PluginInfo.PLUGIN_NAME}/CustomProductSettings.cfg", true); + + foreach (var itemProto in LDB.items.dataArray) + { + var defaultValue = new ItemCalculationRuntimeSetting(itemProto.ID) + { + _enabled = true, + _mode = ItemCalculationMode.Normal + }; + + var configEntry = configFile.Bind("Internal", $"ProliferatorStatsSetting_{itemProto.ID}", + defaultValue.Serialize(), + "For internal use only"); + ConfigEntries[itemProto.ID] = configEntry; + + Pool[itemProto.ID] = Deserialize(ConfigEntries[itemProto.ID].Value); + Pool[itemProto.ID]._configEntry = configEntry; + } + } + + public static ItemCalculationRuntimeSetting ForItemId(int itemId) + { + if (disableProliferatorCalc.Value) + return None; + if (Pool.ContainsKey(itemId)) + return Pool[itemId]; + + var defaultValue = new ItemCalculationRuntimeSetting(itemId) + { + _enabled = true, + _mode = ItemCalculationMode.Normal + }; + + var configEntry = configFile.Bind("Internal", $"ProliferatorStatsSetting_{itemId}", + defaultValue.Serialize(), + "For internal use only"); + ConfigEntries[itemId] = configEntry; + + Pool[itemId] = Deserialize(ConfigEntries[itemId].Value); + Pool[itemId]._configEntry = configEntry; + return Pool[itemId]; + } + } + + [Serializable] + public class SerializableRuntimeState + { + [SerializeField] public int mode; + [SerializeField] public bool enabled; + [SerializeField] public int productId; + + public SerializableRuntimeState(int productId, bool enabled, ItemCalculationMode mode) + { + this.productId = productId; + this.enabled = enabled; + this.mode = (int)mode; + } + + public static SerializableRuntimeState From(ItemCalculationRuntimeSetting setting) + { + return new SerializableRuntimeState(setting.productId, setting.Enabled, setting.Mode); + } + } +} diff --git a/BetterStats/NuGet.Config b/BetterStats/NuGet.Config new file mode 100644 index 0000000..1864ded --- /dev/null +++ b/BetterStats/NuGet.Config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/BetterStats/ProliferatorOperationSetting.cs b/BetterStats/ProliferatorOperationSetting.cs new file mode 100644 index 0000000..1a9a147 --- /dev/null +++ b/BetterStats/ProliferatorOperationSetting.cs @@ -0,0 +1,373 @@ +using System; +using System.Collections.Generic; +using DefaultNamespace; +using UnityEngine; +using UnityEngine.UI; +using static BetterStats.BetterStats; +using static UnityEngine.Object; + +namespace BetterStats +{ + /// + /// Manages operation of proliferator modes + /// + public class ProliferatorOperationSetting + { + private const string PROLIFERATOR_POINT_CALCULATION_DISABLED = "Proliferator Calculation Disabled"; + private const string PROLIFERATOR_POINT_CALCULATION_ENABLE = "Proliferator Calculation Enabled"; + private const string ASSEMBLER_SELECTION_MODE = "Assembler Selection Mode"; + private const string FORCE_PRODUCTIVITY_MODE = "Force Productivity Mode"; + private const string FORCE_SPEED_MODE = "Force Speed Mode"; + + private UIButton _normalOperationButton; + private UIButton _disableButton; + private UIButton _forceSpeedButton; + private UIButton _forceProductivityButton; + private readonly List _availableButtons = new(); + private int _productId; + + private static readonly Dictionary OperationModeSprites = new(); + private static Sprite checkboxOffSprite; + private static Sprite checkboxOnSprite; + private static readonly Dictionary ByProductEntry = new(); + + private ProliferatorOperationSetting() + { + } + + public static void Init() + { + InitSprites(); + ItemCalculationRuntimeSetting.InitConfig(); + } + + + private static void InitSprites() + { + var productivityTexture = Resources.Load("ui/textures/sprites/icons/plus"); + var normalProlifTexture = Resources.Load("ui/textures/sprites/icons/voxel-icon"); + var speedTexture = Resources.Load("ui/textures/sprites/sci-fi/arrow-mark-60px"); + var checkBoxOff = Resources.Load("ui/textures/sprites/icons/checkbox-off"); + var checkBoxOn = Resources.Load("ui/textures/sprites/icons/checkbox-on"); + OperationModeSprites[ItemCalculationMode.ForceSpeed] = Sprite.Create(speedTexture, + new Rect(0, 0, speedTexture.width, speedTexture.height), + new Vector2(0.5f, 0.5f)); + + OperationModeSprites[ItemCalculationMode.ForceProductivity] = Sprite.Create(productivityTexture, + new Rect(0, 0, productivityTexture.width, productivityTexture.height), + new Vector2(0.5f, 0.5f)); + + checkboxOnSprite = Sprite.Create(checkBoxOn, + new Rect(0, 0, checkBoxOn.width, checkBoxOn.height), + new Vector2(0.5f, 0.5f)); + checkboxOffSprite = Sprite.Create(checkBoxOff, + new Rect(0, 0, checkBoxOff.width, checkBoxOff.height), + new Vector2(0.5f, 0.5f)); + OperationModeSprites[ItemCalculationMode.Normal] = Sprite.Create(normalProlifTexture, + new Rect(0, 0, normalProlifTexture.width, normalProlifTexture.height), + new Vector2(0.5f, 0.5f)); + } + + public static ProliferatorOperationSetting ForProductEntry(UIProductEntry uiProductEntry) + { + if (!ResearchTechHelper.IsProliferatorUnlocked()) + return null; + var itemId = uiProductEntry.entryData.itemId; + ProliferatorOperationSetting result; + + if (ByProductEntry.ContainsKey(uiProductEntry)) + { + result = ByProductEntry[uiProductEntry]; + result._productId = uiProductEntry.entryData.itemId; + result.SyncButtons(); + return result; + } + + result = new ProliferatorOperationSetting(); + ByProductEntry[uiProductEntry] = result; + + result._productId = uiProductEntry.entryData.itemId; + + var sourceButton = uiProductEntry.favoriteBtn1; + var xOffset = 80; + + result._disableButton = CopyButton(uiProductEntry, sourceButton, new Vector2(xOffset, 85), + result.OnModeDisable, itemId, checkboxOnSprite, + PROLIFERATOR_POINT_CALCULATION_DISABLED, + "Don't use Proliferator Points for calculation of Theoretical max values"); + result._availableButtons.Add(result._disableButton); + + result._normalOperationButton = CopyButton(uiProductEntry, sourceButton, new Vector2(xOffset, 60), + result.OnNormalClicked, itemId, OperationModeSprites[ItemCalculationMode.Normal], + ASSEMBLER_SELECTION_MODE, + "Max values calculated using currently selected mode for each assembler."); + result._availableButtons.Add(result._normalOperationButton); + + result._forceSpeedButton = CopyButton(uiProductEntry, sourceButton, new Vector2(xOffset, 35), + result.OnForceSpeedClicked, itemId, OperationModeSprites[ItemCalculationMode.ForceSpeed], + FORCE_SPEED_MODE, + "Max values calculated as if all all assemblers were set to 'Production Speedup'."); + result._availableButtons.Add(result._forceSpeedButton); + + result._forceProductivityButton = CopyButton(uiProductEntry, sourceButton, new Vector2(xOffset, 10), + result.OnForceProductivity, itemId, OperationModeSprites[ItemCalculationMode.ForceProductivity], + FORCE_PRODUCTIVITY_MODE, + "Max values calculated as if all all assemblers were set to 'Extra Products'."); + result._availableButtons.Add(result._forceProductivityButton); + result.SyncButtons(); + return result; + } + + private void OnForceProductivity(int itemId) + { + var setting = ItemCalculationRuntimeSetting.ForItemId(_productId); + setting.Enabled = true; + setting.Mode = ItemCalculationMode.ForceProductivity; + SyncButtons(); + } + + private void OnForceSpeedClicked(int itemId) + { + // _productId = itemId; + var setting = ItemCalculationRuntimeSetting.ForItemId(_productId); + setting.Enabled = true; + setting.Mode = ItemCalculationMode.ForceSpeed; + + SyncButtons(); + } + + private void OnNormalClicked(int itemId) + { + var setting = ItemCalculationRuntimeSetting.ForItemId(_productId); + setting.Mode = ItemCalculationMode.Normal; + SyncButtons(); + } + + private void OnModeDisable(int itemId) + { + var setting = ItemCalculationRuntimeSetting.ForItemId(_productId); + Log.LogDebug($"switching item {itemId} to {!setting.Enabled} {setting.productId}"); + setting.Enabled = !setting.Enabled; + if (setting.Enabled && setting.Mode == ItemCalculationMode.None) + { + setting.Mode = ItemCalculationMode.Normal; + } // otherwise we'll just use whatever they had before disabling + + SyncButtons(); + } + + private void SyncButtons() + { + ReInitButtonStates(); + + var runtimeSetting = ItemCalculationRuntimeSetting.ForItemId(_productId); + + if (!runtimeSetting.Enabled) + { + _disableButton.tips.tipTitle = $"{PROLIFERATOR_POINT_CALCULATION_DISABLED}"; + _disableButton.button.image.sprite = checkboxOffSprite; + } + else + { + _disableButton.tips.tipTitle = $"{PROLIFERATOR_POINT_CALCULATION_ENABLE}"; + _disableButton.button.image.sprite = checkboxOnSprite; + } + + if (!runtimeSetting.Enabled) + { + foreach (var availableButton in _availableButtons) + { + if (availableButton == _disableButton) + continue; + availableButton.gameObject + .SetActive(false); + } + return; + } + + switch (runtimeSetting.Mode) + { + case ItemCalculationMode.Normal: + { + _normalOperationButton.tips.tipTitle = $"(current) {ASSEMBLER_SELECTION_MODE}"; + _normalOperationButton.highlighted = true; + break; + } + case ItemCalculationMode.ForceProductivity: + { + _forceProductivityButton.tips.tipTitle = $"(current) {FORCE_PRODUCTIVITY_MODE}"; + _forceProductivityButton.highlighted = true; + break; + } + case ItemCalculationMode.ForceSpeed: + { + _forceSpeedButton.tips.tipTitle = $"(current) {FORCE_SPEED_MODE}"; + _forceSpeedButton.highlighted = true; + break; + } + } + } + + private void ReInitButtonStates() + { + var runtimeSetting = ItemCalculationRuntimeSetting.ForItemId(_productId); + if (!runtimeSetting.SpeedSupported && !runtimeSetting.ProductivitySupported) + { + foreach (var button in _availableButtons) + { + button.gameObject.SetActive(false); + } + return; + } + + if (_forceProductivityButton != null) + { + _forceProductivityButton.tips.tipTitle = FORCE_PRODUCTIVITY_MODE; + _forceProductivityButton.highlighted = false; + _forceProductivityButton.button.interactable = true; + _forceProductivityButton.gameObject.SetActive(runtimeSetting.ProductivitySupported); + } + + if (_forceSpeedButton != null) + { + _forceSpeedButton.tips.tipTitle = FORCE_SPEED_MODE; + _forceSpeedButton.highlighted = false; + _forceSpeedButton.button.interactable = true; + _forceSpeedButton.gameObject.SetActive(runtimeSetting.SpeedSupported); + } + + if (_disableButton != null) + { + _disableButton.tips.tipTitle = PROLIFERATOR_POINT_CALCULATION_DISABLED; + _disableButton.highlighted = true; + _disableButton.button.interactable = true; + _disableButton.gameObject.SetActive(true); + } + + if (_normalOperationButton != null) + { + _normalOperationButton.tips.tipTitle = ASSEMBLER_SELECTION_MODE; + _normalOperationButton.highlighted = false; + _normalOperationButton.button.interactable = true; + _normalOperationButton.gameObject.SetActive(true); + } + } + + private static UIButton CopyButton(UIProductEntry uiProductEntry, + UIButton button, + Vector2 positionDelta, + Action action, + int productId, + Sprite btnSprite, + string buttonHoverTitle, + string buttonHoverText) + { + var rectTransform = button.GetComponent(); + var copied = Instantiate(rectTransform, uiProductEntry.transform, false); + var copiedImage = copied.transform.GetComponent(); + copiedImage.sprite = btnSprite; + copiedImage.fillAmount = 0; + + copied.anchorMin = rectTransform.anchorMin; + copied.anchorMax = rectTransform.anchorMax; + copied.sizeDelta = rectTransform.sizeDelta * 0.75f; + copied.anchoredPosition = rectTransform.anchoredPosition + positionDelta; + var newActionButton = copied.GetComponentInChildren(); + if (newActionButton != null) + { + newActionButton.tips.tipTitle = buttonHoverTitle; + newActionButton.tips.tipText = buttonHoverText; + newActionButton.button.onClick.RemoveAllListeners(); + newActionButton.button.onClick.AddListener(() => action.Invoke(productId)); + newActionButton.highlighted = false; + newActionButton.Init(); + } + + return newActionButton; + } + + public static void Unload() + { + foreach (var operationModeSprite in OperationModeSprites.Values) + { + Destroy(operationModeSprite); + } + foreach (var setting in ByProductEntry.Values) + { + DestroySetting(setting); + } + } + + private static void DestroySetting(ProliferatorOperationSetting setting) + { + try + { + if (setting._disableButton != null && setting._disableButton.gameObject != null) + { + Destroy(setting._disableButton.gameObject); + } + + if (setting._forceProductivityButton != null && setting._forceProductivityButton.gameObject != null) + { + Destroy(setting._forceProductivityButton.gameObject); + } + + if (setting._normalOperationButton != null && setting._normalOperationButton.gameObject != null) + { + Destroy(setting._normalOperationButton.gameObject); + } + + if (setting._forceSpeedButton != null && setting._forceSpeedButton.gameObject != null) + { + Destroy(setting._forceSpeedButton.gameObject); + } + } + catch (Exception e) + { + Log.LogWarning($"exception while disabling setting: {e.Message}"); + } + } + + public void UpdateItemId(int newItemId) + { + if (_productId == newItemId) + { + // nothing to do + return; + } + + _productId = newItemId; + SyncButtons(); + } + + private static readonly Dictionary _chosenItemIdForRecipeId = new(); + + public static ItemCalculationRuntimeSetting ForRecipe(int assemblerRecipeId) + { + if (_chosenItemIdForRecipeId.TryGetValue(assemblerRecipeId, out var chosenItemId)) + { + return ItemCalculationRuntimeSetting.ForItemId(chosenItemId); + } + + var recipeProto = LDB.recipes.Select(assemblerRecipeId); + if (recipeProto == null) + return null; + chosenItemId = recipeProto.Results[0]; + if (recipeProto.Results.Length == 1) + { + _chosenItemIdForRecipeId[assemblerRecipeId] = chosenItemId; + return ItemCalculationRuntimeSetting.ForItemId(chosenItemId); + } + + // todo figure out better way to manage this + _chosenItemIdForRecipeId[assemblerRecipeId] = chosenItemId; + if (chosenItemId == 1120) + { + // hydrogen is kind of boring + chosenItemId = recipeProto.Results[1]; + } + Log.LogDebug($"chose {chosenItemId} for recipe {recipeProto.name} (total: {recipeProto.Results.Length})"); + return ItemCalculationRuntimeSetting.ForItemId(chosenItemId); + } + } +} diff --git a/BetterStats/Properties/AssemblyInfo.cs b/BetterStats/Properties/AssemblyInfo.cs deleted file mode 100644 index 7f0380c..0000000 --- a/BetterStats/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("BetterStats DSP Plugin")] -[assembly: AssemblyDescription("BetterStats DSP Plugin")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("DSM.BetterStats.Properties")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("b339b5f8-5f4e-4671-b752-bfdae0b26a08")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -[assembly: AssemblyVersion("1.3.3")] -[assembly: AssemblyFileVersion("1.3.3")] diff --git a/BetterStats/README.md b/BetterStats/README.md index e0faa10..88a72b0 100644 --- a/BetterStats/README.md +++ b/BetterStats/README.md @@ -62,13 +62,15 @@ Inspired by https://docs.bepinex.dev/master/articles/dev_guide/plugin_tutorial/1 ### Setup visual studio - Install Visual Studio (not visual studio code) -- In Visual Studio, create a new blank project and there choose "add more tools" and add .Net developpement support +- In Visual Studio, create a new blank project and there choose "add more tools" and add .Net development support - Install .Net Framework 3.5 (be careful to choose "Framework") ### project setup - In Visual Studio, choose import and then select "BetterStats.csproj" -- In the right tree, right click references and then select "add references" and add the requered references (most of them located in C:\Program Files (x86)\Steam\steamapps\common\Dyson Sphere Program\DSPGAME_Data\Managed ; others are from BepinEx) -- You will find the last DLL "Assembly-CSharp.refstub.dll" in https://discord.com/channels/806549677209944084/806556447885623336/809241758323572766 +- Game libraries should be loaded from NuGet +### Create a release +- From a PowerShell prompt run `.\package -vertype ` with either major, minor, patch or 'none'. 'none' will not increment the version number +- Uploadable zip file will be placed in .\tmp_release
Icon made by Freepik from www.flaticon.com
diff --git a/BetterStats/ResearchTechHelper.cs b/BetterStats/ResearchTechHelper.cs new file mode 100644 index 0000000..7a2a728 --- /dev/null +++ b/BetterStats/ResearchTechHelper.cs @@ -0,0 +1,82 @@ +using System.Linq; + +namespace DefaultNamespace +{ + public static class ResearchTechHelper + { + private static TechProto _sprayLevel3Proto; + private static TechProto _sprayLevel2Proto; + private static TechProto _sprayLevel1Proto; + + public static float GetMaxProductivityIncrease() + { + var highestProliferatorTechUnlocked = GetMaxIncIndex(); + return (float)Cargo.incTableMilli[highestProliferatorTechUnlocked]; + } + + public static float GetMaxSpeedIncrease() + { + var highestProliferatorTechUnlocked = GetMaxIncIndex(); + return (float)Cargo.accTableMilli[highestProliferatorTechUnlocked]; + } + + private static int GetMaxIncIndex() + { + InitTechProtos(); + if (GameMain.history.techStates[_sprayLevel3Proto.ID].unlocked) + return 4; + if (GameMain.history.techStates[_sprayLevel2Proto.ID].unlocked) + return 2; + if (GameMain.history.techStates[_sprayLevel1Proto.ID].unlocked) + return 1; + return 0; + } + + private static void InitTechProtos() + { + if (_sprayLevel3Proto == null) + { + var proliferatorProtos = LDB.techs.dataArray.ToList().FindAll(t => t.Name.Contains("增产剂")); + proliferatorProtos.Sort((p1, p2) => + { + if (p1.PreTechs.Contains(p2.ID)) + { + // sorting high to low + return -1; + } + + if (p2.PreTechs.Contains(p1.ID)) + { + return 1; + } + + return p1.ID.CompareTo(p2.ID); + }); + if (proliferatorProtos.Count >= 3) + { + // if more are added, add them here + _sprayLevel3Proto = proliferatorProtos[0]; + _sprayLevel2Proto = proliferatorProtos[1]; + _sprayLevel1Proto = proliferatorProtos[2]; + } + } + } + + public static bool IsProliferatorUnlocked() + { + return GetMaxIncIndex() > 0; + } + + public static int GetMaxPilerStackingUnlocked() + { + if (BetterStats.BetterStats.disableStackingCalc.Value) + { + return 1; + } + var stationPilerLevel1 = GameMain.history.TechUnlocked(3801) ? 1 + (int)LDB.techs.Select(3801).UnlockValues[0] : 1; + var stationPilerLevel2 = GameMain.history.TechUnlocked(3802) ? stationPilerLevel1 + (int)LDB.techs.Select(3802).UnlockValues[0] : stationPilerLevel1; + var maxStationPilerTech = GameMain.history.TechUnlocked(3803) ? stationPilerLevel2 + (int)LDB.techs.Select(3803).UnlockValues[0] : stationPilerLevel2; + return maxStationPilerTech; + } + } +} diff --git a/BetterStats/package.ps1 b/BetterStats/package.ps1 new file mode 100644 index 0000000..6fd00b6 --- /dev/null +++ b/BetterStats/package.ps1 @@ -0,0 +1,72 @@ +param ($vertype = 'patch') + +Set-StrictMode -Version Latest + +write-host "version type $vertype" + +Remove-Item .\tmp_release -Force -Recurse +New-Item .\tmp_release -ItemType "directory" -Force -ErrorAction Stop + +$manifestContent = Get-Content -path .\manifest.json -Raw +$j = $manifestContent | ConvertFrom-Json + +$sourceFileContent = Get-Content -path .\BetterStats.csproj -Raw +$sourceFileContent -match '.*(\d+.\d+.\d+).*' + +$old_vernum = $Matches[1] + +$v = [version]$old_vernum +write-host "v = $v" + +if ($vertype -eq "minor") +{ + $new_version = [version]::New($v.Major, $v.Minor + 1, 0) +} +elseif ($vertype -eq "patch") +{ + $new_version = [version]::New($v.Major, $v.Minor, $v.Build + 1) +} +elseif ($vertype -eq "major") +{ + $new_version = [version]::New($v.Major + 1, 0, 0) +} +elseif ($vertype -eq "none") +{ + $new_version = [version]::New($v.Major, $v.Minor, $v.Build) +} +else +{ + Write-Host "invalid vertype: should be (major, minor, patch), got $vertype" + exit +} + +Write-Host "next version $new_version" +$new_version_string = "$([string]::Join(".", $new_version))"; + +$sourceFileContent -replace $old_vernum, $new_version_string | Set-Content -Path .\BetterStats.csproj -NoNewline + +$dotnet_process = Start-Process dotnet.exe -ArgumentList "build" -ErrorAction Stop -NoNewWindow -PassThru +Wait-Process -Id $dotnet_process.Id -ErrorAction Continue + +Copy-Item -Path bin/Debug/netstandard2.0/BetterStats.dll -Destination tmp_release +Copy-Item README.md -Destination tmp_release\README.md +Copy-Item icon.png -Destination tmp_release + +if ($vertype -ne "none") +{ + $j.version_number = $new_version_string + $j |ConvertTo-Json | Set-Content -Path .\tmp_release\manifest.json + Copy-Item .\tmp_release\manifest.json manifest.json +} else +{ + Copy-Item .\manifest.json .\tmp_release\manifest.json +} + + +$compress = @{ + Path = "tmp_release\*" + CompressionLevel = "Fastest" + DestinationPath = "tmp_release\BetterStats.zip" +} +Compress-Archive @compress +