fix(CheatEnabler): restore BuildToolOpt station replacement

This commit is contained in:
2026-07-12 18:30:20 +08:00
parent 66ecebaca7
commit 010ec73c8d
7 changed files with 21 additions and 7 deletions
+1 -1
View File
@@ -150,6 +150,6 @@ The sync is implemented as an inline PowerShell `Exec` step inside the `ZipMod`
- **Fail-soft patch application:** `PatchImpl<T>.Enable(true)` applies Harmony patches inside a try/catch. Runtime patching can fail through no fault of ours (Harmony re-runs other mods' transpilers on shared target methods), and an escaping exception would abort the calling `ConfigEntry.SettingChanged` delegate chain, desyncing config UI from config values. On failure it logs a `LogError` with the feature type name, rolls back via `UnpatchSelf()`, and leaves `_patch` null so a later `Enable(true)` can retry.
- **Convergent in-game UI state:** In-game overlay widgets whose visibility depends on game state (e.g. `AutoConstructUI`) must not rely solely on one-shot event-driven refreshes (`SettingChanged` handlers, patch `OnEnable`/`OnDisable`), because a thrown exception earlier in a delegate chain or a failed patch application silently drops the refresh. `AutoConstructUI.OnUpdate()` reconciles button visibility and the pending-construction count with actual game state every 30 frames (also effective while paused); the `AutoConstructPatch` postfix on `PlayerAction_Rts.GameTick` only implements the fly-to-target behavior, and `AutoConstructPatch.OnEnable` logs its visibility predicate inputs once per enable as a remote-diagnosis aid.
- **Transpiler patches:** Performance-critical mods (LabOpt, MechaDronesTweaks) use `[HarmonyTranspiler]` to rewrite IL instructions directly for maximum efficiency. All transpilers in UXAssist, CheatEnabler, and UniverseGenTweaks carry a standard header comment (`// Harmony transpiler:`, `// Target:`, `// Fallback:`) documenting the target method and fallback behavior. `UXAssist.Common.Patching.TranspilerGuard` provides a reusable `CodeMatcher.Finish` helper that returns original instructions when a matcher becomes invalid.
- **Mod-compatibility reflection:** Use `UXAssist.Common.ModCompat.ModCompatHelper` for BepInEx plugin detection, external mod type/method/field resolution, and property-setter lookup. Use `UXAssist.Common.Utils.DysonSphereReflection` for the DSPOptimizations-compatible `DysonSphereLayer` private fields (`totalNodeSP`, `totalFrameSP`, `totalCP`) instead of resolving them locally in each consumer.
- **Mod-compatibility reflection:** Use `UXAssist.Common.ModCompat.ModCompatHelper` for BepInEx plugin detection, external mod type/method/field resolution, and property-setter lookup. Preserve the old public type identity with a forwarding or inherited compatibility facade when a refactor moves a type that external mods may locate through reflection; build and verify that the legacy reflection target forwards to the refactored implementation. Use `UXAssist.Common.Utils.DysonSphereReflection` for the DSPOptimizations-compatible `DysonSphereLayer` private fields (`totalNodeSP`, `totalFrameSP`, `totalCP`) instead of resolving them locally in each consumer.
- **Build quality gates:** Root `.editorconfig` defines suggestion-only C# style conventions. `Directory.Build.props` enables `TreatWarningsAsErrors` with `NoWarn>0618` for the expected obsolete-API usage, so any new warning fails the build. `.github/workflows/build.yml` runs a Release build and packages the three main mods on every push/PR.
- **Save persistence:** Mods that need to persist data use the `IModCanSave` interface from DSPModSave.
+1
View File
@@ -3,6 +3,7 @@ using BepInEx;
using BepInEx.Configuration;
using CheatEnabler.Patches;
using CheatEnabler.Patches.Factory;
using FactoryPatch = CheatEnabler.Patches.Factory.FactoryPatch;
using UXAssist.Common;
using UXAssist.Common.ModFeatures;
@@ -12,7 +12,7 @@ internal class ArchitectMode : PatchImpl<ArchitectMode>
var factory = GameMain.mainPlayer?.factory;
if (factory?.planet?.data != null)
{
FactoryPatch.ArrivePlanet(factory);
global::CheatEnabler.Patches.FactoryPatch.ArrivePlanet(factory);
}
}
+2 -2
View File
@@ -188,7 +188,7 @@ public class FactoryPatch : PatchImpl<FactoryPatch>
var main = GameMain.instance;
if (main != null && main._running && __instance.factory?.planet?.data != null)
{
ArrivePlanet(__instance.factory);
global::CheatEnabler.Patches.FactoryPatch.ArrivePlanet(__instance.factory);
}
}
@@ -198,7 +198,7 @@ public class FactoryPatch : PatchImpl<FactoryPatch>
var factory = GameMain.mainPlayer?.factory;
if (factory?.planet?.data != null)
{
ArrivePlanet(factory);
global::CheatEnabler.Patches.FactoryPatch.ArrivePlanet(factory);
}
GameMain.data?.warningSystem?.UpdateCriticalWarningText();
}
@@ -121,7 +121,7 @@ internal class ImmediateBuild : PatchImpl<ImmediateBuild>
var factory = GameMain.mainPlayer?.factory;
if (factory?.planet?.data != null)
{
FactoryPatch.ArrivePlanet(factory);
global::CheatEnabler.Patches.FactoryPatch.ArrivePlanet(factory);
}
}
// Harmony transpiler: Transpiler
@@ -157,7 +157,7 @@ internal class ImmediateBuild : PatchImpl<ImmediateBuild>
matcher.Insert(
new CodeInstruction(OpCodes.Ldarg_0).WithLabels(labels),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(BuildTool), nameof(BuildTool.factory))),
new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(FactoryPatch), nameof(FactoryPatch.ArrivePlanet)))
new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(global::CheatEnabler.Patches.FactoryPatch), nameof(global::CheatEnabler.Patches.FactoryPatch.ArrivePlanet)))
);
return matcher.InstructionEnumeration();
}
@@ -255,7 +255,7 @@ internal class ImmediateBuild : PatchImpl<ImmediateBuild>
var factory = GameMain.mainPlayer?.factory;
if (factory?.planet?.data != null)
{
FactoryPatch.ArrivePlanet(factory);
global::CheatEnabler.Patches.FactoryPatch.ArrivePlanet(factory);
}
}
}
@@ -0,0 +1,12 @@
namespace CheatEnabler.Patches;
/// <summary>
/// Preserves the pre-refactor type and method identity used by mods that locate FactoryPatch through reflection.
/// </summary>
public static class FactoryPatch
{
public static void ArrivePlanet(PlanetFactory factory)
{
Factory.FactoryPatch.ArrivePlanet(factory);
}
}
+1
View File
@@ -1,6 +1,7 @@
using CheatEnabler.Functions;
using CheatEnabler.Patches;
using CheatEnabler.Patches.Factory;
using FactoryPatch = CheatEnabler.Patches.Factory.FactoryPatch;
using UnityEngine;
using UnityEngine.UI;
using UXAssist.UI;