diff --git a/Server/Client.cs b/Server/Client.cs index 5f3336f..7e9434f 100644 --- a/Server/Client.cs +++ b/Server/Client.cs @@ -76,6 +76,17 @@ public class Client : IDisposable { await Socket!.SendAsync(data[..(Constants.HeaderSize + header.PacketSize)], SocketFlags.None); } + public void CleanMetadataOnNewConnection() { + object? tmp; + Metadata.TryRemove("time", out tmp); + Metadata.TryRemove("seeking", out tmp); + Metadata.TryRemove("lastCostumePacket", out tmp); + Metadata.TryRemove("lastCapturePacket", out tmp); + Metadata.TryRemove("lastTagPacket", out tmp); + Metadata.TryRemove("lastGamePacket", out tmp); + Metadata.TryRemove("lastPlayerPacket", out tmp); + } + public static bool operator ==(Client? left, Client? right) { return left is { } leftClient && right is { } rightClient && leftClient.Id == rightClient.Id; } diff --git a/Server/Program.cs b/Server/Program.cs index 7e46b1e..230459c 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -72,13 +72,6 @@ server.ClientJoined += (c, _) => { c.Metadata["scenario"] = (byte?) 0; c.Metadata["2d"] = false; c.Metadata["speedrun"] = false; - foreach (Client client in server.ClientsConnected) { - try { - c.Send((GamePacket) client.Metadata["lastGamePacket"]!, client).Wait(); - } catch { - // lol who gives a fuck - } - } }; async Task ClientSyncShineBag(Client client) { @@ -115,13 +108,36 @@ timer.Start(); float MarioSize(bool is2d) => is2d ? 180 : 160; +void flipPlayer(Client c, ref PlayerPacket pp) { + pp.Position += Vector3.UnitY * MarioSize((bool) c.Metadata["2d"]!); + pp.Rotation *= ( + Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationX(MathF.PI)) + * Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(MathF.PI)) + ); +}; + +void logError(Task x) { + if (x.Exception != null) { + consoleLogger.Error(x.Exception.ToString()); + } +}; + server.PacketHandler = (c, p) => { switch (p) { case GamePacket gamePacket: { c.Logger.Info($"Got game packet {gamePacket.Stage}->{gamePacket.ScenarioNum}"); + + // reset lastPlayerPacket on stage changes + object? old = null; + c.Metadata.TryGetValue("lastGamePacket", out old); + if (old != null && ((GamePacket) old).Stage != gamePacket.Stage) { + c.Metadata["lastPlayerPacket"] = null; + } + c.Metadata["scenario"] = gamePacket.ScenarioNum; c.Metadata["2d"] = gamePacket.Is2d; c.Metadata["lastGamePacket"] = gamePacket; + switch (gamePacket.Stage) { case "CapWorldHomeStage" when gamePacket.ScenarioNum == 0: c.Metadata["speedrun"] = true; @@ -145,8 +161,7 @@ server.PacketHandler = (c, p) => { server.BroadcastReplace(gamePacket, c, (from, to, gp) => { gp.ScenarioNum = (byte?) to.Metadata["scenario"] ?? 200; #pragma warning disable CS4014 - to.Send(gp, from) - .ContinueWith(x => { if (x.Exception != null) { consoleLogger.Error(x.Exception.ToString()); } }); + to.Send(gp, from).ContinueWith(logError); #pragma warning restore CS4014 }); return false; @@ -156,14 +171,23 @@ server.PacketHandler = (c, p) => { } case TagPacket tagPacket: { + // c.Logger.Info($"Got tag packet: {tagPacket.IsIt}"); + c.Metadata["lastTagPacket"] = tagPacket; if ((tagPacket.UpdateType & TagPacket.TagUpdate.State) != 0) c.Metadata["seeking"] = tagPacket.IsIt; if ((tagPacket.UpdateType & TagPacket.TagUpdate.Time) != 0) c.Metadata["time"] = new Time(tagPacket.Minutes, tagPacket.Seconds, DateTime.Now); break; } + case CapturePacket capturePacket: { + // c.Logger.Info($"Got capture packet: {capturePacket.ModelName}"); + c.Metadata["lastCapturePacket"] = capturePacket; + break; + } + case CostumePacket costumePacket: c.Logger.Info($"Got costume packet: {costumePacket.BodyName}, {costumePacket.CapName}"); + c.Metadata["lastCostumePacket"] = costumePacket; c.CurrentCostume = costumePacket; #pragma warning disable CS4014 ClientSyncShineBag(c); //no point logging since entire def has try/catch @@ -183,33 +207,35 @@ server.PacketHandler = (c, p) => { break; } - case PlayerPacket playerPacket when Settings.Instance.Flip.Enabled - && Settings.Instance.Flip.Pov is FlipOptions.Both or FlipOptions.Others - && Settings.Instance.Flip.Players.Contains(c.Id): { - playerPacket.Position += Vector3.UnitY * MarioSize((bool) c.Metadata["2d"]!); - playerPacket.Rotation *= Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationX(MathF.PI)) - * Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(MathF.PI)); + case PlayerPacket playerPacket: { + c.Metadata["lastPlayerPacket"] = playerPacket; + // flip for all + if ( Settings.Instance.Flip.Enabled + && Settings.Instance.Flip.Pov is FlipOptions.Both or FlipOptions.Others + && Settings.Instance.Flip.Players.Contains(c.Id) + ) { + flipPlayer(c, ref playerPacket); #pragma warning disable CS4014 - server.Broadcast(playerPacket, c) - .ContinueWith(x => { if (x.Exception != null) { consoleLogger.Error(x.Exception.ToString()); } }); + server.Broadcast(playerPacket, c).ContinueWith(logError); #pragma warning restore CS4014 - return false; - } - case PlayerPacket playerPacket when Settings.Instance.Flip.Enabled - && Settings.Instance.Flip.Pov is FlipOptions.Both or FlipOptions.Self - && !Settings.Instance.Flip.Players.Contains(c.Id): { - server.BroadcastReplace(playerPacket, c, (from, to, sp) => { - if (Settings.Instance.Flip.Players.Contains(to.Id)) { - sp.Position += Vector3.UnitY * MarioSize((bool) c.Metadata["2d"]!); - sp.Rotation *= Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationX(MathF.PI)) - * Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(MathF.PI)); - } + return false; + } + // flip only for specific clients + if ( Settings.Instance.Flip.Enabled + && Settings.Instance.Flip.Pov is FlipOptions.Both or FlipOptions.Self + && !Settings.Instance.Flip.Players.Contains(c.Id) + ) { + server.BroadcastReplace(playerPacket, c, (from, to, sp) => { + if (Settings.Instance.Flip.Players.Contains(to.Id)) { + flipPlayer(c, ref sp); + } #pragma warning disable CS4014 - to.Send(sp, from) - .ContinueWith(x => { if (x.Exception != null) { consoleLogger.Error(x.Exception.ToString()); } }); + to.Send(sp, from).ContinueWith(logError); #pragma warning restore CS4014 - }); - return false; + }); + return false; + } + break; } } @@ -651,7 +677,7 @@ Task.Run(() => { } } } -}).ContinueWith(x => { if (x.Exception != null) { consoleLogger.Error(x.Exception.ToString()); } }); +}).ContinueWith(logError); #pragma warning restore CS4014 await server.Listen(cts.Token); diff --git a/Server/Server.cs b/Server/Server.cs index cfb7091..2e8eff2 100644 --- a/Server/Server.cs +++ b/Server/Server.cs @@ -64,7 +64,7 @@ public class Server { public static void FillPacket(PacketHeader header, T packet, Memory memory) where T : struct, IPacket { Span data = memory.Span; - + header.Serialize(data[..Constants.HeaderSize]); packet.Serialize(data[Constants.HeaderSize..]); } @@ -174,6 +174,8 @@ public class Server { ConnectPacket connect = new ConnectPacket(); connect.Deserialize(memory.Memory.Span[packetRange]); + bool wasFirst = connect.ConnectionType == ConnectPacket.ConnectionTypes.FirstConnection; + lock (Clients) { if (Clients.Count(x => x.Connected) == Settings.Instance.Server.MaxPlayers) { client.Logger.Error($"Turned away as server is at max clients"); @@ -218,27 +220,35 @@ public class Server { // done disconnecting and removing stale clients with the same id ClientJoined?.Invoke(client, connect); + // a new connection, not a reconnect, for an existing client + } else if (wasFirst) { + client.CleanMetadataOnNewConnection(); } } + // for all other clients that are already connected List otherConnectedPlayers = Clients.FindAll(c => c.Id != header.Id && c.Connected && c.Socket != null); await Parallel.ForEachAsync(otherConnectedPlayers, async (other, _) => { IMemoryOwner tempBuffer = MemoryPool.Shared.RentZero(Constants.HeaderSize + (other.CurrentCostume.HasValue ? Math.Max(connect.Size, other.CurrentCostume.Value.Size) : connect.Size)); + + // make the other client known to the (new) client PacketHeader connectHeader = new PacketHeader { - Id = other.Id, - Type = PacketType.Connect, - PacketSize = connect.Size + Id = other.Id, + Type = PacketType.Connect, + PacketSize = connect.Size, }; connectHeader.Serialize(tempBuffer.Memory.Span[..Constants.HeaderSize]); ConnectPacket connectPacket = new ConnectPacket { ConnectionType = ConnectPacket.ConnectionTypes.FirstConnection, // doesn't matter what it is - MaxPlayers = Settings.Instance.Server.MaxPlayers, - ClientName = other.Name + MaxPlayers = Settings.Instance.Server.MaxPlayers, + ClientName = other.Name, }; connectPacket.Serialize(tempBuffer.Memory.Span[Constants.HeaderSize..]); await client.Send(tempBuffer.Memory[..(Constants.HeaderSize + connect.Size)], null); + + // tell the (new) client what costume the other client has if (other.CurrentCostume.HasValue) { - connectHeader.Type = PacketType.Costume; + connectHeader.Type = PacketType.Costume; connectHeader.PacketSize = other.CurrentCostume.Value.Size; connectHeader.Serialize(tempBuffer.Memory.Span[..Constants.HeaderSize]); other.CurrentCostume.Value.Serialize(tempBuffer.Memory.Span[Constants.HeaderSize..(Constants.HeaderSize + connectHeader.PacketSize)]); @@ -246,9 +256,17 @@ public class Server { } tempBuffer.Dispose(); + + // make the other client reset their puppet cache for this client, if it is a new connection (after restart) + if (wasFirst) { + await SendEmptyPackets(client, other); + } }); Logger.Info($"Client {client.Name} ({client.Id}/{remote}) connected."); + + // send missing or outdated packets from others to the new client + await ResendPackets(client); } else if (header.Id != client.Id && client.Id != Guid.Empty) { throw new Exception($"Client {client.Name} sent packet with invalid client id {header.Id} instead of {client.Id}"); } @@ -310,6 +328,38 @@ public class Server { #pragma warning restore CS4014 } + private async Task ResendPackets(Client client) { + async Task trySend(Client other, string packetType) where T : struct, IPacket { + if (! other.Metadata.ContainsKey(packetType)) { return; } + try { + await client.Send((T) other.Metadata[packetType]!, other); + } + catch { + // lol who gives a fuck + } + }; + await Parallel.ForEachAsync(this.ClientsConnected, async (other, _) => { + if (client.Id == other.Id) { return; } + await trySend(other, "lastCostumePacket"); + await trySend(other, "lastCapturePacket"); + await trySend(other, "lastTagPacket"); + await trySend(other, "lastGamePacket"); + await trySend(other, "lastPlayerPacket"); + }); + } + + private async Task SendEmptyPackets(Client client, Client other) { + await other.Send(new TagPacket { + UpdateType = TagPacket.TagUpdate.State | TagPacket.TagUpdate.Time, + IsIt = false, + Seconds = 0, + Minutes = 0, + }, client); + await other.Send(new CapturePacket { + ModelName = "", + }, client); + } + private static PacketHeader GetHeader(Span data) { //no need to error check, the client will disconnect when the packet is invalid :) PacketHeader header = new PacketHeader();