fix: Fixes flaky tests, formatting, and sequential testing. (#2168)

This commit is contained in:
Kamron Batman 2025-04-30 16:42:38 -07:00 committed by GitHub
parent 415c7a6bfd
commit 7dbfc9d161
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 1304 additions and 1289 deletions

View file

@ -1,12 +1,13 @@
using System; using System;
using System.Reflection; using System.Reflection;
using Xunit;
namespace Server.Tests; namespace Server.Tests;
internal class ServerFixture : IDisposable [CollectionDefinition("Sequential Server Tests", DisableParallelization = true)]
public class ServerFixture : ICollectionFixture<ServerFixture>, IDisposable
{ {
// Global setup public ServerFixture()
static ServerFixture()
{ {
Core.ApplicationAssembly = Assembly.GetExecutingAssembly(); // Server.Tests.dll Core.ApplicationAssembly = Assembly.GetExecutingAssembly(); // Server.Tests.dll

View file

@ -1,34 +1,33 @@
namespace Server namespace Server.Tests;
public static class TestMapDefinitions
{ {
public static class TestMapDefinitions public static void ConfigureTestMapDefinitions()
{ {
public static void ConfigureTestMapDefinitions() RegisterMap(0, 0, 0, 7168, 4096, 4, "Felucca", MapRules.FeluccaRules);
{ RegisterMap(1, 1, 1, 7168, 4096, 0, "Trammel", MapRules.TrammelRules);
RegisterMap(0, 0, 0, 7168, 4096, 4, "Felucca", MapRules.FeluccaRules); RegisterMap(2, 2, 2, 2304, 1600, 1, "Ilshenar", MapRules.TrammelRules);
RegisterMap(1, 1, 1, 7168, 4096, 0, "Trammel", MapRules.TrammelRules); RegisterMap(3, 3, 3, 2560, 2048, 1, "Malas", MapRules.TrammelRules);
RegisterMap(2, 2, 2, 2304, 1600, 1, "Ilshenar", MapRules.TrammelRules); RegisterMap(4, 4, 4, 1448, 1448, 1, "Tokuno", MapRules.TrammelRules);
RegisterMap(3, 3, 3, 2560, 2048, 1, "Malas", MapRules.TrammelRules); RegisterMap(5, 5, 5, 1280, 4096, 1, "TerMur", MapRules.TrammelRules);
RegisterMap(4, 4, 4, 1448, 1448, 1, "Tokuno", MapRules.TrammelRules);
RegisterMap(5, 5, 5, 1280, 4096, 1, "TerMur", MapRules.TrammelRules);
RegisterMap(0x7F, 0x7F, 0x7F, Map.SectorSize, Map.SectorSize, 1, "Internal", MapRules.Internal); RegisterMap(0x7F, 0x7F, 0x7F, Map.SectorSize, Map.SectorSize, 1, "Internal", MapRules.Internal);
} }
private static void RegisterMap( private static void RegisterMap(
int mapIndex, int mapIndex,
int mapID, int mapID,
int fileIndex, int fileIndex,
int width, int width,
int height, int height,
int season, int season,
string name, string name,
MapRules rules MapRules rules
) )
{ {
var newMap = new Map(mapID, mapIndex, fileIndex, width, height, season, name, rules); var newMap = new Map(mapID, mapIndex, fileIndex, width, height, season, name, rules);
Map.Maps[mapIndex] = newMap; Map.Maps[mapIndex] = newMap;
Map.AllMaps.Add(newMap); Map.AllMaps.Add(newMap);
}
} }
} }

View file

@ -1,9 +0,0 @@
using Xunit;
namespace Server.Tests
{
[CollectionDefinition("Sequential Tests", DisableParallelization = true)]
public class SequentialTestCollectionDefinition
{
}
}

View file

@ -4,7 +4,7 @@ using Xunit;
namespace Server.Tests.Tests.Buffers; namespace Server.Tests.Tests.Buffers;
[Collection("Sequential Tests")] [Collection("Sequential Server Tests")]
public class STArrayPoolTests public class STArrayPoolTests
{ {
[Theory] [Theory]

View file

@ -3,7 +3,7 @@ using Xunit;
namespace Server.Tests.Buffers; namespace Server.Tests.Buffers;
[Collection("Sequential Tests")] [Collection("Sequential Server Tests")]
public class ValueStringBuilderTests public class ValueStringBuilderTests
{ {
[Theory] [Theory]

View file

@ -2,7 +2,7 @@ using Xunit;
namespace Server.Tests.Network; namespace Server.Tests.Network;
[Collection("Sequential Tests")] [Collection("Sequential Server Tests")]
public class ClientVersionTests public class ClientVersionTests
{ {
[Theory] [Theory]

View file

@ -3,7 +3,8 @@ using Xunit;
namespace Server.Tests; namespace Server.Tests;
public sealed class WorldLocationTests : IClassFixture<ServerFixture> [Collection("Sequential Server Tests")]
public sealed class WorldLocationTests
{ {
private static Map CreateMap(string name) => new(0, 0, 0, 1, 1, 0, name, MapRules.Internal); private static Map CreateMap(string name) => new(0, 0, 0, 1, 1, 0, name, MapRules.Internal);

View file

@ -5,7 +5,8 @@ using Xunit;
namespace Server.Tests; namespace Server.Tests;
public class ContainerTests : IClassFixture<ServerFixture> [Collection("Sequential Server Tests")]
public class ContainerTests
{ {
[Fact] [Fact]
public void TestFindItemsByType() public void TestFindItemsByType()

View file

@ -2,7 +2,7 @@ using Xunit;
namespace Server.Tests; namespace Server.Tests;
[Collection("Sequential Tests")] [Collection("Sequential Server Tests")]
public class LocalizationEntryTests public class LocalizationEntryTests
{ {
[Fact] [Fact]

View file

@ -7,7 +7,8 @@ using Xunit;
namespace Server.Tests.Network; namespace Server.Tests.Network;
public class AccountPacketTests : IClassFixture<ServerFixture> [Collection("Sequential Server Tests")]
public class AccountPacketTests
{ {
private class MockedAccount : IAccount private class MockedAccount : IAccount
{ {

View file

@ -4,8 +4,8 @@ using Xunit;
namespace Server.Tests.Network namespace Server.Tests.Network
{ {
[Collection("Sequential Tests")] [Collection("Sequential Server Tests")]
public class ContainerPacketTests : IClassFixture<ServerFixture> public class ContainerPacketTests
{ {
[Fact] [Fact]

View file

@ -3,7 +3,8 @@ using Xunit;
namespace Server.Tests.Network namespace Server.Tests.Network
{ {
public class DamagePacketTests : IClassFixture<ServerFixture> [Collection("Sequential Server Tests")]
public class DamagePacketTests
{ {
[Theory, InlineData(10), InlineData(-5), InlineData(1024)] [Theory, InlineData(10), InlineData(-5), InlineData(1024)]
public void TestDamagePacketOld(int inputAmount) public void TestDamagePacketOld(int inputAmount)

View file

@ -4,7 +4,8 @@ using Xunit;
namespace Server.Tests.Network namespace Server.Tests.Network
{ {
public class EquipmentPacketTests : IClassFixture<ServerFixture> [Collection("Sequential Server Tests")]
public class EquipmentPacketTests
{ {
[Theory] [Theory]
[InlineData(null, false)] [InlineData(null, false)]

View file

@ -3,8 +3,8 @@ using Xunit;
namespace Server.Tests.Network; namespace Server.Tests.Network;
[Collection("Sequential Tests")] [Collection("Sequential Server Tests")]
public class GumpPacketTests : IClassFixture<ServerFixture> public class GumpPacketTests
{ {
[Theory] [Theory]
[InlineData(100, 10)] [InlineData(100, 10)]

View file

@ -3,7 +3,8 @@ using Xunit;
namespace Server.Tests.Network namespace Server.Tests.Network
{ {
public class ItemPacketTests : IClassFixture<ServerFixture> [Collection("Sequential Server Tests")]
public class ItemPacketTests
{ {
[Fact] [Fact]
public void TestWorldItemPacket() public void TestWorldItemPacket()

View file

@ -3,7 +3,8 @@ using Xunit;
namespace Server.Tests.Network namespace Server.Tests.Network
{ {
public class MapPatchesTests : IClassFixture<ServerFixture> [Collection("Sequential Server Tests")]
public class MapPatchesTests
{ {
[Fact] [Fact]
public void TestMapPatches() public void TestMapPatches()

View file

@ -27,8 +27,8 @@ namespace Server.Tests.Network
} }
} }
[Collection("Sequential Tests")] [Collection("Sequential Server Tests")]
public class MenuPacketTests : IClassFixture<ServerFixture> public class MenuPacketTests
{ {
[Fact] [Fact]
public void TestDisplayItemListMenu() public void TestDisplayItemListMenu()

View file

@ -3,8 +3,8 @@ using Xunit;
namespace Server.Tests.Network; namespace Server.Tests.Network;
[Collection("Sequential Tests")] [Collection("Sequential Server Tests")]
public class MobilePacketTests : IClassFixture<ServerFixture> public class MobilePacketTests
{ {
[Fact] [Fact]
public void TestDeathAnimation() public void TestDeathAnimation()

View file

@ -3,7 +3,8 @@ using Xunit;
namespace Server.Tests.Network namespace Server.Tests.Network
{ {
public class MovementPacketTests : IClassFixture<ServerFixture> [Collection("Sequential Server Tests")]
public class MovementPacketTests
{ {
[Theory] [Theory]
[InlineData(0)] [InlineData(0)]

View file

@ -5,8 +5,8 @@ using Xunit;
namespace Server.Tests.Network namespace Server.Tests.Network
{ {
[Collection("Sequential Tests")] [Collection("Sequential Server Tests")]
public class PlayerPacketTests : IClassFixture<ServerFixture> public class PlayerPacketTests
{ {
[Theory] [Theory]
[InlineData(StatLockType.Down, StatLockType.Up, StatLockType.Locked)] [InlineData(StatLockType.Down, StatLockType.Up, StatLockType.Locked)]

View file

@ -4,7 +4,8 @@ using Xunit;
namespace Server.Tests.Network namespace Server.Tests.Network
{ {
public class SecureTradePacketTests : IClassFixture<ServerFixture> [Collection("Sequential Server Tests")]
public class SecureTradePacketTests
{ {
[Theory] [Theory]
[InlineData("short-name")] [InlineData("short-name")]

View file

@ -5,8 +5,8 @@ using Xunit;
namespace Server.Tests.Network namespace Server.Tests.Network
{ {
[Collection("Sequential Tests")] [Collection("Sequential Server Tests")]
public class VendorBuyPacketTests : IClassFixture<ServerFixture> public class VendorBuyPacketTests
{ {
[Theory] [Theory]
[InlineData(ProtocolChanges.None)] [InlineData(ProtocolChanges.None)]

View file

@ -5,8 +5,8 @@ using Xunit;
namespace Server.Tests.Network namespace Server.Tests.Network
{ {
[Collection("Sequential Tests")] [Collection("Sequential Server Tests")]
public class VendorSellPacketTests : IClassFixture<ServerFixture> public class VendorSellPacketTests
{ {
[Fact] [Fact]
public void TestVendorSellList() public void TestVendorSellList()

View file

@ -2,40 +2,40 @@ using Server.Network;
using Server.Tests.Network; using Server.Tests.Network;
using Xunit; using Xunit;
namespace Server.Tests namespace Server.Tests;
[Collection("Sequential Server Tests")]
public class VirtualHairPacketTests
{ {
public class VirtualHairPacketTests: IClassFixture<ServerFixture> [Fact]
public void TestSendVirtualHairUpdate()
{ {
[Fact] var m = new Mobile((Serial)0x1024u);
public void TestSendVirtualHairUpdate() m.DefaultMobileInit();
{ m.HairHue = 0x1000;
var m = new Mobile((Serial)0x1024u); m.HairItemID = 0x2000;
m.DefaultMobileInit();
m.HairHue = 0x1000;
m.HairItemID = 0x2000;
var expected = new HairEquipUpdate(m).Compile(); var expected = new HairEquipUpdate(m).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendHairEquipUpdatePacket(m, (uint)m.Hair.VirtualSerial, m.Hair.ItemId, m.Hair.Hue, Layer.Hair); ns.SendHairEquipUpdatePacket(m, (uint)m.Hair.VirtualSerial, m.Hair.ItemId, m.Hair.Hue, Layer.Hair);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Fact] [Fact]
public void TestSendRemoveVirtualHair() public void TestSendRemoveVirtualHair()
{ {
var m = new Mobile((Serial)0x1024u); var m = new Mobile((Serial)0x1024u);
m.DefaultMobileInit(); m.DefaultMobileInit();
var expected = new RemoveHair(m).Compile(); var expected = new RemoveHair(m).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendRemoveHairPacket((uint) m.Hair.VirtualSerial); ns.SendRemoveHairPacket((uint) m.Hair.VirtualSerial);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
}
} }
} }

View file

@ -8,7 +8,8 @@ using Xunit;
namespace Server.Tests; namespace Server.Tests;
public class TypeConverterTests : IClassFixture<ServerFixture> [Collection("Sequential Server Tests")]
public class TypeConverterTests
{ {
[Fact] [Fact]
public void TestReadAfterWrite() public void TestReadAfterWrite()

View file

@ -3,8 +3,8 @@ using Xunit;
namespace Server.Tests; namespace Server.Tests;
[Collection("Sequential Tests")] [Collection("Sequential Server Tests")]
public class TimerTests : IClassFixture<ServerFixture> public class TimerTests
{ {
[Theory] [Theory]
[InlineData(0L, 8L)] [InlineData(0L, 8L)]

View file

@ -4,7 +4,7 @@ using Xunit;
namespace Server.Tests; namespace Server.Tests;
[Collection("Sequential Tests")] [Collection("Sequential Server Tests")]
public class TestStringHelpers public class TestStringHelpers
{ {
[Theory] [Theory]

View file

@ -2,8 +2,8 @@ using Xunit;
namespace Server.Tests; namespace Server.Tests;
[Collection("Sequential Tests")] [Collection("Sequential Server Tests")]
public class VirtualSerialTests : IClassFixture<ServerFixture> public class VirtualSerialTests
{ {
[Fact] [Fact]
public void TestNewVirtualGetsAndRollover() public void TestNewVirtualGetsAndRollover()

View file

@ -1,13 +1,14 @@
using System; using System;
using System.Reflection; using System.Reflection;
using Server.Misc; using Server.Misc;
using Xunit;
namespace Server.Tests; namespace Server.Tests;
internal class ServerFixture : IDisposable [CollectionDefinition("Sequential UOContent Tests", DisableParallelization = true)]
public class UOContentFixture : ICollectionFixture<UOContentFixture>, IDisposable
{ {
// Global setup public UOContentFixture()
static ServerFixture()
{ {
Core.ApplicationAssembly = Assembly.GetExecutingAssembly(); Core.ApplicationAssembly = Assembly.GetExecutingAssembly();
Core.LoopContext = new EventLoopContext(); Core.LoopContext = new EventLoopContext();
@ -40,8 +41,16 @@ internal class ServerFixture : IDisposable
World.ExitSerializationThreads(); World.ExitSerializationThreads();
} }
private static int _counter;
public void Dispose() public void Dispose()
{ {
_counter++;
if (_counter > 1)
{
throw new Exception("NO!");
}
Timer.Init(0); Timer.Init(0);
} }
} }

View file

@ -12,12 +12,12 @@ public class ChatPacketTests
[InlineData("ENU", 200, "a third param", "another param")] [InlineData("ENU", 200, "a third param", "another param")]
public void TestSendChatMessage(string lang, int number, string param1, string param2) public void TestSendChatMessage(string lang, int number, string param1, string param2)
{ {
var expected = new ChatMessagePacket(lang, number, param1, param2).Compile(); var expected = new ChatMessagePacket(lang, number, param1, param2).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendChatMessage(lang, number, param1, param2); ns.SendChatMessage(lang, number, param1, param2);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
} }

View file

@ -6,23 +6,23 @@ public sealed class ChatMessagePacket : Packet
{ {
public ChatMessagePacket(string lang, int number, string param1, string param2) : base(0xB2) public ChatMessagePacket(string lang, int number, string param1, string param2) : base(0xB2)
{ {
param1 ??= string.Empty; param1 ??= string.Empty;
param2 ??= string.Empty; param2 ??= string.Empty;
EnsureCapacity(13 + (param1.Length + param2.Length) * 2); EnsureCapacity(13 + (param1.Length + param2.Length) * 2);
Stream.Write((ushort)(number - 20)); Stream.Write((ushort)(number - 20));
if (lang != null) if (lang != null)
{ {
Stream.WriteAsciiFixed(lang, 4); Stream.WriteAsciiFixed(lang, 4);
}
else
{
Stream.Write(0);
}
Stream.WriteBigUniNull(param1);
Stream.WriteBigUniNull(param2);
} }
} else
{
Stream.Write(0);
}
Stream.WriteBigUniNull(param1);
Stream.WriteBigUniNull(param2);
}
}

View file

@ -6,7 +6,7 @@ using Xunit;
namespace UOContent.Tests; namespace UOContent.Tests;
[Collection("Sequential Tests")] [Collection("Sequential UOContent Tests")]
public class EventSchedulerTests public class EventSchedulerTests
{ {
// Test implementations for controlled testing // Test implementations for controlled testing
@ -144,36 +144,39 @@ public class EventSchedulerTests
[Fact] [Fact]
public void Scheduler_HandlesRecurringEvents() public void Scheduler_HandlesRecurringEvents()
{ {
Init(); for (var i = 0; i < 100_000; i++)
try
{ {
int callCount = 0; Init();
// Create a recurrence pattern that fires every 10 seconds, up to 3 times try
var recurrence = new TestRecurrencePattern(TimeSpan.FromSeconds(10), 3); {
int callCount = 0;
var evt = new TestScheduledEvent( // Create a recurrence pattern that fires every 10 seconds, up to 3 times
Core._now, var recurrence = new TestRecurrencePattern(TimeSpan.FromSeconds(10), 3);
() => callCount++,
recurrence
);
EventScheduler.Shared.ScheduleEvent(evt); var evt = new TestScheduledEvent(
Core._now,
() => callCount++,
recurrence
);
// Advance time to after all occurrences should have happened EventScheduler.Shared.ScheduleEvent(evt);
Core._now = Core._now.AddSeconds(50);
Timer.Slice(8);
// Should have fired 4 times (3 recurrences) // Advance time to after all occurrences should have happened
Assert.Equal(3, callCount); Core._now = Core._now.AddSeconds(50);
Assert.Equal(3, evt.CallCount); Timer.Slice(8);
evt.Cancel(); // Should have fired 4 times (3 recurrences)
} Assert.Equal(3, callCount);
finally Assert.Equal(3, evt.CallCount);
{
Finish(); evt.Cancel();
}
finally
{
Finish();
}
} }
} }

View file

@ -4,11 +4,11 @@ public class DisplayHelpTopic : Packet
{ {
public DisplayHelpTopic(int topicID, bool display) : base(0xBF) public DisplayHelpTopic(int topicID, bool display) : base(0xBF)
{ {
EnsureCapacity(11); EnsureCapacity(11);
Stream.Write((short)0x17); Stream.Write((short)0x17);
Stream.Write((byte)1); Stream.Write((byte)1);
Stream.Write(topicID); Stream.Write(topicID);
Stream.Write(display); Stream.Write(display);
} }
} }

View file

@ -13,12 +13,12 @@ public class TestHelpTopicPacket
[InlineData(HelpTopic.EmptyingBowl, true)] [InlineData(HelpTopic.EmptyingBowl, true)]
public void TestDisplayHelpTopic(int topic, bool display) public void TestDisplayHelpTopic(int topic, bool display)
{ {
var expected = new DisplayHelpTopic(topic, display).Compile(); var expected = new DisplayHelpTopic(topic, display).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendDisplayHelpTopic(topic, display); ns.SendDisplayHelpTopic(topic, display);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
} }

View file

@ -6,22 +6,22 @@ public sealed class RaceChanger : Packet
{ {
public RaceChanger(bool female, Race targetRace) : base(0xBF) public RaceChanger(bool female, Race targetRace) : base(0xBF)
{ {
EnsureCapacity(7); EnsureCapacity(7);
Stream.Write((short)0x2A); Stream.Write((short)0x2A);
Stream.Write((byte)(female ? 1 : 0)); Stream.Write((byte)(female ? 1 : 0));
Stream.Write((byte)(targetRace.RaceID + 1)); Stream.Write((byte)(targetRace.RaceID + 1));
} }
} }
public sealed class CloseRaceChanger : Packet public sealed class CloseRaceChanger : Packet
{ {
public CloseRaceChanger() : base(0xBF) public CloseRaceChanger() : base(0xBF)
{ {
EnsureCapacity(7); EnsureCapacity(7);
Stream.Write((short)0x2A); Stream.Write((short)0x2A);
Stream.Write((byte)0); Stream.Write((byte)0);
Stream.Write((byte)0xFF); Stream.Write((byte)0xFF);
} }
} }

View file

@ -6,74 +6,74 @@ public sealed class PartyEmptyList : Packet
{ {
public PartyEmptyList(Serial m) : base(0xBF) public PartyEmptyList(Serial m) : base(0xBF)
{ {
EnsureCapacity(7); EnsureCapacity(7);
Stream.Write((short)0x0006); Stream.Write((short)0x0006);
Stream.Write((byte)0x02); Stream.Write((byte)0x02);
Stream.Write((byte)0); Stream.Write((byte)0);
Stream.Write(m); Stream.Write(m);
} }
} }
public sealed class PartyMemberList : Packet public sealed class PartyMemberList : Packet
{ {
public PartyMemberList(Party p) : base(0xBF) public PartyMemberList(Party p) : base(0xBF)
{ {
EnsureCapacity(7 + p.Count * 4); EnsureCapacity(7 + p.Count * 4);
Stream.Write((short)0x0006); Stream.Write((short)0x0006);
Stream.Write((byte)0x01); Stream.Write((byte)0x01);
Stream.Write((byte)p.Count); Stream.Write((byte)p.Count);
for (var i = 0; i < p.Count; ++i) for (var i = 0; i < p.Count; ++i)
{ {
Stream.Write(p[i].Mobile.Serial); Stream.Write(p[i].Mobile.Serial);
}
} }
}
} }
public sealed class PartyRemoveMember : Packet public sealed class PartyRemoveMember : Packet
{ {
public PartyRemoveMember(Serial removed, Party p) : base(0xBF) public PartyRemoveMember(Serial removed, Party p) : base(0xBF)
{ {
EnsureCapacity(11 + p.Count * 4); EnsureCapacity(11 + p.Count * 4);
Stream.Write((short)0x0006); Stream.Write((short)0x0006);
Stream.Write((byte)0x02); Stream.Write((byte)0x02);
Stream.Write((byte)p.Count); Stream.Write((byte)p.Count);
Stream.Write(removed); Stream.Write(removed);
for (var i = 0; i < p.Count; ++i) for (var i = 0; i < p.Count; ++i)
{ {
Stream.Write(p[i].Mobile.Serial); Stream.Write(p[i].Mobile.Serial);
}
} }
}
} }
public sealed class PartyTextMessage : Packet public sealed class PartyTextMessage : Packet
{ {
public PartyTextMessage(bool toAll, Serial from, string text) : base(0xBF) public PartyTextMessage(bool toAll, Serial from, string text) : base(0xBF)
{ {
text ??= ""; text ??= "";
EnsureCapacity(12 + text.Length * 2); EnsureCapacity(12 + text.Length * 2);
Stream.Write((short)0x0006); Stream.Write((short)0x0006);
Stream.Write((byte)(toAll ? 0x04 : 0x03)); Stream.Write((byte)(toAll ? 0x04 : 0x03));
Stream.Write(from); Stream.Write(from);
Stream.WriteBigUniNull(text); Stream.WriteBigUniNull(text);
} }
} }
public sealed class PartyInvitation : Packet public sealed class PartyInvitation : Packet
{ {
public PartyInvitation(Serial leader) : base(0xBF) public PartyInvitation(Serial leader) : base(0xBF)
{ {
EnsureCapacity(10); EnsureCapacity(10);
Stream.Write((short)0x0006); Stream.Write((short)0x0006);
Stream.Write((byte)0x07); Stream.Write((byte)0x07);
Stream.Write(leader); Stream.Write(leader);
} }
} }

View file

@ -6,92 +6,93 @@ using Xunit;
namespace UOContent.Tests; namespace UOContent.Tests;
public class PartyPacketTests : IClassFixture<ServerFixture> [Collection("Sequential UOContent Tests")]
public class PartyPacketTests
{ {
[Fact] [Fact]
public void TestPartyEmptyList() public void TestPartyEmptyList()
{ {
Serial m = (Serial)0x1024u; Serial m = (Serial)0x1024u;
var expected = new PartyEmptyList(m).Compile(); var expected = new PartyEmptyList(m).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendPartyRemoveMember(m); ns.SendPartyRemoveMember(m);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Fact] [Fact]
public void TestPartyRemoveMember() public void TestPartyRemoveMember()
{ {
var leader = new Mobile((Serial)0x1024u); var leader = new Mobile((Serial)0x1024u);
leader.DefaultMobileInit(); leader.DefaultMobileInit();
var member = new Mobile((Serial)0x2048u); var member = new Mobile((Serial)0x2048u);
member.DefaultMobileInit(); member.DefaultMobileInit();
var p = new Party(leader); var p = new Party(leader);
p.Add(member); p.Add(member);
var expected = new PartyRemoveMember(member.Serial, p).Compile(); var expected = new PartyRemoveMember(member.Serial, p).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendPartyRemoveMember(member.Serial, p); ns.SendPartyRemoveMember(member.Serial, p);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Fact] [Fact]
public void TestPartyMemberList() public void TestPartyMemberList()
{ {
var leader = new Mobile((Serial)0x1024u); var leader = new Mobile((Serial)0x1024u);
leader.DefaultMobileInit(); leader.DefaultMobileInit();
var member = new Mobile((Serial)0x2048u); var member = new Mobile((Serial)0x2048u);
member.DefaultMobileInit(); member.DefaultMobileInit();
var p = new Party(leader); var p = new Party(leader);
p.Add(member); p.Add(member);
var expected = new PartyMemberList(p).Compile(); var expected = new PartyMemberList(p).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendPartyMemberList(p); ns.SendPartyMemberList(p);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Theory] [Theory]
[InlineData(true)] [InlineData(true)]
[InlineData(false)] [InlineData(false)]
public void TestPartyTextMessage(bool toAll) public void TestPartyTextMessage(bool toAll)
{ {
Serial serial = (Serial)0x1024u; Serial serial = (Serial)0x1024u;
var text = "[Party] Stuff Happens"; var text = "[Party] Stuff Happens";
var expected = new PartyTextMessage(toAll, serial, text).Compile(); var expected = new PartyTextMessage(toAll, serial, text).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendPartyTextMessage(serial, text, toAll); ns.SendPartyTextMessage(serial, text, toAll);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Fact] [Fact]
public void TestPartyInvitation() public void TestPartyInvitation()
{ {
Serial m = (Serial)0x1024u; Serial m = (Serial)0x1024u;
var expected = new PartyInvitation(m).Compile(); var expected = new PartyInvitation(m).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendPartyInvitation(m); ns.SendPartyInvitation(m);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
} }

View file

@ -13,12 +13,12 @@ public class CharacterStatuePacketTests
[InlineData(0x1024u, 1, 100, 200)] [InlineData(0x1024u, 1, 100, 200)]
public void TestSendStatueAnimation(uint s, int status, int anim, int frame) public void TestSendStatueAnimation(uint s, int status, int anim, int frame)
{ {
var expected = new UpdateStatueAnimation((Serial)s, status, anim, frame).Compile(); var expected = new UpdateStatueAnimation((Serial)s, status, anim, frame).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendStatueAnimation((Serial)s, status, anim, frame); ns.SendStatueAnimation((Serial)s, status, anim, frame);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
} }

View file

@ -4,16 +4,16 @@ public class UpdateStatueAnimation : Packet
{ {
public UpdateStatueAnimation(Serial serial, int status, int animation, int frame) : base(0xBF, 17) public UpdateStatueAnimation(Serial serial, int status, int animation, int frame) : base(0xBF, 17)
{ {
Stream.Write((short)0x11); Stream.Write((short)0x11);
Stream.Write((short)0x19); Stream.Write((short)0x19);
Stream.Write((byte)0x5); Stream.Write((byte)0x5);
Stream.Write(serial); Stream.Write(serial);
Stream.Write((byte)0); Stream.Write((byte)0);
Stream.Write((byte)0xFF); Stream.Write((byte)0xFF);
Stream.Write((byte)status); Stream.Write((byte)status);
Stream.Write((byte)0); Stream.Write((byte)0);
Stream.Write((byte)animation); Stream.Write((byte)animation);
Stream.Write((byte)0); Stream.Write((byte)0);
Stream.Write((byte)frame); Stream.Write((byte)frame);
} }
} }

View file

@ -7,7 +7,7 @@ using Xunit;
namespace Server.Tests.Gumps; namespace Server.Tests.Gumps;
[Collection("Sequential Tests")] [Collection("Sequential UOContent Tests")]
public class TestLayoutGumps public class TestLayoutGumps
{ {
[Fact] [Fact]

View file

@ -15,78 +15,79 @@ public class TestBook : BaseBook
public TestBook(int itemID, string title, string author, int pageCount, bool writable) : base(itemID, title, author, pageCount, writable) public TestBook(int itemID, string title, string author, int pageCount, bool writable) : base(itemID, title, author, pageCount, writable)
{ {
} }
public TestBook(int itemID, bool writable) : base(itemID, writable) public TestBook(int itemID, bool writable) : base(itemID, writable)
{ {
} }
public TestBook(Serial serial) : base(serial) public TestBook(Serial serial) : base(serial)
{ {
Pages = new BookPageInfo[20]; Pages = new BookPageInfo[20];
for (var i = 0; i < Pages.Length; ++i) for (var i = 0; i < Pages.Length; ++i)
{ {
Pages[i] = new BookPageInfo(); Pages[i] = new BookPageInfo();
}
} }
}
} }
public class BookPacketTests : IClassFixture<ServerFixture> [Collection("Sequential UOContent Tests")]
public class BookPacketTests
{ {
[Theory] [Theory]
[InlineData("🅵🅰🅽🅲🆈 🆃🅴🆇🆃 Author", "🅵🅰🅽🅲🆈 🆃🅴🆇🆃 Title")] [InlineData("🅵🅰🅽🅲🆈 🆃🅴🆇🆃 Author", "🅵🅰🅽🅲🆈 🆃🅴🆇🆃 Title")]
public void TestBookCover(string author, string title) public void TestBookCover(string author, string title)
{ {
var m = new Mobile((Serial)0x1); var m = new Mobile((Serial)0x1);
m.DefaultMobileInit(); m.DefaultMobileInit();
Serial serial = (Serial)0x1001; Serial serial = (Serial)0x1001;
var book = new TestBook(serial) { Author = author, Title = title }; var book = new TestBook(serial) { Author = author, Title = title };
var expected = new BookHeader(m, book).Compile(); var expected = new BookHeader(m, book).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendBookCover(m, book); ns.SendBookCover(m, book);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Fact] [Fact]
public void TestBookContent() public void TestBookContent()
{ {
var m = new Mobile((Serial)0x1); var m = new Mobile((Serial)0x1);
m.DefaultMobileInit(); m.DefaultMobileInit();
Serial serial = (Serial)0x1001; Serial serial = (Serial)0x1001;
var book = new TestBook(serial) { Author = "Some Author", Title = "Some Title" }; var book = new TestBook(serial) { Author = "Some Author", Title = "Some Title" };
book.Pages[0].Lines = new[] book.Pages[0].Lines = new[]
{ {
"Some books start with actual content", "Some books start with actual content",
"This book does not have any actual content", "This book does not have any actual content",
"Instead it has several pages of useless text" "Instead it has several pages of useless text"
}; };
book.Pages[1].Lines = new[] book.Pages[1].Lines = new[]
{ {
"Another page exists but this page:", "Another page exists but this page:",
"Has lots of: 🅵🅰🅽🅲🆈 🆃🅴🆇🆃", "Has lots of: 🅵🅰🅽🅲🆈 🆃🅴🆇🆃",
"And just more: 🅵🅰🅽🅲🆈 🆃🅴🆇🆃", "And just more: 🅵🅰🅽🅲🆈 🆃🅴🆇🆃",
"So everyone can read: 🅵🅰🅽🅲🆈 🆃🅴🆇🆃" "So everyone can read: 🅵🅰🅽🅲🆈 🆃🅴🆇🆃"
}; };
book.Pages[2].Lines = new[] book.Pages[2].Lines = new[]
{ {
"The end" "The end"
}; };
var expected = new BookPageDetails(book).Compile(); var expected = new BookPageDetails(book).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendBookContent(book); ns.SendBookContent(book);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
} }

View file

@ -7,52 +7,52 @@ public sealed class BookPageDetails : Packet
{ {
public BookPageDetails(BaseBook book) : base(0x66) public BookPageDetails(BaseBook book) : base(0x66)
{ {
EnsureCapacity(256); EnsureCapacity(256);
Stream.Write(book.Serial); Stream.Write(book.Serial);
Stream.Write((ushort)book.PagesCount); Stream.Write((ushort)book.PagesCount);
for (var i = 0; i < book.PagesCount; ++i) for (var i = 0; i < book.PagesCount; ++i)
{
var page = book.Pages[i];
Stream.Write((ushort)(i + 1));
Stream.Write((ushort)page.Lines.Length);
for (var j = 0; j < page.Lines.Length; ++j)
{ {
var page = book.Pages[i]; var buffer = page.Lines[j].GetBytesUtf8();
Stream.Write((ushort)(i + 1)); Stream.Write(buffer, 0, buffer.Length);
Stream.Write((ushort)page.Lines.Length); Stream.Write((byte)0);
for (var j = 0; j < page.Lines.Length; ++j)
{
var buffer = page.Lines[j].GetBytesUtf8();
Stream.Write(buffer, 0, buffer.Length);
Stream.Write((byte)0);
}
} }
} }
}
} }
public sealed class BookHeader : Packet public sealed class BookHeader : Packet
{ {
public BookHeader(Mobile from, BaseBook book) : base(0xD4) public BookHeader(Mobile from, BaseBook book) : base(0xD4)
{ {
var title = book.Title ?? ""; var title = book.Title ?? "";
var author = book.Author ?? ""; var author = book.Author ?? "";
var titleBuffer = title.GetBytesUtf8(); var titleBuffer = title.GetBytesUtf8();
var authorBuffer = author.GetBytesUtf8(); var authorBuffer = author.GetBytesUtf8();
EnsureCapacity(15 + titleBuffer.Length + authorBuffer.Length); EnsureCapacity(15 + titleBuffer.Length + authorBuffer.Length);
Stream.Write(book.Serial); Stream.Write(book.Serial);
Stream.Write(true); Stream.Write(true);
Stream.Write(book.Writable && from.InRange(book.GetWorldLocation(), 1)); Stream.Write(book.Writable && from.InRange(book.GetWorldLocation(), 1));
Stream.Write((ushort)book.PagesCount); Stream.Write((ushort)book.PagesCount);
Stream.Write((ushort)(titleBuffer.Length + 1)); Stream.Write((ushort)(titleBuffer.Length + 1));
Stream.Write(titleBuffer, 0, titleBuffer.Length); Stream.Write(titleBuffer, 0, titleBuffer.Length);
Stream.Write((byte)0); // terminate Stream.Write((byte)0); // terminate
Stream.Write((ushort)(authorBuffer.Length + 1)); Stream.Write((ushort)(authorBuffer.Length + 1));
Stream.Write(authorBuffer, 0, authorBuffer.Length); Stream.Write(authorBuffer, 0, authorBuffer.Length);
Stream.Write((byte)0); // terminate Stream.Write((byte)0); // terminate
} }
} }

View file

@ -7,8 +7,8 @@ using Xunit;
namespace UOContent.Tests; namespace UOContent.Tests;
[Collection("Sequential Tests")] [Collection("Sequential UOContent Tests")]
public class BulletinBoardPacketTests : IClassFixture<ServerFixture> public class BulletinBoardPacketTests
{ {
[Theory] [Theory]
[InlineData("Test Name")] [InlineData("Test Name")]
@ -16,16 +16,16 @@ public class BulletinBoardPacketTests : IClassFixture<ServerFixture>
[InlineData("🅵🅰🅽🅲🆈 🆃🅴🆇🆃")] [InlineData("🅵🅰🅽🅲🆈 🆃🅴🆇🆃")]
public void TestSendBBDisplayBoard(string boardName) public void TestSendBBDisplayBoard(string boardName)
{ {
var bb = new TestBulletinBoard(0x234) { BoardName = boardName }; var bb = new TestBulletinBoard(0x234) { BoardName = boardName };
var expected = new BBDisplayBoard(bb).Compile(); var expected = new BBDisplayBoard(bb).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendBBDisplayBoard(bb); ns.SendBBDisplayBoard(bb);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Theory] [Theory]
[InlineData("The Subject", false, "First Line", "Second Line", "Third Line")] [InlineData("The Subject", false, "First Line", "Second Line", "Third Line")]
@ -36,32 +36,32 @@ public class BulletinBoardPacketTests : IClassFixture<ServerFixture>
[InlineData("🅵🅰🅽🅲🆈 🆃🅴🆇🆃", true, "First Line", "Second Line")] [InlineData("🅵🅰🅽🅲🆈 🆃🅴🆇🆃", true, "First Line", "Second Line")]
public void TestSendBBHeaderMessage(string subject, bool content, params string[] lines) public void TestSendBBHeaderMessage(string subject, bool content, params string[] lines)
{ {
var poster = new Mobile((Serial)0x1024u) { Name = "Kamron" }; var poster = new Mobile((Serial)0x1024u) { Name = "Kamron" };
poster.DefaultMobileInit(); poster.DefaultMobileInit();
var bb = new TestBulletinBoard(0x234); var bb = new TestBulletinBoard(0x234);
bb.PostMessage(poster, null, subject, lines); bb.PostMessage(poster, null, subject, lines);
var msg = bb.Items[0] as BulletinMessage; var msg = bb.Items[0] as BulletinMessage;
var expected = (content ? var expected = (content ?
(Packet)new BBMessageContent(bb, msg) : new BBMessageHeader(bb, msg)).Compile(); (Packet)new BBMessageContent(bb, msg) : new BBMessageHeader(bb, msg)).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendBBMessage(bb, msg, content); ns.SendBBMessage(bb, msg, content);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
} }
internal class TestBulletinBoard : BaseBulletinBoard internal class TestBulletinBoard : BaseBulletinBoard
{ {
public TestBulletinBoard(int itemID) : base(itemID) public TestBulletinBoard(int itemID) : base(itemID)
{ {
} }
public TestBulletinBoard(Serial serial) : base(serial) public TestBulletinBoard(Serial serial) : base(serial)
{ {
} }
} }

View file

@ -1,149 +1,148 @@
using Server.Items; using Server.Items;
using Server.Text; using Server.Text;
namespace Server.Network namespace Server.Network;
public class BBDisplayBoard : Packet
{ {
public class BBDisplayBoard : Packet public BBDisplayBoard(BaseBulletinBoard board) : base(0x71)
{ {
public BBDisplayBoard(BaseBulletinBoard board) : base(0x71) EnsureCapacity(38);
var buffer = (board.BoardName ?? "").GetBytesUtf8();
Stream.Write((byte)0x00); // Packet ID
Stream.Write(board.Serial); // Bulletin board serial
// Bulletin board name
if (buffer.Length >= 29)
{ {
EnsureCapacity(38); Stream.Write(buffer, 0, 29);
var buffer = (board.BoardName ?? "").GetBytesUtf8();
Stream.Write((byte)0x00); // Packet ID
Stream.Write(board.Serial); // Bulletin board serial
// Bulletin board name
if (buffer.Length >= 29)
{
Stream.Write(buffer, 0, 29);
Stream.Write((byte)0);
}
else
{
Stream.Write(buffer, 0, buffer.Length);
Stream.Fill(30 - buffer.Length);
}
}
}
public class BBMessageHeader : Packet
{
public BBMessageHeader(BaseBulletinBoard board, BulletinMessage msg) : base(0x71)
{
var poster = SafeString(msg.PostedName);
var subject = SafeString(msg.Subject);
var time = SafeString(msg.GetTimeAsString());
EnsureCapacity(22 + poster.Length + subject.Length + time.Length);
Stream.Write((byte)0x01); // Packet ID
Stream.Write(board.Serial); // Bulletin board serial
Stream.Write(msg.Serial); // Message serial
Stream.Write(msg.Thread?.Serial ?? Serial.Zero); // Thread serial--parent
WriteString(poster);
WriteString(subject);
WriteString(time);
}
public void WriteString(string v)
{
var buffer = v.GetBytesUtf8();
var len = buffer.Length + 1;
if (len > 255)
{
len = 255;
}
Stream.Write((byte)len);
Stream.Write(buffer, 0, len - 1);
Stream.Write((byte)0); Stream.Write((byte)0);
} }
else
public string SafeString(string v) => v ?? string.Empty;
}
public class BBMessageContent : Packet
{
public BBMessageContent(BaseBulletinBoard board, BulletinMessage msg) : base(0x71)
{ {
var poster = SafeString(msg.PostedName); Stream.Write(buffer, 0, buffer.Length);
var subject = SafeString(msg.Subject); Stream.Fill(30 - buffer.Length);
var time = SafeString(msg.GetTimeAsString());
EnsureCapacity(22 + poster.Length + subject.Length + time.Length);
Stream.Write((byte)0x02); // Packet ID
Stream.Write(board.Serial); // Bulletin board serial
Stream.Write(msg.Serial); // Message serial
WriteString(poster);
WriteString(subject);
WriteString(time);
Stream.Write((short)msg.PostedBody);
Stream.Write((short)msg.PostedHue);
var len = msg.PostedEquip.Length;
if (len > 255)
{
len = 255;
}
Stream.Write((byte)len);
for (var i = 0; i < len; ++i)
{
var eq = msg.PostedEquip[i];
Stream.Write((short)eq._itemID);
Stream.Write((short)eq._hue);
}
len = msg.Lines.Length;
if (len > 255)
{
len = 255;
}
Stream.Write((byte)len);
for (var i = 0; i < len; ++i)
{
WriteString(msg.Lines[i], true);
}
} }
public void WriteString(string v, bool padding = false)
{
var buffer = v.GetBytesUtf8();
var tail = padding ? 2 : 1;
var len = buffer.Length + tail;
if (len > 255)
{
len = 255;
}
Stream.Write((byte)len);
Stream.Write(buffer, 0, len - tail);
if (padding)
{
Stream.Write((short)0); // padding compensates for a client bug
}
else
{
Stream.Write((byte)0);
}
}
public string SafeString(string v) => v ?? string.Empty;
} }
} }
public class BBMessageHeader : Packet
{
public BBMessageHeader(BaseBulletinBoard board, BulletinMessage msg) : base(0x71)
{
var poster = SafeString(msg.PostedName);
var subject = SafeString(msg.Subject);
var time = SafeString(msg.GetTimeAsString());
EnsureCapacity(22 + poster.Length + subject.Length + time.Length);
Stream.Write((byte)0x01); // Packet ID
Stream.Write(board.Serial); // Bulletin board serial
Stream.Write(msg.Serial); // Message serial
Stream.Write(msg.Thread?.Serial ?? Serial.Zero); // Thread serial--parent
WriteString(poster);
WriteString(subject);
WriteString(time);
}
public void WriteString(string v)
{
var buffer = v.GetBytesUtf8();
var len = buffer.Length + 1;
if (len > 255)
{
len = 255;
}
Stream.Write((byte)len);
Stream.Write(buffer, 0, len - 1);
Stream.Write((byte)0);
}
public string SafeString(string v) => v ?? string.Empty;
}
public class BBMessageContent : Packet
{
public BBMessageContent(BaseBulletinBoard board, BulletinMessage msg) : base(0x71)
{
var poster = SafeString(msg.PostedName);
var subject = SafeString(msg.Subject);
var time = SafeString(msg.GetTimeAsString());
EnsureCapacity(22 + poster.Length + subject.Length + time.Length);
Stream.Write((byte)0x02); // Packet ID
Stream.Write(board.Serial); // Bulletin board serial
Stream.Write(msg.Serial); // Message serial
WriteString(poster);
WriteString(subject);
WriteString(time);
Stream.Write((short)msg.PostedBody);
Stream.Write((short)msg.PostedHue);
var len = msg.PostedEquip.Length;
if (len > 255)
{
len = 255;
}
Stream.Write((byte)len);
for (var i = 0; i < len; ++i)
{
var eq = msg.PostedEquip[i];
Stream.Write((short)eq._itemID);
Stream.Write((short)eq._hue);
}
len = msg.Lines.Length;
if (len > 255)
{
len = 255;
}
Stream.Write((byte)len);
for (var i = 0; i < len; ++i)
{
WriteString(msg.Lines[i], true);
}
}
public void WriteString(string v, bool padding = false)
{
var buffer = v.GetBytesUtf8();
var tail = padding ? 2 : 1;
var len = buffer.Length + tail;
if (len > 255)
{
len = 255;
}
Stream.Write((byte)len);
Stream.Write(buffer, 0, len - tail);
if (padding)
{
Stream.Write((short)0); // padding compensates for a client bug
}
else
{
Stream.Write((byte)0);
}
}
public string SafeString(string v) => v ?? string.Empty;
}

View file

@ -6,42 +6,42 @@ using Xunit;
namespace UOContent.Tests; namespace UOContent.Tests;
[Collection("Sequential Tests")] [Collection("Sequential UOContent Tests")]
public class MahjongPacketTests : IClassFixture<ServerFixture> public class MahjongPacketTests
{ {
[Fact] [Fact]
public void TestMahjongJoinGame() public void TestMahjongJoinGame()
{ {
Serial game = (Serial)0x1024u; Serial game = (Serial)0x1024u;
var expected = new MahjongJoinGame(game).Compile(); var expected = new MahjongJoinGame(game).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendMahjongJoinGame(game); ns.SendMahjongJoinGame(game);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Theory] [Theory]
[InlineData(true)] [InlineData(true)]
[InlineData(false)] [InlineData(false)]
public void TestMahjongPlayersInfo(bool showScores) public void TestMahjongPlayersInfo(bool showScores)
{ {
var m = new Mobile((Serial)0x1); var m = new Mobile((Serial)0x1);
m.DefaultMobileInit(); m.DefaultMobileInit();
var game = new MahjongGame { ShowScores = showScores }; var game = new MahjongGame { ShowScores = showScores };
game.Players.Join(m); game.Players.Join(m);
var expected = new MahjongPlayersInfo(game, m).Compile(); var expected = new MahjongPlayersInfo(game, m).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendMahjongPlayersInfo(game, m); ns.SendMahjongPlayersInfo(game, m);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Theory] [Theory]
[InlineData(true, true)] [InlineData(true, true)]
@ -50,68 +50,68 @@ public class MahjongPacketTests : IClassFixture<ServerFixture>
[InlineData(false, false)] [InlineData(false, false)]
public void TestMahjongGeneralInfo(bool showScores, bool spectatorVision) public void TestMahjongGeneralInfo(bool showScores, bool spectatorVision)
{ {
var game = new MahjongGame { ShowScores = showScores, SpectatorVision = spectatorVision}; var game = new MahjongGame { ShowScores = showScores, SpectatorVision = spectatorVision};
var expected = new MahjongGeneralInfo(game).Compile(); var expected = new MahjongGeneralInfo(game).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendMahjongGeneralInfo(game); ns.SendMahjongGeneralInfo(game);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Theory] [Theory]
[InlineData(true)] [InlineData(true)]
[InlineData(false)] [InlineData(false)]
public void TestMahjongTilesInfo(bool spectatorVision) public void TestMahjongTilesInfo(bool spectatorVision)
{ {
var m = new Mobile((Serial)0x1); var m = new Mobile((Serial)0x1);
m.DefaultMobileInit(); m.DefaultMobileInit();
var game = new MahjongGame { SpectatorVision = spectatorVision }; var game = new MahjongGame { SpectatorVision = spectatorVision };
game.Players.Join(m); game.Players.Join(m);
var expected = new MahjongTilesInfo(game, m).Compile(); var expected = new MahjongTilesInfo(game, m).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendMahjongTilesInfo(game, m); ns.SendMahjongTilesInfo(game, m);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Theory] [Theory]
[InlineData(true)] [InlineData(true)]
[InlineData(false)] [InlineData(false)]
public void TestMahjongTileInfo(bool spectatorVision) public void TestMahjongTileInfo(bool spectatorVision)
{ {
var m = new Mobile((Serial)0x1); var m = new Mobile((Serial)0x1);
m.DefaultMobileInit(); m.DefaultMobileInit();
var game = new MahjongGame { SpectatorVision = spectatorVision }; var game = new MahjongGame { SpectatorVision = spectatorVision };
game.Players.Join(m); game.Players.Join(m);
var expected = new MahjongTileInfo(game.Tiles[0], m).Compile(); var expected = new MahjongTileInfo(game.Tiles[0], m).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendMahjongTileInfo(game.Tiles[0], m); ns.SendMahjongTileInfo(game.Tiles[0], m);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Fact] [Fact]
public void TestMahjongRelieve() public void TestMahjongRelieve()
{ {
Serial game = (Serial)0x1024u; Serial game = (Serial)0x1024u;
var expected = new MahjongRelieve(game).Compile(); var expected = new MahjongRelieve(game).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendMahjongRelieve(game); ns.SendMahjongRelieve(game);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
} }

View file

@ -7,176 +7,132 @@ public sealed class MahjongJoinGame : Packet
{ {
public MahjongJoinGame(Serial game) : base(0xDA) public MahjongJoinGame(Serial game) : base(0xDA)
{ {
EnsureCapacity(9); EnsureCapacity(9);
Stream.Write(game); Stream.Write(game);
Stream.Write((byte)0); Stream.Write((byte)0);
Stream.Write((byte)0x19); Stream.Write((byte)0x19);
} }
} }
public sealed class MahjongPlayersInfo : Packet public sealed class MahjongPlayersInfo : Packet
{ {
public MahjongPlayersInfo(MahjongGame game, Mobile to) : base(0xDA) public MahjongPlayersInfo(MahjongGame game, Mobile to) : base(0xDA)
{ {
var players = game.Players; var players = game.Players;
EnsureCapacity(11 + 45 * players.Seats); EnsureCapacity(11 + 45 * players.Seats);
Stream.Write(game.Serial); Stream.Write(game.Serial);
Stream.Write((byte)0); Stream.Write((byte)0);
Stream.Write((byte)0x2); Stream.Write((byte)0x2);
Stream.Write((byte)0); Stream.Write((byte)0);
Stream.Write((byte)players.Seats); Stream.Write((byte)players.Seats);
var n = 0; var n = 0;
for (var i = 0; i < players.Seats; i++) for (var i = 0; i < players.Seats; i++)
{
var mobile = players.GetPlayer(i);
if (mobile != null)
{ {
var mobile = players.GetPlayer(i); Stream.Write(mobile.Serial);
Stream.Write(players.DealerPosition == i ? (byte)0x1 : (byte)0x2);
Stream.Write((byte)i);
if (mobile != null) if (game.ShowScores || mobile == to)
{ {
Stream.Write(mobile.Serial); Stream.Write(players.GetScore(i));
Stream.Write(players.DealerPosition == i ? (byte)0x1 : (byte)0x2);
Stream.Write((byte)i);
if (game.ShowScores || mobile == to)
{
Stream.Write(players.GetScore(i));
}
else
{
Stream.Write(0);
}
Stream.Write((short)0);
Stream.Write((byte)0);
Stream.Write(players.IsPublic(i));
Stream.WriteAsciiFixed(mobile.Name, 30);
Stream.Write(!players.IsInGamePlayer(i));
n++;
} }
else if (game.ShowScores) else
{ {
Stream.Write(0); Stream.Write(0);
Stream.Write((byte)0x2);
Stream.Write((byte)i);
Stream.Write(players.GetScore(i));
Stream.Write((short)0);
Stream.Write((byte)0);
Stream.Write(players.IsPublic(i));
Stream.WriteAsciiFixed("", 30);
Stream.Write(true);
n++;
} }
}
if (n != players.Seats) Stream.Write((short)0);
Stream.Write((byte)0);
Stream.Write(players.IsPublic(i));
Stream.WriteAsciiFixed(mobile.Name, 30);
Stream.Write(!players.IsInGamePlayer(i));
n++;
}
else if (game.ShowScores)
{ {
Stream.Seek(10, SeekOrigin.Begin); Stream.Write(0);
Stream.Write((byte)n); Stream.Write((byte)0x2);
Stream.Write((byte)i);
Stream.Write(players.GetScore(i));
Stream.Write((short)0);
Stream.Write((byte)0);
Stream.Write(players.IsPublic(i));
Stream.WriteAsciiFixed("", 30);
Stream.Write(true);
n++;
} }
} }
if (n != players.Seats)
{
Stream.Seek(10, SeekOrigin.Begin);
Stream.Write((byte)n);
}
}
} }
public sealed class MahjongGeneralInfo : Packet public sealed class MahjongGeneralInfo : Packet
{ {
public MahjongGeneralInfo(MahjongGame game) : base(0xDA) public MahjongGeneralInfo(MahjongGame game) : base(0xDA)
{ {
EnsureCapacity(13); EnsureCapacity(13);
Stream.Write(game.Serial); Stream.Write(game.Serial);
Stream.Write((byte)0); Stream.Write((byte)0);
Stream.Write((byte)0x5); Stream.Write((byte)0x5);
Stream.Write((short)0); Stream.Write((short)0);
Stream.Write((byte)0); Stream.Write((byte)0);
Stream.Write((byte)((game.ShowScores ? 0x1 : 0x0) | (game.SpectatorVision ? 0x2 : 0x0))); Stream.Write((byte)((game.ShowScores ? 0x1 : 0x0) | (game.SpectatorVision ? 0x2 : 0x0)));
Stream.Write((byte)game.Dices.First); Stream.Write((byte)game.Dices.First);
Stream.Write((byte)game.Dices.Second); Stream.Write((byte)game.Dices.Second);
Stream.Write((byte)game.DealerIndicator.Wind); Stream.Write((byte)game.DealerIndicator.Wind);
Stream.Write((short)game.DealerIndicator.Position.Y); Stream.Write((short)game.DealerIndicator.Position.Y);
Stream.Write((short)game.DealerIndicator.Position.X); Stream.Write((short)game.DealerIndicator.Position.X);
Stream.Write((byte)game.DealerIndicator.Direction); Stream.Write((byte)game.DealerIndicator.Direction);
Stream.Write((short)game.WallBreakIndicator.Position.Y); Stream.Write((short)game.WallBreakIndicator.Position.Y);
Stream.Write((short)game.WallBreakIndicator.Position.X); Stream.Write((short)game.WallBreakIndicator.Position.X);
} }
} }
public sealed class MahjongTilesInfo : Packet public sealed class MahjongTilesInfo : Packet
{ {
public MahjongTilesInfo(MahjongGame game, Mobile to) : base(0xDA) public MahjongTilesInfo(MahjongGame game, Mobile to) : base(0xDA)
{ {
var tiles = game.Tiles; var tiles = game.Tiles;
var players = game.Players; var players = game.Players;
EnsureCapacity(11 + 9 * tiles.Length); EnsureCapacity(11 + 9 * tiles.Length);
Stream.Write(game.Serial); Stream.Write(game.Serial);
Stream.Write((byte)0); Stream.Write((byte)0);
Stream.Write((byte)0x4); Stream.Write((byte)0x4);
Stream.Write((short)tiles.Length); Stream.Write((short)tiles.Length);
foreach (var tile in tiles)
{
Stream.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))
{
Stream.Write((byte)tile.Value);
}
else
{
Stream.Write((byte)0);
}
}
else
{
Stream.Write((byte)0);
}
Stream.Write((short)tile.Position.Y);
Stream.Write((short)tile.Position.X);
Stream.Write((byte)tile.StackLevel);
Stream.Write((byte)tile.Direction);
Stream.Write(tile.Flipped ? (byte)0x10 : (byte)0x0);
}
}
}
public sealed class MahjongTileInfo : Packet
{
public MahjongTileInfo(MahjongTile tile, Mobile to) : base(0xDA)
{
var game = tile.Game;
var players = game.Players;
EnsureCapacity(18);
Stream.Write(tile.Game.Serial);
Stream.Write((byte)0);
Stream.Write((byte)0x3);
foreach (var tile in tiles)
{
Stream.Write((byte)tile.Number); Stream.Write((byte)tile.Number);
if (tile.Flipped) if (tile.Flipped)
@ -205,16 +161,60 @@ public sealed class MahjongTileInfo : Packet
Stream.Write(tile.Flipped ? (byte)0x10 : (byte)0x0); Stream.Write(tile.Flipped ? (byte)0x10 : (byte)0x0);
} }
}
}
public sealed class MahjongTileInfo : Packet
{
public MahjongTileInfo(MahjongTile tile, Mobile to) : base(0xDA)
{
var game = tile.Game;
var players = game.Players;
EnsureCapacity(18);
Stream.Write(tile.Game.Serial);
Stream.Write((byte)0);
Stream.Write((byte)0x3);
Stream.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))
{
Stream.Write((byte)tile.Value);
}
else
{
Stream.Write((byte)0);
}
}
else
{
Stream.Write((byte)0);
}
Stream.Write((short)tile.Position.Y);
Stream.Write((short)tile.Position.X);
Stream.Write((byte)tile.StackLevel);
Stream.Write((byte)tile.Direction);
Stream.Write(tile.Flipped ? (byte)0x10 : (byte)0x0);
}
} }
public sealed class MahjongRelieve : Packet public sealed class MahjongRelieve : Packet
{ {
public MahjongRelieve(Serial game) : base(0xDA) public MahjongRelieve(Serial game) : base(0xDA)
{ {
EnsureCapacity(9); EnsureCapacity(9);
Stream.Write(game); Stream.Write(game);
Stream.Write((byte)0); Stream.Write((byte)0);
Stream.Write((byte)0x1A); Stream.Write((byte)0x1A);
} }
} }

View file

@ -6,62 +6,62 @@ public sealed class MapDetails : Packet
{ {
public MapDetails(MapItem map) : base(0x90, 19) public MapDetails(MapItem map) : base(0x90, 19)
{ {
Stream.Write(map.Serial); Stream.Write(map.Serial);
Stream.Write((short)0x139D); Stream.Write((short)0x139D);
Stream.Write((short)map.Bounds.Start.X); Stream.Write((short)map.Bounds.Start.X);
Stream.Write((short)map.Bounds.Start.Y); Stream.Write((short)map.Bounds.Start.Y);
Stream.Write((short)map.Bounds.End.X); Stream.Write((short)map.Bounds.End.X);
Stream.Write((short)map.Bounds.End.Y); Stream.Write((short)map.Bounds.End.Y);
Stream.Write((short)map.Width); Stream.Write((short)map.Width);
Stream.Write((short)map.Height); Stream.Write((short)map.Height);
} }
} }
public sealed class MapDetailsNew : Packet public sealed class MapDetailsNew : Packet
{ {
public MapDetailsNew(MapItem map) : base(0xF5, 21) public MapDetailsNew(MapItem map) : base(0xF5, 21)
{ {
Stream.Write(map.Serial); Stream.Write(map.Serial);
Stream.Write((short)0x139D); Stream.Write((short)0x139D);
Stream.Write((short)map.Bounds.Start.X); Stream.Write((short)map.Bounds.Start.X);
Stream.Write((short)map.Bounds.Start.Y); Stream.Write((short)map.Bounds.Start.Y);
Stream.Write((short)map.Bounds.End.X); Stream.Write((short)map.Bounds.End.X);
Stream.Write((short)map.Bounds.End.Y); Stream.Write((short)map.Bounds.End.Y);
Stream.Write((short)map.Width); Stream.Write((short)map.Width);
Stream.Write((short)map.Height); Stream.Write((short)map.Height);
Stream.Write((short)(map.Facet?.MapID ?? 0)); Stream.Write((short)(map.Facet?.MapID ?? 0));
} }
} }
public class MapCommand : Packet public class MapCommand : Packet
{ {
public MapCommand(MapItem map, int command, int number, int x, int y) : base(0x56, 11) public MapCommand(MapItem map, int command, int number, int x, int y) : base(0x56, 11)
{ {
Stream.Write(map.Serial); Stream.Write(map.Serial);
Stream.Write((byte)command); Stream.Write((byte)command);
Stream.Write((byte)number); Stream.Write((byte)number);
Stream.Write((short)x); Stream.Write((short)x);
Stream.Write((short)y); Stream.Write((short)y);
} }
} }
public sealed class MapDisplay : MapCommand public sealed class MapDisplay : MapCommand
{ {
public MapDisplay(MapItem map) : base(map, 5, 0, 0, 0) public MapDisplay(MapItem map) : base(map, 5, 0, 0, 0)
{ {
} }
} }
public sealed class MapAddPin : MapCommand public sealed class MapAddPin : MapCommand
{ {
public MapAddPin(MapItem map, Point2D point) : base(map, 1, 0, point.X, point.Y) public MapAddPin(MapItem map, Point2D point) : base(map, 1, 0, point.X, point.Y)
{ {
} }
} }
public sealed class MapSetEditable : MapCommand public sealed class MapSetEditable : MapCommand
{ {
public MapSetEditable(MapItem map, bool editable) : base(map, 7, editable ? 1 : 0, 0, 0) public MapSetEditable(MapItem map, bool editable) : base(map, 7, editable ? 1 : 0, 0, 0)
{ {
} }
} }

View file

@ -7,26 +7,26 @@ using Xunit;
namespace UOContent.Tests; namespace UOContent.Tests;
[Collection("Sequential Tests")] [Collection("Sequential UOContent Tests")]
public class TestMapItemPackets : IClassFixture<ServerFixture> public class TestMapItemPackets
{ {
[Theory] [Theory]
[InlineData(ProtocolChanges.NewCharacterList)] [InlineData(ProtocolChanges.NewCharacterList)]
[InlineData(ProtocolChanges.None)] [InlineData(ProtocolChanges.None)]
public void TestSendMapDetails(ProtocolChanges changes) public void TestSendMapDetails(ProtocolChanges changes)
{ {
var mapItem = new MapItem(Map.Trammel); var mapItem = new MapItem(Map.Trammel);
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.ProtocolChanges = changes; ns.ProtocolChanges = changes;
var expected = (ns.NewCharacterList ? var expected = (ns.NewCharacterList ?
(Packet)new MapDetailsNew(mapItem) : new MapDetails(mapItem)).Compile(); (Packet)new MapDetailsNew(mapItem) : new MapDetails(mapItem)).Compile();
ns.SendMapDetails(mapItem); ns.SendMapDetails(mapItem);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Theory] [Theory]
[InlineData(5, 0, 0, 0)] [InlineData(5, 0, 0, 0)]
@ -35,14 +35,14 @@ public class TestMapItemPackets : IClassFixture<ServerFixture>
[InlineData(7, 0, 0, 0)] [InlineData(7, 0, 0, 0)]
public void TestSendMapCommand(int command, int number, int x, int y) public void TestSendMapCommand(int command, int number, int x, int y)
{ {
var mapItem = new MapItem(Map.Trammel); var mapItem = new MapItem(Map.Trammel);
var expected = new MapCommand(mapItem, command, number, x, y).Compile(); var expected = new MapCommand(mapItem, command, number, x, y).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendMapCommand(mapItem, command, x, y, number > 0); ns.SendMapCommand(mapItem, command, x, y, number > 0);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
} }

View file

@ -7,50 +7,50 @@ using Xunit;
namespace UOContent.Tests; namespace UOContent.Tests;
[Collection("Sequential Tests")] [Collection("Sequential UOContent Tests")]
public class CorpsePacketTests : IClassFixture<ServerFixture> public class CorpsePacketTests
{ {
[Fact] [Fact]
public void TestCorpseEquipPacket() public void TestCorpseEquipPacket()
{ {
var m = new Mobile((Serial)0x1); var m = new Mobile((Serial)0x1);
m.DefaultMobileInit(); m.DefaultMobileInit();
var weapon = new VikingSword(); var weapon = new VikingSword();
m.EquipItem(weapon); m.EquipItem(weapon);
var c = new Corpse(m, m.Items); var c = new Corpse(m, m.Items);
var expected = new CorpseEquip(m, c).Compile(); var expected = new CorpseEquip(m, c).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendCorpseEquip(m, c); ns.SendCorpseEquip(m, c);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Theory] [Theory]
[InlineData(ProtocolChanges.None)] [InlineData(ProtocolChanges.None)]
[InlineData(ProtocolChanges.ContainerGridLines)] [InlineData(ProtocolChanges.ContainerGridLines)]
public void TestCorpseContainerPacket(ProtocolChanges changes) public void TestCorpseContainerPacket(ProtocolChanges changes)
{ {
var m = new Mobile((Serial)0x1); var m = new Mobile((Serial)0x1);
m.DefaultMobileInit(); m.DefaultMobileInit();
var weapon = new VikingSword(); var weapon = new VikingSword();
m.EquipItem(weapon); m.EquipItem(weapon);
var c = new Corpse(m, m.Items); var c = new Corpse(m, m.Items);
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.ProtocolChanges = changes; ns.ProtocolChanges = changes;
var expected = (ns.ContainerGridLines ? (Packet)new CorpseContent6017(m, c) : new CorpseContent(m, c)).Compile(); var expected = (ns.ContainerGridLines ? (Packet)new CorpseContent6017(m, c) : new CorpseContent(m, c)).Compile();
ns.SendCorpseContent(m, c); ns.SendCorpseContent(m, c);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
} }

View file

@ -14,24 +14,24 @@ public class WeaponAbilityPacketTests
[InlineData(1000, false)] [InlineData(1000, false)]
public void TestSpecialAbility(int abilityId, bool active) public void TestSpecialAbility(int abilityId, bool active)
{ {
var expected = new ToggleSpecialAbility(abilityId, active).Compile(); var expected = new ToggleSpecialAbility(abilityId, active).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendToggleSpecialAbility(abilityId, active); ns.SendToggleSpecialAbility(abilityId, active);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Fact] [Fact]
public void TestClearAbility() public void TestClearAbility()
{ {
var expected = new ClearWeaponAbility().Compile(); var expected = new ClearWeaponAbility().Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendClearWeaponAbility(); ns.SendClearWeaponAbility();
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
} }

View file

@ -6,13 +6,13 @@ public sealed class ToggleSpecialAbility : Packet
{ {
public ToggleSpecialAbility(int abilityID, bool active) : base(0xBF) public ToggleSpecialAbility(int abilityID, bool active) : base(0xBF)
{ {
EnsureCapacity(7); EnsureCapacity(7);
Stream.Write((short)0x25); Stream.Write((short)0x25);
Stream.Write((short)abilityID); Stream.Write((short)abilityID);
Stream.Write(active); Stream.Write(active);
} }
} }
public sealed class ClearWeaponAbility : Packet public sealed class ClearWeaponAbility : Packet
@ -21,8 +21,8 @@ public sealed class ClearWeaponAbility : Packet
public ClearWeaponAbility() : base(0xBF) public ClearWeaponAbility() : base(0xBF)
{ {
EnsureCapacity(5); EnsureCapacity(5);
Stream.Write((short)0x21); Stream.Write((short)0x21);
} }
} }

View file

@ -11,7 +11,8 @@ using Xunit;
namespace UOContent.Tests; namespace UOContent.Tests;
public class BoatPacketTests : IClassFixture<ServerFixture> [Collection("Sequential UOContent Tests")]
public class BoatPacketTests
{ {
[Theory] [Theory]
[InlineData(Direction.West, 10, 100, 200)] [InlineData(Direction.West, 10, 100, 200)]

View file

@ -15,117 +15,117 @@ public sealed class MoveBoatHS : Packet
int yOffset int yOffset
) : base(0xF6) ) : base(0xF6)
{ {
EnsureCapacity(3 + 15 + ents.Count * 10); EnsureCapacity(3 + 15 + ents.Count * 10);
Stream.Write(boat.Serial); Stream.Write(boat.Serial);
Stream.Write((byte)speed); Stream.Write((byte)speed);
Stream.Write((byte)d); Stream.Write((byte)d);
Stream.Write((byte)boat.Facing); Stream.Write((byte)boat.Facing);
Stream.Write((short)(boat.X + xOffset)); Stream.Write((short)(boat.X + xOffset));
Stream.Write((short)(boat.Y + yOffset)); Stream.Write((short)(boat.Y + yOffset));
Stream.Write((short)boat.Z); Stream.Write((short)boat.Z);
Stream.Write((short)0); // count placeholder Stream.Write((short)0); // count placeholder
var count = 0; var count = 0;
foreach (var ent in ents) foreach (var ent in ents)
{ {
Stream.Write(ent.Serial); Stream.Write(ent.Serial);
Stream.Write((short)(ent.X + xOffset)); Stream.Write((short)(ent.X + xOffset));
Stream.Write((short)(ent.Y + yOffset)); Stream.Write((short)(ent.Y + yOffset));
Stream.Write((short)ent.Z); Stream.Write((short)ent.Z);
++count; ++count;
}
Stream.Seek(16, SeekOrigin.Begin);
Stream.Write((short)count);
} }
Stream.Seek(16, SeekOrigin.Begin);
Stream.Write((short)count);
}
} }
public sealed class DisplayBoatHS : Packet public sealed class DisplayBoatHS : Packet
{ {
public DisplayBoatHS(Mobile beholder, BaseBoat boat) : base(0xF7) public DisplayBoatHS(Mobile beholder, BaseBoat boat) : base(0xF7)
{ {
var ents = boat.GetMovingEntities(true); var ents = boat.GetMovingEntities(true);
EnsureCapacity(3 + 2 + 5 * 26); EnsureCapacity(3 + 2 + 5 * 26);
Stream.Write((short)0); // count placeholder Stream.Write((short)0); // count placeholder
var count = 0; var count = 0;
foreach (var ent in ents) foreach (var ent in ents)
{
if (!beholder.CanSee(ent))
{ {
if (!beholder.CanSee(ent)) continue;
{
continue;
}
// Embedded WorldItemHS packets
Stream.Write((byte)0xF3);
Stream.Write((short)0x1);
if (ent is BaseMulti bm)
{
Stream.Write((byte)0x02);
Stream.Write(bm.Serial);
// TODO: Mask no longer needed, merge with Item case?
Stream.Write((ushort)(bm.ItemID & 0x3FFF));
Stream.Write((byte)0);
Stream.Write((short)bm.Amount);
Stream.Write((short)bm.Amount);
Stream.Write((short)(bm.X & 0x7FFF));
Stream.Write((short)(bm.Y & 0x3FFF));
Stream.Write((sbyte)bm.Z);
Stream.Write((byte)bm.Light);
Stream.Write((short)bm.Hue);
Stream.Write((byte)bm.GetPacketFlags());
}
else if (ent is Mobile m)
{
Stream.Write((byte)0x01);
Stream.Write(m.Serial);
Stream.Write((short)m.Body);
Stream.Write((byte)0);
Stream.Write((short)1);
Stream.Write((short)1);
Stream.Write((short)(m.X & 0x7FFF));
Stream.Write((short)(m.Y & 0x3FFF));
Stream.Write((sbyte)m.Z);
Stream.Write((byte)m.Direction);
Stream.Write((short)m.Hue);
Stream.Write((byte)m.GetPacketFlags(true));
}
else if (ent is Item item)
{
Stream.Write((byte)0x00);
Stream.Write(item.Serial);
Stream.Write((ushort)(item.ItemID & 0xFFFF));
Stream.Write((byte)0);
Stream.Write((short)item.Amount);
Stream.Write((short)item.Amount);
Stream.Write((short)(item.X & 0x7FFF));
Stream.Write((short)(item.Y & 0x3FFF));
Stream.Write((sbyte)item.Z);
Stream.Write((byte)item.Light);
Stream.Write((short)item.Hue);
Stream.Write((byte)item.GetPacketFlags());
}
Stream.Write((short)0x00);
++count;
} }
Stream.Seek(3, SeekOrigin.Begin); // Embedded WorldItemHS packets
Stream.Write((short)count); Stream.Write((byte)0xF3);
Stream.Write((short)0x1);
if (ent is BaseMulti bm)
{
Stream.Write((byte)0x02);
Stream.Write(bm.Serial);
// TODO: Mask no longer needed, merge with Item case?
Stream.Write((ushort)(bm.ItemID & 0x3FFF));
Stream.Write((byte)0);
Stream.Write((short)bm.Amount);
Stream.Write((short)bm.Amount);
Stream.Write((short)(bm.X & 0x7FFF));
Stream.Write((short)(bm.Y & 0x3FFF));
Stream.Write((sbyte)bm.Z);
Stream.Write((byte)bm.Light);
Stream.Write((short)bm.Hue);
Stream.Write((byte)bm.GetPacketFlags());
}
else if (ent is Mobile m)
{
Stream.Write((byte)0x01);
Stream.Write(m.Serial);
Stream.Write((short)m.Body);
Stream.Write((byte)0);
Stream.Write((short)1);
Stream.Write((short)1);
Stream.Write((short)(m.X & 0x7FFF));
Stream.Write((short)(m.Y & 0x3FFF));
Stream.Write((sbyte)m.Z);
Stream.Write((byte)m.Direction);
Stream.Write((short)m.Hue);
Stream.Write((byte)m.GetPacketFlags(true));
}
else if (ent is Item item)
{
Stream.Write((byte)0x00);
Stream.Write(item.Serial);
Stream.Write((ushort)(item.ItemID & 0xFFFF));
Stream.Write((byte)0);
Stream.Write((short)item.Amount);
Stream.Write((short)item.Amount);
Stream.Write((short)(item.X & 0x7FFF));
Stream.Write((short)(item.Y & 0x3FFF));
Stream.Write((sbyte)item.Z);
Stream.Write((byte)item.Light);
Stream.Write((short)item.Hue);
Stream.Write((byte)item.GetPacketFlags());
}
Stream.Write((short)0x00);
++count;
} }
}
Stream.Seek(3, SeekOrigin.Begin);
Stream.Write((short)count);
}
}

View file

@ -14,66 +14,66 @@ public class HousePacketTests
[InlineData(0x1001u)] [InlineData(0x1001u)]
public void TestBeginHouseCustomization(uint serial) public void TestBeginHouseCustomization(uint serial)
{ {
var expected = new BeginHouseCustomization((Serial)serial).Compile(); var expected = new BeginHouseCustomization((Serial)serial).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendBeginHouseCustomization((Serial)serial); ns.SendBeginHouseCustomization((Serial)serial);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Theory] [Theory]
[InlineData(0x1001u)] [InlineData(0x1001u)]
public void TestEndHouseCustomization(uint serial) public void TestEndHouseCustomization(uint serial)
{ {
var expected = new EndHouseCustomization((Serial)serial).Compile(); var expected = new EndHouseCustomization((Serial)serial).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendEndHouseCustomization((Serial)serial); ns.SendEndHouseCustomization((Serial)serial);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Theory] [Theory]
[InlineData(0x1001u, 0)] [InlineData(0x1001u, 0)]
[InlineData(0x1001u, 100)] [InlineData(0x1001u, 100)]
public void TestDesignStateGeneral(uint serial, int revision) public void TestDesignStateGeneral(uint serial, int revision)
{ {
var expected = new DesignStateGeneral((Serial)serial, revision).Compile(); var expected = new DesignStateGeneral((Serial)serial, revision).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendDesignStateGeneral((Serial)serial, revision); ns.SendDesignStateGeneral((Serial)serial, revision);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Fact] [Fact]
public void TestHouseDesignStateDetailed() public void TestHouseDesignStateDetailed()
{ {
Serial serial = (Serial)0x40000001; Serial serial = (Serial)0x40000001;
var revision = 10; var revision = 10;
var tiles = new MultiTileEntry[250]; var tiles = new MultiTileEntry[250];
for (var i = 0; i < tiles.Length; i++) for (var i = 0; i < tiles.Length; i++)
{ {
tiles[i] = new MultiTileEntry( tiles[i] = new MultiTileEntry(
(ushort)i, (ushort)i,
(byte)i, (byte)i,
(byte)i, (byte)i,
(byte)(i / 50), (byte)(i / 50),
TileFlag.None TileFlag.None
); );
}
var mcl = new MultiComponentList(tiles.ToList());
var expected = new DesignStateDetailed(
serial, revision, mcl.Min.X, mcl.Min.Y, mcl.Max.X, mcl.Max.Y, tiles
).Compile();
var actual = HousePackets.CreateHouseDesignStateDetailed(serial, revision, mcl);
AssertThat.Equal(actual, expected);
} }
} var mcl = new MultiComponentList(tiles.ToList());
var expected = new DesignStateDetailed(
serial, revision, mcl.Min.X, mcl.Min.Y, mcl.Max.X, mcl.Max.Y, tiles
).Compile();
var actual = HousePackets.CreateHouseDesignStateDetailed(serial, revision, mcl);
AssertThat.Equal(actual, expected);
}
}

View file

@ -3,320 +3,319 @@ using System.Buffers;
using System.IO; using System.IO;
using Server.Compression; using Server.Compression;
namespace Server.Network namespace Server.Network;
public class BeginHouseCustomization : Packet
{ {
public class BeginHouseCustomization : Packet public BeginHouseCustomization(Serial house) : base(0xBF)
{ {
public BeginHouseCustomization(Serial house) : base(0xBF) EnsureCapacity(17);
{
EnsureCapacity(17);
Stream.Write((short)0x20); Stream.Write((short)0x20);
Stream.Write(house); Stream.Write(house);
Stream.Write((byte)0x04); Stream.Write((byte)0x04);
Stream.Write((ushort)0x0000); Stream.Write((ushort)0x0000);
Stream.Write((ushort)0xFFFF); Stream.Write((ushort)0xFFFF);
Stream.Write((ushort)0xFFFF); Stream.Write((ushort)0xFFFF);
Stream.Write((byte)0xFF); Stream.Write((byte)0xFF);
}
}
public class EndHouseCustomization : Packet
{
public EndHouseCustomization(Serial house) : base(0xBF)
{
EnsureCapacity(17);
Stream.Write((short)0x20);
Stream.Write(house);
Stream.Write((byte)0x05);
Stream.Write((ushort)0x0000);
Stream.Write((ushort)0xFFFF);
Stream.Write((ushort)0xFFFF);
Stream.Write((byte)0xFF);
}
}
public sealed class DesignStateGeneral : Packet
{
public DesignStateGeneral(Serial house, int revision) : base(0xBF)
{
EnsureCapacity(13);
Stream.Write((short)0x1D);
Stream.Write(house);
Stream.Write(revision);
}
}
public sealed class DesignStateDetailed : Packet
{
public const int MaxItemsPerStairBuffer = 750;
private readonly bool[] m_PlaneUsed = new bool[9];
private readonly byte[] m_PrimBuffer = new byte[4];
public DesignStateDetailed(Serial serial, int revision, int xMin, int yMin, int xMax, int yMax, MultiTileEntry[] tiles)
: base(0xD8)
{
EnsureCapacity(17 + tiles.Length * 5);
Write((byte)0x03); // Compression Type
Write((byte)0x00); // Unknown
Write(serial.Value);
Write(revision);
Write((short)tiles.Length);
Write((short)0); // Buffer length : reserved
Write((byte)0); // Plane count : reserved
var totalLength = 1; // includes plane count
var width = xMax - xMin + 1;
var height = yMax - yMin + 1;
var planeBuffers = new byte[9][];
for (var i = 0; i < planeBuffers.Length; ++i)
{
planeBuffers[i] = ArrayPool<byte>.Shared.Rent(0x400);
} }
var stairBuffers = new byte[6][];
for (var i = 0; i < stairBuffers.Length; ++i)
{
stairBuffers[i] = ArrayPool<byte>.Shared.Rent(MaxItemsPerStairBuffer * 5);
}
Clear(planeBuffers[0], width * height * 2);
for (var i = 0; i < 4; ++i)
{
Clear(planeBuffers[1 + i], (width - 1) * (height - 2) * 2);
Clear(planeBuffers[5 + i], width * (height - 1) * 2);
}
var totalStairsUsed = 0;
for (var i = 0; i < tiles.Length; ++i)
{
var mte = tiles[i];
var x = mte.OffsetX - xMin;
var y = mte.OffsetY - yMin;
int z = mte.OffsetZ;
var floor = TileData.ItemTable[mte.ItemId & TileData.MaxItemValue].Height <= 0;
int plane, size;
switch (z)
{
case 0:
plane = 0;
break;
case 7:
plane = 1;
break;
case 27:
plane = 2;
break;
case 47:
plane = 3;
break;
case 67:
plane = 4;
break;
default:
{
var stairBufferIndex = totalStairsUsed / MaxItemsPerStairBuffer;
var stairBuffer = stairBuffers[stairBufferIndex];
var byteIndex = totalStairsUsed % MaxItemsPerStairBuffer * 5;
stairBuffer[byteIndex++] = (byte)(mte.ItemId >> 8);
stairBuffer[byteIndex++] = (byte)mte.ItemId;
stairBuffer[byteIndex++] = (byte)mte.OffsetX;
stairBuffer[byteIndex++] = (byte)mte.OffsetY;
stairBuffer[byteIndex] = (byte)mte.OffsetZ;
++totalStairsUsed;
continue;
}
}
if (plane == 0)
{
size = height;
}
else if (floor)
{
size = height - 2;
x -= 1;
y -= 1;
}
else
{
size = height - 1;
plane += 4;
}
var index = (x * size + y) * 2;
if (x < 0 || y < 0 || y >= size || index + 1 >= 0x400)
{
var stairBufferIndex = totalStairsUsed / MaxItemsPerStairBuffer;
var stairBuffer = stairBuffers[stairBufferIndex];
var byteIndex = totalStairsUsed % MaxItemsPerStairBuffer * 5;
stairBuffer[byteIndex++] = (byte)(mte.ItemId >> 8);
stairBuffer[byteIndex++] = (byte)mte.ItemId;
stairBuffer[byteIndex++] = (byte)mte.OffsetX;
stairBuffer[byteIndex++] = (byte)mte.OffsetY;
stairBuffer[byteIndex] = (byte)mte.OffsetZ;
++totalStairsUsed;
}
else
{
m_PlaneUsed[plane] = true;
planeBuffers[plane][index] = (byte)(mte.ItemId >> 8);
planeBuffers[plane][index + 1] = (byte)mte.ItemId;
}
}
var planeCount = 0;
var deflatedBuffer = ArrayPool<byte>.Shared.Rent(0x2000);
for (var i = 0; i < planeBuffers.Length; ++i)
{
if (!m_PlaneUsed[i])
{
ArrayPool<byte>.Shared.Return(planeBuffers[i]);
continue;
}
++planeCount;
int size = i switch
{
0 => width * height * 2,
< 5 => (width - 1) * (height - 2) * 2,
_ => width * (height - 1) * 2
};
var inflatedBuffer = planeBuffers[i];
var deflatedLength = Deflate.Standard.Pack(
deflatedBuffer,
inflatedBuffer.AsSpan(0, size)
);
if (deflatedLength == 0)
{
Console.WriteLine("Compression error");
}
Write((byte)(0x20 | i));
Write((byte)size);
Write((byte)deflatedLength);
Write((byte)(((size >> 4) & 0xF0) | ((deflatedLength >> 8) & 0xF)));
Write(deflatedBuffer, 0, deflatedLength);
totalLength += 4 + deflatedLength;
ArrayPool<byte>.Shared.Return(inflatedBuffer);
}
var totalStairBuffersUsed = (totalStairsUsed + (MaxItemsPerStairBuffer - 1)) / MaxItemsPerStairBuffer;
for (var i = 0; i < totalStairBuffersUsed; ++i)
{
++planeCount;
var count = Math.Min(MaxItemsPerStairBuffer, totalStairsUsed - i * MaxItemsPerStairBuffer);
var size = count * 5;
var inflatedBuffer = stairBuffers[i];
var deflatedLength = Deflate.Standard.Pack(
deflatedBuffer,
inflatedBuffer.AsSpan(0, size)
);
if (deflatedLength == 0)
{
Console.WriteLine("Compression error");
}
Write((byte)(9 + i));
Write((byte)size);
Write((byte)deflatedLength);
Write((byte)(((size >> 4) & 0xF0) | ((deflatedLength >> 8) & 0xF)));
Write(deflatedBuffer, 0, deflatedLength);
totalLength += 4 + deflatedLength;
}
for (var i = 0; i < stairBuffers.Length; ++i)
{
ArrayPool<byte>.Shared.Return(stairBuffers[i]);
}
ArrayPool<byte>.Shared.Return(deflatedBuffer);
Stream.Seek(15, SeekOrigin.Begin);
Write((short)totalLength); // Buffer length
Write((byte)planeCount); // Plane count
} }
public class EndHouseCustomization : Packet public void Write(int value)
{ {
public EndHouseCustomization(Serial house) : base(0xBF) m_PrimBuffer[0] = (byte)(value >> 24);
{ m_PrimBuffer[1] = (byte)(value >> 16);
EnsureCapacity(17); m_PrimBuffer[2] = (byte)(value >> 8);
m_PrimBuffer[3] = (byte)value;
Stream.Write((short)0x20); Stream.UnderlyingStream.Write(m_PrimBuffer, 0, 4);
Stream.Write(house);
Stream.Write((byte)0x05);
Stream.Write((ushort)0x0000);
Stream.Write((ushort)0xFFFF);
Stream.Write((ushort)0xFFFF);
Stream.Write((byte)0xFF);
}
} }
public sealed class DesignStateGeneral : Packet public void Write(uint value)
{ {
public DesignStateGeneral(Serial house, int revision) : base(0xBF) m_PrimBuffer[0] = (byte)(value >> 24);
{ m_PrimBuffer[1] = (byte)(value >> 16);
EnsureCapacity(13); m_PrimBuffer[2] = (byte)(value >> 8);
m_PrimBuffer[3] = (byte)value;
Stream.Write((short)0x1D); Stream.UnderlyingStream.Write(m_PrimBuffer, 0, 4);
Stream.Write(house);
Stream.Write(revision);
}
} }
public sealed class DesignStateDetailed : Packet public void Write(short value)
{ {
public const int MaxItemsPerStairBuffer = 750; m_PrimBuffer[0] = (byte)(value >> 8);
m_PrimBuffer[1] = (byte)value;
private readonly bool[] m_PlaneUsed = new bool[9]; Stream.UnderlyingStream.Write(m_PrimBuffer, 0, 2);
private readonly byte[] m_PrimBuffer = new byte[4]; }
public DesignStateDetailed(Serial serial, int revision, int xMin, int yMin, int xMax, int yMax, MultiTileEntry[] tiles) public void Write(byte value)
: base(0xD8) {
Stream.UnderlyingStream.WriteByte(value);
}
public void Write(byte[] buffer, int offset, int size)
{
Stream.UnderlyingStream.Write(buffer, offset, size);
}
public static void Clear(byte[] buffer, int size)
{
for (var i = 0; i < size; ++i)
{ {
EnsureCapacity(17 + tiles.Length * 5); buffer[i] = 0;
Write((byte)0x03); // Compression Type
Write((byte)0x00); // Unknown
Write(serial.Value);
Write(revision);
Write((short)tiles.Length);
Write((short)0); // Buffer length : reserved
Write((byte)0); // Plane count : reserved
var totalLength = 1; // includes plane count
var width = xMax - xMin + 1;
var height = yMax - yMin + 1;
var planeBuffers = new byte[9][];
for (var i = 0; i < planeBuffers.Length; ++i)
{
planeBuffers[i] = ArrayPool<byte>.Shared.Rent(0x400);
}
var stairBuffers = new byte[6][];
for (var i = 0; i < stairBuffers.Length; ++i)
{
stairBuffers[i] = ArrayPool<byte>.Shared.Rent(MaxItemsPerStairBuffer * 5);
}
Clear(planeBuffers[0], width * height * 2);
for (var i = 0; i < 4; ++i)
{
Clear(planeBuffers[1 + i], (width - 1) * (height - 2) * 2);
Clear(planeBuffers[5 + i], width * (height - 1) * 2);
}
var totalStairsUsed = 0;
for (var i = 0; i < tiles.Length; ++i)
{
var mte = tiles[i];
var x = mte.OffsetX - xMin;
var y = mte.OffsetY - yMin;
int z = mte.OffsetZ;
var floor = TileData.ItemTable[mte.ItemId & TileData.MaxItemValue].Height <= 0;
int plane, size;
switch (z)
{
case 0:
plane = 0;
break;
case 7:
plane = 1;
break;
case 27:
plane = 2;
break;
case 47:
plane = 3;
break;
case 67:
plane = 4;
break;
default:
{
var stairBufferIndex = totalStairsUsed / MaxItemsPerStairBuffer;
var stairBuffer = stairBuffers[stairBufferIndex];
var byteIndex = totalStairsUsed % MaxItemsPerStairBuffer * 5;
stairBuffer[byteIndex++] = (byte)(mte.ItemId >> 8);
stairBuffer[byteIndex++] = (byte)mte.ItemId;
stairBuffer[byteIndex++] = (byte)mte.OffsetX;
stairBuffer[byteIndex++] = (byte)mte.OffsetY;
stairBuffer[byteIndex] = (byte)mte.OffsetZ;
++totalStairsUsed;
continue;
}
}
if (plane == 0)
{
size = height;
}
else if (floor)
{
size = height - 2;
x -= 1;
y -= 1;
}
else
{
size = height - 1;
plane += 4;
}
var index = (x * size + y) * 2;
if (x < 0 || y < 0 || y >= size || index + 1 >= 0x400)
{
var stairBufferIndex = totalStairsUsed / MaxItemsPerStairBuffer;
var stairBuffer = stairBuffers[stairBufferIndex];
var byteIndex = totalStairsUsed % MaxItemsPerStairBuffer * 5;
stairBuffer[byteIndex++] = (byte)(mte.ItemId >> 8);
stairBuffer[byteIndex++] = (byte)mte.ItemId;
stairBuffer[byteIndex++] = (byte)mte.OffsetX;
stairBuffer[byteIndex++] = (byte)mte.OffsetY;
stairBuffer[byteIndex] = (byte)mte.OffsetZ;
++totalStairsUsed;
}
else
{
m_PlaneUsed[plane] = true;
planeBuffers[plane][index] = (byte)(mte.ItemId >> 8);
planeBuffers[plane][index + 1] = (byte)mte.ItemId;
}
}
var planeCount = 0;
var deflatedBuffer = ArrayPool<byte>.Shared.Rent(0x2000);
for (var i = 0; i < planeBuffers.Length; ++i)
{
if (!m_PlaneUsed[i])
{
ArrayPool<byte>.Shared.Return(planeBuffers[i]);
continue;
}
++planeCount;
int size = i switch
{
0 => width * height * 2,
< 5 => (width - 1) * (height - 2) * 2,
_ => width * (height - 1) * 2
};
var inflatedBuffer = planeBuffers[i];
var deflatedLength = Deflate.Standard.Pack(
deflatedBuffer,
inflatedBuffer.AsSpan(0, size)
);
if (deflatedLength == 0)
{
Console.WriteLine("Compression error");
}
Write((byte)(0x20 | i));
Write((byte)size);
Write((byte)deflatedLength);
Write((byte)(((size >> 4) & 0xF0) | ((deflatedLength >> 8) & 0xF)));
Write(deflatedBuffer, 0, deflatedLength);
totalLength += 4 + deflatedLength;
ArrayPool<byte>.Shared.Return(inflatedBuffer);
}
var totalStairBuffersUsed = (totalStairsUsed + (MaxItemsPerStairBuffer - 1)) / MaxItemsPerStairBuffer;
for (var i = 0; i < totalStairBuffersUsed; ++i)
{
++planeCount;
var count = Math.Min(MaxItemsPerStairBuffer, totalStairsUsed - i * MaxItemsPerStairBuffer);
var size = count * 5;
var inflatedBuffer = stairBuffers[i];
var deflatedLength = Deflate.Standard.Pack(
deflatedBuffer,
inflatedBuffer.AsSpan(0, size)
);
if (deflatedLength == 0)
{
Console.WriteLine("Compression error");
}
Write((byte)(9 + i));
Write((byte)size);
Write((byte)deflatedLength);
Write((byte)(((size >> 4) & 0xF0) | ((deflatedLength >> 8) & 0xF)));
Write(deflatedBuffer, 0, deflatedLength);
totalLength += 4 + deflatedLength;
}
for (var i = 0; i < stairBuffers.Length; ++i)
{
ArrayPool<byte>.Shared.Return(stairBuffers[i]);
}
ArrayPool<byte>.Shared.Return(deflatedBuffer);
Stream.Seek(15, SeekOrigin.Begin);
Write((short)totalLength); // Buffer length
Write((byte)planeCount); // Plane count
}
public void Write(int value)
{
m_PrimBuffer[0] = (byte)(value >> 24);
m_PrimBuffer[1] = (byte)(value >> 16);
m_PrimBuffer[2] = (byte)(value >> 8);
m_PrimBuffer[3] = (byte)value;
Stream.UnderlyingStream.Write(m_PrimBuffer, 0, 4);
}
public void Write(uint value)
{
m_PrimBuffer[0] = (byte)(value >> 24);
m_PrimBuffer[1] = (byte)(value >> 16);
m_PrimBuffer[2] = (byte)(value >> 8);
m_PrimBuffer[3] = (byte)value;
Stream.UnderlyingStream.Write(m_PrimBuffer, 0, 4);
}
public void Write(short value)
{
m_PrimBuffer[0] = (byte)(value >> 8);
m_PrimBuffer[1] = (byte)value;
Stream.UnderlyingStream.Write(m_PrimBuffer, 0, 2);
}
public void Write(byte value)
{
Stream.UnderlyingStream.WriteByte(value);
}
public void Write(byte[] buffer, int offset, int size)
{
Stream.UnderlyingStream.Write(buffer, offset, size);
}
public static void Clear(byte[] buffer, int size)
{
for (var i = 0; i < size; ++i)
{
buffer[i] = 0;
}
} }
} }
} }

View file

@ -8,14 +8,14 @@ public class ArrowPacketTests
[Fact] [Fact]
public void TestCancelArrow() public void TestCancelArrow()
{ {
var expected = new CancelArrow().Compile(); var expected = new CancelArrow().Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendCancelArrow(0, 0, Serial.Zero); ns.SendCancelArrow(0, 0, Serial.Zero);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Theory] [Theory]
[InlineData(0, 0)] [InlineData(0, 0)]
@ -23,14 +23,14 @@ public class ArrowPacketTests
[InlineData(100000, 100000)] [InlineData(100000, 100000)]
public void TestSetArrow(int x, int y) public void TestSetArrow(int x, int y)
{ {
var expected = new SetArrow(x, y).Compile(); var expected = new SetArrow(x, y).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.SendSetArrow(x, y, Serial.Zero); ns.SendSetArrow(x, y, Serial.Zero);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Theory] [Theory]
[InlineData(0, 0)] [InlineData(0, 0)]
@ -38,17 +38,17 @@ public class ArrowPacketTests
[InlineData(100000, 100000)] [InlineData(100000, 100000)]
public void TestCancelArrowHS(int x, int y) public void TestCancelArrowHS(int x, int y)
{ {
Serial serial = (Serial)0x1024; Serial serial = (Serial)0x1024;
var expected = new CancelArrowHS(x, y, serial).Compile(); var expected = new CancelArrowHS(x, y, serial).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.ProtocolChanges = ProtocolChanges.HighSeas; ns.ProtocolChanges = ProtocolChanges.HighSeas;
ns.SendCancelArrow(x, y, serial); ns.SendCancelArrow(x, y, serial);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
[Theory] [Theory]
[InlineData(0, 0)] [InlineData(0, 0)]
@ -56,15 +56,15 @@ public class ArrowPacketTests
[InlineData(100000, 100000)] [InlineData(100000, 100000)]
public void TestSetArrowHS(int x, int y) public void TestSetArrowHS(int x, int y)
{ {
Serial serial = (Serial)0x1024; Serial serial = (Serial)0x1024;
var expected = new SetArrowHS(x, y, serial).Compile(); var expected = new SetArrowHS(x, y, serial).Compile();
var ns = PacketTestUtilities.CreateTestNetState(); var ns = PacketTestUtilities.CreateTestNetState();
ns.ProtocolChanges = ProtocolChanges.HighSeas; ns.ProtocolChanges = ProtocolChanges.HighSeas;
ns.SendSetArrow(x, y, serial); ns.SendSetArrow(x, y, serial);
var result = ns.SendPipe.Reader.AvailableToRead(); var result = ns.SendPipe.Reader.AvailableToRead();
AssertThat.Equal(result, expected); AssertThat.Equal(result, expected);
} }
} }

View file

@ -4,40 +4,40 @@ public sealed class CancelArrow : Packet
{ {
public CancelArrow() : base(0xBA, 6) public CancelArrow() : base(0xBA, 6)
{ {
Stream.Write((byte)0); Stream.Write((byte)0);
Stream.Write((short)-1); Stream.Write((short)-1);
Stream.Write((short)-1); Stream.Write((short)-1);
} }
} }
public sealed class SetArrow : Packet public sealed class SetArrow : Packet
{ {
public SetArrow(int x, int y) : base(0xBA, 6) public SetArrow(int x, int y) : base(0xBA, 6)
{ {
Stream.Write((byte)1); Stream.Write((byte)1);
Stream.Write((short)x); Stream.Write((short)x);
Stream.Write((short)y); Stream.Write((short)y);
} }
} }
public sealed class CancelArrowHS : Packet public sealed class CancelArrowHS : Packet
{ {
public CancelArrowHS(int x, int y, Serial s) : base(0xBA, 10) public CancelArrowHS(int x, int y, Serial s) : base(0xBA, 10)
{ {
Stream.Write((byte)0); Stream.Write((byte)0);
Stream.Write((short)x); Stream.Write((short)x);
Stream.Write((short)y); Stream.Write((short)y);
Stream.Write(s); Stream.Write(s);
} }
} }
public sealed class SetArrowHS : Packet public sealed class SetArrowHS : Packet
{ {
public SetArrowHS(int x, int y, Serial s) : base(0xBA, 10) public SetArrowHS(int x, int y, Serial s) : base(0xBA, 10)
{ {
Stream.Write((byte)1); Stream.Write((byte)1);
Stream.Write((short)x); Stream.Write((short)x);
Stream.Write((short)y); Stream.Write((short)y);
Stream.Write(s); Stream.Write(s);
} }
} }

View file

@ -1,84 +1,83 @@
using System; using System;
using Server.Engines.BuffIcons; using Server.Engines.BuffIcons;
namespace Server.Network namespace Server.Network;
public sealed class AddBuffPacket : Packet
{ {
public sealed class AddBuffPacket : Packet public AddBuffPacket(Serial m, BuffInfo info)
{ : this(
public AddBuffPacket(Serial m, BuffInfo info) m,
: this( info.ID,
m, info.TitleCliloc,
info.ID, info.SecondaryCliloc,
info.TitleCliloc, info.Args,
info.SecondaryCliloc, info.Duration
info.Args,
info.Duration
)
{
}
public AddBuffPacket(
Serial mob, BuffIcon iconID, int titleCliloc, int secondaryCliloc, TextDefinition args,
TimeSpan length
) )
: base(0xDF) {
{
var hasArgs = args != null;
EnsureCapacity(hasArgs ? 48 + args.ToString().Length * 2 : 44);
Stream.Write(mob);
Stream.Write((short)iconID); // ID
Stream.Write((short)0x1); // Type 0 for removal. 1 for add 2 for Data
Stream.Fill(4);
Stream.Write((short)iconID); // ID
Stream.Write((short)0x01); // Type 0 for removal. 1 for add 2 for Data
Stream.Fill(4);
Stream.Write((short)Math.Max(length.TotalSeconds, 0)); // Time in seconds
Stream.Fill(3);
Stream.Write(titleCliloc);
Stream.Write(secondaryCliloc);
if (!hasArgs)
{
// m_Stream.Fill( 2 );
Stream.Fill(10);
}
else
{
Stream.Fill(4);
Stream.Write((short)0x1); // Unknown -> Possibly something saying 'hey, I have more data!'?
Stream.Fill(2);
// m_Stream.WriteLittleUniNull( "\t#1018280" );
Stream.WriteLittleUniNull($"\t{args}");
Stream.Write((short)0x1); // Even more Unknown -> Possibly something saying 'hey, I have more data!'?
Stream.Fill(2);
}
}
} }
public sealed class RemoveBuffPacket : Packet public AddBuffPacket(
Serial mob, BuffIcon iconID, int titleCliloc, int secondaryCliloc, TextDefinition args,
TimeSpan length
)
: base(0xDF)
{ {
public RemoveBuffPacket(Serial mob, BuffInfo info) : this(mob, info.ID) var hasArgs = args != null;
EnsureCapacity(hasArgs ? 48 + args.ToString().Length * 2 : 44);
Stream.Write(mob);
Stream.Write((short)iconID); // ID
Stream.Write((short)0x1); // Type 0 for removal. 1 for add 2 for Data
Stream.Fill(4);
Stream.Write((short)iconID); // ID
Stream.Write((short)0x01); // Type 0 for removal. 1 for add 2 for Data
Stream.Fill(4);
Stream.Write((short)Math.Max(length.TotalSeconds, 0)); // Time in seconds
Stream.Fill(3);
Stream.Write(titleCliloc);
Stream.Write(secondaryCliloc);
if (!hasArgs)
{ {
// m_Stream.Fill( 2 );
Stream.Fill(10);
} }
else
public RemoveBuffPacket(Serial mob, BuffIcon iconID) : base(0xDF)
{ {
EnsureCapacity(13);
Stream.Write(mob);
Stream.Write((short)iconID); // ID
Stream.Write((short)0x0); // Type 0 for removal. 1 for add 2 for Data
Stream.Fill(4); Stream.Fill(4);
Stream.Write((short)0x1); // Unknown -> Possibly something saying 'hey, I have more data!'?
Stream.Fill(2);
// m_Stream.WriteLittleUniNull( "\t#1018280" );
Stream.WriteLittleUniNull($"\t{args}");
Stream.Write((short)0x1); // Even more Unknown -> Possibly something saying 'hey, I have more data!'?
Stream.Fill(2);
} }
} }
} }
public sealed class RemoveBuffPacket : Packet
{
public RemoveBuffPacket(Serial mob, BuffInfo info) : this(mob, info.ID)
{
}
public RemoveBuffPacket(Serial mob, BuffIcon iconID) : base(0xDF)
{
EnsureCapacity(13);
Stream.Write(mob);
Stream.Write((short)iconID); // ID
Stream.Write((short)0x0); // Type 0 for removal. 1 for add 2 for Data
Stream.Fill(4);
}
}

View file

@ -6,7 +6,8 @@ using Xunit;
namespace UOContent.Tests; namespace UOContent.Tests;
public class SkillPacketsTests : IClassFixture<ServerFixture> [Collection("Sequential UOContent Tests")]
public class SkillPacketsTests
{ {
[Theory] [Theory]
[InlineData(SkillName.Alchemy, 0, 1)] [InlineData(SkillName.Alchemy, 0, 1)]

View file

@ -10,7 +10,8 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<ProjectReference Include="..\Application\Application.csproj" /> <ProjectReference Include="..\Server\Server.csproj" />
<ProjectReference Include="..\UOContent\UOContent.csproj" />
<ProjectReference Include="..\Server.Tests\Server.Tests.csproj" /> <ProjectReference Include="..\Server.Tests\Server.Tests.csproj" />
<DataFiles Include="$(SolutionDir)\Distribution\Data\**" /> <DataFiles Include="$(SolutionDir)\Distribution\Data\**" />
</ItemGroup> </ItemGroup>