1
0
mirror of https://github.com/soarqin/DSP_Mods.git synced 2025-12-09 04:13:32 +08:00

refactoring UXAssist and CheatEnabler

This commit is contained in:
2024-09-17 01:49:25 +08:00
parent db0a9522da
commit fb916b3813
31 changed files with 858 additions and 1260 deletions

View File

@@ -0,0 +1,539 @@
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using BepInEx.Configuration;
using HarmonyLib;
using UXAssist.Common;
namespace UXAssist.Patches;
public static class DysonSpherePatch
{
public static ConfigEntry<bool> StopEjectOnNodeCompleteEnabled;
public static ConfigEntry<bool> OnlyConstructNodesEnabled;
public static ConfigEntry<int> AutoConstructMultiplier;
private static Harmony _dysonSpherePatch;
private static FieldInfo _totalNodeSpInfo, _totalFrameSpInfo, _totalCpInfo;
public static void Init()
{
I18N.Add("[UXAssist] No node to fill", "[UXAssist] No node to fill", "[UXAssist] 无可建造节点");
_dysonSpherePatch ??= Harmony.CreateAndPatchAll(typeof(DysonSpherePatch));
StopEjectOnNodeCompleteEnabled.SettingChanged += (_, _) => StopEjectOnNodeComplete.Enable(StopEjectOnNodeCompleteEnabled.Value);
OnlyConstructNodesEnabled.SettingChanged += (_, _) => OnlyConstructNodes.Enable(OnlyConstructNodesEnabled.Value);
StopEjectOnNodeComplete.Enable(StopEjectOnNodeCompleteEnabled.Value);
OnlyConstructNodes.Enable(OnlyConstructNodesEnabled.Value);
_totalNodeSpInfo = AccessTools.Field(typeof(DysonSphereLayer), "totalNodeSP");
_totalFrameSpInfo = AccessTools.Field(typeof(DysonSphereLayer), "totalFrameSP");
_totalCpInfo = AccessTools.Field(typeof(DysonSphereLayer), "totalCP");
}
public static void Uninit()
{
StopEjectOnNodeComplete.Enable(false);
OnlyConstructNodes.Enable(false);
_dysonSpherePatch?.UnpatchSelf();
_dysonSpherePatch = null;
}
public static void InitCurrentDysonSphere(int index)
{
var star = GameMain.localStar;
if (star == null) return;
var dysonSpheres = GameMain.data?.dysonSpheres;
if (dysonSpheres == null) return;
if (index < 0)
{
if (dysonSpheres[star.index] == null) return;
var dysonSphere = new DysonSphere();
dysonSpheres[star.index] = dysonSphere;
dysonSphere.Init(GameMain.data, star);
dysonSphere.ResetNew();
return;
}
var ds = dysonSpheres[star.index];
if (ds?.layersIdBased[index] == null) return;
var pool = ds.rocketPool;
for (var id = ds.rocketCursor - 1; id > 0; id--)
{
if (pool[id].id != id) continue;
if (pool[id].nodeLayerId != index) continue;
ds.RemoveDysonRocket(id);
}
ds.RemoveLayer(index);
}
[HarmonyPrefix]
[HarmonyPatch(typeof(DysonSwarm), nameof(DysonSwarm.AutoConstruct))]
private static bool DysonSwarm_AutoConstruct_Prefix(DysonSwarm __instance)
{
return __instance.dysonSphere.autoNodeCount == 0;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(DysonSphere), nameof(DysonSphere.AutoConstruct))]
private static bool DysonSphere_AutoConstruct_Prefix(DysonSphere __instance)
{
var totalCount = AutoConstructMultiplier.Value * 6;
foreach (var dysonSphereLayer in __instance.layersIdBased)
{
if (dysonSphereLayer == null) continue;
for (var j = dysonSphereLayer.nodePool.Length - 1; j >= 0; j--)
{
var dysonNode = dysonSphereLayer.nodePool[j];
if (dysonNode == null || dysonNode.id != j) continue;
var count = dysonNode._spReq - dysonNode.spOrdered;
int todoCount;
int[] productRegister;
if (count > 0)
{
if (count > totalCount)
{
count = totalCount;
}
todoCount = count;
if (dysonNode.sp < dysonNode.spMax)
{
int diff;
if (dysonNode.sp + count > dysonNode.spMax)
{
diff = dysonNode.spMax - dysonNode.sp;
count -= diff;
dysonNode._spReq -= diff;
dysonNode.sp = dysonNode.spMax;
}
else
{
diff = count;
dysonNode._spReq -= diff;
dysonNode.sp += diff;
count = 0;
}
// Make compatible with DSPOptimizations
if (_totalNodeSpInfo != null)
_totalNodeSpInfo.SetValue(dysonSphereLayer, (long)_totalNodeSpInfo.GetValue(dysonSphereLayer) + diff - 1);
__instance.UpdateProgress(dysonNode);
}
if (count > 0)
{
var frameCount = dysonNode.frames.Count;
var frameIndex = dysonNode.frameTurn % frameCount;
for (var i = frameCount; i > 0 && count > 0; i--)
{
var dysonFrame = dysonNode.frames[frameIndex];
var spMax = dysonFrame.spMax >> 1;
if (dysonFrame.nodeA == dysonNode && dysonFrame.spA < spMax)
{
int diff;
if (dysonFrame.spA + count > spMax)
{
diff = spMax - dysonFrame.spA;
count -= diff;
dysonNode._spReq -= diff;
dysonFrame.spA = spMax;
}
else
{
diff = count;
dysonNode._spReq -= diff;
dysonFrame.spA += diff;
count = 0;
}
// Make compatible with DSPOptimizations
if (_totalFrameSpInfo != null)
_totalFrameSpInfo.SetValue(dysonSphereLayer, (long)_totalFrameSpInfo.GetValue(dysonSphereLayer) + diff - 1);
__instance.UpdateProgress(dysonFrame);
}
if (count > 0 && dysonFrame.nodeB == dysonNode && dysonFrame.spB < spMax)
{
int diff;
if (dysonFrame.spB + count > spMax)
{
diff = spMax - dysonFrame.spB;
count -= diff;
dysonNode._spReq -= diff;
dysonFrame.spB = spMax;
}
else
{
diff = count;
dysonNode._spReq -= diff;
dysonFrame.spB += diff;
count = 0;
}
// Make compatible with DSPOptimizations
if (_totalFrameSpInfo != null)
_totalFrameSpInfo.SetValue(dysonSphereLayer, (long)_totalFrameSpInfo.GetValue(dysonSphereLayer) + diff - 1);
__instance.UpdateProgress(dysonFrame);
}
frameIndex = (frameIndex + 1) % frameCount;
}
dysonNode.frameTurn = frameIndex;
}
if (dysonNode.spOrdered >= dysonNode._spReq)
{
__instance.RemoveAutoNode(dysonNode);
__instance.PickAutoNode();
}
productRegister = __instance.productRegister;
if (productRegister != null)
{
lock (productRegister)
{
productRegister[11902] += todoCount - count;
}
}
}
count = dysonNode._cpReq - dysonNode.cpOrdered;
if (count > 0)
{
if (count > totalCount) count = totalCount;
todoCount = count;
var shellCount = dysonNode.shells.Count;
var shellIndex = dysonNode.shellTurn % shellCount;
for (var i = shellCount; i > 0 && count > 0; i--)
{
var dysonShell = dysonNode.shells[shellIndex];
var nodeIndex = dysonShell.nodeIndexMap[dysonNode.id];
var diff = (dysonShell.vertsqOffset[nodeIndex + 1] - dysonShell.vertsqOffset[nodeIndex]) * dysonShell.cpPerVertex - dysonShell.nodecps[nodeIndex];
if (diff > count)
diff = count;
count -= diff;
dysonNode._cpReq -= diff;
dysonShell.nodecps[nodeIndex] += diff;
dysonShell.nodecps[dysonShell.nodecps.Length - 1] += diff;
// Make compatible with DSPOptimizations
if (_totalCpInfo != null)
{
_totalCpInfo.SetValue(dysonSphereLayer, (long)_totalCpInfo.GetValue(dysonSphereLayer) + diff);
dysonShell.SetMaterialDynamicVars();
}
shellIndex = (shellIndex + 1) % shellCount;
}
dysonNode.shellTurn = shellIndex;
var solarSailCount = todoCount - count;
productRegister = __instance.productRegister;
if (productRegister != null)
{
lock (productRegister)
{
productRegister[11901] += solarSailCount;
productRegister[11903] += solarSailCount;
}
}
var consumeRegister = __instance.consumeRegister;
if (consumeRegister != null)
{
lock (consumeRegister)
{
consumeRegister[11901] += solarSailCount;
}
}
}
}
}
return false;
}
[HarmonyTranspiler]
[HarmonyPriority(Priority.First)]
[HarmonyPatch(typeof(DysonNode), nameof(DysonNode.ConstructCp))]
private static IEnumerable<CodeInstruction> DysonSpherePatch_DysonNode_ConstructCp_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.MatchBack(false,
new CodeMatch(OpCodes.Ldc_I4_0),
new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(DysonShell), nameof(DysonShell.Construct)))
).Advance(3).InsertAndAdvance(
// node._cpReq = node._cpReq - 1;
new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(DysonNode), nameof(DysonNode._cpReq))),
new CodeInstruction(OpCodes.Ldc_I4_1),
new CodeInstruction(OpCodes.Sub),
new CodeInstruction(OpCodes.Stfld, AccessTools.Field(typeof(DysonNode), nameof(DysonNode._cpReq)))
);
// Remove use of RecalcCpReq()
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ldarg_0),
new CodeMatch(OpCodes.Call, AccessTools.Method(typeof(DysonNode), nameof(DysonNode.RecalcCpReq)))
);
var labels = matcher.Labels;
matcher.RemoveInstructions(2).Labels.AddRange(labels);
return matcher.InstructionEnumeration();
}
private class StopEjectOnNodeComplete: PatchImpl<StopEjectOnNodeComplete>
{
private static HashSet<int>[] _nodeForAbsorb;
private static bool _initialized;
protected override void OnEnable()
{
InitNodeForAbsorb();
GameLogic.OnGameBegin += GameMain_Begin_Postfix;
GameLogic.OnGameEnd += GameMain_End_Postfix;
}
protected override void OnDisable()
{
GameLogic.OnGameEnd -= GameMain_End_Postfix;
GameLogic.OnGameBegin -= GameMain_Begin_Postfix;
_initialized = false;
_nodeForAbsorb = null;
}
private static void InitNodeForAbsorb()
{
_initialized = false;
_nodeForAbsorb = null;
var data = GameMain.data;
var galaxy = data?.galaxy;
if (galaxy == null) return;
var galaxyStarCount = galaxy.starCount;
_nodeForAbsorb = new HashSet<int>[galaxyStarCount];
var spheres = data.dysonSpheres;
if (spheres == null) return;
foreach (var sphere in spheres)
{
if (sphere?.layersSorted == null) continue;
var starIndex = sphere.starData.index;
if (starIndex >= galaxyStarCount) continue;
foreach (var layer in sphere.layersSorted)
{
if (layer == null) continue;
for (var i = layer.nodeCursor - 1; i > 0; i--)
{
var node = layer.nodePool[i];
if (node == null || node.id != i || node.sp < node.spMax || node.cpReqOrder == 0) continue;
SetNodeForAbsorb(starIndex, layer.id, node.id, true);
}
}
}
_initialized = true;
}
private static void SetNodeForAbsorb(int index, int layerId, int nodeId, bool canAbsorb)
{
ref var comp = ref _nodeForAbsorb[index];
comp ??= [];
var idx = nodeId * 10 + layerId;
if (canAbsorb)
comp.Add(idx);
else
comp.Remove(idx);
}
private static void UpdateNodeForAbsorbOnSpChange(DysonNode node)
{
if (!_initialized) return;
if (node.sp < node.spMax || node.cpReqOrder <= 0) return;
var shells = node.shells;
if (shells.Count == 0) return;
SetNodeForAbsorb(shells[0].dysonSphere.starData.index, node.layerId, node.id, true);
}
private static void UpdateNodeForAbsorbOnCpChange(DysonNode node)
{
if (!_initialized) return;
if (node.sp < node.spMax || node.cpReqOrder > 0) return;
var shells = node.shells;
if (shells.Count == 0) return;
SetNodeForAbsorb(shells[0].dysonSphere.starData.index, node.layerId, node.id, false);
}
private static bool AnyNodeForAbsorb(int starIndex)
{
var comp = _nodeForAbsorb[starIndex];
return comp is { Count: > 0 };
}
private static void GameMain_Begin_Postfix()
{
InitNodeForAbsorb();
}
private static void GameMain_End_Postfix()
{
_initialized = false;
_nodeForAbsorb = null;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(DysonNode), nameof(DysonNode.RecalcCpReq))]
private static void DysonNode_RecalcCpReq_Postfix(DysonNode __instance)
{
UpdateNodeForAbsorbOnCpChange(__instance);
}
[HarmonyPrefix]
[HarmonyPatch(typeof(DysonSphereLayer), nameof(DysonSphereLayer.RemoveDysonNode))]
private static void DysonSphereLayer_RemoveDysonNode_Prefix(DysonSphereLayer __instance, int nodeId)
{
if (_initialized)
SetNodeForAbsorb(__instance.starData.index, __instance.id, nodeId, false);
}
[HarmonyPrefix]
[HarmonyPatch(typeof(DysonSphere), nameof(DysonSphere.ResetNew))]
private static void DysonSphere_ResetNew_Prefix(DysonSphere __instance)
{
if (_nodeForAbsorb == null) return;
var starIndex = __instance.starData.index;
if (starIndex >= _nodeForAbsorb.Length || _nodeForAbsorb[starIndex] == null) return;
_nodeForAbsorb[starIndex].Clear();
_nodeForAbsorb[starIndex] = null;
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(EjectorComponent), nameof(EjectorComponent.InternalUpdate))]
private static IEnumerable<CodeInstruction> EjectorComponent_InternalUpdate_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.MatchForward(false,
// if (this.orbitId == 0
new CodeMatch(OpCodes.Ldarg_0),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(EjectorComponent), nameof(EjectorComponent.orbitId))),
new CodeMatch(OpCodes.Brtrue)
).Advance(2).Insert(
// || !StopEjectOnNodeComplete.AnyNodeForAbsorb(this.starData.index))
new CodeInstruction(OpCodes.Ldc_I4_0),
new CodeInstruction(OpCodes.Cgt),
new CodeInstruction(OpCodes.Ldarg_2),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(DysonSwarm), nameof(DysonSwarm.starData))),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(StarData), nameof(StarData.index))),
new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(StopEjectOnNodeComplete), nameof(StopEjectOnNodeComplete.AnyNodeForAbsorb))),
new CodeInstruction(OpCodes.And)
);
return matcher.InstructionEnumeration();
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(DysonNode), nameof(DysonNode.ConstructSp))]
private static IEnumerable<CodeInstruction> DysonNode_ConstructSp_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.Start().MatchForward(false,
new CodeMatch(OpCodes.Stfld, AccessTools.Field(typeof(DysonNode), nameof(DysonNode.sp)))
).Advance(1);
var labels = matcher.Labels;
matcher.Labels = [];
matcher.Insert(
new CodeInstruction(OpCodes.Ldarg_0).WithLabels(labels),
new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(StopEjectOnNodeComplete), nameof(StopEjectOnNodeComplete.UpdateNodeForAbsorbOnSpChange)))
);
return matcher.InstructionEnumeration();
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(DysonNode), nameof(DysonNode.ConstructCp))]
private static IEnumerable<CodeInstruction> DysonNode_ConstructCp_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.MatchBack(false,
// Search for previous patch:
// node._cpReq = node._cpReq - 1;
new CodeMatch(OpCodes.Ldarg_0),
new CodeMatch(OpCodes.Ldarg_0),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(DysonNode), nameof(DysonNode._cpReq))),
new CodeMatch(OpCodes.Ldc_I4_1),
new CodeMatch(OpCodes.Sub),
new CodeMatch(OpCodes.Stfld, AccessTools.Field(typeof(DysonNode), nameof(DysonNode._cpReq)))
).Advance(6).Insert(
new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(StopEjectOnNodeComplete), nameof(StopEjectOnNodeComplete.UpdateNodeForAbsorbOnCpChange)))
);
return matcher.InstructionEnumeration();
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(UIEjectorWindow), nameof(UIEjectorWindow._OnUpdate))]
static IEnumerable<CodeInstruction> UIEjectorWindow__OnUpdate_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
var label1 = generator.DefineLabel();
var label2 = generator.DefineLabel();
matcher.MatchForward(false,
// this.stateText.text = "轨道未设置".Translate();
new CodeMatch(OpCodes.Ldstr, "待机"),
new CodeMatch(OpCodes.Call, AccessTools.Method(typeof(Localization), nameof(Localization.Translate))),
new CodeMatch(OpCodes.Callvirt, AccessTools.PropertySetter(typeof(UnityEngine.UI.Text), nameof(UnityEngine.UI.Text.text)))
).InsertAndAdvance(
// if (StopEjectOnNodeComplete.AnyNodeForAbsorb(this.starData.index))
new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(UIEjectorWindow), nameof(UIEjectorWindow.factorySystem))),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(FactorySystem), nameof(FactorySystem.planet))),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(PlanetData), nameof(PlanetData.star))),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(StarData), nameof(StarData.index))),
new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(StopEjectOnNodeComplete), nameof(StopEjectOnNodeComplete.AnyNodeForAbsorb))),
new CodeInstruction(OpCodes.Brfalse, label1)
).Advance(1).InsertAndAdvance(
new CodeInstruction(OpCodes.Br, label2),
new CodeInstruction(OpCodes.Ldstr, "[UXAssist] No node to fill").WithLabels(label1),
new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(Localization), nameof(Localization.Translate)))
).Labels.Add(label2);
return matcher.InstructionEnumeration();
}
}
private class OnlyConstructNodes: PatchImpl<OnlyConstructNodes>
{
protected override void OnEnable()
{
var spheres = GameMain.data?.dysonSpheres;
if (spheres == null) return;
foreach (var sphere in spheres)
{
if (sphere == null) continue;
sphere.CheckAutoNodes();
if (sphere.autoNodeCount > 0) continue;
sphere.PickAutoNode();
sphere.PickAutoNode();
sphere.PickAutoNode();
sphere.PickAutoNode();
}
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(DysonNode), nameof(DysonNode.spReqOrder), MethodType.Getter)]
private static IEnumerable<CodeInstruction> DysonNode_spReqOrder_Getter_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ldarg_0),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(DysonNode), nameof(DysonNode._spReq)))
).Advance(1).SetInstructionAndAdvance(
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(DysonNode), nameof(DysonNode.spMax)))
).Insert(
new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(DysonNode), nameof(DysonNode.sp))),
new CodeInstruction(OpCodes.Sub)
);
return matcher.InstructionEnumeration();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,651 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection.Emit;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;
using UXAssist.Common;
namespace UXAssist.Patches;
public static class GamePatch
{
private const string GameWindowClass = "UnityWndClass";
private static string _gameWindowTitle = "Dyson Sphere Program";
public static string ProfileName { get; private set; }
public static ConfigEntry<bool> EnableWindowResizeEnabled;
public static ConfigEntry<bool> LoadLastWindowRectEnabled;
public static ConfigEntry<int> MouseCursorScaleUpMultiplier;
// public static ConfigEntry<bool> AutoSaveOptEnabled;
public static ConfigEntry<bool> ConvertSavesFromPeaceEnabled;
public static ConfigEntry<Vector4> LastWindowRect;
public static ConfigEntry<bool> ProfileBasedSaveFolderEnabled;
public static ConfigEntry<string> DefaultProfileName;
public static ConfigEntry<double> GameUpsFactor;
private static bool _enableGameUpsFactor = true;
public static bool EnableGameUpsFactor
{
get => _enableGameUpsFactor;
set
{
_enableGameUpsFactor = value;
if (value)
{
var oldFixUps = FPSController.instance.fixUPS;
if (oldFixUps <= 1.0)
{
GameUpsFactor.Value = 1.0;
return;
}
GameUpsFactor.Value = Maths.Clamp(FPSController.instance.fixUPS / GameMain.tickPerSec, 0.1, 10.0);
}
else
{
GameUpsFactor.Value = 1.0;
}
}
}
private static Harmony _gamePatch;
public static void Init()
{
// Get profile name from command line arguments, and set window title accordingly
var args = Environment.GetCommandLineArgs();
for (var i = 0; i < args.Length - 1; i++)
{
if (args[i] != "--doorstop-target") continue;
var arg = args[i + 1];
const string doorstopPathSuffix = @"\BepInEx\core\BepInEx.Preloader.dll";
if (!arg.EndsWith(doorstopPathSuffix, StringComparison.OrdinalIgnoreCase))
break;
arg = arg.Substring(0, arg.Length - doorstopPathSuffix.Length);
const string profileSuffix = @"\profiles\";
var index = arg.LastIndexOf(profileSuffix, StringComparison.OrdinalIgnoreCase);
if (index < 0)
break;
arg = arg.Substring(index + profileSuffix.Length);
var wnd = WinApi.FindWindow(GameWindowClass, _gameWindowTitle);
if (wnd == IntPtr.Zero) return;
ProfileName = arg;
_gameWindowTitle = $"Dyson Sphere Program - {arg}";
WinApi.SetWindowText(wnd, _gameWindowTitle);
break;
}
EnableWindowResizeEnabled.SettingChanged += (_, _) => EnableWindowResize.Enable(EnableWindowResizeEnabled.Value);
LoadLastWindowRectEnabled.SettingChanged += (_, _) => LoadLastWindowRect.Enable(LoadLastWindowRectEnabled.Value);
MouseCursorScaleUpMultiplier.SettingChanged += (_, _) =>
{
MouseCursorScaleUp.reload = true;
MouseCursorScaleUp.Enable(MouseCursorScaleUpMultiplier.Value > 1);
};
// AutoSaveOptEnabled.SettingChanged += (_, _) => AutoSaveOpt.Enable(AutoSaveOptEnabled.Value);
ConvertSavesFromPeaceEnabled.SettingChanged += (_, _) => ConvertSavesFromPeace.Enable(ConvertSavesFromPeaceEnabled.Value);
ProfileBasedSaveFolderEnabled.SettingChanged += (_, _) => RefreshSavePath();
DefaultProfileName.SettingChanged += (_, _) => RefreshSavePath();
GameUpsFactor.SettingChanged += (_, _) =>
{
if (!EnableGameUpsFactor || GameUpsFactor.Value == 0.0) return;
if (Math.Abs(GameUpsFactor.Value - 1.0) < 0.001)
{
FPSController.SetFixUPS(0.0);
return;
}
FPSController.SetFixUPS(GameMain.tickPerSec * GameUpsFactor.Value);
};
EnableWindowResize.Enable(EnableWindowResizeEnabled.Value);
LoadLastWindowRect.Enable(LoadLastWindowRectEnabled.Value);
MouseCursorScaleUp.reload = false;
MouseCursorScaleUp.Enable(MouseCursorScaleUpMultiplier.Value > 1);
// AutoSaveOpt.Enable(AutoSaveOptEnabled.Value);
ConvertSavesFromPeace.Enable(ConvertSavesFromPeaceEnabled.Value);
_gamePatch ??= Harmony.CreateAndPatchAll(typeof(GamePatch));
}
public static void Uninit()
{
LoadLastWindowRect.Enable(false);
EnableWindowResize.Enable(false);
MouseCursorScaleUp.reload = false;
MouseCursorScaleUp.Enable(false);
// AutoSaveOpt.Enable(false);
ConvertSavesFromPeace.Enable(false);
_gamePatch?.UnpatchSelf();
_gamePatch = null;
}
private static void RefreshSavePath()
{
if (ProfileName == null) return;
if (UIRoot.instance.loadGameWindow.gameObject.activeSelf)
{
UIRoot.instance.loadGameWindow._Close();
}
if (UIRoot.instance.saveGameWindow.gameObject.activeSelf)
{
UIRoot.instance.saveGameWindow._Close();
}
string gameSavePath;
if (ProfileBasedSaveFolderEnabled.Value && string.Compare(DefaultProfileName.Value, ProfileName, StringComparison.OrdinalIgnoreCase) != 0)
gameSavePath = $"{GameConfig.overrideDocumentFolder}{GameConfig.gameName}/Save/{ProfileName}/";
else
gameSavePath = $"{GameConfig.overrideDocumentFolder}{GameConfig.gameName}/Save/";
if (string.Compare(GameConfig.gameSavePath, gameSavePath, StringComparison.OrdinalIgnoreCase) == 0) return;
GameConfig.gameSavePath = gameSavePath;
if (!Directory.Exists(GameConfig.gameSavePath))
{
Directory.CreateDirectory(GameConfig.gameSavePath);
}
}
[HarmonyPrefix, HarmonyPatch(typeof(GameMain), nameof(GameMain.HandleApplicationQuit))]
private static void GameMain_HandleApplicationQuit_Prefix()
{
var wnd = WinApi.FindWindow(GameWindowClass, _gameWindowTitle);
if (wnd == IntPtr.Zero) return;
WinApi.GetWindowRect(wnd, out var rect);
LastWindowRect.Value = new Vector4(rect.Left, rect.Top, Screen.width, Screen.height);
}
private class EnableWindowResize: PatchImpl<EnableWindowResize>
{
private static bool _enabled;
protected override void OnEnable()
{
var wnd = WinApi.FindWindow(GameWindowClass, _gameWindowTitle);
if (wnd == IntPtr.Zero)
{
Enable(false);
return;
}
_enabled = true;
WinApi.SetWindowLong(wnd, (int)WindowLongFlags.GWL_STYLE,
WinApi.GetWindowLong(wnd, (int)WindowLongFlags.GWL_STYLE) | (int)WindowStyles.WS_THICKFRAME | (int)WindowStyles.WS_MAXIMIZEBOX);
}
protected override void OnDisable()
{
var wnd = WinApi.FindWindow(GameWindowClass, _gameWindowTitle);
if (wnd == IntPtr.Zero)
return;
_enabled = false;
WinApi.SetWindowLong(wnd, (int)WindowLongFlags.GWL_STYLE,
WinApi.GetWindowLong(wnd, (int)WindowLongFlags.GWL_STYLE) & ~((int)WindowStyles.WS_THICKFRAME | (int)WindowStyles.WS_MAXIMIZEBOX));
}
[HarmonyPostfix]
[HarmonyPatch(typeof(UIOptionWindow), nameof(UIOptionWindow.ApplyOptions))]
private static void UIOptionWindow_ApplyOptions_Postfix()
{
var wnd = WinApi.FindWindow(GameWindowClass, _gameWindowTitle);
if (wnd == IntPtr.Zero) return;
if (_enabled)
WinApi.SetWindowLong(wnd, (int)WindowLongFlags.GWL_STYLE,
WinApi.GetWindowLong(wnd, (int)WindowLongFlags.GWL_STYLE) | (int)WindowStyles.WS_THICKFRAME | (int)WindowStyles.WS_MAXIMIZEBOX);
else
WinApi.SetWindowLong(wnd, (int)WindowLongFlags.GWL_STYLE,
WinApi.GetWindowLong(wnd, (int)WindowLongFlags.GWL_STYLE) & ~((int)WindowStyles.WS_THICKFRAME | (int)WindowStyles.WS_MAXIMIZEBOX));
}
}
private class LoadLastWindowRect: PatchImpl<LoadLastWindowRect>
{
private static bool _loaded;
protected override void OnEnable()
{
GameLogic.OnDataLoaded += VFPreload_InvokeOnLoadWorkEnded_Postfix;
if (Screen.fullScreenMode is not (FullScreenMode.ExclusiveFullScreen or FullScreenMode.FullScreenWindow or FullScreenMode.MaximizedWindow))
{
var rect = LastWindowRect.Value;
var x = Mathf.RoundToInt(rect.x);
var y = Mathf.RoundToInt(rect.y);
var w = Mathf.RoundToInt(rect.z);
var h = Mathf.RoundToInt(rect.w);
var needFix = false;
if (w < 100)
{
w = 1280;
needFix = true;
}
if (h < 100)
{
h = 720;
needFix = true;
}
var sw = Screen.currentResolution.width;
var sh = Screen.currentResolution.height;
if (x + w > sw)
{
x = sw - w;
needFix = true;
}
if (y + h > sh)
{
y = sh - h;
needFix = true;
}
if (x < 0)
{
x = 0;
needFix = true;
}
if (y < 0)
{
y = 0;
needFix = true;
}
if (needFix)
{
LastWindowRect.Value = new Vector4(x, y, w, h);
}
}
MoveWindowPosition();
}
protected override void OnDisable()
{
GameLogic.OnDataLoaded -= VFPreload_InvokeOnLoadWorkEnded_Postfix;
}
private static void MoveWindowPosition()
{
if (Screen.fullScreenMode is FullScreenMode.ExclusiveFullScreen or FullScreenMode.FullScreenWindow or FullScreenMode.MaximizedWindow || GameMain.isRunning) return;
var wnd = WinApi.FindWindow(GameWindowClass, _gameWindowTitle);
if (wnd == IntPtr.Zero) return;
var rect = LastWindowRect.Value;
if (rect is { z: 0f, w: 0f }) return;
var x = Mathf.RoundToInt(rect.x);
var y = Mathf.RoundToInt(rect.y);
WinApi.SetWindowPos(wnd, IntPtr.Zero, x, y, 0, 0, 0x0235);
}
[HarmonyPrefix]
[HarmonyPatch(typeof(Screen), nameof(Screen.SetResolution), typeof(int), typeof(int), typeof(FullScreenMode), typeof(int))]
private static void Screen_SetResolution_Prefix(ref int width, ref int height, FullScreenMode fullscreenMode)
{
if (fullscreenMode is FullScreenMode.ExclusiveFullScreen or FullScreenMode.FullScreenWindow or FullScreenMode.MaximizedWindow || GameMain.isRunning) return;
var rect = LastWindowRect.Value;
if (rect is { z: 0f, w: 0f }) return;
var w = Mathf.RoundToInt(rect.z);
var h = Mathf.RoundToInt(rect.w);
width = w;
height = h;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(Screen), nameof(Screen.SetResolution), typeof(int), typeof(int), typeof(FullScreenMode), typeof(int))]
private static void Screen_SetResolution_Postfix(FullScreenMode fullscreenMode)
{
MoveWindowPosition();
}
private static void VFPreload_InvokeOnLoadWorkEnded_Postfix()
{
if (_loaded || Screen.fullScreenMode is FullScreenMode.ExclusiveFullScreen or FullScreenMode.FullScreenWindow or FullScreenMode.MaximizedWindow) return;
_loaded = true;
var wnd = WinApi.FindWindow(GameWindowClass, _gameWindowTitle);
if (wnd == IntPtr.Zero) return;
var rect = LastWindowRect.Value;
if (rect is { z: 0f, w: 0f }) return;
var x = Mathf.RoundToInt(rect.x);
var y = Mathf.RoundToInt(rect.y);
var w = Mathf.RoundToInt(rect.z);
var h = Mathf.RoundToInt(rect.w);
Screen.SetResolution(w, h, false);
WinApi.SetWindowPos(wnd, IntPtr.Zero, x, y, 0, 0, 0x0235);
if (EnableWindowResizeEnabled.Value)
WinApi.SetWindowLong(wnd, (int)WindowLongFlags.GWL_STYLE,
WinApi.GetWindowLong(wnd, (int)WindowLongFlags.GWL_STYLE) | (int)WindowStyles.WS_THICKFRAME | (int)WindowStyles.WS_MAXIMIZEBOX);
}
private static GameOption _gameOption;
[HarmonyPostfix]
[HarmonyPatch(typeof(UIOptionWindow), nameof(UIOptionWindow._OnOpen))]
private static void UIOptionWindow__OnOpen_Postfix()
{
_gameOption = DSPGame.globalOption;
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(GameOption), nameof(GameOption.Apply))]
private static IEnumerable<CodeInstruction> UIOptionWindow_ApplyOptions_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
var label1 = generator.DefineLabel();
matcher.MatchForward(false,
new CodeMatch(OpCodes.Call, AccessTools.Method(typeof(Screen), nameof(Screen.SetResolution), [typeof(int), typeof(int), typeof(bool), typeof(int)]))
).Advance(1).Labels.Add(label1);
matcher.Start().Insert(
Transpilers.EmitDelegate(() =>
_gameOption.fullscreen == DSPGame.globalOption.fullscreen &&
_gameOption.resolution.width == DSPGame.globalOption.resolution.width &&
_gameOption.resolution.height == DSPGame.globalOption.resolution.height &&
_gameOption.resolution.refreshRate == DSPGame.globalOption.resolution.refreshRate
),
new CodeInstruction(OpCodes.Brtrue, label1)
);
return matcher.InstructionEnumeration();
}
}
/*
private static class AutoSaveOpt
{
private static Harmony _patch;
public static void Enable(bool on)
{
if (on)
{
Directory.CreateDirectory(GameConfig.gameSaveFolder + "AutoSaves/");
_patch ??= Harmony.CreateAndPatchAll(typeof(AutoSaveOpt));
return;
}
_patch?.UnpatchSelf();
_patch = null;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(GameSave), nameof(GameSave.AutoSave))]
private static bool GameSave_AutoSave_Prefix(ref bool __result)
{
if (!GameSave.SaveCurrentGame(GameSave.AutoSaveTmp))
{
GlobalObject.SaveOpCounter();
__result = false;
return false;
}
var tmpFilename = GameConfig.gameSaveFolder + GameSave.AutoSaveTmp + GameSave.saveExt;
var targetFilename = $"{GameConfig.gameSaveFolder}AutoSaves/[{GameMain.data.gameDesc.clusterString}] {DateTime.Now:yyyy-MM-dd_hh-mm-ss}{GameSave.saveExt}";
File.Move(tmpFilename, targetFilename);
__result = true;
return false;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(UILoadGameWindow), nameof(UILoadGameWindow.RefreshList))]
public static void UILoadGameWindow_RefreshList_Postfix(UILoadGameWindow __instance)
{
var baseDir = GameConfig.gameSaveFolder + "AutoSaves/";
var files = Directory.GetFiles(baseDir, "*" + GameSave.saveExt, SearchOption.TopDirectoryOnly);
var entries = __instance.entries;
var entries2 = new List<UIGameSaveEntry>();
var entryPrefab = __instance.entryPrefab;
var entryPrefabParent = entryPrefab.transform.parent;
foreach (var f in files)
{
var fileInfo = new FileInfo(f);
var entry = Object.Instantiate(entryPrefab, entryPrefabParent);
entry.fileInfo = fileInfo;
entries2.Add(entry);
}
entries2.Sort((x, y) => -x.fileDate.CompareTo(y.fileDate));
if (entries2.Count > 10)
entries2.RemoveRange(10, entries2.Count - 10);
var autoSaveText = ">> " + "自动存档条目".Translate();
foreach (var entry in entries2)
{
entry.indexText.text = "";
var saveName = entry.saveName;
entry._saveName = $"AutoSaves/{saveName}";
var quoteIndex = saveName.IndexOf('[');
if (quoteIndex >= 0)
{
var quoteIndex2 = saveName.IndexOf(']', quoteIndex + 1);
if (quoteIndex2 > 0) saveName = saveName.Substring(quoteIndex, quoteIndex2 + 1 - quoteIndex);
}
entry.nameText.text = $"{autoSaveText} {saveName}";
entry.nameText.fontStyle = FontStyle.Italic;
entry.nameText.color = new Color(1f, 1f, 1f, 0.7f);
entry.timeText.text = $"{entry.fileDate:yyyy-MM-dd HH:mm:ss}";
GameSave.ReadModes(entry.fileInfo.FullName, out var isSandbox, out var isPeace);
if (entry.sandboxIcon != null)
{
entry.sandboxIcon.gameObject.SetActive(isSandbox);
}
if (entry.combatIcon != null)
{
entry.combatIcon.gameObject.SetActive(!isPeace);
}
entry.selected = false;
entry.gameObject.SetActive(true);
}
entries.AddRange(entries2);
entries.Sort((x, y) => -x.fileDate.CompareTo(y.fileDate));
var displayIndex = 1;
for (var i = 0; i < entries.Count; i++)
{
var entry = entries[i];
entry.index = i + 1;
entry.rectTrans.anchoredPosition = new Vector2(entry.rectTrans.anchoredPosition.x, -40 * i);
if (string.IsNullOrEmpty(entry.indexText.text)) continue;
entry.indexText.text = displayIndex.ToString();
displayIndex++;
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(UISaveGameWindow), nameof(UISaveGameWindow.RefreshList))]
public static void UISaveGameWindow_RefreshList_Postfix(UISaveGameWindow __instance)
{
var entries = __instance.entries;
entries.Sort((x, y) => -x.fileDate.CompareTo(y.fileDate));
for (var i = 0; i < entries.Count; i++)
{
var entry = entries[i];
entry.index = i + 1;
entry.rectTrans.anchoredPosition = new Vector2(entry.rectTrans.anchoredPosition.x, -40 * i);
entry.indexText.text = (i + 1).ToString();
}
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(UILoadGameWindow), nameof(UILoadGameWindow.DoLoadSelectedGame))]
[HarmonyPatch(typeof(UILoadGameWindow), nameof(UILoadGameWindow.OnSelectedChange))]
private static IEnumerable<CodeInstruction> UILoadGameWindow_ReplaceSaveName_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.Start().MatchForward(false,
new CodeMatch(OpCodes.Callvirt, AccessTools.PropertyGetter(typeof(UIGameSaveEntry), nameof(UIGameSaveEntry.saveName)))
);
matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldfld, AccessTools.Field(typeof(UIGameSaveEntry), nameof(UIGameSaveEntry._saveName))));
return matcher.InstructionEnumeration();
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(GameSave), nameof(GameSave.LoadCurrentGame))]
[HarmonyPatch(typeof(GameSave), nameof(GameSave.LoadGameDesc))]
[HarmonyPatch(typeof(GameSave), nameof(GameSave.ReadHeader))]
[HarmonyPatch(typeof(GameSave), nameof(GameSave.ReadHeaderAndDescAndProperty))]
[HarmonyPatch(typeof(GameSave), nameof(GameSave.SaveExist))]
[HarmonyPatch(typeof(GameSave), nameof(GameSave.SavePath))]
private static IEnumerable<CodeInstruction> GameSave_RemoveValidateOnLoad_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.Start().MatchForward(false,
new CodeMatch(OpCodes.Call, AccessTools.Method(typeof(CommonUtils), nameof(CommonUtils.ValidFileName)))
);
matcher.RemoveInstruction();
return matcher.InstructionEnumeration();
}
}
*/
private class ConvertSavesFromPeace: PatchImpl<ConvertSavesFromPeace>
{
private static bool _needConvert;
[HarmonyPostfix]
[HarmonyPatch(typeof(GameDesc), nameof(GameDesc.Import))]
private static void GameDesc_Import_Postfix(GameDesc __instance)
{
if (DSPGame.IsMenuDemo || !__instance.isPeaceMode) return;
__instance.combatSettings = UIRoot.instance.galaxySelect.uiCombat.combatSettings;
__instance.isPeaceMode = false;
_needConvert = true;
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(GameData), nameof(GameData.Import))]
private static IEnumerable<CodeInstruction> GameData_Import_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.Start().MatchForward(false,
new CodeMatch(OpCodes.Callvirt, AccessTools.PropertyGetter(typeof(Player), nameof(Player.mecha))),
new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(Mecha), nameof(Mecha.CheckCombatModuleDataIsValidPatch)))
);
matcher.Advance(2).Opcode = OpCodes.Brfalse;
matcher.Insert(
new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(ConvertSavesFromPeace), nameof(ConvertSavesFromPeace._needConvert)))
);
return matcher.InstructionEnumeration();
}
[HarmonyPostfix]
[HarmonyPatch(typeof(GameData), nameof(GameData.Import))]
private static void GameData_Import_Postfix()
{
_needConvert = false;
}
}
private class MouseCursorScaleUp: PatchImpl<MouseCursorScaleUp>
{
public static bool reload;
protected override void OnEnable()
{
if (!reload) return;
if (!UICursor.loaded) return;
UICursor.loaded = false;
UICursor.LoadCursors();
}
protected override void OnDisable()
{
if (!reload) return;
if (!UICursor.loaded) return;
UICursor.loaded = false;
UICursor.LoadCursors();
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(UICursor), nameof(UICursor.LoadCursors))]
private static IEnumerable<CodeInstruction> UICursor_LoadCursors_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
/*
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ldc_I4_S),
new CodeMatch(OpCodes.Newarr)
);
var startPos = matcher.Pos;
matcher.Advance(2).MatchForward(false,
new CodeMatch(OpCodes.Stsfld, AccessTools.Field(typeof(UICursor), nameof(UICursor.cursorTexs)))
);
var endPos = matcher.Pos + 1;
matcher.Start().Advance(startPos).RemoveInstructions(endPos - startPos);
matcher.InsertAndAdvance(
Transpilers.EmitDelegate(() =>
{
var pluginfolder = Util.PluginFolder;
UICursor.cursorTexs =
[
Util.LoadTexture($"{pluginfolder}/assets/cursor/cursor.png"),
Util.LoadTexture($"{pluginfolder}/assets/cursor/cursor-transfer.png"),
Util.LoadTexture($"{pluginfolder}/assets/cursor/cursor-target-in.png"),
Util.LoadTexture($"{pluginfolder}/assets/cursor/cursor-target-out.png"),
Util.LoadTexture($"{pluginfolder}/assets/cursor/cursor-target-a.png"),
Util.LoadTexture($"{pluginfolder}/assets/cursor/cursor-target-b.png"),
Util.LoadTexture($"{pluginfolder}/assets/cursor/cursor-ban.png"),
Util.LoadTexture($"{pluginfolder}/assets/cursor/cursor-delete.png"),
Util.LoadTexture($"{pluginfolder}/assets/cursor/cursor-reform.png"),
Util.LoadTexture($"{pluginfolder}/assets/cursor/cursor-dyson-node-create.png"),
Util.LoadTexture($"{pluginfolder}/assets/cursor/cursor-painter.png"),
Util.LoadTexture($"{pluginfolder}/assets/cursor/cursor-eyedropper.png"),
Util.LoadTexture($"{pluginfolder}/assets/cursor/cursor-eraser.png"),
Util.LoadTexture($"{pluginfolder}/assets/cursor/cursor-upgrade.png"),
Util.LoadTexture($"{pluginfolder}/assets/cursor/cursor-downgrade.png"),
Util.LoadTexture($"{pluginfolder}/assets/cursor/cursor-blank.png"),
Util.LoadTexture($"{pluginfolder}/assets/cursor/cursor-remove.png")
];
})
);
*/
matcher.MatchForward(false,
new CodeMatch(OpCodes.Stsfld, AccessTools.Field(typeof(UICursor), nameof(UICursor.cursorHots))),
new CodeMatch(OpCodes.Ldc_I4_1),
new CodeMatch(OpCodes.Stsfld, AccessTools.Field(typeof(UICursor), nameof(UICursor.loaded)))
).Advance(1).InsertAndAdvance(
Transpilers.EmitDelegate(() =>
{
var multiplier = MouseCursorScaleUpMultiplier.Value;
for (var i = 0; i < UICursor.cursorTexs.Length; i++)
{
var cursor = UICursor.cursorTexs[i];
if (cursor == null) continue;
var newWidth = 32 * multiplier;
var newHeight = 32 * multiplier;
if (cursor.width == newWidth && cursor.height == newHeight) continue;
UICursor.cursorTexs[i] = ResizeTexture2D(cursor, newWidth, newHeight);
}
if (multiplier <= 1) return;
for (var i = UICursor.cursorHots.Length - 1; i >= 0; i--)
{
UICursor.cursorHots[i] = new Vector2(UICursor.cursorHots[i].x * multiplier, UICursor.cursorHots[i].y * multiplier);
}
})
).MatchForward(false,
new CodeMatch(OpCodes.Ldc_I4_0),
new CodeMatch(OpCodes.Call, AccessTools.Method(typeof(Cursor), nameof(Cursor.SetCursor), [typeof(Texture2D), typeof(Vector2), typeof(CursorMode)]))
).SetInstruction(new CodeInstruction(OpCodes.Ldc_I4_1));
return matcher.InstructionEnumeration();
Texture2D ResizeTexture2D(Texture2D texture2D, int targetWidth, int targetHeight)
{
var oldActive = RenderTexture.active;
var rt = new RenderTexture(targetWidth, targetHeight, 32)
{
antiAliasing = 8
};
RenderTexture.active = rt;
Graphics.Blit(texture2D, rt);
rt.ResolveAntiAliasedSurface();
var result = new Texture2D(targetWidth, targetHeight, texture2D.format, false);
result.ReadPixels(new Rect(0, 0, targetWidth, targetHeight), 0, 0);
result.filterMode = FilterMode.Trilinear;
result.Apply();
RenderTexture.active = oldActive;
rt.Release();
return result;
}
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(UICursor), nameof(UICursor.cursorIndexApply), MethodType.Setter)]
private static IEnumerable<CodeInstruction> UICursor_set_cursorIndexApply_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.Start().MatchForward(false,
new CodeMatch(OpCodes.Ldc_I4_0),
new CodeMatch(OpCodes.Call, AccessTools.Method(typeof(Cursor), nameof(Cursor.SetCursor), [typeof(Texture2D), typeof(Vector2), typeof(CursorMode)]))
).SetInstruction(new CodeInstruction(OpCodes.Ldc_I4_1));
return matcher.InstructionEnumeration();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,95 @@
using System.Collections.Generic;
using System.Reflection.Emit;
using BepInEx.Configuration;
using HarmonyLib;
using UXAssist.Common;
namespace UXAssist.Patches;
public static class PlanetPatch
{
public static ConfigEntry<bool> PlayerActionsInGlobeViewEnabled;
public static void Init()
{
PlayerActionsInGlobeViewEnabled.SettingChanged += (_, _) => PlayerActionsInGlobeView.Enable(PlayerActionsInGlobeViewEnabled.Value);
PlayerActionsInGlobeView.Enable(PlayerActionsInGlobeViewEnabled.Value);
}
public static void Uninit()
{
PlayerActionsInGlobeView.Enable(false);
}
public class PlayerActionsInGlobeView: PatchImpl<PlayerActionsInGlobeView>
{
[HarmonyTranspiler]
[HarmonyPatch(typeof(VFInput), nameof(VFInput.UpdateGameStates))]
private static IEnumerable<CodeInstruction> VFInput_UpdateGameStates_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
/* remove UIGame.viewMode != EViewMode.Globe in two places:
* so search for:
* ldsfld bool VFInput::viewMode
* ldc.i4.3
*/
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ldsfld, AccessTools.Field(typeof(UIGame), nameof(UIGame.viewMode))),
new CodeMatch(OpCodes.Ldc_I4_3)
);
matcher.Repeat(codeMatcher =>
{
var labels = codeMatcher.Labels;
codeMatcher.Labels = [];
codeMatcher.RemoveInstructions(3).Labels.AddRange(labels);
});
return matcher.InstructionEnumeration();
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(PlayerController), nameof(PlayerController.GetInput))]
private static IEnumerable<CodeInstruction> PlayerController_GetInput_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
// replace `UIGame.viewMode >= EViewMode.Globe` with `UIGame.viewMode >= EViewMode.Starmap`
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ldsfld, AccessTools.Field(typeof(UIGame), nameof(UIGame.viewMode))),
new CodeMatch(OpCodes.Ldc_I4_3)
).Advance(1).Opcode = OpCodes.Ldc_I4_4;
return matcher.InstructionEnumeration();
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(PlayerAction_Rts), nameof(PlayerAction_Rts.GameTick))]
private static IEnumerable<CodeInstruction> PlayerAction_Rts_GameTick_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
var local1 = generator.DeclareLocal(typeof(bool));
// var local1 = UIGame.viewMode == 3;
matcher.MatchForward(false,
new CodeMatch(OpCodes.Call, AccessTools.PropertyGetter(typeof(VFInput), nameof(VFInput.rtsMoveCameraConflict))),
new CodeMatch(OpCodes.Stloc_1)
);
var labels = matcher.Labels;
matcher.Labels = [];
matcher.InsertAndAdvance(
new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(UIGame), nameof(UIGame.viewMode))).WithLabels(labels),
new CodeInstruction(OpCodes.Ldc_I4_3),
new CodeInstruction(OpCodes.Ceq),
new CodeInstruction(OpCodes.Stloc, local1)
);
// Add extra condition:
// VFInput.rtsMoveCameraConflict / VFInput.rtsMineCameraConflict `|| local1`
matcher.MatchForward(false,
new CodeMatch(instr => instr.opcode == OpCodes.Ldloc_1 || instr.opcode == OpCodes.Ldloc_2)
);
matcher.Repeat(codeMatcher =>
{
codeMatcher.Advance(1).InsertAndAdvance(
new CodeInstruction(OpCodes.Ldloc, local1),
new CodeInstruction(OpCodes.Or)
);
});
return matcher.InstructionEnumeration();
}
}
}

View File

@@ -0,0 +1,346 @@
using System.Collections.Generic;
using System.Reflection.Emit;
using BepInEx.Configuration;
using CommonAPI.Systems;
using HarmonyLib;
using UnityEngine;
using UXAssist.Common;
namespace UXAssist.Patches;
public static class PlayerPatch
{
public static ConfigEntry<bool> EnhancedMechaForgeCountControlEnabled;
public static ConfigEntry<bool> HideTipsForSandsChangesEnabled;
public static ConfigEntry<bool> AutoNavigationEnabled;
public static ConfigEntry<bool> AutoCruiseEnabled;
public static ConfigEntry<bool> AutoBoostEnabled;
public static ConfigEntry<double> DistanceToWarp;
private static PressKeyBind _autoDriveKey;
public static void Init()
{
EnhancedMechaForgeCountControlEnabled.SettingChanged += (_, _) => EnhancedMechaForgeCountControl.Enable(EnhancedMechaForgeCountControlEnabled.Value);
HideTipsForSandsChangesEnabled.SettingChanged += (_, _) => HideTipsForSandsChanges.Enable(HideTipsForSandsChangesEnabled.Value);
AutoNavigationEnabled.SettingChanged += (_, _) => AutoNavigation.Enable(AutoNavigationEnabled.Value);
EnhancedMechaForgeCountControl.Enable(EnhancedMechaForgeCountControlEnabled.Value);
HideTipsForSandsChanges.Enable(HideTipsForSandsChangesEnabled.Value);
AutoNavigation.Enable(AutoNavigationEnabled.Value);
_autoDriveKey = KeyBindings.RegisterKeyBinding(new BuiltinKey
{
key = new CombineKey(0, 0, ECombineKeyAction.OnceClick, true),
conflictGroup = KeyBindConflict.MOVEMENT | KeyBindConflict.FLYING | KeyBindConflict.SAILING | KeyBindConflict.BUILD_MODE_1 | KeyBindConflict.KEYBOARD_KEYBIND,
name = "ToggleAutoCruise",
canOverride = true
});
I18N.Add("AutoCruiseOn", "Auto-cruise enabled", "已启用自动巡航");
I18N.Add("AutoCruiseOff", "Auto-cruise disabled", "已禁用自动巡航");
}
public static void OnUpdate()
{
if (_autoDriveKey.keyValue)
{
AutoNavigation.ToggleAutoCruise();
}
}
public static void Uninit()
{
EnhancedMechaForgeCountControl.Enable(false);
HideTipsForSandsChanges.Enable(false);
AutoNavigation.Enable(false);
}
private class EnhancedMechaForgeCountControl: PatchImpl<EnhancedMechaForgeCountControl>
{
[HarmonyTranspiler]
[HarmonyPatch(typeof(UIReplicatorWindow), nameof(UIReplicatorWindow.OnOkButtonClick))]
private static IEnumerable<CodeInstruction> UIReplicatorWindow_OnOkButtonClick_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.MatchForward(false,
new CodeMatch(ci => ci.opcode == OpCodes.Ldc_I4_S && ci.OperandIs(10))
);
matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4, 1000));
return matcher.InstructionEnumeration();
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(UIReplicatorWindow), nameof(UIReplicatorWindow.OnPlusButtonClick))]
[HarmonyPatch(typeof(UIReplicatorWindow), nameof(UIReplicatorWindow.OnMinusButtonClick))]
private static IEnumerable<CodeInstruction> UIReplicatorWindow_OnPlusButtonClick_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var label1 = generator.DefineLabel();
var label2 = generator.DefineLabel();
var label3 = generator.DefineLabel();
var label4 = generator.DefineLabel();
var matcher = new CodeMatcher(instructions, generator);
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ldloc_0),
new CodeMatch(OpCodes.Ldc_I4_1),
new CodeMatch(o => o.opcode == OpCodes.Add || o.opcode == OpCodes.Sub)
).Advance(1).RemoveInstruction().InsertAndAdvance(
new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(VFInput), nameof(VFInput.control))),
new CodeInstruction(OpCodes.Brfalse_S, label1),
new CodeInstruction(OpCodes.Ldc_I4_S, 10),
new CodeInstruction(OpCodes.Br_S, label4),
new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(VFInput), nameof(VFInput.shift))).WithLabels(label1),
new CodeInstruction(OpCodes.Brfalse_S, label2),
new CodeInstruction(OpCodes.Ldc_I4_S, 100),
new CodeInstruction(OpCodes.Br_S, label4),
new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(VFInput), nameof(VFInput.alt))).WithLabels(label2),
new CodeInstruction(OpCodes.Brfalse_S, label3),
new CodeInstruction(OpCodes.Ldc_I4, 1000),
new CodeInstruction(OpCodes.Br_S, label4),
new CodeInstruction(OpCodes.Ldc_I4_1).WithLabels(label3)
).Labels.Add(label4);
matcher.MatchForward(false,
new CodeMatch(ci => ci.opcode == OpCodes.Ldc_I4_S && ci.OperandIs(10))
);
matcher.Repeat(m => m.SetAndAdvance(OpCodes.Ldc_I4, 1000));
return matcher.InstructionEnumeration();
}
}
private class HideTipsForSandsChanges: PatchImpl<HideTipsForSandsChanges>
{
[HarmonyTranspiler]
[HarmonyPatch(typeof(Player), nameof(Player.SetSandCount))]
private static IEnumerable<CodeInstruction> Player_SetSandCount_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.MatchForward(false,
new CodeMatch(OpCodes.Call, AccessTools.PropertySetter(typeof(Player), nameof(Player.sandCount)))
).Advance(1).Insert(new CodeInstruction(OpCodes.Ret));
return matcher.InstructionEnumeration();
}
}
public class AutoNavigation: PatchImpl<AutoNavigation>
{
private static bool _canUseWarper;
private static int _indicatorAstroId;
private static bool _speedUp;
private static Vector3 _direction;
public static void ToggleAutoCruise()
{
AutoCruiseEnabled.Value = !AutoCruiseEnabled.Value;
if (!DSPGame.IsMenuDemo && GameMain.isRunning)
{
UIRoot.instance.uiGame.generalTips.InvokeRealtimeTipAhead((AutoCruiseEnabled.Value ? "AutoCruiseOn" : "AutoCruiseOff").Translate());
}
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(PlayerController), nameof(PlayerController.GameTick))]
private static IEnumerable<CodeInstruction> PlayerController_GameTick_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.MatchForward(false,
new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(BuildModel), nameof(BuildModel.EarlyGameTickIgnoreActive)))
).Advance(1).InsertAndAdvance(
new CodeInstruction(OpCodes.Ldarg_0),
Transpilers.EmitDelegate((PlayerController controller) =>
{
/* Update target astro if changed */
_speedUp = false;
var player = controller.player;
var navi = player.navigation;
if (navi.indicatorAstroId != _indicatorAstroId)
{
_indicatorAstroId = navi.indicatorAstroId;
if (_indicatorAstroId == 0) return;
}
else if (_indicatorAstroId == 0) return;
switch (controller.movementStateInFrame)
{
case EMovementState.Walk:
case EMovementState.Drift:
if (!AutoCruiseEnabled.Value) return;
if (GameMain.localStar?.astroId == _indicatorAstroId) return;
/* Press jump key to fly */
controller.input0.z = 1f;
break;
case EMovementState.Fly:
if (!AutoCruiseEnabled.Value) return;
if (GameMain.localStar?.astroId == _indicatorAstroId) return;
/* Keep pressing jump and pullup key to sail */
controller.input0.y = 1f;
controller.input1.y = 1f;
break;
case EMovementState.Sail:
if (VFInput._pullUp.pressing || VFInput._pushDown.pressing || VFInput._moveLeft.pressing || VFInput._moveRight.pressing ||
(!player.warping && UIRoot.instance.uiGame.disableLockCursor && (VFInput._moveForward.pressing || VFInput._moveBackward.pressing)))
return;
var playerPos = player.uPosition;
var isHive = _indicatorAstroId > 1000000;
ref var astro = ref isHive ? ref GameMain.spaceSector.astros[_indicatorAstroId - 1000000] : ref GameMain.galaxy.astrosData[_indicatorAstroId];
var astroVec = astro.uPos - playerPos;
var distance = astroVec.magnitude;
if (distance < astro.type switch
{
EAstroType.Planet => 800.0 + astro.uRadius,
EAstroType.Star => 4000.0 + astro.uRadius,
EAstroType.EnemyHive => 400.0,
_ => 2000.0 + astro.uRadius
})
{
if (isHive)
{
player.uVelocity = Vector3.zero;
}
return;
}
var autoCruise = AutoCruiseEnabled.Value;
if (GameMain.instance.timei % 6 == 0 || _direction == Vector3.zero)
{
_direction = astroVec.normalized;
/* Check nearest astroes, try to bypass them */
var localStar = GameMain.localStar;
_canUseWarper = autoCruise && !player.warping && player.mecha.warpStorage.GetItemCount(1210) > 0;
if (localStar != null)
{
var nearestRange = (playerPos - localStar.uPosition).sqrMagnitude;
var nearestPos = localStar.uPosition;
var nearestAstroId = localStar.astroId;
foreach (var p in localStar.planets)
{
var range = (playerPos - p.uPosition).sqrMagnitude;
if (range >= nearestRange) continue;
nearestRange = range;
nearestPos = p.uPosition;
nearestAstroId = p.astroId;
}
/* If targeting hives, do not bypass them */
if (!isHive)
{
var hiveSys = GameMain.spaceSector.dfHives[localStar.index];
while (hiveSys != null)
{
if (hiveSys.realized && hiveSys.hiveAstroId > 1000000)
{
ref var hiveAstro = ref GameMain.spaceSector.astros[hiveSys.hiveAstroId - 1000000];
/* Divide by 4, so that the real range is 2 times of the calculated range,
which means the minimal range allowed is 4000 */
var range = (playerPos - hiveAstro.uPos).sqrMagnitude / 4.0;
if (range < nearestRange)
{
nearestRange = range;
nearestPos = hiveAstro.uPos;
nearestAstroId = hiveSys.hiveAstroId;
}
}
hiveSys = hiveSys.nextSibling;
}
}
if (nearestAstroId != _indicatorAstroId && nearestRange < 2000.0 * 2000.0)
{
Vector3 leavingDirection = (playerPos - nearestPos).normalized;
var dot = Vector3.Dot(leavingDirection, _direction);
if (dot < 0)
{
var cross = Vector3.Cross(_direction, leavingDirection);
_direction = Vector3.Cross(leavingDirection, cross).normalized;
}
else
{
_direction = leavingDirection;
}
}
}
}
Vector3 uVel = player.uVelocity;
var speed = uVel.magnitude;
if (player.warping)
{
_speedUp = false;
if (autoCruise)
{
/* Speed down if too close */
var actionSail = controller.actionSail;
if (distance < GalaxyData.LY * 1.5)
{
if (distance < actionSail.currentWarpSpeed * distance switch
{
> GalaxyData.LY * 0.6 => 0.33,
> GalaxyData.LY * 0.3 => 0.5,
> GalaxyData.LY * 0.1 => 0.66,
_ => 1.0
})
{
controller.input0.y = -1f;
}
}
}
}
else
{
var mecha = player.mecha;
var energyRatio = mecha.coreEnergy / mecha.coreEnergyCap;
if (_canUseWarper && GameMain.localPlanet == null && distance > GalaxyData.AU * DistanceToWarp.Value && energyRatio >= 0.8 && player.mecha.UseWarper())
{
player.warpCommand = true;
VFAudio.Create("warp-begin", player.transform, Vector3.zero, true);
}
else
{
/* Speed up if needed */
_speedUp = autoCruise && AutoBoostEnabled.Value && speed + 0.2f < player.mecha.maxSailSpeed && energyRatio >= 0.1;
}
}
/* Update direction, gracefully rotate for 2 degrees for each frame */
var angle = Vector3.Angle(uVel, _direction);
if (angle < 2f)
{
player.uVelocity = _direction * speed;
}
else
{
player.uVelocity = Vector3.Slerp(uVel, _direction * speed, 2f / angle);
}
break;
default:
_speedUp = false;
break;
}
})
);
return matcher.InstructionEnumeration();
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(VFInput), nameof(VFInput._sailSpeedUp), MethodType.Getter)]
private static IEnumerable<CodeInstruction> VFInput_sailSpeedUp_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ret)
);
matcher.Repeat(m => m.InsertAndAdvance(
new CodeInstruction(OpCodes.Ldsfld, AccessTools.Field(typeof(AutoNavigation), nameof(_speedUp))),
new CodeInstruction(OpCodes.Or)
).Advance(1));
return matcher.InstructionEnumeration();
}
/* Disable Lock Cursor Mode on entering sail panel
[HarmonyPrefix]
[HarmonyPatch(typeof(UISailPanel), nameof(UISailPanel._OnOpen))]
public static void OnOpen_Prefix()
{
if (_aimingEnabled)
{
UIRoot.instance.uiGame.disableLockCursor = true;
}
}
*/
}
}

View File

@@ -0,0 +1,400 @@
using System;
using System.Collections.Generic;
using System.Reflection.Emit;
using BepInEx.Configuration;
using HarmonyLib;
using UnityEngine;
using UXAssist.Common;
namespace UXAssist.Patches;
public static class TechPatch
{
public static ConfigEntry<bool> SorterCargoStackingEnabled;
public static ConfigEntry<bool> BatchBuyoutTechEnabled;
public static void Init()
{
I18N.Add("分拣器运货量", "Sorter Mk.III cargo stacking : ", "极速分拣器每次可运送 ");
SorterCargoStackingEnabled.SettingChanged += (_, _) => SorterCargoStacking.Enable(SorterCargoStackingEnabled.Value);
BatchBuyoutTechEnabled.SettingChanged += (_, _) => BatchBuyoutTech.Enable(BatchBuyoutTechEnabled.Value);
SorterCargoStacking.Enable(SorterCargoStackingEnabled.Value);
BatchBuyoutTech.Enable(BatchBuyoutTechEnabled.Value);
}
public static void Uninit()
{
BatchBuyoutTech.Enable(false);
SorterCargoStacking.Enable(false);
}
private class SorterCargoStacking: PatchImpl<SorterCargoStacking>
{
private static bool _protoPatched;
protected override void OnEnable()
{
TryPatchProto(true);
GameLogic.OnDataLoaded += VFPreload_InvokeOnLoadWorkEnded_Postfix;
}
protected override void OnDisable()
{
GameLogic.OnDataLoaded -= VFPreload_InvokeOnLoadWorkEnded_Postfix;
TryPatchProto(false);
}
private static void TryPatchProto(bool on)
{
var techs = LDB.techs;
if (techs == null || techs.dataArray == null || techs.dataArray.Length == 0) return;
if (on)
{
var delim = -26.0f;
var tp3301 = techs.Select(3301);
if (tp3301 != null && tp3301.IsObsolete)
{
_protoPatched = false;
delim = tp3301.Position.y + 1.0f;
}
if (_protoPatched) return;
foreach (var tp in techs.dataArray)
{
switch (tp.ID)
{
case >= 3301 and <= 3305:
tp.UnlockValues[0] = tp.ID - 3300 + 1;
tp.IsObsolete = false;
continue;
case 3306:
tp.PreTechs = [];
continue;
}
if (tp.Position.y > delim) continue;
tp.Position.y -= 4.0f;
}
_protoPatched = true;
}
else
{
var delim = -28.0f;
var tp3301 = techs.Select(3301);
if (tp3301 != null && !tp3301.IsObsolete)
{
_protoPatched = true;
delim = tp3301.Position.y - 1.0f;
}
if (!_protoPatched) return;
foreach (var tp in techs.dataArray)
{
if (tp.ID is >= 3301 and <= 3306)
{
tp.IsObsolete = true;
continue;
}
if (tp.Position.y > delim) continue;
tp.Position.y += 4.0f;
}
_protoPatched = false;
}
}
private static void VFPreload_InvokeOnLoadWorkEnded_Postfix()
{
TryPatchProto(true);
}
}
private class BatchBuyoutTech: PatchImpl<BatchBuyoutTech>
{
private static void GenerateTechList(GameHistoryData history, int techId, List<int> techIdList)
{
var techProto = LDB.techs.Select(techId);
if (techProto == null || !techProto.Published) return;
var flag = true;
for (var i = 0; i < 2; i++)
{
var array = techProto.PreTechs;
if (i == 1)
{
array = techProto.PreTechsImplicit;
}
for (var j = 0; j < array.Length; j++)
{
if (!history.techStates.ContainsKey(array[j]) || history.techStates[array[j]].unlocked) continue;
if (history.techStates[array[j]].maxLevel > history.techStates[array[j]].curLevel)
{
flag = false;
}
GenerateTechList(history, array[j], techIdList);
}
}
if (history.techStates.ContainsKey(techId) && !history.techStates[techId].unlocked && flag)
{
techIdList.Add(techId);
}
}
private static void CheckTechUnlockProperties(GameHistoryData history, TechProto techProto, int[] properties, List<Tuple<TechProto, int, int>> techList, int maxLevel = 10000)
{
var techStates = history.techStates;
var techID = techProto.ID;
if (techStates == null || !techStates.TryGetValue(techID, out var value)) return;
if (value.unlocked) return;
var maxLvl = Math.Min(maxLevel < 0 ? value.curLevel - maxLevel - 1 : maxLevel, value.maxLevel);
foreach (var preid in techProto.PreTechs)
{
var preProto = LDB.techs.Select(preid);
if (preProto != null)
CheckTechUnlockProperties(history, preProto, properties, techList, techProto.PreTechsMax ? 10000 : preProto.Level);
}
foreach (var preid in techProto.PreTechsImplicit)
{
var preProto = LDB.techs.Select(preid);
if (preProto != null)
CheckTechUnlockProperties(history, preProto, properties, techList, techProto.PreTechsMax ? 10000 : preProto.Level);
}
if (value.curLevel < techProto.Level) value.curLevel = techProto.Level;
techList.Add(new Tuple<TechProto, int, int>(techProto, value.curLevel, techProto.Level));
while (value.curLevel <= maxLvl)
{
if (techProto.PropertyOverrideItemArray != null)
{
var propertyOverrideItemArray = techProto.PropertyOverrideItemArray;
for (var i = 0; i < propertyOverrideItemArray.Length; i++)
{
var id = propertyOverrideItemArray[i].id;
var count = (float)propertyOverrideItemArray[i].count;
var ratio = Mathf.Clamp01((float)((double)value.hashUploaded / value.hashNeeded));
var consume = Mathf.CeilToInt(count * (1f - ratio));
properties[id - 6001] += consume;
}
}
else
{
for (var j = 0; j < techProto.itemArray.Length; j++)
{
var id = techProto.itemArray[j].ID;
var consume = (int)(techProto.ItemPoints[j] * (value.hashNeeded - value.hashUploaded) / 3600L);
properties[id - 6001] += consume;
}
}
value.curLevel++;
value.hashUploaded = 0;
value.hashNeeded = techProto.GetHashNeeded(value.curLevel);
}
}
private static int UnlockTechRecursiveImpl(GameHistoryData history, TechProto techProto, int maxLevel = 10000)
{
var techStates = history.techStates;
var techID = techProto.ID;
if (techStates == null || !techStates.TryGetValue(techID, out var value))
{
return -1;
}
if (value.unlocked)
{
return -1;
}
var maxLvl = Math.Min(maxLevel < 0 ? value.curLevel - maxLevel - 1 : maxLevel, value.maxLevel);
foreach (var preid in techProto.PreTechs)
{
var preProto = LDB.techs.Select(preid);
if (preProto != null)
UnlockTechRecursiveImpl(history, preProto, techProto.PreTechsMax ? 10000 : preProto.Level);
}
foreach (var preid in techProto.PreTechsImplicit)
{
var preProto = LDB.techs.Select(preid);
if (preProto != null)
UnlockTechRecursiveImpl(history, preProto, techProto.PreTechsMax ? 10000 : preProto.Level);
}
if (value.curLevel < techProto.Level) value.curLevel = techProto.Level;
while (value.curLevel <= maxLvl)
{
if (value.curLevel == 0)
{
foreach (var recipe in techProto.UnlockRecipes)
{
history.UnlockRecipe(recipe);
}
}
for (var j = 0; j < techProto.UnlockFunctions.Length; j++)
{
history.UnlockTechFunction(techProto.UnlockFunctions[j], techProto.UnlockValues[j], value.curLevel);
}
for (var k = 0; k < techProto.AddItems.Length; k++)
{
history.GainTechAwards(techProto.AddItems[k], techProto.AddItemCounts[k]);
}
value.curLevel++;
}
value.unlocked = maxLvl >= value.maxLevel;
value.curLevel = value.unlocked ? maxLvl : maxLvl + 1;
value.hashNeeded = techProto.GetHashNeeded(value.curLevel);
value.hashUploaded = value.unlocked ? value.hashNeeded : 0;
techStates[techID] = value;
return maxLvl;
}
private static bool UnlockTechRecursive(TechProto techProto, int maxLevel = 10000)
{
if (techProto == null) return false;
var history = GameMain.history;
var ulvl = UnlockTechRecursiveImpl(history, techProto, maxLevel);
if (ulvl < 0) return false;
history.RegFeatureKey(1000100);
history.NotifyTechUnlock(techProto.ID, ulvl);
return true;
}
[HarmonyTranspiler]
[HarmonyPatch(typeof(UITechNode), nameof(UITechNode.UpdateInfoDynamic))]
private static IEnumerable<CodeInstruction> UITechNode_UpdateInfoDynamic_Transpiler(IEnumerable<CodeInstruction> instructions)
{
var matcher = new CodeMatcher(instructions);
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ldsfld, AccessTools.Field(typeof(UITechTree), nameof(UITechTree.showProperty))),
new CodeMatch(ci => ci.IsLdloc()),
new CodeMatch(OpCodes.And)
).Advance(1).SetAndAdvance(OpCodes.Ldloc_3, null).InsertAndAdvance(
new CodeInstruction(OpCodes.Ldc_I4_0),
new CodeInstruction(OpCodes.Ceq)
);
return matcher.InstructionEnumeration();
}
[HarmonyPrefix]
[HarmonyPatch(typeof(UITechNode), nameof(UITechNode.OnBuyoutButtonClick))]
private static bool UITechNode_OnBuyoutButtonClick_Prefix(UITechNode __instance)
{
if (GameMain.isFullscreenPaused)
{
return false;
}
var techProto = __instance.techProto;
if (techProto == null) return false;
var properties = new int[6];
List<Tuple<TechProto, int, int>> techList = new();
var history = GameMain.history;
var maxLevel = -1;
CheckTechUnlockProperties(history, techProto, properties, techList, maxLevel);
var propertySystem = DSPGame.propertySystem;
var clusterSeedKey = history.gameData.GetClusterSeedKey();
var enough = true;
for (var i = 0; i < 6; i++)
{
if (propertySystem.GetItemAvaliableProperty(clusterSeedKey, 6001 + i) >= properties[i]) continue;
enough = false;
break;
}
if (!enough)
{
UIRealtimeTip.Popup("元数据不足".Translate(), true, 0);
return false;
}
if (!history.hasUsedPropertyBanAchievement)
{
UIMessageBox.Show("初次使用元数据标题".Translate(), "初次使用元数据描述".Translate(), "取消".Translate(), "确定".Translate(), 2, null, DoUnlockFunc);
return false;
}
DoUnlockFunc();
return false;
void DoUnlockFunc()
{
if (techList.Count <= 1)
{
DoUnlockFuncInternal();
return;
}
var msg = "要使用元数据买断以下科技吗?";
if (techList.Count <= 10)
{
foreach (var tuple in techList)
{
AddToMsg(ref msg, tuple);
}
}
else
{
for (var i = 0; i < 5; i++)
{
AddToMsg(ref msg, techList[i]);
}
msg += " ...\n";
for (var i = techList.Count - 5; i < techList.Count; i++)
{
AddToMsg(ref msg, techList[i]);
}
}
msg += "\n\n";
msg += "以下是买断所需元数据:";
for (var i = 0; i < 6; i++)
{
var itemCount = properties[i];
if (itemCount <= 0) continue;
msg += $"\n {LDB.items.Select(6001 + i).propertyName}x{itemCount}";
}
UIMessageBox.Show("批量买断科技", msg, "取消".Translate(), "确定".Translate(), 2, null, DoUnlockFuncInternal);
return;
void AddToMsg(ref string str, Tuple<TechProto, int, int> tuple)
{
if (tuple.Item2 == tuple.Item3)
{
if (tuple.Item2 <= 0)
str += $"\n {tuple.Item1.name}";
else
str += $"\n {tuple.Item1.name}{"".Translate()}{tuple.Item2}";
}
else
str += $"\n {tuple.Item1.name}{"".Translate()}{tuple.Item2}->{tuple.Item3}";
}
}
void DoUnlockFuncInternal()
{
UnlockTechRecursive(__instance.techProto, maxLevel);
history.VarifyTechQueue();
if (history.currentTech != history.techQueue[0])
{
history.currentTech = history.techQueue[0];
}
var mainPlayer = GameMain.mainPlayer;
for (var i = 0; i < 6; i++)
{
var itemCount = properties[i];
if (itemCount <= 0) continue;
var itemId = 6001 + i;
propertySystem.AddItemConsumption(clusterSeedKey, itemId, itemCount);
history.AddPropertyItemConsumption(itemId, itemCount, true);
mainPlayer.mecha.AddProductionStat(itemId, itemCount, mainPlayer.nearestFactory);
mainPlayer.mecha.AddConsumptionStat(itemId, itemCount, mainPlayer.nearestFactory);
}
}
}
}
}