diff --git a/NebulaModel/MultiplayerOptions.cs b/NebulaModel/MultiplayerOptions.cs index 440a861d5..71180dc8a 100644 --- a/NebulaModel/MultiplayerOptions.cs +++ b/NebulaModel/MultiplayerOptions.cs @@ -22,6 +22,8 @@ public class MultiplayerOptions : ICloneable [DisplayName("Nickname")] public string Nickname { get; set; } = string.Empty; + [DisplayName("NameTagSize")] public int NameTagSize { get; set; } = 100; + [DisplayName("Server Password")] [Category("Network")] [Description("If provided, this will set a password for your hosted server.")] @@ -118,6 +120,10 @@ public bool StreamerMode } } + [DisplayName("Enable Achievement")] + [Description("Toggle to enable achievement in multiplayer game")] + public bool EnableAchievement { get; set; } = true; + [DisplayName("Chat Hotkey")] [Category("Chat")] [Description("Keyboard shortcut to toggle the chat window")] diff --git a/NebulaModel/Packets/Universe/DysonSphereLoadRequest.cs b/NebulaModel/Packets/Universe/DysonSphereLoadRequest.cs index 9a11b2ae2..89d376bb9 100644 --- a/NebulaModel/Packets/Universe/DysonSphereLoadRequest.cs +++ b/NebulaModel/Packets/Universe/DysonSphereLoadRequest.cs @@ -18,5 +18,6 @@ public enum DysonSphereRequestEvent { List = 1, Load = 2, - Unload = 3 + Unload = 3, + Query = 4 } diff --git a/NebulaNetwork/Messaging/WebSocketService.cs b/NebulaNetwork/Messaging/WebSocketService.cs index 043d6e26a..01521b63d 100644 --- a/NebulaNetwork/Messaging/WebSocketService.cs +++ b/NebulaNetwork/Messaging/WebSocketService.cs @@ -57,8 +57,12 @@ protected override void OnMessage(MessageEventArgs e) protected override void OnClose(CloseEventArgs e) { - var connection = connections[Context.UserEndPoint.GetHashCode()]; - connections.Remove(connection.GetHashCode()); + if (!connections.TryGetValue(Context.UserEndPoint.GetHashCode(), out var connection)) + { + return; + } + connections.Remove(Context.UserEndPoint.GetHashCode()); + // If the reason of a client disconnect is because we are still loading the game, // we don't need to inform the other clients since the disconnected client never // joined the game in the first place. @@ -81,9 +85,13 @@ protected override void OnClose(CloseEventArgs e) protected override void OnError(ErrorEventArgs e) { + if (!connections.TryGetValue(Context.UserEndPoint.GetHashCode(), out var connection)) + { + return; + } connections.Remove(Context.UserEndPoint.GetHashCode()); - // TODO: seems like clients erroring out in the sync process can lock the host with the joining player message, maybe this fixes it + Log.Info($"Client disconnected because of an error: {ID}, reason: {e.Exception}"); UnityDispatchQueue.RunOnMainThread(() => { @@ -91,9 +99,8 @@ protected override void OnError(ErrorEventArgs e) // if it is because we have stopped the server and are not in a multiplayer game anymore. if (Multiplayer.IsActive) { - Server.OnSocketDisconnection(new NebulaConnection(Context.WebSocket, Context.UserEndPoint, - PacketProcessor)); + Server.OnSocketDisconnection(connection); } }); } -} \ No newline at end of file +} diff --git a/NebulaNetwork/PacketProcessors/Universe/DysonSphereRequestProcessor.cs b/NebulaNetwork/PacketProcessors/Universe/DysonSphereRequestProcessor.cs index 2d1faaee7..33096f802 100644 --- a/NebulaNetwork/PacketProcessors/Universe/DysonSphereRequestProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Universe/DysonSphereRequestProcessor.cs @@ -60,6 +60,29 @@ protected override void ProcessPacket(DysonSphereLoadRequest packet, NebulaConne case DysonSphereRequestEvent.Unload: Multiplayer.Session.DysonSpheres.UnRegisterPlayer(conn, packet.StarIndex); break; + + case DysonSphereRequestEvent.Query: + // Ignore query if dyson sphere doesn't exist on host + if (packet.StarIndex < 0 || packet.StarIndex >= GameMain.data.galaxy.starCount) + { + return; + } + dysonSphere = GameMain.data.dysonSpheres[packet.StarIndex]; + if (dysonSphere == null) + { + return; + } + using (var writer = new BinaryUtils.Writer()) + { + dysonSphere.Export(writer.BinaryWriter); + var data = writer.CloseAndGetBytes(); + Log.Info($"Sent {data.Length} bytes of data for DysonSphereData (INDEX: {packet.StarIndex})"); + conn.SendPacket(new FragmentInfo(data.Length)); + conn.SendPacket(new DysonSphereData(packet.StarIndex, data, DysonSphereRespondEvent.Load)); + Multiplayer.Session.DysonSpheres.RegisterPlayer(conn, packet.StarIndex); + } + break; + default: throw new ArgumentOutOfRangeException(nameof(packet), "Unknown DysonSphereRequestEvent: " + packet.Event); } diff --git a/NebulaNetwork/SaveManager.cs b/NebulaNetwork/SaveManager.cs index bf999bc79..eb589e94d 100644 --- a/NebulaNetwork/SaveManager.cs +++ b/NebulaNetwork/SaveManager.cs @@ -147,7 +147,7 @@ public static void LoadServerData() break; } - if (playerSaves.ContainsKey(hash) && playerData != null) + if (!playerSaves.ContainsKey(hash) && playerData != null) { playerSaves.Add(hash, playerData); } diff --git a/NebulaPatcher/Patches/Dynamic/AbnormalityLogic_Patch.cs b/NebulaPatcher/Patches/Dynamic/AbnormalityLogic_Patch.cs new file mode 100644 index 000000000..f61911413 --- /dev/null +++ b/NebulaPatcher/Patches/Dynamic/AbnormalityLogic_Patch.cs @@ -0,0 +1,20 @@ +#region + +using HarmonyLib; +using NebulaModel; +using NebulaWorld; + +#endregion + +namespace NebulaPatcher.Patches.Dynamic; + +[HarmonyPatch(typeof(AbnormalityLogic))] +internal class AbnormalityLogic_Patch +{ + [HarmonyPrefix] + [HarmonyPatch(nameof(AbnormalityLogic.GameTick))] + public static bool GameTick_Prefix() + { + return !Multiplayer.IsActive || !Config.Options.EnableAchievement; + } +} diff --git a/NebulaPatcher/Patches/Dynamic/AchievementLogic_Patch.cs b/NebulaPatcher/Patches/Dynamic/AchievementLogic_Patch.cs new file mode 100644 index 000000000..4ae92a149 --- /dev/null +++ b/NebulaPatcher/Patches/Dynamic/AchievementLogic_Patch.cs @@ -0,0 +1,27 @@ +#region + +using HarmonyLib; +using NebulaModel; +using NebulaWorld; + +#endregion + +namespace NebulaPatcher.Patches.Dynamic; + +[HarmonyPatch(typeof(AchievementLogic))] +internal class AchievementLogic_Patch +{ + [HarmonyPrefix] + [HarmonyPatch(nameof(AchievementLogic.isSelfFormalGame), MethodType.Getter)] + public static bool IsSelfFormalGame_Prefix(ref bool __result) + { + if (!Multiplayer.IsActive) + { + return true; + } + + // Decide to enable achievement or not in multiplayer game + __result = Config.Options.EnableAchievement; + return false; + } +} diff --git a/NebulaPatcher/Patches/Dynamic/GameAbnormalityData_0925_Patch.cs b/NebulaPatcher/Patches/Dynamic/GameAbnormalityData_0925_Patch.cs new file mode 100644 index 000000000..afd88b04e --- /dev/null +++ b/NebulaPatcher/Patches/Dynamic/GameAbnormalityData_0925_Patch.cs @@ -0,0 +1,35 @@ +#region + +using ABN; +using HarmonyLib; +using NebulaModel; +using NebulaWorld; + +#endregion + +namespace NebulaPatcher.Patches.Dynamic; + +[HarmonyPatch(typeof(GameAbnormalityData_0925))] +internal class GameAbnormalityData_0925_Patch +{ + [HarmonyPrefix] + [HarmonyPatch(nameof(GameAbnormalityData_0925.TriggerAbnormality))] + public static bool TriggerAbnormality_Prefix() + { + return !Multiplayer.IsActive || !Config.Options.EnableAchievement; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(GameAbnormalityData_0925.NothingAbnormal))] + [HarmonyPatch(nameof(GameAbnormalityData_0925.IsAbnormalTriggerred))] + public static bool NothingAbnormal_Prefix(ref bool __result) + { + if (!Multiplayer.IsActive || !Config.Options.EnableAchievement) + { + return true; + } + + __result = true; + return false; + } +} diff --git a/NebulaPatcher/Patches/Dynamic/UIStarmap_Patch.cs b/NebulaPatcher/Patches/Dynamic/UIStarmap_Patch.cs index 144f6f86e..6a9c8656f 100644 --- a/NebulaPatcher/Patches/Dynamic/UIStarmap_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/UIStarmap_Patch.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using HarmonyLib; +using NebulaModel.Packets.Universe; using NebulaWorld; #endregion @@ -49,4 +50,28 @@ public static bool TeleportToUPosition_Prefix(VectorLF3 uPos) InGamePopup.ShowWarning("Unavailable", "Cannot teleport to gas giant", "OK"); return false; } + + static int s_queryingIndex = -1; + + [HarmonyPrefix] + [HarmonyPatch(typeof(UIStarmap), nameof(UIStarmap.OnCursorFunction2Click))] + [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Original Function Name")] + public static void QueryDysonSphere(UIStarmap __instance) + { + // Client: Query existing dyson sphere when clicking on 'View' star on starmap + if (!Multiplayer.IsActive || Multiplayer.Session.LocalPlayer.IsHost || Multiplayer.Session.IsInLobby || __instance.focusStar == null) + { + return; + } + + var starIndex = __instance.focusStar.star.index; + if (GameMain.data.dysonSpheres[starIndex] == null) + { + if (s_queryingIndex != starIndex) + { + Multiplayer.Session.Network.SendPacket(new DysonSphereLoadRequest(starIndex, DysonSphereRequestEvent.Query)); + s_queryingIndex = starIndex; + } + } + } } diff --git a/NebulaWorld/Logistics/StationUIManager.cs b/NebulaWorld/Logistics/StationUIManager.cs index 399d19cc9..183ec7b59 100644 --- a/NebulaWorld/Logistics/StationUIManager.cs +++ b/NebulaWorld/Logistics/StationUIManager.cs @@ -46,7 +46,7 @@ public static void UpdateStation(ref StationUI packet) public void UpdateStorage(StorageUI packet) { var stationComponent = GetStation(packet.PlanetId, packet.StationId, packet.StationGId); - if (stationComponent == null || stationComponent.storage.Length == 0) + if (stationComponent?.storage == null || stationComponent.storage.Length == 0) { return; } diff --git a/NebulaWorld/RemotePlayerModel.cs b/NebulaWorld/RemotePlayerModel.cs index db5c6fa7c..a7969c7a9 100644 --- a/NebulaWorld/RemotePlayerModel.cs +++ b/NebulaWorld/RemotePlayerModel.cs @@ -90,7 +90,7 @@ public RemotePlayerModel(ushort playerId, string username) public Transform PlayerModelTransform { get; set; } public RemotePlayerMovement Movement { get; set; } public RemotePlayerAnimation Animator { get; set; } - public GameObject InGameNameText { get; set; } + public TextMesh InGameNameText { get; set; } public Text StarmapNameText { get; set; } public Transform StarmapTracker { get; set; } diff --git a/NebulaWorld/SimulatedWorld.cs b/NebulaWorld/SimulatedWorld.cs index 0d97c7adf..559d4f396 100644 --- a/NebulaWorld/SimulatedWorld.cs +++ b/NebulaWorld/SimulatedWorld.cs @@ -455,54 +455,50 @@ public void ClearPlayerNameTagsOnStarmap() public void RenderPlayerNameTagsInGame() { - TextMesh uiSailIndicator_targetText = null; - using (GetRemotePlayersModels(out var remotePlayersModels)) { foreach (var playerModel in remotePlayersModels.Select(player => player.Value)) { - GameObject playerNameText; + TextMesh playerNameText; if (playerModel.InGameNameText != null) { playerNameText = playerModel.InGameNameText; } else { - // Only get the field required if we actually need to, no point getting it every time - if (uiSailIndicator_targetText == null) - { - uiSailIndicator_targetText = UIRoot.instance.uiGame.sailIndicator.targetText; - } + var uiSailIndicator_targetText = UIRoot.instance.uiGame.sailIndicator.targetText; // Initialise a new game object to contain the text - playerModel.InGameNameText = playerNameText = new GameObject(); + var go = new GameObject(); // Make it follow the player transform - playerNameText.transform.SetParent(playerModel.PlayerTransform, false); + go.transform.SetParent(playerModel.PlayerTransform, false); // Add a meshrenderer and textmesh component to show the text with a different font - var meshRenderer = playerNameText.AddComponent(); - var textMesh = playerNameText.AddComponent(); + var meshRenderer = go.AddComponent(); + meshRenderer.sharedMaterial = + uiSailIndicator_targetText.gameObject.GetComponent().sharedMaterial; + var textMesh = go.AddComponent(); // Set the text to be their name textMesh.text = $"{playerModel.Username}"; // Align it to be centered below them textMesh.anchor = TextAnchor.UpperCenter; // Copy the font over from the sail indicator textMesh.font = uiSailIndicator_targetText.font; - meshRenderer.sharedMaterial = - uiSailIndicator_targetText.gameObject.GetComponent().sharedMaterial; + textMesh.fontSize = 36; - playerNameText.SetActive(true); + playerModel.InGameNameText = playerNameText = textMesh; + playerNameText.gameObject.SetActive(true); } // If the player is not on the same planet or is in space, then do not render their in-world tag if (playerModel.Movement.localPlanetId != Multiplayer.Session.LocalPlayer.Data.LocalPlanetId && playerModel.Movement.localPlanetId <= 0) { - playerNameText.SetActive(false); + playerNameText.gameObject.SetActive(false); } - else if (!playerNameText.activeSelf) + else if (!playerNameText.gameObject.activeSelf) { - playerNameText.SetActive(true); + playerNameText.gameObject.SetActive(true); } // Make sure the text is pointing at the camera @@ -512,23 +508,9 @@ public void RenderPlayerNameTagsInGame() // Resizes the text based on distance from camera for better visual quality var distanceFromCamera = Vector3.Distance(playerNameText.transform.position, transform.position); - var nameTextMesh = playerNameText.GetComponent(); - switch (distanceFromCamera) - { - case > 100f: - nameTextMesh.characterSize = 0.2f; - nameTextMesh.fontSize = 60; - break; - case > 50f: - nameTextMesh.characterSize = 0.15f; - nameTextMesh.fontSize = 48; - break; - default: - nameTextMesh.characterSize = 0.1f; - nameTextMesh.fontSize = 36; - break; - } + var scaleRatio = Config.Options.NameTagSize / (GameCamera.instance.planetMode ? 10000f : 30000f); + playerNameText.characterSize = scaleRatio * Mathf.Clamp(distanceFromCamera, 20f, 200f); } } }