diff --git a/NebulaModel/Packets/Combat/GroundEnemy/DFGRemoveBasePacket.cs b/NebulaModel/Packets/Combat/GroundEnemy/DFGRemoveBasePacket.cs new file mode 100644 index 000000000..a839f4ee0 --- /dev/null +++ b/NebulaModel/Packets/Combat/GroundEnemy/DFGRemoveBasePacket.cs @@ -0,0 +1,15 @@ +namespace NebulaModel.Packets.Combat.GroundEnemy; + +public class DFGRemoveBasePacket +{ + public DFGRemoveBasePacket() { } + + public DFGRemoveBasePacket(int planetId, int baseId) + { + PlanetId = planetId; + BaseId = baseId; + } + + public int PlanetId { get; set; } + public int BaseId { get; set; } +} diff --git a/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGActivateBaseProcessor.cs b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGActivateBaseProcessor.cs index fdf52527d..fd6ca03bd 100644 --- a/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGActivateBaseProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGActivateBaseProcessor.cs @@ -1,6 +1,5 @@ #region -using NebulaAPI.DataStructures; using NebulaAPI.Packets; using NebulaModel.Networking; using NebulaModel.Packets; @@ -26,7 +25,9 @@ protected override void ProcessPacket(DFGActivateBasePacket packet, NebulaConnec if (!packet.SetToSeekForm) { + if (packet.BaseId >= factory.enemySystem.bases.capacity) return; var dFBase = factory.enemySystem.bases.buffer[packet.BaseId]; + if (dFBase == null) return; dFBase.activeTick = 3; using (Multiplayer.Session.Combat.IsIncomingRequest.On()) { diff --git a/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGActivateUnitProcessor.cs b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGActivateUnitProcessor.cs index 7c7f48e72..d2cc148e1 100644 --- a/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGActivateUnitProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGActivateUnitProcessor.cs @@ -23,7 +23,9 @@ protected override void ProcessPacket(DFGActivateUnitPacket packet, NebulaConnec using (Multiplayer.Session.Combat.IsIncomingRequest.On()) { EnemyManager.SetPlanetFactoryNextEnemyId(factory, packet.EnemyId); + if (packet.BaseId >= factory.enemySystem.bases.capacity) return; var dfBase = factory.enemySystem.bases.buffer[packet.BaseId]; + if (dfBase == null) return; var gameTick = GameMain.gameTick; // the value inside enemyFormation.units[portId] is not reliable, so just overwrite it diff --git a/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGDeferredCreateEnemyProcessor.cs b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGDeferredCreateEnemyProcessor.cs index dad2aecf7..fbedc107b 100644 --- a/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGDeferredCreateEnemyProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGDeferredCreateEnemyProcessor.cs @@ -23,6 +23,8 @@ protected override void ProcessPacket(DFGDeferredCreateEnemyPacket packet, Nebul using (Multiplayer.Session.Combat.IsIncomingRequest.On()) { EnemyManager.SetPlanetFactoryNextEnemyId(factory, packet.EnemyId); + if (packet.BaseId >= factory.enemySystem.bases.capacity) return; + if (factory.enemySystem.bases.buffer[packet.BaseId] == null) return; var enemyId = factory.CreateEnemyFinal(packet.BaseId, packet.BuilderIndex); #if DEBUG diff --git a/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGFormationAddUnitProcessor.cs b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGFormationAddUnitProcessor.cs index 1d342b8b7..2123f0a64 100644 --- a/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGFormationAddUnitProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGFormationAddUnitProcessor.cs @@ -5,7 +5,6 @@ using NebulaModel.Packets; using NebulaModel.Packets.Combat.GroundEnemy; using NebulaWorld; -using static UnityEngine.UI.CanvasScaler; #endregion @@ -19,7 +18,9 @@ protected override void ProcessPacket(DFGFormationAddUnitPacket packet, NebulaCo var factory = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory; if (factory == null) return; + if (packet.BaseId >= factory.enemySystem.bases.capacity) return; var dFBase = factory.enemySystem.bases.buffer[packet.BaseId]; + if (dFBase == null) return; using (Multiplayer.Session.Combat.IsIncomingRequest.On()) { // Set the next id in EnemyFormation diff --git a/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGLaunchAssaultProcessor.cs b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGLaunchAssaultProcessor.cs index f40a5062a..6e352e5ce 100644 --- a/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGLaunchAssaultProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGLaunchAssaultProcessor.cs @@ -1,6 +1,5 @@ #region -using System; using NebulaAPI.DataStructures; using NebulaAPI.Packets; using NebulaModel.Networking; @@ -31,7 +30,9 @@ protected override void ProcessPacket(DFGLaunchAssaultPacket packet, NebulaConne // Set enemyRecycle pool to make enemyId stay in sync EnemyManager.SetPlanetFactoryRecycle(factory, packet.EnemyCursor, packet.EnemyRecyle); + if (packet.BaseId >= factory.enemySystem.bases.capacity) return; var dFBase = factory.enemySystem.bases.buffer[packet.BaseId]; + if (dFBase == null) return; dFBase.turboTicks = 60; dFBase.turboRepress = 0; dFBase.evolve.threat = packet.EvolveThreat; diff --git a/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGRemoveBaseProcessor.cs b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGRemoveBaseProcessor.cs new file mode 100644 index 000000000..09bded370 --- /dev/null +++ b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGRemoveBaseProcessor.cs @@ -0,0 +1,27 @@ +#region + +using NebulaAPI.Packets; +using NebulaModel.Networking; +using NebulaModel.Packets; +using NebulaModel.Packets.Combat.GroundEnemy; +using NebulaWorld; + +#endregion + +namespace NebulaNetwork.PacketProcessors.Combat.GroundEnemy; + +[RegisterPacketProcessor] +public class DFGRemoveBaseProcessor : PacketProcessor +{ + protected override void ProcessPacket(DFGRemoveBasePacket packet, NebulaConnection conn) + { + var factory = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory; + if (IsHost || factory == null) return; + + using (Multiplayer.Session.Combat.IsIncomingRequest.On()) + { + // EnemyDFGroundSystem.bases.SetNull(id); + factory.enemySystem.RemoveDFGBaseComponent(packet.BaseId); + } + } +} diff --git a/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGUpdateBaseStatusProcessor.cs b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGUpdateBaseStatusProcessor.cs index 2f67d01f0..e5c377d58 100644 --- a/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGUpdateBaseStatusProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGUpdateBaseStatusProcessor.cs @@ -17,6 +17,7 @@ protected override void ProcessPacket(DFGUpdateBaseStatusPacket packet, NebulaCo var factory = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory; if (factory == null) return; + if (packet.BaseId >= factory.enemySystem.bases.capacity) return; var dFBase = factory.enemySystem.bases.buffer[packet.BaseId]; if (dFBase == null) return; ref var evolveData = ref dFBase.evolve; diff --git a/NebulaNetwork/PacketProcessors/Factory/BuildEntityRequestProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/BuildEntityRequestProcessor.cs index 8017eae7f..b648b819f 100644 --- a/NebulaNetwork/PacketProcessors/Factory/BuildEntityRequestProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Factory/BuildEntityRequestProcessor.cs @@ -56,8 +56,13 @@ protected override void ProcessPacket(BuildEntityRequest packet, NebulaConnectio } else { - // Remote planets, or the factory is not loaded yet - planet.factory.BuildFinally(GameMain.mainPlayer, packet.PrebuildId, false, false); + Multiplayer.Session.Factories.AddPlanetTimer(packet.PlanetId); + // setting specifyPlanet here to avoid accessing a null object (see GPUInstancingManager activePlanet getter) + var pData = GameMain.gpuiManager.specifyPlanet; + GameMain.gpuiManager.specifyPlanet = GameMain.galaxy.PlanetById(packet.PlanetId); + // Flatten the terrain for remote planet build by other players + planet.factory.BuildFinally(GameMain.mainPlayer, packet.PrebuildId, true, true); + GameMain.gpuiManager.specifyPlanet = pData; } Multiplayer.Session.Factories.EventFactory = null; diff --git a/NebulaNetwork/PacketProcessors/Players/PlayerSandCountProcessor.cs b/NebulaNetwork/PacketProcessors/Players/PlayerSandCountProcessor.cs index 95c4b59b4..1daad0f39 100644 --- a/NebulaNetwork/PacketProcessors/Players/PlayerSandCountProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Players/PlayerSandCountProcessor.cs @@ -14,19 +14,17 @@ public class PlayerSandCountProcessor : PacketProcessor { protected override void ProcessPacket(PlayerSandCount packet, NebulaConnection conn) { + var player = GameMain.mainPlayer; + var originalSandCount = player.sandCount; + if (IsHost) { - if (!packet.IsDelta) - { - // when receive update request, host UpdateSyncedSandCount and send to other players - GameMain.mainPlayer.SetSandCount(packet.SandCount); - } + // when receive update request, host UpdateSyncedSandCount and send to other players + player.SetSandCount(packet.IsDelta ? originalSandCount + packet.SandCount : packet.SandCount); return; } // taken from Player.SetSandCount() - var player = GameMain.mainPlayer; - var originalSandCount = player.sandCount; if (packet.IsDelta) { player.sandCount += packet.SandCount; diff --git a/NebulaPatcher/Patches/Dynamic/EnemyDFGroundSystem_Patch.cs b/NebulaPatcher/Patches/Dynamic/EnemyDFGroundSystem_Patch.cs index 354983b6c..7ab0973ba 100644 --- a/NebulaPatcher/Patches/Dynamic/EnemyDFGroundSystem_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/EnemyDFGroundSystem_Patch.cs @@ -137,6 +137,23 @@ public static bool RemoveBasePit_Prefix(EnemyDFGroundSystem __instance, int pitR return false; } + [HarmonyPrefix] + [HarmonyPatch(nameof(EnemyDFGroundSystem.RemoveDFGBaseComponent))] + public static bool RemoveDFGBaseComponent_Prefix(EnemyDFGroundSystem __instance, int id) + { + if (!Multiplayer.IsActive) return true; + + if (Multiplayer.Session.IsServer) + { + var packet = new DFGRemoveBasePacket(__instance.factory.planetId, id); + Multiplayer.Session.Network.SendPacketToStar(packet, __instance.factory.planet.star.id); + return true; + } + + // Client should wait for server to approve the removal of base from the base buffer + return Multiplayer.Session.Combat.IsIncomingRequest; + } + [HarmonyPrefix] [HarmonyPatch(nameof(EnemyDFGroundSystem.GameTickLogic))] public static void GameTickLogic_Prefix(EnemyDFGroundSystem __instance, long gameTick) diff --git a/NebulaPatcher/Patches/Dynamic/GameData_Patch.cs b/NebulaPatcher/Patches/Dynamic/GameData_Patch.cs index 7b177e020..6455195f9 100644 --- a/NebulaPatcher/Patches/Dynamic/GameData_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/GameData_Patch.cs @@ -310,7 +310,7 @@ public static void NewGame_Postfix(GameData __instance) [HarmonyPostfix] [HarmonyPatch(nameof(GameData.GameTick))] - public static void GameTick_Postfix(GameData __instance, long time) + public static void GameTick_Postfix(long time) { if (!Multiplayer.IsActive) { @@ -326,7 +326,26 @@ public static void GameTick_Postfix(GameData __instance, long time) Multiplayer.Session.Launch.CollectProjectile(); return; } - Multiplayer.Session.Launch.LaunchProjectile(); + + try + { + // Client: Update visual effects that don't affect the production + Multiplayer.Session.Launch.LaunchProjectile(); + ILSUpdateShipPos(time); + } + catch (Exception e) + { + _ = e; +#if DEBUG + Log.Warn(e); +#endif + } + } + + private static void ILSUpdateShipPos(long time) + { + if (!Multiplayer.Session.IsGameLoaded) return; + // call StationComponent::InternalTickRemote() from here, see StationComponent_Patch.cs for info var timeGene = (int)(time % 60L); if (timeGene < 0) @@ -337,18 +356,22 @@ public static void GameTick_Postfix(GameData __instance, long time) var shipSailSpeed = history.logisticShipSailSpeedModified; var shipWarpSpeed = !history.logisticShipWarpDrive ? shipSailSpeed : history.logisticShipWarpSpeedModified; var shipCarries = history.logisticShipCarries; - var gStationPool = __instance.galacticTransport.stationPool; - var astroPoses = __instance.galaxy.astrosData; - var relativePos = __instance.relativePos; - var relativeRot = __instance.relativeRot; + var gameData = GameMain.data; + var gStationPool = gameData.galacticTransport.stationPool; + var astroPoses = gameData.galaxy.astrosData; + var relativePos = gameData.relativePos; + var relativeRot = gameData.relativeRot; var starmap = UIGame.viewMode == EViewMode.Starmap; foreach (var stationComponent in GameMain.data.galacticTransport.stationPool) { - if (stationComponent is { isStellar: true } && !Multiplayer.Session.IsInLobby) + if (stationComponent != null && stationComponent.isStellar && stationComponent.planetId > 0) { + var planet = GameMain.galaxy.PlanetById(stationComponent.planetId); + if (planet == null) continue; + StationComponent_Transpiler.ILSUpdateShipPos(stationComponent, - GameMain.galaxy.PlanetById(stationComponent.planetId).factory, timeGene, shipSailSpeed, shipWarpSpeed, + planet.factory, timeGene, shipSailSpeed, shipWarpSpeed, shipCarries, gStationPool, astroPoses, ref relativePos, ref relativeRot, starmap, null); } } diff --git a/NebulaPatcher/Patches/Dynamic/Player_Patch.cs b/NebulaPatcher/Patches/Dynamic/Player_Patch.cs index 3758c3ab3..668f8581e 100644 --- a/NebulaPatcher/Patches/Dynamic/Player_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/Player_Patch.cs @@ -14,36 +14,83 @@ namespace NebulaPatcher.Patches.Dynamic; [HarmonyPatch(typeof(Player))] internal class Player_Patch { + [HarmonyPrefix] + [HarmonyPatch(nameof(Player.ExchangeSand))] + public static bool ExchangeSand_Prefix(Player __instance) + { + if (!Multiplayer.IsActive) + { + return true; + } + + var gainedSand = 0; + for (var i = 0; i < __instance.package.size; i++) + { + if (__instance.package.grids[i].itemId == 1099) // 1099: enemy drop sand item + { + gainedSand += __instance.package.grids[i].count; + __instance.package.grids[i].itemId = 0; + __instance.package.grids[i].filter = 0; + __instance.package.grids[i].count = 0; + __instance.package.grids[i].inc = 0; + __instance.package.grids[i].stackSize = 0; + } + } + + // Only call SetSandCount when there is sand change in client + if (gainedSand > 0) + { + if (Config.Options.SyncSoil && Multiplayer.Session.IsClient) + { + // Report to server to add sand in shared pool + Multiplayer.Session.Client.SendPacket(new PlayerSandCount(gainedSand, true)); + } + else + { + __instance.SetSandCount(__instance.sandCount + gainedSand); + } + } + return false; + } + + [HarmonyPrefix] [HarmonyPatch(nameof(Player.SetSandCount))] public static bool SetSandCount_Prefix(long newSandCount) { + if (!Multiplayer.IsActive) + { + return true; + } + if (!Config.Options.SyncSoil) { - return !Multiplayer.IsActive || Multiplayer.Session.Factories.PacketAuthor == Multiplayer.Session.LocalPlayer.Id || + return Multiplayer.Session.Factories.PacketAuthor == Multiplayer.Session.LocalPlayer.Id || Multiplayer.Session.LocalPlayer.IsHost && Multiplayer.Session.Factories.PacketAuthor == NebulaModAPI.AUTHOR_NONE || !Multiplayer.Session.Factories.IsIncomingRequest.Value; } - switch (Multiplayer.IsActive) + if (Multiplayer.Session.LocalPlayer.IsHost) { //Soil should be given in singleplayer or to the host who then syncs it back to all players. - case true when Multiplayer.Session.LocalPlayer.IsHost: - var deltaSandCount = (int)(newSandCount - GameMain.mainPlayer.sandCount); - if (deltaSandCount != 0) - { - UpdateSyncedSandCount(deltaSandCount); - Multiplayer.Session.Server.SendPacket(new PlayerSandCount(newSandCount)); - } - break; + var deltaSandCount = (int)(newSandCount - GameMain.mainPlayer.sandCount); + if (deltaSandCount != 0) + { + UpdateSyncedSandCount(deltaSandCount); + Multiplayer.Session.Server.SendPacket(new PlayerSandCount(newSandCount)); + } + } + else + { //Or client that use reform tool - case true when GameMain.mainPlayer.controller.actionBuild.reformTool.drawing: - Multiplayer.Session.Network.SendPacket(new PlayerSandCount(newSandCount)); - break; + if (GameMain.mainPlayer.controller.actionBuild.reformTool.drawing) + { + Multiplayer.Session.Client.SendPacket(new PlayerSandCount(newSandCount)); + } } - return !Multiplayer.IsActive || Multiplayer.Session.LocalPlayer.IsHost; + return Multiplayer.Session.LocalPlayer.IsHost; //Soil should be given in singleplayer or to the player who is author of the "Build" request, or to the host if there is no author. } diff --git a/NebulaWorld/Combat/CombatManager.cs b/NebulaWorld/Combat/CombatManager.cs index e8b22f858..97e72ffe4 100644 --- a/NebulaWorld/Combat/CombatManager.cs +++ b/NebulaWorld/Combat/CombatManager.cs @@ -4,9 +4,11 @@ using System.Collections.Generic; using NebulaAPI.DataStructures; using NebulaModel.DataStructures; +using NebulaModel.Logger; using NebulaModel.Packets.Combat.Mecha; using UnityEngine; #pragma warning disable IDE1006 // Naming Styles +#pragma warning disable CA1822 // Mark members as static #endregion @@ -269,6 +271,23 @@ public void OnFactoryLoadFinished(PlanetFactory factory) { factory.veinPool[i].combatStatId = 0; } + + // Clear the combatStat pool + var astroId = factory.planet.id; + var count = 0; + var combatStats = GameMain.data.spaceSector.skillSystem.combatStats; + var combatStatCursor = combatStats.cursor; + var combatStatbuffer = combatStats.buffer; + for (var i = 1; i < combatStatCursor; i++) + { + ref var ptr = ref combatStatbuffer[i]; + if (ptr.id == i && ptr.astroId == astroId) + { + combatStats.Remove(i); + count++; + } + } + Log.Info($"CombatManager: Clear {count} combatStat on {astroId}"); } public void OnAstroFactoryUnload() diff --git a/NebulaWorld/Logistics/ILSShipManager.cs b/NebulaWorld/Logistics/ILSShipManager.cs index 955f955fa..b53ad6867 100644 --- a/NebulaWorld/Logistics/ILSShipManager.cs +++ b/NebulaWorld/Logistics/ILSShipManager.cs @@ -29,7 +29,7 @@ public static void IdleShipGetToWork(ILSIdleShipBackToWork packet) var planetA = GameMain.galaxy.PlanetById(packet.PlanetA); var planetB = GameMain.galaxy.PlanetById(packet.PlanetB); - if (planetA == null || planetB == null) + if (planetA == null || planetB == null || packet.ThisGId < 1) { return; } @@ -96,7 +96,7 @@ public static void IdleShipGetToWork(ILSIdleShipBackToWork packet) */ public static void WorkShipBackToIdle(ILSWorkShipBackToIdle packet) { - if (!Multiplayer.IsActive || Multiplayer.Session.LocalPlayer.IsHost) + if (packet.GId < 1) { return; }