diff --git a/NebulaModel/Packets/Factory/Belt/BeltReversePacket.cs b/NebulaModel/Packets/Factory/Belt/BeltReversePacket.cs new file mode 100644 index 000000000..a83c8459c --- /dev/null +++ b/NebulaModel/Packets/Factory/Belt/BeltReversePacket.cs @@ -0,0 +1,15 @@ +namespace NebulaModel.Packets.Factory.Belt +{ + public class BeltReversePacket + { + public int BeltId { get; set; } + public int PlanetId { get; set; } + + public BeltReversePacket() { } + public BeltReversePacket(int beltId, int planetId) + { + BeltId = beltId; + PlanetId = planetId; + } + } +} diff --git a/NebulaModel/Packets/Factory/Inserter/InserterOffsetCorrectionPacket.cs b/NebulaModel/Packets/Factory/Inserter/InserterOffsetCorrectionPacket.cs new file mode 100644 index 000000000..4e28605b1 --- /dev/null +++ b/NebulaModel/Packets/Factory/Inserter/InserterOffsetCorrectionPacket.cs @@ -0,0 +1,19 @@ +namespace NebulaModel.Packets.Factory.Inserter +{ + public class InserterOffsetCorrectionPacket + { + public int InserterId { get; set; } + public short PickOffset { get; set; } + public short InsertOffset { get; set; } + public int PlanetId { get; set; } + + public InserterOffsetCorrectionPacket() { } + public InserterOffsetCorrectionPacket(int inserterId, short pickOffset, short insertOffset, int planetId) + { + InserterId = inserterId; + PickOffset = pickOffset; + InsertOffset = insertOffset; + PlanetId = planetId; + } + } +} diff --git a/NebulaModel/Packets/GameHistory/GameHistoryDataResponse.cs b/NebulaModel/Packets/GameHistory/GameHistoryDataResponse.cs index e05dd338a..41f9ca535 100644 --- a/NebulaModel/Packets/GameHistory/GameHistoryDataResponse.cs +++ b/NebulaModel/Packets/GameHistory/GameHistoryDataResponse.cs @@ -3,11 +3,13 @@ public class GameHistoryDataResponse { public byte[] HistoryBinaryData { get; set; } + public bool SandboxToolsEnabled { get; set; } public GameHistoryDataResponse() { } - public GameHistoryDataResponse(byte[] historyBinaryData) + public GameHistoryDataResponse(byte[] historyBinaryData, bool sandboxToolsEnabled) { HistoryBinaryData = historyBinaryData; + SandboxToolsEnabled = sandboxToolsEnabled; } } } diff --git a/NebulaModel/Packets/GameHistory/GameHistoryResearchUpdatePacket.cs b/NebulaModel/Packets/GameHistory/GameHistoryResearchUpdatePacket.cs index 6ca6b7848..32ab0c278 100644 --- a/NebulaModel/Packets/GameHistory/GameHistoryResearchUpdatePacket.cs +++ b/NebulaModel/Packets/GameHistory/GameHistoryResearchUpdatePacket.cs @@ -5,14 +5,16 @@ public class GameHistoryResearchUpdatePacket public int TechId { get; set; } public long HashUploaded { get; set; } public long HashNeeded { get; set; } + public int TechHashedFor10Frames { get; set; } public GameHistoryResearchUpdatePacket() { } - public GameHistoryResearchUpdatePacket(int techId, long hashUploaded, long hashNeeded) + public GameHistoryResearchUpdatePacket(int techId, long hashUploaded, long hashNeeded, int techHashedFor10Frames) { TechId = techId; HashUploaded = hashUploaded; HashNeeded = hashNeeded; + TechHashedFor10Frames = techHashedFor10Frames; } } } diff --git a/NebulaModel/Packets/GameHistory/GameHistoryUnlockTechPacket.cs b/NebulaModel/Packets/GameHistory/GameHistoryUnlockTechPacket.cs index cd7949048..0bb1a41b5 100644 --- a/NebulaModel/Packets/GameHistory/GameHistoryUnlockTechPacket.cs +++ b/NebulaModel/Packets/GameHistory/GameHistoryUnlockTechPacket.cs @@ -3,11 +3,13 @@ public class GameHistoryUnlockTechPacket { public int TechId { get; set; } + public int Level { get; set; } public GameHistoryUnlockTechPacket() { } - public GameHistoryUnlockTechPacket(int techId) + public GameHistoryUnlockTechPacket(int techId, int level) { TechId = techId; + Level = level; } } } diff --git a/NebulaNetwork/PacketProcessors/Factory/Belt/BeltReverseProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/Belt/BeltReverseProcessor.cs new file mode 100644 index 000000000..66ed443bc --- /dev/null +++ b/NebulaNetwork/PacketProcessors/Factory/Belt/BeltReverseProcessor.cs @@ -0,0 +1,40 @@ +using NebulaAPI; +using NebulaModel.Packets.Factory.Belt; + +namespace NebulaNetwork.PacketProcessors.Factory.Belt +{ + [RegisterPacketProcessor] + internal class BeltReverseProcessor : BasePacketProcessor + { + public override void ProcessPacket(BeltReversePacket packet, INebulaConnection conn) + { + PlanetFactory factory = GameMain.galaxy.PlanetById(packet.PlanetId).factory; + if (factory == null) + return; + + using (NebulaModAPI.MultiplayerSession.Factories.IsIncomingRequest.On()) + { + NebulaModAPI.MultiplayerSession.Factories.EventFactory = factory; + NebulaModAPI.MultiplayerSession.Factories.TargetPlanet = packet.PlanetId; + if (NebulaModAPI.MultiplayerSession.LocalPlayer.IsHost) + { + // Load planet model + NebulaModAPI.MultiplayerSession.Factories.AddPlanetTimer(packet.PlanetId); + } + + UIBeltWindow beltWindow = UIRoot.instance.uiGame.beltWindow; + beltWindow._Close(); // close the window first to avoid changing unwant variable when setting beltId + PlanetFactory tmpFactory = beltWindow.factory; + int tmpBeltId = beltWindow.beltId; + beltWindow.factory = factory; + beltWindow.beltId = packet.BeltId; + beltWindow.OnReverseButtonClick(0); + beltWindow.factory = tmpFactory; + beltWindow.beltId = tmpBeltId; + + NebulaModAPI.MultiplayerSession.Factories.EventFactory = null; + NebulaModAPI.MultiplayerSession.Factories.TargetPlanet = NebulaModAPI.PLANET_NONE; + } + } + } +} diff --git a/NebulaNetwork/PacketProcessors/Factory/Belt/BeltUpdatePutItemOnProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/Belt/BeltUpdatePutItemOnProcessor.cs index 6fa615e10..bfb511661 100644 --- a/NebulaNetwork/PacketProcessors/Factory/Belt/BeltUpdatePutItemOnProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Factory/Belt/BeltUpdatePutItemOnProcessor.cs @@ -12,33 +12,9 @@ internal class BeltUpdatePutItemOnProcessor : PacketProcessor + { + public override void ProcessPacket(InserterOffsetCorrectionPacket packet, INebulaConnection conn) + { + InserterComponent[] pool = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory?.factorySystem?.inserterPool; + if (pool != null) + { + NebulaModel.Logger.Log.Warn($"{packet.PlanetId} Fix inserter{packet.InserterId} pickOffset->{packet.PickOffset} insertOffset->{packet.InsertOffset}"); + pool[packet.InserterId].pickOffset = packet.PickOffset; + pool[packet.InserterId].insertOffset = packet.InsertOffset; + } + } + } +} diff --git a/NebulaNetwork/PacketProcessors/Factory/Splitter/SplitterPriorityChangeProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/Splitter/SplitterPriorityChangeProcessor.cs index 3d85f39f9..a048d874c 100644 --- a/NebulaNetwork/PacketProcessors/Factory/Splitter/SplitterPriorityChangeProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Factory/Splitter/SplitterPriorityChangeProcessor.cs @@ -2,7 +2,6 @@ using NebulaModel.Networking; using NebulaModel.Packets; using NebulaModel.Packets.Factory.Splitter; -using NebulaWorld; namespace NebulaNetwork.PacketProcessors.Factory.Splitter { @@ -11,13 +10,11 @@ internal class SplitterPriorityChangeProcessor : PacketProcessor= techState.maxLevel) - { - techState.curLevel = techState.maxLevel; - techState.hashUploaded = techState.hashNeeded; - techState.unlocked = true; - } - else - { - techState.curLevel++; - techState.hashUploaded = 0L; - techState.hashNeeded = techProto.GetHashNeeded(techState.curLevel); - } - // UnlockTech() unlocks tech to techState.maxLevel, so change it to curLevel temporarily - int maxLevl = techState.maxLevel; - techState.maxLevel = techState.curLevel; - GameMain.history.techStates[packet.TechId] = techState; - GameMain.history.UnlockTech(packet.TechId); - techState.maxLevel = maxLevl; + Log.Info($"Unlocking tech={packet.TechId} local:{techState.curLevel} remote:{packet.Level}"); + techState.curLevel = packet.Level; GameMain.history.techStates[packet.TechId] = techState; + + GameMain.history.UnlockTechUnlimited(packet.TechId, false); GameMain.history.DequeueTech(); } } diff --git a/NebulaNetwork/PacketProcessors/Planet/VegeMinedProcessor.cs b/NebulaNetwork/PacketProcessors/Planet/VegeMinedProcessor.cs index 61c30be10..ad12e850e 100644 --- a/NebulaNetwork/PacketProcessors/Planet/VegeMinedProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Planet/VegeMinedProcessor.cs @@ -13,11 +13,12 @@ internal class VegeMinedProcessor : PacketProcessor { public override void ProcessPacket(VegeMinedPacket packet, NebulaConnection conn) { - if (GameMain.galaxy.PlanetById(packet.PlanetId)?.factory != null && GameMain.galaxy.PlanetById(packet.PlanetId)?.factory?.vegePool != null) + PlanetData planetData = GameMain.galaxy.PlanetById(packet.PlanetId); + PlanetFactory factory = planetData?.factory; + if (factory != null && factory?.vegePool != null) { using (Multiplayer.Session.Planets.IsIncomingRequest.On()) { - PlanetFactory factory = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory; if (packet.Amount == 0 && factory != null) { if (packet.IsVein) @@ -26,8 +27,8 @@ public override void ProcessPacket(VegeMinedPacket packet, NebulaConnection conn VeinProto veinProto = LDB.veins.Select((int)veinData.type); factory.RemoveVeinWithComponents(packet.VegeId); - - if (veinProto != null) + + if (veinProto != null && GameMain.localPlanet == planetData) { VFEffectEmitter.Emit(veinProto.MiningEffect, veinData.pos, Maths.SphericalRotation(veinData.pos, 0f)); VFAudio.Create(veinProto.MiningAudio, null, veinData.pos, true, 0, -1, -1L); @@ -40,7 +41,7 @@ public override void ProcessPacket(VegeMinedPacket packet, NebulaConnection conn factory.RemoveVegeWithComponents(packet.VegeId); - if (vegeProto != null) + if (vegeProto != null && GameMain.localPlanet == planetData) { VFEffectEmitter.Emit(vegeProto.MiningEffect, vegeData.pos, Maths.SphericalRotation(vegeData.pos, 0f)); VFAudio.Create(vegeProto.MiningAudio, null, vegeData.pos, true, 0, -1, -1L); diff --git a/NebulaNetwork/PacketProcessors/Session/GlobalGameDataRequestProcessor.cs b/NebulaNetwork/PacketProcessors/Session/GlobalGameDataRequestProcessor.cs index accc93c30..3911d7e11 100644 --- a/NebulaNetwork/PacketProcessors/Session/GlobalGameDataRequestProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Session/GlobalGameDataRequestProcessor.cs @@ -24,7 +24,7 @@ public override void ProcessPacket(GlobalGameDataRequest packet, NebulaConnectio using (BinaryUtils.Writer writer = new BinaryUtils.Writer()) { GameMain.history.Export(writer.BinaryWriter); - conn.SendPacket(new GameHistoryDataResponse(writer.CloseAndGetBytes())); + conn.SendPacket(new GameHistoryDataResponse(writer.CloseAndGetBytes(), GameMain.sandboxToolsEnabled)); } using (BinaryUtils.Writer writer = new BinaryUtils.Writer()) diff --git a/NebulaNetwork/PacketProcessors/Trash/TrashSystemResponseDataProcessor.cs b/NebulaNetwork/PacketProcessors/Trash/TrashSystemResponseDataProcessor.cs index d848827b6..eb06a1d9f 100644 --- a/NebulaNetwork/PacketProcessors/Trash/TrashSystemResponseDataProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Trash/TrashSystemResponseDataProcessor.cs @@ -19,6 +19,12 @@ public override void ProcessPacket(TrashSystemResponseDataPacket packet, NebulaC { GameMain.data.trashSystem.Import(reader.BinaryReader); } + // Wait until WarningDataPacket to assign warningId + TrashContainer container = GameMain.data.trashSystem.container; + for (int i = 0; i < container.trashCursor; i++) + { + container.trashDataPool[i].warningId = -1; + } } } } \ No newline at end of file diff --git a/NebulaNetwork/PacketProcessors/Trash/TrashSystemTrashRemovedProcessor.cs b/NebulaNetwork/PacketProcessors/Trash/TrashSystemTrashRemovedProcessor.cs index 423c1898a..5b00e8ad7 100644 --- a/NebulaNetwork/PacketProcessors/Trash/TrashSystemTrashRemovedProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Trash/TrashSystemTrashRemovedProcessor.cs @@ -9,36 +9,15 @@ namespace NebulaNetwork.PacketProcessors.Trash [RegisterPacketProcessor] internal class TrashSystemTrashRemovedProcessor : PacketProcessor { - private readonly IPlayerManager playerManager; - - public TrashSystemTrashRemovedProcessor() - { - playerManager = Multiplayer.Session.Network.PlayerManager; - } - public override void ProcessPacket(TrashSystemTrashRemovedPacket packet, NebulaConnection conn) { - bool valid = true; - if (IsHost) { - INebulaPlayer player = playerManager.GetPlayer(conn); - if (player != null) - { - playerManager.SendPacketToOtherPlayers(packet, player); - } - else - { - valid = false; - } + Multiplayer.Session.Network.PlayerManager.SendPacketToOtherPlayers(packet, conn); } - - if (valid) + using (Multiplayer.Session.Trashes.RemoveTrashFromOtherPlayers.On()) { - using (Multiplayer.Session.Trashes.RemoveTrashFromOtherPlayers.On()) - { - GameMain.data.trashSystem.container.RemoveTrash(packet.TrashId); - } + GameMain.data.trashSystem.RemoveTrash(packet.TrashId); } } } diff --git a/NebulaNetwork/Server.cs b/NebulaNetwork/Server.cs index c65080112..f52c25980 100644 --- a/NebulaNetwork/Server.cs +++ b/NebulaNetwork/Server.cs @@ -244,7 +244,7 @@ public override void Update() if (GameMain.data.history.currentTech != 0) { TechState state = GameMain.data.history.techStates[GameMain.data.history.currentTech]; - SendPacket(new GameHistoryResearchUpdatePacket(GameMain.data.history.currentTech, state.hashUploaded, state.hashNeeded)); + SendPacket(new GameHistoryResearchUpdatePacket(GameMain.data.history.currentTech, state.hashUploaded, state.hashNeeded, GameMain.statistics.techHashedFor10Frames)); } } diff --git a/NebulaPatcher/Patches/Dynamic/GameData_Patch.cs b/NebulaPatcher/Patches/Dynamic/GameData_Patch.cs index 8a8bf7ffc..3f1740b70 100644 --- a/NebulaPatcher/Patches/Dynamic/GameData_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/GameData_Patch.cs @@ -292,6 +292,7 @@ public static void GameTick_Postfix(GameData __instance, long time) } Multiplayer.Session.Couriers.GameTick(); + Multiplayer.Session.Belts.GameTick(); if (Multiplayer.Session.LocalPlayer.IsHost) { diff --git a/NebulaPatcher/Patches/Dynamic/GameHistoryData_Patch.cs b/NebulaPatcher/Patches/Dynamic/GameHistoryData_Patch.cs index f153d3722..bfba83ac7 100644 --- a/NebulaPatcher/Patches/Dynamic/GameHistoryData_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/GameHistoryData_Patch.cs @@ -26,6 +26,14 @@ public static void EnqueueTech_Postfix(int techId) Multiplayer.Session.Network.SendPacket(new GameHistoryEnqueueTechPacket(techId)); } + [HarmonyPrefix] + [HarmonyPatch(nameof(GameHistoryData.RemoveTechInQueue))] + public static void RemoveTechInQueue_Prefix(int index, out int __state) + { + __state = GameMain.history.techQueue[index]; + Log.Info($"RemoveTechInQueue: remove tech at index {index} with techId { __state}"); + } + [HarmonyPostfix] [HarmonyPatch(nameof(GameHistoryData.RemoveTechInQueue))] public static void RemoveTechInQueue_Postfix(int index, int __state) @@ -119,12 +127,19 @@ public static bool UnlockTech_Prefix() return !Multiplayer.IsActive || Multiplayer.Session.LocalPlayer.IsHost || Multiplayer.Session.History.IsIncomingRequest; } - [HarmonyPrefix] - [HarmonyPatch(nameof(GameHistoryData.RemoveTechInQueue))] - public static void RemoveTechInQueue_Prefix(int index, out int __state) + [HarmonyPostfix] + [HarmonyPatch(nameof(GameHistoryData.NotifyTechUnlock))] + public static void NotifyTechUnlock_Postfix(int _techId, int _level) { - __state = GameMain.history.techQueue[index]; - Log.Info($"RemoveTechInQueue: remove tech at index {index} with techId { __state}"); + Log.Info($"NotifyTechUnlock techId={_techId} level={_level}"); + if (!Multiplayer.IsActive || !Multiplayer.Session.LocalPlayer.IsHost) + { + return; + } + // Synchronize unlocking techs + Log.Info($"Sending Tech Unlocked notification"); + GameMain.mainPlayer.mecha.lab.itemPoints.Clear(); + Multiplayer.Session.Network.SendPacket(new GameHistoryUnlockTechPacket(_techId, _level)); } } } \ No newline at end of file diff --git a/NebulaPatcher/Patches/Dynamic/GameSave_Patch.cs b/NebulaPatcher/Patches/Dynamic/GameSave_Patch.cs index dc7ce8e66..7160d477d 100644 --- a/NebulaPatcher/Patches/Dynamic/GameSave_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/GameSave_Patch.cs @@ -59,5 +59,30 @@ public static bool SaveAsLastExit_Prefix() // Only save if in single player, since multiplayer requires to load from the Load Save Window return (!Multiplayer.IsActive && !Multiplayer.IsLeavingGame); } + + [HarmonyPostfix] + [HarmonyPatch(nameof(GameSave.LoadCurrentGame))] + public static void LoadCurrentGame_Postfix(bool __result) + { + // If loading success, check and correct offset for all inserters + if (__result) + { + for (int index = 0; index < GameMain.data.factoryCount; index++) + { + PlanetFactory factory = GameMain.data.factories[index]; + EntityData[] entityPool = factory.entityPool; + CargoTraffic traffic = factory.factorySystem.traffic; + BeltComponent[] beltPool = factory.factorySystem.traffic.beltPool; + for (int i = 1; i < factory.factorySystem.inserterCursor; i++) + { + ref InserterComponent inserter = ref factory.factorySystem.inserterPool[i]; + if (inserter.id == i) + { + inserter.InternalOffsetCorrection(entityPool, traffic, beltPool); + } + } + } + } + } } } diff --git a/NebulaPatcher/Patches/Dynamic/GameScenarioLogic_Patch.cs b/NebulaPatcher/Patches/Dynamic/GameScenarioLogic_Patch.cs deleted file mode 100644 index 6a3a1e013..000000000 --- a/NebulaPatcher/Patches/Dynamic/GameScenarioLogic_Patch.cs +++ /dev/null @@ -1,27 +0,0 @@ -using HarmonyLib; -using NebulaModel.Logger; -using NebulaModel.Packets.GameHistory; -using NebulaWorld; - -namespace NebulaPatcher.Patches.Dynamic -{ - [HarmonyPatch(typeof(GameScenarioLogic))] - internal class GameScenarioLogic_Patch - { - [HarmonyPostfix] - [HarmonyPatch(nameof(GameScenarioLogic.NotifyOnUnlockTech))] - public static void NotifyOnUnlockTech_Postfix(int techId) - { - //Synchronize unlocking techs - // Do not run if it is not multiplayer and if the player is not a client - if (!Multiplayer.IsActive || !Multiplayer.Session.LocalPlayer.IsHost) - { - return; - } - //Notify all clients about unlocked tech - Log.Info($"Sending Tech Unlocked notification"); - GameMain.mainPlayer.mecha.lab.itemPoints.Clear(); - Multiplayer.Session.Network.SendPacket(new GameHistoryUnlockTechPacket(techId)); - } - } -} diff --git a/NebulaPatcher/Patches/Dynamic/GameStatData_Patch.cs b/NebulaPatcher/Patches/Dynamic/GameStatData_Patch.cs index 34d6cdeb9..d675393a4 100644 --- a/NebulaPatcher/Patches/Dynamic/GameStatData_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/GameStatData_Patch.cs @@ -6,6 +6,24 @@ namespace NebulaPatcher.Patches.Dynamic [HarmonyPatch(typeof(GameStatData))] internal class GameStatData_Patch { + [HarmonyPrefix] + [HarmonyPatch(nameof(GameStatData.AfterTick))] + public static void AfterTick_Prefix(GameStatData __instance) + { + if (Multiplayer.IsActive && !Multiplayer.Session.LocalPlayer.IsHost) + { + if (GameMain.history.currentTech != 0) + { + int hostTechHashedFor10Frames = Multiplayer.Session.Statistics.TechHashedFor10Frames; + __instance.techHashedThisFrame = hostTechHashedFor10Frames / 10; + if (GameMain.gameTick % 10 < hostTechHashedFor10Frames % 10) + { + ++__instance.techHashedThisFrame; + } + } + } + } + [HarmonyPostfix] [HarmonyPatch(nameof(GameStatData.AfterTick))] public static void AfterTick_Postfix() diff --git a/NebulaPatcher/Patches/Dynamic/InserterComponent_Patch.cs b/NebulaPatcher/Patches/Dynamic/InserterComponent_Patch.cs new file mode 100644 index 000000000..2a5529d57 --- /dev/null +++ b/NebulaPatcher/Patches/Dynamic/InserterComponent_Patch.cs @@ -0,0 +1,72 @@ +using HarmonyLib; +using NebulaModel.Logger; +using NebulaModel.Packets.Factory.Inserter; +using NebulaWorld; + +namespace NebulaPatcher.Patches.Dynamic +{ + [HarmonyPatch(typeof(InserterComponent))] + internal class InserterComponent_Patch + { + [HarmonyPrefix] + [HarmonyPatch(nameof(InserterComponent.InternalOffsetCorrection))] + internal static bool InternalOffsetCorrection_Prefix(ref InserterComponent __instance, EntityData[] entityPool, CargoTraffic traffic, BeltComponent[] beltPool) + { + bool flag = false; + int beltId = entityPool[__instance.pickTarget].beltId; + if (beltId > 0) + { + CargoPath cargoPath = traffic.GetCargoPath(beltPool[beltId].segPathId); + if (cargoPath != null) + { + int num = beltPool[beltId].segPivotOffset + beltPool[beltId].segIndex; + int num2 = num + (int)__instance.pickOffset; + if (num2 < 4) + { + num2 = 4; + } + if (num2 + 5 >= cargoPath.pathLength) + { + num2 = cargoPath.pathLength - 5 - 1; + } + if (__instance.pickOffset != (short)(num2 - num)) + { + Log.Warn($"{traffic.factory.planetId} Fix inserter{__instance.id} pickOffset {__instance.pickOffset} -> {num2 - num}"); + __instance.pickOffset = (short)(num2 - num); + flag = true; + } + } + } + int beltId2 = entityPool[__instance.insertTarget].beltId; + if (beltId2 > 0) + { + CargoPath cargoPath2 = traffic.GetCargoPath(beltPool[beltId2].segPathId); + if (cargoPath2 != null) + { + int num3 = beltPool[beltId2].segPivotOffset + beltPool[beltId2].segIndex; + int num4 = num3 + (int)__instance.insertOffset; + if (num4 < 4) + { + num4 = 4; + } + if (num4 + 5 >= cargoPath2.pathLength) + { + num4 = cargoPath2.pathLength - 5 - 1; + } + if (__instance.insertOffset != (short)(num4 - num3)) + { + Log.Warn($"{traffic.factory.planetId} Fix inserter{__instance.id} insertOffset {__instance.insertOffset} -> {num4 - num3}"); + __instance.insertOffset = (short)(num4 - num3); + flag = true; + } + } + } + if (flag && Multiplayer.IsActive) + { + Multiplayer.Session.Network.SendPacketToLocalStar(new InserterOffsetCorrectionPacket(__instance.id, __instance.pickOffset, __instance.insertOffset, traffic.factory.planetId)); + } + + return false; + } + } +} diff --git a/NebulaPatcher/Patches/Dynamic/SplitterComponent_Patch.cs b/NebulaPatcher/Patches/Dynamic/SplitterComponent_Patch.cs deleted file mode 100644 index 8d28a0c3e..000000000 --- a/NebulaPatcher/Patches/Dynamic/SplitterComponent_Patch.cs +++ /dev/null @@ -1,20 +0,0 @@ -using HarmonyLib; -using NebulaModel.Packets.Factory.Splitter; -using NebulaWorld; - -namespace NebulaPatcher.Patches.Dynamic -{ - [HarmonyPatch(typeof(SplitterComponent))] - internal class SplitterComponent_Patch - { - [HarmonyPostfix] - [HarmonyPatch(nameof(SplitterComponent.SetPriority))] - public static void SetPriority_Postfix(SplitterComponent __instance, int slot, bool isPriority, int filter) - { - if (Multiplayer.IsActive && Multiplayer.Session.Storage.IsHumanInput) - { - Multiplayer.Session.Network.SendPacketToLocalStar(new SplitterPriorityChangePacket(__instance.id, slot, isPriority, filter, GameMain.localPlanet?.id ?? -1)); - } - } - } -} diff --git a/NebulaPatcher/Patches/Dynamic/TrashContainer_Patch.cs b/NebulaPatcher/Patches/Dynamic/TrashContainer_Patch.cs index ac24a38ae..16ffd6eb0 100644 --- a/NebulaPatcher/Patches/Dynamic/TrashContainer_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/TrashContainer_Patch.cs @@ -20,7 +20,7 @@ public static void RemoveTrash_Postfix(int index) [HarmonyPostfix] [HarmonyPatch(nameof(TrashContainer.NewTrash))] - public static void NewTrash_Postfix(int __result, TrashObject trashObj, TrashData trashData) + public static void NewTrash_Postfix(TrashContainer __instance, int __result, TrashObject trashObj, TrashData trashData) { //Notify other that trash was created if (Multiplayer.IsActive && !Multiplayer.Session.Trashes.NewTrashFromOtherPlayers) @@ -29,6 +29,11 @@ public static void NewTrash_Postfix(int __result, TrashObject trashObj, TrashDat GameMain.data.trashSystem.Gravity(ref trashData, GameMain.data.galaxy.astrosData, 0, 0, 0, (GameMain.data.localPlanet != null) ? GameMain.data.localPlanet.id : 0, (GameMain.data.localPlanet != null) ? GameMain.data.localPlanet.data : null); Multiplayer.Session.Network.SendPacket(new TrashSystemNewTrashCreatedPacket(__result, trashObj, trashData, Multiplayer.Session.LocalPlayer.Id, GameMain.mainPlayer.planetId)); } + // Wait until WarningDataPacket to assign warningId + if (Multiplayer.IsActive && Multiplayer.Session.LocalPlayer.IsClient) + { + __instance.trashDataPool[__result].warningId = -1; + } } } } diff --git a/NebulaPatcher/Patches/Dynamic/UIBeltWindow_Patch.cs b/NebulaPatcher/Patches/Dynamic/UIBeltWindow_Patch.cs new file mode 100644 index 000000000..7ff875b4b --- /dev/null +++ b/NebulaPatcher/Patches/Dynamic/UIBeltWindow_Patch.cs @@ -0,0 +1,21 @@ +using HarmonyLib; +using NebulaModel.Packets.Factory.Belt; +using NebulaWorld; + +namespace NebulaPatcher.Patches.Dynamic +{ + [HarmonyPatch(typeof(UIBeltWindow))] + internal class UIBeltWindow_Patch + { + [HarmonyPostfix] + [HarmonyPatch(typeof(UIBeltWindow), nameof(UIBeltWindow.OnReverseButtonClick))] + public static void OnReverseButtonClick_Postfix(UIBeltWindow __instance) + { + // Notify others about belt direction reverse + if (Multiplayer.IsActive && !Multiplayer.Session.Factories.IsIncomingRequest.Value) + { + Multiplayer.Session.Network.SendPacketToLocalStar(new BeltReversePacket(__instance.beltId, __instance.factory.planetId)); + } + } + } +} diff --git a/NebulaPatcher/Patches/Dynamic/UIFatalErrorTip_Patch.cs b/NebulaPatcher/Patches/Dynamic/UIFatalErrorTip_Patch.cs index 9af1f31b3..9aaef19f1 100644 --- a/NebulaPatcher/Patches/Dynamic/UIFatalErrorTip_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/UIFatalErrorTip_Patch.cs @@ -49,7 +49,10 @@ public static void _OnOpen_Postfix(UIFatalErrorTip __instance) button.transform.localPosition = errorPanel.transform.Find("icon").localPosition + new Vector3(30, -35, 0); //-885 -30 //-855 -60 button.GetComponent().color = new Color(0.3113f, 0f, 0.0097f, 0.6f); button.GetComponent().BindOnClickSafe(OnClick); - button.GetComponent().tips = new UIButton.TipSettings(); + ref UIButton.TipSettings tips = ref button.GetComponent().tips; + tips.tipTitle = "Copy & Close Error"; + tips.tipText = "Copy the message to clipboard and close error."; + tips.corner = 1; } DedicatedServerReportError(); @@ -83,7 +86,7 @@ private static string Title() stringBuilder.Append(Multiplayer.Session.LocalPlayer.IsHost ? " (Host)" : " (Client)"); } stringBuilder.AppendLine(); - stringBuilder.Append("Mods used: "); + stringBuilder.Append(Chainloader.PluginInfos.Values.Count + " Mods used: "); foreach (BepInEx.PluginInfo pluginInfo in Chainloader.PluginInfos.Values) { stringBuilder.Append('['); diff --git a/NebulaPatcher/Patches/Transpilers/PlanetTransport_Transpiler.cs b/NebulaPatcher/Patches/Transpilers/PlanetTransport_Transpiler.cs new file mode 100644 index 000000000..775a7660e --- /dev/null +++ b/NebulaPatcher/Patches/Transpilers/PlanetTransport_Transpiler.cs @@ -0,0 +1,44 @@ +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; + +namespace NebulaPatcher.Patches.Transpilers +{ + [HarmonyPatch(typeof(PlanetTransport))] + public class PlanetTransport_Transpiler + { + [HarmonyTranspiler] + [HarmonyPatch(nameof(PlanetTransport.RefreshDispenserOnStoragePrebuildBuild))] + public static IEnumerable RefreshDispenserOnStoragePrebuildBuild_Transpiler(IEnumerable instructions) + { + try + { + // factoryModel.gpuiManager is null for remote planets, so we need to use GameMain.gpuiManager which is initialized by nebula + // replace : this.factory.planet.factoryModel.gpuiManager + // with : GameMain.gpuiManager + var codeMatcher = new CodeMatcher(instructions) + .MatchForward(false, + new CodeMatch(OpCodes.Ldarg_0), + new CodeMatch(OpCodes.Ldfld), + new CodeMatch(OpCodes.Callvirt), + new CodeMatch(OpCodes.Ldfld), + new CodeMatch(i => i.opcode == OpCodes.Ldfld && ((FieldInfo)i.operand).Name == "gpuiManager") + ) + .Repeat(matcher => matcher + .RemoveInstructions(4) + .SetAndAdvance(OpCodes.Call, typeof(GameMain).GetProperty("gpuiManager").GetGetMethod() + )); + + return codeMatcher.InstructionEnumeration(); + } + catch (Exception e) + { + NebulaModel.Logger.Log.Error("RefreshDispenserOnStoragePrebuildBuild_Transpiler fail!"); + NebulaModel.Logger.Log.Error(e); + return instructions; + } + } + } +} diff --git a/NebulaPatcher/Patches/Transpilers/UIDysonBrush_Paint_Transpiler.cs b/NebulaPatcher/Patches/Transpilers/UIDysonBrush_Paint_Transpiler.cs index c21da0529..89289f4e0 100644 --- a/NebulaPatcher/Patches/Transpilers/UIDysonBrush_Paint_Transpiler.cs +++ b/NebulaPatcher/Patches/Transpilers/UIDysonBrush_Paint_Transpiler.cs @@ -1,6 +1,7 @@ using HarmonyLib; using NebulaModel.Packets.Universe; using NebulaWorld; +using System; using System.Collections.Generic; using System.Reflection.Emit; @@ -14,10 +15,11 @@ internal class UIDysonBrush_Paint_Transpiler [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Original Function Name")] public static IEnumerable _OnUpdate_Transpiler(IEnumerable instructions) { - try { + try + { int pos1, pos2; CodeMatcher matcher = new CodeMatcher(instructions); - + matcher.MatchForward(false, new CodeMatch(OpCodes.Stfld, AccessTools.Field(typeof(DysonNode), "color"))); pos1 = matcher.Pos; matcher.MatchBack(false, new CodeMatch(OpCodes.Ldloc_S)); @@ -25,7 +27,14 @@ public static IEnumerable _OnUpdate_Transpiler(IEnumerable>((node) => + { + if (Multiplayer.IsActive) + { + int starIndex = UIRoot.instance.uiGame.dysonEditor.selection.viewStar.index; + Multiplayer.Session.Network.SendPacket(new DysonSphereColorChangePacket(starIndex, node.layerId, node.color, DysonSphereColorChangePacket.ComponentType.Node, node.id)); + } + }) ); matcher.MatchForward(false, new CodeMatch(OpCodes.Stfld, AccessTools.Field(typeof(DysonFrame), "color"))); @@ -35,7 +44,14 @@ public static IEnumerable _OnUpdate_Transpiler(IEnumerable>((frame) => + { + if (Multiplayer.IsActive) + { + int starIndex = UIRoot.instance.uiGame.dysonEditor.selection.viewStar.index; + Multiplayer.Session.Network.SendPacket(new DysonSphereColorChangePacket(starIndex, frame.layerId, frame.color, DysonSphereColorChangePacket.ComponentType.Frame, frame.id)); + } + }) ); matcher.MatchForward(false, new CodeMatch(OpCodes.Stfld, AccessTools.Field(typeof(DysonShell), "color"))); @@ -45,7 +61,14 @@ public static IEnumerable _OnUpdate_Transpiler(IEnumerable>((shell) => + { + if (Multiplayer.IsActive) + { + int starIndex = UIRoot.instance.uiGame.dysonEditor.selection.viewStar.index; + Multiplayer.Session.Network.SendPacket(new DysonSphereColorChangePacket(starIndex, shell.layerId, shell.color, DysonSphereColorChangePacket.ComponentType.Shell, shell.id)); + } + }) ); return matcher.InstructionEnumeration(); @@ -56,23 +79,5 @@ public static IEnumerable _OnUpdate_Transpiler(IEnumerable SetPriority_Transpiler(IEnumerable instructions) + { + // Intercept SetPriority() with warper to broadcast the change + try + { + CodeMatcher matcher = new CodeMatcher(instructions) + .MatchForward(false, new CodeMatch(i => i.opcode == OpCodes.Call && ((MethodInfo)i.operand).Name == "SetPriority")) + .Repeat(matcher => matcher + .SetAndAdvance(OpCodes.Call, AccessTools.Method(typeof(UISplitterWindow_Transpiler), nameof(UISplitterWindow_Transpiler.SetPriority))) + ); + return matcher.InstructionEnumeration(); + } + catch + { + NebulaModel.Logger.Log.Error("UISpraycoaterWindow.SetPriority_Transpiler failed. Mod version not compatible with game version."); + return instructions; + } + } + + private static void SetPriority(ref SplitterComponent splitter, int slot, bool isPriority, int filter) + { + splitter.SetPriority(slot, isPriority, filter); + if (Multiplayer.IsActive) + { + Multiplayer.Session.Network.SendPacketToLocalStar(new SplitterPriorityChangePacket(splitter.id, slot, isPriority, filter, GameMain.localPlanet?.id ?? -1)); + } + } + } +} diff --git a/NebulaWorld/Factory/BeltManager.cs b/NebulaWorld/Factory/BeltManager.cs index d4e856657..1348305c1 100644 --- a/NebulaWorld/Factory/BeltManager.cs +++ b/NebulaWorld/Factory/BeltManager.cs @@ -1,4 +1,5 @@ using NebulaModel.Packets.Factory.Belt; +using NebulaModel.Logger; using System; using System.Collections.Generic; @@ -6,28 +7,34 @@ namespace NebulaWorld.Factory { public class BeltManager : IDisposable { - public List BeltUpdates; + private List beltPickupUpdates; + private List beltPutdownPackets; + private int putDownTimer; + const int MAX_PUTDOWN_WAIT_TICK = 30; public BeltManager() { - BeltUpdates = new List(); + beltPickupUpdates = new List(); + beltPutdownPackets = new List(); + putDownTimer = 0; } public void Dispose() { - BeltUpdates = null; + beltPickupUpdates = null; + beltPutdownPackets = null; } public void BeltPickupStarted() { - BeltUpdates.Clear(); + beltPickupUpdates.Clear(); } public void RegisterBeltPickupUpdate(int itemId, int count, int beltId) { if (Multiplayer.IsActive) { - BeltUpdates.Add(new BeltUpdate(itemId, count, beltId)); + beltPickupUpdates.Add(new BeltUpdate(itemId, count, beltId)); } } @@ -35,10 +42,72 @@ public void BeltPickupEnded() { if (GameMain.data.localPlanet != null) { - Multiplayer.Session.Network.SendPacketToLocalStar(new BeltUpdatePickupItemsPacket(BeltUpdates.ToArray(), GameMain.data.localPlanet.id)); + Multiplayer.Session.Network.SendPacketToLocalStar(new BeltUpdatePickupItemsPacket(beltPickupUpdates.ToArray(), GameMain.data.localPlanet.id)); } - BeltUpdates.Clear(); + beltPickupUpdates.Clear(); + } + + public bool TryPutItemOnBelt(BeltUpdatePutItemOnPacket packet) + { + using (Multiplayer.Session.Factories.IsIncomingRequest.On()) + { + CargoTraffic cargoTraffic = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory?.cargoTraffic; + bool ret = false; + if (cargoTraffic == null) + { + // Ignore events on factroies not loaded yet + return true; + } + if (packet.ItemCount == 1) + { + ret = cargoTraffic.PutItemOnBelt(packet.BeltId, packet.ItemId, packet.ItemInc); + return ret; + } + else + { + if (cargoTraffic.beltPool[packet.BeltId].id != 0 && cargoTraffic.beltPool[packet.BeltId].id == packet.BeltId) + { + int index = cargoTraffic.beltPool[packet.BeltId].segIndex + cargoTraffic.beltPool[packet.BeltId].segPivotOffset; + ret = cargoTraffic.GetCargoPath(cargoTraffic.beltPool[packet.BeltId].segPathId).TryInsertItem(index, packet.ItemId, packet.ItemCount, packet.ItemInc); + } + return ret; + } + } + } + + public void RegiserbeltPutdownPacket(BeltUpdatePutItemOnPacket packet) + { + beltPutdownPackets.Add(packet); + } + + public void GameTick() + { + if (beltPutdownPackets.Count <= 0) + { + return; + } + try + { + if (TryPutItemOnBelt(beltPutdownPackets[0])) + { + beltPutdownPackets.RemoveAt(0); + putDownTimer = 0; + } + else if (++putDownTimer > MAX_PUTDOWN_WAIT_TICK) + { + Log.Warn($"Cannot put item{beltPutdownPackets[0].ItemId} on belt{beltPutdownPackets[0].BeltId}, planet{beltPutdownPackets[0].PlanetId}"); + beltPutdownPackets.RemoveAt(0); + putDownTimer = 0; + } + } + catch (Exception ex) + { + Log.Warn($"BeltManager error! Cannot put item{beltPutdownPackets[0].ItemId} on belt{beltPutdownPackets[0].BeltId}, planet{beltPutdownPackets[0].PlanetId}"); + Log.Warn(ex); + beltPutdownPackets.Clear(); + putDownTimer = 0; + } } } } diff --git a/NebulaWorld/SimulatedWorld.cs b/NebulaWorld/SimulatedWorld.cs index 278fa1fd7..d7d6196cc 100644 --- a/NebulaWorld/SimulatedWorld.cs +++ b/NebulaWorld/SimulatedWorld.cs @@ -137,6 +137,25 @@ public void SetupInitialPlayerState() // If warp has unlocked, give new client few warpers GameMain.mainPlayer.TryAddItemToPackage(1210, 5, 0, false); } + // Make new client spawn higher to avoid collision + float magnitude = GameMain.mainPlayer.transform.localPosition.magnitude; + if (magnitude > 0) + { + GameMain.mainPlayer.transform.localPosition *= (magnitude + 20f) / magnitude; + } + } + else + { + // Prevent old client from dropping into gas gaint + PlanetData planet = GameMain.galaxy.PlanetById(player.Data.LocalPlanetId); + if (planet != null) + { + float altitude = GameMain.mainPlayer.transform.localPosition.magnitude - planet.realRadius; + if (altitude > 5f || planet.type == EPlanetType.Gas) + { + GameMain.mainPlayer.movementState = EMovementState.Fly; + } + } } // Refresh Logistics Distributor traffic for player delivery package changes diff --git a/NebulaWorld/Statistics/StatisticsManager.cs b/NebulaWorld/Statistics/StatisticsManager.cs index 80aa59154..02b7ead36 100644 --- a/NebulaWorld/Statistics/StatisticsManager.cs +++ b/NebulaWorld/Statistics/StatisticsManager.cs @@ -28,6 +28,8 @@ public Locker GetRequestors(out Dictionary requestors) public bool IsStatisticsNeeded { get; set; } public long[] PowerEnergyStoredData { get; set; } public int FactoryCount { get; set; } + public int TechHashedFor10Frames { get; set; } + private PlanetData[] planetDataMap; private Dictionary factoryIndexMap; diff --git a/NebulaWorld/Warning/WarningManager.cs b/NebulaWorld/Warning/WarningManager.cs index bb091be40..87cf00c70 100644 --- a/NebulaWorld/Warning/WarningManager.cs +++ b/NebulaWorld/Warning/WarningManager.cs @@ -134,6 +134,10 @@ public int ExportBinaryData(BinaryWriter bw) bw.Write(data.localPos.x); bw.Write(data.localPos.y); bw.Write(data.localPos.z); + + int trashId = data.factoryId == WarningData.TRASH_SYSTEM ? data.objectId : -1; + bw.Write(trashId); + activeWarningCount++; } } @@ -158,14 +162,23 @@ public void ImportBinaryData(BinaryReader br, int activeWarningCount) //index start from 1 in warningPool for (int i = 1; i <= activeWarningCount; i++) { + // factoryId is not synced to skip WarningLogic update in client warningPool[i].id = i; warningPool[i].state = 1; warningPool[i].signalId = br.ReadInt32(); warningPool[i].detailId = br.ReadInt32(); + // localPos is base on astroId warningPool[i].astroId = br.ReadInt32(); warningPool[i].localPos.x = br.ReadSingle(); warningPool[i].localPos.y = br.ReadSingle(); warningPool[i].localPos.z = br.ReadSingle(); + + // reassign warningId for trash + int trashId = br.ReadInt32(); + if (trashId >= 0 && trashId < GameMain.data.trashSystem.container.trashCursor) + { + GameMain.data.trashSystem.container.trashDataPool[trashId].warningId = i; + } } }