baseline: OpenMU upstream b5a0961 (fresh source)
This commit is contained in:
408
src/GameServer/RemoteView/ItemSerializerExtended.cs
Normal file
408
src/GameServer/RemoteView/ItemSerializerExtended.cs
Normal file
@@ -0,0 +1,408 @@
|
||||
// <copyright file="ItemSerializerExtended.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[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,
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int NeededSpace => 15;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int SerializeItem(Span<byte> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Item DeserializeItem(Span<byte> 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>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private readonly ref struct ItemStruct(Span<byte> data)
|
||||
{
|
||||
private readonly Span<byte> _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<byte> 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user