diff --git a/Server/ConsoleSystem.cs b/Server/ConsoleSystem.cs index eefc99a..53de384 100644 --- a/Server/ConsoleSystem.cs +++ b/Server/ConsoleSystem.cs @@ -238,7 +238,7 @@ namespace PSO2SERVER public void CreateCommands() { // Help - var help = new ConsoleCommand(Help, "help") { Help = "显示服务端所有指令帮助" }; + var help = new ConsoleCommand(Help, "help") { Help = "顯示服務端所有指令幫助" }; Commands.Add(help); // Config @@ -391,7 +391,7 @@ namespace PSO2SERVER Commands.Add(tellLoc); // Exit - var exit = new ConsoleCommand(Exit, "exit", "quit") { Help = "关闭PSO2服务器" }; + var exit = new ConsoleCommand(Exit, "exit", "quit") { Help = "關閉PSO2服務器" }; Commands.Add(exit); } @@ -456,7 +456,7 @@ namespace PSO2SERVER // Stop if the command line is blank if (string.IsNullOrEmpty(_commandLine)) - Logger.WriteWarning("[CMD] 未指定指令"); + Logger.WriteWarning("[CMD] 空白指令"); else { // Iterate commands @@ -476,7 +476,7 @@ namespace PSO2SERVER } if (!valid) - Logger.WriteError("[CMD] {0} - 指令未找到", _commandLine.Split(' ')[0].Trim('\r')); + Logger.WriteError("[CMD] {0} - 指令不存在", _commandLine.Split(' ')[0].Trim('\r')); // Add the command line to history and wipe it _history.Add(_commandLine); @@ -499,7 +499,7 @@ namespace PSO2SERVER // TODO: Use that fancy popup box when sending help to a client private void Help(string[] args, int length, string full, Client client) { - Logger.WriteCommand(client, "[CMD] 指令帮助菜单"); + Logger.WriteCommand(client, "[CMD] 指令幫助菜單"); foreach (var command in Commands) { @@ -566,7 +566,7 @@ namespace PSO2SERVER c.SendPacket(messagePacket); } - Logger.WriteCommand(client, "[CMD] 发送公告至所有玩家"); + Logger.WriteCommand(client, "[CMD] 發送公告至所有玩家"); } private void ClearLog(string[] args, int length, string full, Client client) @@ -590,29 +590,29 @@ namespace PSO2SERVER break; case "list": - Logger.WriteCommand(client, "[CMD] 设置选项"); + Logger.WriteCommand(client, "[CMD] 設置選項"); foreach (var f in fields) Logger.WriteCommand(client, "[CMD] {0} = {1}", f.Name, f.GetValue(ServerApp.Config)); break; default: // Set a config option if (args.Length < 3) - Logger.WriteCommand(client, "[CMD] Too few arguments"); + Logger.WriteCommand(client, "[CMD] 指令參數不足"); else if (field != null) { var value = args[2].Contains('\"') ? full.Split('"')[1].Split('"')[0].Trim('\"') : args[2]; if (!ServerApp.Config.SetField(args[1], value)) - Logger.WriteCommand(client, "[CMD] Config option {0} could not be changed to {1}", args[1], + Logger.WriteCommand(client, "[CMD] 設置選項 {0} 无法更改為 {1}", args[1], value); else { - Logger.WriteCommand(client, "[CMD] Config option {0} changed to {1}", args[1], value); + Logger.WriteCommand(client, "[CMD] 設置選項 {0} 更改為 {1}", args[1], value); ServerApp.Config.SettingsChanged(); } } else - Logger.WriteCommand(client, "[CMD] Config option {0} not found", args[1]); + Logger.WriteCommand(client, "[CMD] 設置選項 {0} 不存在", args[1]); break; } } @@ -640,7 +640,7 @@ namespace PSO2SERVER // Couldn't find the username if (!foundPlayer) { - Logger.WriteCommand(client, "[CMD] Could not find user " + name); + Logger.WriteCommand(client, "[CMD] 无法找到玩家 " + name); return; } } @@ -680,7 +680,7 @@ namespace PSO2SERVER // Couldn't find the username if (!foundPlayer) { - Logger.WriteError("[CMD] Could not find user " + name); + Logger.WriteError("[CMD] 无法找到玩家 " + name); return; } diff --git a/Server/Models/PSO2Item.cs b/Server/Models/PSO2Item.cs index f256d97..21c2918 100644 --- a/Server/Models/PSO2Item.cs +++ b/Server/Models/PSO2Item.cs @@ -317,6 +317,13 @@ namespace PSO2SERVER.Models // ... } + + /// Player equiped item. + public struct EquipedItem + { + public PSO2Items item; + public uint unk; + } } public static class AffixUtils { diff --git a/Server/Protocol/Handlers/04-ObjectHandler/04-75-ActionEnd.cs b/Server/Protocol/Handlers/04-ObjectHandler/04-75-ActionEnd.cs new file mode 100644 index 0000000..a74c3cf --- /dev/null +++ b/Server/Protocol/Handlers/04-ObjectHandler/04-75-ActionEnd.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using PSO2SERVER.Models; +using PSO2SERVER.Party; +using PSO2SERVER.Protocol.Packets; +using PSO2SERVER.Zone; + +namespace PSO2SERVER.Protocol.Handlers +{ + [PacketHandlerAttr(0x04, 0x75)] + public class ActionEnd : PacketHandler + { + public ObjectHeader unk1 { get; set; } = new ObjectHeader(); + /// Object that was performing an action. + public ObjectHeader performer { get; set; } = new ObjectHeader(); + public uint unk2 { get; set; } = 0; + public ObjectHeader unk3 { get; set; } = new ObjectHeader(); + public ObjectHeader unk4 { get; set; } = new ObjectHeader(); + public byte[] unk5 { get; set; } = new byte[0x04]; + public string action { get; set; } = string.Empty;//Ascii + + public override void HandlePacket(Client context, byte flags, byte[] data, uint position, uint size) + { + var info = string.Format("[<--] 接收到的数据 (hex): {0} 字节", data.Length); + Logger.WriteHex(info, data); + + var reader = new PacketReader(data, position, size); + unk1 = reader.ReadStruct(); + performer = reader.ReadStruct(); + unk2 = reader.ReadUInt32(); + unk3 = reader.ReadStruct(); + unk4 = reader.ReadStruct(); + unk5 = reader.ReadBytes(4); + action = reader.ReadAscii(0x83EF, 0x40); + // 打印各个字段的内容 + Logger.Write("unk1: " + unk1); // 假设 ObjectHeader 重载了 ToString() 方法 + Logger.Write("performer: " + performer); // 同上 + Logger.Write("unk2: " + unk2); + Logger.Write("unk3: " + unk3); + Logger.Write("unk4: " + unk4); + Logger.Write("unk5: " + BitConverter.ToString(unk5)); // 打印 byte 数组 + Logger.Write("action: " + action); // 打印 ASCII 字符串 + + foreach (Client cl in PartyManager.instance.GetCurrentPartyForClient(context).getMembers()) + { + cl.SendPacket(new ActionEndPacket(unk1, performer, unk2, unk3, unk4, unk5, action)); + } + } + } +} diff --git a/Server/Protocol/Packets/04-ObjectRelatedPacket/04-75-ActionEndPacket.cs b/Server/Protocol/Packets/04-ObjectRelatedPacket/04-75-ActionEndPacket.cs new file mode 100644 index 0000000..5c57c4d --- /dev/null +++ b/Server/Protocol/Packets/04-ObjectRelatedPacket/04-75-ActionEndPacket.cs @@ -0,0 +1,54 @@ +using PSO2SERVER.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace PSO2SERVER.Protocol.Packets +{ + public class ActionEndPacket : Packet + { + public ObjectHeader unk1 { get; set; } = new ObjectHeader(); + /// Object that was performing an action. + public ObjectHeader performer { get; set; } = new ObjectHeader(); + public uint unk2 { get; set; } = 0; + public ObjectHeader unk3 { get; set; } = new ObjectHeader(); + public ObjectHeader unk4 { get; set; } = new ObjectHeader(); + public byte[] unk5 { get; set; } = new byte[0x04]; + public string action { get; set; } = string.Empty;//Ascii + + // 带参数的构造函数,用来初始化字段 + public ActionEndPacket(ObjectHeader unk1, ObjectHeader performer, uint unk2, ObjectHeader unk3, ObjectHeader unk4, byte[] unk5, string action) + { + this.unk1 = unk1; // 如果传入的值为 null, 使用默认值 + this.performer = performer; + this.unk2 = unk2; + this.unk3 = unk3; + this.unk4 = unk4; + this.unk5 = unk5 ?? new byte[0x04]; // 如果传入的值为 null, 使用默认字节数组 + this.action = action ?? string.Empty; // 如果传入的值为 null, 使用空字符串 + } + + #region implemented abstract members of Packet + + public override byte[] Build() + { + var pkt = new PacketWriter(); + pkt.WriteStruct(unk1); + pkt.WriteStruct(performer); + pkt.Write(unk2); + pkt.WriteStruct(unk3); + pkt.WriteStruct(unk4); + pkt.WriteByteArray(unk5); + pkt.WriteAscii(action, 0x83EF, 0x40); + return pkt.ToArray(); + } + + public override PacketHeader GetHeader() + { + return new PacketHeader(0x04, 0x75, PacketFlags.PACKED_OBJECT_RELATED); + } + + #endregion + } +} \ No newline at end of file diff --git a/Server/Protocol/Packets/08-SpawnPacket/08-04-CharacterSpawnPacket.cs b/Server/Protocol/Packets/08-SpawnPacket/08-04-CharacterSpawnPacket.cs index f5d0298..cc40281 100644 --- a/Server/Protocol/Packets/08-SpawnPacket/08-04-CharacterSpawnPacket.cs +++ b/Server/Protocol/Packets/08-SpawnPacket/08-04-CharacterSpawnPacket.cs @@ -1,6 +1,7 @@ using PSO2SERVER.Database; using PSO2SERVER.Models; using PSO2SERVER.Zone; +using System.IO; using System.Runtime.InteropServices; using static PSO2SERVER.Models.CharacterStruct; using static PSO2SERVER.Protocol.Packets.CharacterSpawnPacket; @@ -31,37 +32,40 @@ namespace PSO2SERVER.Protocol.Packets public PSOLocation ObjPosition { get; set; } = new PSOLocation(); public ushort Unk1 { get; set; } = 0; /// Always `Character`. (?) - public string ObjName { get; set; } = "Character";//0x20 + public string ObjName { get; set; } = "Character";//Ascii 0x20 public ushort Unk3 { get; set; } = 1; public ushort Unk4 { get; set; } public uint Unk5 { get; set; } public uint Unk6 { get; set; } public uint Unk7 { get; set; } public uint Unk8 { get; set; } + /// Character spawn type. public CharacterSpawnType SpawnType { get; set; } public byte Unk9 { get; set; } public ushort Unk10 { get; set; } + /// Character data. public Character Character { get; set; } public uint Unk11 { get; set; } /// Set to `1` if the player is a GM. public uint GmFlag { get; set; } - public string Nickname { get; set; } + /// Player's nickname. + public string Nickname { get; set; }//0x10 //#[SeekAfter(0x60)] public byte[] Unk12 { get; set; } = new byte[0x40]; public CharacterSpawnPacket(Character character, PSOLocation locatiion) { ObjHeader = new ObjectHeader((uint)character.Account.AccountId, ObjectType.Player); - Character = character; ObjPosition = locatiion; + Character = character; } public CharacterSpawnPacket(Character character, PSOLocation locatiion, bool isme, bool isgm) { ObjHeader = new ObjectHeader((uint)character.Account.AccountId, ObjectType.Player); + ObjPosition = locatiion; Character = character; SpawnType = isme ? CharacterSpawnType.Myself : CharacterSpawnType.Other; - ObjPosition = locatiion; GmFlag = isgm ? (uint)1 : 0; } @@ -90,56 +94,8 @@ namespace PSO2SERVER.Protocol.Packets pkt.Write(Unk11); pkt.Write(GmFlag); pkt.WriteFixedLengthUtf16(Character.Account.Nickname, 0x10); - //for (var i = 0; i < 0x60; i++) - // pkt.Write((byte)0); - //pkt.Write(unk12); - - //pkt.Write((ushort)0); // 0x46 - //pkt.Write((uint)602); // 0x48 - //pkt.Write((uint)1); // 0x4C - //pkt.Write((uint)53); // 0x50 - //pkt.Write((uint)0); // 0x54 - - //// Character spawn type. - //pkt.Write(IsItMe); // 0x58 - //pkt.Write((byte)0x00); - //pkt.Write((ushort)0x00); - - //////writer.write((ushort)0x022f); // 0x5c - ////writer.write((byte)0x2f); // 0x5c - ////writer.write((byte)0x02); - //////writer.write((ushort)0x0132); // 0x5e - ////writer.write((byte)0x32); - ////writer.write((byte)0x01); - - ////JobParam jobParam = _character.Jobs; - ////jobParam.mainClass = ClassType.Luster; - ////jobParam.subClass = ClassType.Phantom; - ////jobParam.entries.Luster.level = 100; - - //// Character data. - //pkt.Write((uint)_character.AccountID); - //pkt.Write((uint)_character.CharacterID); - //pkt.Write((uint)_character.Unk1);//4 - //pkt.Write((uint)_character.VoiceType);//4 - //pkt.Write((ushort)_character.Unk2);//2 - //pkt.Write(_character.VoicePitch);//2 - //pkt.WriteFixedLengthUtf16(_character.Name, 16); - //pkt.Write((uint)_character.Unk3); // 0x90 - //pkt.WriteStruct(_character.Looks); - //pkt.WriteStruct(_character.Jobs); - //pkt.WriteFixedLengthUtf16(_character.Account.Nickname, 16); - //pkt.WriteBytes(0, 116); - - //pkt.Write((uint)0); // 0x204 - //pkt.Write(IsGM); // gmflag? - - - //for (var i = 0; i < 0x60; i++) - // pkt.Write((byte)0); - - //for (var i = 0; i < 0x40; i++) - // writer.Write((byte)0); + pkt.BaseStream.Seek(0x60, SeekOrigin.Current); + pkt.Write(Unk12); return pkt.ToArray(); } diff --git a/Server/Protocol/Packets/0F-ItemPacket/0F-0C-LoadEquipedPacket.cs b/Server/Protocol/Packets/0F-ItemPacket/0F-0C-LoadEquipedPacket.cs index 3d4d444..7e5e48f 100644 --- a/Server/Protocol/Packets/0F-ItemPacket/0F-0C-LoadEquipedPacket.cs +++ b/Server/Protocol/Packets/0F-ItemPacket/0F-0C-LoadEquipedPacket.cs @@ -1,21 +1,43 @@ using System; +using System.Collections.Generic; using PSO2SERVER.Models; namespace PSO2SERVER.Protocol.Packets { public class LoadEquipedPacket : Packet { - public Int64 NewAmount = 0; + /// Player whose equipment is loaded. + public ObjectHeader Player { get; set; } = new ObjectHeader(); + /// Player's equiped items. + public List Items { get; set; } = new List(); + // 0-back unit + // 1-arm unit + // 2-leg unit + // 3-outfit + // 9-weapon + public uint Unk1 { get; set; } = 0; + public byte[] Unk2 { get; set; } = new byte[0x28]; #region implemented abstract members of Packet + public LoadEquipedPacket(uint accountid, List items) + { + Player = new ObjectHeader(accountid, ObjectType.Player); + Items = items; + } + public override byte[] Build() { - var writer = new PacketWriter(); - - writer.Write(NewAmount); - - return writer.ToArray(); + var pkt = new PacketWriter(); + pkt.WriteStruct(Player); + pkt.WriteMagic((uint)Items.Count, 0xCF76, 0xB5); + foreach (var item in Items) + { + pkt.WriteStruct(item); + } + pkt.Write(Unk1); + pkt.Write(Unk2); + return pkt.ToArray(); } public override PacketHeader GetHeader() diff --git a/Server/Server.csproj b/Server/Server.csproj index 192627e..474641a 100644 --- a/Server/Server.csproj +++ b/Server/Server.csproj @@ -187,6 +187,7 @@ + @@ -258,6 +259,7 @@ +