1
0
mirror of https://github.com/soarqin/DSP_Mods.git synced 2026-05-09 12:17:12 +08:00

fix ghost station tips persisting after planet transition

Object.Destroy(stationTip) where stationTip is a MonoBehaviour destroys
only the component, leaving the cloned UI GameObject (icons, sliders, text)
orphaned and potentially visible under _stationTipsRoot. When the recycle
pool (128 slots) fills up during a planet with many stations, excess tips
were disposed this way; those orphaned GameObjects would reappear as frozen
'ghost' tips on every subsequent planet.

- Add ReleaseStationTip() helper: calls Object.Destroy(go) on the whole
  GameObject after SetActive(false), replacing the broken Destroy(component)
- Simplify RecycleStationTips() and RecycleStationTip(int) to delegate to
  ReleaseStationTip()
- Add HideAndRecycleStationTips() single cleanup entry point used by
  Enable(false), OnGameBegin(), and new OnGameEnd()
- Add LocalPlanetWatcher (PatchImpl) hooking GameData.localPlanet setter:
  triggers HideAndRecycleStationTips() on any planet id change, covering
  frames where Update() is skipped by VFInput.inputing
- Subscribe OnGameEnd to clear tips when exiting a save/returning to menu
This commit is contained in:
2026-04-30 14:47:01 +08:00
parent e5cb52c3f4
commit b7180afff2

View File

@@ -75,12 +75,14 @@ public static class LogisticsPatch
RealtimeLogisticsInfoPanel.EnableBars(RealtimeLogisticsInfoPanelBarsEnabled.Value); RealtimeLogisticsInfoPanel.EnableBars(RealtimeLogisticsInfoPanelBarsEnabled.Value);
GameLogicProc.OnGameBegin += RealtimeLogisticsInfoPanel.OnGameBegin; GameLogicProc.OnGameBegin += RealtimeLogisticsInfoPanel.OnGameBegin;
GameLogicProc.OnGameEnd += RealtimeLogisticsInfoPanel.OnGameEnd;
GameLogicProc.OnDataLoaded += RealtimeLogisticsInfoPanel.OnDataLoaded; GameLogicProc.OnDataLoaded += RealtimeLogisticsInfoPanel.OnDataLoaded;
} }
public static void Uninit() public static void Uninit()
{ {
GameLogicProc.OnDataLoaded -= RealtimeLogisticsInfoPanel.OnDataLoaded; GameLogicProc.OnDataLoaded -= RealtimeLogisticsInfoPanel.OnDataLoaded;
GameLogicProc.OnGameEnd -= RealtimeLogisticsInfoPanel.OnGameEnd;
GameLogicProc.OnGameBegin -= RealtimeLogisticsInfoPanel.OnGameBegin; GameLogicProc.OnGameBegin -= RealtimeLogisticsInfoPanel.OnGameBegin;
AutoConfigLogistics.Enable(false); AutoConfigLogistics.Enable(false);
@@ -798,12 +800,13 @@ public static class LogisticsPatch
public static void Enable(bool on) public static void Enable(bool on)
{ {
// Toggle the defensive localPlanet setter hook regardless of whether the GUI
// root has been initialized yet (InitGUI may run later via OnDataLoaded).
LocalPlanetWatcher.Enable(on);
if (_stationTipsRoot == null) return; if (_stationTipsRoot == null) return;
if (!on) if (!on)
{ {
RecycleStationTips(); HideAndRecycleStationTips();
_lastPlanetId = 0;
_stationTipsRoot.SetActive(false);
return; return;
} }
if (DSPGame.IsMenuDemo || !GameMain.isRunning) if (DSPGame.IsMenuDemo || !GameMain.isRunning)
@@ -827,9 +830,12 @@ public static class LogisticsPatch
public static void OnGameBegin() public static void OnGameBegin()
{ {
RecycleStationTips(); HideAndRecycleStationTips();
_lastPlanetId = 0; }
_stationTipsRoot?.SetActive(false);
public static void OnGameEnd()
{
HideAndRecycleStationTips();
} }
public static void OnDataLoaded() public static void OnDataLoaded()
@@ -1065,22 +1071,32 @@ public static class LogisticsPatch
StateSprite[2] = Util.LoadEmbeddedSprite("assets/icon/in.png"); StateSprite[2] = Util.LoadEmbeddedSprite("assets/icon/in.png");
} }
private static void ReleaseStationTip(StationTip stationTip)
{
if (!stationTip) return;
var go = stationTip.gameObject;
if (_stationTipsRecycleCount < StationTipsRecycle.Length)
{
stationTip.ResetStationTip();
go.SetActive(false);
StationTipsRecycle[_stationTipsRecycleCount++] = stationTip;
}
else
{
// Recycle pool is full: destroy the cloned UI GameObject (not just the
// StationTip MonoBehaviour). Destroying only the component would leave the
// GameObject and all its UI children orphaned under _stationTipsRoot,
// causing "ghost" tips to remain visible whenever the root is reactivated.
go.SetActive(false);
Object.Destroy(go);
}
}
private static void RecycleStationTips() private static void RecycleStationTips()
{ {
foreach (var stationTip in _stationTips) foreach (var stationTip in _stationTips)
{ {
if (!stationTip) continue; ReleaseStationTip(stationTip);
if (_stationTipsRecycleCount < 128)
{
stationTip.ResetStationTip();
stationTip.gameObject.SetActive(false);
StationTipsRecycle[_stationTipsRecycleCount] = stationTip;
_stationTipsRecycleCount++;
}
else
{
Object.Destroy(stationTip);
}
} }
_stationTips = new StationTip[16]; _stationTips = new StationTip[16];
} }
@@ -1089,19 +1105,17 @@ public static class LogisticsPatch
{ {
var stationTip = _stationTips[index]; var stationTip = _stationTips[index];
if (!stationTip) return; if (!stationTip) return;
if (_stationTipsRecycleCount < 128) ReleaseStationTip(stationTip);
{
stationTip.ResetStationTip();
stationTip.gameObject.SetActive(false);
StationTipsRecycle[_stationTipsRecycleCount++] = stationTip;
}
else
{
Object.Destroy(stationTip);
}
_stationTips[index] = null; _stationTips[index] = null;
} }
private static void HideAndRecycleStationTips()
{
_stationTipsRoot?.SetActive(false);
RecycleStationTips();
_lastPlanetId = 0;
}
private static StationTip AllocateStationTip() private static StationTip AllocateStationTip()
{ {
if (_stationTipsRecycleCount > 0) if (_stationTipsRecycleCount > 0)
@@ -1247,6 +1261,26 @@ public static class LogisticsPatch
} }
} }
// Defensive Harmony hook on GameData.localPlanet setter.
// It guarantees tips are hidden and recycled when the local planet id changes,
// even if UXAssist.Update() is skipped that frame (e.g. by VFInput.inputing
// while the player is typing while transitioning between planets, or by a brief
// !GameMain.isRunning state during loading). _lastPlanetId is reset to 0 here so
// the next StationInfoPanelsUpdate() re-validates factoryLoaded/transport/viewMode
// and re-allocates fresh tips for the new planet.
private class LocalPlanetWatcher : PatchImpl<LocalPlanetWatcher>
{
[HarmonyPrefix]
[HarmonyPatch(typeof(GameData), nameof(GameData.localPlanet), MethodType.Setter)]
private static void GameData_localPlanet_Setter_Prefix(GameData __instance, PlanetData value)
{
var oldId = __instance.localPlanet?.id ?? 0;
var newId = value?.id ?? 0;
if (oldId == newId) return;
HideAndRecycleStationTips();
}
}
public class StationTip : MonoBehaviour public class StationTip : MonoBehaviour
{ {
[FormerlySerializedAs("RectTransform")] [FormerlySerializedAs("RectTransform")]