Compare commits

6 Commits
Author SHA1 Message Date
soarqin 7a8f4af31d Update game dll 2026-05-31 12:59:27 +08:00
soarqin 44efbdbb4f UXAssist 1.5.8 and CheatEnabler 2.4.4 2026-05-30 18:49:16 +08:00
soarqin b7180afff2 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
2026-04-30 14:47:01 +08:00
soarqin e5cb52c3f4 updated dll 2026-04-27 23:09:26 +08:00
soarqin ca4244384c crash fix 2026-04-27 23:09:16 +08:00
soarqin 19629eae97 CheatEnabler: Add a new button and fix a crash 2026-04-08 00:48:35 +08:00
12 changed files with 175 additions and 69 deletions
Binary file not shown.
Binary file not shown.
+8
View File
@@ -3,6 +3,10 @@
## Changlog
* 2.4.4
* New button `Generate illegal dyson shells for all layers without nodes and shells`, which is used to extend dyson layers.
* You must enable `IllegalDysonShellFunctionsEnabled` of `DysonSphere` section in config to see it.
* `Finish build immediately`: Fix a possible crash.
* 2.4.3
* `Duplicate shells from that with highest production`: Fix crash on using with frameless shells.
* `Keep max production shells and remove others`: Fix wrong behaviour when left count > 1.
@@ -185,6 +189,10 @@
## 更新日志
* 2.4.4
* 新按钮 `为所有没有节点和壳的层级生成仙术戴森壳`,用于扩展新戴森壳层面的时候。
* 你必须在设置文件里开启`DysonSphere`分类的`IllegalDysonShellFunctionsEnabled`才能看到它。
* `立即完成建造`: 修复一个可能的崩溃问题。
* 2.4.3
* `从发电量最高的壳复制戴森壳`:修复在无框壳体时可能崩溃的问题。
* `保留发电量最高的戴森壳并移除其他戴森壳`:修复当剩余壳体数量大于1时的异常行为。
+1 -1
View File
@@ -3,7 +3,7 @@
<PropertyGroup>
<BepInExPluginGuid>org.soardev.cheatenabler</BepInExPluginGuid>
<Description>DSP MOD - CheatEnabler</Description>
<Version>2.4.3</Version>
<Version>2.4.4</Version>
<PackageId>CheatEnabler</PackageId>
<RootNamespace>CheatEnabler</RootNamespace>
<PackHasChangelog>true</PackHasChangelog>
+81 -35
View File
@@ -1334,46 +1334,13 @@ public static class DysonSphereFunctions
}
}
public static void CreateIllegalDysonShellWithMaxOutput()
private static bool CreateIllegalDysonShellWithMaxOutputForLayer(DysonSphereLayer layer)
{
StarData star = null;
var dysonEditor = UIRoot.instance?.uiGame?.dysonEditor;
if (dysonEditor != null && dysonEditor.gameObject.activeSelf)
{
star = dysonEditor.selection.viewStar;
}
if (star == null)
{
star = GameMain.data?.localStar;
if (star == null)
{
UIMessageBox.Show("CheatEnabler".Translate(), "You are not in any system.".Translate(), "确定".Translate(), UIMessageBox.ERROR, null);
return;
}
}
UXAssist.Functions.DysonSphereFunctions.InitCurrentDysonLayer(star, 0);
var dysonSphere = GameMain.data?.dysonSpheres[star.index];
if (dysonSphere == null)
{
UIMessageBox.Show("CheatEnabler".Translate(), string.Format("There is no Dyson Sphere data on \"{0}\".".Translate(), star.displayName), "确定".Translate(), UIMessageBox.ERROR, null);
return;
}
DysonShell.s_vmap ??= new Dictionary<int, Vector3>(16384);
DysonShell.s_outvmap ??= new Dictionary<int, Vector3>(16384);
DysonShell.s_ivmap ??= new Dictionary<int, int>(16384);
var shellsChanged = false;
var mutex = new object();
Dictionary<(int, int), int> availableFrames = [];
HashSet<int> unusedFrameIds = [];
var layer = dysonSphere.layersIdBased[1];
if (layer != null)
{
dysonSphere.RemoveLayer(1);
}
var maxOrbitRadius = Patches.DysonSpherePatch.UnlockMaxOrbitRadiusEnabled.Value ? Patches.DysonSpherePatch.UnlockMaxOrbitRadiusValue.Value : dysonSphere.maxOrbitRadius;
layer = dysonSphere.AddLayerOnId(1, maxOrbitRadius, Quaternion.Euler(0f, 0f, 0f), Mathf.Sqrt(dysonSphere.gravity / maxOrbitRadius) / maxOrbitRadius * 57.2957802f);
if (layer == null) return;
var supposedShells = new List<SupposedShell>(60 * 59 * 58);
Vector3[] nodePos = new Vector3[60];
for (var i = 0; i < 60; i++)
@@ -1397,7 +1364,7 @@ public static class DysonSphereFunctions
var maxJ = -1;
var options = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount - 1 };
var gridScale = (int)(Math.Pow(maxOrbitRadius / 4000.0, 0.75) + 0.5);
var gridScale = (int)(Math.Pow(layer.orbitRadius / 4000.0, 0.75) + 0.5);
gridScale = (gridScale < 1) ? 1 : gridScale;
var cpPerVertex = gridScale * gridScale * 2;
var barrier = 0x7FFFFFFF / cpPerVertex;
@@ -1457,6 +1424,85 @@ public static class DysonSphereFunctions
}
shellsChanged = true;
}
return shellsChanged;
}
public static void CreateIllegalDysonShellWithMaxOutput()
{
StarData star = null;
var dysonEditor = UIRoot.instance?.uiGame?.dysonEditor;
if (dysonEditor != null && dysonEditor.gameObject.activeSelf)
{
star = dysonEditor.selection.viewStar;
}
if (star == null)
{
star = GameMain.data?.localStar;
if (star == null)
{
UIMessageBox.Show("CheatEnabler".Translate(), "You are not in any system.".Translate(), "确定".Translate(), UIMessageBox.ERROR, null);
return;
}
}
UXAssist.Functions.DysonSphereFunctions.InitCurrentDysonLayer(star, 0);
var dysonSphere = GameMain.data?.dysonSpheres[star.index];
if (dysonSphere == null)
{
UIMessageBox.Show("CheatEnabler".Translate(), string.Format("There is no Dyson Sphere data on \"{0}\".".Translate(), star.displayName), "确定".Translate(), UIMessageBox.ERROR, null);
return;
}
var layer = dysonSphere.layersIdBased[1];
if (layer != null)
{
dysonSphere.RemoveLayer(1);
}
var maxOrbitRadius = Patches.DysonSpherePatch.UnlockMaxOrbitRadiusEnabled.Value ? Patches.DysonSpherePatch.UnlockMaxOrbitRadiusValue.Value : dysonSphere.maxOrbitRadius;
layer = dysonSphere.AddLayerOnId(1, maxOrbitRadius, Quaternion.Euler(0f, 0f, 0f), Mathf.Sqrt(dysonSphere.gravity / maxOrbitRadius) / maxOrbitRadius * 57.2957802f);
if (layer == null) return;
var shellsChanged = CreateIllegalDysonShellWithMaxOutputForLayer(layer);
dysonSphere.CheckAutoNodes();
if (dysonSphere.autoNodeCount <= 0) dysonSphere.PickAutoNode();
dysonSphere.modelRenderer.RebuildModels();
if (shellsChanged) GameMain.gameScenario?.NotifyOnPlanDysonShell();
dysonSphere.inEditorRenderMaskS = 0;
dysonSphere.inEditorRenderMaskL = 0;
dysonSphere.inGameRenderMaskS = 0;
dysonSphere.inGameRenderMaskL = 0;
}
public static void CreateIllegalDysonShellWithMaxOutput2()
{
StarData star = null;
var dysonEditor = UIRoot.instance?.uiGame?.dysonEditor;
if (dysonEditor != null && dysonEditor.gameObject.activeSelf)
{
star = dysonEditor.selection.viewStar;
}
if (star == null)
{
star = GameMain.data?.localStar;
if (star == null)
{
UIMessageBox.Show("CheatEnabler".Translate(), "You are not in any system.".Translate(), "确定".Translate(), UIMessageBox.ERROR, null);
return;
}
}
var dysonSphere = GameMain.data?.dysonSpheres[star.index];
if (dysonSphere == null)
{
UIMessageBox.Show("CheatEnabler".Translate(), string.Format("There is no Dyson Sphere data on \"{0}\".".Translate(), star.displayName), "确定".Translate(), UIMessageBox.ERROR, null);
return;
}
var shellsChanged = false;
for (int i = dysonSphere.layersSorted.Length - 1; i >= 0; i--)
{
var layer = dysonSphere.layersSorted[i];
if (layer == null) continue;
shellsChanged = CreateIllegalDysonShellWithMaxOutputForLayer(layer) || shellsChanged;
}
dysonSphere.CheckAutoNodes();
if (dysonSphere.autoNodeCount <= 0) dysonSphere.PickAutoNode();
+3 -1
View File
@@ -446,7 +446,9 @@ public class FactoryPatch : PatchImpl<FactoryPatch>
{
var time = __instance.timei;
if (time % 6 != 0) return;
var factory = GameMain.localPlanet?.factory;
var planet = GameMain.localPlanet;
if (planet == null || !planet.factoryLoaded) return;
var factory = planet.factory;
if (factory == null || factory.prebuildCount <= 0) return;
var player = GameMain.mainPlayer;
if (player == null) return;
+9 -1
View File
@@ -74,6 +74,7 @@ public static class UIConfigWindow
I18N.Add("Complete Dyson Sphere shells instantly", "Complete Dyson Sphere shells instantly", "立即完成戴森壳建造");
I18N.Add("Remove all frames on Dyson Sphere", "Remove all frames on Dyson Sphere", "移除戴森球上的所有框架");
I18N.Add("Generate illegal dyson shell", "Generate an illegal dyson shell (!!1st shell layer will be replaced!!)", "生成单层仙术戴森壳(!!会先删除第一层戴森壳!!)");
I18N.Add("Generate illegal dyson shell 2", "Generate illegal dyson shells for all layers without nodes and shells", "为所有没有节点和壳的层级生成仙术戴森壳");
I18N.Add("Keep max production shells and remove others", "Keep max production shells and remove others", "保留发电量最高的戴森壳并移除其他戴森壳");
I18N.Add("Duplicate shells from that with highest production", "Duplicate shells from that with highest production", "从发电量最高的壳复制戴森壳");
I18N.Add("Generate illegal dyson shell quickly", "Generate illegal dyson shell quickly", "快速生成仙术戴森壳");
@@ -316,12 +317,18 @@ public static class UIConfigWindow
{
y += 72f;
var originalY = y;
var btn1 = wnd.AddButton(x, y, 300f, tab4, "Generate illegal dyson shell", 16, "button-generate-illegal-dyson-shells", () =>
var btn0 = wnd.AddButton(x, y, 300f, tab4, "Generate illegal dyson shell", 16, "button-generate-illegal-dyson-shells", () =>
{
UIMessageBox.Show("Generate illegal dyson shell".Translate(), "WARNING: This operation can be very slow, continue?".Translate(), "取消".Translate(), "确定".Translate(), UIMessageBox.WARNING, null,
() => { DysonSphereFunctions.CreateIllegalDysonShellWithMaxOutput(); });
});
y += 36f;
var btn1 = wnd.AddButton(x, y, 300f, tab4, "Generate illegal dyson shell 2", 16, "button-generate-illegal-dyson-shells", () =>
{
UIMessageBox.Show("Generate illegal dyson shell 2".Translate(), "WARNING: This operation can be very slow, continue?".Translate(), "取消".Translate(), "确定".Translate(), UIMessageBox.WARNING, null,
() => { DysonSphereFunctions.CreateIllegalDysonShellWithMaxOutput2(); });
});
y += 36f;
var btn2 = wnd.AddButton(x, y, 300f, tab4, "Keep max production shells and remove others", 16, "button-keep-max-production-shells", () =>
{
UIMessageBox.Show("Keep max production shells and remove others".Translate(), "WARNING: This operation is DANGEROUS, continue?".Translate(), "取消".Translate(), "确定".Translate(), UIMessageBox.WARNING, null,
@@ -352,6 +359,7 @@ public static class UIConfigWindow
void onIllegalDysonShellFunctionsChanged(object o, EventArgs e)
{
var enabled = Functions.DysonSphereFunctions.IllegalDysonShellFunctionsEnabled.Value;
btn0.gameObject.SetActive(enabled);
btn1.gameObject.SetActive(enabled);
btn2.gameObject.SetActive(enabled);
btn3.gameObject.SetActive(enabled);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "CheatEnabler",
"version_number": "2.4.3",
"version_number": "2.4.4",
"website_url": "https://github.com/soarqin/DSP_Mods/tree/master/CheatEnabler",
"description": "Add various cheat functions while disabling abnormal determinants / 添加一些作弊功能,同时屏蔽异常检测",
"dependencies": [
+8
View File
@@ -3,6 +3,10 @@
## Changlog
* 1.5.8
* `Initialize This Planet`: Fix a possible crash.
* `Remember window position and size on last exit`: Remove a window hook that are not useful anymore.
* `Real-time logistic stations info panel`: Fix display issue (finally!) when count of logistic towers > 128.
* 1.5.7
* Fix compatibility with game update 0.10.34.28505.
* 1.5.6
@@ -397,6 +401,10 @@
## 更新日志
* 1.5.8
* `初始化本行星`:修复可能的崩溃。
* `记住上次退出时的窗口位置和大小`:移除不再有用的窗口钩子。
* `物流站实时信息面板`:修复物流塔数量 > 128 时的显示问题。
* 1.5.7
* 修复了与游戏更新0.10.34.28505的兼容性。
* 1.5.6
+62 -28
View File
@@ -75,12 +75,14 @@ public static class LogisticsPatch
RealtimeLogisticsInfoPanel.EnableBars(RealtimeLogisticsInfoPanelBarsEnabled.Value);
GameLogicProc.OnGameBegin += RealtimeLogisticsInfoPanel.OnGameBegin;
GameLogicProc.OnGameEnd += RealtimeLogisticsInfoPanel.OnGameEnd;
GameLogicProc.OnDataLoaded += RealtimeLogisticsInfoPanel.OnDataLoaded;
}
public static void Uninit()
{
GameLogicProc.OnDataLoaded -= RealtimeLogisticsInfoPanel.OnDataLoaded;
GameLogicProc.OnGameEnd -= RealtimeLogisticsInfoPanel.OnGameEnd;
GameLogicProc.OnGameBegin -= RealtimeLogisticsInfoPanel.OnGameBegin;
AutoConfigLogistics.Enable(false);
@@ -798,12 +800,13 @@ public static class LogisticsPatch
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 (!on)
{
RecycleStationTips();
_lastPlanetId = 0;
_stationTipsRoot.SetActive(false);
HideAndRecycleStationTips();
return;
}
if (DSPGame.IsMenuDemo || !GameMain.isRunning)
@@ -827,9 +830,12 @@ public static class LogisticsPatch
public static void OnGameBegin()
{
RecycleStationTips();
_lastPlanetId = 0;
_stationTipsRoot?.SetActive(false);
HideAndRecycleStationTips();
}
public static void OnGameEnd()
{
HideAndRecycleStationTips();
}
public static void OnDataLoaded()
@@ -1065,22 +1071,32 @@ public static class LogisticsPatch
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()
{
foreach (var stationTip in _stationTips)
{
if (!stationTip) continue;
if (_stationTipsRecycleCount < 128)
{
stationTip.ResetStationTip();
stationTip.gameObject.SetActive(false);
StationTipsRecycle[_stationTipsRecycleCount] = stationTip;
_stationTipsRecycleCount++;
}
else
{
Object.Destroy(stationTip);
}
ReleaseStationTip(stationTip);
}
_stationTips = new StationTip[16];
}
@@ -1089,19 +1105,17 @@ public static class LogisticsPatch
{
var stationTip = _stationTips[index];
if (!stationTip) return;
if (_stationTipsRecycleCount < 128)
{
stationTip.ResetStationTip();
stationTip.gameObject.SetActive(false);
StationTipsRecycle[_stationTipsRecycleCount++] = stationTip;
}
else
{
Object.Destroy(stationTip);
}
ReleaseStationTip(stationTip);
_stationTips[index] = null;
}
private static void HideAndRecycleStationTips()
{
_stationTipsRoot?.SetActive(false);
RecycleStationTips();
_lastPlanetId = 0;
}
private static StationTip AllocateStationTip()
{
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
{
[FormerlySerializedAs("RectTransform")]
+1 -1
View File
@@ -3,7 +3,7 @@
<PropertyGroup>
<BepInExPluginGuid>org.soardev.uxassist</BepInExPluginGuid>
<Description>DSP MOD - UXAssist</Description>
<Version>1.5.7</Version>
<Version>1.5.8</Version>
<PackageId>UXAssist</PackageId>
<RootNamespace>UXAssist</RootNamespace>
<PackHasChangelog>true</PackHasChangelog>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "UXAssist",
"version_number": "1.5.7",
"version_number": "1.5.8",
"website_url": "https://github.com/soarqin/DSP_Mods/tree/master/UXAssist",
"description": "Some functions and patches for better user experience / 一些提升用户体验的功能和补丁",
"dependencies": [