diff --git a/Projects/Server/ExpansionInfo.cs b/Projects/Server/ExpansionInfo.cs index 9733c5eec..db9034647 100644 --- a/Projects/Server/ExpansionInfo.cs +++ b/Projects/Server/ExpansionInfo.cs @@ -304,7 +304,6 @@ namespace Server return ex switch { - Expansion.None => FeatureFlags.ExpansionNone, Expansion.T2A => FeatureFlags.ExpansionT2A, Expansion.UOR => FeatureFlags.ExpansionUOR, Expansion.UOTD => FeatureFlags.ExpansionUOTD, diff --git a/Projects/Server/Network/PacketUtilities.cs b/Projects/Server/Network/PacketUtilities.cs index 20defb3b6..83e268728 100644 --- a/Projects/Server/Network/PacketUtilities.cs +++ b/Projects/Server/Network/PacketUtilities.cs @@ -24,7 +24,7 @@ namespace Server.Network public static class PacketUtilities { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WritePacketLength(this CircularBufferWriter writer) + public static void WritePacketLength(this ref CircularBufferWriter writer) { var length = writer.Position; writer.Seek(1, SeekOrigin.Begin); @@ -33,7 +33,7 @@ namespace Server.Network } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WritePacketLength(this SpanWriter writer) + public static void WritePacketLength(this ref SpanWriter writer) { writer.Seek(1, SeekOrigin.Begin); writer.Write((ushort)writer.BytesWritten); diff --git a/Projects/UOContent.Tests/Tests/Items/Games/Mahjong/MahjongPacketTests.cs b/Projects/UOContent.Tests/Tests/Items/Games/Mahjong/MahjongPacketTests.cs new file mode 100644 index 000000000..b7b37279f --- /dev/null +++ b/Projects/UOContent.Tests/Tests/Items/Games/Mahjong/MahjongPacketTests.cs @@ -0,0 +1,118 @@ +using System; +using Server; +using Server.Engines.Mahjong; +using Server.Tests; +using Server.Tests.Network; +using Xunit; + +namespace UOContent.Tests +{ + public class MahjongPacketTests : IClassFixture + { + [Fact] + public void TestMahjongJoinGame() + { + Serial game = 0x1024u; + + var expected = new MahjongJoinGame(game).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendMahjongJoinGame(game); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void TestMahjongPlayersInfo(bool showScores) + { + var m = new Mobile(0x1); + m.DefaultMobileInit(); + + var game = new MahjongGame { ShowScores = showScores }; + game.Players.Join(m); + + var expected = new MahjongPlayersInfo(game, m).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendMahjongPlayersInfo(game, m); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void TestMahjongGeneralInfo(bool showScores, bool spectatorVision) + { + var game = new MahjongGame { ShowScores = showScores, SpectatorVision = spectatorVision}; + + var expected = new MahjongGeneralInfo(game).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendMahjongGeneralInfo(game); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void TestMahjongTilesInfo(bool spectatorVision) + { + var m = new Mobile(0x1); + m.DefaultMobileInit(); + + var game = new MahjongGame { SpectatorVision = spectatorVision }; + game.Players.Join(m); + + var expected = new MahjongTilesInfo(game, m).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendMahjongTilesInfo(game, m); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void TestMahjongTileInfo(bool spectatorVision) + { + var m = new Mobile(0x1); + m.DefaultMobileInit(); + + var game = new MahjongGame { SpectatorVision = spectatorVision }; + game.Players.Join(m); + + var expected = new MahjongTileInfo(game.Tiles[0], m).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendMahjongTileInfo(game.Tiles[0], m); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + + [Fact] + public void TestMahjongRelieve() + { + Serial game = 0x1024u; + + var expected = new MahjongRelieve(game).Compile(); + + using var ns = PacketTestUtilities.CreateTestNetState(); + ns.SendMahjongRelieve(game); + + var result = ns.SendPipe.Reader.TryRead(); + AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + } + } +} diff --git a/Projects/UOContent/Items/Games/Mahjong/Packets.cs b/Projects/UOContent.Tests/Tests/Items/Games/Mahjong/Packets.cs similarity index 96% rename from Projects/UOContent/Items/Games/Mahjong/Packets.cs rename to Projects/UOContent.Tests/Tests/Items/Games/Mahjong/Packets.cs index e71ecb304..c7c817279 100644 --- a/Projects/UOContent/Items/Games/Mahjong/Packets.cs +++ b/Projects/UOContent.Tests/Tests/Items/Games/Mahjong/Packets.cs @@ -5,11 +5,11 @@ namespace Server.Engines.Mahjong { public sealed class MahjongJoinGame : Packet { - public MahjongJoinGame(MahjongGame game) : base(0xDA) + public MahjongJoinGame(Serial game) : base(0xDA) { EnsureCapacity(9); - Stream.Write(game.Serial); + Stream.Write(game); Stream.Write((byte)0); Stream.Write((byte)0x19); } @@ -209,11 +209,11 @@ namespace Server.Engines.Mahjong public sealed class MahjongRelieve : Packet { - public MahjongRelieve(MahjongGame game) : base(0xDA) + public MahjongRelieve(Serial game) : base(0xDA) { EnsureCapacity(9); - Stream.Write(game.Serial); + Stream.Write(game); Stream.Write((byte)0); Stream.Write((byte)0x1A); } diff --git a/Projects/UOContent/Items/Games/Mahjong/MahjongGame.cs b/Projects/UOContent/Items/Games/Mahjong/MahjongGame.cs index 396b651a5..348d2d47e 100644 --- a/Projects/UOContent/Items/Games/Mahjong/MahjongGame.cs +++ b/Projects/UOContent/Items/Games/Mahjong/MahjongGame.cs @@ -83,7 +83,7 @@ namespace Server.Engines.Mahjong if (Players.IsInGamePlayer(Players.DealerPosition)) { - Players.Dealer.Send(new MahjongGeneralInfo(this)); + Players.Dealer.NetState.SendMahjongGeneralInfo(this); } Players.SendTilesPacket(false, true); diff --git a/Projects/UOContent/Items/Games/Mahjong/MahjongPackets.cs b/Projects/UOContent/Items/Games/Mahjong/MahjongPackets.cs new file mode 100644 index 000000000..98b18923f --- /dev/null +++ b/Projects/UOContent/Items/Games/Mahjong/MahjongPackets.cs @@ -0,0 +1,271 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: MahjongPackets.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Buffers; +using System.IO; +using Server.Network; + +namespace Server.Engines.Mahjong +{ + public static class MahjongPackets + { + public const int MahjongGeneralInfoPacketLength = 25; + public const int MahjongRelievePacketLength = 9; + + public static void SendMahjongJoinGame(this NetState ns, Serial game) + { + if (ns == null) + { + return; + } + + var writer = new SpanWriter(stackalloc byte[9]); + writer.Write((byte)0xDA); // Packet ID + writer.Write((ushort)9); + writer.Write(game); + writer.Write((ushort)0x19); // Command + + ns.Send(writer.Span); + } + + public static void SendMahjongPlayersInfo(this NetState ns, MahjongGame game, Mobile to) + { + if (ns == null) + { + return; + } + + var maxLength = 11 + game.Players.Seats * 45; + var writer = new SpanWriter(stackalloc byte[maxLength]); + writer.Write((byte)0xDA); // Packet ID + writer.Seek(2, SeekOrigin.Current); + writer.Write(game.Serial); + writer.Write((ushort)0x02); // Command + + writer.Seek(2, SeekOrigin.Current); // Seats + + var players = game.Players; + var count = 0; + + for (var i = 0; i < players.Seats; i++) + { + var m = players.GetPlayer(i); + + if (m == null && !game.ShowScores) + { + continue; + } + + writer.Write(m?.Serial ?? Serial.Zero); + writer.Write(m != null && players.DealerPosition == i ? (byte)0x1 : (byte)0x2); + writer.Write((byte)i); + + if (game.ShowScores || m == to) + { + writer.Write(players.GetScore(i)); + } + else + { + writer.Write(0); + } + + writer.Write((short)0); + writer.Write((byte)0); + + writer.Write(players.IsPublic(i)); + writer.WriteAscii(m?.Name ?? "", 30); + + writer.Write(m == null || !players.IsInGamePlayer(i)); + count++; + } + + writer.Seek(9, SeekOrigin.Begin); + writer.Write((ushort)count); + writer.WritePacketLength(); + + ns.Send(writer.Span); + } + + public static void SendMahjongTileInfo(this NetState ns, MahjongTile tile, Mobile to) + { + if (ns == null) + { + return; + } + + var game = tile.Game; + var players = game.Players; + + var writer = new SpanWriter(stackalloc byte[18]); + writer.Write((byte)0xDA); // Packet ID + writer.Write((ushort)18); + writer.Write(game.Serial); + writer.Write((ushort)0x03); // Command + + writer.Write((byte)tile.Number); + + if (tile.Flipped) + { + var hand = tile.Dimensions.GetHandArea(); + + if (hand < 0 || players.IsPublic(hand) || players.GetPlayer(hand) == to || + game.SpectatorVision && players.IsSpectator(to)) + { + writer.Write((byte)tile.Value); + } + else + { + writer.Write((byte)0); + } + } + else + { + writer.Write((byte)0); + } + + writer.Write((short)tile.Position.Y); + writer.Write((short)tile.Position.X); + writer.Write((byte)tile.StackLevel); + writer.Write((byte)tile.Direction); + writer.Write((byte)(tile.Flipped ? 0x10 : 0x0)); + + ns.Send(writer.Span); + } + + public static void SendMahjongTilesInfo(this NetState ns, MahjongGame game, Mobile to) + { + if (ns == null) + { + return; + } + + var tiles = game.Tiles; + var players = game.Players; + + var length = 11 + tiles.Length * 9; + var writer = new SpanWriter(stackalloc byte[length]); + writer.Write((byte)0xDA); // Packet ID + writer.Write((ushort)length); + writer.Write(game.Serial); + writer.Write((ushort)0x04); // Command + + writer.Write((short)tiles.Length); + + foreach (var tile in tiles) + { + writer.Write((byte)tile.Number); + + if (tile.Flipped) + { + var hand = tile.Dimensions.GetHandArea(); + + if (hand < 0 || players.IsPublic(hand) || players.GetPlayer(hand) == to || + game.SpectatorVision && players.IsSpectator(to)) + { + writer.Write((byte)tile.Value); + } + else + { + writer.Write((byte)0); + } + } + else + { + writer.Write((byte)0); + } + + writer.Write((short)tile.Position.Y); + writer.Write((short)tile.Position.X); + writer.Write((byte)tile.StackLevel); + writer.Write((byte)tile.Direction); + + writer.Write((byte)(tile.Flipped ? 0x10 : 0x0)); + } + + ns.Send(writer.Span); + } + + public static void SendMahjongGeneralInfo(this NetState ns, MahjongGame game) + { + if (ns == null) + { + return; + } + + Span buffer = stackalloc byte[MahjongGeneralInfoPacketLength].InitializePacket(); + CreateMahjongGeneralInfo(buffer, game); + + ns.Send(buffer); + } + + public static void CreateMahjongGeneralInfo(Span buffer, MahjongGame game) + { + if (buffer[0] != 0) + { + return; + } + + var writer = new SpanWriter(buffer); + writer.Write((byte)0xDA); // Packet ID + writer.Write((ushort)MahjongGeneralInfoPacketLength); + writer.Write(game.Serial); + writer.Write((ushort)0x05); // Command + + writer.Write((short)0); + writer.Write((byte)0); + + writer.Write((byte)((game.ShowScores ? 0x1 : 0x0) | (game.SpectatorVision ? 0x2 : 0x0))); + + writer.Write((byte)game.Dices.First); + writer.Write((byte)game.Dices.Second); + + writer.Write((byte)game.DealerIndicator.Wind); + writer.Write((short)game.DealerIndicator.Position.Y); + writer.Write((short)game.DealerIndicator.Position.X); + writer.Write((byte)game.DealerIndicator.Direction); + + writer.Write((short)game.WallBreakIndicator.Position.Y); + writer.Write((short)game.WallBreakIndicator.Position.X); + } + + public static void SendMahjongRelieve(this NetState ns, Serial game) + { + if (ns == null) + { + return; + } + + Span buffer = stackalloc byte[MahjongRelievePacketLength].InitializePacket(); + CreateMahjongRelieve(buffer, game); + + ns.Send(buffer); + } + + public static void CreateMahjongRelieve(Span buffer, Serial game) + { + if (buffer[0] != 0) + { + return; + } + + var writer = new SpanWriter(buffer); + writer.Write((byte)0xDA); // Packet ID + writer.Write((ushort)MahjongRelievePacketLength); + writer.Write(game); + writer.Write((ushort)0x1A); // Command + } + } +} diff --git a/Projects/UOContent/Items/Games/Mahjong/MahjongPlayers.cs b/Projects/UOContent/Items/Games/Mahjong/MahjongPlayers.cs index 2f1e719a8..898b64009 100644 --- a/Projects/UOContent/Items/Games/Mahjong/MahjongPlayers.cs +++ b/Projects/UOContent/Items/Games/Mahjong/MahjongPlayers.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using Server.Network; namespace Server.Engines.Mahjong { @@ -171,6 +173,8 @@ namespace Server.Engines.Mahjong { var removed = false; + Span relievePacket = stackalloc byte[MahjongPackets.MahjongRelievePacketLength].InitializePacket(); + for (var i = 0; i < m_Players.Length; i++) { var player = m_Players[i]; @@ -205,7 +209,8 @@ namespace Server.Engines.Mahjong { m_InGame[i] = false; - player.Send(new MahjongRelieve(Game)); + MahjongPackets.CreateMahjongRelieve(relievePacket, Game.Serial); + player.NetState?.Send(relievePacket); SendPlayerExitMessage(player); UpdateDealer(true); @@ -228,7 +233,8 @@ namespace Server.Engines.Mahjong { m_Spectators.RemoveAt(i); - mobile.Send(new MahjongRelieve(Game)); + MahjongPackets.CreateMahjongRelieve(relievePacket, Game.Serial); + mobile.NetState?.Send(relievePacket); } else { @@ -335,13 +341,13 @@ namespace Server.Engines.Mahjong if (sendJoinGame) { - player.Send(new MahjongJoinGame(Game)); + player.NetState.SendMahjongJoinGame(Game.Serial); } SendPlayersPacket(true, true); - player.Send(new MahjongGeneralInfo(Game)); - player.Send(new MahjongTilesInfo(Game, player)); + player.NetState.SendMahjongGeneralInfo(Game); + player.NetState.SendMahjongTilesInfo(Game, player); if (DealerPosition == index) { @@ -360,10 +366,10 @@ namespace Server.Engines.Mahjong m_Spectators.Add(mobile); } - mobile.Send(new MahjongJoinGame(Game)); - mobile.Send(new MahjongPlayersInfo(Game, mobile)); - mobile.Send(new MahjongGeneralInfo(Game)); - mobile.Send(new MahjongTilesInfo(Game, mobile)); + mobile.NetState.SendMahjongJoinGame(Game.Serial); + mobile.NetState.SendMahjongPlayersInfo(Game, mobile); + mobile.NetState.SendMahjongGeneralInfo(Game); + mobile.NetState.SendMahjongTilesInfo(Game, mobile); } public void Join(Mobile mobile) @@ -373,19 +379,18 @@ namespace Server.Engines.Mahjong if (index >= 0) { AddPlayer(mobile, index, true); + return; + } + + var nextSeat = GetNextSeat(); + + if (nextSeat >= 0) + { + AddPlayer(mobile, nextSeat, true); } else { - var nextSeat = GetNextSeat(); - - if (nextSeat >= 0) - { - AddPlayer(mobile, nextSeat, true); - } - else - { - AddSpectator(mobile); - } + AddSpectator(mobile); } } @@ -438,14 +443,12 @@ namespace Server.Engines.Mahjong } else { - from.Send(new MahjongPlayersInfo(Game, from)); - to.Send(new MahjongPlayersInfo(Game, to)); + from.NetState.SendMahjongPlayersInfo(Game, from); + to.NetState.SendMahjongPlayersInfo(Game, to); } - SendLocalizedMessage( - 1062774, - $"{from.Name}\t{to.Name}\t{amount}" - ); // ~1_giver~ gives ~2_receiver~ ~3_number~ points. + // ~1_giver~ gives ~2_receiver~ ~3_number~ points. + SendLocalizedMessage(1062774, $"{from.Name}\t{to.Name}\t{amount}"); } public void OpenSeat(int index) @@ -458,7 +461,7 @@ namespace Server.Engines.Mahjong if (m_InGame[index]) { - player.Send(new MahjongRelieve(Game)); + player.NetState.SendMahjongRelieve(Game.Serial); } m_Players[index] = null; @@ -488,10 +491,10 @@ namespace Server.Engines.Mahjong if (IsInGamePlayer(oldDealer)) { - m_Players[oldDealer].Send(new MahjongPlayersInfo(Game, m_Players[oldDealer])); + m_Players[oldDealer].NetState.SendMahjongPlayersInfo(Game, m_Players[oldDealer]); } - to.Send(new MahjongPlayersInfo(Game, to)); + to.NetState.SendMahjongPlayersInfo(Game, to); SendDealerChangedMessage(); } @@ -513,7 +516,7 @@ namespace Server.Engines.Mahjong { foreach (var mobile in GetInGameMobiles(players, spectators)) { - mobile.Send(new MahjongPlayersInfo(Game, mobile)); + mobile.NetState.SendMahjongPlayersInfo(Game, mobile); } } @@ -526,23 +529,20 @@ namespace Server.Engines.Mahjong return; } - var generalInfo = new MahjongGeneralInfo(Game); - - generalInfo.Acquire(); + Span generalInfo = stackalloc byte[MahjongPackets.MahjongGeneralInfoPacketLength].InitializePacket(); foreach (var mobile in mobiles) { - mobile.Send(generalInfo); + MahjongPackets.CreateMahjongGeneralInfo(generalInfo, Game); + mobile.NetState?.Send(generalInfo); } - - generalInfo.Release(); } public void SendTilesPacket(bool players, bool spectators) { foreach (var mobile in GetInGameMobiles(players, spectators)) { - mobile.Send(new MahjongTilesInfo(Game, mobile)); + mobile.NetState.SendMahjongTilesInfo(Game, mobile); } } @@ -550,7 +550,7 @@ namespace Server.Engines.Mahjong { foreach (var mobile in GetInGameMobiles(players, spectators)) { - mobile.Send(new MahjongTileInfo(tile, mobile)); + mobile.NetState.SendMahjongTileInfo(tile, mobile); } } @@ -563,16 +563,13 @@ namespace Server.Engines.Mahjong return; } - var relieve = new MahjongRelieve(Game); - - relieve.Acquire(); + Span relievePacket = stackalloc byte[MahjongPackets.MahjongRelievePacketLength].InitializePacket(); foreach (var mobile in mobiles) { - mobile.Send(relieve); + MahjongPackets.CreateMahjongRelieve(relievePacket, Game.Serial); + mobile.NetState?.Send(relievePacket); } - - relieve.Release(); } public void SendLocalizedMessage(int number)