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