diff --git a/Distribution/Data/expansion.json b/Distribution/Data/expansion.json index a89041813..c269c8f47 100644 --- a/Distribution/Data/expansion.json +++ b/Distribution/Data/expansion.json @@ -60,7 +60,8 @@ "Shadowguard": false, "TOL": false, "EJ": false - } + }, + "MobileStatusVersion": 3 }, { "Name": "The Second Age", @@ -123,7 +124,8 @@ "Shadowguard": false, "TOL": false, "EJ": false - } + }, + "MobileStatusVersion": 3 }, { "Name": "Renaissance", @@ -186,7 +188,8 @@ "Shadowguard": false, "TOL": false, "EJ": false - } + }, + "MobileStatusVersion": 3 }, { "Name": "Third Dawn", @@ -249,7 +252,8 @@ "Shadowguard": false, "TOL": false, "EJ": false - } + }, + "MobileStatusVersion": 3 }, { "Name": "Blackthorn's Revenge", @@ -312,7 +316,8 @@ "Shadowguard": false, "TOL": false, "EJ": false - } + }, + "MobileStatusVersion": 3 }, { "Name": "Age of Shadows", @@ -375,7 +380,8 @@ "Shadowguard": false, "TOL": false, "EJ": false - } + }, + "MobileStatusVersion": 6 }, { "Name": "Samurai Empire", @@ -438,7 +444,8 @@ "Shadowguard": false, "TOL": false, "EJ": false - } + }, + "MobileStatusVersion": 6 }, { "Name": "Mondain's Legacy", @@ -501,7 +508,8 @@ "Shadowguard": false, "TOL": false, "EJ": false - } + }, + "MobileStatusVersion": 6 }, { "Name": "Stygian Abyss", @@ -564,7 +572,8 @@ "Shadowguard": false, "TOL": false, "EJ": false - } + }, + "MobileStatusVersion": 6 }, { "Name": "High Seas", @@ -627,7 +636,8 @@ "Shadowguard": false, "TOL": false, "EJ": false - } + }, + "MobileStatusVersion": 6 }, { "Name": "Time of Legends", @@ -690,7 +700,8 @@ "Shadowguard": true, "TOL": true, "EJ": false - } + }, + "MobileStatusVersion": 6 }, { "Name": "Endless Journey", @@ -753,6 +764,7 @@ "Shadowguard": true, "TOL": true, "EJ": true - } + }, + "MobileStatusVersion": 6 } ] diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs index 08dae2a70..e01150cb0 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MobilePacketTests.cs @@ -232,11 +232,23 @@ namespace Server.Tests.Network } [Theory] - [InlineData(ProtocolChanges.Version70610)] - [InlineData(ProtocolChanges.Version400a)] - [InlineData(ProtocolChanges.Version502b)] - public void TestMobileStatusExtendedSelf(ProtocolChanges changes) + [InlineData(ProtocolChanges.Version70610, "7.0.61.0", ClientFlags.TerMur, Expansion.HS, 6)] + [InlineData(ProtocolChanges.Version400a, "4.0.0a", ClientFlags.Malas, Expansion.AOS, 4)] + [InlineData(ProtocolChanges.Version502b, "5.0.2b", ClientFlags.Malas, Expansion.ML, 5)] + public void TestMobileStatusExtendedSelf( + ProtocolChanges changes, + string version, + ClientFlags clientFlags, + Expansion expansion, + int mobileStatusVersion + ) { + var expansionInfo = ExpansionInfo.GetInfo(Core.Expansion); + var oldExpansion = Core.Expansion; + var oldVersion = expansionInfo.MobileStatusVersion; + Core.Expansion = expansion; + ExpansionInfo.GetInfo(Core.Expansion).MobileStatusVersion = mobileStatusVersion; + var m = new Mobile((Serial)0x1) { Name = "Random Mobile 1" }; m.DefaultMobileInit(); m.Str = 50; @@ -248,12 +260,16 @@ namespace Server.Tests.Network var ns = PacketTestUtilities.CreateTestNetState(); ns.ProtocolChanges = changes; + ns.Version = new ClientVersion(version); + ns.Flags = clientFlags; var expected = new MobileStatusExtended(m, ns).Compile(); ns.SendMobileStatus(m, m); var result = ns.SendPipe.Reader.TryRead(); AssertThat.Equal(result.Buffer[0].AsSpan(0), expected); + Core.Expansion = oldExpansion; + expansionInfo.MobileStatusVersion = oldVersion; } [Theory] diff --git a/Projects/Server/ExpansionInfo.cs b/Projects/Server/ExpansionInfo.cs index 909658add..3bd7fcf81 100644 --- a/Projects/Server/ExpansionInfo.cs +++ b/Projects/Server/ExpansionInfo.cs @@ -19,289 +19,298 @@ using System.IO; using System.Text.Json.Serialization; using Server.Json; -namespace Server +namespace Server; + +public enum Expansion { - public enum Expansion - { - None, - T2A, - UOR, - UOTD, - LBR, - AOS, - SE, - ML, - SA, - HS, - TOL, - EJ - } - - [Flags] - public enum ClientFlags - { - None = 0x00000000, - Felucca = 0x00000001, - Trammel = 0x00000002, - Ilshenar = 0x00000004, - Malas = 0x00000008, - Tokuno = 0x00000010, - TerMur = 0x00000020, - Unk1 = 0x00000040, - Unk2 = 0x00000080, - UOTD = 0x00000100 - } - - [Flags] - public enum FeatureFlags - { - None = 0x00000000, - T2A = 0x00000001, - UOR = 0x00000002, // In later clients, the T2A/UOR flags are negative feature flags to disable body replacement of Pre-AOS graphics. - UOTD = 0x00000004, - LBR = 0x00000008, - AOS = 0x00000010, - SixthCharacterSlot = 0x00000020, - SE = 0x00000040, - ML = 0x00000080, - EighthAge = 0x00000100, - NinthAge = 0x00000200, // Crystal/Shadow Custom House Tiles - TenthAge = 0x00000400, - IncreasedStorage = 0x00000800, // Increased Housing/Bank Storage - SeventhCharacterSlot = 0x00001000, - RoleplayFaces = 0x00002000, - TrialAccount = 0x00004000, - LiveAccount = 0x00008000, - SA = 0x00010000, - HS = 0x00020000, - Gothic = 0x00040000, - Rustic = 0x00080000, - Jungle = 0x00100000, - Shadowguard = 0x00200000, - TOL = 0x00400000, - EJ = 0x00800000, - - ExpansionNone = None, - ExpansionT2A = T2A, - ExpansionUOR = ExpansionT2A | UOR, - ExpansionUOTD = ExpansionUOR | UOTD, - ExpansionLBR = ExpansionUOTD | LBR, - ExpansionAOS = LBR | AOS | LiveAccount, - ExpansionSE = ExpansionAOS | SE, - ExpansionML = ExpansionSE | ML | NinthAge, - ExpansionSA = ExpansionML | SA | Gothic | Rustic, - ExpansionHS = ExpansionSA | HS, - ExpansionTOL = ExpansionHS | TOL | Jungle | Shadowguard, - ExpansionEJ = ExpansionTOL | EJ - } - - [Flags] - public enum CharacterListFlags - { - None = 0x00000000, - Unk1 = 0x00000001, - OverwriteConfigButton = 0x00000002, - OneCharacterSlot = 0x00000004, - ContextMenus = 0x00000008, - SlotLimit = 0x00000010, - AOS = 0x00000020, - SixthCharacterSlot = 0x00000040, - SE = 0x00000080, - ML = 0x00000100, - Unk2 = 0x00000200, - UO3DClientType = 0x00000400, - Unk3 = 0x00000800, - SeventhCharacterSlot = 0x00001000, - Unk4 = 0x00002000, - NewMovementSystem = 0x00004000, // Doesn't seem to be used on OSI - NewFeluccaAreas = 0x00008000, - - ExpansionNone = ContextMenus, - ExpansionT2A = ContextMenus, - ExpansionUOR = ContextMenus, - ExpansionUOTD = ContextMenus, - ExpansionLBR = ContextMenus, - ExpansionAOS = ContextMenus | AOS, - ExpansionSE = ExpansionAOS | SE, - ExpansionML = ExpansionSE | ML, - ExpansionSA = ExpansionML, - ExpansionHS = ExpansionSA, - ExpansionTOL = ExpansionHS, - ExpansionEJ = ExpansionTOL - } - - [Flags] - public enum HousingFlags - { - None = 0x0, - AOS = 0x10, - SE = 0x40, - ML = 0x80, - Crystal = 0x200, - SA = 0x10000, - HS = 0x20000, - Gothic = 0x40000, - Rustic = 0x80000, - Jungle = 0x100000, - Shadowguard = 0x200000, - TOL = 0x400000, - EJ = 0x800000, - - HousingAOS = AOS, - HousingSE = HousingAOS | SE, - HousingML = HousingSE | ML | Crystal, - HousingSA = HousingML | SA | Gothic | Rustic, - HousingHS = HousingSA | HS, - HousingTOL = HousingHS | TOL | Jungle | Shadowguard, - HousingEJ = HousingTOL | EJ - } - - public class ExpansionInfo - { - public static bool ForceOldAnimations { get; private set; } - public static void Configure() - { - ForceOldAnimations = ServerConfiguration.GetSetting("expansion.forceOldAnimations", false); - } - - public static string GetEraFolder(string parentDirectory) - { - var expansion = Core.Expansion; - var folders = Directory.GetDirectories( - parentDirectory, - "*", - new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive } - ); - - while (expansion-- >= 0) - { - foreach (var folder in folders) - { - var di = new DirectoryInfo(folder); - if (di.Name.InsensitiveEquals(expansion.ToString())) - { - return folder; - } - } - } - - return null; - } - - static ExpansionInfo() - { - var path = Path.Combine(Core.BaseDirectory, "Data/expansion.json"); - var expansions = JsonConfig.Deserialize>(path); - - Table = new ExpansionInfo[expansions.Count]; - - for (var i = 0; i < expansions.Count; i++) - { - var expansion = expansions[i]; - if (expansion.ClientVersion != null) - { - Table[i] = new ExpansionInfo( - i, - expansion.Name, - expansion.ClientVersion, - expansion.FeatureFlags, - expansion.CharacterListFlags, - expansion.HousingFlags - ); - } - else - { - Table[i] = new ExpansionInfo( - i, - expansion.Name, - expansion.ClientFlags ?? ClientFlags.None, - expansion.FeatureFlags, - expansion.CharacterListFlags, - expansion.HousingFlags - ); - } - } - } - - public ExpansionInfo( - int id, - string name, - ClientFlags clientFlags, - FeatureFlags supportedFeatures, - CharacterListFlags charListFlags, - HousingFlags customHousingFlag - ) : this(id, name, supportedFeatures, charListFlags, customHousingFlag) => ClientFlags = clientFlags; - - public ExpansionInfo( - int id, - string name, - ClientVersion requiredClient, - FeatureFlags supportedFeatures, - CharacterListFlags charListFlags, - HousingFlags customHousingFlag - ) : this(id, name, supportedFeatures, charListFlags, customHousingFlag) => RequiredClient = requiredClient; - - private ExpansionInfo( - int id, - string name, - FeatureFlags supportedFeatures, - CharacterListFlags charListFlags, - HousingFlags customHousingFlag - ) - { - ID = id; - Name = name; - - SupportedFeatures = supportedFeatures; - CharacterListFlags = charListFlags; - CustomHousingFlag = customHousingFlag; - } - - public static ExpansionInfo CoreExpansion => GetInfo(Core.Expansion); - - public static ExpansionInfo[] Table { get; } - - public int ID { get; } - public string Name { get; set; } - - public ClientFlags ClientFlags { get; set; } - public FeatureFlags SupportedFeatures { get; set; } - public CharacterListFlags CharacterListFlags { get; set; } - public ClientVersion RequiredClient { get; set; } - public HousingFlags CustomHousingFlag { get; set; } - - public static ExpansionInfo GetInfo(Expansion ex) => GetInfo((int)ex); - - public static ExpansionInfo GetInfo(int ex) - { - var v = ex; - - if (v < 0 || v >= Table.Length) - { - v = 0; - } - - return Table[v]; - } - - public override string ToString() => Name; - } - - public record ExpansionConfig - { - public string Name { get; init; } - - public ClientVersion? ClientVersion { get; init; } - - public ClientFlags? ClientFlags { get; init; } - - [JsonConverter(typeof(FlagsConverter))] - public FeatureFlags FeatureFlags { get; init; } - - [JsonConverter(typeof(FlagsConverter))] - public CharacterListFlags CharacterListFlags { get; init; } - - [JsonConverter(typeof(FlagsConverter))] - public HousingFlags HousingFlags { get; init; } - } + None, + T2A, + UOR, + UOTD, + LBR, + AOS, + SE, + ML, + SA, + HS, + TOL, + EJ +} + +[Flags] +public enum ClientFlags +{ + None = 0x00000000, + Felucca = 0x00000001, + Trammel = 0x00000002, + Ilshenar = 0x00000004, + Malas = 0x00000008, + Tokuno = 0x00000010, + TerMur = 0x00000020, + Unk1 = 0x00000040, + Unk2 = 0x00000080, + UOTD = 0x00000100 +} + +[Flags] +public enum FeatureFlags +{ + None = 0x00000000, + T2A = 0x00000001, + UOR = 0x00000002, // In later clients, the T2A/UOR flags are negative feature flags to disable body replacement of Pre-AOS graphics. + UOTD = 0x00000004, + LBR = 0x00000008, + AOS = 0x00000010, + SixthCharacterSlot = 0x00000020, + SE = 0x00000040, + ML = 0x00000080, + EighthAge = 0x00000100, + NinthAge = 0x00000200, // Crystal/Shadow Custom House Tiles + TenthAge = 0x00000400, + IncreasedStorage = 0x00000800, // Increased Housing/Bank Storage + SeventhCharacterSlot = 0x00001000, + RoleplayFaces = 0x00002000, + TrialAccount = 0x00004000, + LiveAccount = 0x00008000, + SA = 0x00010000, + HS = 0x00020000, + Gothic = 0x00040000, + Rustic = 0x00080000, + Jungle = 0x00100000, + Shadowguard = 0x00200000, + TOL = 0x00400000, + EJ = 0x00800000, + + ExpansionNone = None, + ExpansionT2A = T2A, + ExpansionUOR = ExpansionT2A | UOR, + ExpansionUOTD = ExpansionUOR | UOTD, + ExpansionLBR = ExpansionUOTD | LBR, + ExpansionAOS = LBR | AOS | LiveAccount, + ExpansionSE = ExpansionAOS | SE, + ExpansionML = ExpansionSE | ML | NinthAge, + ExpansionSA = ExpansionML | SA | Gothic | Rustic, + ExpansionHS = ExpansionSA | HS, + ExpansionTOL = ExpansionHS | TOL | Jungle | Shadowguard, + ExpansionEJ = ExpansionTOL | EJ +} + +[Flags] +public enum CharacterListFlags +{ + None = 0x00000000, + Unk1 = 0x00000001, + OverwriteConfigButton = 0x00000002, + OneCharacterSlot = 0x00000004, + ContextMenus = 0x00000008, + SlotLimit = 0x00000010, + AOS = 0x00000020, + SixthCharacterSlot = 0x00000040, + SE = 0x00000080, + ML = 0x00000100, + Unk2 = 0x00000200, + UO3DClientType = 0x00000400, + Unk3 = 0x00000800, + SeventhCharacterSlot = 0x00001000, + Unk4 = 0x00002000, + NewMovementSystem = 0x00004000, // Doesn't seem to be used on OSI + NewFeluccaAreas = 0x00008000, + + ExpansionNone = ContextMenus, + ExpansionT2A = ContextMenus, + ExpansionUOR = ContextMenus, + ExpansionUOTD = ContextMenus, + ExpansionLBR = ContextMenus, + ExpansionAOS = ContextMenus | AOS, + ExpansionSE = ExpansionAOS | SE, + ExpansionML = ExpansionSE | ML, + ExpansionSA = ExpansionML, + ExpansionHS = ExpansionSA, + ExpansionTOL = ExpansionHS, + ExpansionEJ = ExpansionTOL +} + +[Flags] +public enum HousingFlags +{ + None = 0x0, + AOS = 0x10, + SE = 0x40, + ML = 0x80, + Crystal = 0x200, + SA = 0x10000, + HS = 0x20000, + Gothic = 0x40000, + Rustic = 0x80000, + Jungle = 0x100000, + Shadowguard = 0x200000, + TOL = 0x400000, + EJ = 0x800000, + + HousingAOS = AOS, + HousingSE = HousingAOS | SE, + HousingML = HousingSE | ML | Crystal, + HousingSA = HousingML | SA | Gothic | Rustic, + HousingHS = HousingSA | HS, + HousingTOL = HousingHS | TOL | Jungle | Shadowguard, + HousingEJ = HousingTOL | EJ +} + +public class ExpansionInfo +{ + public static bool ForceOldAnimations { get; private set; } + public static void Configure() + { + ForceOldAnimations = ServerConfiguration.GetSetting("expansion.forceOldAnimations", false); + } + + public static string GetEraFolder(string parentDirectory) + { + var expansion = Core.Expansion; + var folders = Directory.GetDirectories( + parentDirectory, + "*", + new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive } + ); + + while (expansion-- >= 0) + { + foreach (var folder in folders) + { + var di = new DirectoryInfo(folder); + if (di.Name.InsensitiveEquals(expansion.ToString())) + { + return folder; + } + } + } + + return null; + } + + static ExpansionInfo() + { + var path = Path.Combine(Core.BaseDirectory, "Data/expansion.json"); + var expansions = JsonConfig.Deserialize>(path); + + Table = new ExpansionInfo[expansions.Count]; + + for (var i = 0; i < expansions.Count; i++) + { + var expansion = expansions[i]; + if (expansion.ClientVersion != null) + { + Table[i] = new ExpansionInfo( + i, + expansion.Name, + expansion.ClientVersion, + expansion.FeatureFlags, + expansion.CharacterListFlags, + expansion.HousingFlags, + expansion.MobileStatusVersion + ); + } + else + { + Table[i] = new ExpansionInfo( + i, + expansion.Name, + expansion.ClientFlags ?? ClientFlags.None, + expansion.FeatureFlags, + expansion.CharacterListFlags, + expansion.HousingFlags, + expansion.MobileStatusVersion + ); + } + } + } + + public ExpansionInfo( + int id, + string name, + ClientFlags clientFlags, + FeatureFlags supportedFeatures, + CharacterListFlags charListFlags, + HousingFlags customHousingFlag, + int mobileStatusVersion + ) : this(id, name, supportedFeatures, charListFlags, customHousingFlag, mobileStatusVersion) => + ClientFlags = clientFlags; + + public ExpansionInfo( + int id, + string name, + ClientVersion requiredClient, + FeatureFlags supportedFeatures, + CharacterListFlags charListFlags, + HousingFlags customHousingFlag, + int mobileStatusVersion + ) : this(id, name, supportedFeatures, charListFlags, customHousingFlag, mobileStatusVersion) => + RequiredClient = requiredClient; + + private ExpansionInfo( + int id, + string name, + FeatureFlags supportedFeatures, + CharacterListFlags charListFlags, + HousingFlags customHousingFlag, + int mobileStatusVersion + ) + { + ID = id; + Name = name; + + SupportedFeatures = supportedFeatures; + CharacterListFlags = charListFlags; + CustomHousingFlag = customHousingFlag; + MobileStatusVersion = mobileStatusVersion; + } + + public static ExpansionInfo CoreExpansion => GetInfo(Core.Expansion); + + public static ExpansionInfo[] Table { get; } + + public int ID { get; } + public string Name { get; set; } + public ClientFlags ClientFlags { get; set; } + public FeatureFlags SupportedFeatures { get; set; } + public CharacterListFlags CharacterListFlags { get; set; } + public ClientVersion RequiredClient { get; set; } + public HousingFlags CustomHousingFlag { get; set; } + public int MobileStatusVersion { get; set; } + + public static ExpansionInfo GetInfo(Expansion ex) => GetInfo((int)ex); + + public static ExpansionInfo GetInfo(int ex) + { + var v = ex; + + if (v < 0 || v >= Table.Length) + { + v = 0; + } + + return Table[v]; + } + + public override string ToString() => Name; +} + +public record ExpansionConfig +{ + public string Name { get; init; } + + public ClientVersion? ClientVersion { get; init; } + + public ClientFlags? ClientFlags { get; init; } + + [JsonConverter(typeof(FlagsConverter))] + public FeatureFlags FeatureFlags { get; init; } + + [JsonConverter(typeof(FlagsConverter))] + public CharacterListFlags CharacterListFlags { get; init; } + + [JsonConverter(typeof(FlagsConverter))] + public HousingFlags HousingFlags { get; init; } + + public int MobileStatusVersion { get; set; } } diff --git a/Projects/Server/Network/Packets/OutgoingMobilePackets.cs b/Projects/Server/Network/Packets/OutgoingMobilePackets.cs index 271af7d97..876ce54ea 100644 --- a/Projects/Server/Network/Packets/OutgoingMobilePackets.cs +++ b/Projects/Server/Network/Packets/OutgoingMobilePackets.cs @@ -39,13 +39,6 @@ public static class OutgoingMobilePackets public const int MobileStatusMLLength = 91; public const int MobileStatusHSLength = 121; - public static bool ExtendedStatus { get; private set; } = true; - - public static void Configure() - { - ExtendedStatus = ServerConfiguration.GetSetting("client.showExtendedStatus", true); - } - public static void CreateBondedStatus(Span buffer, Serial serial, bool bonded) { if (buffer[0] != 0) @@ -467,25 +460,31 @@ public static class OutgoingMobilePackets version = 0; length = MobileStatusCompactLength; } - else if (ExtendedStatus && ns.ExtendedStatus) - { - version = 6; - length = MobileStatusHSLength; - } - else if (Core.ML && ns.SupportsExpansion(Expansion.ML)) - { - version = 5; - length = MobileStatusMLLength; - } - else if (Core.AOS) - { - version = 4; - length = MobileStatusAOSLength; - } else { - version = 3; - length = MobileStatusLength; + var maxVersion = ExpansionInfo.CoreExpansion.MobileStatusVersion; + var nsExpansion = (Expansion)ns.ExpansionInfo.ID; + + if (maxVersion >= 6 && nsExpansion >= Expansion.HS && ns.ExtendedStatus) + { + version = 6; + length = MobileStatusHSLength; + } + else if (maxVersion >= 5 && nsExpansion >= Expansion.ML) + { + version = 5; + length = MobileStatusMLLength; + } + else if (maxVersion >= 4 && nsExpansion >= Expansion.AOS) + { + version = 4; + length = MobileStatusAOSLength; + } + else + { + version = 3; + length = MobileStatusLength; + } } Span buffer = stackalloc byte[length]; @@ -563,6 +562,8 @@ public static class OutgoingMobilePackets if (version >= 6) { + // TODO: Once the new statuses are added, the length should be capped by expansion. + // This will allow newer clients to see AOS stats, but not newer ones. for (var i = 0; i < 15; ++i) { writer.Write((short)beheld.GetAOSStatus(i)); diff --git a/Projects/UOContent/Assistants/AssistantProtocol.cs b/Projects/UOContent/Assistants/AssistantProtocol.cs index 90bad5eeb..e92c64243 100644 --- a/Projects/UOContent/Assistants/AssistantProtocol.cs +++ b/Projects/UOContent/Assistants/AssistantProtocol.cs @@ -4,6 +4,7 @@ public static class AssistantProtocol { private static PacketHandler[] _handlers; + [CallPriority(10)] public static void Configure() { _handlers = ProtocolExtensions.Register(new AssistantsProtocolInfo());