From 0c6d458ffe6b9aa0ce0010f071a3f19a5a332a0f Mon Sep 17 00:00:00 2001 From: Joshua Date: Sun, 6 Sep 2026 16:03:55 +0200 Subject: [PATCH] Fix Dark Fog enemy synchronization on client --- .../GroundEnemy/DFGRetargetProcessor.cs | 4 +- .../Patches/Dynamic/DFGBaseComponent_Patch.cs | 4 +- .../Dynamic/EnemyDFGroundSystem_Patch.cs | 36 +++++++++++- .../Patches/Dynamic/SpaceSector_Patch.cs | 23 ++------ .../EnemyDFGroundSystem_Transpiler.cs | 3 +- NebulaWorld/Combat/CombatManager.cs | 9 +-- NebulaWorld/Combat/EnemyManager.cs | 55 +++++++++++++++++-- 7 files changed, 98 insertions(+), 36 deletions(-) diff --git a/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGRetargetProcessor.cs b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGRetargetProcessor.cs index 419ee3a39..559143cb6 100644 --- a/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGRetargetProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGRetargetProcessor.cs @@ -1,4 +1,4 @@ -#region +#region using NebulaAPI.Packets; using NebulaModel.Networking; @@ -19,7 +19,7 @@ protected override void ProcessPacket(DFGRetargetPacket packet, NebulaConnection if (factory == null) return; var targets = Multiplayer.Session.Enemies.GroundTargets[packet.PlanetId]; - if (packet.EnemyId <= targets.Length) + if (packet.EnemyId >= 0 && packet.EnemyId < targets.Length) { targets[packet.EnemyId] = packet.Target; } diff --git a/NebulaPatcher/Patches/Dynamic/DFGBaseComponent_Patch.cs b/NebulaPatcher/Patches/Dynamic/DFGBaseComponent_Patch.cs index 47cad0469..00c1b029f 100644 --- a/NebulaPatcher/Patches/Dynamic/DFGBaseComponent_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/DFGBaseComponent_Patch.cs @@ -1,4 +1,4 @@ -#region +#region using System; using HarmonyLib; @@ -27,7 +27,7 @@ public static bool UnderAttack_Prefix1() public static bool ActiveAllUnit_Prefix(DFGBaseComponent __instance, long gameTick) { if (!Multiplayer.IsActive) return true; - if (!Multiplayer.Session.Combat.IsIncomingRequest.Value) return false; + if (Multiplayer.Session.IsClient && !Multiplayer.Session.Combat.IsIncomingRequest.Value) return false; // This will only trigger in event instead of every actived tick ref var ptr = ref __instance.groundSystem.units.buffer; diff --git a/NebulaPatcher/Patches/Dynamic/EnemyDFGroundSystem_Patch.cs b/NebulaPatcher/Patches/Dynamic/EnemyDFGroundSystem_Patch.cs index b49a0de5c..1981874a9 100644 --- a/NebulaPatcher/Patches/Dynamic/EnemyDFGroundSystem_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/EnemyDFGroundSystem_Patch.cs @@ -1,4 +1,4 @@ -#region +#region using System; using HarmonyLib; @@ -69,13 +69,45 @@ public static bool ExecuteDeferredUnitFormation_Prefix(EnemyDFGroundSystem __ins { // Wait for server to authorize __instance._initiate_unit_list?.Clear(); + __instance._activate_unit_list?.Clear(); __instance._deactivate_unit_list?.Clear(); return false; } // Skip InitiateUnit in multiplayer game __instance._initiate_unit_list?.Clear(); - return true; + if (__instance._activate_unit_list?.Count > 0) + { + var planetId = __instance.planet.id; + var starId = __instance.planet.star.id; + foreach (var item in __instance._activate_unit_list) + { + int unitId = __instance.ActivateUnit(item.baseId, item.formId, item.port, item.gameTick); + if (unitId > 0) + { + ref var ptr = ref __instance.units.buffer[unitId]; + if (ptr.id == unitId) + { + ptr.stateTick = item.stateTick; + ptr.behavior = item.behavior; + + var packet = new DFGActivateUnitPacket(planetId, item.baseId, item.formId, item.port, + item.behavior, item.stateTick, ptr.enemyId); + Multiplayer.Session.Network.SendPacketToStar(packet, starId); + } + } + } + __instance._activate_unit_list.Clear(); + } + if (__instance._deactivate_unit_list?.Count > 0) + { + foreach (var unitId in __instance._deactivate_unit_list) + { + __instance.DeactivateUnit(unitId); + } + __instance._deactivate_unit_list.Clear(); + } + return false; } [HarmonyPrefix] diff --git a/NebulaPatcher/Patches/Dynamic/SpaceSector_Patch.cs b/NebulaPatcher/Patches/Dynamic/SpaceSector_Patch.cs index f488f19b0..62e62d39a 100644 --- a/NebulaPatcher/Patches/Dynamic/SpaceSector_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/SpaceSector_Patch.cs @@ -1,4 +1,4 @@ -#region +#region using HarmonyLib; using NebulaWorld; @@ -19,9 +19,8 @@ public static void SetForNewGame_Prefix(SpaceSector __instance) { if (Multiplayer.IsActive && Multiplayer.Session.IsClient) { - // Set this.isCombatMode to false to skip the part of initial enemyDFHiveSystem - // Will revert it to the correct value during SpaceSector.Import called in GameStatesManager.OverwriteGlobalGameData - __instance.isCombatMode = false; + // Ensure isCombatMode is properly set so SectorModel and PrefabDescByModelIndex are initialized on client + __instance.isCombatMode = __instance.gameData?.gameDesc?.isCombatMode ?? true; } } @@ -89,19 +88,7 @@ public static void GameTick_Prefix(SpaceSector __instance) { if (!Multiplayer.IsActive || Multiplayer.Session.IsServer) return; - // Fix NRE in DFSTurretComponent.InternalUpdate (PrefabDesc pdesc);(IL_0017) - for (var enemyId = 1; enemyId < __instance.enemyCursor; enemyId++) - { - ref var enemy = ref __instance.enemyPool[enemyId]; - if (enemy.id != enemyId) continue; - - if (SpaceSector.PrefabDescByModelIndex[enemy.modelIndex] == null) - { - var msg = $"Remove SpaceSector enemy[{enemyId}]: modelIndex{enemy.modelIndex}"; - Log.WarnInform(msg); - - __instance.enemyPool[enemyId].SetEmpty(); - } - } + // Prevent purging valid space enemies with SetEmpty() + if (__instance.model == null || SpaceSector.PrefabDescByModelIndex == null) return; } } diff --git a/NebulaPatcher/Patches/Transpilers/EnemyDFGroundSystem_Transpiler.cs b/NebulaPatcher/Patches/Transpilers/EnemyDFGroundSystem_Transpiler.cs index a582dc9f8..20824d899 100644 --- a/NebulaPatcher/Patches/Transpilers/EnemyDFGroundSystem_Transpiler.cs +++ b/NebulaPatcher/Patches/Transpilers/EnemyDFGroundSystem_Transpiler.cs @@ -1,4 +1,4 @@ -#region +#region using System.Collections.Generic; using System.Reflection; @@ -72,6 +72,7 @@ public static IEnumerable GameTickLogic_Prepare_Transpiler(IEnu [HarmonyTranspiler] [HarmonyPatch(nameof(EnemyDFGroundSystem.GameTickLogic_Unit))] + [HarmonyPatch(typeof(GameLogic), nameof(GameLogic._enemy_ground_unit_parallel))] public static IEnumerable GameTickLogic_Unit_Transpiler(IEnumerable instructions) { try diff --git a/NebulaWorld/Combat/CombatManager.cs b/NebulaWorld/Combat/CombatManager.cs index 17c449067..39afeb4eb 100644 --- a/NebulaWorld/Combat/CombatManager.cs +++ b/NebulaWorld/Combat/CombatManager.cs @@ -1,4 +1,4 @@ -#region +#region using System; using System.Collections.Generic; @@ -263,10 +263,7 @@ public void OnFactoryLoadFinished(PlanetFactory factory) { factory.vegePool[i].combatStatId = 0; } - for (var i = 1; i < factory.enemyCursor; i++) - { - factory.enemyPool[i].combatStatId = 0; - } + // Do not erase enemy combatStatId to preserve HP and shields on client for (var i = 1; i < factory.veinCursor; i++) { factory.veinPool[i].combatStatId = 0; @@ -281,7 +278,7 @@ public void OnFactoryLoadFinished(PlanetFactory factory) for (var i = 1; i < combatStatCursor; i++) { ref var ptr = ref combatStatbuffer[i]; - if (ptr.id == i && ptr.astroId == astroId) + if (ptr.id == i && ptr.astroId == astroId && ptr.objectType != (int)EObjectType.Enemy) { combatStats.Remove(i); count++; diff --git a/NebulaWorld/Combat/EnemyManager.cs b/NebulaWorld/Combat/EnemyManager.cs index 8955420e1..2dec9f92e 100644 --- a/NebulaWorld/Combat/EnemyManager.cs +++ b/NebulaWorld/Combat/EnemyManager.cs @@ -1,4 +1,4 @@ -#region +#region using System; using System.Collections.Generic; @@ -187,8 +187,30 @@ public static void SetPlanetFactoryNextEnemyId(PlanetFactory factory, int enemyI factory.KillEnemyFinally(enemyId, ref CombatStat.empty); } - factory.enemyRecycle[0] = enemyId; - factory.enemyRecycleCursor = 1; + var found = false; + for (var i = 0; i < factory.enemyRecycleCursor; i++) + { + if (factory.enemyRecycle[i] == enemyId) + { + found = true; + var lastIdx = factory.enemyRecycleCursor - 1; + if (i != lastIdx) + { + factory.enemyRecycle[i] = factory.enemyRecycle[lastIdx]; + factory.enemyRecycle[lastIdx] = enemyId; + } + break; + } + } + + if (!found) + { + if (factory.enemyRecycleCursor >= factory.enemyRecycle.Length) + { + Array.Resize(ref factory.enemyRecycle, Math.Max(factory.enemyRecycle.Length * 2, factory.enemyRecycleCursor + 1)); + } + factory.enemyRecycle[factory.enemyRecycleCursor++] = enemyId; + } } } @@ -246,8 +268,31 @@ public static void SetSpaceSectorNextEnemyId(int enemyId) ptr.isInvincible = false; spaceSector.KillEnemyFinal(enemyId, ref CombatStat.empty); } - spaceSector.enemyRecycle[0] = enemyId; - spaceSector.enemyRecycleCursor = 1; + + var found = false; + for (var i = 0; i < spaceSector.enemyRecycleCursor; i++) + { + if (spaceSector.enemyRecycle[i] == enemyId) + { + found = true; + var lastIdx = spaceSector.enemyRecycleCursor - 1; + if (i != lastIdx) + { + spaceSector.enemyRecycle[i] = spaceSector.enemyRecycle[lastIdx]; + spaceSector.enemyRecycle[lastIdx] = enemyId; + } + break; + } + } + + if (!found) + { + if (spaceSector.enemyRecycleCursor >= spaceSector.enemyRecycle.Length) + { + Array.Resize(ref spaceSector.enemyRecycle, Math.Max(spaceSector.enemyRecycle.Length * 2, spaceSector.enemyRecycleCursor + 1)); + } + spaceSector.enemyRecycle[spaceSector.enemyRecycleCursor++] = enemyId; + } } }