From dde3b5b794c854abf7eee922801f818c7564cc86 Mon Sep 17 00:00:00 2001 From: Leon Davis Date: Thu, 21 Aug 2025 04:35:08 +0100 Subject: [PATCH 1/3] Fix implicit casts --- .../Components/MainMenu/ServerBrowser/ServerBrowserElement.cs | 4 ++-- Multiplayer/Networking/Managers/Server/NetworkServer.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Multiplayer/Components/MainMenu/ServerBrowser/ServerBrowserElement.cs b/Multiplayer/Components/MainMenu/ServerBrowser/ServerBrowserElement.cs index 18476b38..5110137e 100644 --- a/Multiplayer/Components/MainMenu/ServerBrowser/ServerBrowserElement.cs +++ b/Multiplayer/Components/MainMenu/ServerBrowser/ServerBrowserElement.cs @@ -76,7 +76,7 @@ protected override void Awake() goIconLAN = this.FindChildByName("LAN Icon"); } else - { + { goIconLAN = Instantiate(goIconPassword, goIconPassword.transform.parent); goIconLAN.name = "LAN Icon"; Vector3 LANpos = goIconLAN.transform.localPosition; @@ -113,7 +113,7 @@ public void UpdateView() serverName.text = data.Name; playerCount.text = $"{data.CurrentPlayers} / {data.MaxPlayers}"; - ping.text = $"{(data.Ping < 0 ? "?" : data.Ping)} ms"; + ping.text = $"{(data.Ping < 0 ? "?" : data.Ping.ToString())} ms"; // Hide the icon if the server does not have a password goIconPassword.SetActive(data.HasPassword); diff --git a/Multiplayer/Networking/Managers/Server/NetworkServer.cs b/Multiplayer/Networking/Managers/Server/NetworkServer.cs index a71b1def..06244f56 100644 --- a/Multiplayer/Networking/Managers/Server/NetworkServer.cs +++ b/Multiplayer/Networking/Managers/Server/NetworkServer.cs @@ -208,7 +208,7 @@ public override void OnPeerConnected(ITransportPeer peer) public override void OnPeerDisconnected(ITransportPeer peer, DisconnectReason disconnectReason) { byte id = (byte)peer.Id; - Log($"Player {(serverPlayers.TryGetValue(id, out ServerPlayer player) ? player : id)} disconnected: {disconnectReason}"); + Log($"Player {(serverPlayers.TryGetValue(id, out ServerPlayer player) ? player.Id : id)} disconnected: {disconnectReason}"); if (WorldStreamingInit.isLoaded) SaveGameManager.Instance.UpdateInternalData(); @@ -1176,7 +1176,7 @@ private void OnServerboundWarehouseMachineControllerRequestPacket(ServerboundWar if(!NetworkedWarehouseMachineController.Get(packet.NetId, out var targetWarehouse)) { LogWarning($"ServerboundWarehouseMachineControllerRequestPacket() WarehouseMachineController not found. NetId: {packet.NetId}"); - return; + return; } //Todo: add check for player distance from machine From 1b3cf799ac2130e3224c4cd159dac1b2e62f1d5f Mon Sep 17 00:00:00 2001 From: Leon Davis Date: Thu, 21 Aug 2025 17:23:06 +0100 Subject: [PATCH 2/3] Allow clients to spawn cars Whenever the client's CarSpawner.SpawnCar() or CarSpawner.SpawnCars functions are called: 1. The client sends the server a spawn request packet with the required information. 2. The client-side car is deleted. 3. The server spawns an appropriate NetworkedTrainCar. 4. The server sends all clients a request to spawn that NetworkedTrainCar. This could be done more efficiently, but it works well from testing. --- .../Managers/Client/NetworkClient.cs | 5 + .../Networking/Managers/NetworkManager.cs | 4 +- .../Managers/Server/NetworkServer.cs | 16 ++++ .../Train/ServerboundTrainCarSpawnRequest.cs | 23 +++++ .../CommsRadio/CommsRadioCarSpawnerPatch.cs | 43 --------- Multiplayer/Patches/Train/CarSpawnerPatch.cs | 91 +++++++++++++------ 6 files changed, 107 insertions(+), 75 deletions(-) create mode 100644 Multiplayer/Networking/Packets/Serverbound/Train/ServerboundTrainCarSpawnRequest.cs delete mode 100644 Multiplayer/Patches/CommsRadio/CommsRadioCarSpawnerPatch.cs diff --git a/Multiplayer/Networking/Managers/Client/NetworkClient.cs b/Multiplayer/Networking/Managers/Client/NetworkClient.cs index f2f22879..4eceeffc 100644 --- a/Multiplayer/Networking/Managers/Client/NetworkClient.cs +++ b/Multiplayer/Networking/Managers/Client/NetworkClient.cs @@ -1293,6 +1293,11 @@ public void SendTrainSyncRequest(ushort netId) }, DeliveryMethod.ReliableUnordered); } + public void SendTrainCarSpawnRequest(TrainCar trainCar) + { + SendPacketToServer(ServerboundTrainCarSpawnRequest.FromTrainCar(trainCar), DeliveryMethod.ReliableOrdered); + } + public void SendTrainDeleteRequest(ushort netId) { SendPacketToServer(new ServerboundTrainDeleteRequestPacket diff --git a/Multiplayer/Networking/Managers/NetworkManager.cs b/Multiplayer/Networking/Managers/NetworkManager.cs index 68d06194..6fafb025 100644 --- a/Multiplayer/Networking/Managers/NetworkManager.cs +++ b/Multiplayer/Networking/Managers/NetworkManager.cs @@ -29,8 +29,8 @@ public abstract class NetworkManager protected NetworkManager(Settings settings) { netPacketProcessor = new NetPacketProcessor(); - //transport = new LiteNetLibTransport(); - transport = new SteamWorksTransport(); + transport = new LiteNetLibTransport(); + // transport = new SteamWorksTransport(); transport.OnConnectionRequest += OnConnectionRequest; transport.OnPeerConnected += OnPeerConnected; diff --git a/Multiplayer/Networking/Managers/Server/NetworkServer.cs b/Multiplayer/Networking/Managers/Server/NetworkServer.cs index 06244f56..07e322fe 100644 --- a/Multiplayer/Networking/Managers/Server/NetworkServer.cs +++ b/Multiplayer/Networking/Managers/Server/NetworkServer.cs @@ -31,6 +31,7 @@ using Multiplayer.Networking.Packets.Serverbound.Train; using Multiplayer.Networking.Packets.Unconnected; using System.Text; +using Multiplayer.Components; using Multiplayer.Networking.Data.Train; using Multiplayer.Networking.TransportLayers; using Multiplayer.Networking.Packets.Serverbound.Jobs; @@ -131,6 +132,7 @@ protected override void Subscribe() netPacketProcessor.SubscribeReusable(OnServerboundPlayerPositionPacket); netPacketProcessor.SubscribeReusable(OnServerboundTrainSyncRequestPacket); + netPacketProcessor.SubscribeReusable(OnServerboundTrainCarSpawnRequest); netPacketProcessor.SubscribeReusable(OnServerboundTrainDeleteRequestPacket); netPacketProcessor.SubscribeReusable(OnServerboundTrainRerailRequestPacket); netPacketProcessor.SubscribeReusable(OnServerboundLicensePurchaseRequestPacket); @@ -1025,6 +1027,20 @@ private void OnServerboundTrainSyncRequestPacket(ServerboundTrainSyncRequestPack networkedTrainCar.Server_DirtyAllState(); } + private void OnServerboundTrainCarSpawnRequest(ServerboundTrainCarSpawnRequest packet) + { + if (!TrainComponentLookup.Instance.LiveryFromId(packet.LiveryID, out TrainCarLivery livery)) + { + NetworkLifecycle.Instance.Client.LogDebug(() => $"Tried spawning car but couldn't find TrainCarLivery with ID {packet.LiveryID}"); + return; + } + + Multiplayer.Log($"Client spawned {livery.name} car"); + TrainCar trainCar = CarSpawner.Instance.SpawnCar(livery.prefab, RailTrack.GetClosest(packet.Position).track, packet.Position, packet.Forward, true); + NetworkedTrainCar networkedTrainCar = NetworkedCarSpawner.SpawnCar(TrainsetSpawnPart.FromTrainSet([trainCar])[0]); + NetworkLifecycle.Instance.Server.SendSpawnTrainCar(networkedTrainCar); + } + private void OnServerboundTrainDeleteRequestPacket(ServerboundTrainDeleteRequestPacket packet, ITransportPeer peer) { if (!TryGetServerPlayer(peer, out ServerPlayer player)) diff --git a/Multiplayer/Networking/Packets/Serverbound/Train/ServerboundTrainCarSpawnRequest.cs b/Multiplayer/Networking/Packets/Serverbound/Train/ServerboundTrainCarSpawnRequest.cs new file mode 100644 index 00000000..fbeccfcd --- /dev/null +++ b/Multiplayer/Networking/Packets/Serverbound/Train/ServerboundTrainCarSpawnRequest.cs @@ -0,0 +1,23 @@ +using DV.ThingTypes; +using UnityEngine; + +namespace Multiplayer.Networking.Packets.Serverbound.Train; + +public class ServerboundTrainCarSpawnRequest +{ + public Vector3 Position { get; set; } + public Vector3 Forward { get; set; } + public bool Derailed { get; set; } + public string LiveryID { get; set; } + + public static ServerboundTrainCarSpawnRequest FromTrainCar(TrainCar trainCar) + { + return new ServerboundTrainCarSpawnRequest + { + Position = trainCar.transform.position, + Forward = trainCar.transform.forward, + Derailed = trainCar.derailed, + LiveryID = trainCar.carLivery.id, + }; + } +} diff --git a/Multiplayer/Patches/CommsRadio/CommsRadioCarSpawnerPatch.cs b/Multiplayer/Patches/CommsRadio/CommsRadioCarSpawnerPatch.cs deleted file mode 100644 index 092e5874..00000000 --- a/Multiplayer/Patches/CommsRadio/CommsRadioCarSpawnerPatch.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Collections; -using DV; -using DV.InventorySystem; -using HarmonyLib; -using Multiplayer.Components.Networking; -using Multiplayer.Components.Networking.Train; -using Multiplayer.Utils; -using UnityEngine; - -namespace Multiplayer.Patches.CommsRadio; - - -[HarmonyPatch(typeof(CommsRadioCarSpawner))] -public static class CommsRadioCarSpawnerPatch -{ - [HarmonyPrefix] - [HarmonyPatch(nameof(CommsRadioCarSpawner.OnUse))] - private static bool OnUse_Prefix(CommsRadioCarSpawner __instance) - { - if (__instance.state != CommsRadioCarSpawner.State.PickDestination) - return true; - if (NetworkLifecycle.Instance.IsHost()) - return true; - - //temporarily disable client spawning - CommsRadioController.PlayAudioFromRadio(__instance.cancelSound, __instance.transform); - __instance.ClearFlags(); - return false; - - } -} - - //private static IEnumerator PlaySoundsLater(CommsRadioCarDeleter __instance, Vector3 trainPosition, bool playMoneyRemovedSound = true) - //{ - // yield return new WaitForSecondsRealtime((NetworkLifecycle.Instance.Client.Ping * 3f)/1000); - // if (playMoneyRemovedSound && __instance.moneyRemovedSound != null) - // __instance.moneyRemovedSound.Play2D(); - // // The TrainCar may already be deleted when we're done waiting, so we play the sound manually. - // __instance.removeCarSound.Play(trainPosition, minDistance: CommsRadioController.CAR_AUDIO_SOURCE_MIN_DISTANCE, parent: WorldMover.Instance.originShiftParent); - // CommsRadioController.PlayAudioFromRadio(__instance.confirmSound, __instance.transform); - //} - - diff --git a/Multiplayer/Patches/Train/CarSpawnerPatch.cs b/Multiplayer/Patches/Train/CarSpawnerPatch.cs index 7aaad68f..e68fc0dd 100644 --- a/Multiplayer/Patches/Train/CarSpawnerPatch.cs +++ b/Multiplayer/Patches/Train/CarSpawnerPatch.cs @@ -3,6 +3,8 @@ using Multiplayer.Components.Networking.Train; using Multiplayer.Utils; using System.Collections.Generic; +using DV.ThingTypes; +using UnityEngine; namespace Multiplayer.Patches.Train; @@ -24,59 +26,88 @@ private static void PrepareTrainCarForDeleting(TrainCar trainCar) NetworkLifecycle.Instance.Server?.SendDestroyTrainCar(networkedTrainCar); } - //Called from - [HarmonyPatch(nameof(CarSpawner.SpawnCars))] - [HarmonyPostfix] - private static void SpawnCars(List __result) - { - if (UnloadWatcher.isUnloading) - return; - - if (!NetworkLifecycle.Instance.IsHost()) - return; - - if (__result == null || __result.Count == 0) - return; - - //Coupling is delayed by AutoCouple(), so a true trainset for the entire consist doesn't exist yet - Multiplayer.LogDebug(() => $"SpawnCars() {__result?.Count} cars spawned, sending to players"); - NetworkLifecycle.Instance.Server.SendSpawnTrainset(__result, true, true); - - } - - [HarmonyPatch(nameof(CarSpawner.SpawnCarFromRemote))] + [HarmonyPatch(nameof(CarSpawner.SpawnCar))] [HarmonyPostfix] - private static void SpawnCarFromRemote(TrainCar __result) + private static void SpawnCar(TrainCar __result) { if (UnloadWatcher.isUnloading) return; if (!NetworkLifecycle.Instance.IsHost()) + { + NetworkLifecycle.Instance.Client.SendTrainCarSpawnRequest(__result); + CarSpawner.Instance.DeleteCar(__result); return; + } if (__result == null) return; - Multiplayer.LogDebug(() => $"SpawnCarFromRemote() {__result?.carLivery?.name} spawned, sending to players"); + //Coupling is delayed by AutoCouple(), so a true trainset for the entire consist doesn't exist yet + Multiplayer.LogDebug(() => $"SpawnCar() {__result?.carLivery?.name} car spawned, sending to players"); NetworkLifecycle.Instance.Server.SendSpawnTrainset([__result], true, true); - } - [HarmonyPatch(nameof(CarSpawner.SpawnCarOnClosestTrack))] + [HarmonyPatch(nameof(CarSpawner.SpawnCars))] [HarmonyPostfix] - private static void SpawnCarOnClosestTrack(TrainCar __result) + private static void SpawnCars(List __result) { if (UnloadWatcher.isUnloading) return; if (!NetworkLifecycle.Instance.IsHost()) - return; + { + // todo: Create a SendTrainSetSpawnRequest packet and use that in these situations. + foreach (TrainCar trainCar in __result) + { + NetworkLifecycle.Instance.Client.SendTrainCarSpawnRequest(trainCar); + CarSpawner.Instance.DeleteCar(trainCar); + } - if (__result == null) return; + } - Multiplayer.LogDebug(() => $"SpawnCarOnClosestTrack() {__result?.carLivery?.name} spawned, sending to players"); - NetworkLifecycle.Instance.Server.SendSpawnTrainset([__result], true, true); + if (__result == null || __result.Count == 0) + return; + //Coupling is delayed by AutoCouple(), so a true trainset for the entire consist doesn't exist yet + Multiplayer.LogDebug(() => $"SpawnCars() {__result?.Count} cars spawned, sending to players"); + NetworkLifecycle.Instance.Server.SendSpawnTrainset(__result, true, true); } + + // [HarmonyPatch(nameof(CarSpawner.SpawnCarFromRemote))] + // [HarmonyPostfix] + // private static void SpawnCarFromRemote(TrainCar __result) + // { + // if (UnloadWatcher.isUnloading) + // return; + // + // if (!NetworkLifecycle.Instance.IsHost()) + // return; + // + // if (__result == null) + // return; + // + // Multiplayer.LogDebug(() => $"SpawnCarFromRemote() {__result?.carLivery?.name} spawned, sending to players"); + // NetworkLifecycle.Instance.Server.SendSpawnTrainset([__result], true, true); + // + // } + // + // [HarmonyPatch(nameof(CarSpawner.SpawnCarOnClosestTrack))] + // [HarmonyPostfix] + // private static void SpawnCarOnClosestTrack(TrainCar __result) + // { + // if (UnloadWatcher.isUnloading) + // return; + // + // if (!NetworkLifecycle.Instance.IsHost()) + // return; + // + // if (__result == null) + // return; + // + // Multiplayer.LogDebug(() => $"SpawnCarOnClosestTrack() {__result?.carLivery?.name} spawned, sending to players"); + // NetworkLifecycle.Instance.Server.SendSpawnTrainset([__result], true, true); + // + // } } From e6361fdce679f14ae743828fedaab988fbd88b97 Mon Sep 17 00:00:00 2001 From: Leon Davis Date: Thu, 21 Aug 2025 17:53:49 +0100 Subject: [PATCH 3/3] Revert to using the SteamWorks transport layer --- Multiplayer/Networking/Managers/NetworkManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Multiplayer/Networking/Managers/NetworkManager.cs b/Multiplayer/Networking/Managers/NetworkManager.cs index 6fafb025..3bf9e5ab 100644 --- a/Multiplayer/Networking/Managers/NetworkManager.cs +++ b/Multiplayer/Networking/Managers/NetworkManager.cs @@ -29,8 +29,8 @@ public abstract class NetworkManager protected NetworkManager(Settings settings) { netPacketProcessor = new NetPacketProcessor(); - transport = new LiteNetLibTransport(); - // transport = new SteamWorksTransport(); + // transport = new LiteNetLibTransport(); + transport = new SteamWorksTransport(); transport.OnConnectionRequest += OnConnectionRequest; transport.OnPeerConnected += OnPeerConnected;