mirror of
https://github.com/soarqin/DSP_Mods.git
synced 2026-08-06 01:20:18 +08:00
fix(CheatEnabler): restore BuildToolOpt station replacement
This commit is contained in:
@@ -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.
|
- **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.
|
- **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.
|
- **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.
|
- **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.
|
- **Save persistence:** Mods that need to persist data use the `IModCanSave` interface from DSPModSave.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using BepInEx;
|
|||||||
using BepInEx.Configuration;
|
using BepInEx.Configuration;
|
||||||
using CheatEnabler.Patches;
|
using CheatEnabler.Patches;
|
||||||
using CheatEnabler.Patches.Factory;
|
using CheatEnabler.Patches.Factory;
|
||||||
|
using FactoryPatch = CheatEnabler.Patches.Factory.FactoryPatch;
|
||||||
using UXAssist.Common;
|
using UXAssist.Common;
|
||||||
using UXAssist.Common.ModFeatures;
|
using UXAssist.Common.ModFeatures;
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ internal class ArchitectMode : PatchImpl<ArchitectMode>
|
|||||||
var factory = GameMain.mainPlayer?.factory;
|
var factory = GameMain.mainPlayer?.factory;
|
||||||
if (factory?.planet?.data != null)
|
if (factory?.planet?.data != null)
|
||||||
{
|
{
|
||||||
FactoryPatch.ArrivePlanet(factory);
|
global::CheatEnabler.Patches.FactoryPatch.ArrivePlanet(factory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ public class FactoryPatch : PatchImpl<FactoryPatch>
|
|||||||
var main = GameMain.instance;
|
var main = GameMain.instance;
|
||||||
if (main != null && main._running && __instance.factory?.planet?.data != null)
|
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;
|
var factory = GameMain.mainPlayer?.factory;
|
||||||
if (factory?.planet?.data != null)
|
if (factory?.planet?.data != null)
|
||||||
{
|
{
|
||||||
ArrivePlanet(factory);
|
global::CheatEnabler.Patches.FactoryPatch.ArrivePlanet(factory);
|
||||||
}
|
}
|
||||||
GameMain.data?.warningSystem?.UpdateCriticalWarningText();
|
GameMain.data?.warningSystem?.UpdateCriticalWarningText();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ internal class ImmediateBuild : PatchImpl<ImmediateBuild>
|
|||||||
var factory = GameMain.mainPlayer?.factory;
|
var factory = GameMain.mainPlayer?.factory;
|
||||||
if (factory?.planet?.data != null)
|
if (factory?.planet?.data != null)
|
||||||
{
|
{
|
||||||
FactoryPatch.ArrivePlanet(factory);
|
global::CheatEnabler.Patches.FactoryPatch.ArrivePlanet(factory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Harmony transpiler: Transpiler
|
// Harmony transpiler: Transpiler
|
||||||
@@ -157,7 +157,7 @@ internal class ImmediateBuild : PatchImpl<ImmediateBuild>
|
|||||||
matcher.Insert(
|
matcher.Insert(
|
||||||
new CodeInstruction(OpCodes.Ldarg_0).WithLabels(labels),
|
new CodeInstruction(OpCodes.Ldarg_0).WithLabels(labels),
|
||||||
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(BuildTool), nameof(BuildTool.factory))),
|
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();
|
return matcher.InstructionEnumeration();
|
||||||
}
|
}
|
||||||
@@ -255,7 +255,7 @@ internal class ImmediateBuild : PatchImpl<ImmediateBuild>
|
|||||||
var factory = GameMain.mainPlayer?.factory;
|
var factory = GameMain.mainPlayer?.factory;
|
||||||
if (factory?.planet?.data != null)
|
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,6 +1,7 @@
|
|||||||
using CheatEnabler.Functions;
|
using CheatEnabler.Functions;
|
||||||
using CheatEnabler.Patches;
|
using CheatEnabler.Patches;
|
||||||
using CheatEnabler.Patches.Factory;
|
using CheatEnabler.Patches.Factory;
|
||||||
|
using FactoryPatch = CheatEnabler.Patches.Factory.FactoryPatch;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
using UXAssist.UI;
|
using UXAssist.UI;
|
||||||
|
|||||||
Reference in New Issue
Block a user