refactor(UXAssist): split LogisticsPatch into focused classes

This commit is contained in:
2026-06-23 02:56:19 +08:00
parent f2abb7cc2c
commit dace35a691
8 changed files with 2152 additions and 2109 deletions
@@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.Reflection.Emit;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;
using UXAssist.Common;
namespace UXAssist.Patches.Logistics;
internal class AutoConfigLogistics : PatchImpl<AutoConfigLogistics>
{
protected override void OnEnable()
{
ToggleLimitAutoReplenishCount();
}
protected override void OnDisable()
{
ToggleLimitAutoReplenishCount();
}
internal static void ToggleLimitAutoReplenishCount()
{
LimitAutoReplenishCount.Enable(LogisticsPatch.AutoConfigLogisticsEnabled.Value && LogisticsPatch.AutoConfigLimitAutoReplenishCount.Value);
}
private class LimitAutoReplenishCount : PatchImpl<LimitAutoReplenishCount>
{
[HarmonyTranspiler]
[HarmonyPatch(typeof(PlanetFactory), nameof(PlanetFactory.EntityAutoReplenishIfNeeded))]
[HarmonyPatch(typeof(PlanetFactory), nameof(PlanetFactory.StationAutoReplenishIfNeeded))]
private static IEnumerable<CodeInstruction> PlanetFactory_StationAutoReplenishIfNeeded_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
// Patch dispenser courier count
matcher.Start().MatchForward(false,
new CodeMatch(ci => ci.IsLdloc()),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(DispenserComponent), nameof(DispenserComponent.workCourierDatas))),
new CodeMatch(OpCodes.Ldlen),
new CodeMatch(OpCodes.Conv_I4)
);
matcher.Repeat(m => m.Advance(4).InsertAndAdvance(
new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(LogisticsPatch), nameof(LogisticsPatch.AutoConfigDispenserCourierCount))),
new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(ConfigEntry<int>), nameof(ConfigEntry<int>.Value))),
new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Math), nameof(Math.Min), [typeof(int), typeof(int)]))
));
// Patch PLS/ILS drone count
matcher.Start().MatchForward(false,
new CodeMatch(ci => ci.IsLdloc()),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(StationComponent), nameof(StationComponent.workDroneDatas))),
new CodeMatch(OpCodes.Ldlen),
new CodeMatch(OpCodes.Conv_I4)
);
matcher.Repeat(m =>
{
var instr = m.Instruction;
m.Advance(4).InsertAndAdvance(
instr,
Transpilers.EmitDelegate((int x, StationComponent station)
=> Math.Min(x, station.isStellar ? LogisticsPatch.AutoConfigILSDroneCount.Value : LogisticsPatch.AutoConfigPLSDroneCount.Value))
);
});
// Patch ILS ship count
matcher.Start().MatchForward(false,
new CodeMatch(ci => ci.IsLdloc()),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(StationComponent), nameof(StationComponent.workShipDatas))),
new CodeMatch(OpCodes.Ldlen),
new CodeMatch(OpCodes.Conv_I4)
);
matcher.Repeat(m => m.Advance(4).InsertAndAdvance(
new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(LogisticsPatch), nameof(LogisticsPatch.AutoConfigILSShipCount))),
new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(ConfigEntry<int>), nameof(ConfigEntry<int>.Value))),
new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Math), nameof(Math.Min), [typeof(int), typeof(int)]))
));
return matcher.InstructionEnumeration();
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(BuildTool_Addon), nameof(BuildTool_Addon.SetDefaultParams))]
private static void BuildTool_Addon_SetDefaultParams_Postfix(BuildTool_Addon __instance, int bpIndex)
{
if (__instance.handPrefabDesc.isDispenser)
{
__instance.handBpParams[bpIndex][2] = (int)(long)(5000.0 * LogisticsPatch.AutoConfigDispenserChargePower.Value + 0.5);
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(PlanetTransport), nameof(PlanetTransport.NewStationComponent))]
private static void PlanetTransport_NewStationComponent_Postfix(PlanetTransport __instance, StationComponent __result)
{
LogisticsPatch.DoConfigStation(__instance.factory, __result);
}
[HarmonyPostfix]
[HarmonyPatch(typeof(DefenseSystem), nameof(DefenseSystem.NewBattleBaseComponent))]
private static void DefenseSystem_NewBattleBaseComponent_Postfix(DefenseSystem __instance, int __result)
{
if (__result <= 0) return;
LogisticsPatch.BattleBaseSetChargePower(__instance.factory, __instance.battleBases[__result]);
}
[HarmonyPostfix]
[HarmonyPatch(typeof(PlanetTransport), nameof(PlanetTransport.NewDispenserComponent))]
private static void PlanetTransport_NewDispenserComponent_Postfix(PlanetTransport __instance, int __result)
{
if (__result <= 0) return;
LogisticsPatch.DispenserFillCouriers(__instance.factory, __instance.dispenserPool[__result]);
}
}
internal class AutoConfigLogisticsSetDefaultRemoteLogicToStorage : PatchImpl<AutoConfigLogisticsSetDefaultRemoteLogicToStorage>
{
[HarmonyTranspiler]
[HarmonyPatch(typeof(UIControlPanelStationStorage), nameof(UIControlPanelStationStorage.OnItemPickerReturn))]
[HarmonyPatch(typeof(UIStationStorage), nameof(UIStationStorage.OnItemPickerReturn))]
private static IEnumerable<CodeInstruction> UIStationStorage_OnItemPickerReturn_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ldarg_0),
new CodeMatch(OpCodes.Ldfld),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(StationComponent), nameof(StationComponent.isStellar))),
new CodeMatch(ci => ci.opcode == OpCodes.Brtrue_S || ci.opcode == OpCodes.Brtrue),
new CodeMatch(OpCodes.Ldc_I4_0),
new CodeMatch(ci => ci.opcode == OpCodes.Br_S || ci.opcode == OpCodes.Br),
new CodeMatch(OpCodes.Ldc_I4_1),
new CodeMatch(OpCodes.Call, AccessTools.PropertyGetter(typeof(GameMain), nameof(GameMain.mainPlayer)))
);
if (matcher.IsValid)
{
matcher.RemoveInstructions(7).InsertAndAdvance(
new CodeInstruction(OpCodes.Ldc_I4_0)
);
}
return matcher.InstructionEnumeration();
}
}
+419
View File
@@ -0,0 +1,419 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using HarmonyLib;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UXAssist.Common;
namespace UXAssist.Patches.Logistics;
internal class LogisticsCapacityTweaks : PatchImpl<LogisticsCapacityTweaks>
{
private static KeyCode _lastKey = KeyCode.None;
private static long _nextKeyTick;
private static bool _skipNextUIStationStorageEvent;
private static bool _skipNextUIControlPanelStationStorageEvent;
private static bool _refreshingUIStationStorage;
private static bool _refreshingUIControlPanelStationStorage;
private static bool UpdateKeyPressed(KeyCode code)
{
if (!Input.GetKey(code))
return false;
var main = GameMain.instance;
if (main == null)
return false;
if (code != _lastKey)
{
_lastKey = code;
_nextKeyTick = main.timei + 30;
return true;
}
var currTick = main.timei;
if (_nextKeyTick > currTick) return false;
_nextKeyTick = currTick + 4;
return true;
}
internal static void UpdateInput()
{
if (_lastKey != KeyCode.None && Input.GetKeyUp(_lastKey))
{
_lastKey = KeyCode.None;
}
if (VFInput.shift) return;
var ctrl = VFInput.control;
var alt = VFInput.alt;
if (ctrl && alt) return;
int delta;
if (UpdateKeyPressed(KeyCode.LeftArrow))
{
if (ctrl)
delta = -100000;
else if (alt)
delta = -1000;
else
delta = -10;
}
else if (UpdateKeyPressed(KeyCode.RightArrow))
{
if (ctrl)
delta = 100000;
else if (alt)
delta = 1000;
else
delta = 10;
}
else if (UpdateKeyPressed(KeyCode.DownArrow))
{
if (ctrl)
delta = -1000000;
else if (alt)
delta = -10000;
else
delta = -100;
}
else if (UpdateKeyPressed(KeyCode.UpArrow))
{
if (ctrl)
delta = 1000000;
else if (alt)
delta = 10000;
else
delta = 100;
}
else
{
return;
}
var targets = new List<RaycastResult>();
EventSystem.current.RaycastAll(new PointerEventData(EventSystem.current) { position = Input.mousePosition }, targets);
foreach (var target in targets)
{
StationComponent station = null;
int index = -1;
PlanetFactory planetFactory = null;
var stationStorage = target.gameObject.GetComponentInParent<UIStationStorage>();
bool isControlPanelStationStorage = false;
if (stationStorage != null)
{
station = stationStorage.station;
index = stationStorage.index;
planetFactory = stationStorage.stationWindow?.factory;
}
else
{
var controlPanelStationStorage = target.gameObject.GetComponentInParent<UIControlPanelStationStorage>();
if (controlPanelStationStorage == null) continue;
station = controlPanelStationStorage.station;
index = controlPanelStationStorage.index;
planetFactory = controlPanelStationStorage.factory;
isControlPanelStationStorage = true;
}
if (station?.storage is null) continue;
ref var storage = ref station.storage[index];
var oldMax = storage.max;
var newMax = oldMax + delta;
if (newMax < 0)
{
newMax = 0;
}
else
{
int itemCountMax;
if (LogisticsPatch.AllowOverflowInLogisticsEnabled.Value)
{
itemCountMax = 90000000;
}
else
{
if (planetFactory == null || station.entityId <= 0 || station.entityId >= planetFactory.entityCursor) continue;
var modelProto = LDB.models.Select(planetFactory.entityPool[station.entityId].modelIndex);
itemCountMax = modelProto == null ? 0 : modelProto.prefabDesc.stationMaxItemCount;
itemCountMax += station.isStellar && !station.isCollector ? GameMain.history.remoteStationExtraStorage : GameMain.history.localStationExtraStorage;
}
if (newMax > itemCountMax)
{
newMax = itemCountMax;
}
}
storage.max = newMax;
if (isControlPanelStationStorage)
{
_skipNextUIControlPanelStationStorageEvent = oldMax / 100 != newMax / 100;
}
else
{
_skipNextUIStationStorageEvent = oldMax / 100 != newMax / 100;
}
break;
}
}
[HarmonyPrefix]
[HarmonyPatch(typeof(UIStationStorage), "RefreshValues")]
private static void UIStationStorage_RefreshValues_Prefix()
{
_refreshingUIStationStorage = true;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(UIStationStorage), "RefreshValues")]
private static void UIStationStorage_RefreshValues_Postfix()
{
_refreshingUIStationStorage = false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(UIControlPanelStationStorage), "RefreshValues")]
private static void UIControlPanelStationStorage_RefreshValues_Prefix()
{
_refreshingUIControlPanelStationStorage = true;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(UIControlPanelStationStorage), "RefreshValues")]
private static void UIControlPanelStationStorage_RefreshValues_Postfix()
{
_refreshingUIControlPanelStationStorage = false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(UIStationStorage), nameof(UIStationStorage.OnMaxSliderValueChange))]
private static bool UIStationStorage_OnMaxSliderValueChange_Prefix()
{
if (!_refreshingUIStationStorage && !_skipNextUIStationStorageEvent) return true;
_skipNextUIStationStorageEvent = false;
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(UIControlPanelStationStorage), nameof(UIControlPanelStationStorage.OnMaxSliderValueChange))]
private static bool UIControlPanelStationStorage_OnMaxSliderValueChange_Prefix()
{
if (!_refreshingUIControlPanelStationStorage && !_skipNextUIControlPanelStationStorageEvent) return true;
_skipNextUIControlPanelStationStorageEvent = false;
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(PlanetTransport), nameof(PlanetTransport.OnTechFunctionUnlocked))]
private static bool PlanetTransport_OnTechFunctionUnlocked_Prefix(PlanetTransport __instance, int _funcId, double _valuelf, int _level)
{
switch (_funcId)
{
case 30:
{
var stationPool = __instance.stationPool;
var factory = __instance.factory;
var history = GameMain.history;
for (var i = __instance.stationCursor - 1; i > 0; i--)
{
if (stationPool[i] == null || stationPool[i].id != i || (stationPool[i].isStellar && !stationPool[i].isCollector && !stationPool[i].isVeinCollector)) continue;
var modelIndex = factory.entityPool[stationPool[i].entityId].modelIndex;
var maxCount = LDB.models.Select(modelIndex).prefabDesc.stationMaxItemCount;
var oldMaxCount = maxCount + history.localStationExtraStorage - _valuelf;
if (oldMaxCount < 1.0) continue;
var intOldMaxCount = (int)Math.Round(oldMaxCount);
var intNewMaxCount = maxCount + history.localStationExtraStorage;
var ratio = intNewMaxCount / oldMaxCount;
var storage = stationPool[i].storage;
for (var j = storage.Length - 1; j >= 0; j--)
{
var max = storage[j].max;
if (max + 10 < intOldMaxCount || max >= intNewMaxCount) continue;
storage[j].max = Mathf.RoundToInt((float)(max * ratio / 50.0)) * 50;
}
}
break;
}
case 31:
{
var stationPool = __instance.stationPool;
var factory = __instance.factory;
var history = GameMain.history;
for (var i = __instance.stationCursor - 1; i > 0; i--)
{
if (stationPool[i] == null || stationPool[i].id != i || !stationPool[i].isStellar || stationPool[i].isCollector || stationPool[i].isVeinCollector) continue;
var modelIndex = factory.entityPool[stationPool[i].entityId].modelIndex;
var maxCount = LDB.models.Select(modelIndex).prefabDesc.stationMaxItemCount;
var oldMaxCount = maxCount + history.remoteStationExtraStorage - _valuelf;
if (oldMaxCount < 1.0) continue;
var intOldMaxCount = (int)Math.Round(oldMaxCount);
var intNewMaxCount = maxCount + history.remoteStationExtraStorage;
var ratio = intNewMaxCount / oldMaxCount;
var storage = stationPool[i].storage;
for (var j = storage.Length - 1; j >= 0; j--)
{
var max = storage[j].max;
if (max + 10 < intOldMaxCount || max >= intNewMaxCount) continue;
storage[j].max = Mathf.RoundToInt((float)(max * ratio / 100.0)) * 100;
}
}
break;
}
}
return false;
}
}
internal class GreaterPowerUsageInLogistics : PatchImpl<GreaterPowerUsageInLogistics>
{
protected override void OnEnable()
{
var window = UIRoot.instance?.uiGame?.stationWindow;
if (window == null) return;
window._Close();
window.maxMiningSpeedSlider.maxValue = 27f;
}
protected override void OnDisable()
{
var window = UIRoot.instance?.uiGame?.stationWindow;
if (window == null) return;
window._Close();
window.maxMiningSpeedSlider.maxValue = 20f;
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(UIStationWindow), nameof(UIStationWindow.OnStationIdChange))]
private static IEnumerable<CodeInstruction> UIStationWindow_OnStationIdChange_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.Start().Insert(
new CodeInstruction(OpCodes.Ldarg_0),
Transpilers.EmitDelegate((UIStationWindow window) =>
{
window.maxMiningSpeedSlider.maxValue = 27f;
})
).MatchForward(false,
new CodeMatch(OpCodes.Ldarg_0),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(UIStationWindow), nameof(UIStationWindow.maxChargePowerSlider))),
new CodeMatch(ci => ci.IsLdloc()),
new CodeMatch(ci => ci.opcode == OpCodes.Ldc_I4 && ci.OperandIs(0xC350)),
new CodeMatch(OpCodes.Conv_I8)
);
var pos = matcher.Pos + 1;
matcher.Advance(5).MatchForward(false,
new CodeMatch(OpCodes.Conv_R4),
new CodeMatch(OpCodes.Callvirt, AccessTools.PropertySetter(typeof(Slider), nameof(Slider.value)))
);
var pos2 = matcher.Pos + 2;
matcher.Start().Advance(pos);
var ldvar = matcher.InstructionAt(1).Clone();
var locWorkEnergyPerTick = matcher.InstructionAt(-2).operand;
matcher.RemoveInstructions(pos2 - pos).InsertAndAdvance(
ldvar,
new CodeInstruction(OpCodes.Ldloc_S, locWorkEnergyPerTick),
Transpilers.EmitDelegate((UIStationWindow window, long maxWorkEnergy, long workEnergyPerTick) =>
{
var maxSliderValue = maxWorkEnergy / 50000L;
window.maxChargePowerSlider.maxValue = maxSliderValue + 9;
window.maxChargePowerSlider.minValue = maxWorkEnergy / 500000L;
if (workEnergyPerTick <= maxWorkEnergy)
window.maxChargePowerSlider.Set(workEnergyPerTick / 50000L, false);
else
window.maxChargePowerSlider.Set(maxSliderValue + (workEnergyPerTick - 1) / maxWorkEnergy + 1, false);
})
);
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ldarg_0),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(UIStationWindow), nameof(UIStationWindow.maxMiningSpeedSlider))),
new CodeMatch(OpCodes.Ldarg_0),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(UIStationWindow), nameof(UIStationWindow.factorySystem))),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(FactorySystem), nameof(FactorySystem.minerPool))),
new CodeMatch(ci => ci.IsLdloc()),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(StationComponent), nameof(StationComponent.minerId))),
new CodeMatch(OpCodes.Ldelema, typeof(MinerComponent)),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(MinerComponent), nameof(MinerComponent.speed)))
);
pos = matcher.Pos + 9;
matcher.Advance(5).MatchForward(false,
new CodeMatch(OpCodes.Conv_R4),
new CodeMatch(OpCodes.Callvirt, AccessTools.PropertySetter(typeof(Slider), nameof(Slider.value)))
);
pos2 = matcher.Pos;
matcher.Start().Advance(pos).RemoveInstructions(pos2 - pos).Insert(
Transpilers.EmitDelegate((int speed) =>
{
if (speed <= 30000)
return (speed - 10000) / 1000;
return (speed - 30000) / 10000 + 20;
})
);
return matcher.InstructionEnumeration();
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(UIStationWindow), nameof(UIStationWindow.OnMaxMiningSpeedChange))]
private static IEnumerable<CodeInstruction> UIStationWindow_OnMaxMiningSpeedChange_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.MatchForward(false,
new CodeMatch(ci => ci.opcode == OpCodes.Ldc_I4 && ci.OperandIs(10000)),
new CodeMatch(OpCodes.Ldarg_1)
);
var pos = matcher.Pos;
matcher.MatchForward(false,
new CodeMatch(OpCodes.Stloc_1)
);
var pos2 = matcher.Pos;
matcher.Start().Advance(pos);
var labels = matcher.Labels;
matcher.RemoveInstructions(pos2 - pos);
matcher.Insert(
new CodeInstruction(OpCodes.Ldarg_1).WithLabels(labels),
Transpilers.EmitDelegate((float value) =>
{
var intval = (int)(value + 0.5f);
if (intval <= 20)
return intval * 1000 + 10000;
return (intval - 20) * 10000 + 30000;
})
);
return matcher.InstructionEnumeration();
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(UIStationWindow), nameof(UIStationWindow.OnMaxChargePowerSliderValueChange))]
private static IEnumerable<CodeInstruction> UIStationWindow_OnMaxChargePowerSliderValueChange_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ldarg_0),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(UIStationWindow), nameof(UIStationWindow.factory))),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(PlanetFactory), nameof(PlanetFactory.powerSystem))),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(PowerSystem), nameof(PowerSystem.consumerPool)))
);
var labels = matcher.Labels;
matcher.Labels = null;
matcher.Insert(
new CodeInstruction(OpCodes.Ldarg_0).WithLabels(labels),
new CodeInstruction(OpCodes.Ldarg_1),
Transpilers.EmitDelegate((UIStationWindow window, float value) =>
{
float prevMax = window.workEnergyPrefab * 5L / 50000L;
if (value <= prevMax)
{
return value;
}
return prevMax * (value - prevMax + 1);
}),
new CodeInstruction(OpCodes.Starg_S, 1)
);
return matcher.InstructionEnumeration();
}
}
@@ -0,0 +1,340 @@
using System;
using System.Collections.Generic;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;
using UXAssist.Common;
using UXAssist.Common.ModFeatures;
using GameLogicProc = UXAssist.Common.GameLogic;
namespace UXAssist.Patches.Logistics;
[ModFeature("Logistics", Order = 11)]
public static class LogisticsPatch
{
public static ConfigEntry<bool> AutoConfigLogisticsEnabled;
public static ConfigEntry<bool> AutoConfigLimitAutoReplenishCount;
// Dispenser config
public static ConfigEntry<int> AutoConfigDispenserChargePower; // 3~30, display as 300000.0 * value
public static ConfigEntry<int> AutoConfigDispenserCourierCount; // 0~10
// Battlefield Analysis Base
public static ConfigEntry<int> AutoConfigBattleBaseChargePower; // 4~40, display as 300000.0 * value
// PLS config
public static ConfigEntry<int> AutoConfigPLSChargePower; // 2~20, display as 3000000.0 * value
public static ConfigEntry<int> AutoConfigPLSMaxTripDrone; // 1~180, by degress
public static ConfigEntry<int> AutoConfigPLSDroneMinDeliver; // 0~10; 0 = 1%, 1~10 = 10% *value
public static ConfigEntry<int> AutoConfigPLSMinPilerValue; // 0~4; 0 = Maximum in tech, 1~4 = piler stacking count
public static ConfigEntry<int> AutoConfigPLSDroneCount; // 0~50
// ILS config
public static ConfigEntry<bool> SetDefaultRemoteLogicToStorage;
public static ConfigEntry<int> AutoConfigILSChargePower; // 2~20, display as 15000000.0 * value
public static ConfigEntry<int> AutoConfigILSMaxTripDrone; // 1~180, by degress
public static ConfigEntry<int> AutoConfigILSMaxTripShip; // 1~41; 1~20 = value LY, 21-40 = 2*value-20LY, 41 = Unlimited
public static ConfigEntry<int> AutoConfigILSWarperDistance; // 2~21; 2~7 = value * 0.5 - 0.5AU, 8~16 = value - 4AU, 17~20 = value * 2 - 20AU, 21 = 60AU
public static ConfigEntry<int> AutoConfigILSDroneMinDeliver; // 0~10; 0 = 1%, 1~10 = 10% *value
public static ConfigEntry<int> AutoConfigILSShipMinDeliver; // 0~10; 0 = 1%, 1~10 = 10% *value
public static ConfigEntry<int> AutoConfigILSMinPilerValue; // 0~4; 0 = Maximum in tech, 1~4 = piler stacking count
public static ConfigEntry<bool> AutoConfigILSIncludeOrbitCollector;
public static ConfigEntry<bool> AutoConfigILSWarperNecessary;
public static ConfigEntry<int> AutoConfigILSDroneCount; // 0~100
public static ConfigEntry<int> AutoConfigILSShipCount; // 0~10
// Vein collector config
public static ConfigEntry<int> AutoConfigVeinCollectorHarvestSpeed; // 0-20, 100% + 10% * value
public static ConfigEntry<int> AutoConfigVeinCollectorMinPilerValue; // 0~4; 0 = Maximum in tech, 1~4 = piler stacking count
public static ConfigEntry<bool> LogisticsCapacityTweaksEnabled;
public static ConfigEntry<bool> AllowOverflowInLogisticsEnabled;
public static ConfigEntry<bool> GreaterPowerUsageInLogisticsEnabled;
public static ConfigEntry<bool> LogisticsConstrolPanelImprovementEnabled;
public static ConfigEntry<bool> RealtimeLogisticsInfoPanelEnabled;
public static ConfigEntry<bool> RealtimeLogisticsInfoPanelBarsEnabled;
public static void Init()
{
AutoConfigLogisticsEnabled.SettingChanged += (_, _) => AutoConfigLogistics.Enable(AutoConfigLogisticsEnabled.Value);
AutoConfigLimitAutoReplenishCount.SettingChanged += (_, _) => AutoConfigLogistics.ToggleLimitAutoReplenishCount();
SetDefaultRemoteLogicToStorage.SettingChanged += (_, _) => AutoConfigLogisticsSetDefaultRemoteLogicToStorage.Enable(SetDefaultRemoteLogicToStorage.Value);
LogisticsCapacityTweaksEnabled.SettingChanged += (_, _) => LogisticsCapacityTweaks.Enable(LogisticsCapacityTweaksEnabled.Value);
AllowOverflowInLogisticsEnabled.SettingChanged += (_, _) => AllowOverflowInLogistics.Enable(AllowOverflowInLogisticsEnabled.Value);
GreaterPowerUsageInLogisticsEnabled.SettingChanged += (_, _) => GreaterPowerUsageInLogistics.Enable(GreaterPowerUsageInLogisticsEnabled.Value);
LogisticsConstrolPanelImprovementEnabled.SettingChanged += (_, _) => LogisticsConstrolPanelImprovement.Enable(LogisticsConstrolPanelImprovementEnabled.Value);
RealtimeLogisticsInfoPanelEnabled.SettingChanged += (_, _) => RealtimeLogisticsInfoPanel.Enable(RealtimeLogisticsInfoPanelEnabled.Value);
RealtimeLogisticsInfoPanelBarsEnabled.SettingChanged += (_, _) => RealtimeLogisticsInfoPanel.EnableBars(RealtimeLogisticsInfoPanelBarsEnabled.Value);
}
public static void Start()
{
AutoConfigLogistics.Enable(AutoConfigLogisticsEnabled.Value);
AutoConfigLogisticsSetDefaultRemoteLogicToStorage.Enable(SetDefaultRemoteLogicToStorage.Value);
LogisticsCapacityTweaks.Enable(LogisticsCapacityTweaksEnabled.Value);
AllowOverflowInLogistics.Enable(AllowOverflowInLogisticsEnabled.Value);
GreaterPowerUsageInLogistics.Enable(GreaterPowerUsageInLogisticsEnabled.Value);
LogisticsConstrolPanelImprovement.Enable(LogisticsConstrolPanelImprovementEnabled.Value);
RealtimeLogisticsInfoPanel.Enable(RealtimeLogisticsInfoPanelEnabled.Value);
RealtimeLogisticsInfoPanel.EnableBars(RealtimeLogisticsInfoPanelBarsEnabled.Value);
GameLogicProc.OnGameBegin += RealtimeLogisticsInfoPanel.OnGameBegin;
GameLogicProc.OnGameEnd += RealtimeLogisticsInfoPanel.OnGameEnd;
GameLogicProc.OnDataLoaded += RealtimeLogisticsInfoPanel.OnDataLoaded;
}
public static void Uninit()
{
GameLogicProc.OnDataLoaded -= RealtimeLogisticsInfoPanel.OnDataLoaded;
GameLogicProc.OnGameEnd -= RealtimeLogisticsInfoPanel.OnGameEnd;
GameLogicProc.OnGameBegin -= RealtimeLogisticsInfoPanel.OnGameBegin;
AutoConfigLogistics.Enable(false);
AutoConfigLogisticsSetDefaultRemoteLogicToStorage.Enable(false);
LogisticsCapacityTweaks.Enable(false);
AllowOverflowInLogistics.Enable(false);
GreaterPowerUsageInLogistics.Enable(false);
LogisticsConstrolPanelImprovement.Enable(false);
RealtimeLogisticsInfoPanel.Enable(false);
}
public static void OnUpdate() => RealtimeInfoPanelPatch.OnUpdate();
public static void OnInputUpdate()
{
if (VFInput.onGUI && LogisticsCapacityTweaksEnabled.Value)
{
LogisticsCapacityTweaks.UpdateInput();
}
}
#region Apply auto-config values to existing facilities on the current planet
private enum StationKind
{
Pls,
Ils,
VeinCollector
}
// === Per-field setters (single source of truth, shared by auto-config-on-build and apply-to-planet) ===
internal static void StationSetChargePower(PlanetFactory factory, StationComponent station) =>
factory.powerSystem.consumerPool[station.pcId].workEnergyPerTick =
(long)((station.isStellar ? 250000.0 * AutoConfigILSChargePower.Value : 50000.0 * AutoConfigPLSChargePower.Value) + 0.5);
internal static void StationSetTripRangeDrones(PlanetFactory factory, StationComponent station) =>
station.tripRangeDrones = Math.Cos((station.isStellar ? AutoConfigILSMaxTripDrone.Value : AutoConfigPLSMaxTripDrone.Value) / 180.0 * Math.PI);
internal static void StationSetDeliveryDrones(PlanetFactory factory, StationComponent station)
{
var v = station.isStellar ? AutoConfigILSDroneMinDeliver.Value : AutoConfigPLSDroneMinDeliver.Value;
station.deliveryDrones = v == 0 ? 1 : v * 10;
}
internal static void StationSetPilerCount(PlanetFactory factory, StationComponent station) =>
station.pilerCount = station.isStellar ? AutoConfigILSMinPilerValue.Value : AutoConfigPLSMinPilerValue.Value;
internal static void StationFillDrones(PlanetFactory factory, StationComponent station)
{
var target = station.isStellar ? AutoConfigILSDroneCount.Value : AutoConfigPLSDroneCount.Value;
var toFill = Math.Max(0, target - station.idleDroneCount - station.workDroneCount);
if (toFill > 0) station.idleDroneCount += GameMain.data.mainPlayer.package.TakeItem((int)KnownItemId.Drone, toFill, out _);
}
internal static void StationSetTripRangeShips(PlanetFactory factory, StationComponent station) =>
station.tripRangeShips = AutoConfigILSMaxTripShip.Value switch
{
<= 20 => AutoConfigILSMaxTripShip.Value,
<= 40 => AutoConfigILSMaxTripShip.Value * 2 - 20,
_ => 10000,
} * 2400000.0;
internal static void StationSetWarpDistance(PlanetFactory factory, StationComponent station) =>
station.warpEnableDist = AutoConfigILSWarperDistance.Value switch
{
<= 7 => AutoConfigILSWarperDistance.Value * 0.5 - 0.5,
<= 16 => AutoConfigILSWarperDistance.Value - 4.0,
<= 20 => AutoConfigILSWarperDistance.Value * 2 - 20.0,
_ => 60.0,
} * 40000.0;
internal static void StationSetDeliveryShips(PlanetFactory factory, StationComponent station)
{
var v = AutoConfigILSShipMinDeliver.Value;
station.deliveryShips = v == 0 ? 1 : v * 10;
}
internal static void StationFillShips(PlanetFactory factory, StationComponent station)
{
var toFill = Math.Max(0, AutoConfigILSShipCount.Value - station.idleShipCount - station.workShipCount);
if (toFill > 0) station.idleShipCount += GameMain.data.mainPlayer.package.TakeItem((int)KnownItemId.Ship, toFill, out _);
}
internal static void StationSetIncludeOrbitCollector(PlanetFactory factory, StationComponent station) =>
station.includeOrbitCollector = AutoConfigILSIncludeOrbitCollector.Value;
internal static void StationSetWarperNecessary(PlanetFactory factory, StationComponent station) =>
station.warperNecessary = AutoConfigILSWarperNecessary.Value;
/* station.minerId may not be set yet on freshly built collectors, so resolve the minerId from the EntityData. */
internal static bool VeinCollectorSetHarvestSpeed(PlanetFactory factory, StationComponent station)
{
ref var entity = ref factory.entityPool[station.entityId];
if (entity.id != station.entityId || entity.minerId <= 0 || entity.minerId >= factory.factorySystem.minerCursor) return false;
factory.factorySystem.minerPool[entity.minerId].speed = 10000 + AutoConfigVeinCollectorHarvestSpeed.Value * 1000;
return true;
}
internal static void VeinCollectorSetPilerCount(PlanetFactory factory, StationComponent station) =>
station.pilerCount = AutoConfigVeinCollectorMinPilerValue.Value;
internal static void DispenserSetChargePower(PlanetFactory factory, DispenserComponent dispenser) =>
factory.powerSystem.consumerPool[dispenser.pcId].workEnergyPerTick = (long)(5000.0 * AutoConfigDispenserChargePower.Value + 0.5);
internal static void DispenserFillCouriers(PlanetFactory factory, DispenserComponent dispenser)
{
var toFill = Math.Max(0, AutoConfigDispenserCourierCount.Value - dispenser.idleCourierCount - dispenser.workCourierCount);
if (toFill > 0) dispenser.idleCourierCount += GameMain.data.mainPlayer.package.TakeItem((int)KnownItemId.Bot, toFill, out _);
}
internal static void BattleBaseSetChargePower(PlanetFactory factory, BattleBaseComponent battleBase) =>
factory.powerSystem.consumerPool[battleBase.pcId].workEnergyPerTick = (long)(5000.0 * AutoConfigBattleBaseChargePower.Value + 0.5);
// === Per-facility "apply all settings" (also used as auto-config-on-build entry point) ===
internal static void DoConfigStation(PlanetFactory factory, StationComponent station)
{
if (station.isCollector) return;
if (station.isVeinCollector)
{
if (VeinCollectorSetHarvestSpeed(factory, station))
VeinCollectorSetPilerCount(factory, station);
return;
}
if (!station.isStellar)
{
StationSetChargePower(factory, station);
StationSetTripRangeDrones(factory, station);
StationSetDeliveryDrones(factory, station);
StationSetPilerCount(factory, station);
StationFillDrones(factory, station);
return;
}
StationSetChargePower(factory, station);
StationSetTripRangeDrones(factory, station);
StationSetTripRangeShips(factory, station);
StationSetWarpDistance(factory, station);
StationSetDeliveryDrones(factory, station);
StationSetDeliveryShips(factory, station);
StationSetPilerCount(factory, station);
StationSetIncludeOrbitCollector(factory, station);
StationSetWarperNecessary(factory, station);
StationFillDrones(factory, station);
StationFillShips(factory, station);
}
// === Iterate over the current planet's facilities of a given kind ===
private static void ForEachStation(StationKind kind, Action<PlanetFactory, StationComponent> action)
{
var factory = GameMain.localPlanet?.factory;
var transport = factory?.transport;
var stationPool = transport?.stationPool;
if (stationPool == null) return;
for (var i = transport.stationCursor - 1; i > 0; i--)
{
var station = stationPool[i];
if (station == null || station.id != i || station.isCollector) continue;
var skip = kind switch
{
StationKind.VeinCollector => !station.isVeinCollector,
StationKind.Ils => station.isVeinCollector || !station.isStellar,
StationKind.Pls => station.isVeinCollector || station.isStellar,
_ => true
};
if (skip) continue;
action(factory, station);
}
RefreshOpenLogisticsWindows();
}
private static void ForEachDispenser(Action<PlanetFactory, DispenserComponent> action)
{
var factory = GameMain.localPlanet?.factory;
var transport = factory?.transport;
var dispenserPool = transport?.dispenserPool;
if (dispenserPool == null) return;
for (var i = transport.dispenserCursor - 1; i > 0; i--)
{
var dispenser = dispenserPool[i];
if (dispenser == null || dispenser.id != i) continue;
action(factory, dispenser);
}
RefreshOpenLogisticsWindows();
}
private static void ForEachBattleBase(Action<PlanetFactory, BattleBaseComponent> action)
{
var factory = GameMain.localPlanet?.factory;
var battleBases = factory?.defenseSystem?.battleBases;
if (battleBases?.buffer == null) return;
for (var i = battleBases.cursor - 1; i > 0; i--)
{
var battleBase = battleBases.buffer[i];
if (battleBase == null || battleBase.id != i) continue;
action(factory, battleBase);
}
RefreshOpenLogisticsWindows();
}
// Re-populate any open logistic facility detail window so applied values show immediately.
internal static void RefreshOpenLogisticsWindows()
{
var uiRoot = UIRoot.instance;
if (!uiRoot) return;
var uiGame = uiRoot.uiGame;
if (!uiGame) return;
var stationWindow = uiGame.stationWindow;
if (stationWindow && stationWindow.active) stationWindow.OnStationIdChange();
var dispenserWindow = uiGame.dispenserWindow;
if (dispenserWindow && dispenserWindow.active) dispenserWindow.OnDispenserIdChange();
var battleBaseWindow = uiGame.battleBaseWindow;
if (battleBaseWindow && battleBaseWindow.active) battleBaseWindow.OnBattleBaseIdChange();
}
// === Public entry points invoked by the config panel buttons ===
// Dispenser
public static void ApplyDispenserChargePower() => ForEachDispenser(DispenserSetChargePower);
public static void ApplyDispenserCourierCount() => ForEachDispenser(DispenserFillCouriers);
public static void ApplyAllDispenser() => ForEachDispenser((f, d) => { DispenserSetChargePower(f, d); DispenserFillCouriers(f, d); });
// Battlefield Analysis Base
public static void ApplyBattleBaseChargePower() => ForEachBattleBase(BattleBaseSetChargePower);
public static void ApplyAllBattleBase() => ForEachBattleBase(BattleBaseSetChargePower);
// PLS
public static void ApplyPLSChargePower() => ForEachStation(StationKind.Pls, StationSetChargePower);
public static void ApplyPLSTripRangeDrones() => ForEachStation(StationKind.Pls, StationSetTripRangeDrones);
public static void ApplyPLSDroneMinDeliver() => ForEachStation(StationKind.Pls, StationSetDeliveryDrones);
public static void ApplyPLSMinPilerValue() => ForEachStation(StationKind.Pls, StationSetPilerCount);
public static void ApplyPLSDroneCount() => ForEachStation(StationKind.Pls, StationFillDrones);
public static void ApplyAllPLS() => ForEachStation(StationKind.Pls, DoConfigStation);
// ILS
public static void ApplyILSChargePower() => ForEachStation(StationKind.Ils, StationSetChargePower);
public static void ApplyILSTripRangeDrones() => ForEachStation(StationKind.Ils, StationSetTripRangeDrones);
public static void ApplyILSTripRangeShips() => ForEachStation(StationKind.Ils, StationSetTripRangeShips);
public static void ApplyILSWarpDistance() => ForEachStation(StationKind.Ils, StationSetWarpDistance);
public static void ApplyILSDroneMinDeliver() => ForEachStation(StationKind.Ils, StationSetDeliveryDrones);
public static void ApplyILSShipMinDeliver() => ForEachStation(StationKind.Ils, StationSetDeliveryShips);
public static void ApplyILSMinPilerValue() => ForEachStation(StationKind.Ils, StationSetPilerCount);
public static void ApplyILSDroneCount() => ForEachStation(StationKind.Ils, StationFillDrones);
public static void ApplyILSShipCount() => ForEachStation(StationKind.Ils, StationFillShips);
public static void ApplyILSIncludeOrbitCollector() => ForEachStation(StationKind.Ils, StationSetIncludeOrbitCollector);
public static void ApplyILSWarperNecessary() => ForEachStation(StationKind.Ils, StationSetWarperNecessary);
public static void ApplyAllILS() => ForEachStation(StationKind.Ils, DoConfigStation);
// Vein Collector (Advanced Mining Machine)
public static void ApplyVeinCollectorHarvestSpeed() => ForEachStation(StationKind.VeinCollector, (f, s) => VeinCollectorSetHarvestSpeed(f, s));
public static void ApplyVeinCollectorMinPilerValue() => ForEachStation(StationKind.VeinCollector, VeinCollectorSetPilerCount);
public static void ApplyAllVeinCollector() => ForEachStation(StationKind.VeinCollector, DoConfigStation);
#endregion
}
@@ -0,0 +1,82 @@
using System.Collections.Generic;
using System.Reflection.Emit;
using HarmonyLib;
using UnityEngine;
using UXAssist.Common;
namespace UXAssist.Patches.Logistics;
internal class AllowOverflowInLogistics : PatchImpl<AllowOverflowInLogistics>
{
private static bool _blueprintPasting;
// Do not check for overflow when try to send hand items into storages
[HarmonyTranspiler]
[HarmonyPatch(typeof(UIControlPanelStationStorage), nameof(UIControlPanelStationStorage.OnItemIconMouseDown))]
[HarmonyPatch(typeof(UIStationStorage), nameof(UIStationStorage.OnItemIconMouseDown))]
private static IEnumerable<CodeInstruction> UIStationStorage_OnItemIconMouseDown_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.MatchForward(false,
new CodeMatch(OpCodes.Call, AccessTools.PropertyGetter(typeof(LDB), nameof(LDB.items))),
new CodeMatch(OpCodes.Ldarg_0)
);
var pos = matcher.Pos;
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ldloc_S),
new CodeMatch(OpCodes.Stloc_S)
);
var inst = matcher.InstructionAt(1).Clone();
var pos2 = matcher.Pos + 2;
matcher.Start().Advance(pos);
var labels = matcher.Labels;
matcher.RemoveInstructions(pos2 - pos).Insert(
new CodeInstruction(OpCodes.Ldloc_1).WithLabels(labels),
new CodeInstruction(OpCodes.Callvirt, AccessTools.PropertyGetter(typeof(Player), nameof(Player.inhandItemCount))),
inst
);
return matcher.InstructionEnumeration();
}
// Remove storage limit check
[HarmonyTranspiler]
[HarmonyPatch(typeof(PlanetTransport), nameof(PlanetTransport.SetStationStorage))]
private static IEnumerable<CodeInstruction> PlanetTransport_SetStationStorage_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.MatchForward(false,
new CodeMatch(ci => ci.IsLdarg()),
new CodeMatch(ci => ci.IsLdloc()),
new CodeMatch(ci => ci.IsLdloc()),
new CodeMatch(OpCodes.Add),
new CodeMatch(ci => ci.Branches(out _)),
new CodeMatch(ci => ci.IsLdloc()),
new CodeMatch(ci => ci.IsLdloc()),
new CodeMatch(OpCodes.Add),
new CodeMatch(ci => ci.IsStarg())
);
var label = generator.DefineLabel();
var oldLabels = matcher.Labels;
matcher.Labels = [];
matcher.InsertAndAdvance(
new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(AllowOverflowInLogistics), nameof(_blueprintPasting))).WithLabels(oldLabels),
new CodeInstruction(OpCodes.Brfalse, label)
);
matcher.Advance(9).Labels.Add(label);
return matcher.InstructionEnumeration();
}
[HarmonyPrefix]
[HarmonyPatch(typeof(BuildTool_BlueprintPaste), nameof(BuildTool_BlueprintPaste.CreatePrebuilds))]
private static void BuildTool_BlueprintPaste_CreatePrebuilds_Prefix()
{
_blueprintPasting = true;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(BuildTool_BlueprintPaste), nameof(BuildTool_BlueprintPaste.CreatePrebuilds))]
private static void BuildTool_BlueprintPaste_CreatePrebuilds_Postfix()
{
_blueprintPasting = false;
}
}
File diff suppressed because it is too large Load Diff