diff --git a/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs index ad9912df5b..0e40564ac6 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs @@ -91,10 +91,11 @@ public static MethodDefinition ProcessCommandCall(WeaverTypes weaverTypes, Write ParameterDefinition param = md.Parameters[i]; if (NetworkBehaviourProcessor.IsSenderConnection(param, RemoteCallType.Command)) { - // load 'this.' - worker.Emit(OpCodes.Ldarg_0); - // call get_connectionToClient - worker.Emit(OpCodes.Call, weaverTypes.NetworkBehaviourConnectionToClientReference); + // Server-owned object => no owner connection (connectionToClient is null). + // Host calling [Command] should use NetworkServer.localConnection as sender. + // Load NetworkServer.localConnection + // Fixes: https://github.com/MirrorNetworking/Mirror/issues/4070 + worker.Emit(OpCodes.Call, weaverTypes.NetworkServerLocalConnectionReference); } else { diff --git a/Assets/Mirror/Editor/Weaver/WeaverTypes.cs b/Assets/Mirror/Editor/Weaver/WeaverTypes.cs index 62d654bebd..27c3b7ba30 100644 --- a/Assets/Mirror/Editor/Weaver/WeaverTypes.cs +++ b/Assets/Mirror/Editor/Weaver/WeaverTypes.cs @@ -17,6 +17,7 @@ public class WeaverTypes public MethodReference GetWriterReference; public MethodReference ReturnWriterReference; + public MethodReference NetworkServerLocalConnectionReference; public MethodReference NetworkClientConnectionReference; public MethodReference RemoteCallDelegateConstructor; @@ -86,6 +87,7 @@ public WeaverTypes(AssemblyDefinition assembly, Logger Log, ref bool WeavingFail TypeReference NetworkServerType = Import(typeof(NetworkServer)); NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_active", ref WeavingFailed); + NetworkServerLocalConnectionReference = Resolvers.ResolveMethod(NetworkServerType, assembly, Log, "get_localConnection", ref WeavingFailed); TypeReference NetworkClientType = Import(typeof(NetworkClient)); NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_active", ref WeavingFailed); diff --git a/Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs b/Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs index 819e98e198..ecc40ee97a 100644 --- a/Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkServer/NetworkServerTest.cs @@ -851,10 +851,9 @@ public void SendCommand_RequiresAuthority() Assert.That(serverComponent.called, Is.EqualTo(0)); } - // [Command] with NetworkConnectionToClient parameter on host should be automatically set to .connectionToClient. - // this is what happens in server-only mode too. + // Command with NetworkConnectionToClient arg expects a non-null connection arg when called on the client owned object. [Test] - public void SendCommand_ConnectionToClient_HostMode() + public void SendCommand_ClientOwnedObject_ConnectionNotNull() { // listen & connect NetworkServer.Listen(1); @@ -871,10 +870,35 @@ public void SendCommand_ConnectionToClient_HostMode() // on server it should be != null Assert.That(comp.conn, !Is.Null); + // Host player-owned object: sender == connectionToClient (host's localConnection). Assert.That(comp.conn, Is.EqualTo(comp.connectionToClient)); Assert.That(comp.called, Is.EqualTo(1)); } + // Command with NetworkConnectionToClient arg expects a non-null connection arg when called on the server owned object. + [Test] + public void SendCommand_ServerOwnedObject_ConnectionNotNull() + { + // listen & connect + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + + // add an identity with two networkbehaviour components + // spawned, otherwise command handler won't find it in .spawned. + // WITHOUT OWNER (SERVER-OWNED OBJECT) + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out CommandWithConnectionToClientNetworkBehaviour comp, ownerConnection: null); + + // call the command, which has a 'NetworkConnectionToClient = null' default parameter + comp.TestCommand(); + ProcessMessages(); + + // on server it should be != null + Assert.That(comp.conn, !Is.Null); + // Host mode + server-owned object: connectionToClient is null, so sender must be localConnection. + Assert.That(comp.conn, Is.EqualTo(NetworkServer.localConnection)); + Assert.That(comp.called, Is.EqualTo(1)); + } + [Test] public void SendToAll() {