diff --git a/AGENTS.md b/AGENTS.md index 4ce3d3e..8df13fa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,7 +51,7 @@ DSP_Mods/ | Mod | GUID | Description | |-----|------|-------------| -| **UXAssist** | `org.soardev.uxassist` | Core QoL mod and shared library. Window resize, profile-based saves, FPS control, factory/logistics/navigation/Dyson Sphere tweaks, UI improvements, config panel UI, and `Common/` + `UI/` widget library shared by other mods. | +| **UXAssist** | `org.soardev.uxassist` | Core QoL mod and shared library. Window resize, profile-based saves, FPS control, factory/logistics/navigation/Dyson Sphere tweaks, UI improvements, config panel UI, and `Common/` + `UI/` widget library shared by other mods. The Logistics tab can also push auto-config values to all existing facilities of a type on the current planet (per-setting `Apply` and per-category `Apply All` buttons; logic in `LogisticsPatch.Apply*`/`ForEach*`, wired in `UIConfigWindow`). | | **CheatEnabler** | `org.soardev.cheatenabler` | Cheat pack (depends on UXAssist). Instant build, architect mode, infinite resources, power boosts, Dyson Sphere cheats, mecha invincibility, and more. | | **LogisticMiner** | — | Makes logistic stations automatically mine ores and water from the current planet. | | **HideTips** | — | Suppresses all tutorial popups, random tips, achievement/milestone cards, and skips the prologue cutscene. | diff --git a/UXAssist/Patches/LogisticsPatch.cs b/UXAssist/Patches/LogisticsPatch.cs index 69d34dd..f2d6185 100644 --- a/UXAssist/Patches/LogisticsPatch.cs +++ b/UXAssist/Patches/LogisticsPatch.cs @@ -109,6 +109,241 @@ public static class LogisticsPatch } } + #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) === + + private 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); + + private static void StationSetTripRangeDrones(PlanetFactory factory, StationComponent station) => + station.tripRangeDrones = Math.Cos((station.isStellar ? AutoConfigILSMaxTripDrone.Value : AutoConfigPLSMaxTripDrone.Value) / 180.0 * Math.PI); + + private static void StationSetDeliveryDrones(PlanetFactory factory, StationComponent station) + { + var v = station.isStellar ? AutoConfigILSDroneMinDeliver.Value : AutoConfigPLSDroneMinDeliver.Value; + station.deliveryDrones = v == 0 ? 1 : v * 10; + } + + private static void StationSetPilerCount(PlanetFactory factory, StationComponent station) => + station.pilerCount = station.isStellar ? AutoConfigILSMinPilerValue.Value : AutoConfigPLSMinPilerValue.Value; + + private 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 _); + } + + private static void StationSetTripRangeShips(PlanetFactory factory, StationComponent station) => + station.tripRangeShips = AutoConfigILSMaxTripShip.Value switch + { + <= 20 => AutoConfigILSMaxTripShip.Value, + <= 40 => AutoConfigILSMaxTripShip.Value * 2 - 20, + _ => 10000, + } * 2400000.0; + + private 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; + + private static void StationSetDeliveryShips(PlanetFactory factory, StationComponent station) + { + var v = AutoConfigILSShipMinDeliver.Value; + station.deliveryShips = v == 0 ? 1 : v * 10; + } + + private 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 _); + } + + private static void StationSetIncludeOrbitCollector(PlanetFactory factory, StationComponent station) => + station.includeOrbitCollector = AutoConfigILSIncludeOrbitCollector.Value; + + private 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. */ + private 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; + } + + private static void VeinCollectorSetPilerCount(PlanetFactory factory, StationComponent station) => + station.pilerCount = AutoConfigVeinCollectorMinPilerValue.Value; + + private static void DispenserSetChargePower(PlanetFactory factory, DispenserComponent dispenser) => + factory.powerSystem.consumerPool[dispenser.pcId].workEnergyPerTick = (long)(5000.0 * AutoConfigDispenserChargePower.Value + 0.5); + + private 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 _); + } + + private 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) === + + private 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 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 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 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. + private 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 + private class AutoConfigLogistics : PatchImpl { protected override void OnEnable() @@ -180,66 +415,6 @@ public static class LogisticsPatch } } - 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) @@ -262,7 +437,7 @@ public static class LogisticsPatch private static void DefenseSystem_NewBattleBaseComponent_Postfix(DefenseSystem __instance, int __result) { if (__result <= 0) return; - DoConfigBattleBase(__instance.factory, __instance.battleBases[__result]); + BattleBaseSetChargePower(__instance.factory, __instance.battleBases[__result]); } [HarmonyPostfix] @@ -270,7 +445,7 @@ public static class LogisticsPatch private static void PlanetTransport_NewDispenserComponent_Postfix(PlanetTransport __instance, int __result) { if (__result <= 0) return; - DoConfigDispenser(__instance.factory, __instance.dispenserPool[__result]); + DispenserFillCouriers(__instance.factory, __instance.dispenserPool[__result]); } } diff --git a/UXAssist/README.md b/UXAssist/README.md index 160f1a3..8f5ecfe 100644 --- a/UXAssist/README.md +++ b/UXAssist/README.md @@ -104,6 +104,9 @@ * Real-time logistic stations info panel * Auto-config logistic stations * Auto-config buildings include: Logistics Distributor, PLS, ILS, Advanced Mining Machine + * Apply settings to all existing facilities on the current planet + * Each setting on the Logistics tab has an `Apply` button that pushes that single value to all facilities of that type on the current planet. + * Each category header (Logistics Distributor, Battlefield Analysis Base, PLS, ILS, Advanced Mining Machine) has an `Apply All` button that pushes every setting of that category to all facilities of that type on the current planet. * Player/Mecha * Unlimited interactive range * Enable player actions in globe view @@ -268,6 +271,9 @@ * 注意:如果你启用了`Auxilaryfunction`中的`展示物流站信息`,此功能将被隐藏 * 自动配置物流站 * 自动配置的建筑包括:物流配送器、行星物流站、星际物流站、高级采矿机 + * 将设置应用到当前行星上所有已建成的物流设施 + * 物流标签页上每一项设置右侧都有一个`应用`按钮,按下后将该项数值应用到当前行星上所有该类型的物流设施。 + * 每个大分类标题(物流配送器、战场分析基站、行星物流站、星际物流站、大型采矿机)右侧都有一个`应用全部`按钮,按下后将该分类的所有设置数值应用到当前行星上所有该类型的物流设施。 * 玩家/机甲 * 无限交互距离 * 移除建造数量和范围限制 diff --git a/UXAssist/UI/MyFlatButton.cs b/UXAssist/UI/MyFlatButton.cs index 37fcda3..b477352 100644 --- a/UXAssist/UI/MyFlatButton.cs +++ b/UXAssist/UI/MyFlatButton.cs @@ -79,6 +79,15 @@ public class MyFlatButton : MonoBehaviour return this; } + public MyFlatButton WithFontSize(int fontSize) + { + if (labelText != null) + { + labelText.fontSize = fontSize; + } + return this; + } + public MyFlatButton WithTip(string tip, float delay = 1f) { uiButton.tips.type = UIButton.ItemTipType.Other; diff --git a/UXAssist/UIConfigWindow.cs b/UXAssist/UIConfigWindow.cs index 6914948..e541a49 100644 --- a/UXAssist/UIConfigWindow.cs +++ b/UXAssist/UIConfigWindow.cs @@ -1,6 +1,7 @@ using System; using System.Text; using UnityEngine; +using UnityEngine.Events; using UXAssist.Common; using UXAssist.Functions; using UXAssist.ModsCompat; @@ -98,6 +99,10 @@ public static class UIConfigWindow I18N.Add("Count of Vessels filled", "Count of Vessels filled", "填充的运输船数量"); I18N.Add("Collecting Speed", "Collecting Speed", "开采速度"); I18N.Add("Min. Piler Value", "Outgoing integration count", "输出货物集装数量"); + I18N.Add("Apply config to planet", "Apply", "应用"); + I18N.Add("Apply all config to planet", "Apply All", "应用全部"); + I18N.Add("Apply config to planet tips", "Apply this value to all facilities of this type on the current planet", "将此项数值应用到当前行星上所有该类型物流设施"); + I18N.Add("Apply all config to planet tips", "Apply all settings of this category to all facilities of this type on the current planet", "将本分类的所有设置数值应用到当前行星上所有该类型物流设施"); I18N.Add("Allow overflow for Logistic Stations and Advanced Mining Machines", "Allow overflow for Logistic Stations and Advanced Mining Machines", "允许物流站和大型采矿机物品溢出"); I18N.Add("Enhance control for logistic storage capacities", "Enhance control for logistic storage capacities", "物流塔存储容量控制改进"); @@ -573,6 +578,7 @@ public static class UIConfigWindow y += 16f; var maxWidth = 0f; wnd.AddText2(10f, y, tab3, "Dispenser", 14, "text-dispenser"); + var dispenserCatY = y; y += 18f; var oy = y; x = 20f; @@ -583,11 +589,13 @@ public static class UIConfigWindow maxWidth = Mathf.Max(maxWidth, textForMeasureTextWidth.preferredWidth); y += 18f; wnd.AddText2(10f, y, tab3, "Battlefield Analysis Base", 14, "text-battlefield-analysis-base"); + var battleBaseCatY = y; y += 18f; textForMeasureTextWidth = wnd.AddText2(x, y, tab3, "Max. Charging Power", 13, "text-battlefield-analysis-base-max-charging-power"); maxWidth = Mathf.Max(maxWidth, textForMeasureTextWidth.preferredWidth); y += 18f; wnd.AddText2(10f, y, tab3, "PLS", 14, "text-pls"); + var plsCatY = y; y += 18f; textForMeasureTextWidth = wnd.AddText2(x, y, tab3, "Max. Charging Power", 13, "text-pls-max-charging-power"); maxWidth = Mathf.Max(maxWidth, textForMeasureTextWidth.preferredWidth); @@ -605,6 +613,7 @@ public static class UIConfigWindow maxWidth = Mathf.Max(maxWidth, textForMeasureTextWidth.preferredWidth); y += 18f; wnd.AddText2(10f, y, tab3, "ILS", 14, "text-ils"); + var ilsCatY = y; y += 18f; textForMeasureTextWidth = wnd.AddText2(x, y, tab3, "Max. Charging Power", 13, "text-ils-max-charging-power"); maxWidth = Mathf.Max(maxWidth, textForMeasureTextWidth.preferredWidth); @@ -614,11 +623,9 @@ public static class UIConfigWindow y += 18f; textForMeasureTextWidth = wnd.AddText2(x, y, tab3, "Vessel transport range", 13, "text-ils-vessel-transport-range"); maxWidth = Mathf.Max(maxWidth, textForMeasureTextWidth.preferredWidth); - wnd.AddCheckBox(x + 360f, y + 6f, tab3, LogisticsPatch.AutoConfigILSIncludeOrbitCollector, "Include Orbital Collector", 13).WithSmallerBox(); y += 18f; textForMeasureTextWidth = wnd.AddText2(x, y, tab3, "Warp distance", 13, "text-ils-warp-distance"); maxWidth = Mathf.Max(maxWidth, textForMeasureTextWidth.preferredWidth); - wnd.AddCheckBox(x + 360f, y + 6f, tab3, LogisticsPatch.AutoConfigILSWarperNecessary, "Warpers required", 13).WithSmallerBox(); y += 18f; textForMeasureTextWidth = wnd.AddText2(x, y, tab3, "Min. Load of Drones", 13, "text-ils-min-load-of-drones"); maxWidth = Mathf.Max(maxWidth, textForMeasureTextWidth.preferredWidth); @@ -636,6 +643,7 @@ public static class UIConfigWindow maxWidth = Mathf.Max(maxWidth, textForMeasureTextWidth.preferredWidth); y += 18f; wnd.AddText2(10f, y, tab3, "Advanced Mining Machine", 14, "text-amm"); + var ammCatY = y; y += 18f; textForMeasureTextWidth = wnd.AddText2(x, y, tab3, "Collecting Speed", 13, "text-amm-collecting-speed"); maxWidth = Mathf.Max(maxWidth, textForMeasureTextWidth.preferredWidth); @@ -644,43 +652,95 @@ public static class UIConfigWindow maxWidth = Mathf.Max(maxWidth, textForMeasureTextWidth.preferredWidth); y = oy + 1; var nx = x + maxWidth + 5f + 10f; + const float applyBtnWidth = 44f; + const float applyBtnHeight = 15f; + const int applyBtnFontSize = 11; + const float applyAllBtnWidth = 58f; + var applyBtnX = nx + 265f; + // Right-align the wider "Apply All" buttons with the right edge of the per-option "Apply" buttons. + var applyAllBtnX = applyBtnX + applyBtnWidth - applyAllBtnWidth; + var checkBoxX = applyBtnX + applyBtnWidth + 12f; + + // Buttons are vertically centered on their row (NormalizeRectWithTopLeft places y at the top edge). + void AddApplyButtonAt(float bx, float rowY, string objName, UnityAction onClick) => + wnd.AddFlatButton(bx, rowY + 6f, tab3, "Apply config to planet", applyBtnFontSize, objName, onClick).WithSize(applyBtnWidth, applyBtnHeight) + .WithFontSize(applyBtnFontSize).WithTip("Apply config to planet tips".Translate()); + + void AddApplyButton(float rowY, string objName, UnityAction onClick) => AddApplyButtonAt(applyBtnX, rowY, objName, onClick); + + void AddCategoryApplyButton(float catY, string objName, UnityAction onClick) => + wnd.AddFlatButton(applyAllBtnX, catY + 7f, tab3, "Apply all config to planet", applyBtnFontSize, objName, onClick).WithSize(applyAllBtnWidth, applyBtnHeight) + .WithFontSize(applyBtnFontSize).WithTip("Apply all config to planet tips".Translate()); + + AddCategoryApplyButton(dispenserCatY, "btn-apply-all-dispenser", LogisticsPatch.ApplyAllDispenser); + AddCategoryApplyButton(battleBaseCatY, "btn-apply-all-battle-base", LogisticsPatch.ApplyAllBattleBase); + AddCategoryApplyButton(plsCatY, "btn-apply-all-pls", LogisticsPatch.ApplyAllPLS); + AddCategoryApplyButton(ilsCatY, "btn-apply-all-ils", LogisticsPatch.ApplyAllILS); + AddCategoryApplyButton(ammCatY, "btn-apply-all-amm", LogisticsPatch.ApplyAllVeinCollector); + wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigDispenserChargePower, new AutoConfigDispenserChargePowerMapper(), "G", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-dispenser-charge-power", LogisticsPatch.ApplyDispenserChargePower); y += 18f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigDispenserCourierCount, new MyWindow.RangeValueMapper(0, 10), "G", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-dispenser-courier-count", LogisticsPatch.ApplyDispenserCourierCount); y += 36f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigBattleBaseChargePower, new AutoConfigBattleBaseChargePowerMapper(), "G", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-battle-base-charge-power", LogisticsPatch.ApplyBattleBaseChargePower); y += 36f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigPLSChargePower, new AutoConfigPLSChargePowerMapper(), "G", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-pls-charge-power", LogisticsPatch.ApplyPLSChargePower); y += 18f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigPLSMaxTripDrone, new MyWindow.RangeValueMapper(1, 180), "0°", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-pls-trip-range-drones", LogisticsPatch.ApplyPLSTripRangeDrones); y += 18f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigPLSDroneMinDeliver, new AutoConfigCarrierMinDeliverMapper(), "G", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-pls-drone-min-deliver", LogisticsPatch.ApplyPLSDroneMinDeliver); y += 18f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigPLSMinPilerValue, new AutoConfigMinPilerValueMapper(), "G", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-pls-min-piler-value", LogisticsPatch.ApplyPLSMinPilerValue); y += 18f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigPLSDroneCount, new MyWindow.RangeValueMapper(0, 50), "G", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-pls-drone-count", LogisticsPatch.ApplyPLSDroneCount); y += 36f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigILSChargePower, new AutoConfigILSChargePowerMapper(), "G", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-ils-charge-power", LogisticsPatch.ApplyILSChargePower); y += 18f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigILSMaxTripDrone, new MyWindow.RangeValueMapper(1, 180), "0°", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-ils-trip-range-drones", LogisticsPatch.ApplyILSTripRangeDrones); y += 18f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigILSMaxTripShip, new AutoConfigILSMaxTripShipMapper(), "G", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-ils-trip-range-ships", LogisticsPatch.ApplyILSTripRangeShips); + var includeOrbitCollectorCheckBox = wnd.AddCheckBox(checkBoxX, y + 4f, tab3, LogisticsPatch.AutoConfigILSIncludeOrbitCollector, "Include Orbital Collector", 13).WithSmallerBox(); + var includeOrbitCollectorY = y; y += 18f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigILSWarperDistance, new AutoConfigILSWarperDistanceMapper(), "G", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-ils-warp-distance", LogisticsPatch.ApplyILSWarpDistance); + var warperNecessaryCheckBox = wnd.AddCheckBox(checkBoxX, y + 4f, tab3, LogisticsPatch.AutoConfigILSWarperNecessary, "Warpers required", 13).WithSmallerBox(); + // Align both checkbox "Apply" buttons in a common column after the widest checkbox label. + var checkBoxApplyBtnX = checkBoxX + Mathf.Max(includeOrbitCollectorCheckBox.Width, warperNecessaryCheckBox.Width) + 10f; + AddApplyButtonAt(checkBoxApplyBtnX, includeOrbitCollectorY, "btn-apply-ils-include-orbit-collector", LogisticsPatch.ApplyILSIncludeOrbitCollector); + AddApplyButtonAt(checkBoxApplyBtnX, y, "btn-apply-ils-warper-necessary", LogisticsPatch.ApplyILSWarperNecessary); y += 18f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigILSDroneMinDeliver, new AutoConfigCarrierMinDeliverMapper(), "G", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-ils-drone-min-deliver", LogisticsPatch.ApplyILSDroneMinDeliver); y += 18f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigILSShipMinDeliver, new AutoConfigCarrierMinDeliverMapper(), "G", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-ils-ship-min-deliver", LogisticsPatch.ApplyILSShipMinDeliver); y += 18f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigILSMinPilerValue, new AutoConfigMinPilerValueMapper(), "G", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-ils-min-piler-value", LogisticsPatch.ApplyILSMinPilerValue); y += 18f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigILSDroneCount, new MyWindow.RangeValueMapper(0, 100), "G", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-ils-drone-count", LogisticsPatch.ApplyILSDroneCount); y += 18f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigILSShipCount, new MyWindow.RangeValueMapper(0, 10), "G", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-ils-ship-count", LogisticsPatch.ApplyILSShipCount); y += 36f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigVeinCollectorHarvestSpeed, new AutoConfigVeinCollectorHarvestSpeedMapper(), "G", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-amm-harvest-speed", LogisticsPatch.ApplyVeinCollectorHarvestSpeed); y += 18f; wnd.AddSideSlider(nx, y, tab3, LogisticsPatch.AutoConfigVeinCollectorMinPilerValue, new AutoConfigMinPilerValueMapper(), "G", 150f, -100f).WithFontSize(13); + AddApplyButton(y, "btn-apply-amm-min-piler-value", LogisticsPatch.ApplyVeinCollectorMinPilerValue); x = 0f; var tab4 = wnd.AddTab(trans, "Player/Mecha");