diff --git a/NebulaModel/Packets/Chat/NewChatMessagePacket.cs b/NebulaModel/Packets/Chat/NewChatMessagePacket.cs index 31dee010e..4457a7208 100644 --- a/NebulaModel/Packets/Chat/NewChatMessagePacket.cs +++ b/NebulaModel/Packets/Chat/NewChatMessagePacket.cs @@ -11,11 +11,11 @@ public class NewChatMessagePacket { public NewChatMessagePacket() { } - public NewChatMessagePacket(ChatMessageType messageType, string messageText, DateTime sentAt, string userName) + public NewChatMessagePacket(ChatMessageType messageType, string messageText, DateTime sentAt = default, string userName = "") { MessageType = messageType; MessageText = messageText; - SentAt = sentAt.ToBinary(); + SentAt = sentAt == default ? 0 : sentAt.ToBinary(); UserName = userName; } diff --git a/NebulaModel/Packets/Warning/WarningBroadcastDataPacket.cs b/NebulaModel/Packets/Warning/WarningBroadcastDataPacket.cs new file mode 100644 index 000000000..315047428 --- /dev/null +++ b/NebulaModel/Packets/Warning/WarningBroadcastDataPacket.cs @@ -0,0 +1,21 @@ +using NebulaAPI.DataStructures; +using UnityEngine; + +namespace NebulaModel.Packets.Warning; + +public class WarningBroadcastDataPacket +{ + public WarningBroadcastDataPacket() { } + public WarningBroadcastDataPacket(EBroadcastVocal vocal, int astroId, int content, in Vector3 lpos) + { + Vocal = (short)vocal; + AstroId = astroId; + Content = content; + Lpos = new Float3(lpos); + } + + public short Vocal { get; set; } + public int AstroId { get; set; } + public int Content { get; set; } + public Float3 Lpos { get; set; } +} diff --git a/NebulaNetwork/PacketProcessors/Chat/NewChatMessageProcessor.cs b/NebulaNetwork/PacketProcessors/Chat/NewChatMessageProcessor.cs index 94c94203d..2a7388f40 100644 --- a/NebulaNetwork/PacketProcessors/Chat/NewChatMessageProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Chat/NewChatMessageProcessor.cs @@ -29,6 +29,12 @@ protected override void ProcessPacket(NewChatMessagePacket packet, NebulaConnect Server.SendPacketExclude(packet, conn); } + if (string.IsNullOrEmpty(packet.UserName)) + { + // non-player chat + ChatManager.Instance.SendChatMessage(packet.MessageText, packet.MessageType); + return; + } var sentAt = packet.SentAt == 0 ? DateTime.Now : DateTime.FromBinary(packet.SentAt); ChatManager.Instance.SendChatMessage(ChatManager.FormatChatMessage(sentAt, packet.UserName, packet.MessageText), packet.MessageType); diff --git a/NebulaNetwork/PacketProcessors/Combat/DFRelay/DFRelayArriveBaseProcessor.cs b/NebulaNetwork/PacketProcessors/Combat/DFRelay/DFRelayArriveBaseProcessor.cs index dd312632a..bce16c771 100644 --- a/NebulaNetwork/PacketProcessors/Combat/DFRelay/DFRelayArriveBaseProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Combat/DFRelay/DFRelayArriveBaseProcessor.cs @@ -42,12 +42,6 @@ protected override void ProcessPacket(DFRelayArriveBasePacket packet, NebulaConn EnemyManager.SetPlanetFactoryNextEnemyId(factory, packet.NextGroundEnemyId); } dfrelayComponent.ArriveBase(); - - // Display message when DF relay is successfully landed and the planet has buildings - if (packet.HasFactory) - { - Multiplayer.Session.Enemies.DisplayPlanetPingMessage("DF relay landed on planet".Translate(), dfrelayComponent.targetAstroId, dfrelayComponent.targetLPos); - } } } } diff --git a/NebulaNetwork/PacketProcessors/Combat/DFTinder/DFTinderDispatchProcessor.cs b/NebulaNetwork/PacketProcessors/Combat/DFTinder/DFTinderDispatchProcessor.cs index 6cdfeb192..4b7b28e13 100644 --- a/NebulaNetwork/PacketProcessors/Combat/DFTinder/DFTinderDispatchProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Combat/DFTinder/DFTinderDispatchProcessor.cs @@ -18,8 +18,6 @@ protected override void ProcessPacket(DFTinderDispatchPacket packet, NebulaConne var hiveSystem = GameMain.spaceSector.GetHiveByAstroId(packet.OriginHiveAstroId); if (hiveSystem == null) return; - Multiplayer.Session.Enemies.DisplayAstroMessage("DF seed sent out from".Translate(), hiveSystem.starData.astroId); - ref var tinderComponent = ref hiveSystem.tinders.buffer[packet.TinderId]; if (tinderComponent.id != packet.TinderId) return; diff --git a/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGLaunchAssaultProcessor.cs b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGLaunchAssaultProcessor.cs index 6e352e5ce..0797984bc 100644 --- a/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGLaunchAssaultProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Combat/GroundEnemy/DFGLaunchAssaultProcessor.cs @@ -20,8 +20,6 @@ protected override void ProcessPacket(DFGLaunchAssaultPacket packet, NebulaConne var factory = GameMain.galaxy.PlanetById(packet.PlanetId)?.factory; if (factory == null) { - // Display message in chat if it can't show in UIDarkFogMonitor - Multiplayer.Session.Enemies.DisplayPlanetPingMessage("Planetary base is attacking".Translate(), packet.PlanetId, packet.TarPos.ToVector3()); return; } diff --git a/NebulaNetwork/PacketProcessors/Combat/SpaceEnemy/DFSLaunchLancerAssaultProcessor.cs b/NebulaNetwork/PacketProcessors/Combat/SpaceEnemy/DFSLaunchLancerAssaultProcessor.cs index 629c4d04a..aa31e8fd4 100644 --- a/NebulaNetwork/PacketProcessors/Combat/SpaceEnemy/DFSLaunchLancerAssaultProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Combat/SpaceEnemy/DFSLaunchLancerAssaultProcessor.cs @@ -17,7 +17,6 @@ public class DFSLaunchLancerAssaultProcessor : PacketProcessor +{ + protected override void ProcessPacket(WarningBroadcastDataPacket packet, NebulaConnection conn) + { + using (Multiplayer.Session.Warning.IsIncomingBroadcast.On()) + { + var vocal = (EBroadcastVocal)packet.Vocal; + var factoryIndex = GameMain.data.galaxy.astrosFactory[packet.AstroId]?.index ?? -1; + var astroId = packet.AstroId; + var content = packet.Content; + var lpos = packet.Lpos.ToVector3(); + + if (lpos == Vector3.zero) + { + GameMain.data.warningSystem.Broadcast(vocal, factoryIndex, astroId, content); + } + else + { + GameMain.data.warningSystem.Broadcast(vocal, factoryIndex, astroId, content, lpos); + } + } + } +} diff --git a/NebulaPatcher/Patches/Dynamic/DFRelayComponent_Patch.cs b/NebulaPatcher/Patches/Dynamic/DFRelayComponent_Patch.cs index 0b88e7003..1108893f2 100644 --- a/NebulaPatcher/Patches/Dynamic/DFRelayComponent_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/DFRelayComponent_Patch.cs @@ -54,7 +54,7 @@ public static void ArriveBase_Postfix(DFRelayComponent __instance) var factory = __instance.hive.galaxy.astrosFactory[__instance.targetAstroId]; if (factory != null && factory.entityCount > 0 && __instance.baseId > 0) { - Multiplayer.Session.Enemies.DisplayPlanetPingMessage("DF relay landed on planet".Translate(), __instance.targetAstroId, __instance.targetLPos); + Multiplayer.Session.Enemies.SendPlanetPosMessage("DF relay landed on".Translate(), __instance.targetAstroId, __instance.targetLPos); } } @@ -80,7 +80,7 @@ public static bool LeaveBase_Prefix(DFRelayComponent __instance) var planet = GameMain.galaxy.PlanetById(__instance.targetAstroId); if (planet != null) { - Multiplayer.Session.Enemies.DisplayPlanetPingMessage("DF relay left from planet".Translate(), __instance.targetAstroId, __instance.targetLPos); + Multiplayer.Session.Enemies.SendPlanetPosMessage("DF relay left from".Translate(), __instance.targetAstroId, __instance.targetLPos); } } diff --git a/NebulaPatcher/Patches/Dynamic/DFTinderComponent_Patch.cs b/NebulaPatcher/Patches/Dynamic/DFTinderComponent_Patch.cs index 59d183bf3..7c34e9278 100644 --- a/NebulaPatcher/Patches/Dynamic/DFTinderComponent_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/DFTinderComponent_Patch.cs @@ -24,7 +24,19 @@ public static bool DispatchFromHive_Prefix(ref DFTinderComponent __instance, int { __instance.targetHiveAstroId = _targetHiveAstroId; Multiplayer.Session.Network.SendPacket(new DFTinderDispatchPacket(__instance)); - Multiplayer.Session.Enemies.DisplayAstroMessage("DF seed sent out from".Translate(), hive1.starData.astroId); + var hasFactory = false; + foreach (var planet in hive2.starData.planets) + { + if (planet != null && planet.factory != null && planet.factory.entityCount > 0) + { + hasFactory = true; + break; + } + } + if (hasFactory) + { + Multiplayer.Session.Enemies.SendAstroMessage("DF seed sent out".Translate(), hive1.starData.astroId, hive2.starData.astroId); + } } return true; } diff --git a/NebulaPatcher/Patches/Dynamic/EnemyDFHiveSystem_Patch.cs b/NebulaPatcher/Patches/Dynamic/EnemyDFHiveSystem_Patch.cs index 87bd8ef83..f1d6865c5 100644 --- a/NebulaPatcher/Patches/Dynamic/EnemyDFHiveSystem_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/EnemyDFHiveSystem_Patch.cs @@ -233,19 +233,24 @@ public static bool LaunchLancerAssault_Prefix(EnemyDFHiveSystem __instance, EAgg if (!Multiplayer.IsActive) return true; if (Multiplayer.Session.IsClient) return Multiplayer.Session.Enemies.IsIncomingRequest; - // Brocast launch assault events to all players + // Broadcast launch assault events to all players var packet = new DFSLaunchLancerAssaultPacket(in __instance, aggressiveLevel, in tarPos, in maxHatredPos, targetAstroId, unitCount0, unitThreat); Multiplayer.Session.Server.SendPacket(packet); - Multiplayer.Session.Enemies.DisplayAstroMessage("Space hive is attacking".Translate(), packet.TargetAstroId); + Multiplayer.Session.Enemies.SendAstroMessage("Space hive is attacking".Translate(), packet.TargetAstroId); return true; } [HarmonyPrefix] [HarmonyPatch(nameof(EnemyDFHiveSystem.NotifyRelayKilled))] - public static bool NotifyRelayKilled(EnemyDFHiveSystem __instance, ref EnemyData enemy) + public static bool NotifyRelayKilled_Prefix(EnemyDFHiveSystem __instance, ref EnemyData enemy) { - if (!Multiplayer.IsActive || Multiplayer.Session.IsServer) return true; + if (!Multiplayer.IsActive) return true; + if (Multiplayer.Session.IsServer) + { + Multiplayer.Session.Enemies.SendAstroMessage("DF relay killed on".Translate(), enemy.astroId); + return true; + } var dfrelayComponent = __instance.relays.buffer[enemy.dfRelayId]; if (dfrelayComponent?.id == enemy.dfRelayId) diff --git a/NebulaPatcher/Patches/Dynamic/UIBroadcastWarningEntry_Patch.cs b/NebulaPatcher/Patches/Dynamic/UIBroadcastWarningEntry_Patch.cs new file mode 100644 index 000000000..cdb2883f9 --- /dev/null +++ b/NebulaPatcher/Patches/Dynamic/UIBroadcastWarningEntry_Patch.cs @@ -0,0 +1,71 @@ +#region + +using HarmonyLib; +using NebulaModel.Packets.Factory.Tank; +using NebulaWorld; +using UnityEngine; + +#endregion + +namespace NebulaPatcher.Patches.Dynamic; + +[HarmonyPatch(typeof(UIBroadcastWarningEntry))] +internal class UIBroadcastWarningEntry_Patch +{ + [HarmonyPrefix] + [HarmonyPatch(nameof(UIBroadcastWarningEntry.DrawLine))] + public static bool DrawLine_Prefix(UIBroadcastWarningEntry __instance) + { + if (!Multiplayer.IsActive || Multiplayer.Session.IsServer) return true; + + // Fix guide line to unloaded factory in client + switch (__instance.broadcastData.vocal) + { + case EBroadcastVocal.LandingRelay: + case EBroadcastVocal.ApproachingSeed: + // Guard to avoid index out of range exception + if (__instance.broadcastData.context >= __instance.window.gameData.spaceSector.enemyPool.Length) + { + return false; + } + return true; + + case EBroadcastVocal.BuildingDestroyed: + case EBroadcastVocal.MineralDepleted: + case EBroadcastVocal.OilSeepDepleted: + // Replace factory reference with planet + var position = __instance.trackerTrans.transform.position; + var vector = UIRoot.instance.overlayCanvas.worldCamera.WorldToScreenPoint(position); + var startPos = GameCamera.main.ScreenPointToRay(vector).GetPoint(4.5f); + var relativePos = __instance.window.gameData.relativePos; + var relativeRot = __instance.window.gameData.relativeRot; + var planet = GameMain.galaxy.PlanetById(__instance.broadcastData.astroId); + if (planet == null) return true; + + var lpos = __instance.broadcastData.lpos; + var vectorLF5 = planet.uPosition + Maths.QRotateLF(planet.runtimeRotation, lpos); + var vectorLF6 = Maths.QInvRotateLF(relativeRot, vectorLF5 - relativePos); + UniverseSimulator.VirtualMapping(vectorLF6.x, vectorLF6.y, vectorLF6.z, GameCamera.main.transform.position, out var endPos, out _, 10000.0); + + if (__instance.gizmo == null) + { + __instance.gizmo = LineGizmo.Create(1, startPos, endPos); + __instance.gizmo.autoRefresh = false; + __instance.gizmo.multiplier = 1.5f; + __instance.gizmo.alphaMultiplier = 0.4f; + __instance.gizmo.width = 0.15f; + __instance.gizmo.color = Configs.builtin.gizmoColors[3]; + __instance.gizmo.spherical = false; + __instance.gizmo.Open(); + return false; + } + __instance.gizmo.startPoint = startPos; + __instance.gizmo.endPoint = endPos; + __instance.gizmo.ManualRefresh(); + return false; + + default: + return true; + } + } +} diff --git a/NebulaPatcher/Patches/Dynamic/WarningSystem_Patch.cs b/NebulaPatcher/Patches/Dynamic/WarningSystem_Patch.cs index 41a571c2d..7dc84e58a 100644 --- a/NebulaPatcher/Patches/Dynamic/WarningSystem_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/WarningSystem_Patch.cs @@ -1,8 +1,11 @@ #region +using System; +using System.Collections.Generic; using HarmonyLib; using NebulaModel.Packets.Warning; using NebulaWorld; +using UnityEngine; #endregion @@ -12,8 +15,8 @@ namespace NebulaPatcher.Patches.Dynamic; internal class WarningSystem_Patch { [HarmonyPrefix] - [HarmonyPatch(typeof(WarningSystem), nameof(WarningSystem.RemoveWarningData))] - [HarmonyPatch(typeof(WarningSystem), nameof(WarningSystem.WarningLogic))] + [HarmonyPatch(nameof(WarningSystem.RemoveWarningData))] + [HarmonyPatch(nameof(WarningSystem.WarningLogic))] public static bool AlterWarningData_Prefix() { //Let warningPool only be updated by packet @@ -21,7 +24,7 @@ public static bool AlterWarningData_Prefix() } [HarmonyPrefix] - [HarmonyPatch(typeof(WarningSystem), nameof(WarningSystem.NewWarningData))] + [HarmonyPatch(nameof(WarningSystem.NewWarningData))] public static void NewWarningData_Prefix(WarningSystem __instance, ref int factoryId) { if (!Multiplayer.IsActive || Multiplayer.Session.LocalPlayer.IsHost) @@ -37,7 +40,7 @@ public static void NewWarningData_Prefix(WarningSystem __instance, ref int facto } [HarmonyPrefix] - [HarmonyPatch(typeof(WarningSystem), nameof(WarningSystem.CalcFocusDetail))] + [HarmonyPatch(nameof(WarningSystem.CalcFocusDetail))] public static void CalcFocusDetail_Prefix(int __0) { if (__0 == 0 || !Multiplayer.IsActive || Multiplayer.Session.LocalPlayer.IsHost) @@ -55,4 +58,59 @@ public static void CalcFocusDetail_Prefix(int __0) Multiplayer.Session.Network.SendPacket(new WarningDataRequest(WarningRequestEvent.Data)); Multiplayer.Session.Warning.LastRequestTime = GameMain.gameTick; } + + + [HarmonyPrefix] + [HarmonyPatch(nameof(WarningSystem.CheckRelayAndSeed))] + [HarmonyPatch(nameof(WarningSystem.CheckShieldCollapse))] + public static bool DisableCheckInClient() + { + return !Multiplayer.IsActive || Multiplayer.Session.IsServer; + } + + static readonly HashSet syncBroadcasts = + [ + EBroadcastVocal.LandingRelay, + EBroadcastVocal.PlanetaryShieldDown, + EBroadcastVocal.ApproachingSeed, + EBroadcastVocal.BuildingDestroyed, + EBroadcastVocal.MineralDepleted, + EBroadcastVocal.OilSeepDepleted + ]; + + [HarmonyPrefix] + [HarmonyPatch(nameof(WarningSystem.Broadcast), new Type[] { typeof(EBroadcastVocal), typeof(int), typeof(int), typeof(int), typeof(Vector3) })] + public static bool Broadcast_Prefix(WarningSystem __instance, EBroadcastVocal vocal, int factoryIndex, int astroId, int context, Vector3 lpos) + { + if (!Multiplayer.IsActive || !syncBroadcasts.Contains(vocal)) return true; + + // In client, let server authorize the trigger of those sync broadcast + if (Multiplayer.Session.IsClient) return Multiplayer.Session.Warning.IsIncomingBroadcast.Value; + lock (__instance.broadcasts) + { + if (__instance.IndexOf(vocal, factoryIndex, astroId, context) < 0) // new data + { + Multiplayer.Session.Server.SendPacket(new WarningBroadcastDataPacket(vocal, astroId, context, lpos)); + } + } + return true; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(WarningSystem.Broadcast), new Type[] { typeof(EBroadcastVocal), typeof(int), typeof(int), typeof(int) })] + public static bool Broadcast_Prefix(WarningSystem __instance, EBroadcastVocal vocal, int factoryIndex, int astroId, int context) + { + if (!Multiplayer.IsActive || !syncBroadcasts.Contains(vocal)) return true; + + // In client, let server authorize the trigger of those sync broadcast + if (Multiplayer.Session.IsClient) return Multiplayer.Session.Warning.IsIncomingBroadcast.Value; + lock (__instance.broadcasts) + { + if (__instance.IndexOf(vocal, factoryIndex, astroId, context) < 0) // new data + { + Multiplayer.Session.Server.SendPacket(new WarningBroadcastDataPacket(vocal, astroId, context, Vector3.zero)); + } + } + return true; + } } diff --git a/NebulaWorld/Combat/EnemyManager.cs b/NebulaWorld/Combat/EnemyManager.cs index 89c1278da..7ff84f844 100644 --- a/NebulaWorld/Combat/EnemyManager.cs +++ b/NebulaWorld/Combat/EnemyManager.cs @@ -5,6 +5,7 @@ using NebulaModel.DataStructures; using NebulaModel.DataStructures.Chat; using NebulaModel.Logger; +using NebulaModel.Packets.Chat; using NebulaModel.Packets.Combat.DFHive; using NebulaModel.Packets.Combat.GroundEnemy; using NebulaWorld.Chat.ChatLinks; @@ -86,31 +87,44 @@ public void BroadcastHiveStatusPackets(EnemyDFHiveSystem hive, long gameTick) } } - public void DisplayPlanetPingMessage(string text, int planetId, Vector3 pos) + public void SendPlanetPosMessage(string text, int planetId, Vector3 pos) { + if (Multiplayer.Session.IsClient) return; var planet = GameMain.galaxy.PlanetById(planetId); if (planet == null) return; var message = text + " [" + NavigateChatLinkHandler.FormatNavigateToPlanetPos(planetId, pos, planet.displayName) + "]"; ChatManager.Instance.SendChatMessage(message, ChatMessageType.BattleMessage); + Multiplayer.Session.Network.SendPacket(new NewChatMessagePacket(ChatMessageType.BattleMessage, message)); } - public void DisplayAstroMessage(string text, int astroId) + public void SendAstroMessage(string text, int astroId0, int astroId1 = 0) { - string displayMessage = null; + if (Multiplayer.Session.IsClient) return; + var message = text + " " + GetAstroLinkText(astroId0); + if (astroId1 != 0) message += " => " + GetAstroLinkText(astroId1); + ChatManager.Instance.SendChatMessage(message, ChatMessageType.BattleMessage); + Multiplayer.Session.Network.SendPacket(new NewChatMessagePacket(ChatMessageType.BattleMessage, message)); + } + + static string GetAstroLinkText(int astroId) + { + string displayName = null; if (GameMain.galaxy.PlanetById(astroId) != null) { - displayMessage = GameMain.galaxy.PlanetById(astroId).displayName; + displayName = GameMain.galaxy.PlanetById(astroId).displayName; } else if (GameMain.galaxy.StarById(astroId / 100) != null) { - displayMessage = GameMain.galaxy.StarById(astroId / 100).displayName; + displayName = GameMain.galaxy.StarById(astroId / 100).displayName; } - if (displayMessage == null) return; - - var message = text + " [" + NavigateChatLinkHandler.FormatNavigateToAstro(astroId, displayMessage) + "]"; - ChatManager.Instance.SendChatMessage(message, ChatMessageType.BattleMessage); + else if (GameMain.spaceSector.GetHiveByAstroId(astroId) != null) + { + displayName = GameMain.spaceSector.GetHiveByAstroId(astroId).displayName; + } + if (displayName == null) return ""; + return "[" + NavigateChatLinkHandler.FormatNavigateToAstro(astroId, displayName) + "]"; } public void OnFactoryLoadFinished(PlanetFactory factory) diff --git a/NebulaWorld/Warning/WarningManager.cs b/NebulaWorld/Warning/WarningManager.cs index c34e60e5a..b93e76ac2 100644 --- a/NebulaWorld/Warning/WarningManager.cs +++ b/NebulaWorld/Warning/WarningManager.cs @@ -16,6 +16,7 @@ namespace NebulaWorld.Warning; public class WarningManager : IDisposable { public readonly ToggleSwitch IsIncomingMonitorPacket = new(); + public readonly ToggleSwitch IsIncomingBroadcast = new(); private int idleCycle; private ConcurrentBag requesters = [];