mirror of
https://github.com/Sanae6/SmoOnlineServer.git
synced 2024-11-21 18:55:17 +00:00
fix: synchronization issues
- Send empty `TagPacket` and `CapturePacket` on new connections, to reset old data back that other players might still have in their puppet from an earlier connection. - Cache and send `CostumePacket`, `CapturePacket`, `TagPacket`, `GamePacket` and `PlayerPacket` to (re-)connecting players. - Clear Metadata cache for existing clients that connect fresh (after a game restart).
This commit is contained in:
parent
1e9d334d6f
commit
86c79177fd
3 changed files with 127 additions and 40 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -64,7 +64,7 @@ public class Server {
|
|||
|
||||
public static void FillPacket<T>(PacketHeader header, T packet, Memory<byte> memory) where T : struct, IPacket {
|
||||
Span<byte> 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<Client> otherConnectedPlayers = Clients.FindAll(c => c.Id != header.Id && c.Connected && c.Socket != null);
|
||||
await Parallel.ForEachAsync(otherConnectedPlayers, async (other, _) => {
|
||||
IMemoryOwner<byte> tempBuffer = MemoryPool<byte>.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<T>(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<CostumePacket>(other, "lastCostumePacket");
|
||||
await trySend<CapturePacket>(other, "lastCapturePacket");
|
||||
await trySend<TagPacket>(other, "lastTagPacket");
|
||||
await trySend<GamePacket>(other, "lastGamePacket");
|
||||
await trySend<PlayerPacket>(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<byte> data) {
|
||||
//no need to error check, the client will disconnect when the packet is invalid :)
|
||||
PacketHeader header = new PacketHeader();
|
||||
|
|
Loading…
Reference in a new issue