1
0
mirror of https://github.com/soarqin/DSP_Mods.git synced 2025-12-08 21:33:28 +08:00
Files
DSP_Mods/UXAssist/Patches/LogisticsPatch.cs
2025-05-25 10:55:03 +08:00

1592 lines
75 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
using UnityEngine.UI;
using UXAssist.Common;
using Object = UnityEngine.Object;
namespace UXAssist.Patches;
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<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> 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();
LogisticsCapacityTweaksEnabled.SettingChanged += (_, _) => LogisticsCapacityTweaks.Enable(LogisticsCapacityTweaksEnabled.Value);
AllowOverflowInLogisticsEnabled.SettingChanged += (_, _) => AllowOverflowInLogistics.Enable(AllowOverflowInLogisticsEnabled.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);
LogisticsCapacityTweaks.Enable(LogisticsCapacityTweaksEnabled.Value);
AllowOverflowInLogistics.Enable(AllowOverflowInLogisticsEnabled.Value);
LogisticsConstrolPanelImprovement.Enable(LogisticsConstrolPanelImprovementEnabled.Value);
RealtimeLogisticsInfoPanel.Enable(RealtimeLogisticsInfoPanelEnabled.Value);
RealtimeLogisticsInfoPanel.EnableBars(RealtimeLogisticsInfoPanelBarsEnabled.Value);
GameLogic.OnGameBegin += RealtimeLogisticsInfoPanel.OnGameBegin;
GameLogic.OnDataLoaded += RealtimeLogisticsInfoPanel.OnDataLoaded;
}
public static void Uninit()
{
GameLogic.OnDataLoaded -= RealtimeLogisticsInfoPanel.OnDataLoaded;
GameLogic.OnGameBegin -= RealtimeLogisticsInfoPanel.OnGameBegin;
AutoConfigLogistics.Enable(false);
LogisticsCapacityTweaks.Enable(false);
AllowOverflowInLogistics.Enable(false);
LogisticsConstrolPanelImprovement.Enable(false);
RealtimeLogisticsInfoPanel.Enable(false);
}
public static void OnUpdate()
{
if (RealtimeLogisticsInfoPanelEnabled.Value)
{
RealtimeLogisticsInfoPanel.StationInfoPanelsUpdate();
}
}
public static void OnInputUpdate()
{
if (VFInput.onGUI && LogisticsCapacityTweaksEnabled.Value)
{
LogisticsCapacityTweaks.UpdateInput();
}
}
private class AutoConfigLogistics : PatchImpl<AutoConfigLogistics>
{
enum KnownItemId : int
{
Drone = 5001,
Ship = 5002,
Bot = 5003,
Warper = 1210,
}
protected override void OnEnable()
{
ToggleLimitAutoReplenishCount();
}
protected override void OnDisable()
{
ToggleLimitAutoReplenishCount();
}
public static void ToggleLimitAutoReplenishCount()
{
LimitAutoReplenishCount.Enable(AutoConfigLogisticsEnabled.Value && 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(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 ? AutoConfigILSDroneCount.Value : 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(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();
}
}
private static void DoConfigStation(PlanetFactory factory, StationComponent station)
{
if (station.isCollector) return;
if (station.isVeinCollector)
{
/* station.minerId is not set at this point, so we need to fetch the minerId from the EntityData */
ref var entity = ref factory.entityPool[station.entityId];
if (entity.id != station.entityId || entity.minerId <= 0 || entity.minerId >= factory.factorySystem.minerCursor) return;
factory.factorySystem.minerPool[entity.minerId].speed = 10000 + AutoConfigVeinCollectorHarvestSpeed.Value * 1000;
station.pilerCount = AutoConfigVeinCollectorMinPilerValue.Value;
return;
}
int toFill;
if (!station.isStellar)
{
factory.powerSystem.consumerPool[station.pcId].workEnergyPerTick = (long)(50000.0 * AutoConfigPLSChargePower.Value + 0.5);
station.tripRangeDrones = Math.Cos(AutoConfigPLSMaxTripDrone.Value / 180.0 * Math.PI);
station.deliveryDrones = AutoConfigPLSDroneMinDeliver.Value switch { 0 => 1, _ => AutoConfigPLSDroneMinDeliver.Value * 10 };
station.pilerCount = AutoConfigPLSMinPilerValue.Value;
toFill = Math.Max(0, AutoConfigPLSDroneCount.Value - station.idleDroneCount - station.workDroneCount);
if (toFill > 0) station.idleDroneCount += GameMain.data.mainPlayer.package.TakeItem((int)KnownItemId.Drone, toFill, out _);
return;
}
factory.powerSystem.consumerPool[station.pcId].workEnergyPerTick = (long)(250000.0 * AutoConfigILSChargePower.Value + 0.5);
station.tripRangeDrones = Math.Cos(AutoConfigILSMaxTripDrone.Value / 180.0 * Math.PI);
station.tripRangeShips = AutoConfigILSMaxTripShip.Value switch
{
<= 20 => AutoConfigILSMaxTripShip.Value,
<= 40 => AutoConfigILSMaxTripShip.Value * 2 - 20,
_ => 10000,
} * 2400000.0;
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;
station.deliveryDrones = AutoConfigILSDroneMinDeliver.Value switch { 0 => 1, _ => AutoConfigILSDroneMinDeliver.Value * 10 };
station.deliveryShips = AutoConfigILSShipMinDeliver.Value switch { 0 => 1, _ => AutoConfigILSShipMinDeliver.Value * 10 };
station.pilerCount = AutoConfigILSMinPilerValue.Value;
station.includeOrbitCollector = AutoConfigILSIncludeOrbitCollector.Value;
station.warperNecessary = AutoConfigILSWarperNecessary.Value;
toFill = Math.Max(0, AutoConfigILSDroneCount.Value - station.idleDroneCount - station.workDroneCount);
if (toFill > 0) station.idleDroneCount += GameMain.data.mainPlayer.package.TakeItem((int)KnownItemId.Drone, toFill, out _);
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 _);
}
private static void DoConfigDispenser(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 _);
}
private static void DoConfigBattleBase(PlanetFactory factory, BattleBaseComponent battleBase)
{
factory.powerSystem.consumerPool[battleBase.pcId].workEnergyPerTick = (long)(5000.0 * AutoConfigBattleBaseChargePower.Value + 0.5);
}
[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 * AutoConfigDispenserChargePower.Value + 0.5);
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(PlanetTransport), nameof(PlanetTransport.NewStationComponent))]
private static void PlanetTransport_NewStationComponent_Postfix(PlanetTransport __instance, StationComponent __result)
{
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;
DoConfigBattleBase(__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;
DoConfigDispenser(__instance.factory, __instance.dispenserPool[__result]);
}
}
public class LogisticsCapacityTweaks : PatchImpl<LogisticsCapacityTweaks>
{
private static KeyCode _lastKey = KeyCode.None;
private static long _nextKeyTick;
private static bool _skipNextEvent;
private static bool UpdateKeyPressed(KeyCode code)
{
if (!Input.GetKey(code))
return false;
if (code != _lastKey)
{
_lastKey = code;
_nextKeyTick = GameMain.instance.timei + 30;
return true;
}
var currTick = GameMain.instance.timei;
if (_nextKeyTick > currTick) return false;
_nextKeyTick = currTick + 4;
return true;
}
public 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)
{
var stationStorage = target.gameObject.GetComponentInParent<UIStationStorage>();
var station = stationStorage?.station;
if (station?.storage is null) continue;
ref var storage = ref station.storage[stationStorage.index];
var oldMax = storage.max;
var newMax = oldMax + delta;
if (newMax < 0)
{
newMax = 0;
}
else
{
int itemCountMax;
if (AllowOverflowInLogisticsEnabled.Value)
{
itemCountMax = 90000000;
}
else
{
var factory = stationStorage.stationWindow?.factory;
if (factory == null || station.entityId <= 0 || station.entityId >= factory.entityCursor) continue;
var modelProto = LDB.models.Select(factory.entityPool[station.entityId].modelIndex);
itemCountMax = modelProto == null ? 0 : modelProto.prefabDesc.stationMaxItemCount;
itemCountMax += station.isStellar ? GameMain.history.remoteStationExtraStorage : GameMain.history.localStationExtraStorage;
}
if (newMax > itemCountMax)
{
newMax = itemCountMax;
}
}
storage.max = newMax;
_skipNextEvent = oldMax / 100 != newMax / 100;
break;
}
}
[HarmonyPrefix]
[HarmonyPatch(typeof(UIStationStorage), nameof(UIStationStorage.OnMaxSliderValueChange))]
private static bool UIStationStorage_OnMaxSliderValueChange_Prefix()
{
if (!_skipNextEvent) return true;
_skipNextEvent = 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;
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;
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;
}
}
private 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;
}
}
private class LogisticsConstrolPanelImprovement : PatchImpl<LogisticsConstrolPanelImprovement>
{
private static int ItemIdHintUnderMouse()
{
var itemId = GameMain.data.mainPlayer.inhandItemId;
if (itemId > 0) return itemId;
List<RaycastResult> targets = [];
var pointer = new PointerEventData(EventSystem.current)
{
position = Input.mousePosition
};
EventSystem.current.RaycastAll(pointer, targets);
foreach (var target in targets)
{
var btn = target.gameObject.GetComponentInParent<UIButton>();
if (btn?.tips is { itemId: > 0 })
{
return btn.tips.itemId;
}
var repWin = target.gameObject.GetComponentInParent<UIReplicatorWindow>();
if (repWin != null)
{
var mouseRecipeIndex = repWin.mouseRecipeIndex;
var recipeProtoArray = repWin.recipeProtoArray;
if (mouseRecipeIndex < 0)
{
return 0;
}
var recipeProto = recipeProtoArray[mouseRecipeIndex];
return recipeProto != null ? recipeProto.Results[0] : 0;
}
var grid = target.gameObject.GetComponentInParent<UIStorageGrid>();
if (grid != null)
{
var storage = grid.storage;
if (storage == null) return 0;
var mouseOnX = grid.mouseOnX;
var mouseOnY = grid.mouseOnY;
if (mouseOnX < 0 || mouseOnY < 0) return 0;
var gridIndex = mouseOnX + mouseOnY * grid.colCount;
return storage.grids[gridIndex].itemId;
}
var productEntry = target.gameObject.GetComponentInParent<UIProductEntry>();
if (productEntry == null) continue;
if (!productEntry.productionStatWindow.isProductionTab) return 0;
return productEntry.entryData?.itemId ?? 0;
}
return 0;
}
private static bool SetFilterItemId(UIControlPanelFilterPanel filterPanel, int itemId)
{
var filter = filterPanel.GetCurrentFilter();
if (filter.itemsFilter is { Length: 1 } && filter.itemsFilter[0] == itemId) return false;
filter.itemsFilter = [itemId];
filterPanel.SetNewFilter(filter);
return true;
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(UIGame), nameof(UIGame.On_I_Switch))]
private static IEnumerable<CodeInstruction> UIGame_On_I_Switch_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.End().MatchBack(false,
new CodeMatch(OpCodes.Ldarg_0),
new CodeMatch(OpCodes.Call, AccessTools.Method(typeof(UIGame), nameof(UIGame.ShutAllFunctionWindow)))
);
if (matcher.IsInvalid)
{
UXAssist.Logger.LogWarning("Failed to patch UIGame.On_I_Switch()");
return matcher.InstructionEnumeration();
}
var labels = matcher.Labels;
matcher.Labels = null;
matcher.Insert(
new CodeInstruction(OpCodes.Ldarg_0).WithLabels(labels),
Transpilers.EmitDelegate((UIGame uiGame) =>
{
var itemId = ItemIdHintUnderMouse();
if (itemId <= 0) return;
SetFilterItemId(uiGame.controlPanelWindow.filterPanel, itemId);
})
);
return matcher.InstructionEnumeration();
}
private static void OnStationEntryItemIconRightClick(UIControlPanelStationEntry stationEntry, int slot)
{
var storage = stationEntry.station?.storage;
if (storage == null) return;
var itemId = storage.Length > slot ? storage[slot].itemId : 0;
var uiRoot = UIRoot.instance;
if (!uiRoot) return;
var controlPanelWindow = uiRoot.uiGame?.controlPanelWindow;
if (controlPanelWindow == null) return;
var filterPanel = controlPanelWindow.filterPanel;
if (filterPanel == null) return;
if (!SetFilterItemId(filterPanel, itemId)) return;
filterPanel.RefreshFilterUI();
controlPanelWindow.DetermineFilterResults();
}
private static readonly Action<int>[] OnStationEntryItemIconRightClickActions = new Action<int>[5];
[HarmonyPostfix]
[HarmonyPatch(typeof(UIControlPanelStationEntry), nameof(UIControlPanelStationEntry._OnRegEvent))]
private static void UIControlPanelStationEntry__OnRegEvent_Postfix(UIControlPanelStationEntry __instance)
{
OnStationEntryItemIconRightClickActions[0] = _ => OnStationEntryItemIconRightClick(__instance, 0);
OnStationEntryItemIconRightClickActions[1] = _ => OnStationEntryItemIconRightClick(__instance, 1);
OnStationEntryItemIconRightClickActions[2] = _ => OnStationEntryItemIconRightClick(__instance, 2);
OnStationEntryItemIconRightClickActions[3] = _ => OnStationEntryItemIconRightClick(__instance, 3);
OnStationEntryItemIconRightClickActions[4] = _ => OnStationEntryItemIconRightClick(__instance, 4);
__instance.storageItem0.itemButton.onRightClick += OnStationEntryItemIconRightClickActions[0];
__instance.storageItem1.itemButton.onRightClick += OnStationEntryItemIconRightClickActions[1];
__instance.storageItem2.itemButton.onRightClick += OnStationEntryItemIconRightClickActions[2];
__instance.storageItem3.itemButton.onRightClick += OnStationEntryItemIconRightClickActions[3];
__instance.storageItem4.itemButton.onRightClick += OnStationEntryItemIconRightClickActions[4];
}
[HarmonyPostfix]
[HarmonyPatch(typeof(UIControlPanelStationEntry), nameof(UIControlPanelStationEntry._OnUnregEvent))]
private static void UIControlPanelStationEntry__OnUnregEvent_Postfix(UIControlPanelStationEntry __instance)
{
__instance.storageItem0.itemButton.onRightClick -= OnStationEntryItemIconRightClickActions[0];
__instance.storageItem1.itemButton.onRightClick -= OnStationEntryItemIconRightClickActions[1];
__instance.storageItem2.itemButton.onRightClick -= OnStationEntryItemIconRightClickActions[2];
__instance.storageItem3.itemButton.onRightClick -= OnStationEntryItemIconRightClickActions[3];
__instance.storageItem4.itemButton.onRightClick -= OnStationEntryItemIconRightClickActions[4];
for (var i = 0; i < 5; i++)
{
OnStationEntryItemIconRightClickActions[i] = null;
}
}
}
private static class RealtimeLogisticsInfoPanel
{
private static StationTip[] _stationTips = new StationTip[16];
private static readonly StationTip[] StationTipsRecycle = new StationTip[128];
private static int _stationTipsRecycleCount;
private static GameObject _stationTipsRoot;
private static GameObject _tipPrefab;
private static readonly Color DemandColor = new(253f / 255, 150f / 255, 94f / 255);
private static readonly Color SupplyColor = new(97f / 255, 216f / 255, 255f / 255);
private static readonly Color OrderInColor = new(108f / 255, 187f / 255, 214f / 255);
private static readonly Color OrderOutColor = new(255f / 255, 161f / 255, 109.5f / 255);
private static PlanetData _lastPlanet;
private static int _localStorageMax = 5000;
private static int _remoteStorageMax = 10000;
private static int _localStorageExtra;
private static int _remoteStorageExtra;
private static int _localStorageMaxTotal = _localStorageMax;
private static int _remoteStorageMaxTotal = _remoteStorageMax;
private static float _localStoragePixelPerItem = StorageSliderWidth / _localStorageMaxTotal;
private static float _remoteStoragePixelPerItem = StorageSliderWidth / _remoteStorageMaxTotal;
private static int _storageMaxSlotCount = 5;
private const int CarrierSlotCount = 3;
private const float StorageSliderWidth = 70f;
private const float StorageSliderHeight = 5f;
private static bool UpdateStorageMax()
{
var history = GameMain.history;
if (history == null) return false;
if (_remoteStorageExtra == history.remoteStationExtraStorage) return false;
_localStorageExtra = history.localStationExtraStorage;
_remoteStorageExtra = history.remoteStationExtraStorage;
_localStorageMaxTotal = _localStorageMax + _localStorageExtra;
_remoteStorageMaxTotal = _remoteStorageMax + _remoteStorageExtra;
_localStoragePixelPerItem = StorageSliderWidth / _localStorageMaxTotal;
_remoteStoragePixelPerItem = StorageSliderWidth / _remoteStorageMaxTotal;
return true;
}
private static readonly Sprite[] LogisticsExtraItemSprites =
[
Resources.Load<Sprite>("Icons/ItemRecipe/logistic-drone"),
Resources.Load<Sprite>("Icons/ItemRecipe/logistic-vessel"),
Resources.Load<Sprite>("Icons/ItemRecipe/space-warper")
];
public static void Enable(bool on)
{
if (_stationTipsRoot)
_stationTipsRoot.SetActive(on);
}
public static void EnableBars(bool on)
{
foreach (var stationTip in _stationTips)
{
if (stationTip == null) continue;
stationTip.SetBarsVisible(on);
}
}
public static void OnGameBegin()
{
_lastPlanet = null;
}
public static void OnDataLoaded()
{
_storageMaxSlotCount = 5;
_localStorageMax = 5000;
_remoteStorageMax = 10000;
foreach (var model in LDB.models.dataArray)
{
var prefabDesc = model?.prefabDesc;
if (prefabDesc == null) continue;
if (prefabDesc.isStation)
{
_storageMaxSlotCount = Math.Max(_storageMaxSlotCount, prefabDesc.stationMaxItemKinds);
if (prefabDesc.isStellarStation)
{
if (!prefabDesc.isCollectStation)
{
_remoteStorageMax = prefabDesc.stationMaxItemCount;
}
}
else
{
if (!prefabDesc.isVeinCollector)
{
_localStorageMax = prefabDesc.stationMaxItemCount;
}
}
}
}
_localStorageExtra = -1;
_remoteStorageExtra = -1;
_localStoragePixelPerItem = 0f;
_remoteStoragePixelPerItem = 0f;
UpdateStorageMax();
RealtimeLogisticsInfoPanel.InitGUI();
}
public static void InitGUI()
{
if (_stationTipsRoot != null) return;
_stationTipsRoot = new GameObject("station-tips-root");
var rtrans = _stationTipsRoot.AddComponent<RectTransform>();
_stationTipsRoot.transform.SetParent(GameObject.Find("UI Root/Overlay Canvas/In Game/Scene UIs").transform);
rtrans.sizeDelta = new Vector2(0f, 0f);
rtrans.localScale = new Vector3(1f, 1f, 1f);
rtrans.anchorMax = new Vector2(1f, 1f);
rtrans.anchorMin = new Vector2(0f, 0f);
rtrans.pivot = new Vector2(0f, 0f);
rtrans.anchoredPosition3D = new Vector3(0, 0, 0f);
var sliderBgPrefab = GameObject.Find("UI Root/Overlay Canvas/In Game/Windows/Station Window/storage-box-0/slider-bg");
_tipPrefab = Object.Instantiate(GameObject.Find("UI Root/Overlay Canvas/In Game/Scene UIs/Vein Marks/vein-tip-prefab"), _stationTipsRoot.transform);
_tipPrefab.name = "tipPrefab";
Object.Destroy(_tipPrefab.GetComponent<UIVeinDetailNode>());
var image = _tipPrefab.GetComponent<Image>();
image.sprite = GameObject.Find("UI Root/Overlay Canvas/In Game/Windows/Key Tips/tip-prefab").GetComponent<Image>().sprite;
image.color = new Color(0, 0, 0, 0.8f);
image.enabled = true;
var rectTrans = (RectTransform)_tipPrefab.transform;
rectTrans.localPosition = new Vector3(200f, 800f, 0);
rectTrans.sizeDelta = new Vector2(150f, 160f);
rectTrans.pivot = new Vector2(0.5f, 0.5f);
var infoText = _tipPrefab.transform.Find("info-text").gameObject;
var tipIconPrefab = _tipPrefab.transform.Find("icon");
for (var index = 0; index < _storageMaxSlotCount; ++index)
{
var y = -5f - 35f * index;
var itemIcon = Object.Instantiate(tipIconPrefab.gameObject, new Vector3(0, 0, 0), Quaternion.identity, _tipPrefab.transform);
itemIcon.name = "icon" + index;
rectTrans = (RectTransform)itemIcon.transform;
rectTrans.sizeDelta = new Vector2(30f, 30f);
rectTrans.anchorMax = new Vector2(0f, 1f);
rectTrans.anchorMin = new Vector2(0f, 1f);
rectTrans.pivot = new Vector2(0f, 1f);
rectTrans.anchoredPosition3D = new Vector3(0, y, 0);
var sliderBg = Object.Instantiate(sliderBgPrefab.gameObject, new Vector3(0, 0, 0), Quaternion.identity, _tipPrefab.transform);
sliderBg.name = "sliderBg" + index;
rectTrans = (RectTransform)sliderBg.transform;
rectTrans.sizeDelta = new Vector2(StorageSliderWidth, StorageSliderHeight);
rectTrans.anchorMax = new Vector2(0f, 1f);
rectTrans.anchorMin = new Vector2(0f, 1f);
rectTrans.pivot = new Vector2(0f, 1f);
rectTrans.anchoredPosition3D = new Vector3(30f, y - 22f, 0f);
rectTrans = (RectTransform)sliderBg.transform.Find("current-fg").transform;
rectTrans.sizeDelta = new Vector2(0f, StorageSliderHeight);
rectTrans.anchorMax = new Vector2(0f, 1f);
rectTrans.anchorMin = new Vector2(0f, 1f);
rectTrans.pivot = new Vector2(0f, 1f);
rectTrans.localPosition = new Vector3(0f, 0f, 0f);
rectTrans = (RectTransform)sliderBg.transform.Find("ordered-fg").transform;
rectTrans.sizeDelta = new Vector2(0f, StorageSliderHeight);
rectTrans.anchorMax = new Vector2(0f, 1f);
rectTrans.anchorMin = new Vector2(0f, 1f);
rectTrans.pivot = new Vector2(0f, 1f);
rectTrans.localPosition = new Vector3(0f, 0f, 0f);
rectTrans = (RectTransform)sliderBg.transform.Find("max-fg").transform;
rectTrans.sizeDelta = new Vector2(StorageSliderWidth, StorageSliderHeight);
rectTrans.anchorMax = new Vector2(0f, 1f);
rectTrans.anchorMin = new Vector2(0f, 1f);
rectTrans.pivot = new Vector2(0f, 1f);
rectTrans.localPosition = new Vector3(0f, 0f, 0f);
image = rectTrans.GetComponent<Image>();
image.color = new Color(image.color.r, image.color.g, image.color.b, 0.15f);
Object.Destroy(sliderBg.GetComponent<Slider>());
Object.Destroy(sliderBg.transform.Find("thumb").gameObject);
Object.Destroy(sliderBg.transform.Find("speed-text").gameObject);
sliderBg.gameObject.SetActive(false);
var countText = Object.Instantiate(infoText, Vector3.zero, Quaternion.identity, _tipPrefab.transform);
countText.name = "countText" + index;
var text = countText.GetComponent<Text>();
text.fontSize = 18;
text.text = "";
text.alignment = TextAnchor.UpperRight;
rectTrans = (RectTransform)countText.transform;
rectTrans.sizeDelta = new Vector2(70f, 20f);
rectTrans.anchorMax = new Vector2(0f, 1f);
rectTrans.anchorMin = new Vector2(0f, 1f);
rectTrans.pivot = new Vector2(0f, 1f);
rectTrans.anchoredPosition3D = new Vector3(30f, y, 0);
Object.Destroy(countText.GetComponent<Shadow>());
var stateLocal = Object.Instantiate(tipIconPrefab.gameObject, new Vector3(0, 0, 0), Quaternion.identity, _tipPrefab.transform);
stateLocal.name = "iconLocal" + index;
stateLocal.GetComponent<Image>().material = null;
rectTrans = (RectTransform)stateLocal.transform;
rectTrans.sizeDelta = new Vector2(16f, 16f);
rectTrans.anchorMax = new Vector2(0f, 1f);
rectTrans.anchorMin = new Vector2(0f, 1f);
rectTrans.pivot = new Vector2(0f, 1f);
rectTrans.anchoredPosition3D = new Vector3(102f, y, 0);
var stateRemote = Object.Instantiate(tipIconPrefab.gameObject, new Vector3(0, 0, 0), Quaternion.identity, _tipPrefab.transform);
stateRemote.name = "iconRemote" + index;
stateRemote.GetComponent<Image>().material = null;
rectTrans = (RectTransform)stateRemote.transform;
rectTrans.sizeDelta = new Vector2(20f, 20f);
rectTrans.anchorMax = new Vector2(0f, 1f);
rectTrans.anchorMin = new Vector2(0f, 1f);
rectTrans.pivot = new Vector2(0f, 1f);
rectTrans.anchoredPosition3D = new Vector3(100f, y - 12f, 0);
}
var iconPrefab = Object.Instantiate(GameObject.Find("UI Root/Overlay Canvas/In Game/Top Tips/Entity Briefs/brief-info-top/brief-info/content/icons/icon"), _tipPrefab.transform);
Object.Destroy(iconPrefab.transform.Find("count-text").gameObject);
Object.Destroy(iconPrefab.transform.Find("bg").gameObject);
Object.Destroy(iconPrefab.transform.Find("inc").gameObject);
Object.Destroy(iconPrefab.GetComponent<UIIconCountInc>());
iconPrefab.SetActive(false);
for (var i = 0; i < CarrierSlotCount; i++)
{
var iconObj = Object.Instantiate(iconPrefab, new Vector3(0, 0, 0), Quaternion.identity, _tipPrefab.transform);
iconObj.name = "carrierIcon" + i;
iconObj.GetComponent<Image>().sprite = LogisticsExtraItemSprites[i];
rectTrans = (RectTransform)iconObj.transform;
rectTrans.localScale = new Vector3(0.7f, 0.7f, 1f);
rectTrans.anchorMax = new Vector2(0f, 1f);
rectTrans.anchorMin = new Vector2(0f, 1f);
rectTrans.pivot = new Vector2(0f, 1f);
rectTrans.anchoredPosition3D = new Vector3(0f, -180f, 0);
iconObj.SetActive(true);
var countText = Object.Instantiate(infoText, Vector3.zero, Quaternion.identity, iconObj.transform);
Object.Destroy(countText.GetComponent<Shadow>());
countText.name = "carrierTotalCountText";
var text = countText.GetComponent<Text>();
text.fontSize = 22;
text.text = "100";
text.alignment = TextAnchor.MiddleRight;
text.color = Color.white;
rectTrans = (RectTransform)countText.transform;
rectTrans.sizeDelta = new Vector2(40f, 30f);
rectTrans.anchorMax = new Vector2(0f, 1f);
rectTrans.anchorMin = new Vector2(0f, 1f);
rectTrans.pivot = new Vector2(0f, 1f);
rectTrans.localPosition = new Vector3(0f, -18f, 0f);
if (i >= CarrierSlotCount - 1) continue;
countText = Object.Instantiate(infoText, Vector3.zero, Quaternion.identity, iconObj.transform);
Object.Destroy(countText.GetComponent<Shadow>());
countText.name = "carrierIdleCountText";
text = countText.GetComponent<Text>();
text.fontSize = 22;
text.text = "100";
text.alignment = TextAnchor.MiddleRight;
text.color = Color.white;
rectTrans = (RectTransform)countText.transform;
rectTrans.sizeDelta = new Vector2(40f, 30f);
rectTrans.anchorMax = new Vector2(0f, 1f);
rectTrans.anchorMin = new Vector2(0f, 1f);
rectTrans.pivot = new Vector2(0f, 1f);
rectTrans.localPosition = new Vector3(0f, 8f, 0f);
}
tipIconPrefab.gameObject.SetActive(false);
_tipPrefab.SetActive(false);
}
private static void RecycleStationTips()
{
foreach (var stationTip in _stationTips)
{
if (!stationTip) continue;
if (_stationTipsRecycleCount < 128)
{
stationTip.ResetStationTip();
stationTip.gameObject.SetActive(false);
StationTipsRecycle[_stationTipsRecycleCount++] = stationTip;
}
else
{
Object.Destroy(stationTip);
}
}
Array.Clear(_stationTips, 0, _stationTips.Length);
}
private static StationTip AllocateStationTip()
{
if (_stationTipsRecycleCount > 0)
{
var result = StationTipsRecycle[--_stationTipsRecycleCount];
StationTipsRecycle[_stationTipsRecycleCount] = null;
return result;
}
var tempTip = Object.Instantiate(_tipPrefab, _stationTipsRoot.transform);
var stationTip = tempTip.AddComponent<StationTip>();
stationTip.InitStationTip();
return stationTip;
}
public static void StationInfoPanelsUpdate()
{
if (DSPGame.IsMenuDemo || !GameMain.isRunning) return;
var localPlanet = GameMain.localPlanet;
if (localPlanet == null)
{
if (_lastPlanet == null) return;
_lastPlanet = null;
_stationTipsRoot.SetActive(false);
return;
}
if (_lastPlanet != localPlanet)
{
RecycleStationTips();
_lastPlanet = localPlanet;
}
if (UpdateStorageMax())
{
foreach (var tip in _stationTips)
{
tip?.ResetStorageSlider();
}
}
var factory = localPlanet.factory;
var transport = factory?.transport;
if (transport is not { stationCursor: > 1 } || (UIGame.viewMode != EViewMode.Normal && UIGame.viewMode != EViewMode.Globe))
{
if (_stationTipsRoot.activeSelf)
{
_stationTipsRoot.SetActive(false);
}
return;
}
_stationTipsRoot.SetActive(true);
var localPosition = GameCamera.main.transform.localPosition;
var forward = GameCamera.main.transform.forward;
var realRadius = localPlanet.realRadius;
var stationCount = transport.stationCursor;
if (stationCount >= _stationTips.Length)
{
var newSize = stationCount;
newSize |= newSize >> 1;
newSize |= newSize >> 2;
newSize |= newSize >> 4;
newSize |= newSize >> 8;
newSize |= newSize >> 16;
newSize++;
Array.Resize(ref _stationTips, newSize);
}
for (var i = stationCount - 1; i > 0; i--)
{
var stationComponent = transport.stationPool[i];
var storageArray = stationComponent?.storage;
if (storageArray == null)
{
_stationTips[i]?.gameObject.SetActive(false);
continue;
}
#if DEBUG
if (i != stationComponent.id)
{
UXAssist.Logger.LogWarning($"Station index mismatch: {i} != {stationComponent.id}");
_stationTips[i]?.gameObject.SetActive(false);
continue;
}
#endif
var position = factory.entityPool[stationComponent.entityId].pos.normalized;
var radius = realRadius;
if (stationComponent.isCollector)
{
radius += 35f;
}
else if (stationComponent.isStellar)
{
radius += 20f;
}
else if (stationComponent.isVeinCollector)
{
radius += 8f;
}
else
{
radius += 15f;
}
position *= radius;
var vec = position - localPosition;
var magnitude = vec.magnitude;
if (magnitude < 1.0
|| Vector3.Dot(forward, vec) < 1.0
|| !UIRoot.ScreenPointIntoRect(GameCamera.main.WorldToScreenPoint(position), (RectTransform)_stationTipsRoot.transform, out var rectPoint)
|| rectPoint.x is < -4096f or > 4096f
|| rectPoint.y is < -4096f or > 4096f
|| Phys.RayCastSphere(localPosition, vec / magnitude, magnitude, Vector3.zero, realRadius, out _)
|| storageArray.Select(x => x.itemId).All(x => x == 0))
{
_stationTips[i]?.gameObject.SetActive(false);
continue;
}
var stationTip = _stationTips[i];
if (!stationTip)
{
stationTip = AllocateStationTip();
_stationTips[i] = stationTip;
}
stationTip.gameObject.SetActive(true);
var localScaleMultiple = magnitude switch
{
< 50f => 1.5f,
< 250f => 1.75f - magnitude * 0.005f,
_ => 0.5f
};
/*
rectPoint.x = Mathf.Round(rectPoint.x);
rectPoint.y = Mathf.Round(rectPoint.y);
*/
stationTip.rectTransform.anchoredPosition = rectPoint;
stationTip.transform.localScale = Vector3.one * localScaleMultiple;
stationTip.UpdateStationInfo(stationComponent);
}
}
public class StationTip : MonoBehaviour
{
[FormerlySerializedAs("RectTransform")]
public RectTransform rectTransform;
private Transform[] _icons;
private Transform[] _iconLocals;
private Transform[] _iconRemotes;
private Transform[] _countTexts;
private Transform[] _sliderBg;
private Transform[] _sliderMax;
private Transform[] _sliderCurrent;
private Transform[] _sliderOrdered;
private Image[] _sliderOrderedImage;
private Image[] _iconsImage;
private Image[] _iconLocalsImage;
private Image[] _iconRemotesImage;
private Text[] _countTextsText;
private Transform[] _carrierIcons;
private Text[] _carrierTotalCountText;
private Text[] _carrierIdleCountText;
private GameObject _infoText;
private EStationTipLayout _layout = EStationTipLayout.None;
private int _storageNum;
private float _pixelPerItem;
private StorageItemData[] _storageItems;
private static readonly Dictionary<int, Sprite> ItemSprites = new();
private static readonly Color[] StateColor = [Color.gray, SupplyColor, DemandColor];
private struct StorageItemData
{
public int ItemId;
public int ItemCount;
public int ItemOrdered;
public int ItemMax;
public ELogisticStorage LocalState;
public ELogisticStorage RemoteState;
}
private static readonly Sprite[] StateSprite =
[
Util.LoadEmbeddedSprite("assets/icon/keep.png"),
Util.LoadEmbeddedSprite("assets/icon/out.png"),
Util.LoadEmbeddedSprite("assets/icon/in.png")
];
private enum EStationTipLayout
{
None,
Collector,
VeinCollector,
PlanetaryLogistics,
InterstellarLogistics
}
public void InitStationTip()
{
_storageItems = new StorageItemData[_storageMaxSlotCount];
rectTransform = (RectTransform)transform;
_icons = new Transform[_storageMaxSlotCount];
_iconLocals = new Transform[_storageMaxSlotCount];
_iconRemotes = new Transform[_storageMaxSlotCount];
_iconsImage = new Image[_storageMaxSlotCount];
_iconLocalsImage = new Image[_storageMaxSlotCount];
_iconRemotesImage = new Image[_storageMaxSlotCount];
_countTexts = new Transform[_storageMaxSlotCount];
_countTextsText = new Text[_storageMaxSlotCount];
_sliderBg = new Transform[_storageMaxSlotCount];
_sliderMax = new Transform[_storageMaxSlotCount];
_sliderCurrent = new Transform[_storageMaxSlotCount];
_sliderOrdered = new Transform[_storageMaxSlotCount];
_sliderOrderedImage = new Image[_storageMaxSlotCount];
_carrierIcons = new Transform[3];
_carrierTotalCountText = new Text[3];
_carrierIdleCountText = new Text[2];
_infoText = transform.Find("info-text").gameObject;
for (var i = CarrierSlotCount - 1; i >= 0; i--)
{
_carrierIcons[i] = transform.Find("carrierIcon" + i);
_carrierIcons[i].gameObject.SetActive(false);
_carrierTotalCountText[i] = _carrierIcons[i].Find("carrierTotalCountText").GetComponent<Text>();
if (i >= CarrierSlotCount - 1) continue;
_carrierIdleCountText[i] = _carrierIcons[i].Find("carrierIdleCountText").GetComponent<Text>();
}
for (var i = _storageMaxSlotCount - 1; i >= 0; i--)
{
_countTexts[i] = transform.Find("countText" + i);
_countTextsText[i] = _countTexts[i].GetComponent<Text>();
_sliderBg[i] = transform.Find("sliderBg" + i);
_sliderMax[i] = _sliderBg[i].Find("max-fg");
_sliderCurrent[i] = _sliderBg[i].Find("current-fg");
_sliderOrdered[i] = _sliderBg[i].Find("ordered-fg");
_sliderOrderedImage[i] = _sliderOrdered[i].GetComponent<Image>();
_icons[i] = transform.Find("icon" + i);
_iconsImage[i] = _icons[i].GetComponent<Image>();
_iconLocals[i] = transform.Find("iconLocal" + i);
_iconRemotes[i] = transform.Find("iconRemote" + i);
_iconLocalsImage[i] = _iconLocals[i].GetComponent<Image>();
_iconRemotesImage[i] = _iconRemotes[i].GetComponent<Image>();
_countTexts[i].gameObject.SetActive(false);
_sliderBg[i].gameObject.SetActive(false);
_icons[i].gameObject.SetActive(false);
_iconLocals[i].gameObject.SetActive(false);
_iconRemotes[i].gameObject.SetActive(false);
_storageItems[i] = new StorageItemData
{
ItemId = -1,
ItemCount = -1,
ItemOrdered = -1,
ItemMax = -1,
LocalState = ELogisticStorage.None,
RemoteState = ELogisticStorage.None
};
}
_infoText.SetActive(false);
}
public void ResetStationTip()
{
_layout = EStationTipLayout.None;
for (var i = _storageMaxSlotCount - 1; i >= 0; i--)
{
_countTexts[i].gameObject.SetActive(false);
_sliderBg[i].gameObject.SetActive(false);
_icons[i].gameObject.SetActive(false);
_iconLocals[i].gameObject.SetActive(false);
_iconRemotes[i].gameObject.SetActive(false);
_countTextsText[i].color = StateColor[0];
_iconLocalsImage[i].color = StateColor[0];
_iconRemotesImage[i].color = StateColor[0];
ref var storageItem = ref _storageItems[i];
storageItem.ItemId = -1;
storageItem.ItemCount = -1;
storageItem.ItemOrdered = -1;
storageItem.ItemMax = -1;
storageItem.LocalState = ELogisticStorage.None;
storageItem.RemoteState = ELogisticStorage.None;
}
for (var i = CarrierSlotCount - 1; i >= 0; i--)
{
_carrierIcons[i].gameObject.SetActive(false);
}
}
public void ResetStorageSlider()
{
for (var i = _storageMaxSlotCount - 1; i >= 0; i--)
{
ref var storageItem = ref _storageItems[i];
storageItem.ItemId = -1;
storageItem.ItemCount = -1;
storageItem.ItemOrdered = -1;
storageItem.ItemMax = -1;
}
_pixelPerItem = _layout == EStationTipLayout.InterstellarLogistics ? _remoteStoragePixelPerItem : _localStoragePixelPerItem;
}
private static Sprite GetItemSprite(int itemId)
{
if (ItemSprites.TryGetValue(itemId, out var sprite))
return sprite;
sprite = LDB.items.Select(itemId)?.iconSprite;
ItemSprites[itemId] = sprite;
return sprite;
}
public void SetItem(int i, StationStore storage, bool barEnabled)
{
ref var storageState = ref _storageItems[i];
var countUIText = _countTextsText[i];
var itemId = storage.itemId;
if (itemId != storageState.ItemId)
{
var icon = _icons[i];
storageState.ItemId = itemId;
if (itemId <= 0)
{
icon.gameObject.SetActive(false);
_iconLocals[i].gameObject.SetActive(false);
_iconRemotes[i].gameObject.SetActive(false);
_sliderBg[i].gameObject.SetActive(false);
countUIText.color = StateColor[0];
countUIText.text = "— ";
return;
}
storage.count = -1;
icon.gameObject.SetActive(true);
_iconsImage[i].sprite = GetItemSprite(itemId);
_iconLocals[i].gameObject.SetActive(CarrierEnabled[(int)_layout][0]);
_iconLocalsImage[i].sprite = StateSprite[(int)storageState.LocalState];
_iconRemotes[i].gameObject.SetActive(CarrierEnabled[(int)_layout][1]);
_iconRemotesImage[i].sprite = StateSprite[(int)storageState.RemoteState];
_sliderBg[i].gameObject.SetActive(barEnabled);
switch (_layout)
{
case EStationTipLayout.InterstellarLogistics:
{
countUIText.color = _iconRemotesImage[i].color = StateColor[(int)storageState.RemoteState];
break;
}
case EStationTipLayout.VeinCollector:
case EStationTipLayout.PlanetaryLogistics:
{
countUIText.color = _iconLocalsImage[i].color = StateColor[(int)storageState.LocalState];
break;
}
case EStationTipLayout.None:
case EStationTipLayout.Collector:
default:
break;
}
}
else if (itemId <= 0) return;
var itemCount = storage.count;
var itemLimit = _layout == EStationTipLayout.InterstellarLogistics ? _remoteStorageMaxTotal : _localStorageMaxTotal;
if (storageState.ItemCount != itemCount)
{
storageState.ItemCount = itemCount;
countUIText.text = itemCount.ToString();
if (itemCount > itemLimit) itemCount = itemLimit;
if (barEnabled)
{
if (itemCount == 0)
{
_sliderCurrent[i].gameObject.SetActive(false);
}
else
{
((RectTransform)_sliderCurrent[i].transform).sizeDelta = new Vector2(
_pixelPerItem * itemCount,
StorageSliderHeight
);
_sliderCurrent[i].gameObject.SetActive(true);
}
}
}
if (barEnabled)
{
var itemOrdered = storage.totalOrdered;
if (storageState.ItemOrdered != itemOrdered)
{
storageState.ItemOrdered = itemOrdered;
switch (itemOrdered)
{
case > 0:
if (itemOrdered + itemCount > itemLimit) itemOrdered = itemLimit - itemCount;
_sliderOrderedImage[i].color = OrderInColor;
var rectTrans = (RectTransform)_sliderOrdered[i].transform;
rectTrans.localPosition = new Vector3(
_pixelPerItem * itemCount,
0f, 0f
);
rectTrans.sizeDelta = new Vector2(
_pixelPerItem * itemOrdered + 0.49f,
StorageSliderHeight
);
_sliderOrdered[i].gameObject.SetActive(true);
break;
case < 0:
if (itemOrdered + itemCount < 0) itemOrdered = -itemCount;
_sliderOrderedImage[i].color = OrderOutColor;
rectTrans = (RectTransform)_sliderOrdered[i].transform;
rectTrans.localPosition = new Vector3(
_pixelPerItem * (itemCount + itemOrdered),
0f, 0f
);
rectTrans.sizeDelta = new Vector2(
_pixelPerItem * -itemOrdered + 0.49f,
StorageSliderHeight
);
break;
}
_sliderOrdered[i].gameObject.SetActive(itemOrdered != 0);
}
var itemMax = storage.max;
if (storageState.ItemMax != itemMax)
{
storageState.ItemMax = itemMax;
_sliderBg[i].gameObject.SetActive(itemMax > 0);
((RectTransform)_sliderMax[i].transform).sizeDelta = new Vector2(
_pixelPerItem * itemMax,
StorageSliderHeight
);
}
}
switch (_layout)
{
case EStationTipLayout.InterstellarLogistics:
{
var localLogic = storage.localLogic;
if (storageState.LocalState != localLogic)
{
storageState.LocalState = localLogic;
var iconLocalImage = _iconLocalsImage[i];
iconLocalImage.sprite = StateSprite[(int)localLogic];
iconLocalImage.color = StateColor[(int)localLogic];
}
var remoteLogic = storage.remoteLogic;
if (storageState.RemoteState != remoteLogic)
{
storageState.RemoteState = remoteLogic;
var iconRemoteImage = _iconRemotesImage[i];
iconRemoteImage.sprite = StateSprite[(int)remoteLogic];
countUIText.color = iconRemoteImage.color = StateColor[(int)remoteLogic];
}
break;
}
case EStationTipLayout.VeinCollector:
case EStationTipLayout.PlanetaryLogistics:
{
var localLogic = storage.localLogic;
if (storageState.LocalState != localLogic)
{
storageState.LocalState = localLogic;
var iconLocalImage = _iconLocalsImage[i];
iconLocalImage.sprite = StateSprite[(int)localLogic];
countUIText.color = iconLocalImage.color = StateColor[(int)localLogic];
}
break;
}
case EStationTipLayout.None:
case EStationTipLayout.Collector:
default:
break;
}
}
private static readonly bool[][] CarrierEnabled = [
[false, false, false],
[false, false, false],
[false, false, false],
[true, false, false],
[true, true, true],
];
private static readonly float[] TipWindowWidths = [0f, 100f, 120f, 120f, 120f];
private static readonly float[] TipWindowExtraHeights = [0f, 5f, 5f, 40f, 40f];
private static readonly float[] CarrierPositionX = [5f, 35f, 85f];
public void UpdateStationInfo(StationComponent stationComponent)
{
var layout = stationComponent.isCollector ? EStationTipLayout.Collector :
stationComponent.isVeinCollector ? EStationTipLayout.VeinCollector :
stationComponent.isStellar ? EStationTipLayout.InterstellarLogistics : EStationTipLayout.PlanetaryLogistics;
if (_layout != layout)
{
_layout = layout;
for (var i = _storageMaxSlotCount - 1; i >= 0; i--)
{
_iconLocals[i].gameObject.SetActive(false);
_iconRemotes[i].gameObject.SetActive(false);
_icons[i].gameObject.SetActive(false);
switch (layout)
{
case EStationTipLayout.PlanetaryLogistics:
var rectTrans = (RectTransform)_iconLocals[i].transform;
rectTrans.sizeDelta = new Vector2(20f, 20f);
rectTrans.anchoredPosition3D = new Vector3(100f, -5f - 35f * i - 5f, 0);
break;
case EStationTipLayout.InterstellarLogistics:
rectTrans = (RectTransform)_iconLocals[i].transform;
rectTrans.sizeDelta = new Vector2(16f, 16f);
rectTrans.anchoredPosition3D = new Vector3(102f, -5f - 35f * i, 0);
break;
}
}
for (var i = _storageNum; i < _storageMaxSlotCount; i++)
{
_iconLocals[i].gameObject.SetActive(false);
_iconRemotes[i].gameObject.SetActive(false);
_icons[i].gameObject.SetActive(false);
_countTexts[i].gameObject.SetActive(false);
_sliderBg[i].gameObject.SetActive(false);
}
_storageNum = Math.Min(_storageMaxSlotCount, stationComponent.storage.Length);
rectTransform.sizeDelta = new Vector2(TipWindowWidths[(int)layout], TipWindowExtraHeights[(int)layout] + 35f * _storageNum);
for (var i = _storageMaxSlotCount - 1; i >= 0; i--)
{
_countTexts[i].gameObject.SetActive(i < _storageNum);
}
for (var i = CarrierSlotCount - 1; i >= 0; i--)
{
var active = CarrierEnabled[(int)layout][i];
_carrierIcons[i].gameObject.SetActive(active);
if (!active) continue;
var rectTrans = (RectTransform)_carrierIcons[i].transform;
rectTrans.anchoredPosition3D = new Vector3(CarrierPositionX[i], -5f - 35f * _storageNum, 0);
}
_pixelPerItem = _layout == EStationTipLayout.InterstellarLogistics ? _remoteStoragePixelPerItem : _localStoragePixelPerItem;
}
if (_storageNum > 0)
{
var storageArray = stationComponent.storage;
var barEnabled = RealtimeLogisticsInfoPanelBarsEnabled.Value;
for (var j = _storageNum - 1; j >= 0; j--)
{
var storage = storageArray[j];
SetItem(j, storage, barEnabled);
}
}
switch (_layout)
{
case EStationTipLayout.PlanetaryLogistics:
var totalCount = stationComponent.idleDroneCount + stationComponent.workDroneCount;
var currentCount = stationComponent.idleDroneCount;
_carrierIdleCountText[0].text = currentCount.ToString();
_carrierTotalCountText[0].text = totalCount.ToString();
break;
case EStationTipLayout.InterstellarLogistics:
totalCount = stationComponent.idleDroneCount + stationComponent.workDroneCount;
currentCount = stationComponent.idleDroneCount;
_carrierIdleCountText[0].text = currentCount.ToString();
_carrierTotalCountText[0].text = totalCount.ToString();
totalCount = stationComponent.idleShipCount + stationComponent.workShipCount;
currentCount = stationComponent.idleShipCount;
_carrierIdleCountText[1].text = currentCount.ToString();
_carrierTotalCountText[1].text = totalCount.ToString();
currentCount = stationComponent.warperCount;
_carrierTotalCountText[2].text = currentCount.ToString();
break;
case EStationTipLayout.None:
case EStationTipLayout.Collector:
case EStationTipLayout.VeinCollector:
default:
break;
}
}
public void SetBarsVisible(bool on)
{
for (var i = _storageNum - 1; i >= 0; i--)
{
_sliderBg[i].gameObject.SetActive(on && _storageItems[i].ItemId > 0);
}
for (var i = _storageNum; i < _storageMaxSlotCount; i++)
{
_sliderBg[i].gameObject.SetActive(false);
}
}
}
}
}