// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.GameServer.RemoteView; using System.Runtime.InteropServices; using MUnique.OpenMU.DataModel; using MUnique.OpenMU.DataModel.Configuration; using MUnique.OpenMU.DataModel.Configuration.Items; using MUnique.OpenMU.DataModel.Entities; using MUnique.OpenMU.GameLogic; using MUnique.OpenMU.Network.PlugIns; using MUnique.OpenMU.Persistence; using MUnique.OpenMU.PlugIns; using static ItemSerializerHelper; /// /// This item serializer is used to serialize the item data to the data packets. /// At the moment, each item is serialized into a dynamical length 5 to 15-byte /// long part of an array. /// [Guid("9EBB4761-93D4-49DE-AC53-BD8744315439")] [PlugIn] [Display(Name = nameof(PlugInResources.ItemSerializerExtended_Name), Description = nameof(PlugInResources.ItemSerializerExtended_Description), ResourceType = typeof(PlugInResources))] [MinimumClient(106, 3, ClientLanguage.Invariant)] public class ItemSerializerExtended : IItemSerializer { [Flags] private enum OptionFlags : byte { None = 0x00, HasOption = 0x01, HasLuck = 0x02, HasSkill = 0x04, HasExcellent = 0x08, HasAncient = 0x10, HasHarmony = 0x20, HasGuardian = 0x40, HasSockets = 0x80, } /// public int NeededSpace => 15; /// public int SerializeItem(Span target, Item item) { item.ThrowNotInitializedProperty(item.Definition is null, nameof(item.Definition)); var targetStruct = new ItemStruct(target); targetStruct.Group = (byte)item.Definition.Group; targetStruct.Number = (ushort)item.Definition.Number; targetStruct.Level = item.IsTrainablePet() ? (byte)0 : (byte)item.Level; targetStruct.Durability = item.Durability(); targetStruct.Options = this.GetOptionFlags(item); if (item.ItemOptions.FirstOrDefault(o => o.ItemOption?.OptionType == ItemOptionTypes.Option) is { } itemOption) { targetStruct.OptionLevel = (byte)(itemOption.Level & 0xF); // Some items (wings) can have different options (3rd wings up to 3!) targetStruct.OptionType = (byte)((itemOption.ItemOption?.Number ?? 0) & 0xF); } if (targetStruct.Options.HasFlag(OptionFlags.HasExcellent)) { targetStruct.Excellent = GetExcellentByte(item); targetStruct.Excellent |= GetFenrirByte(item); } if (targetStruct.Options.HasFlag(OptionFlags.HasAncient) && item.ItemSetGroups.FirstOrDefault(set => set.AncientSetDiscriminator != 0) is { } ancientSet) { targetStruct.AncientDiscriminator = (byte)ancientSet.AncientSetDiscriminator; // An ancient item may or may not have an ancient bonus option. Example without bonus: Gywen Pendant. if (item.ItemOptions.FirstOrDefault(o => o.ItemOption?.OptionType == ItemOptionTypes.AncientBonus) is { } ancientBonus) { targetStruct.AncientBonusLevel = (byte)ancientBonus.Level; } } if (targetStruct.Options.HasFlag(OptionFlags.HasHarmony)) { targetStruct.Harmony = GetHarmonyByte(item); } if (targetStruct.Options.HasFlag(OptionFlags.HasSockets)) { targetStruct.SocketCount = (byte)item.SocketCount; targetStruct.SocketBonus = GetSocketBonusByte(item); SetSocketBytes(targetStruct.Sockets, item); } return targetStruct.Length; } /// public Item DeserializeItem(Span array, GameConfiguration gameConfiguration, IContext persistenceContext) { var itemStruct = new ItemStruct(array); var itemGroup = itemStruct.Group; var itemNumber = itemStruct.Number; var definition = gameConfiguration.Items.FirstOrDefault(def => def.Number == itemNumber && def.Group == itemGroup) ?? throw new ArgumentException($"Couldn't find the item definition for the given byte array. Extracted item number and group: {itemNumber}, {itemGroup}"); var item = persistenceContext.CreateNew(); item.Definition = definition; item.Level = itemStruct.Level; item.Durability = itemStruct.Durability; item.HasSkill = itemStruct.Options.HasFlag(OptionFlags.HasSkill) && item.Definition?.Skill is not null; if (itemStruct.Options.HasFlag(OptionFlags.HasOption)) { AddNormalOption(itemStruct.OptionType, itemStruct.OptionLevel, persistenceContext, item); } if (itemStruct.Options.HasFlag(OptionFlags.HasLuck)) { AddLuckOption(persistenceContext, item); } if (itemStruct.Options.HasFlag(OptionFlags.HasExcellent)) { if (item.IsWing()) { ReadWingOptionBits(itemStruct.Excellent, persistenceContext, item); } else { ReadExcellentOptionBits(itemStruct.Excellent, persistenceContext, item); } } if (itemStruct.Options.HasFlag(OptionFlags.HasAncient)) { AddAncientOption(itemStruct.AncientDiscriminator, itemStruct.AncientBonusLevel, persistenceContext, item); } if (itemStruct.Options.HasFlag(OptionFlags.HasHarmony)) { AddHarmonyOption(itemStruct.Harmony, persistenceContext, item); } if (itemStruct.Options.HasFlag(OptionFlags.HasGuardian)) { AddLevel380Option(persistenceContext, item); } if (itemStruct.Options.HasFlag(OptionFlags.HasSockets)) { ReadSocketBonus(itemStruct.SocketBonus, persistenceContext, item); ReadSockets(itemStruct.Sockets, persistenceContext, item); } return item; } private OptionFlags GetOptionFlags(Item item) { OptionFlags result = default; if (item.ItemOptions.Any(o => o.ItemOption?.OptionType == ItemOptionTypes.Luck)) { result |= OptionFlags.HasLuck; } if (item.HasSkill) { result |= OptionFlags.HasSkill; } if (item.ItemOptions.Any(o => o.ItemOption?.OptionType == ItemOptionTypes.Option)) { result |= OptionFlags.HasOption; } if (item.ItemOptions.Any(o => o.ItemOption?.OptionType == ItemOptionTypes.Excellent || o.ItemOption?.OptionType == ItemOptionTypes.Wing || o.ItemOption?.OptionType == ItemOptionTypes.BlackFenrir || o.ItemOption?.OptionType == ItemOptionTypes.BlueFenrir || o.ItemOption?.OptionType == ItemOptionTypes.GoldFenrir)) { result |= OptionFlags.HasExcellent; } if (item.ItemOptions.Any(o => o.ItemOption?.OptionType == ItemOptionTypes.HarmonyOption)) { result |= OptionFlags.HasHarmony; } if (item.ItemSetGroups.Any(set => set.AncientSetDiscriminator != 0)) { result |= OptionFlags.HasAncient; } if (item.ItemOptions.Any(o => o.ItemOption?.OptionType == ItemOptionTypes.GuardianOption)) { result |= OptionFlags.HasGuardian; } if (item.SocketCount > 0) { result |= OptionFlags.HasSockets; } return result; } /// /// Layout: /// Group: 4 bit /// Number: 12 bit /// Level: 8 bit /// Dura: 8 bit /// OptFlags: 8 bit /// HasOpt /// HasLuck /// HasSkill /// HasExc /// HasAnc /// HasGuardian /// HasHarmony /// HasSockets /// Optional, depending on Flags: /// Opt_Lvl 4 bit /// Opt_Typ 4 bit /// Exc: 8 bit /// Anc_Dis 4 bit /// Anc_Bon 4 bit /// Harmony 8 bit /// Soc_Bon 4 bit /// Soc_Cnt 4 bit /// Sockets n * 8 bit /// /// Total: 5 ~ 15 bytes. /// private readonly ref struct ItemStruct(Span data) { private readonly Span _data = data; public byte Group { get => (byte)((this._data[0] >> 4) & 0xF); set { value <<= 4; value |= (byte)(this._data[0] & 0xF); this._data[0] = value; } } public ushort Number { get => (ushort)(((this._data[0] & 0xF) << 8) + this._data[1]); set { // Higher 4 bits of the first byte for the higher bits of the value this._data[0] = (byte)((this._data[0] & 0xF0) | (((value & 0x0F00) >> 8) & 0xF)); // The lower bits in the second byte this._data[1] = (byte)(value & 0xFF); } } public byte Level { get => this._data[2]; set => this._data[2] = value; } public byte Durability { get => this._data[3]; set => this._data[3] = value; } public OptionFlags Options { get => (OptionFlags)this._data[4]; set => this._data[4] = (byte)value; } public byte OptionByte { get => this.Options.HasFlag(OptionFlags.HasOption) ? this._data[5] : default; set => this._data[5] = value; } public byte OptionLevel { get => this.Options.HasFlag(OptionFlags.HasOption) ? (byte)(this._data[5] & 0xF) : default; set => this._data[5] = (byte)((this._data[5] & 0xF0) | (value & 0xF)); } public byte OptionType { get => this.Options.HasFlag(OptionFlags.HasOption) ? (byte)((this._data[5] & 0xF0) >> 4) : default; set { value = (byte)((value & 0xF) << 4); value |= (byte)(this._data[5] & 0xF); this._data[5] = value; } } public byte Excellent { get => this.Options.HasFlag(OptionFlags.HasExcellent) ? this._data[this.ExcellentIndex] : default; set => this._data[this.ExcellentIndex] = value; } public byte AncientByte { get => this.Options.HasFlag(OptionFlags.HasAncient) ? this._data[this.AncientIndex] : default; set => this._data[this.AncientIndex] = value; } public byte AncientDiscriminator { get => this.Options.HasFlag(OptionFlags.HasAncient) ? (byte)(this._data[this.AncientIndex] & 0xF) : default; set => this._data[this.AncientIndex] = (byte)((this._data[this.AncientIndex] & 0xF0) | (value & 0xF)); } public byte AncientBonusLevel { get => this.Options.HasFlag(OptionFlags.HasAncient) ? (byte)((this._data[this.AncientIndex] & 0xF0) >> 4) : default; set { value = (byte)((value & 0xF) << 4); value |= (byte)(this._data[this.AncientIndex] & 0xF); this._data[this.AncientIndex] = value; } } public byte Harmony { get => this.Options.HasFlag(OptionFlags.HasHarmony) ? this._data[this.HarmonyIndex] : default; set => this._data[this.HarmonyIndex] = value; } public byte SocketBonus { get => this.Options.HasFlag(OptionFlags.HasSockets) ? (byte)((this._data[this.SocketStartIndex] & 0xF0) >> 4) : default; set { value = (byte)((value & 0xF) << 4); value |= (byte)(this._data[this.SocketStartIndex] & 0xF); this._data[this.SocketStartIndex] = value; } } public byte SocketCount { get => this.Options.HasFlag(OptionFlags.HasSockets) ? (byte)(this._data[this.SocketStartIndex] & 0xF) : default; set => this._data[this.SocketStartIndex] = (byte)((this._data[this.SocketStartIndex] & 0xF0) | (value & 0xF)); } public Span Sockets => this.Options.HasFlag(OptionFlags.HasSockets) ? this._data.Slice(this.SocketStartIndex + 1, this.SocketCount) : []; public int Length { get { int size = 5; if (this.Options.HasFlag(OptionFlags.HasOption)) { size++; } if (this.Options.HasFlag(OptionFlags.HasExcellent)) { size++; } if (this.Options.HasFlag(OptionFlags.HasAncient)) { size++; } if (this.Options.HasFlag(OptionFlags.HasHarmony)) { size++; } if (this.Options.HasFlag(OptionFlags.HasSockets)) { size++; size += this.SocketCount; } return size; } } private int AdditionalOptionIndex => this.Options.HasFlag(OptionFlags.HasOption) ? 5 : 4; private int ExcellentIndex => this.Options.HasFlag(OptionFlags.HasExcellent) ? this.AdditionalOptionIndex + 1 : this.AdditionalOptionIndex; private int AncientIndex => this.Options.HasFlag(OptionFlags.HasAncient) ? this.ExcellentIndex + 1 : this.ExcellentIndex; private int HarmonyIndex => this.Options.HasFlag(OptionFlags.HasHarmony) ? this.AncientIndex + 1 : this.AncientIndex; private int SocketStartIndex => this.Options.HasFlag(OptionFlags.HasSockets) ? this.HarmonyIndex + 1 : this.HarmonyIndex; } }