diff --git a/Source/ACE.Common/ACE.Common.csproj b/Source/ACE.Common/ACE.Common.csproj index 7d29e76029..72c50ab51f 100644 --- a/Source/ACE.Common/ACE.Common.csproj +++ b/Source/ACE.Common/ACE.Common.csproj @@ -18,6 +18,8 @@ + + diff --git a/Source/ACE.Common/ConfigManager.cs b/Source/ACE.Common/ConfigManager.cs index de0630cf62..8e9f2e8ddd 100644 --- a/Source/ACE.Common/ConfigManager.cs +++ b/Source/ACE.Common/ConfigManager.cs @@ -61,7 +61,7 @@ public static void Initialize(string path = @"Config.js") var fileText = File.ReadAllText(pathToUse); - Config = JsonSerializer.Deserialize(fileText, new JsonSerializerOptions { ReadCommentHandling = JsonCommentHandling.Skip, NumberHandling = JsonNumberHandling.AllowReadingFromString }); + Config = JsonSerializer.Deserialize(fileText, SerializerOptions); } catch (Exception exception) { @@ -72,5 +72,13 @@ public static void Initialize(string path = @"Config.js") throw; } } + + public static JsonSerializerOptions SerializerOptions = new JsonSerializerOptions + { + AllowTrailingCommas = true, + NumberHandling = JsonNumberHandling.AllowReadingFromString, + ReadCommentHandling = JsonCommentHandling.Skip, + WriteIndented = true + }; } } diff --git a/Source/ACE.Common/Cryptography/BCryptProvider.cs b/Source/ACE.Common/Cryptography/BCryptProvider.cs index abee3b94ca..b173904c94 100644 --- a/Source/ACE.Common/Cryptography/BCryptProvider.cs +++ b/Source/ACE.Common/Cryptography/BCryptProvider.cs @@ -4,7 +4,12 @@ public static class BCryptProvider { public static string HashPassword(string input, int workFactor = 10) { - return BCrypt.Net.BCrypt.HashPassword(input, workFactor, BCrypt.Net.SaltRevision.Revision2Y); + // Force BCrypt.Net-Next to use 2y instead of the default 2a + // The older bcrypt package ACE used (BCrypt.Net-Core) defaultd to 2y + // Reference: https://stackoverflow.com/questions/49878948/hashing-password-with-2y-identifier/75114685 + string salt = BCrypt.Net.BCrypt.GenerateSalt(workFactor, 'y'); + + return BCrypt.Net.BCrypt.HashPassword(input, salt); } public static bool Verify(string text, string hash) @@ -14,7 +19,12 @@ public static bool Verify(string text, string hash) public static int GetPasswordWorkFactor(string hash) { - return BCrypt.Net.BCrypt.GetPasswordWorkFactor(hash); + var hashInformation = BCrypt.Net.BCrypt.InterrogateHash(hash); + + if (int.TryParse(hashInformation.WorkFactor, out var workFactor)) + return workFactor; + + return 0; } } } diff --git a/Source/ACE.Common/DerethDateTime.cs b/Source/ACE.Common/DerethDateTime.cs index 89a80e5937..28131eec85 100644 --- a/Source/ACE.Common/DerethDateTime.cs +++ b/Source/ACE.Common/DerethDateTime.cs @@ -1,7 +1,5 @@ using System; -using TimeZoneConverter; - namespace ACE.Common { /// @@ -32,7 +30,7 @@ public class DerethDateTime private static DateTime dayOne_RealWorld = new DateTime(1999, 4, 2, 00, 00, 00); private static DateTime retailDayOne_RealWorld = new DateTime(1999, 11, 2, 00, 00, 00); - private static DateTime retailDayLast_RealWorld = new DateTime(2017, 1, 31, 12, 00, 00); + private static DateTime retailDayLast_RealWorld = new DateTime(2017, 1, 31, 12, 00, 00); // Eastern Standard Time /// /// A instance set to the Derethian Date, Portal Year and Time when the worlds first opened. @@ -1061,6 +1059,6 @@ public static DerethDateTime ConvertRealWorldToLoreDateTime(DateTime dateTime) /// /// Converts the object to a new object set to EMU Standard Sync Time. /// - public static DerethDateTime UtcNowToEMUTime => new DerethDateTime((DateTime.UtcNow - TimeZoneInfo.ConvertTimeToUtc(retailDayLast_RealWorld, TZConvert.GetTimeZoneInfo("Eastern Standard Time"))).TotalSeconds); + public static DerethDateTime UtcNowToEMUTime => new DerethDateTime((DateTime.UtcNow.AddHours(-5) - retailDayLast_RealWorld).TotalSeconds); // -5 is Eastern Standard Time UTC Offset } } diff --git a/Source/ACE.DatLoader/ACE.DatLoader.csproj b/Source/ACE.DatLoader/ACE.DatLoader.csproj index a1b7cb286f..800aed779a 100644 --- a/Source/ACE.DatLoader/ACE.DatLoader.csproj +++ b/Source/ACE.DatLoader/ACE.DatLoader.csproj @@ -25,6 +25,8 @@ + + diff --git a/Source/ACE.Database/ACE.Database.csproj b/Source/ACE.Database/ACE.Database.csproj index df1d6757d8..513e28068f 100644 --- a/Source/ACE.Database/ACE.Database.csproj +++ b/Source/ACE.Database/ACE.Database.csproj @@ -20,13 +20,6 @@ - - - - - - - diff --git a/Source/ACE.Server/ACE.Server.csproj b/Source/ACE.Server/ACE.Server.csproj index b5af2d8618..9578da1250 100644 --- a/Source/ACE.Server/ACE.Server.csproj +++ b/Source/ACE.Server/ACE.Server.csproj @@ -234,11 +234,10 @@ - + - diff --git a/Source/ACE.Server/Command/Handlers/DeveloperCommands.cs b/Source/ACE.Server/Command/Handlers/DeveloperCommands.cs index c47185f60b..2f7c1810ef 100644 --- a/Source/ACE.Server/Command/Handlers/DeveloperCommands.cs +++ b/Source/ACE.Server/Command/Handlers/DeveloperCommands.cs @@ -408,6 +408,7 @@ public static void MoveTo(Session session, params string[] parameters) [CommandHandler("barbershop", AccessLevel.Developer, CommandHandlerFlag.RequiresWorld, "Displays the barber ui")] public static void BarberShop(Session session, params string[] parameters) { + session.Player.BarberActive = true; session.Network.EnqueueSend(new GameEventStartBarber(session)); } diff --git a/Source/ACE.Server/Factories/StarterGearFactory.cs b/Source/ACE.Server/Factories/StarterGearFactory.cs index d248cb70af..4c2a8a2f68 100644 --- a/Source/ACE.Server/Factories/StarterGearFactory.cs +++ b/Source/ACE.Server/Factories/StarterGearFactory.cs @@ -2,8 +2,8 @@ using System.IO; using System.Reflection; using System.Text.Json; -using System.Text.Json.Serialization; +using ACE.Common; using ACE.Server.Entity; using log4net; @@ -36,7 +36,7 @@ private static StarterGearConfiguration LoadConfigFromResource() { var starterGearText = File.ReadAllText(starterGearFile); - config = JsonSerializer.Deserialize(starterGearText, new JsonSerializerOptions { ReadCommentHandling = JsonCommentHandling.Skip, NumberHandling = JsonNumberHandling.AllowReadingFromString }); + config = JsonSerializer.Deserialize(starterGearText, ConfigManager.SerializerOptions); return config; } diff --git a/Source/ACE.Server/Mods/ModContainer.cs b/Source/ACE.Server/Mods/ModContainer.cs index 11cbd8ad9f..6aeb25ceb6 100644 --- a/Source/ACE.Server/Mods/ModContainer.cs +++ b/Source/ACE.Server/Mods/ModContainer.cs @@ -1,3 +1,5 @@ +using ACE.Common; + using log4net; using McMaster.NETCore.Plugins; @@ -204,7 +206,7 @@ private bool TryCreateModInstance() public void SaveMetadata() { - var json = JsonSerializer.Serialize(Meta, new JsonSerializerOptions { WriteIndented = true }); + var json = JsonSerializer.Serialize(Meta, ConfigManager.SerializerOptions); var info = new FileInfo(MetadataPath); if (!info.RetryWrite(json)) diff --git a/Source/ACE.Server/Mods/ModManager.cs b/Source/ACE.Server/Mods/ModManager.cs index fe9316de31..45607937d5 100644 --- a/Source/ACE.Server/Mods/ModManager.cs +++ b/Source/ACE.Server/Mods/ModManager.cs @@ -1,3 +1,4 @@ +using ACE.Common; using ACE.Server.Managers; using ACE.Server.WorldObjects; @@ -9,7 +10,6 @@ using System.Linq; using System.Text; using System.Text.Json; -using System.Text.Json.Serialization; namespace ACE.Server.Mods { @@ -144,7 +144,7 @@ private static bool TryLoadModContainer(string metadataPath, out ModContainer co try { - var metadata = JsonSerializer.Deserialize(File.ReadAllText(metadataPath), new JsonSerializerOptions { ReadCommentHandling = JsonCommentHandling.Skip, NumberHandling = JsonNumberHandling.AllowReadingFromString }); + var metadata = JsonSerializer.Deserialize(File.ReadAllText(metadataPath), ConfigManager.SerializerOptions); container = new ModContainer() { diff --git a/Source/ACE.Server/Program_Setup.cs b/Source/ACE.Server/Program_Setup.cs index a483d42851..4735d8f699 100644 --- a/Source/ACE.Server/Program_Setup.cs +++ b/Source/ACE.Server/Program_Setup.cs @@ -3,7 +3,6 @@ using System.IO.Compression; using System.Linq; using System.Text.Json; -using System.Text.Json.Serialization; using System.Threading; using ACE.Common; @@ -38,7 +37,7 @@ private static void DoOutOfBoxSetup(string configFile) } var fileText = File.ReadAllText(configFile); - config = JsonSerializer.Deserialize(fileText, new JsonSerializerOptions { ReadCommentHandling = JsonCommentHandling.Skip, NumberHandling = JsonNumberHandling.AllowReadingFromString }); + config = JsonSerializer.Deserialize(fileText, ConfigManager.SerializerOptions); } Console.WriteLine("Performing setup for ACEmulator..."); @@ -260,7 +259,7 @@ private static void DoOutOfBoxSetup(string configFile) Console.WriteLine("commiting configuration to disk..."); - var jsonString = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true }); + var jsonString = JsonSerializer.Serialize(config, ConfigManager.SerializerOptions); File.WriteAllText(configFile, jsonString); diff --git a/Source/ACE.Server/ServerBuildInfo_Dynamic.cs b/Source/ACE.Server/ServerBuildInfo_Dynamic.cs index 09cf51c059..79fc43e845 100644 --- a/Source/ACE.Server/ServerBuildInfo_Dynamic.cs +++ b/Source/ACE.Server/ServerBuildInfo_Dynamic.cs @@ -4,17 +4,17 @@ namespace ACE.Server public static partial class ServerBuildInfo { public static string Branch = "master"; - public static string Commit = "7dd702ace2dc4ed2c3da5d221bd28a8a10a37db4"; + public static string Commit = "74b57b215c600e480657fc0bbeaeab9f568920d6"; - public static string Version = "1.56"; - public static string Build = "4466"; + public static string Version = "1.57"; + public static string Build = "4475"; - public static int BuildYear = 2023; - public static int BuildMonth = 12; - public static int BuildDay = 29; - public static int BuildHour = 17; - public static int BuildMinute = 47; - public static int BuildSecond = 46; + public static int BuildYear = 2024; + public static int BuildMonth = 01; + public static int BuildDay = 11; + public static int BuildHour = 00; + public static int BuildMinute = 58; + public static int BuildSecond = 45; } } diff --git a/Source/ACE.Server/WorldObjects/Player.cs b/Source/ACE.Server/WorldObjects/Player.cs index 0bdebf106f..3c88c29438 100644 --- a/Source/ACE.Server/WorldObjects/Player.cs +++ b/Source/ACE.Server/WorldObjects/Player.cs @@ -675,71 +675,7 @@ public void SendAutonomousPosition() - public void HandleActionFinishBarber(ClientMessage message) - { - // Read the payload sent from the client... - PaletteBaseId = message.Payload.ReadUInt32(); - HeadObjectDID = message.Payload.ReadUInt32(); - Character.HairTexture = message.Payload.ReadUInt32(); - Character.DefaultHairTexture = message.Payload.ReadUInt32(); - CharacterChangesDetected = true; - EyesTextureDID = message.Payload.ReadUInt32(); - DefaultEyesTextureDID = message.Payload.ReadUInt32(); - NoseTextureDID = message.Payload.ReadUInt32(); - DefaultNoseTextureDID = message.Payload.ReadUInt32(); - MouthTextureDID = message.Payload.ReadUInt32(); - DefaultMouthTextureDID = message.Payload.ReadUInt32(); - SkinPaletteDID = message.Payload.ReadUInt32(); - HairPaletteDID = message.Payload.ReadUInt32(); - EyesPaletteDID = message.Payload.ReadUInt32(); - SetupTableId = message.Payload.ReadUInt32(); - - uint option_bound = message.Payload.ReadUInt32(); // Supress Levitation - Empyrean Only - uint option_unk = message.Payload.ReadUInt32(); // Unknown - Possibly set aside for future use? - - // Check if Character is Empyrean, and if we need to set/change/send new motion table - if (Heritage == 9) - { - // These are the motion tables for Empyrean float and not-float (one for each gender). They are hard-coded into the client. - const uint EmpyreanMaleFloatMotionDID = 0x0900020Bu; - const uint EmpyreanFemaleFloatMotionDID = 0x0900020Au; - const uint EmpyreanMaleMotionDID = 0x0900020Eu; - const uint EmpyreanFemaleMotionDID = 0x0900020Du; - - // Check for the Levitation option for Empyrean. Shadow crown and Undead flames are handled by client. - if (Gender == 1) // Male - { - if (option_bound == 1 && MotionTableId != EmpyreanMaleMotionDID) - { - MotionTableId = EmpyreanMaleMotionDID; - Session.Network.EnqueueSend(new GameMessagePrivateUpdateDataID(this, PropertyDataId.MotionTable, (uint)MotionTableId)); - } - else if (option_bound == 0 && MotionTableId != EmpyreanMaleFloatMotionDID) - { - MotionTableId = EmpyreanMaleFloatMotionDID; - Session.Network.EnqueueSend(new GameMessagePrivateUpdateDataID(this, PropertyDataId.MotionTable, (uint)MotionTableId)); - } - } - else // Female - { - if (option_bound == 1 && MotionTableId != EmpyreanFemaleMotionDID) - { - MotionTableId = EmpyreanFemaleMotionDID; - Session.Network.EnqueueSend(new GameMessagePrivateUpdateDataID(this, PropertyDataId.MotionTable, (uint)MotionTableId)); - } - else if (option_bound == 0 && MotionTableId != EmpyreanFemaleFloatMotionDID) - { - MotionTableId = EmpyreanFemaleFloatMotionDID; - Session.Network.EnqueueSend(new GameMessagePrivateUpdateDataID(this, PropertyDataId.MotionTable, (uint)MotionTableId)); - } - } - } - - - // Broadcast updated character appearance - EnqueueBroadcast(new GameMessageObjDescEvent(this)); - BarberActive = false; - } + /// /// Sends object description if the client requests it diff --git a/Source/ACE.Server/WorldObjects/Player_Character.cs b/Source/ACE.Server/WorldObjects/Player_Character.cs index 6b58a84d86..388a55bfd6 100644 --- a/Source/ACE.Server/WorldObjects/Player_Character.cs +++ b/Source/ACE.Server/WorldObjects/Player_Character.cs @@ -3,13 +3,16 @@ using System.Linq; using ACE.DatLoader; +using ACE.DatLoader.Entity; using ACE.DatLoader.FileTypes; using ACE.Database.Models.Shard; using ACE.Entity; using ACE.Entity.Enum; +using ACE.Entity.Enum.Properties; using ACE.Server.Managers; using ACE.Server.Network; using ACE.Server.Network.GameEvent.Events; +using ACE.Server.Network.GameMessages.Messages; using ACE.Server.Network.Structure; namespace ACE.Server.WorldObjects @@ -384,5 +387,379 @@ public string GetTitle(CharacterTitle title) return entry.Strings.FirstOrDefault(); } + + // ===================================== + // Barber + // ===================================== + + public void StartBarber() + { + BarberActive = true; + Session.Network.EnqueueSend(new GameEventStartBarber(Session)); + } + + public void HandleActionFinishBarber(ClientMessage message) + { + if (!BarberActive) return; + + // Read the payload sent from the client... + var requestedPaletteBaseId = message.Payload.ReadUInt32(); + var requestedHeadObjectDID = message.Payload.ReadUInt32(); + var requestedCharacterHairTexture = message.Payload.ReadUInt32(); + var requestedCharacterDefaultHairTexture = message.Payload.ReadUInt32(); + var requestedEyesTextureDID = message.Payload.ReadUInt32(); + var requestedDefaultEyesTextureDID = message.Payload.ReadUInt32(); + var requestedNoseTextureDID = message.Payload.ReadUInt32(); + var requestedDefaultNoseTextureDID = message.Payload.ReadUInt32(); + var requestedMouthTextureDID = message.Payload.ReadUInt32(); + var requestedDefaultMouthTextureDID = message.Payload.ReadUInt32(); + var requestedSkinPaletteDID = message.Payload.ReadUInt32(); + var requestedHairPaletteDID = message.Payload.ReadUInt32(); + var requestedEyesPaletteDID = message.Payload.ReadUInt32(); + var requestedSetupTableId = message.Payload.ReadUInt32(); + + uint option_bound = message.Payload.ReadUInt32(); // Supress Levitation - Empyrean Only + uint option_unk = message.Payload.ReadUInt32(); // Unknown - Possibly set aside for future use? + + //var debugMsg = "Barber Change Request:"; + //debugMsg += System.Environment.NewLine + $"PaletteBaseId: 0x{PaletteBaseId:X8} to 0x{requestedPaletteBaseId:X8}"; + //debugMsg += System.Environment.NewLine + $"HeadObjectDID: 0x{HeadObjectDID ?? 0:X8} to 0x{requestedHeadObjectDID:X8}"; + //debugMsg += System.Environment.NewLine + $"Character.HairTexture: 0x{Character.HairTexture:X8} to 0x{requestedCharacterHairTexture:X8}"; + //debugMsg += System.Environment.NewLine + $"Character.DefaultHairTexture: 0x{Character.DefaultHairTexture:X8} to 0x{requestedCharacterDefaultHairTexture:X8}"; + //debugMsg += System.Environment.NewLine + $"EyesTextureDID: 0x{EyesTextureDID:X8} to 0x{requestedEyesTextureDID:X8}"; + //debugMsg += System.Environment.NewLine + $"DefaultEyesTextureDID: 0x{DefaultEyesTextureDID:X8} to 0x{requestedDefaultEyesTextureDID:X8}"; + //debugMsg += System.Environment.NewLine + $"NoseTextureDID: 0x{NoseTextureDID:X8} to 0x{requestedNoseTextureDID:X8}"; + //debugMsg += System.Environment.NewLine + $"DefaultNoseTextureDID: 0x{DefaultNoseTextureDID:X8} to 0x{requestedDefaultNoseTextureDID:X8}"; + //debugMsg += System.Environment.NewLine + $"MouthTextureDID: 0x{MouthTextureDID:X8} to 0x{requestedMouthTextureDID:X8}"; + //debugMsg += System.Environment.NewLine + $"DefaultMouthTextureDID: 0x{DefaultMouthTextureDID:X8} to 0x{requestedDefaultMouthTextureDID:X8}"; + //debugMsg += System.Environment.NewLine + $"SkinPaletteDID: 0x{SkinPaletteDID:X8} to 0x{requestedSkinPaletteDID:X8}"; + //debugMsg += System.Environment.NewLine + $"HairPaletteDID: 0x{HairPaletteDID:X8} to 0x{requestedHairPaletteDID:X8}"; + //debugMsg += System.Environment.NewLine + $"EyesPaletteDID: 0x{EyesPaletteDID:X8} to 0x{requestedEyesPaletteDID:X8}"; + //debugMsg += System.Environment.NewLine + $"SetupTableId: 0x{SetupTableId:X8} to 0x{requestedSetupTableId:X8}"; + //debugMsg += System.Environment.NewLine + $"Option Bound: {option_bound}"; + //debugMsg += System.Environment.NewLine + $"Option Unknown: {option_unk}" + System.Environment.NewLine; + //Console.WriteLine(debugMsg); + + var heritageGroup = DatManager.PortalDat.CharGen.HeritageGroups[(uint)Heritage]; + var sex = heritageGroup.Genders[(int)Gender]; + + var validPaletteBase = sex.BasePalette == requestedPaletteBaseId; + + var validHeadObject = ValidateHairStyle(requestedHeadObjectDID, sex.HairStyleList, out var validatedHairStyle); + + var validEyesTexture = ValidateEyeTexture(requestedEyesTextureDID, sex.EyeStripList, isBald: validatedHairStyle?.Bald ?? false); + + var validDefaultEyesTexture = ValidateEyeTexture(requestedDefaultEyesTextureDID, sex.EyeStripList, compareOldTexture: true, isBald: validatedHairStyle?.Bald ?? false); + + var validNoseTexture = ValidateFaceTexture(requestedNoseTextureDID, sex.NoseStripList); + + var validDefaultNoseTexture = ValidateFaceTexture(requestedDefaultNoseTextureDID, sex.NoseStripList, compareOldTexture: true); + + var validMouthTexture = ValidateFaceTexture(requestedMouthTextureDID, sex.MouthStripList); + + var validDefaultMouthTexture = ValidateFaceTexture(requestedDefaultMouthTextureDID, sex.MouthStripList, compareOldTexture: true); + + var validCharacterHairTexture = ValidateHairTexture(requestedCharacterHairTexture, validatedHairStyle); + + var validCharacterDefaultHairTexture = ValidateHairTexture(requestedCharacterDefaultHairTexture, validatedHairStyle, compareOldTexture: true); + + var validSkinPalette = ValidateSkinPalette(requestedSkinPaletteDID, sex.SkinPalSet); + + var validEyesPalette = ValidateEyesPalette(requestedEyesPaletteDID, sex.EyeColorList); + + var validHairPalette = ValidateHairPalette(requestedHairPaletteDID, sex.HairColorList); + + var validSetupTable = ValidateSetupTable(requestedSetupTableId, heritageGroup, sex, validatedHairStyle); + + var validChangeRequested = validPaletteBase && validHeadObject + && validEyesTexture && validDefaultEyesTexture && validEyesPalette + && validNoseTexture && validDefaultNoseTexture + && validMouthTexture && validDefaultMouthTexture + && validCharacterHairTexture && validCharacterDefaultHairTexture && validHairPalette + && validSkinPalette && validSetupTable; + + //var previousSetupTableId = SetupTableId; + //var previousMotionTableId = MotionTableId; + + if (!validChangeRequested) + { + // Don't know what, if anything, to send to player, so silently failing for now. + //SendTransientError("The barber cannot do what you requested."); + BarberActive = false; + return; + } + else + { + if (requestedPaletteBaseId > 0) + PaletteBaseId = requestedPaletteBaseId; + + if (requestedHeadObjectDID > 0) + HeadObjectDID = requestedHeadObjectDID; + + if (requestedCharacterHairTexture > 0) + { + Character.HairTexture = requestedCharacterHairTexture; + CharacterChangesDetected = true; + } + if (requestedCharacterDefaultHairTexture > 0) + { + Character.DefaultHairTexture = requestedCharacterDefaultHairTexture; + CharacterChangesDetected = true; + } + + if (requestedEyesTextureDID > 0) + EyesTextureDID = requestedEyesTextureDID; + if (requestedDefaultEyesTextureDID > 0) + DefaultEyesTextureDID = requestedDefaultEyesTextureDID; + + if (requestedNoseTextureDID > 0) + NoseTextureDID = requestedNoseTextureDID; + if (requestedDefaultNoseTextureDID > 0) + DefaultNoseTextureDID = requestedDefaultNoseTextureDID; + + if (requestedMouthTextureDID > 0) + MouthTextureDID = requestedMouthTextureDID; + if (requestedDefaultMouthTextureDID > 0) + DefaultMouthTextureDID = requestedDefaultMouthTextureDID; + + if (requestedSkinPaletteDID > 0) + SkinPaletteDID = requestedSkinPaletteDID; + + if (requestedHairPaletteDID > 0) + HairPaletteDID = requestedHairPaletteDID; + + if (requestedEyesPaletteDID > 0) + EyesPaletteDID = requestedEyesPaletteDID; + + if (requestedSetupTableId > 0) + SetupTableId = requestedSetupTableId; + } + + // Check if Character is Empyrean, and if we need to set/change/send new motion table + if (Heritage == (int)HeritageGroup.Empyrean) + { + // These are the motion tables for Empyrean float and not-float (one for each gender). They are hard-coded into the client. + const uint EmpyreanMaleFloatMotionDID = 0x0900020Bu; + const uint EmpyreanFemaleFloatMotionDID = 0x0900020Au; + const uint EmpyreanMaleMotionDID = 0x0900020Eu; + const uint EmpyreanFemaleMotionDID = 0x0900020Du; + + // Check for the Levitation option for Empyrean. Shadow crown and Undead flames are handled by client. + if (Gender == (int)ACE.Entity.Enum.Gender.Male) // Male + { + if (option_bound == 1 && MotionTableId != EmpyreanMaleMotionDID) + { + MotionTableId = EmpyreanMaleMotionDID; + Session.Network.EnqueueSend(new GameMessagePrivateUpdateDataID(this, PropertyDataId.MotionTable, MotionTableId)); + } + else if (option_bound == 0 && MotionTableId != EmpyreanMaleFloatMotionDID) + { + MotionTableId = EmpyreanMaleFloatMotionDID; + Session.Network.EnqueueSend(new GameMessagePrivateUpdateDataID(this, PropertyDataId.MotionTable, MotionTableId)); + } + } + else // Female + { + if (option_bound == 1 && MotionTableId != EmpyreanFemaleMotionDID) + { + MotionTableId = EmpyreanFemaleMotionDID; + Session.Network.EnqueueSend(new GameMessagePrivateUpdateDataID(this, PropertyDataId.MotionTable, MotionTableId)); + } + else if (option_bound == 0 && MotionTableId != EmpyreanFemaleFloatMotionDID) + { + MotionTableId = EmpyreanFemaleFloatMotionDID; + Session.Network.EnqueueSend(new GameMessagePrivateUpdateDataID(this, PropertyDataId.MotionTable, MotionTableId)); + } + } + } + + + // Broadcast updated character appearance + EnqueueBroadcast(new GameMessageObjDescEvent(this)); + + // The following code provides for updated [no]flame/[no]crown/[no]hover setups to be seen by others immediately without need for the player to log out/in + // however it creates movement desync which must be the result of using UpdateObject message. + // We don't have (that I could find) pcaps of barber changes from observer perspective so I'm uncertain if the visual desync between setup changes was resolved by some other method + // + //if (previousSetupTableId != SetupTableId || previousMotionTableId != MotionTableId) + //{ + // EnqueueBroadcast(false, new GameMessageUpdateObject(this)); + // Session.Network.EnqueueSend(new GameMessageObjDescEvent(this)); + //} + //else + // EnqueueBroadcast(new GameMessageObjDescEvent(this)); + + BarberActive = false; + } + + private bool ValidateSetupTable(uint requestedSetupTableId, HeritageGroupCG heritageGroup, SexCG sex, HairStyleCG validHairStyle) + { + if (validHairStyle?.AlternateSetup > 0 && validHairStyle?.AlternateSetup == requestedSetupTableId) + return true; + else if (sex.SetupID == requestedSetupTableId) + return true; + //else if (heritageGroup.SetupID == requestedSetupTableId) + // return true; + + if (ValidHeritageSetups.TryGetValue((HeritageGroup)Heritage, out var genders) && genders.TryGetValue((Gender)Gender, out var gender) && gender.Contains(requestedSetupTableId)) + return true; + + return false; + } + + /// + /// Valid Setups defined in acclient that are not defined in the dat files. + /// + private static readonly Dictionary>> ValidHeritageSetups = new() + { + { HeritageGroup.Shadowbound, new () { + { ACE.Entity.Enum.Gender.Male, new() { + (uint)SetupConst.UmbraenMaleCrown, (uint)SetupConst.UmbraenMaleNoCrown } + }, + { ACE.Entity.Enum.Gender.Female, new() { + (uint)SetupConst.UmbraenFemaleCrown, (uint)SetupConst.UmbraenFemaleNoCrown } } + } + }, + { HeritageGroup.Penumbraen, new () { + { ACE.Entity.Enum.Gender.Male, new() { + (uint)SetupConst.PenumbraenMaleCrown, (uint)SetupConst.PenumbraenMaleNoCrown } + }, + { ACE.Entity.Enum.Gender.Female, new() { + (uint)SetupConst.PenumbraenFemaleCrown, (uint)SetupConst.PenumbraenFemaleNoCrown } } + } + }, + { HeritageGroup.Undead, new () { + { ACE.Entity.Enum.Gender.Male, new() { + (uint)SetupConst.UndeadMaleSkeleton, (uint)SetupConst.UndeadMaleSkeletonNoFlame, + (uint)SetupConst.UndeadMaleZombie, (uint)SetupConst.UndeadMaleZombieNoFlame } + }, + { ACE.Entity.Enum.Gender.Female, new() { + (uint)SetupConst.UndeadFemaleSkeleton, (uint)SetupConst.UndeadFemaleSkeletonNoFlame, + (uint)SetupConst.UndeadFemaleZombie, (uint)SetupConst.UndeadFemaleZombieNoFlame } } + } + } + }; + + private bool ValidateHairStyle(uint requestedHeadObjectDID, List hairStyleList, out HairStyleCG validHairStyle) + { + validHairStyle = null; + + //var validHairStyles = hairStyleList.Where(h => h.ObjDesc.AnimPartChanges[0].PartID == requestedHeadObjectDID).ToList(); + + if (requestedHeadObjectDID == 0) + { + if (Heritage == (int)HeritageGroup.Gearknight || Heritage == (int)HeritageGroup.Olthoi || Heritage == (int)HeritageGroup.OlthoiAcid) + return true; + } + + foreach (var hairStyle in hairStyleList) + { + foreach (var animPartChange in hairStyle.ObjDesc.AnimPartChanges) + { + if (animPartChange.PartID == requestedHeadObjectDID) + { + validHairStyle = hairStyle; + return true; + } + } + } + + return false; + } + + private bool ValidateEyeTexture(uint requestedEyesTextureDID, List eyeStripList, bool compareOldTexture = false, bool isBald = false) + { + foreach (var eyeStrip in eyeStripList) + { + if (isBald) + { + foreach (var textureMapChange in eyeStrip.ObjDescBald.TextureChanges) + { + if (compareOldTexture && textureMapChange.OldTexture == requestedEyesTextureDID) + return true; + else if (!compareOldTexture && textureMapChange.NewTexture == requestedEyesTextureDID) + return true; + } + } + else + { + foreach (var textureMapChange in eyeStrip.ObjDesc.TextureChanges) + { + if (compareOldTexture && textureMapChange.OldTexture == requestedEyesTextureDID) + return true; + else if (!compareOldTexture && textureMapChange.NewTexture == requestedEyesTextureDID) + return true; + } + } + } + + return false; + } + + private bool ValidateFaceTexture(uint requestedFaceTextureDID, List faceStripList, bool compareOldTexture = false) + { + foreach (var faceStrip in faceStripList) + { + foreach (var textureMapChange in faceStrip.ObjDesc.TextureChanges) + { + if (compareOldTexture && textureMapChange.OldTexture == requestedFaceTextureDID) + return true; + else if (!compareOldTexture && textureMapChange.NewTexture == requestedFaceTextureDID) + return true; + } + } + + return false; + } + + private bool ValidateHairTexture(uint requestedHairTextureDID, HairStyleCG hairStyle, bool compareOldTexture = false) + { + if (requestedHairTextureDID == 0) + { + if (Heritage == (int)HeritageGroup.Gearknight || Heritage == (int)HeritageGroup.Olthoi || Heritage == (int)HeritageGroup.OlthoiAcid) + return true; + } + else if (hairStyle == null) + return false; + + foreach (var textureMapChange in hairStyle.ObjDesc.TextureChanges) + { + if (compareOldTexture && textureMapChange.OldTexture == requestedHairTextureDID) + return true; + else if (!compareOldTexture && textureMapChange.NewTexture == requestedHairTextureDID) + return true; + } + + return false; + } + + private bool ValidateSkinPalette(uint requestedSkinPaletteDID, uint paletteSet) + { + var skinPalSet = DatManager.PortalDat.ReadFromDat(paletteSet); + if (skinPalSet.PaletteList.Contains(requestedSkinPaletteDID)) + return true; + + return false; + } + + private bool ValidateEyesPalette(uint requestedEyesPaletteDID, List eyeColorList) + { + if (eyeColorList.Contains(requestedEyesPaletteDID)) + return true; + + return false; + } + + private bool ValidateHairPalette(uint requestedHairPaletteDID, List hairColorList) + { + foreach (var hairPalette in hairColorList) + { + var hairPalSet = DatManager.PortalDat.ReadFromDat(hairPalette); + if (hairPalSet.PaletteList.Contains(requestedHairPaletteDID)) + return true; + } + + return false; + } } } diff --git a/Source/ACE.Server/WorldObjects/Player_Use.cs b/Source/ACE.Server/WorldObjects/Player_Use.cs index d62dd5d3ad..2c0aeb15c2 100644 --- a/Source/ACE.Server/WorldObjects/Player_Use.cs +++ b/Source/ACE.Server/WorldObjects/Player_Use.cs @@ -265,12 +265,6 @@ public void HandleActionNoLongerViewingContents(uint objectGuid) public Pet CurrentActivePet { get; set; } - public void StartBarber() - { - BarberActive = true; - Session.Network.EnqueueSend(new GameEventStartBarber(Session)); - } - public void ApplyConsumable(MotionCommand useMotion, Action action, float animMod = 1.0f) { if (PropertyManager.GetBool("allow_fast_chug").Item && FastTick) diff --git a/appveyor.yml b/appveyor.yml index 72a8167480..baea791b67 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 1.56.{build} +version: 1.57.{build} pull_requests: do_not_increment_build_number: true skip_tags: true