fix: Auto-Construct panel visibility on panel switching

This commit is contained in:
2026-07-11 00:53:06 +08:00
parent db15c65961
commit 6b03801681
4 changed files with 56 additions and 30 deletions
+24 -1
View File
@@ -40,7 +40,30 @@ public class PatchImpl<T> where T : PatchImpl<T>, new()
var guid = typeof(T).GetCustomAttribute<PatchGuidAttribute>()?.Guid ?? $"PatchImpl.{typeof(T).FullName ?? typeof(T).ToString()}";
var callOnEnableBefore = typeof(T).GetCustomAttributes<PatchSetCallbackFlagAttribute>().Any(n => n.Flag == PatchCallbackFlag.CallOnEnableBeforePatch);
if (callOnEnableBefore) thisInstance.OnEnable();
thisInstance._patch = Harmony.CreateAndPatchAll(typeof(T), guid);
// Fail-soft patch application: applying patches at runtime can fail through no
// fault of ours (e.g. Harmony re-runs another mod's fragile transpiler on a shared
// target method). An escaping exception would abort the caller, which is usually a
// ConfigEntry.SettingChanged handler chain, leaving later handlers (such as config
// UI state sync) unexecuted. Log the failure and roll back instead of throwing.
var patch = new Harmony(guid);
try
{
patch.PatchAll(typeof(T));
}
catch (Exception e)
{
UXAssist.Logger.LogError($"Failed to apply Harmony patches for {typeof(T).FullName}: {e}");
try
{
patch.UnpatchSelf();
}
catch (Exception e2)
{
UXAssist.Logger.LogError($"Failed to roll back partially applied patches for {typeof(T).FullName}: {e2}");
}
return;
}
thisInstance._patch = patch;
if (!callOnEnableBefore) thisInstance.OnEnable();
return;
}
+16
View File
@@ -11,6 +11,7 @@ internal static class AutoConstructUI
public static MyCheckButton ToggleAutoConstruct;
public static GameObject ConstructCountPanel;
public static Text ConstructCountText;
private static int _lastPrebuildCount = -1;
public static void Init()
{
@@ -30,6 +31,21 @@ internal static class AutoConstructUI
public static void OnUpdate()
{
// Self-healing state sync: the event-driven refreshes (config SettingChanged handlers,
// patch OnEnable/OnDisable) are one-shot and can be lost, e.g. when an exception thrown
// by an earlier handler aborts the delegate chain, or when Harmony patch application
// fails. Periodically reconcile the button visibility and the pending-construction count
// with the actual game state so a missed event never leaves the UI stuck. This also
// works while the game is paused, unlike the PlayerAction_Rts.GameTick polling.
if (Time.frameCount % 30 != 0) return;
if (ToggleAutoConstruct == null) return;
UpdateToggleAutoConstructCheckButtonVisiblility();
var localPlanet = GameMain.localPlanet;
if (localPlanet == null || !localPlanet.factoryLoaded) return;
var prebuildCount = localPlanet.factory.prebuildCount;
if (prebuildCount == _lastPrebuildCount) return;
_lastPrebuildCount = prebuildCount;
UpdateConstructCountText(prebuildCount);
}
public static void InitToggleAutoConstructCheckButton()
+13 -29
View File
@@ -11,55 +11,39 @@ internal static class FactoryBuildPatches
{
internal class AutoConstructPatch : PatchImpl<AutoConstructPatch>
{
private static int _lastPrebuildCount = -1;
protected override void OnEnable()
{
Functions.UIFunctions.UpdateToggleAutoConstructCheckButtonVisiblility();
// Diagnostic aid for reports of the auto-construct button not showing up:
// log the visibility predicate inputs once per enable.
var planet = GameMain.localPlanet;
var factoryLoaded = planet != null && planet.factoryLoaded;
UXAssist.Logger.LogInfo(
$"AutoConstruct button enabled: buttonCreated={Functions.UI.AutoConstructUI.ToggleAutoConstruct != null}, " +
$"localPlanet={planet != null}, factoryLoaded={factoryLoaded}, " +
$"prebuildCount={(factoryLoaded ? planet.factory.prebuildCount : 0)}");
}
protected override void OnDisable()
{
Functions.UIFunctions.UpdateToggleAutoConstructCheckButtonVisiblility();
_lastPrebuildCount = -1;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(PlanetData), nameof(PlanetData.NotifyFactoryLoaded))]
private static void PlanetData_NotifyFactoryLoaded_Postfix()
{
Functions.UIFunctions.UpdateToggleAutoConstructCheckButtonVisiblility();
_lastPrebuildCount = -1;
}
[HarmonyPostfix]
[HarmonyPatch(typeof(PlanetData), nameof(PlanetData.UnloadFactory))]
private static void PlanetData_UnloadFactory_Postfix()
{
Functions.UIFunctions.UpdateToggleAutoConstructCheckButtonVisiblility();
_lastPrebuildCount = -1;
}
// Button visibility and the pending-construction count text are reconciled periodically
// in AutoConstructUI.OnUpdate() (independent of Harmony patch state), so this postfix
// only implements the auto-construct fly-to-target behavior. This also keeps the patch
// surface small: PlanetData.NotifyFactoryLoaded/UnloadFactory no longer need postfixes.
[HarmonyPostfix]
[HarmonyPatch(typeof(PlayerAction_Rts), nameof(PlayerAction_Rts.GameTick))]
private static void PlayerAction_Rts_GameTick_Postfix(PlayerAction_Rts __instance, long timei)
{
if (timei % 60L != 0) return;
if (!FactoryPatch.AutoConstructEnabled.Value) return;
var planet = GameMain.localPlanet;
if (planet == null || !planet.factoryLoaded) return;
var factory = planet.factory;
var prebuildCount = factory.prebuildCount;
if (_lastPrebuildCount != prebuildCount)
{
if (_lastPrebuildCount <= 0 || prebuildCount == 0)
{
Functions.UIFunctions.UpdateToggleAutoConstructCheckButtonVisiblility();
}
_lastPrebuildCount = prebuildCount;
Functions.UIFunctions.UpdateConstructCountText(prebuildCount);
}
if (prebuildCount <= 0) return;
if (!FactoryPatch.AutoConstructEnabled.Value) return;
var player = __instance.player;
if (prebuildCount <= player.mecha.constructionModule.buildTargetTotalCount) return;
if (player.orders.orderCount > 0) return;