diff --git a/UXAssist/Functions/FactoryFunctions.cs b/UXAssist/Functions/FactoryFunctions.cs index b84387b..ce1964a 100644 --- a/UXAssist/Functions/FactoryFunctions.cs +++ b/UXAssist/Functions/FactoryFunctions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; namespace UXAssist.Functions; @@ -135,4 +136,117 @@ public static class FactoryFunctions blueprintCopyTool.RefreshBlueprintData(); blueprintCopyTool.DeterminePreviews(); } + + private struct BPBuildingData + { + public BlueprintBuilding building; + public int itemType; + public double offset; + } + + private struct BPBeltData + { + public BlueprintBuilding building; + public double offset; + } + + private static HashSet _itemIsBelt = null; + private static Dictionary _upgradeTypes = null; + + public static void SortBlueprintData(BlueprintData blueprintData) + { + // Initialize itemIsBelt and upgradeTypes + if (_itemIsBelt == null) + { + _itemIsBelt = []; + _upgradeTypes = []; + foreach (var proto in LDB.items.dataArray) + { + if (proto.prefabDesc?.isBelt ?? false) + { + _itemIsBelt.Add(proto.ID); + continue; + } + if (proto.Upgrades != null && proto.Upgrades.Length > 0) + { + var minUpgrade = proto.Upgrades.Min(u => u); + if (minUpgrade != 0 && minUpgrade != proto.ID) + { + _upgradeTypes.Add(proto.ID, minUpgrade); + } + } + } + } + + // Separate belt and non-belt buildings + List bpBuildings = []; + Dictionary bpBelts = []; + foreach (var building in blueprintData.buildings) + { + var offset = building.areaIndex * 1073741824.0 + building.localOffset_y * 262144.0 + building.localOffset_x * 1024.0 + building.localOffset_z; + if (_itemIsBelt.Contains(building.itemId)) + { + bpBelts.Add(building, new BPBeltData { building = building, offset = offset }); + } + else + { + var itemType = _upgradeTypes.TryGetValue(building.itemId, out var upgradeType) ? upgradeType : building.itemId; + bpBuildings.Add(new BPBuildingData { building = building, itemType = itemType, offset = offset }); + } + } + var beltsWithInput = bpBelts.Select(pair => pair.Value.building.outputObj).ToHashSet(); + var beltHeads = bpBelts.Where(pair => !beltsWithInput.Contains(pair.Value.building)).ToDictionary(pair => pair.Key, pair => pair.Value); + // Sort belt buildings + List sortedBpBelts = []; + // Deal with non-cycle belt paths + foreach (var pair in beltHeads.OrderByDescending(pair => pair.Value.offset)) + { + var building = pair.Key; + while (building != null) + { + if (!bpBelts.Remove(building)) break; + sortedBpBelts.Add(building); + building = building.outputObj; + } + } + // Deal with cycle belt paths + foreach (var pair in bpBelts.OrderByDescending(pair => pair.Value.offset)) + { + var building = pair.Key; + while (building != null) + { + if (!bpBelts.Remove(building)) break; + sortedBpBelts.Add(building); + building = building.outputObj; + } + } + + // Sort non-belt buildings + bpBuildings.Sort((a, b) => + { + var sign = b.itemType.CompareTo(a.itemType); + if (sign != 0) return sign; + + sign = b.building.modelIndex.CompareTo(a.building.modelIndex); + if (sign != 0) return sign; + + sign = b.building.recipeId.CompareTo(a.building.recipeId); + if (sign != 0) return sign; + + sign = a.building.areaIndex.CompareTo(b.building.areaIndex); + if (sign != 0) return sign; + + return b.offset.CompareTo(a.offset); + }); + + // Concatenate sorted belts and non-belt buildings + sortedBpBelts.Reverse(); + blueprintData.buildings = [.. bpBuildings.Select(b => b.building), .. sortedBpBelts]; + var buildings = blueprintData.buildings; + + for (var i = buildings.Length - 1; i >= 0; i--) + { + buildings[i].index = i; + } + } } diff --git a/UXAssist/Patches/LogisticsPatch.cs b/UXAssist/Patches/LogisticsPatch.cs index 647e1e1..53c61c1 100644 --- a/UXAssist/Patches/LogisticsPatch.cs +++ b/UXAssist/Patches/LogisticsPatch.cs @@ -764,8 +764,15 @@ public static class LogisticsPatch public static void Enable(bool on) { - if (_stationTipsRoot) - _stationTipsRoot.SetActive(on); + if (_stationTipsRoot == null) return; + if (!on) + { + _stationTipsRoot.SetActive(false); + return; + } + if (DSPGame.IsMenuDemo || !GameMain.isRunning) return; + _lastPlanet = GameMain.localPlanet; + _stationTipsRoot.SetActive(on && _lastPlanet != null); } public static void EnableBars(bool on) diff --git a/UXAssist/Patches/PersistPatch.cs b/UXAssist/Patches/PersistPatch.cs index d0a1db9..e50f0b0 100644 --- a/UXAssist/Patches/PersistPatch.cs +++ b/UXAssist/Patches/PersistPatch.cs @@ -88,126 +88,29 @@ public class PersistPatch : PatchImpl return matcher.InstructionEnumeration(); } - // Sort blueprint structures by item id, model index, recipe id, area index, and position before saving - private struct BPBuildingData + + [HarmonyTranspiler] + [HarmonyPatch(typeof(BuildTool_BlueprintCopy), nameof(BuildTool_BlueprintCopy.UseToPasteNow))] + private static IEnumerable BuildTool_BlueprintCopy_UseToPasteNow_Transpiler(IEnumerable instructions, ILGenerator generator) { - public BlueprintBuilding building; - public int itemType; - public double offset; + var matcher = new CodeMatcher(instructions, generator); + matcher.MatchForward(false, + new CodeMatch(OpCodes.Ldarg_0), + new CodeMatch(OpCodes.Call, AccessTools.Method(typeof(BuildTool_BlueprintCopy), nameof(BuildTool_BlueprintCopy.RefreshBlueprintData))) + ).Advance(2).Insert( + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(BuildTool_BlueprintCopy), nameof(BuildTool_BlueprintCopy.blueprint))), + new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Functions.FactoryFunctions), nameof(Functions.FactoryFunctions.SortBlueprintData))) + ); + return matcher.InstructionEnumeration(); } - private struct BPBeltData + [HarmonyPrefix] + [HarmonyPatch(typeof(BlueprintData), nameof(BlueprintData.SaveBlueprintData))] + private static void BlueprintData_SaveBlueprintData_Prefix(BlueprintData __instance) { - public BlueprintBuilding building; - public double offset; - } - - private static HashSet _itemIsBelt = null; - private static Dictionary _upgradeTypes = null; - - [HarmonyPostfix] - [HarmonyPatch(typeof(BlueprintUtils), nameof(BlueprintUtils.GenerateBlueprintData))] - private static void BlueprintUtils_GenerateBlueprintData_Postfix(BlueprintData _blueprintData) - { - // Initialize itemIsBelt and upgradeTypes - if (_itemIsBelt == null) - { - _itemIsBelt = []; - _upgradeTypes = []; - foreach (var proto in LDB.items.dataArray) - { - if (proto.prefabDesc?.isBelt ?? false) - { - _itemIsBelt.Add(proto.ID); - continue; - } - if (proto.Upgrades != null && proto.Upgrades.Length > 0) - { - var minUpgrade = proto.Upgrades.Min(u => u); - if (minUpgrade != 0 && minUpgrade != proto.ID) - { - _upgradeTypes.Add(proto.ID, minUpgrade); - } - } - } - } - - // Separate belt and non-belt buildings - List bpBuildings = []; - Dictionary bpBelts = []; - for (var i = 0; i < _blueprintData.buildings.Length; i++) - { - var building = _blueprintData.buildings[i]; - var offset = building.localOffset_y * 262144.0 + building.localOffset_x * 1024.0 + building.localOffset_z; - if (_itemIsBelt.Contains(building.itemId)) - { - bpBelts.Add(building, new BPBeltData { building = building, offset = offset }); - } - else - { - var itemType = _upgradeTypes.TryGetValue(building.itemId, out var upgradeType) ? upgradeType : building.itemId; - bpBuildings.Add(new BPBuildingData { building = building, itemType = itemType, offset = offset }); - } - } - Dictionary beltHeads = new(bpBelts); - foreach (var building in bpBelts.Keys) - { - var next = building.outputObj; - if (next == null) continue; - beltHeads.Remove(next); - } - // Sort belt buildings - List sortedBpBelts = []; - // Deal with non-cycle belt paths - foreach (var pair in beltHeads.OrderByDescending(pair => pair.Value.offset)) - { - var building = pair.Key; - while (building != null) - { - if (!bpBelts.Remove(building)) break; - sortedBpBelts.Add(building); - building = building.outputObj; - } - } - // Deal with cycle belt paths - while (bpBelts.Count > 0) - { - var building = bpBelts.OrderByDescending(pair => pair.Value.offset).First().Key; - while (building != null) - { - if (!bpBelts.Remove(building)) break; - sortedBpBelts.Add(building); - building = building.outputObj; - } - } - - // Sort non-belt buildings - bpBuildings.Sort((a, b) => - { - var sign = b.itemType.CompareTo(a.itemType); - if (sign != 0) return sign; - - sign = b.building.modelIndex.CompareTo(a.building.modelIndex); - if (sign != 0) return sign; - - sign = b.building.recipeId.CompareTo(a.building.recipeId); - if (sign != 0) return sign; - - sign = a.building.areaIndex.CompareTo(b.building.areaIndex); - if (sign != 0) return sign; - - return b.offset.CompareTo(a.offset); - }); - - // Concatenate sorted belts and non-belt buildings - sortedBpBelts.Reverse(); - _blueprintData.buildings = [.. bpBuildings.Select(b => b.building), .. sortedBpBelts]; - var buildings = _blueprintData.buildings; - - for (var i = buildings.Length - 1; i >= 0; i--) - { - buildings[i].index = i; - } + if (!__instance.isValid) return; + Functions.FactoryFunctions.SortBlueprintData(__instance); } // Increase maximum value of property realizing, 2000 -> 20000