mirror of
https://github.com/Sanae6/SmoOnlineServer.git
synced 2024-11-22 03:05:16 +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);
|
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) {
|
public static bool operator ==(Client? left, Client? right) {
|
||||||
return left is { } leftClient && right is { } rightClient && leftClient.Id == rightClient.Id;
|
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["scenario"] = (byte?) 0;
|
||||||
c.Metadata["2d"] = false;
|
c.Metadata["2d"] = false;
|
||||||
c.Metadata["speedrun"] = 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) {
|
async Task ClientSyncShineBag(Client client) {
|
||||||
|
@ -115,13 +108,36 @@ timer.Start();
|
||||||
|
|
||||||
float MarioSize(bool is2d) => is2d ? 180 : 160;
|
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) => {
|
server.PacketHandler = (c, p) => {
|
||||||
switch (p) {
|
switch (p) {
|
||||||
case GamePacket gamePacket: {
|
case GamePacket gamePacket: {
|
||||||
c.Logger.Info($"Got game packet {gamePacket.Stage}->{gamePacket.ScenarioNum}");
|
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["scenario"] = gamePacket.ScenarioNum;
|
||||||
c.Metadata["2d"] = gamePacket.Is2d;
|
c.Metadata["2d"] = gamePacket.Is2d;
|
||||||
c.Metadata["lastGamePacket"] = gamePacket;
|
c.Metadata["lastGamePacket"] = gamePacket;
|
||||||
|
|
||||||
switch (gamePacket.Stage) {
|
switch (gamePacket.Stage) {
|
||||||
case "CapWorldHomeStage" when gamePacket.ScenarioNum == 0:
|
case "CapWorldHomeStage" when gamePacket.ScenarioNum == 0:
|
||||||
c.Metadata["speedrun"] = true;
|
c.Metadata["speedrun"] = true;
|
||||||
|
@ -145,8 +161,7 @@ server.PacketHandler = (c, p) => {
|
||||||
server.BroadcastReplace(gamePacket, c, (from, to, gp) => {
|
server.BroadcastReplace(gamePacket, c, (from, to, gp) => {
|
||||||
gp.ScenarioNum = (byte?) to.Metadata["scenario"] ?? 200;
|
gp.ScenarioNum = (byte?) to.Metadata["scenario"] ?? 200;
|
||||||
#pragma warning disable CS4014
|
#pragma warning disable CS4014
|
||||||
to.Send(gp, from)
|
to.Send(gp, from).ContinueWith(logError);
|
||||||
.ContinueWith(x => { if (x.Exception != null) { consoleLogger.Error(x.Exception.ToString()); } });
|
|
||||||
#pragma warning restore CS4014
|
#pragma warning restore CS4014
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
|
@ -156,14 +171,23 @@ server.PacketHandler = (c, p) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
case TagPacket tagPacket: {
|
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.State) != 0) c.Metadata["seeking"] = tagPacket.IsIt;
|
||||||
if ((tagPacket.UpdateType & TagPacket.TagUpdate.Time) != 0)
|
if ((tagPacket.UpdateType & TagPacket.TagUpdate.Time) != 0)
|
||||||
c.Metadata["time"] = new Time(tagPacket.Minutes, tagPacket.Seconds, DateTime.Now);
|
c.Metadata["time"] = new Time(tagPacket.Minutes, tagPacket.Seconds, DateTime.Now);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case CapturePacket capturePacket: {
|
||||||
|
// c.Logger.Info($"Got capture packet: {capturePacket.ModelName}");
|
||||||
|
c.Metadata["lastCapturePacket"] = capturePacket;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case CostumePacket costumePacket:
|
case CostumePacket costumePacket:
|
||||||
c.Logger.Info($"Got costume packet: {costumePacket.BodyName}, {costumePacket.CapName}");
|
c.Logger.Info($"Got costume packet: {costumePacket.BodyName}, {costumePacket.CapName}");
|
||||||
|
c.Metadata["lastCostumePacket"] = costumePacket;
|
||||||
c.CurrentCostume = costumePacket;
|
c.CurrentCostume = costumePacket;
|
||||||
#pragma warning disable CS4014
|
#pragma warning disable CS4014
|
||||||
ClientSyncShineBag(c); //no point logging since entire def has try/catch
|
ClientSyncShineBag(c); //no point logging since entire def has try/catch
|
||||||
|
@ -183,34 +207,36 @@ server.PacketHandler = (c, p) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case PlayerPacket playerPacket when Settings.Instance.Flip.Enabled
|
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.Pov is FlipOptions.Both or FlipOptions.Others
|
||||||
&& Settings.Instance.Flip.Players.Contains(c.Id): {
|
&& Settings.Instance.Flip.Players.Contains(c.Id)
|
||||||
playerPacket.Position += Vector3.UnitY * MarioSize((bool) c.Metadata["2d"]!);
|
) {
|
||||||
playerPacket.Rotation *= Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationX(MathF.PI))
|
flipPlayer(c, ref playerPacket);
|
||||||
* Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(MathF.PI));
|
|
||||||
#pragma warning disable CS4014
|
#pragma warning disable CS4014
|
||||||
server.Broadcast(playerPacket, c)
|
server.Broadcast(playerPacket, c).ContinueWith(logError);
|
||||||
.ContinueWith(x => { if (x.Exception != null) { consoleLogger.Error(x.Exception.ToString()); } });
|
|
||||||
#pragma warning restore CS4014
|
#pragma warning restore CS4014
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case PlayerPacket playerPacket when Settings.Instance.Flip.Enabled
|
// flip only for specific clients
|
||||||
|
if ( Settings.Instance.Flip.Enabled
|
||||||
&& Settings.Instance.Flip.Pov is FlipOptions.Both or FlipOptions.Self
|
&& Settings.Instance.Flip.Pov is FlipOptions.Both or FlipOptions.Self
|
||||||
&& !Settings.Instance.Flip.Players.Contains(c.Id): {
|
&& !Settings.Instance.Flip.Players.Contains(c.Id)
|
||||||
|
) {
|
||||||
server.BroadcastReplace(playerPacket, c, (from, to, sp) => {
|
server.BroadcastReplace(playerPacket, c, (from, to, sp) => {
|
||||||
if (Settings.Instance.Flip.Players.Contains(to.Id)) {
|
if (Settings.Instance.Flip.Players.Contains(to.Id)) {
|
||||||
sp.Position += Vector3.UnitY * MarioSize((bool) c.Metadata["2d"]!);
|
flipPlayer(c, ref sp);
|
||||||
sp.Rotation *= Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationX(MathF.PI))
|
|
||||||
* Quaternion.CreateFromRotationMatrix(Matrix4x4.CreateRotationY(MathF.PI));
|
|
||||||
}
|
}
|
||||||
#pragma warning disable CS4014
|
#pragma warning disable CS4014
|
||||||
to.Send(sp, from)
|
to.Send(sp, from).ContinueWith(logError);
|
||||||
.ContinueWith(x => { if (x.Exception != null) { consoleLogger.Error(x.Exception.ToString()); } });
|
|
||||||
#pragma warning restore CS4014
|
#pragma warning restore CS4014
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true; // Broadcast packet to all other clients
|
return true; // Broadcast packet to all other clients
|
||||||
|
@ -651,7 +677,7 @@ Task.Run(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).ContinueWith(x => { if (x.Exception != null) { consoleLogger.Error(x.Exception.ToString()); } });
|
}).ContinueWith(logError);
|
||||||
#pragma warning restore CS4014
|
#pragma warning restore CS4014
|
||||||
|
|
||||||
await server.Listen(cts.Token);
|
await server.Listen(cts.Token);
|
||||||
|
|
|
@ -174,6 +174,8 @@ public class Server {
|
||||||
|
|
||||||
ConnectPacket connect = new ConnectPacket();
|
ConnectPacket connect = new ConnectPacket();
|
||||||
connect.Deserialize(memory.Memory.Span[packetRange]);
|
connect.Deserialize(memory.Memory.Span[packetRange]);
|
||||||
|
bool wasFirst = connect.ConnectionType == ConnectPacket.ConnectionTypes.FirstConnection;
|
||||||
|
|
||||||
lock (Clients) {
|
lock (Clients) {
|
||||||
if (Clients.Count(x => x.Connected) == Settings.Instance.Server.MaxPlayers) {
|
if (Clients.Count(x => x.Connected) == Settings.Instance.Server.MaxPlayers) {
|
||||||
client.Logger.Error($"Turned away as server is at max clients");
|
client.Logger.Error($"Turned away as server is at max clients");
|
||||||
|
@ -218,25 +220,33 @@ public class Server {
|
||||||
// done disconnecting and removing stale clients with the same id
|
// done disconnecting and removing stale clients with the same id
|
||||||
|
|
||||||
ClientJoined?.Invoke(client, connect);
|
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);
|
List<Client> otherConnectedPlayers = Clients.FindAll(c => c.Id != header.Id && c.Connected && c.Socket != null);
|
||||||
await Parallel.ForEachAsync(otherConnectedPlayers, async (other, _) => {
|
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));
|
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 {
|
PacketHeader connectHeader = new PacketHeader {
|
||||||
Id = other.Id,
|
Id = other.Id,
|
||||||
Type = PacketType.Connect,
|
Type = PacketType.Connect,
|
||||||
PacketSize = connect.Size
|
PacketSize = connect.Size,
|
||||||
};
|
};
|
||||||
connectHeader.Serialize(tempBuffer.Memory.Span[..Constants.HeaderSize]);
|
connectHeader.Serialize(tempBuffer.Memory.Span[..Constants.HeaderSize]);
|
||||||
ConnectPacket connectPacket = new ConnectPacket {
|
ConnectPacket connectPacket = new ConnectPacket {
|
||||||
ConnectionType = ConnectPacket.ConnectionTypes.FirstConnection, // doesn't matter what it is
|
ConnectionType = ConnectPacket.ConnectionTypes.FirstConnection, // doesn't matter what it is
|
||||||
MaxPlayers = Settings.Instance.Server.MaxPlayers,
|
MaxPlayers = Settings.Instance.Server.MaxPlayers,
|
||||||
ClientName = other.Name
|
ClientName = other.Name,
|
||||||
};
|
};
|
||||||
connectPacket.Serialize(tempBuffer.Memory.Span[Constants.HeaderSize..]);
|
connectPacket.Serialize(tempBuffer.Memory.Span[Constants.HeaderSize..]);
|
||||||
await client.Send(tempBuffer.Memory[..(Constants.HeaderSize + connect.Size)], null);
|
await client.Send(tempBuffer.Memory[..(Constants.HeaderSize + connect.Size)], null);
|
||||||
|
|
||||||
|
// tell the (new) client what costume the other client has
|
||||||
if (other.CurrentCostume.HasValue) {
|
if (other.CurrentCostume.HasValue) {
|
||||||
connectHeader.Type = PacketType.Costume;
|
connectHeader.Type = PacketType.Costume;
|
||||||
connectHeader.PacketSize = other.CurrentCostume.Value.Size;
|
connectHeader.PacketSize = other.CurrentCostume.Value.Size;
|
||||||
|
@ -246,9 +256,17 @@ public class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
tempBuffer.Dispose();
|
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.");
|
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) {
|
} 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}");
|
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
|
#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) {
|
private static PacketHeader GetHeader(Span<byte> data) {
|
||||||
//no need to error check, the client will disconnect when the packet is invalid :)
|
//no need to error check, the client will disconnect when the packet is invalid :)
|
||||||
PacketHeader header = new PacketHeader();
|
PacketHeader header = new PacketHeader();
|
||||||
|
|
Loading…
Reference in a new issue