PSO2SERVER/Server/Models/PSO2Item.cs
2024-12-10 13:33:57 +08:00

322 lines
8.5 KiB
C#

using PSO2SERVER.Protocol;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using static PSO2SERVER.Models.CharacterStruct;
namespace PSO2SERVER.Models
{
[StructLayout(LayoutKind.Sequential)]
public unsafe struct ShortItemId
{
public byte ItemType;
public byte Id;
public ushort Subid;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct ItemId
{
public ushort ItemType;
public ushort Id;
public ushort Unk3;
public ushort Subid;
}
[StructLayout(LayoutKind.Explicit)]
public struct Items
{
[FieldOffset(0)]
public PSO2ItemWeapon Weapon;
[FieldOffset(0)]
public PSO2ItemClothing Clothing;
[FieldOffset(0)]
public PSO2ItemConsumable Consumable;
[FieldOffset(0)]
public PSO2ItemCamo Camo;
[FieldOffset(0)]
public PSO2ItemUnit Unit;
//[FieldOffset(0)]
//public byte[] Unknown;
//[FieldOffset(0)]
//public PSO2ItemNone None;
}
[StructLayout(LayoutKind.Sequential)]
public struct PSO2Items
{
public ulong uuid;
public ItemId id;
public Items data;
public byte[] ToByteArray()
{
using (var pkt = new PacketWriter())
{
pkt.Write(uuid);
pkt.WriteStruct(id);
pkt.WriteStruct(data);
return pkt.ToArray();
}
}
// 从字节流转换为结构体
public static PSO2Items FromByteArray(byte[] byteArray)
{
using (var reader = new PacketReader(byteArray))
{
PSO2Items items = new PSO2Items
{
uuid = reader.ReadUInt64(),
id = reader.ReadStruct<ItemId>(),
data = reader.ReadStruct<Items>()
};
return items;
}
}
public ulong GetGUID()
{
return uuid;
}
public void SetGUID(ulong guid)
{
uuid = guid;
}
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct PSO2ItemNone
{
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct PSO2ItemWeapon
{
public byte flags;
public byte element;
public byte force;
public byte grind;
public byte grindPercent;
public byte unknown1;
public short unknown2;
public fixed short affixes[8];
public uint potential;
public byte extend;
public byte unknown3;
public ushort unknown4;
public uint unknown5;
public uint unknown6;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct PSO2ItemClothing
{
public ushort flags;
public fixed byte unk1[0x14];
public HSVColor Color;
public fixed byte unk2[0xA];
public ushort Unk3;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct PSO2ItemConsumable
{
public ushort flags;
public fixed byte unk1[0x24];
public ushort amount;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct PSO2ItemCamo
{
public byte unk1;
public byte unk2;
public byte unk3;
public fixed byte unk4[0x24];
public byte unk5;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct PSO2ItemUnit
{
public byte flags;
public byte EnhLevel;
public byte EnhPercent;
public byte Unk1;
// 使用 fixed 数组来存储附加信息
public fixed ushort Affixes[8]; // Item affix IDs (0 to 4095)
public fixed byte unk4[0x7];
public uint Potential;
// 使用 fixed 数组来存储未知字段
public fixed byte Unk2[4];
public uint Unk3;
public ushort Unk4;
public ushort Unk5;
// 提供访问固定数组的属性
public Span<ushort> AffixSpan
{
get
{
fixed (ushort* p = Affixes)
{
return new Span<ushort>(p, 8);
}
}
}
}
public struct Campaign
{
/// Campaign ID.
public uint Id; // 对应 Rust 的 u32
/// Start timestamp.
public TimeSpan StartDate; // 对应 Rust 的 Duration
/// End timestamp.
public TimeSpan EndDate; // 对应 Rust 的 Duration
/// Campaign title (固定长度 0x3E).
public const int TitleLength = 0x3E;
public byte[] titleBytes;
public string Title
{
get => Encoding.ASCII.GetString(titleBytes).TrimEnd('\0');
set
{
titleBytes = new byte[TitleLength];
byte[] valueBytes = Encoding.ASCII.GetBytes(value);
Array.Copy(valueBytes, titleBytes, Math.Min(valueBytes.Length, TitleLength));
}
}
/// Campaign conditions (固定长度 0x102).
public const int ConditionsLength = 0x102;
public byte[] conditionsBytes;
public string Conditions
{
get => Encoding.ASCII.GetString(conditionsBytes).TrimEnd('\0');
set
{
conditionsBytes = new byte[ConditionsLength];
byte[] valueBytes = Encoding.ASCII.GetBytes(value);
Array.Copy(valueBytes, conditionsBytes, Math.Min(valueBytes.Length, ConditionsLength));
}
}
// 从数据流中读取 Campaign
public static Campaign FromStream(Stream stream)
{
using (BinaryReader reader = new BinaryReader(stream, Encoding.ASCII, true))
{
Campaign campaign = new Campaign
{
Id = reader.ReadUInt32(),
StartDate = TimeSpan.FromTicks(reader.ReadInt64()),
EndDate = TimeSpan.FromTicks(reader.ReadInt64()),
};
campaign.titleBytes = reader.ReadBytes(TitleLength);
campaign.conditionsBytes = reader.ReadBytes(ConditionsLength);
return campaign;
}
}
// 将 Campaign 写入数据流
public void WriteToStream(Stream stream)
{
using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII, true))
{
writer.Write(Id);
writer.Write(StartDate.Ticks);
writer.Write(EndDate.Ticks);
writer.Write(titleBytes);
writer.Write(conditionsBytes);
}
}
}
public enum ItemTypes
{
NoItem,
Weapon,
Clothing,
Consumable,
Camo,
Unit,
Unknown
}
[Flags]
public enum ItemFlags
{
Locked = 0x01,
BoundToOwner = 0x02
}
public enum ItemElement
{
None,
Fire,
Ice,
Lightning,
Wind,
Light,
Dark
}
}
public static class AffixUtils
{
public static ushort[] ReadPackedAffixes(Stream reader)
{
byte[] packed = new byte[12];
reader.Read(packed, 0, packed.Length);
ushort[] affixes = new ushort[8];
for (int i = 0; i < 4; i++)
{
affixes[i * 2] = BitConverter.ToUInt16(new byte[] { packed[i * 3], (byte)((packed[i * 3 + 2] & 0xF0) >> 4) }, 0);
affixes[i * 2 + 1] = BitConverter.ToUInt16(new byte[] { packed[i * 3 + 1], (byte)(packed[i * 3 + 2] & 0xF) }, 0);
}
return affixes;
}
public static void WritePackedAffixes(ushort[] affixes, Stream writer)
{
byte[] packed = new byte[12];
for (int i = 0; i < 4; i++)
{
byte[] affix1 = BitConverter.GetBytes(affixes[i * 2]);
byte[] affix2 = BitConverter.GetBytes(affixes[i * 2 + 1]);
packed[i * 3] = affix1[0];
packed[i * 3 + 1] = affix2[0];
packed[i * 3 + 2] = (byte)((affix1[1] << 4) | (affix2[1] & 0xF));
}
writer.Write(packed, 0, packed.Length);
}
}
public class PacketError : Exception
{
public string PacketName { get; }
public string FieldName { get; }
public PacketError(string packetName, string fieldName, Exception innerException)
: base($"Error in packet '{packetName}', field '{fieldName}'", innerException)
{
PacketName = packetName;
FieldName = fieldName;
}
}