fix: Stops creating blocked packets entirely (#944)

Optimizes larger servers where users are logging in and packets are being created for no reason.
This commit is contained in:
Kamron Batman 2022-02-27 00:13:49 -08:00 committed by GitHub
parent 5db8b1354c
commit 941452de4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
58 changed files with 5510 additions and 5537 deletions

View file

@ -4139,7 +4139,7 @@ namespace Server
var ns = from.NetState;
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -12,7 +12,7 @@ namespace Server
public static void SendHairEquipUpdatePacket(this NetState ns, Mobile m, uint hairSerial, int itemId, int hue, Layer layer)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -42,7 +42,7 @@ namespace Server
public static void SendRemoveHairPacket(this NetState ns, uint hairSerial)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -8317,7 +8317,7 @@ namespace Server
{
var ns = m_NetState;
if (ns == null)
if (ns.CannotSendPackets())
{
return false;
}

View file

@ -481,7 +481,7 @@ namespace Server.Network
public void Send(ReadOnlySpan<byte> span)
{
if (span == null || Connection == null || BlockAllPackets)
if (span == null || this.CannotSendPackets())
{
return;
}

View file

@ -18,526 +18,525 @@ using System.Collections.Generic;
using System.IO;
using CV = Server.ClientVersion;
namespace Server.Network
namespace Server.Network;
public static class IncomingAccountPackets
{
public static class IncomingAccountPackets
private const int m_AuthIDWindowSize = 128;
private static readonly Dictionary<int, AuthIDPersistence> m_AuthIDWindow =
new(m_AuthIDWindowSize);
internal struct AuthIDPersistence
{
private const int m_AuthIDWindowSize = 128;
private static readonly Dictionary<int, AuthIDPersistence> m_AuthIDWindow =
new(m_AuthIDWindowSize);
public DateTime Age;
public readonly ClientVersion Version;
internal struct AuthIDPersistence
public AuthIDPersistence(ClientVersion v)
{
public DateTime Age;
public readonly ClientVersion Version;
Age = Core.Now;
Version = v;
}
}
public AuthIDPersistence(ClientVersion v)
{
Age = Core.Now;
Version = v;
}
public static void Configure()
{
IncomingPackets.Register(0x00, 104, false, CreateCharacter);
IncomingPackets.Register(0x5D, 73, false, PlayCharacter);
IncomingPackets.Register(0x80, 62, false, AccountLogin);
IncomingPackets.Register(0x83, 39, false, DeleteCharacter);
IncomingPackets.Register(0x91, 65, false, GameLogin);
IncomingPackets.Register(0xA0, 3, false, PlayServer);
IncomingPackets.Register(0xBB, 9, false, AccountID);
IncomingPackets.Register(0xBD, 0, false, ClientVersion);
IncomingPackets.Register(0xBE, 0, true, AssistVersion);
IncomingPackets.Register(0xCF, 0, false, AccountLogin);
IncomingPackets.Register(0xE1, 0, false, ClientType);
IncomingPackets.Register(0xEF, 21, false, LoginServerSeed);
IncomingPackets.Register(0xF8, 106, false, CreateCharacter);
}
public static void CreateCharacter(NetState state, CircularBufferReader reader, ref int packetLength)
{
reader.Seek(9, SeekOrigin.Current);
/*
var unk1 = reader.ReadInt32();
var unk2 = reader.ReadInt32();
int unk3 = reader.ReadByte();
*/
var name = reader.ReadAscii(30);
reader.Seek(2, SeekOrigin.Current);
var flags = reader.ReadInt32();
reader.Seek(8, SeekOrigin.Current);
int prof = reader.ReadByte();
reader.Seek(15, SeekOrigin.Current);
int genderRace = reader.ReadByte();
var stats = new StatNameValue[]
{
new(StatType.Str, reader.ReadByte()),
new(StatType.Dex, reader.ReadByte()),
new(StatType.Int, reader.ReadByte())
};
var skills = new SkillNameValue[state.NewCharacterCreation ? 4 : 3];
skills[0] = new SkillNameValue((SkillName)reader.ReadByte(), reader.ReadByte());
skills[1] = new SkillNameValue((SkillName)reader.ReadByte(), reader.ReadByte());
skills[2] = new SkillNameValue((SkillName)reader.ReadByte(), reader.ReadByte());
if (state.NewCharacterCreation)
{
skills[3] = new SkillNameValue((SkillName)reader.ReadByte(), reader.ReadByte());
}
public static void Configure()
int hue = reader.ReadUInt16();
int hairVal = reader.ReadInt16();
int hairHue = reader.ReadInt16();
int hairValf = reader.ReadInt16();
int hairHuef = reader.ReadInt16();
reader.ReadByte();
int cityIndex = reader.ReadByte();
reader.Seek(8, SeekOrigin.Current);
/*
var charSlot = reader.ReadInt32();
var clientIP = reader.ReadInt32();
*/
int shirtHue = reader.ReadInt16();
int pantsHue = reader.ReadInt16();
/*
Pre-7.0.0.0:
0x00, 0x01 -> Human Male, Human Female
0x02, 0x03 -> Elf Male, Elf Female
Post-7.0.0.0:
0x00, 0x01
0x02, 0x03 -> Human Male, Human Female
0x04, 0x05 -> Elf Male, Elf Female
0x05, 0x06 -> Gargoyle Male, Gargoyle Female
*/
var female = genderRace % 2 != 0;
var raceID = state.StygianAbyss ? (byte)(genderRace < 4 ? 0 : genderRace / 2 - 1) : (byte)(genderRace / 2);
Race race = Race.Races[raceID] ?? Race.DefaultRace;
var info = state.CityInfo;
var a = state.Account;
if (info == null || a == null || cityIndex < 0 || cityIndex >= info.Length)
{
IncomingPackets.Register(0x00, 104, false, CreateCharacter);
IncomingPackets.Register(0x5D, 73, false, PlayCharacter);
IncomingPackets.Register(0x80, 62, false, AccountLogin);
IncomingPackets.Register(0x83, 39, false, DeleteCharacter);
IncomingPackets.Register(0x91, 65, false, GameLogin);
IncomingPackets.Register(0xA0, 3, false, PlayServer);
IncomingPackets.Register(0xBB, 9, false, AccountID);
IncomingPackets.Register(0xBD, 0, false, ClientVersion);
IncomingPackets.Register(0xBE, 0, true, AssistVersion);
IncomingPackets.Register(0xCF, 0, false, AccountLogin);
IncomingPackets.Register(0xE1, 0, false, ClientType);
IncomingPackets.Register(0xEF, 21, false, LoginServerSeed);
IncomingPackets.Register(0xF8, 106, false, CreateCharacter);
state.Disconnect("Invalid city selected during character creation.");
return;
}
public static void CreateCharacter(NetState state, CircularBufferReader reader, ref int packetLength)
// Check if anyone is using this account
for (var i = 0; i < a.Length; ++i)
{
reader.Seek(9, SeekOrigin.Current);
/*
var unk1 = reader.ReadInt32();
var unk2 = reader.ReadInt32();
int unk3 = reader.ReadByte();
*/
var name = reader.ReadAscii(30);
var check = a[i];
reader.Seek(2, SeekOrigin.Current);
var flags = reader.ReadInt32();
reader.Seek(8, SeekOrigin.Current);
int prof = reader.ReadByte();
reader.Seek(15, SeekOrigin.Current);
int genderRace = reader.ReadByte();
var stats = new StatNameValue[]
if (check != null && check.Map != Map.Internal)
{
new(StatType.Str, reader.ReadByte()),
new(StatType.Dex, reader.ReadByte()),
new(StatType.Int, reader.ReadByte())
};
var skills = new SkillNameValue[state.NewCharacterCreation ? 4 : 3];
skills[0] = new SkillNameValue((SkillName)reader.ReadByte(), reader.ReadByte());
skills[1] = new SkillNameValue((SkillName)reader.ReadByte(), reader.ReadByte());
skills[2] = new SkillNameValue((SkillName)reader.ReadByte(), reader.ReadByte());
if (state.NewCharacterCreation)
{
skills[3] = new SkillNameValue((SkillName)reader.ReadByte(), reader.ReadByte());
}
int hue = reader.ReadUInt16();
int hairVal = reader.ReadInt16();
int hairHue = reader.ReadInt16();
int hairValf = reader.ReadInt16();
int hairHuef = reader.ReadInt16();
reader.ReadByte();
int cityIndex = reader.ReadByte();
reader.Seek(8, SeekOrigin.Current);
/*
var charSlot = reader.ReadInt32();
var clientIP = reader.ReadInt32();
*/
int shirtHue = reader.ReadInt16();
int pantsHue = reader.ReadInt16();
/*
Pre-7.0.0.0:
0x00, 0x01 -> Human Male, Human Female
0x02, 0x03 -> Elf Male, Elf Female
Post-7.0.0.0:
0x00, 0x01
0x02, 0x03 -> Human Male, Human Female
0x04, 0x05 -> Elf Male, Elf Female
0x05, 0x06 -> Gargoyle Male, Gargoyle Female
*/
var female = genderRace % 2 != 0;
var raceID = state.StygianAbyss ? (byte)(genderRace < 4 ? 0 : genderRace / 2 - 1) : (byte)(genderRace / 2);
Race race = Race.Races[raceID] ?? Race.DefaultRace;
var info = state.CityInfo;
var a = state.Account;
if (info == null || a == null || cityIndex < 0 || cityIndex >= info.Length)
{
state.Disconnect("Invalid city selected during character creation.");
state.LogInfo("Account in use");
state.SendPopupMessage(PMMessage.CharInWorld);
return;
}
// Check if anyone is using this account
for (var i = 0; i < a.Length; ++i)
{
var check = a[i];
if (check != null && check.Map != Map.Internal)
{
state.LogInfo("Account in use");
state.SendPopupMessage(PMMessage.CharInWorld);
return;
}
}
state.Flags = (ClientFlags)flags;
var args = new CharacterCreatedEventArgs(
state,
a,
name,
female,
hue,
stats,
info[cityIndex],
skills,
shirtHue,
pantsHue,
hairVal,
hairHue,
hairValf,
hairHuef,
prof,
race
);
state.SendClientVersionRequest();
state.BlockAllPackets = true;
EventSink.InvokeCharacterCreated(args);
var m = args.Mobile;
if (m != null)
{
state.Mobile = m;
m.NetState = state;
new LoginTimer(state, m).Start();
}
else
{
state.BlockAllPackets = false;
state.Disconnect("Character creation blocked.");
}
}
public static void DeleteCharacter(NetState state, CircularBufferReader reader, ref int packetLength)
state.Flags = (ClientFlags)flags;
var args = new CharacterCreatedEventArgs(
state,
a,
name,
female,
hue,
stats,
info[cityIndex],
skills,
shirtHue,
pantsHue,
hairVal,
hairHue,
hairValf,
hairHuef,
prof,
race
);
state.SendClientVersionRequest();
state.BlockAllPackets = true;
EventSink.InvokeCharacterCreated(args);
var m = args.Mobile;
if (m != null)
{
reader.Seek(30, SeekOrigin.Current);
var index = reader.ReadInt32();
EventSink.InvokeDeleteRequest(state, index);
}
public static void AccountID(NetState state, CircularBufferReader reader, ref int packetLength)
{
}
public static void AssistVersion(NetState state, CircularBufferReader reader, ref int packetLength)
{
var unk = reader.ReadInt32();
var av = reader.ReadAscii();
}
public static void ClientVersion(NetState state, CircularBufferReader reader, ref int packetLength)
{
var version = state.Version = new CV(reader.ReadAscii());
EventSink.InvokeClientVersionReceived(state, version);
}
public static void ClientType(NetState state, CircularBufferReader reader, ref int packetLength)
{
reader.ReadUInt16();
int type = reader.ReadUInt16();
var version = state.Version = new CV(reader.ReadAscii());
EventSink.InvokeClientVersionReceived(state, version);
}
public static void PlayCharacter(NetState state, CircularBufferReader reader, ref int packetLength)
{
reader.Seek(4, SeekOrigin.Current); // 0xEDEDEDED
reader.Seek(30, SeekOrigin.Current); // var name = reader.ReadAscii(30);
reader.Seek(2, SeekOrigin.Current);
var flags = reader.ReadInt32();
reader.Seek(24, SeekOrigin.Current);
var charSlot = reader.ReadInt32();
reader.Seek(4, SeekOrigin.Current); // var clientIP = reader.ReadInt32();
var a = state.Account;
if (a == null || charSlot < 0 || charSlot >= a.Length)
{
state.Disconnect("Invalid character slot selected.");
return;
}
var m = a[charSlot];
// Check if anyone is using this account
for (var i = 0; i < a.Length; ++i)
{
var check = a[i];
if (check != null && check.Map != Map.Internal && check != m)
{
state.LogInfo("Account in use");
state.SendPopupMessage(PMMessage.CharInWorld);
return;
}
}
if (m == null)
{
state.Disconnect("Empty character slot selected.");
return;
}
m.NetState?.Disconnect("Character selected for a player already logged in.");
state.SendClientVersionRequest();
state.BlockAllPackets = true;
state.Flags = (ClientFlags)flags;
state.Mobile = m;
m.NetState = state;
new LoginTimer(state, m).Start();
}
public static void DoLogin(this NetState state, Mobile m)
else
{
state.SendLoginConfirmation(m);
state.BlockAllPackets = false;
state.Disconnect("Character creation blocked.");
}
}
state.SendMapChange(m.Map);
public static void DeleteCharacter(NetState state, CircularBufferReader reader, ref int packetLength)
{
reader.Seek(30, SeekOrigin.Current);
var index = reader.ReadInt32();
state.SendMapPatches();
EventSink.InvokeDeleteRequest(state, index);
}
state.SendSeasonChange((byte)m.GetSeason(), true);
public static void AccountID(NetState state, CircularBufferReader reader, ref int packetLength)
{
}
public static void AssistVersion(NetState state, CircularBufferReader reader, ref int packetLength)
{
var unk = reader.ReadInt32();
var av = reader.ReadAscii();
}
public static void ClientVersion(NetState state, CircularBufferReader reader, ref int packetLength)
{
var version = state.Version = new CV(reader.ReadAscii());
EventSink.InvokeClientVersionReceived(state, version);
}
public static void ClientType(NetState state, CircularBufferReader reader, ref int packetLength)
{
reader.ReadUInt16();
int type = reader.ReadUInt16();
var version = state.Version = new CV(reader.ReadAscii());
EventSink.InvokeClientVersionReceived(state, version);
}
public static void PlayCharacter(NetState state, CircularBufferReader reader, ref int packetLength)
{
reader.Seek(4, SeekOrigin.Current); // 0xEDEDEDED
reader.Seek(30, SeekOrigin.Current); // var name = reader.ReadAscii(30);
reader.Seek(2, SeekOrigin.Current);
var flags = reader.ReadInt32();
reader.Seek(24, SeekOrigin.Current);
var charSlot = reader.ReadInt32();
reader.Seek(4, SeekOrigin.Current); // var clientIP = reader.ReadInt32();
var a = state.Account;
if (a == null || charSlot < 0 || charSlot >= a.Length)
{
state.Disconnect("Invalid character slot selected.");
return;
}
var m = a[charSlot];
// Check if anyone is using this account
for (var i = 0; i < a.Length; ++i)
{
var check = a[i];
if (check != null && check.Map != Map.Internal && check != m)
{
state.LogInfo("Account in use");
state.SendPopupMessage(PMMessage.CharInWorld);
return;
}
}
if (m == null)
{
state.Disconnect("Empty character slot selected.");
return;
}
m.NetState?.Disconnect("Character selected for a player already logged in.");
state.SendClientVersionRequest();
state.BlockAllPackets = true;
state.Flags = (ClientFlags)flags;
state.Mobile = m;
m.NetState = state;
new LoginTimer(state, m).Start();
}
public static void DoLogin(this NetState state, Mobile m)
{
state.SendLoginConfirmation(m);
state.SendMapChange(m.Map);
state.SendMapPatches();
state.SendSeasonChange((byte)m.GetSeason(), true);
state.SendSupportedFeature();
state.Sequence = 0;
state.SendMobileUpdate(m);
state.SendMobileUpdate(m);
m.CheckLightLevels(true);
state.SendMobileUpdate(m);
state.SendMobileIncoming(m, m);
state.SendMobileStatus(m);
state.SendSetWarMode(m.Warmode);
m.SendEverything();
state.SendSupportedFeature();
state.SendMobileUpdate(m);
state.SendMobileStatus(m);
state.SendSetWarMode(m.Warmode);
state.SendMobileIncoming(m, m);
state.SendLoginComplete();
state.SendCurrentTime();
state.SendSeasonChange((byte)m.GetSeason(), true);
state.SendMapChange(m.Map);
state.SendPlayMusic(m.Region.Music);
EventSink.InvokeLogin(m);
}
private static int GenerateAuthID(this NetState state)
{
if (m_AuthIDWindow.Count == m_AuthIDWindowSize)
{
var oldestID = 0;
var oldest = DateTime.MaxValue;
foreach (var (key, authId) in m_AuthIDWindow)
{
if (authId.Age < oldest)
{
oldestID = key;
oldest = authId.Age;
}
}
m_AuthIDWindow.Remove(oldestID);
}
int authID;
do
{
authID = Utility.Random(1, int.MaxValue - 1);
if (Utility.RandomBool())
{
authID |= 1 << 31;
}
} while (m_AuthIDWindow.ContainsKey(authID));
m_AuthIDWindow[authID] = new AuthIDPersistence(state.Version);
return authID;
}
public static void GameLogin(NetState state, CircularBufferReader reader, ref int packetLength)
{
// TODO: Connection throttling
if (state.SentFirstPacket)
{
state.Disconnect("Duplicate game login packet received.");
return;
}
state.SentFirstPacket = true;
var authID = reader.ReadInt32();
if (!m_AuthIDWindow.TryGetValue(authID, out var ap))
{
state.LogInfo("Invalid client detected, disconnecting...");
state.Disconnect("Unable to find auth id.");
}
if (state._authId != 0 && authID != state._authId || state._authId == 0 && authID != state._seed)
{
state.LogInfo("Invalid client detected, disconnecting...");
state.Disconnect("Invalid auth id in game login packet.");
return;
}
m_AuthIDWindow.Remove(authID);
state.Version = ap.Version;
var username = reader.ReadAscii(30);
var password = reader.ReadAscii(30);
var e = new GameLoginEventArgs(state, username, password);
EventSink.InvokeGameLogin(e);
if (e.Accepted)
{
state.CityInfo = e.CityInfo;
// Comment out these lines to turn off huffman compression
state.CompressionEnabled = true;
state.PacketEncoder ??= NetworkCompression.Compress;
state.SendSupportedFeature();
state.SendCharacterList();
}
else
{
state.Disconnect("Login rejected by GameLogin packet handler.");
}
}
state.Sequence = 0;
public static void PlayServer(NetState state, CircularBufferReader reader, ref int packetLength)
{
int index = reader.ReadInt16();
var info = state.ServerInfo;
var a = state.Account;
state.SendMobileUpdate(m);
state.SendMobileUpdate(m);
if (info == null || a == null || index < 0 || index >= info.Length)
{
state.Disconnect("Invalid server selected.");
}
else
{
var si = info[index];
m.CheckLightLevels(true);
state._authId = GenerateAuthID(state);
state.SendMobileUpdate(m);
state.SentFirstPacket = false;
state.SendPlayServerAck(si, state._authId);
}
}
state.SendMobileIncoming(m, m);
public static void LoginServerSeed(NetState state, CircularBufferReader reader, ref int packetLength)
{
state._seed = reader.ReadInt32();
state.Seeded = true;
state.SendMobileStatus(m);
state.SendSetWarMode(m.Warmode);
m.SendEverything();
state.SendSupportedFeature();
state.SendMobileUpdate(m);
state.SendMobileStatus(m);
state.SendSetWarMode(m.Warmode);
state.SendMobileIncoming(m, m);
state.SendLoginComplete();
state.SendCurrentTime();
state.SendSeasonChange((byte)m.GetSeason(), true);
state.SendMapChange(m.Map);
state.SendPlayMusic(m.Region.Music);
EventSink.InvokeLogin(m);
if (state._seed == 0)
{
state.LogInfo("Invalid client detected, disconnecting");
state.Disconnect("Duplicate seed sent.");
return;
}
private static int GenerateAuthID(this NetState state)
var clientMaj = reader.ReadInt32();
var clientMin = reader.ReadInt32();
var clientRev = reader.ReadInt32();
var clientPat = reader.ReadInt32();
state.Version = new ClientVersion(clientMaj, clientMin, clientRev, clientPat);
}
public static void AccountLogin(NetState state, CircularBufferReader reader, ref int packetLength)
{
// TODO: Throttle Connection
if (state.SentFirstPacket)
{
if (m_AuthIDWindow.Count == m_AuthIDWindowSize)
{
var oldestID = 0;
var oldest = DateTime.MaxValue;
foreach (var (key, authId) in m_AuthIDWindow)
{
if (authId.Age < oldest)
{
oldestID = key;
oldest = authId.Age;
}
}
m_AuthIDWindow.Remove(oldestID);
}
int authID;
do
{
authID = Utility.Random(1, int.MaxValue - 1);
if (Utility.RandomBool())
{
authID |= 1 << 31;
}
} while (m_AuthIDWindow.ContainsKey(authID));
m_AuthIDWindow[authID] = new AuthIDPersistence(state.Version);
return authID;
state.Disconnect("Duplicate account login packet sent.");
return;
}
public static void GameLogin(NetState state, CircularBufferReader reader, ref int packetLength)
state.SentFirstPacket = true;
var username = reader.ReadAscii(30);
var password = reader.ReadAscii(30);
var accountLoginEventArgs = new AccountLoginEventArgs(state, username, password);
EventSink.InvokeAccountLogin(accountLoginEventArgs);
if (accountLoginEventArgs.Accepted)
{
// TODO: Connection throttling
var serverListEventArgs = new ServerListEventArgs(state, state.Account);
if (state.SentFirstPacket)
EventSink.InvokeServerList(serverListEventArgs);
if (serverListEventArgs.Rejected)
{
state.Disconnect("Duplicate game login packet received.");
return;
}
state.SentFirstPacket = true;
var authID = reader.ReadInt32();
if (!m_AuthIDWindow.TryGetValue(authID, out var ap))
{
state.LogInfo("Invalid client detected, disconnecting...");
state.Disconnect("Unable to find auth id.");
}
if (state._authId != 0 && authID != state._authId || state._authId == 0 && authID != state._seed)
{
state.LogInfo("Invalid client detected, disconnecting...");
state.Disconnect("Invalid auth id in game login packet.");
return;
}
m_AuthIDWindow.Remove(authID);
state.Version = ap.Version;
var username = reader.ReadAscii(30);
var password = reader.ReadAscii(30);
var e = new GameLoginEventArgs(state, username, password);
EventSink.InvokeGameLogin(e);
if (e.Accepted)
{
state.CityInfo = e.CityInfo;
// Comment out these lines to turn off huffman compression
state.CompressionEnabled = true;
state.PacketEncoder ??= NetworkCompression.Compress;
state.SendSupportedFeature();
state.SendCharacterList();
state.Account = null;
AccountLogin_ReplyRej(state, ALRReason.BadComm);
}
else
{
state.Disconnect("Login rejected by GameLogin packet handler.");
state.ServerInfo = serverListEventArgs.Servers.ToArray();
state.SendAccountLoginAck();
}
}
public static void PlayServer(NetState state, CircularBufferReader reader, ref int packetLength)
else
{
int index = reader.ReadInt16();
var info = state.ServerInfo;
var a = state.Account;
AccountLogin_ReplyRej(state, accountLoginEventArgs.RejectReason);
}
}
if (info == null || a == null || index < 0 || index >= info.Length)
{
state.Disconnect("Invalid server selected.");
}
else
{
var si = info[index];
private static void AccountLogin_ReplyRej(this NetState state, ALRReason reason)
{
state.SendAccountLoginRejected(reason);
state.Disconnect($"Account login rejected due to {reason}");
}
state._authId = GenerateAuthID(state);
private class LoginTimer : Timer
{
private readonly Mobile _mobile;
private readonly NetState _state;
state.SentFirstPacket = false;
state.SendPlayServerAck(si, state._authId);
}
public LoginTimer(NetState state, Mobile m) : base(TimeSpan.FromMilliseconds(64), TimeSpan.FromMilliseconds(64))
{
_state = state;
_mobile = m;
}
public static void LoginServerSeed(NetState state, CircularBufferReader reader, ref int packetLength)
protected override void OnTick()
{
state._seed = reader.ReadInt32();
state.Seeded = true;
if (state._seed == 0)
if (_state != null)
{
state.LogInfo("Invalid client detected, disconnecting");
state.Disconnect("Duplicate seed sent.");
return;
}
var clientMaj = reader.ReadInt32();
var clientMin = reader.ReadInt32();
var clientRev = reader.ReadInt32();
var clientPat = reader.ReadInt32();
state.Version = new ClientVersion(clientMaj, clientMin, clientRev, clientPat);
}
public static void AccountLogin(NetState state, CircularBufferReader reader, ref int packetLength)
{
// TODO: Throttle Connection
if (state.SentFirstPacket)
{
state.Disconnect("Duplicate account login packet sent.");
return;
}
state.SentFirstPacket = true;
var username = reader.ReadAscii(30);
var password = reader.ReadAscii(30);
var accountLoginEventArgs = new AccountLoginEventArgs(state, username, password);
EventSink.InvokeAccountLogin(accountLoginEventArgs);
if (accountLoginEventArgs.Accepted)
{
var serverListEventArgs = new ServerListEventArgs(state, state.Account);
EventSink.InvokeServerList(serverListEventArgs);
if (serverListEventArgs.Rejected)
if (_state.Account == null)
{
state.Account = null;
AccountLogin_ReplyRej(state, ALRReason.BadComm);
_state.Disconnect("Account was deleted during the login process.");
}
else
else if (_mobile == null)
{
state.ServerInfo = serverListEventArgs.Servers.ToArray();
state.SendAccountLoginAck();
_state.Disconnect("Player was deleted during the login process.");
}
else if (_state.Version != null)
{
_state.BlockAllPackets = false;
DoLogin(_state, _mobile);
}
else // Waiting to receive the client version before we continue the login process
{
return;
}
}
else
{
AccountLogin_ReplyRej(state, accountLoginEventArgs.RejectReason);
}
}
private static void AccountLogin_ReplyRej(this NetState state, ALRReason reason)
{
state.SendAccountLoginRejected(reason);
state.Disconnect($"Account login rejected due to {reason}");
}
private class LoginTimer : Timer
{
private readonly Mobile _mobile;
private readonly NetState _state;
public LoginTimer(NetState state, Mobile m) : base(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0))
{
_state = state;
_mobile = m;
}
protected override void OnTick()
{
if (_state != null)
{
if (_state.Account == null)
{
_state.Disconnect("Account was deleted during the login process.");
}
else if (_mobile == null)
{
_state.Disconnect("Player was deleted during the login process.");
}
else if (_state.Version != null)
{
_state.BlockAllPackets = false;
DoLogin(_state, _mobile);
}
else // Waiting to receive the client version before we continue the login process
{
return;
}
}
Stop();
}
Stop();
}
}
}

View file

@ -13,97 +13,160 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
namespace Server.Network
namespace Server.Network;
public static class IncomingEntityPackets
{
public static class IncomingEntityPackets
public static bool SingleClickProps { get; set; }
public static void Configure()
{
public static bool SingleClickProps { get; set; }
IncomingPackets.Register(0x06, 5, true, UseReq);
IncomingPackets.Register(0x09, 5, true, LookReq);
IncomingPackets.Register(0xB6, 9, true, ObjectHelpRequest);
IncomingPackets.Register(0xD6, 0, true, BatchQueryProperties);
}
public static void Configure()
public static void ObjectHelpRequest(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
var serial = (Serial)reader.ReadUInt32();
int unk = reader.ReadByte();
var lang = reader.ReadAscii(3);
if (serial.IsItem)
{
IncomingPackets.Register(0x06, 5, true, UseReq);
IncomingPackets.Register(0x09, 5, true, LookReq);
IncomingPackets.Register(0xB6, 9, true, ObjectHelpRequest);
IncomingPackets.Register(0xD6, 0, true, BatchQueryProperties);
}
var item = World.FindItem(serial);
public static void ObjectHelpRequest(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
var serial = (Serial)reader.ReadUInt32();
int unk = reader.ReadByte();
var lang = reader.ReadAscii(3);
if (serial.IsItem)
if (item != null && from.Map == item.Map && Utility.InUpdateRange(item.GetWorldLocation(), from.Location) &&
from.CanSee(item))
{
var item = World.FindItem(serial);
if (item != null && from.Map == item.Map && Utility.InUpdateRange(item.GetWorldLocation(), from.Location) &&
from.CanSee(item))
{
item.OnHelpRequest(from);
}
}
else if (serial.IsMobile)
{
var m = World.FindMobile(serial);
if (m != null && from.Map == m.Map && Utility.InUpdateRange(m.Location, from.Location) && from.CanSee(m))
{
m.OnHelpRequest(m);
}
item.OnHelpRequest(from);
}
}
public static void UseReq(NetState state, CircularBufferReader reader, ref int packetLength)
else if (serial.IsMobile)
{
var from = state.Mobile;
var m = World.FindMobile(serial);
if (from.AccessLevel >= AccessLevel.Counselor || Core.TickCount - from.NextActionTime >= 0)
if (m != null && from.Map == m.Map && Utility.InUpdateRange(m.Location, from.Location) && from.CanSee(m))
{
var value = reader.ReadUInt32();
m.OnHelpRequest(m);
}
}
}
if ((value & ~0x7FFFFFFF) != 0)
{
from.OnPaperdollRequest();
}
else
{
Serial s = (Serial)value;
public static void UseReq(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (s.IsMobile)
{
var m = World.FindMobile(s);
if (from.AccessLevel >= AccessLevel.Counselor || Core.TickCount - from.NextActionTime >= 0)
{
var value = reader.ReadUInt32();
if (m?.Deleted == false)
{
from.Use(m);
}
}
else if (s.IsItem)
{
var item = World.FindItem(s);
if (item?.Deleted == false)
{
from.Use(item);
}
}
}
from.NextActionTime = Core.TickCount + Mobile.ActionDelay;
if ((value & ~0x7FFFFFFF) != 0)
{
from.OnPaperdollRequest();
}
else
{
from.SendActionMessage();
Serial s = (Serial)value;
if (s.IsMobile)
{
var m = World.FindMobile(s);
if (m?.Deleted == false)
{
from.Use(m);
}
}
else if (s.IsItem)
{
var item = World.FindItem(s);
if (item?.Deleted == false)
{
from.Use(item);
}
}
}
from.NextActionTime = Core.TickCount + Mobile.ActionDelay;
}
else
{
from.SendActionMessage();
}
}
public static void LookReq(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
Serial s = (Serial)reader.ReadUInt32();
if (s.IsMobile)
{
var m = World.FindMobile(s);
if (m != null && from.CanSee(m) && Utility.InUpdateRange(from.Location, m.Location))
{
if (SingleClickProps)
{
m.OnAosSingleClick(from);
}
else
{
if (from.Region.OnSingleClick(from, m))
{
m.OnSingleClick(from);
}
}
}
}
public static void LookReq(NetState state, CircularBufferReader reader, ref int packetLength)
else if (s.IsItem)
{
var from = state.Mobile;
var item = World.FindItem(s);
if (item?.Deleted == false && from.CanSee(item) &&
Utility.InUpdateRange(from.Location, item.GetWorldLocation()))
{
if (SingleClickProps)
{
item.OnAosSingleClick(from);
}
else if (from.Region.OnSingleClick(from, item))
{
if (item.Parent is Item parentItem)
{
parentItem.OnSingleClickContained(from, item);
}
item.OnSingleClick(from);
}
}
}
}
public static void BatchQueryProperties(NetState state, CircularBufferReader reader, ref int packetLength)
{
if (!ObjectPropertyList.Enabled)
{
return;
}
var from = state.Mobile;
var length = reader.Remaining;
if (length % 4 != 0)
{
return;
}
while (reader.Remaining > 0)
{
Serial s = (Serial)reader.ReadUInt32();
if (s.IsMobile)
@ -112,17 +175,7 @@ namespace Server.Network
if (m != null && from.CanSee(m) && Utility.InUpdateRange(from.Location, m.Location))
{
if (SingleClickProps)
{
m.OnAosSingleClick(from);
}
else
{
if (from.Region.OnSingleClick(from, m))
{
m.OnSingleClick(from);
}
}
m.SendPropertiesTo(from);
}
}
else if (s.IsItem)
@ -132,61 +185,7 @@ namespace Server.Network
if (item?.Deleted == false && from.CanSee(item) &&
Utility.InUpdateRange(from.Location, item.GetWorldLocation()))
{
if (SingleClickProps)
{
item.OnAosSingleClick(from);
}
else if (from.Region.OnSingleClick(from, item))
{
if (item.Parent is Item parentItem)
{
parentItem.OnSingleClickContained(from, item);
}
item.OnSingleClick(from);
}
}
}
}
public static void BatchQueryProperties(NetState state, CircularBufferReader reader, ref int packetLength)
{
if (!ObjectPropertyList.Enabled)
{
return;
}
var from = state.Mobile;
var length = reader.Remaining;
if (length % 4 != 0)
{
return;
}
while (reader.Remaining > 0)
{
Serial s = (Serial)reader.ReadUInt32();
if (s.IsMobile)
{
var m = World.FindMobile(s);
if (m != null && from.CanSee(m) && Utility.InUpdateRange(from.Location, m.Location))
{
m.SendPropertiesTo(from);
}
}
else if (s.IsItem)
{
var item = World.FindItem(s);
if (item?.Deleted == false && from.CanSee(item) &&
Utility.InUpdateRange(from.Location, item.GetWorldLocation()))
{
item.SendPropertiesTo(from);
}
item.SendPropertiesTo(from);
}
}
}

View file

@ -16,502 +16,501 @@
using System.Collections.Generic;
using Server.ContextMenus;
namespace Server.Network
namespace Server.Network;
public static class IncomingExtendedCommandPackets
{
public static class IncomingExtendedCommandPackets
private static readonly PacketHandler[] m_ExtendedHandlersLow = new PacketHandler[0x100];
private static readonly Dictionary<int, PacketHandler> m_ExtendedHandlersHigh = new();
// TODO: Change to outside configuration
public static int[] ValidAnimations { get; set; } =
{
private static readonly PacketHandler[] m_ExtendedHandlersLow = new PacketHandler[0x100];
private static readonly Dictionary<int, PacketHandler> m_ExtendedHandlersHigh = new();
6, 21, 32, 33,
100, 101, 102, 103,
104, 105, 106, 107,
108, 109, 110, 111,
112, 113, 114, 115,
116, 117, 118, 119,
120, 121, 123, 124,
125, 126, 127, 128
};
// TODO: Change to outside configuration
public static int[] ValidAnimations { get; set; } =
public static void Configure()
{
IncomingPackets.Register(0xBF, 0, true, ExtendedCommand);
RegisterExtended(0x05, false, ScreenSize);
RegisterExtended(0x06, true, PartyMessage);
RegisterExtended(0x09, true, DisarmRequest);
RegisterExtended(0x0A, true, StunRequest);
RegisterExtended(0x0B, false, Language);
RegisterExtended(0x0C, true, CloseStatus);
RegisterExtended(0x0E, true, Animate);
RegisterExtended(0x0F, false, Empty); // What's this?
RegisterExtended(0x10, true, QueryProperties);
RegisterExtended(0x13, true, ContextMenuRequest);
RegisterExtended(0x15, true, ContextMenuResponse);
RegisterExtended(0x1A, true, StatLockChange);
RegisterExtended(0x1C, true, CastSpell);
RegisterExtended(0x24, false, UnhandledBF);
RegisterExtended(0x2C, true, BandageTarget);
RegisterExtended(0x2D, true, TargetedSpell);
RegisterExtended(0x2E, true, TargetedSkillUse);
RegisterExtended(0x30, true, TargetByResourceMacro);
RegisterExtended(0x32, true, ToggleFlying);
}
private static void UnhandledBF(NetState state, CircularBufferReader reader, ref int packetLength)
{
}
public static void Empty(NetState state, CircularBufferReader reader, ref int packetLength)
{
}
public static void RegisterExtended(int packetID, bool ingame, OnPacketReceive onReceive)
{
if (packetID >= 0 && packetID < 0x100)
{
6, 21, 32, 33,
100, 101, 102, 103,
104, 105, 106, 107,
108, 109, 110, 111,
112, 113, 114, 115,
116, 117, 118, 119,
120, 121, 123, 124,
125, 126, 127, 128
};
public static void Configure()
m_ExtendedHandlersLow[packetID] = new PacketHandler(packetID, 0, ingame, onReceive);
}
else
{
IncomingPackets.Register(0xBF, 0, true, ExtendedCommand);
m_ExtendedHandlersHigh[packetID] = new PacketHandler(packetID, 0, ingame, onReceive);
}
}
RegisterExtended(0x05, false, ScreenSize);
RegisterExtended(0x06, true, PartyMessage);
RegisterExtended(0x09, true, DisarmRequest);
RegisterExtended(0x0A, true, StunRequest);
RegisterExtended(0x0B, false, Language);
RegisterExtended(0x0C, true, CloseStatus);
RegisterExtended(0x0E, true, Animate);
RegisterExtended(0x0F, false, Empty); // What's this?
RegisterExtended(0x10, true, QueryProperties);
RegisterExtended(0x13, true, ContextMenuRequest);
RegisterExtended(0x15, true, ContextMenuResponse);
RegisterExtended(0x1A, true, StatLockChange);
RegisterExtended(0x1C, true, CastSpell);
RegisterExtended(0x24, false, UnhandledBF);
RegisterExtended(0x2C, true, BandageTarget);
RegisterExtended(0x2D, true, TargetedSpell);
RegisterExtended(0x2E, true, TargetedSkillUse);
RegisterExtended(0x30, true, TargetByResourceMacro);
RegisterExtended(0x32, true, ToggleFlying);
public static PacketHandler GetExtendedHandler(int packetID)
{
if (packetID >= 0 && packetID < 0x100)
{
return m_ExtendedHandlersLow[packetID];
}
private static void UnhandledBF(NetState state, CircularBufferReader reader, ref int packetLength)
m_ExtendedHandlersHigh.TryGetValue(packetID, out var handler);
return handler;
}
public static void RemoveExtendedHandler(int packetID)
{
if (packetID >= 0 && packetID < 0x100)
{
m_ExtendedHandlersLow[packetID] = null;
}
else
{
m_ExtendedHandlersHigh.Remove(packetID);
}
}
public static void ExtendedCommand(NetState state, CircularBufferReader reader, ref int packetLength)
{
int packetId = reader.ReadUInt16();
var ph = GetExtendedHandler(packetId);
if (ph == null)
{
reader.Trace(state);
return;
}
public static void Empty(NetState state, CircularBufferReader reader, ref int packetLength)
{
}
public static void RegisterExtended(int packetID, bool ingame, OnPacketReceive onReceive)
{
if (packetID >= 0 && packetID < 0x100)
{
m_ExtendedHandlersLow[packetID] = new PacketHandler(packetID, 0, ingame, onReceive);
}
else
{
m_ExtendedHandlersHigh[packetID] = new PacketHandler(packetID, 0, ingame, onReceive);
}
}
public static PacketHandler GetExtendedHandler(int packetID)
{
if (packetID >= 0 && packetID < 0x100)
{
return m_ExtendedHandlersLow[packetID];
}
m_ExtendedHandlersHigh.TryGetValue(packetID, out var handler);
return handler;
}
public static void RemoveExtendedHandler(int packetID)
{
if (packetID >= 0 && packetID < 0x100)
{
m_ExtendedHandlersLow[packetID] = null;
}
else
{
m_ExtendedHandlersHigh.Remove(packetID);
}
}
public static void ExtendedCommand(NetState state, CircularBufferReader reader, ref int packetLength)
{
int packetId = reader.ReadUInt16();
var ph = GetExtendedHandler(packetId);
if (ph == null)
{
reader.Trace(state);
return;
}
if (ph.Ingame && state.Mobile?.Deleted != false)
{
if (state.Mobile == null)
{
state.LogInfo(
"Sent in-game packet (0xBFx{0:X2}) before having been attached to a mobile",
packetId
);
}
state.Disconnect($"Sent in-game packet(0xBFx{packetId:X2}) but mobile is deleted.");
}
else
{
ph.OnReceive(state, reader, ref packetLength);
}
}
public static void ScreenSize(NetState state, CircularBufferReader reader, ref int packetLength)
{
var width = reader.ReadInt32();
var unk = reader.ReadInt32();
}
// TODO: Move out of the core
public static void PartyMessage(NetState state, CircularBufferReader reader, ref int packetLength)
if (ph.Ingame && state.Mobile?.Deleted != false)
{
if (state.Mobile == null)
{
return;
state.LogInfo(
"Sent in-game packet (0xBFx{0:X2}) before having been attached to a mobile",
packetId
);
}
switch (reader.ReadByte())
state.Disconnect($"Sent in-game packet(0xBFx{packetId:X2}) but mobile is deleted.");
}
else
{
ph.OnReceive(state, reader, ref packetLength);
}
}
public static void ScreenSize(NetState state, CircularBufferReader reader, ref int packetLength)
{
var width = reader.ReadInt32();
var unk = reader.ReadInt32();
}
// TODO: Move out of the core
public static void PartyMessage(NetState state, CircularBufferReader reader, ref int packetLength)
{
if (state.Mobile == null)
{
return;
}
switch (reader.ReadByte())
{
case 0x01:
PartyMessage_AddMember(state, reader, ref packetLength);
break;
case 0x02:
PartyMessage_RemoveMember(state, reader, ref packetLength);
break;
case 0x03:
PartyMessage_PrivateMessage(state, reader, ref packetLength);
break;
case 0x04:
PartyMessage_PublicMessage(state, reader, ref packetLength);
break;
case 0x06:
PartyMessage_SetCanLoot(state, reader, ref packetLength);
break;
case 0x08:
PartyMessage_Accept(state, reader, ref packetLength);
break;
case 0x09:
PartyMessage_Decline(state, reader, ref packetLength);
break;
default:
reader.Trace(state);
break;
}
}
public static void PartyMessage_AddMember(NetState state, CircularBufferReader reader, ref int packetLength)
{
PartyCommands.Handler?.OnAdd(state.Mobile);
}
public static void PartyMessage_RemoveMember(NetState state, CircularBufferReader reader, ref int packetLength)
{
PartyCommands.Handler?.OnRemove(state.Mobile, World.FindMobile((Serial)reader.ReadUInt32()));
}
public static void PartyMessage_PrivateMessage(NetState state, CircularBufferReader reader, ref int packetLength)
{
PartyCommands.Handler?.OnPrivateMessage(
state.Mobile,
World.FindMobile((Serial)reader.ReadUInt32()),
reader.ReadBigUniSafe()
);
}
public static void PartyMessage_PublicMessage(NetState state, CircularBufferReader reader, ref int packetLength)
{
PartyCommands.Handler?.OnPublicMessage(state.Mobile, reader.ReadBigUniSafe());
}
public static void PartyMessage_SetCanLoot(NetState state, CircularBufferReader reader, ref int packetLength)
{
PartyCommands.Handler?.OnSetCanLoot(state.Mobile, reader.ReadBoolean());
}
public static void PartyMessage_Accept(NetState state, CircularBufferReader reader, ref int packetLength)
{
PartyCommands.Handler?.OnAccept(state.Mobile, World.FindMobile((Serial)reader.ReadUInt32()));
}
public static void PartyMessage_Decline(NetState state, CircularBufferReader reader, ref int packetLength)
{
PartyCommands.Handler?.OnDecline(state.Mobile, World.FindMobile((Serial)reader.ReadUInt32()));
}
public static void Animate(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
var action = reader.ReadInt32();
var ok = false;
for (var i = 0; !ok && i < ValidAnimations.Length; ++i)
{
ok = action == ValidAnimations[i];
}
if (ok && from.Alive && from.Body.IsHuman && !from.Mounted)
{
from.Animate(action, 7, 1, true, false, 0);
}
}
public static void CastSpell(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
Item spellbook = reader.ReadInt16() == 1 ? World.FindItem((Serial)reader.ReadUInt32()) : null;
var spellID = reader.ReadInt16() - 1;
EventSink.InvokeCastSpellRequest(from, spellID, spellbook);
}
public static void ToggleFlying(NetState state, CircularBufferReader reader, ref int packetLength)
{
state.Mobile?.ToggleFlying();
}
public static void StunRequest(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
EventSink.InvokeStunRequest(from);
}
public static void DisarmRequest(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
EventSink.InvokeDisarmRequest(from);
}
public static void StatLockChange(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
int stat = reader.ReadByte();
int lockValue = reader.ReadByte();
if (lockValue > 2)
{
lockValue = 0;
}
switch (stat)
{
case 0:
from.StrLock = (StatLockType)lockValue;
break;
case 1:
from.DexLock = (StatLockType)lockValue;
break;
case 2:
from.IntLock = (StatLockType)lockValue;
break;
}
}
public static void CloseStatus(NetState state, CircularBufferReader reader, ref int packetLength)
{
var serial = (Serial)reader.ReadUInt32();
}
public static void Language(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
from.Language = reader.ReadAscii(4);
}
public static void QueryProperties(NetState state, CircularBufferReader reader, ref int packetLength)
{
if (!ObjectPropertyList.Enabled)
{
return;
}
var from = state.Mobile;
Serial s = (Serial)reader.ReadUInt32();
if (s.IsMobile)
{
var m = World.FindMobile(s);
if (m != null && from.CanSee(m) && Utility.InUpdateRange(from.Location, m.Location))
{
case 0x01:
PartyMessage_AddMember(state, reader, ref packetLength);
break;
case 0x02:
PartyMessage_RemoveMember(state, reader, ref packetLength);
break;
case 0x03:
PartyMessage_PrivateMessage(state, reader, ref packetLength);
break;
case 0x04:
PartyMessage_PublicMessage(state, reader, ref packetLength);
break;
case 0x06:
PartyMessage_SetCanLoot(state, reader, ref packetLength);
break;
case 0x08:
PartyMessage_Accept(state, reader, ref packetLength);
break;
case 0x09:
PartyMessage_Decline(state, reader, ref packetLength);
break;
default:
reader.Trace(state);
break;
m.SendPropertiesTo(from);
}
}
public static void PartyMessage_AddMember(NetState state, CircularBufferReader reader, ref int packetLength)
else if (s.IsItem)
{
PartyCommands.Handler?.OnAdd(state.Mobile);
}
var item = World.FindItem(s);
public static void PartyMessage_RemoveMember(NetState state, CircularBufferReader reader, ref int packetLength)
{
PartyCommands.Handler?.OnRemove(state.Mobile, World.FindMobile((Serial)reader.ReadUInt32()));
}
public static void PartyMessage_PrivateMessage(NetState state, CircularBufferReader reader, ref int packetLength)
{
PartyCommands.Handler?.OnPrivateMessage(
state.Mobile,
World.FindMobile((Serial)reader.ReadUInt32()),
reader.ReadBigUniSafe()
);
}
public static void PartyMessage_PublicMessage(NetState state, CircularBufferReader reader, ref int packetLength)
{
PartyCommands.Handler?.OnPublicMessage(state.Mobile, reader.ReadBigUniSafe());
}
public static void PartyMessage_SetCanLoot(NetState state, CircularBufferReader reader, ref int packetLength)
{
PartyCommands.Handler?.OnSetCanLoot(state.Mobile, reader.ReadBoolean());
}
public static void PartyMessage_Accept(NetState state, CircularBufferReader reader, ref int packetLength)
{
PartyCommands.Handler?.OnAccept(state.Mobile, World.FindMobile((Serial)reader.ReadUInt32()));
}
public static void PartyMessage_Decline(NetState state, CircularBufferReader reader, ref int packetLength)
{
PartyCommands.Handler?.OnDecline(state.Mobile, World.FindMobile((Serial)reader.ReadUInt32()));
}
public static void Animate(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
if (item?.Deleted == false && from.CanSee(item) &&
Utility.InUpdateRange(from.Location, item.GetWorldLocation()))
{
return;
}
var action = reader.ReadInt32();
var ok = false;
for (var i = 0; !ok && i < ValidAnimations.Length; ++i)
{
ok = action == ValidAnimations[i];
}
if (ok && from.Alive && from.Body.IsHuman && !from.Mounted)
{
from.Animate(action, 7, 1, true, false, 0);
}
}
public static void CastSpell(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
Item spellbook = reader.ReadInt16() == 1 ? World.FindItem((Serial)reader.ReadUInt32()) : null;
var spellID = reader.ReadInt16() - 1;
EventSink.InvokeCastSpellRequest(from, spellID, spellbook);
}
public static void ToggleFlying(NetState state, CircularBufferReader reader, ref int packetLength)
{
state.Mobile?.ToggleFlying();
}
public static void StunRequest(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
EventSink.InvokeStunRequest(from);
}
public static void DisarmRequest(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
EventSink.InvokeDisarmRequest(from);
}
public static void StatLockChange(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
int stat = reader.ReadByte();
int lockValue = reader.ReadByte();
if (lockValue > 2)
{
lockValue = 0;
}
switch (stat)
{
case 0:
from.StrLock = (StatLockType)lockValue;
break;
case 1:
from.DexLock = (StatLockType)lockValue;
break;
case 2:
from.IntLock = (StatLockType)lockValue;
break;
}
}
public static void CloseStatus(NetState state, CircularBufferReader reader, ref int packetLength)
{
var serial = (Serial)reader.ReadUInt32();
}
public static void Language(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
from.Language = reader.ReadAscii(4);
}
public static void QueryProperties(NetState state, CircularBufferReader reader, ref int packetLength)
{
if (!ObjectPropertyList.Enabled)
{
return;
}
var from = state.Mobile;
Serial s = (Serial)reader.ReadUInt32();
if (s.IsMobile)
{
var m = World.FindMobile(s);
if (m != null && from.CanSee(m) && Utility.InUpdateRange(from.Location, m.Location))
{
m.SendPropertiesTo(from);
}
}
else if (s.IsItem)
{
var item = World.FindItem(s);
if (item?.Deleted == false && from.CanSee(item) &&
Utility.InUpdateRange(from.Location, item.GetWorldLocation()))
{
item.SendPropertiesTo(from);
}
}
}
public static void ContextMenuResponse(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
var menu = from.ContextMenu;
from.ContextMenu = null;
if (menu != null && from == menu.From)
{
var entity = World.FindEntity((Serial)reader.ReadUInt32());
if (entity != null && entity == menu.Target && from.CanSee(entity))
{
Point3D p;
if (entity is Mobile)
{
p = entity.Location;
}
else if (entity is Item item)
{
p = item.GetWorldLocation();
}
else
{
return;
}
int index = reader.ReadUInt16();
if (index >= 0 && index < menu.Entries.Length)
{
var e = menu.Entries[index];
var range = e.Range;
if (range == -1)
{
range = 18;
}
if (e.Enabled && from.InRange(p, range))
{
e.OnClick();
}
}
}
}
}
public static void ContextMenuRequest(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
var target = World.FindEntity((Serial)reader.ReadUInt32());
if (from != null && target != null && from.Map == target.Map && from.CanSee(target))
{
var item = target as Item;
var checkLocation = item?.GetWorldLocation() ?? target.Location;
if (!(Utility.InUpdateRange(from.Location, checkLocation) && from.CheckContextMenuDisplay(target)))
{
return;
}
var c = new ContextMenu(from, target);
if (c.Entries.Length > 0)
{
if (item?.RootParent is Mobile mobile && mobile != from && mobile.AccessLevel >= from.AccessLevel)
{
for (var i = 0; i < c.Entries.Length; ++i)
{
if (!c.Entries[i].NonLocalUse)
{
c.Entries[i].Enabled = false;
}
}
}
from.ContextMenu = c;
}
}
}
public static void BandageTarget(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
if (from.AccessLevel >= AccessLevel.Counselor || Core.TickCount - from.NextActionTime >= 0)
{
var bandage = World.FindItem((Serial)reader.ReadUInt32());
if (bandage == null)
{
return;
}
var target = World.FindMobile((Serial)reader.ReadUInt32());
if (target == null)
{
return;
}
EventSink.InvokeBandageTargetRequest(from, bandage, target);
from.NextActionTime = Core.TickCount + Mobile.ActionDelay;
}
else
{
from.SendActionMessage();
}
}
public static void TargetedSpell(NetState state, CircularBufferReader reader, ref int packetLength)
{
var spellId = (short)(reader.ReadInt16() - 1); // zero based;
EventSink.InvokeTargetedSpell(state.Mobile, World.FindEntity((Serial)reader.ReadUInt32()), spellId);
}
public static void TargetedSkillUse(NetState state, CircularBufferReader reader, ref int packetLength)
{
var skillId = reader.ReadInt16();
EventSink.InvokeTargetedSkillUse(state.Mobile, World.FindEntity((Serial)reader.ReadUInt32()), skillId);
}
public static void TargetByResourceMacro(NetState state, CircularBufferReader reader, ref int packetLength)
{
var serial = (Serial)reader.ReadUInt32();
if (serial.IsItem)
{
EventSink.InvokeTargetByResourceMacro(state.Mobile, World.FindItem(serial), reader.ReadInt16());
item.SendPropertiesTo(from);
}
}
}
public static void ContextMenuResponse(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
var menu = from.ContextMenu;
from.ContextMenu = null;
if (menu != null && from == menu.From)
{
var entity = World.FindEntity((Serial)reader.ReadUInt32());
if (entity != null && entity == menu.Target && from.CanSee(entity))
{
Point3D p;
if (entity is Mobile)
{
p = entity.Location;
}
else if (entity is Item item)
{
p = item.GetWorldLocation();
}
else
{
return;
}
int index = reader.ReadUInt16();
if (index >= 0 && index < menu.Entries.Length)
{
var e = menu.Entries[index];
var range = e.Range;
if (range == -1)
{
range = 18;
}
if (e.Enabled && from.InRange(p, range))
{
e.OnClick();
}
}
}
}
}
public static void ContextMenuRequest(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
var target = World.FindEntity((Serial)reader.ReadUInt32());
if (from != null && target != null && from.Map == target.Map && from.CanSee(target))
{
var item = target as Item;
var checkLocation = item?.GetWorldLocation() ?? target.Location;
if (!(Utility.InUpdateRange(from.Location, checkLocation) && from.CheckContextMenuDisplay(target)))
{
return;
}
var c = new ContextMenu(from, target);
if (c.Entries.Length > 0)
{
if (item?.RootParent is Mobile mobile && mobile != from && mobile.AccessLevel >= from.AccessLevel)
{
for (var i = 0; i < c.Entries.Length; ++i)
{
if (!c.Entries[i].NonLocalUse)
{
c.Entries[i].Enabled = false;
}
}
}
from.ContextMenu = c;
}
}
}
public static void BandageTarget(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
if (from.AccessLevel >= AccessLevel.Counselor || Core.TickCount - from.NextActionTime >= 0)
{
var bandage = World.FindItem((Serial)reader.ReadUInt32());
if (bandage == null)
{
return;
}
var target = World.FindMobile((Serial)reader.ReadUInt32());
if (target == null)
{
return;
}
EventSink.InvokeBandageTargetRequest(from, bandage, target);
from.NextActionTime = Core.TickCount + Mobile.ActionDelay;
}
else
{
from.SendActionMessage();
}
}
public static void TargetedSpell(NetState state, CircularBufferReader reader, ref int packetLength)
{
var spellId = (short)(reader.ReadInt16() - 1); // zero based;
EventSink.InvokeTargetedSpell(state.Mobile, World.FindEntity((Serial)reader.ReadUInt32()), spellId);
}
public static void TargetedSkillUse(NetState state, CircularBufferReader reader, ref int packetLength)
{
var skillId = reader.ReadInt16();
EventSink.InvokeTargetedSkillUse(state.Mobile, World.FindEntity((Serial)reader.ReadUInt32()), skillId);
}
public static void TargetByResourceMacro(NetState state, CircularBufferReader reader, ref int packetLength)
{
var serial = (Serial)reader.ReadUInt32();
if (serial.IsItem)
{
EventSink.InvokeTargetByResourceMacro(state.Mobile, World.FindItem(serial), reader.ReadInt16());
}
}
}

View file

@ -13,18 +13,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
namespace Server.Network
{
public static class IncomingHousePackets
{
public static void Configure()
{
IncomingPackets.Register(0xFB, 2, false, ShowPublicHouseContent);
}
namespace Server.Network;
public static void ShowPublicHouseContent(NetState state, CircularBufferReader reader, ref int packetLength)
{
var showPublicHouseContent = reader.ReadBoolean();
}
public static class IncomingHousePackets
{
public static void Configure()
{
IncomingPackets.Register(0xFB, 2, false, ShowPublicHouseContent);
}
public static void ShowPublicHouseContent(NetState state, CircularBufferReader reader, ref int packetLength)
{
var showPublicHouseContent = reader.ReadBoolean();
}
}

View file

@ -17,159 +17,158 @@ using System.Collections.Generic;
using System.IO;
using Server.Items;
namespace Server.Network
namespace Server.Network;
public static class IncomingItemPackets
{
public static class IncomingItemPackets
public static void Configure()
{
public static void Configure()
IncomingPackets.Register(0x07, 7, true, LiftReq);
IncomingPackets.Register(0x08, 15, true, DropReq);
IncomingPackets.Register(0x13, 10, true, EquipReq);
IncomingPackets.Register(0xEC, 0, false, EquipMacro);
IncomingPackets.Register(0xED, 0, false, UnequipMacro);
}
public static void LiftReq(NetState state, CircularBufferReader reader, ref int packetLength)
{
var serial = (Serial)reader.ReadUInt32();
int amount = reader.ReadUInt16();
var item = World.FindItem(serial);
state.Mobile.Lift(item, amount, out _, out _);
}
public static void EquipReq(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
var item = from.Holding;
var valid = item != null && item.HeldBy == from && item.Map == Map.Internal;
from.Holding = null;
if (!valid)
{
IncomingPackets.Register(0x07, 7, true, LiftReq);
IncomingPackets.Register(0x08, 15, true, DropReq);
IncomingPackets.Register(0x13, 10, true, EquipReq);
IncomingPackets.Register(0xEC, 0, false, EquipMacro);
IncomingPackets.Register(0xED, 0, false, UnequipMacro);
return;
}
public static void LiftReq(NetState state, CircularBufferReader reader, ref int packetLength)
{
var serial = (Serial)reader.ReadUInt32();
int amount = reader.ReadUInt16();
var item = World.FindItem(serial);
reader.Seek(5, SeekOrigin.Current);
var to = World.FindMobile((Serial)reader.ReadUInt32()) ?? from;
state.Mobile.Lift(item, amount, out _, out _);
if (!to.AllowEquipFrom(from) || !to.EquipItem(item))
{
item.Bounce(from);
}
public static void EquipReq(NetState state, CircularBufferReader reader, ref int packetLength)
item.ClearBounce();
}
public static void DropReq(NetState state, CircularBufferReader reader, ref int packetLength)
{
reader.ReadInt32(); // serial, ignored
int x = reader.ReadInt16();
int y = reader.ReadInt16();
int z = reader.ReadSByte();
if (state.ContainerGridLines)
{
var from = state.Mobile;
var item = from.Holding;
var valid = item != null && item.HeldBy == from && item.Map == Map.Internal;
from.Holding = null;
if (!valid)
{
return;
}
reader.Seek(5, SeekOrigin.Current);
var to = World.FindMobile((Serial)reader.ReadUInt32()) ?? from;
if (!to.AllowEquipFrom(from) || !to.EquipItem(item))
{
item.Bounce(from);
}
item.ClearBounce();
}
public static void DropReq(NetState state, CircularBufferReader reader, ref int packetLength)
{
reader.ReadInt32(); // serial, ignored
int x = reader.ReadInt16();
int y = reader.ReadInt16();
int z = reader.ReadSByte();
if (state.ContainerGridLines)
{
reader.ReadByte(); // Grid Location?
}
else
{
packetLength -= 1;
}
Serial dest = (Serial)reader.ReadUInt32();
var loc = new Point3D(x, y, z);
var from = state.Mobile;
if (dest.IsMobile)
{
from.Drop(World.FindMobile(dest), loc);
}
else if (dest.IsItem)
{
var item = World.FindItem(dest);
if (item is BaseMulti multi && multi.AllowsRelativeDrop)
{
loc.m_X += multi.X;
loc.m_Y += multi.Y;
from.Drop(loc);
}
else
{
from.Drop(item, loc);
}
}
else
{
from.Drop(loc);
}
}
public static void DropReq6017(NetState state, CircularBufferReader reader, ref int packetLength)
{
reader.ReadInt32(); // serial, ignored
int x = reader.ReadInt16();
int y = reader.ReadInt16();
int z = reader.ReadSByte();
reader.ReadByte(); // Grid Location?
Serial dest = (Serial)reader.ReadUInt32();
}
else
{
packetLength -= 1;
}
var loc = new Point3D(x, y, z);
Serial dest = (Serial)reader.ReadUInt32();
var from = state.Mobile;
var loc = new Point3D(x, y, z);
if (dest.IsMobile)
var from = state.Mobile;
if (dest.IsMobile)
{
from.Drop(World.FindMobile(dest), loc);
}
else if (dest.IsItem)
{
var item = World.FindItem(dest);
if (item is BaseMulti multi && multi.AllowsRelativeDrop)
{
from.Drop(World.FindMobile(dest), loc);
}
else if (dest.IsItem)
{
var item = World.FindItem(dest);
if (item is BaseMulti multi && multi.AllowsRelativeDrop)
{
loc.m_X += multi.X;
loc.m_Y += multi.Y;
from.Drop(loc);
}
else
{
from.Drop(item, loc);
}
loc.m_X += multi.X;
loc.m_Y += multi.Y;
from.Drop(loc);
}
else
{
from.Drop(loc);
from.Drop(item, loc);
}
}
public static void EquipMacro(NetState state, CircularBufferReader reader, ref int packetLength)
else
{
int count = reader.ReadByte();
var serialList = new List<Serial>(count);
for (var i = 0; i < count; ++i)
{
serialList.Add((Serial)reader.ReadUInt32());
}
EventSink.InvokeEquipMacro(state.Mobile, serialList);
}
public static void UnequipMacro(NetState state, CircularBufferReader reader, ref int packetLength)
{
int count = reader.ReadByte();
var layers = new List<Layer>(count);
for (var i = 0; i < count; ++i)
{
layers.Add((Layer)reader.ReadUInt16());
}
EventSink.InvokeUnequipMacro(state.Mobile, layers);
from.Drop(loc);
}
}
public static void DropReq6017(NetState state, CircularBufferReader reader, ref int packetLength)
{
reader.ReadInt32(); // serial, ignored
int x = reader.ReadInt16();
int y = reader.ReadInt16();
int z = reader.ReadSByte();
reader.ReadByte(); // Grid Location?
Serial dest = (Serial)reader.ReadUInt32();
var loc = new Point3D(x, y, z);
var from = state.Mobile;
if (dest.IsMobile)
{
from.Drop(World.FindMobile(dest), loc);
}
else if (dest.IsItem)
{
var item = World.FindItem(dest);
if (item is BaseMulti multi && multi.AllowsRelativeDrop)
{
loc.m_X += multi.X;
loc.m_Y += multi.Y;
from.Drop(loc);
}
else
{
from.Drop(item, loc);
}
}
else
{
from.Drop(loc);
}
}
public static void EquipMacro(NetState state, CircularBufferReader reader, ref int packetLength)
{
int count = reader.ReadByte();
var serialList = new List<Serial>(count);
for (var i = 0; i < count; ++i)
{
serialList.Add((Serial)reader.ReadUInt32());
}
EventSink.InvokeEquipMacro(state.Mobile, serialList);
}
public static void UnequipMacro(NetState state, CircularBufferReader reader, ref int packetLength)
{
int count = reader.ReadByte();
var layers = new List<Layer>(count);
for (var i = 0; i < count; ++i)
{
layers.Add((Layer)reader.ReadUInt16());
}
EventSink.InvokeUnequipMacro(state.Mobile, layers);
}
}

View file

@ -15,146 +15,145 @@
using System;
namespace Server.Network
namespace Server.Network;
[Flags]
public enum MessageType
{
[Flags]
public enum MessageType
Regular = 0x00,
System = 0x01,
Emote = 0x02,
Label = 0x06,
Focus = 0x07,
Whisper = 0x08,
Yell = 0x09,
Spell = 0x0A,
Guild = 0x0D,
Alliance = 0x0E,
Command = 0x0F,
Encoded = 0xC0
}
public static class IncomingMessagePackets
{
private static readonly KeywordList m_KeywordList = new();
public static void Configure()
{
Regular = 0x00,
System = 0x01,
Emote = 0x02,
Label = 0x06,
Focus = 0x07,
Whisper = 0x08,
Yell = 0x09,
Spell = 0x0A,
Guild = 0x0D,
Alliance = 0x0E,
Command = 0x0F,
Encoded = 0xC0
IncomingPackets.Register(0x03, 0, true, AsciiSpeech);
IncomingPackets.Register(0xAD, 0, true, UnicodeSpeech);
}
public static class IncomingMessagePackets
public static void AsciiSpeech(NetState state, CircularBufferReader reader, ref int packetLength)
{
private static readonly KeywordList m_KeywordList = new();
var from = state.Mobile;
public static void Configure()
if (from == null)
{
IncomingPackets.Register(0x03, 0, true, AsciiSpeech);
IncomingPackets.Register(0xAD, 0, true, UnicodeSpeech);
return;
}
public static void AsciiSpeech(NetState state, CircularBufferReader reader, ref int packetLength)
var type = (MessageType)reader.ReadByte();
int hue = reader.ReadInt16();
reader.ReadInt16(); // font
var text = reader.ReadAsciiSafe().Trim();
if (text.Length is <= 0 or > 128)
{
var from = state.Mobile;
if (from == null)
{
return;
}
var type = (MessageType)reader.ReadByte();
int hue = reader.ReadInt16();
reader.ReadInt16(); // font
var text = reader.ReadAsciiSafe().Trim();
if (text.Length is <= 0 or > 128)
{
return;
}
if (!Enum.IsDefined(typeof(MessageType), type))
{
type = MessageType.Regular;
}
from.DoSpeech(text, Array.Empty<int>(), type, Utility.ClipDyedHue(hue));
return;
}
public static void UnicodeSpeech(NetState state, CircularBufferReader reader, ref int packetLength)
if (!Enum.IsDefined(typeof(MessageType), type))
{
var from = state.Mobile;
type = MessageType.Regular;
}
if (from == null)
from.DoSpeech(text, Array.Empty<int>(), type, Utility.ClipDyedHue(hue));
}
public static void UnicodeSpeech(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
var type = (MessageType)reader.ReadByte();
int hue = reader.ReadInt16();
reader.ReadInt16(); // font
var lang = reader.ReadAscii(4);
string text;
var isEncoded = (type & MessageType.Encoded) != 0;
int[] keywords;
if (isEncoded)
{
int value = reader.ReadInt16();
var count = (value & 0xFFF0) >> 4;
var hold = value & 0xF;
if (count is < 0 or > 50)
{
return;
}
var type = (MessageType)reader.ReadByte();
int hue = reader.ReadInt16();
reader.ReadInt16(); // font
var lang = reader.ReadAscii(4);
string text;
var keyList = m_KeywordList;
var isEncoded = (type & MessageType.Encoded) != 0;
int[] keywords;
if (isEncoded)
for (var i = 0; i < count; ++i)
{
int value = reader.ReadInt16();
var count = (value & 0xFFF0) >> 4;
var hold = value & 0xF;
int speechID;
if (count is < 0 or > 50)
if ((i & 1) == 0)
{
return;
hold <<= 8;
hold |= reader.ReadByte();
speechID = hold;
hold = 0;
}
else
{
value = reader.ReadInt16();
speechID = (value & 0xFFF0) >> 4;
hold = value & 0xF;
}
var keyList = m_KeywordList;
for (var i = 0; i < count; ++i)
if (!keyList.Contains(speechID))
{
int speechID;
if ((i & 1) == 0)
{
hold <<= 8;
hold |= reader.ReadByte();
speechID = hold;
hold = 0;
}
else
{
value = reader.ReadInt16();
speechID = (value & 0xFFF0) >> 4;
hold = value & 0xF;
}
if (!keyList.Contains(speechID))
{
keyList.Add(speechID);
}
keyList.Add(speechID);
}
text = reader.ReadUTF8Safe();
keywords = keyList.ToArray();
}
else
{
text = reader.ReadBigUniSafe();
keywords = Array.Empty<int>();
}
text = text.Trim();
text = reader.ReadUTF8Safe();
if (text.Length is <= 0 or > 128)
{
return;
}
type &= ~MessageType.Encoded;
if (!Enum.IsDefined(typeof(MessageType), type))
{
type = MessageType.Regular;
}
from.Language = lang;
from.DoSpeech(text, keywords, type, Utility.ClipDyedHue(hue));
keywords = keyList.ToArray();
}
else
{
text = reader.ReadBigUniSafe();
keywords = Array.Empty<int>();
}
text = text.Trim();
if (text.Length is <= 0 or > 128)
{
return;
}
type &= ~MessageType.Encoded;
if (!Enum.IsDefined(typeof(MessageType), type))
{
type = MessageType.Regular;
}
from.Language = lang;
from.DoSpeech(text, keywords, type, Utility.ClipDyedHue(hue));
}
}

View file

@ -15,152 +15,151 @@
using Server.Items;
namespace Server.Network
namespace Server.Network;
public static class IncomingMobilePackets
{
public static class IncomingMobilePackets
public static void Configure()
{
public static void Configure()
IncomingPackets.Register(0x75, 35, true, RenameRequest);
IncomingPackets.Register(0x98, 0, true, MobileNameRequest);
IncomingPackets.Register(0xB8, 0, true, ProfileReq);
IncomingPackets.Register(0x6F, 0, true, SecureTrade);
}
public static void RenameRequest(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
var targ = World.FindMobile((Serial)reader.ReadUInt32());
if (targ != null)
{
IncomingPackets.Register(0x75, 35, true, RenameRequest);
IncomingPackets.Register(0x98, 0, true, MobileNameRequest);
IncomingPackets.Register(0xB8, 0, true, ProfileReq);
IncomingPackets.Register(0x6F, 0, true, SecureTrade);
EventSink.InvokeRenameRequest(from, targ, reader.ReadAsciiSafe());
}
}
public static void MobileNameRequest(NetState state, CircularBufferReader reader, ref int packetLength)
{
var m = World.FindMobile((Serial)reader.ReadUInt32());
if (m != null && Utility.InUpdateRange(state.Mobile.Location, m.Location) && state.Mobile.CanSee(m))
{
state.SendMobileName(m);
}
}
public static void ProfileReq(NetState state, CircularBufferReader reader, ref int packetLength)
{
int type = reader.ReadByte();
var serial = (Serial)reader.ReadUInt32();
var beholder = state.Mobile;
var beheld = World.FindMobile(serial);
if (beheld == null)
{
return;
}
public static void RenameRequest(NetState state, CircularBufferReader reader, ref int packetLength)
switch (type)
{
var from = state.Mobile;
var targ = World.FindMobile((Serial)reader.ReadUInt32());
case 0x00: // display request
{
EventSink.InvokeProfileRequest(beholder, beheld);
if (targ != null)
{
EventSink.InvokeRenameRequest(from, targ, reader.ReadAsciiSafe());
}
}
public static void MobileNameRequest(NetState state, CircularBufferReader reader, ref int packetLength)
{
var m = World.FindMobile((Serial)reader.ReadUInt32());
if (m != null && Utility.InUpdateRange(state.Mobile.Location, m.Location) && state.Mobile.CanSee(m))
{
state.SendMobileName(m);
}
}
public static void ProfileReq(NetState state, CircularBufferReader reader, ref int packetLength)
{
int type = reader.ReadByte();
var serial = (Serial)reader.ReadUInt32();
var beholder = state.Mobile;
var beheld = World.FindMobile(serial);
if (beheld == null)
{
return;
}
switch (type)
{
case 0x00: // display request
{
EventSink.InvokeProfileRequest(beholder, beheld);
break;
}
case 0x01: // edit request
{
reader.ReadInt16(); // Skip
int length = reader.ReadUInt16();
if (length > 511)
{
return;
}
var text = reader.ReadBigUni(length);
EventSink.InvokeChangeProfileRequest(beholder, beheld, text);
break;
}
}
}
public static void SecureTrade(NetState state, CircularBufferReader reader, ref int packetLength)
{
switch (reader.ReadByte())
{
case 1: // Cancel
{
var serial = (Serial)reader.ReadUInt32();
if (World.FindItem(serial) is SecureTradeContainer cont && cont.Trade != null &&
(cont.Trade.From.Mobile == state.Mobile || cont.Trade.To.Mobile == state.Mobile))
{
cont.Trade.Cancel();
}
break;
}
case 2: // Check
{
var serial = (Serial)reader.ReadUInt32();
if (World.FindItem(serial) is SecureTradeContainer cont)
{
var trade = cont.Trade;
var value = reader.ReadInt32() != 0;
if (trade != null)
{
if (trade.From.Mobile == state.Mobile)
{
trade.From.Accepted = value;
trade.Update();
}
else if (trade.To.Mobile == state.Mobile)
{
trade.To.Accepted = value;
trade.Update();
}
}
}
break;
}
case 3: // Update Gold
{
var serial = (Serial)reader.ReadUInt32();
if (World.FindItem(serial) is SecureTradeContainer cont)
{
var gold = reader.ReadInt32();
var plat = reader.ReadInt32();
var trade = cont.Trade;
if (trade != null)
{
if (trade.From.Mobile == state.Mobile)
{
trade.From.Gold = gold;
trade.From.Plat = plat;
trade.UpdateFromCurrency();
}
else if (trade.To.Mobile == state.Mobile)
{
trade.To.Gold = gold;
trade.To.Plat = plat;
trade.UpdateToCurrency();
}
}
}
}
break;
}
}
case 0x01: // edit request
{
reader.ReadInt16(); // Skip
int length = reader.ReadUInt16();
if (length > 511)
{
return;
}
var text = reader.ReadBigUni(length);
EventSink.InvokeChangeProfileRequest(beholder, beheld, text);
break;
}
}
}
public static void SecureTrade(NetState state, CircularBufferReader reader, ref int packetLength)
{
switch (reader.ReadByte())
{
case 1: // Cancel
{
var serial = (Serial)reader.ReadUInt32();
if (World.FindItem(serial) is SecureTradeContainer cont && cont.Trade != null &&
(cont.Trade.From.Mobile == state.Mobile || cont.Trade.To.Mobile == state.Mobile))
{
cont.Trade.Cancel();
}
break;
}
case 2: // Check
{
var serial = (Serial)reader.ReadUInt32();
if (World.FindItem(serial) is SecureTradeContainer cont)
{
var trade = cont.Trade;
var value = reader.ReadInt32() != 0;
if (trade != null)
{
if (trade.From.Mobile == state.Mobile)
{
trade.From.Accepted = value;
trade.Update();
}
else if (trade.To.Mobile == state.Mobile)
{
trade.To.Accepted = value;
trade.Update();
}
}
}
break;
}
case 3: // Update Gold
{
var serial = (Serial)reader.ReadUInt32();
if (World.FindItem(serial) is SecureTradeContainer cont)
{
var gold = reader.ReadInt32();
var plat = reader.ReadInt32();
var trade = cont.Trade;
if (trade != null)
{
if (trade.From.Mobile == state.Mobile)
{
trade.From.Gold = gold;
trade.From.Plat = plat;
trade.UpdateFromCurrency();
}
else if (trade.To.Mobile == state.Mobile)
{
trade.To.Gold = gold;
trade.To.Plat = plat;
trade.UpdateToCurrency();
}
}
}
}
break;
}
}
}

View file

@ -13,88 +13,49 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
namespace Server.Network
namespace Server.Network;
public static class IncomingMovementPackets
{
public static class IncomingMovementPackets
public static void Configure()
{
public static void Configure()
IncomingPackets.Register(0x02, 7, true, MovementReq);
// Not used by OSI, and interferes with ClassicUO/Razor protocol extensions
// IncomingPackets.Register(0xF0, 0, true, NewMovementReq);
// IncomingPackets.Register(0xF1, 9, true, TimeSyncReq);
}
public static void NewMovementReq(NetState ns, CircularBufferReader reader)
{
var from = ns.Mobile;
if (from == null)
{
IncomingPackets.Register(0x02, 7, true, MovementReq);
// Not used by OSI, and interferes with ClassicUO/Razor protocol extensions
// IncomingPackets.Register(0xF0, 0, true, NewMovementReq);
// IncomingPackets.Register(0xF1, 9, true, TimeSyncReq);
return;
}
public static void NewMovementReq(NetState ns, CircularBufferReader reader)
var steps = reader.ReadByte();
for (int i = 0; i < steps; i++)
{
var from = ns.Mobile;
if (from == null)
{
return;
}
var steps = reader.ReadByte();
for (int i = 0; i < steps; i++)
{
var t1 = reader.ReadUInt64(); // start time?
var t2 = reader.ReadUInt64(); // end time?
int seq = reader.ReadByte();
var dir = (Direction)reader.ReadByte();
var mode = reader.ReadInt32(); // 1 = walk, 2 = run
if (mode == 2)
{
dir |= Direction.Running;
}
// Location
reader.ReadInt32(); // x
reader.ReadInt32(); // y
reader.ReadInt32(); // z
if (ns.Sequence == 0 && seq != 0 || !from.Move(dir))
{
ns.SendMovementRej(seq, from);
ns.Sequence = 0;
}
else
{
++seq;
if (seq == 256)
{
seq = 1;
}
ns.Sequence = seq;
}
}
}
public static void TimeSyncReq(NetState ns, CircularBufferReader reader)
{
reader.ReadUInt64(); // Client Time?
ns.SendTimeSyncResponse();
}
public static void MovementReq(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
var dir = (Direction)reader.ReadByte();
var t1 = reader.ReadUInt64(); // start time?
var t2 = reader.ReadUInt64(); // end time?
int seq = reader.ReadByte();
var key = reader.ReadUInt32();
if (state.Sequence == 0 && seq != 0 || !from.Move(dir))
var dir = (Direction)reader.ReadByte();
var mode = reader.ReadInt32(); // 1 = walk, 2 = run
if (mode == 2)
{
state.SendMovementRej(seq, from);
state.Sequence = 0;
dir |= Direction.Running;
}
// Location
reader.ReadInt32(); // x
reader.ReadInt32(); // y
reader.ReadInt32(); // z
if (ns.Sequence == 0 && seq != 0 || !from.Move(dir))
{
ns.SendMovementRej(seq, from);
ns.Sequence = 0;
}
else
{
@ -105,8 +66,46 @@ namespace Server.Network
seq = 1;
}
state.Sequence = seq;
ns.Sequence = seq;
}
}
}
public static void TimeSyncReq(NetState ns, CircularBufferReader reader)
{
reader.ReadUInt64(); // Client Time?
ns.SendTimeSyncResponse();
}
public static void MovementReq(NetState state, CircularBufferReader reader, ref int packetLength)
{
var from = state.Mobile;
if (from == null)
{
return;
}
var dir = (Direction)reader.ReadByte();
int seq = reader.ReadByte();
var key = reader.ReadUInt32();
if (state.Sequence == 0 && seq != 0 || !from.Move(dir))
{
state.SendMovementRej(seq, from);
state.Sequence = 0;
}
else
{
++seq;
if (seq == 256)
{
seq = 1;
}
state.Sequence = seq;
}
}
}

View file

@ -16,91 +16,90 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Server.Network
namespace Server.Network;
public static class IncomingPackets
{
public static class IncomingPackets
private static readonly PacketHandler[] m_6017Handlers = new PacketHandler[0x100];
private static readonly EncodedPacketHandler[] m_EncodedHandlersLow = new EncodedPacketHandler[0x100];
private static readonly Dictionary<int, EncodedPacketHandler> m_EncodedHandlersHigh =
new();
public static PacketHandler[] Handlers { get; } = new PacketHandler[0x100];
public static void Register(int packetID, int length, bool ingame, OnPacketReceive onReceive)
{
private static readonly PacketHandler[] m_6017Handlers = new PacketHandler[0x100];
Handlers[packetID] = new PacketHandler(packetID, length, ingame, onReceive);
m_6017Handlers[packetID] ??= new PacketHandler(packetID, length, ingame, onReceive);
}
private static readonly EncodedPacketHandler[] m_EncodedHandlersLow = new EncodedPacketHandler[0x100];
public static PacketHandler GetHandler(int packetID) => Handlers[packetID];
private static readonly Dictionary<int, EncodedPacketHandler> m_EncodedHandlersHigh =
new();
public static PacketHandler[] Handlers { get; } = new PacketHandler[0x100];
public static void Register(int packetID, int length, bool ingame, OnPacketReceive onReceive)
public static void RegisterEncoded(int packetID, bool ingame, OnEncodedPacketReceive onReceive)
{
if (packetID >= 0 && packetID < 0x100)
{
Handlers[packetID] = new PacketHandler(packetID, length, ingame, onReceive);
m_6017Handlers[packetID] ??= new PacketHandler(packetID, length, ingame, onReceive);
m_EncodedHandlersLow[packetID] = new EncodedPacketHandler(packetID, ingame, onReceive);
}
public static PacketHandler GetHandler(int packetID) => Handlers[packetID];
public static void RegisterEncoded(int packetID, bool ingame, OnEncodedPacketReceive onReceive)
else
{
if (packetID >= 0 && packetID < 0x100)
{
m_EncodedHandlersLow[packetID] = new EncodedPacketHandler(packetID, ingame, onReceive);
}
else
{
m_EncodedHandlersHigh[packetID] = new EncodedPacketHandler(packetID, ingame, onReceive);
}
}
public static EncodedPacketHandler GetEncodedHandler(int packetID)
{
if (packetID >= 0 && packetID < 0x100)
{
return m_EncodedHandlersLow[packetID];
}
m_EncodedHandlersHigh.TryGetValue(packetID, out var handler);
return handler;
}
public static void RemoveEncodedHandler(int packetID)
{
if (packetID >= 0 && packetID < 0x100)
{
m_EncodedHandlersLow[packetID] = null;
}
else
{
m_EncodedHandlersHigh.Remove(packetID);
}
}
public static void RegisterThrottler(int packetID, ThrottlePacketCallback t)
{
var ph = GetHandler(packetID);
if (ph != null)
{
ph.ThrottleCallback = t;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInfoPacket(byte packetId)
{
// These packets can arrive at any time during the login process. They're just informational.
return packetId switch
{
0x01 => true, // Disconnect
0x73 => true, // Ping
0xA4 => true, // SystemInfo
0xB1 => true, // Gump Response
0xBB => true, // Account ID
0xBD => true, // Client Version
0xBE => true, // Assist Version
0xD9 => true, // Hardware Info
0xDD => true, // Gumps (Packed)
0xE1 => true, // Client Type
0xF4 => true, // CrashReport
_ => false
};
m_EncodedHandlersHigh[packetID] = new EncodedPacketHandler(packetID, ingame, onReceive);
}
}
public static EncodedPacketHandler GetEncodedHandler(int packetID)
{
if (packetID >= 0 && packetID < 0x100)
{
return m_EncodedHandlersLow[packetID];
}
m_EncodedHandlersHigh.TryGetValue(packetID, out var handler);
return handler;
}
public static void RemoveEncodedHandler(int packetID)
{
if (packetID >= 0 && packetID < 0x100)
{
m_EncodedHandlersLow[packetID] = null;
}
else
{
m_EncodedHandlersHigh.Remove(packetID);
}
}
public static void RegisterThrottler(int packetID, ThrottlePacketCallback t)
{
var ph = GetHandler(packetID);
if (ph != null)
{
ph.ThrottleCallback = t;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInfoPacket(byte packetId)
{
// These packets can arrive at any time during the login process. They're just informational.
return packetId switch
{
0x01 => true, // Disconnect
0x73 => true, // Ping
0xA4 => true, // SystemInfo
0xB1 => true, // Gump Response
0xBB => true, // Account ID
0xBD => true, // Client Version
0xBE => true, // Assist Version
0xD9 => true, // Hardware Info
0xDD => true, // Gumps (Packed)
0xE1 => true, // Client Type
0xF4 => true, // CrashReport
_ => false
};
}
}

File diff suppressed because it is too large Load diff

View file

@ -16,130 +16,129 @@
using Server.Diagnostics;
using Server.Targeting;
namespace Server.Network
namespace Server.Network;
public static class IncomingTargetingPackets
{
public static class IncomingTargetingPackets
public static void Configure()
{
public static void Configure()
IncomingPackets.Register(0x6C, 19, true, TargetResponse);
}
public static void TargetResponse(NetState state, CircularBufferReader reader, ref int packetLength)
{
int type = reader.ReadByte();
var targetID = reader.ReadInt32();
int flags = reader.ReadByte();
var serial = (Serial)reader.ReadUInt32();
int x = reader.ReadInt16();
int y = reader.ReadInt16();
reader.ReadByte();
int z = reader.ReadSByte();
int graphic = reader.ReadUInt16();
if (targetID == unchecked((int)0xDEADBEEF))
{
IncomingPackets.Register(0x6C, 19, true, TargetResponse);
return;
}
public static void TargetResponse(NetState state, CircularBufferReader reader, ref int packetLength)
var from = state.Mobile;
var t = from.Target;
if (t == null)
{
int type = reader.ReadByte();
var targetID = reader.ReadInt32();
int flags = reader.ReadByte();
var serial = (Serial)reader.ReadUInt32();
int x = reader.ReadInt16();
int y = reader.ReadInt16();
reader.ReadByte();
int z = reader.ReadSByte();
int graphic = reader.ReadUInt16();
return;
}
if (targetID == unchecked((int)0xDEADBEEF))
var prof = TargetProfile.Acquire(t.GetType());
prof?.Start();
try
{
if (x == -1 && y == -1 && !serial.IsValid)
{
return;
// User pressed escape
t.Cancel(from, TargetCancelType.Canceled);
}
var from = state.Mobile;
var t = from.Target;
if (t == null)
else if (t.TargetID != targetID)
{
return;
// Sanity, prevent fake target
}
var prof = TargetProfile.Acquire(t.GetType());
prof?.Start();
try
else
{
if (x == -1 && y == -1 && !serial.IsValid)
{
// User pressed escape
t.Cancel(from, TargetCancelType.Canceled);
}
else if (t.TargetID != targetID)
{
// Sanity, prevent fake target
}
else
{
object toTarget;
object toTarget;
if (type == 1)
if (type == 1)
{
if (graphic == 0)
{
if (graphic == 0)
toTarget = new LandTarget(new Point3D(x, y, z), from.Map);
}
else
{
var map = from.Map;
if (map == null || map == Map.Internal)
{
toTarget = new LandTarget(new Point3D(x, y, z), from.Map);
t.Cancel(from, TargetCancelType.Canceled);
return;
}
else
{
var map = from.Map;
var tiles = map.Tiles.GetStaticTiles(x, y, !t.DisallowMultis);
if (map == null || map == Map.Internal)
var valid = false;
if (state.HighSeas)
{
var id = TileData.ItemTable[graphic & TileData.MaxItemValue];
if (id.Surface)
{
z -= id.Height;
}
}
for (var i = 0; !valid && i < tiles.Length; ++i)
{
if (tiles[i].Z == z && tiles[i].ID == graphic)
{
valid = true;
}
}
if (!valid)
{
t.Cancel(from, TargetCancelType.Canceled);
return;
}
else
{
var tiles = map.Tiles.GetStaticTiles(x, y, !t.DisallowMultis);
var valid = false;
if (state.HighSeas)
{
var id = TileData.ItemTable[graphic & TileData.MaxItemValue];
if (id.Surface)
{
z -= id.Height;
}
}
for (var i = 0; !valid && i < tiles.Length; ++i)
{
if (tiles[i].Z == z && tiles[i].ID == graphic)
{
valid = true;
}
}
if (!valid)
{
t.Cancel(from, TargetCancelType.Canceled);
return;
}
else
{
toTarget = new StaticTarget(new Point3D(x, y, z), graphic);
}
toTarget = new StaticTarget(new Point3D(x, y, z), graphic);
}
}
}
else if (serial.IsMobile)
{
toTarget = World.FindMobile(serial);
}
else if (serial.IsItem)
{
toTarget = World.FindItem(serial);
}
else
{
t.Cancel(from, TargetCancelType.Canceled);
return;
}
t.Invoke(from, toTarget);
}
else if (serial.IsMobile)
{
toTarget = World.FindMobile(serial);
}
else if (serial.IsItem)
{
toTarget = World.FindItem(serial);
}
else
{
t.Cancel(from, TargetCancelType.Canceled);
return;
}
t.Invoke(from, toTarget);
}
finally
{
prof?.Finish();
}
}
finally
{
prof?.Finish();
}
}
}

View file

@ -15,96 +15,95 @@
using System.Collections.Generic;
namespace Server.Network
namespace Server.Network;
public static class IncomingVendorPackets
{
public static class IncomingVendorPackets
public static void Configure()
{
public static void Configure()
IncomingPackets.Register(0x3B, 0, true, VendorBuyReply);
IncomingPackets.Register(0x9F, 0, true, VendorSellReply);
}
public static void VendorBuyReply(NetState state, CircularBufferReader reader, ref int packetLength)
{
var vendor = World.FindMobile((Serial)reader.ReadUInt32());
if (vendor == null)
{
IncomingPackets.Register(0x3B, 0, true, VendorBuyReply);
IncomingPackets.Register(0x9F, 0, true, VendorSellReply);
return;
}
public static void VendorBuyReply(NetState state, CircularBufferReader reader, ref int packetLength)
var flag = reader.ReadByte();
if (!vendor.Deleted && Utility.InRange(vendor.Location, state.Mobile.Location, 10) && flag == 0x02)
{
var vendor = World.FindMobile((Serial)reader.ReadUInt32());
var msgSize = packetLength - 8; // Remaining bytes
if (vendor == null)
if (msgSize / 7 > 100)
{
return;
}
var flag = reader.ReadByte();
if (!vendor.Deleted && Utility.InRange(vendor.Location, state.Mobile.Location, 10) && flag == 0x02)
var buyList = new List<BuyItemResponse>(msgSize / 7);
while (msgSize > 0)
{
var msgSize = packetLength - 8; // Remaining bytes
if (msgSize / 7 > 100)
{
return;
}
var buyList = new List<BuyItemResponse>(msgSize / 7);
while (msgSize > 0)
{
var layer = reader.ReadByte();
var serial = (Serial)reader.ReadUInt32();
int amount = reader.ReadInt16();
buyList.Add(new BuyItemResponse(serial, amount));
msgSize -= 7;
}
if (buyList.Count <= 0 || (vendor as IVendor)?.OnBuyItems(state.Mobile, buyList) != true)
{
return;
}
}
state.SendEndVendorBuy(vendor.Serial);
}
public static void VendorSellReply(NetState state, CircularBufferReader reader, ref int packetLength)
{
var serial = (Serial)reader.ReadUInt32();
var vendor = World.FindMobile(serial);
if (vendor == null)
{
return;
}
if (vendor.Deleted || !Utility.InRange(vendor.Location, state.Mobile.Location, 10))
{
state.SendEndVendorSell(vendor.Serial);
return;
}
int count = reader.ReadUInt16();
if (count >= 100 || reader.Remaining != count * 6)
{
return;
}
var sellList = new List<SellItemResponse>(count);
for (var i = 0; i < count; i++)
{
var item = World.FindItem((Serial)reader.ReadUInt32());
var layer = reader.ReadByte();
var serial = (Serial)reader.ReadUInt32();
int amount = reader.ReadInt16();
if (item != null && amount > 0)
{
sellList.Add(new SellItemResponse(item, amount));
}
buyList.Add(new BuyItemResponse(serial, amount));
msgSize -= 7;
}
if (sellList.Count > 0 && vendor is IVendor v && v.OnSellItems(state.Mobile, sellList))
if (buyList.Count <= 0 || (vendor as IVendor)?.OnBuyItems(state.Mobile, buyList) != true)
{
state.SendEndVendorSell(vendor.Serial);
return;
}
}
state.SendEndVendorBuy(vendor.Serial);
}
public static void VendorSellReply(NetState state, CircularBufferReader reader, ref int packetLength)
{
var serial = (Serial)reader.ReadUInt32();
var vendor = World.FindMobile(serial);
if (vendor == null)
{
return;
}
if (vendor.Deleted || !Utility.InRange(vendor.Location, state.Mobile.Location, 10))
{
state.SendEndVendorSell(vendor.Serial);
return;
}
int count = reader.ReadUInt16();
if (count >= 100 || reader.Remaining != count * 6)
{
return;
}
var sellList = new List<SellItemResponse>(count);
for (var i = 0; i < count; i++)
{
var item = World.FindItem((Serial)reader.ReadUInt32());
int amount = reader.ReadInt16();
if (item != null && amount > 0)
{
sellList.Add(new SellItemResponse(item, amount));
}
}
if (sellList.Count > 0 && vendor is IVendor v && v.OnSellItems(state.Mobile, sellList))
{
state.SendEndVendorSell(vendor.Serial);
}
}
}

View file

@ -19,436 +19,435 @@ using System.Buffers;
using System.Runtime.CompilerServices;
using Server.Accounting;
namespace Server.Network
namespace Server.Network;
public enum ALRReason : byte
{
public enum ALRReason : byte
{
Invalid = 0,
InUse = 1,
Blocked = 2,
BadPass = 3,
Idle = 254,
BadComm = 255
}
Invalid = 0,
InUse = 1,
Blocked = 2,
BadPass = 3,
Idle = 254,
BadComm = 255
}
public enum PMMessage : byte
{
None = 0,
CharNoExist = 1,
CharExists = 2,
CharInWorld = 5,
LoginSyncError = 6,
IdleWarning = 7
}
public enum PMMessage : byte
{
None = 0,
CharNoExist = 1,
CharExists = 2,
CharInWorld = 5,
LoginSyncError = 6,
IdleWarning = 7
}
public enum DeleteResultType
{
PasswordInvalid,
CharNotExist,
CharBeingPlayed,
CharTooYoung,
CharQueued,
BadRequest
}
public enum DeleteResultType
{
PasswordInvalid,
CharNotExist,
CharBeingPlayed,
CharTooYoung,
CharQueued,
BadRequest
}
public static class OutgoingAccountPackets
{
/**
public static class OutgoingAccountPackets
{
/**
* Packet: 0x81
* Length: Up to 425 bytes
*
* Displays the list of characters during the login process.
* Note: Currently Unused
*/
public static void SendChangeCharacter(this NetState ns, IAccount a)
public static void SendChangeCharacter(this NetState ns, IAccount a)
{
if (ns == null || a == null)
{
if (ns == null || a == null)
{
return;
}
var length = 5 + a.Length * 60;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0x81); // Packet ID
writer.Write((ushort)length);
writer.Write((ushort)0); // Count & Placeholder
int count = 0;
for (var i = 0; i < a.Length; ++i)
{
var m = a[i];
if (m == null)
{
writer.Clear(60);
}
else
{
var name = (m.RawName?.Trim()).DefaultIfNullOrEmpty("-no name-");
count++;
writer.WriteAscii(name, 30);
writer.Clear(30); // Password (empty)
}
}
var position = writer.Position;
writer.Seek(3, SeekOrigin.Begin);
writer.Write((byte)count);
writer.Seek(position, SeekOrigin.Begin);
ns.Send(writer.Span);
return;
}
/**
var length = 5 + a.Length * 60;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0x81); // Packet ID
writer.Write((ushort)length);
writer.Write((ushort)0); // Count & Placeholder
int count = 0;
for (var i = 0; i < a.Length; ++i)
{
var m = a[i];
if (m == null)
{
writer.Clear(60);
}
else
{
var name = (m.RawName?.Trim()).DefaultIfNullOrEmpty("-no name-");
count++;
writer.WriteAscii(name, 30);
writer.Clear(30); // Password (empty)
}
}
var position = writer.Position;
writer.Seek(3, SeekOrigin.Begin);
writer.Write((byte)count);
writer.Seek(position, SeekOrigin.Begin);
ns.Send(writer.Span);
}
/**
* Packet: 0xBD
* Length: 3 bytes
*
* Sends a requests for the client version
*/
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendClientVersionRequest(this NetState ns) => ns?.Send(stackalloc byte[] { 0xBD, 0x00, 0x03 });
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendClientVersionRequest(this NetState ns) => ns?.Send(stackalloc byte[] { 0xBD, 0x00, 0x03 });
/**
/**
* Packet: 0x85
* Length: 2 bytes
*
* Sends the result of a deletion request
*/
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendCharacterDeleteResult(this NetState ns, DeleteResultType res) =>
ns?.Send(stackalloc byte[] { 0x85, (byte)res });
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendCharacterDeleteResult(this NetState ns, DeleteResultType res) =>
ns?.Send(stackalloc byte[] { 0x85, (byte)res });
/**
/**
* Packet: 0x53
* Length: 2 bytes
*
* Sends a PopupMessage with a predetermined message
*/
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendPopupMessage(this NetState ns, PMMessage msg) =>
ns?.Send(stackalloc byte[] { 0x53, (byte)msg });
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendPopupMessage(this NetState ns, PMMessage msg) =>
ns?.Send(stackalloc byte[] { 0x53, (byte)msg });
/**
/**
* Packet: 0xB9
* Length: 3 or 5 bytes
*
* Sends support features based on the client version
*/
public static void SendSupportedFeature(this NetState ns)
public static void SendSupportedFeature(this NetState ns)
{
if (ns.CannotSendPackets())
{
if (ns == null)
return;
}
var flags = ExpansionInfo.CoreExpansion.SupportedFeatures;
if (ns.Account.Limit >= 6)
{
flags |= FeatureFlags.LiveAccount;
flags &= ~FeatureFlags.UOTD;
if (ns.Account.Limit > 6)
{
return;
}
var flags = ExpansionInfo.CoreExpansion.SupportedFeatures;
if (ns.Account.Limit >= 6)
{
flags |= FeatureFlags.LiveAccount;
flags &= ~FeatureFlags.UOTD;
if (ns.Account.Limit > 6)
{
flags |= FeatureFlags.SeventhCharacterSlot;
}
else
{
flags |= FeatureFlags.SixthCharacterSlot;
}
}
var length = ns.ExtendedSupportedFeatures ? 5 : 3;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0xB9); // Packet ID
if (ns.ExtendedSupportedFeatures)
{
writer.Write((uint)flags);
flags |= FeatureFlags.SeventhCharacterSlot;
}
else
{
writer.Write((ushort)flags);
flags |= FeatureFlags.SixthCharacterSlot;
}
ns.Send(writer.Span);
}
/**
var length = ns.ExtendedSupportedFeatures ? 5 : 3;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0xB9); // Packet ID
if (ns.ExtendedSupportedFeatures)
{
writer.Write((uint)flags);
}
else
{
writer.Write((ushort)flags);
}
ns.Send(writer.Span);
}
/**
* Packet: 0x1B
* Length: 37 bytes
*
* Sends login confirmation
*/
public static void SendLoginConfirmation(this NetState ns, Mobile m)
public static void SendLoginConfirmation(this NetState ns, Mobile m)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[37]);
writer.Write((byte)0x1B); // PacketID
writer.Write(m.Serial);
writer.Write(0);
writer.Write((short)m.Body);
writer.Write((short)m.X);
writer.Write((short)m.Y);
writer.Write((short)m.Z);
writer.Write((byte)m.Direction);
writer.Write((byte)0);
writer.Write(-1);
writer.Write(0);
var map = m.Map;
if (map == null || map == Map.Internal)
{
map = m.LogoutMap;
}
writer.Write((short)(map?.Width ?? Map.Felucca.Width));
writer.Write((short)(map?.Height ?? Map.Felucca.Height));
writer.Clear(writer.Capacity - writer.Position); // Remaining is zero
ns.Send(writer.Span);
return;
}
/**
var writer = new SpanWriter(stackalloc byte[37]);
writer.Write((byte)0x1B); // PacketID
writer.Write(m.Serial);
writer.Write(0);
writer.Write((short)m.Body);
writer.Write((short)m.X);
writer.Write((short)m.Y);
writer.Write((short)m.Z);
writer.Write((byte)m.Direction);
writer.Write((byte)0);
writer.Write(-1);
writer.Write(0);
var map = m.Map;
if (map == null || map == Map.Internal)
{
map = m.LogoutMap;
}
writer.Write((short)(map?.Width ?? Map.Felucca.Width));
writer.Write((short)(map?.Height ?? Map.Felucca.Height));
writer.Clear(writer.Capacity - writer.Position); // Remaining is zero
ns.Send(writer.Span);
}
/**
* Packet: 0x55
* Length: 1 byte
*
* Sends login completion
*/
public static void SendLoginComplete(this NetState ns)
{
ns?.Send(stackalloc byte[] { 0x55 });
}
public static void SendLoginComplete(this NetState ns)
{
ns?.Send(stackalloc byte[] { 0x55 });
}
/**
/**
* Packet: 0x86
* Length: Up to 424 bytes
*
* Sends updated character list
*/
public static void SendCharacterListUpdate(this NetState ns, IAccount a)
public static void SendCharacterListUpdate(this NetState ns, IAccount a)
{
if (ns == null || a == null)
{
if (ns == null || a == null)
{
return;
}
var highSlot = -1;
for (var i = a.Length - 1; i >= 0; i--)
{
if (a[i] != null)
{
highSlot = i;
break;
}
}
var count = Math.Max(Math.Max(highSlot + 1, a.Limit), 5);
var length = 4 + count * 60;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0x86); // Packet ID
writer.Write((ushort)length);
writer.Write((byte)count);
for (int i = 0; i < count; i++)
{
var m = a[i];
if (m == null)
{
writer.Clear(60);
}
else
{
var name = (m.RawName?.Trim()).DefaultIfNullOrEmpty("-no name-");
writer.WriteAscii(name, 30);
writer.Clear(30); // password
}
}
ns.Send(writer.Span);
return;
}
/**
var highSlot = -1;
for (var i = a.Length - 1; i >= 0; i--)
{
if (a[i] != null)
{
highSlot = i;
break;
}
}
var count = Math.Max(Math.Max(highSlot + 1, a.Limit), 5);
var length = 4 + count * 60;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0x86); // Packet ID
writer.Write((ushort)length);
writer.Write((byte)count);
for (int i = 0; i < count; i++)
{
var m = a[i];
if (m == null)
{
writer.Clear(60);
}
else
{
var name = (m.RawName?.Trim()).DefaultIfNullOrEmpty("-no name-");
writer.WriteAscii(name, 30);
writer.Clear(30); // password
}
}
ns.Send(writer.Span);
}
/**
* Packet: 0xA9
* Length: 1410 or more bytes
*
* Sends list of characters and starting cities.
*/
public static void SendCharacterList(this NetState ns)
public static void SendCharacterList(this NetState ns)
{
var acct = ns?.Account;
if (acct == null)
{
var acct = ns?.Account;
if (acct == null)
{
return;
}
var client70130 = ns.NewCharacterList;
var textLength = client70130 ? 32 : 31;
var cityInfo = ns.CityInfo;
var highSlot = -1;
for (var i = acct.Length - 1; i >= 0; i--)
{
if (acct[i] != null)
{
highSlot = i;
break;
}
}
var count = Math.Max(Math.Max(highSlot + 1, acct.Limit), 5);
var length = (client70130 ?
11 + (textLength * 2 + 25) * cityInfo.Length :
9 + (textLength * 2 + 1) * cityInfo.Length) + count * 60;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0xA9); // Packet ID
writer.Write((ushort)length);
writer.Write((byte)count);
for (int i = 0; i < count; i++)
{
var m = acct[i];
if (m == null)
{
writer.Clear(60);
}
else
{
var name = (m.RawName?.Trim()).DefaultIfNullOrEmpty("-no name-");
writer.WriteAscii(name, 30);
writer.Clear(30); // password
}
}
writer.Write((byte)cityInfo.Length);
for (int i = 0; i < cityInfo.Length; ++i)
{
var ci = cityInfo[i];
writer.Write((byte)i);
writer.WriteAscii(ci.City, textLength);
writer.WriteAscii(ci.Building, textLength);
if (client70130)
{
writer.Write(ci.X);
writer.Write(ci.Y);
writer.Write(ci.Z);
writer.Write(ci.Map?.MapID ?? 0);
writer.Write(ci.Description);
writer.Write(0);
}
}
var flags = ExpansionInfo.CoreExpansion.CharacterListFlags;
if (count > 6)
{
flags |= CharacterListFlags.SeventhCharacterSlot |
CharacterListFlags.SixthCharacterSlot; // 7th Character Slot - TODO: Is SixthCharacterSlot Required?
}
else if (count == 6)
{
flags |= CharacterListFlags.SixthCharacterSlot; // 6th Character Slot
}
else if (acct.Limit == 1)
{
flags |= CharacterListFlags.SlotLimit &
CharacterListFlags.OneCharacterSlot; // Limit Characters & One Character
}
writer.Write((int)flags);
if (client70130)
{
writer.Write((short)-1);
}
ns.Send(writer.Span);
return;
}
/**
var client70130 = ns.NewCharacterList;
var textLength = client70130 ? 32 : 31;
var cityInfo = ns.CityInfo;
var highSlot = -1;
for (var i = acct.Length - 1; i >= 0; i--)
{
if (acct[i] != null)
{
highSlot = i;
break;
}
}
var count = Math.Max(Math.Max(highSlot + 1, acct.Limit), 5);
var length = (client70130 ?
11 + (textLength * 2 + 25) * cityInfo.Length :
9 + (textLength * 2 + 1) * cityInfo.Length) + count * 60;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0xA9); // Packet ID
writer.Write((ushort)length);
writer.Write((byte)count);
for (int i = 0; i < count; i++)
{
var m = acct[i];
if (m == null)
{
writer.Clear(60);
}
else
{
var name = (m.RawName?.Trim()).DefaultIfNullOrEmpty("-no name-");
writer.WriteAscii(name, 30);
writer.Clear(30); // password
}
}
writer.Write((byte)cityInfo.Length);
for (int i = 0; i < cityInfo.Length; ++i)
{
var ci = cityInfo[i];
writer.Write((byte)i);
writer.WriteAscii(ci.City, textLength);
writer.WriteAscii(ci.Building, textLength);
if (client70130)
{
writer.Write(ci.X);
writer.Write(ci.Y);
writer.Write(ci.Z);
writer.Write(ci.Map?.MapID ?? 0);
writer.Write(ci.Description);
writer.Write(0);
}
}
var flags = ExpansionInfo.CoreExpansion.CharacterListFlags;
if (count > 6)
{
flags |= CharacterListFlags.SeventhCharacterSlot |
CharacterListFlags.SixthCharacterSlot; // 7th Character Slot - TODO: Is SixthCharacterSlot Required?
}
else if (count == 6)
{
flags |= CharacterListFlags.SixthCharacterSlot; // 6th Character Slot
}
else if (acct.Limit == 1)
{
flags |= CharacterListFlags.SlotLimit &
CharacterListFlags.OneCharacterSlot; // Limit Characters & One Character
}
writer.Write((int)flags);
if (client70130)
{
writer.Write((short)-1);
}
ns.Send(writer.Span);
}
/**
* Packet: 0x82
* Length: 2 bytes
*
* Sends a reason for rejecting the login
*/
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendAccountLoginRejected(this NetState ns, ALRReason reason) =>
ns?.Send(stackalloc byte[] { 0x82, (byte)reason });
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendAccountLoginRejected(this NetState ns, ALRReason reason) =>
ns?.Send(stackalloc byte[] { 0x82, (byte)reason });
/**
/**
* Packet: 0xA8
* Length: 6 + 40 bytes per server listing
*
* Sends login acknowledge with server listing
*/
public static void SendAccountLoginAck(this NetState ns)
public static void SendAccountLoginAck(this NetState ns)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var info = ns.ServerInfo;
var length = 6 + 40 * info.Length;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0xA8); // Packet ID
writer.Write((ushort)length);
writer.Write((byte)0x5D);
writer.Write((ushort)info.Length);
for (var i = 0; i < info.Length; ++i)
{
var si = info[i];
writer.Write((ushort)i);
writer.WriteAscii(si.Name, 32);
writer.Write((byte)si.FullPercent);
writer.Write((sbyte)si.TimeZone);
// UO only supports IPv4
writer.Write(si.RawAddress);
}
ns.Send(writer.Span);
return;
}
/**
var info = ns.ServerInfo;
var length = 6 + 40 * info.Length;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0xA8); // Packet ID
writer.Write((ushort)length);
writer.Write((byte)0x5D);
writer.Write((ushort)info.Length);
for (var i = 0; i < info.Length; ++i)
{
var si = info[i];
writer.Write((ushort)i);
writer.WriteAscii(si.Name, 32);
writer.Write((byte)si.FullPercent);
writer.Write((sbyte)si.TimeZone);
// UO only supports IPv4
writer.Write(si.RawAddress);
}
ns.Send(writer.Span);
}
/**
* Packet: 0x8C
* Length: 11 bytes
*
* Sends acknowledge play server
*/
public static void SendPlayServerAck(this NetState ns, ServerInfo si, int authId)
public static void SendPlayServerAck(this NetState ns, ServerInfo si, int authId)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[11]);
writer.Write((byte)0x8C); // Packet ID
writer.WriteLE(si.RawAddress);
writer.Write((short)si.Address.Port);
writer.Write(authId);
ns.Send(writer.Span);
return;
}
var writer = new SpanWriter(stackalloc byte[11]);
writer.Write((byte)0x8C); // Packet ID
writer.WriteLE(si.RawAddress);
writer.Write((short)si.Address.Port);
writer.Write(authId);
ns.Send(writer.Span);
}
}

View file

@ -16,42 +16,41 @@
using System.Buffers;
using System.Runtime.CompilerServices;
namespace Server.Network
namespace Server.Network;
public static class OutgoingCombatPackets
{
public static class OutgoingCombatPackets
public static void SendSwing(this NetState ns, Serial attacker, Serial defender)
{
public static void SendSwing(this NetState ns, Serial attacker, Serial defender)
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[10]);
writer.Write((byte)0x2F); // Packet ID
writer.Write((byte)0);
writer.Write(attacker);
writer.Write(defender);
ns.Send(writer.Span);
return;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void SendSetWarMode(this NetState ns, bool warmode) =>
ns?.Send(stackalloc byte[] { 0x72, *(byte*)&warmode, 0x00, 0x32, 0x00 });
var writer = new SpanWriter(stackalloc byte[10]);
writer.Write((byte)0x2F); // Packet ID
writer.Write((byte)0);
writer.Write(attacker);
writer.Write(defender);
public static void SendChangeCombatant(this NetState ns, Serial combatant)
ns.Send(writer.Span);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void SendSetWarMode(this NetState ns, bool warmode) =>
ns?.Send(stackalloc byte[] { 0x72, *(byte*)&warmode, 0x00, 0x32, 0x00 });
public static void SendChangeCombatant(this NetState ns, Serial combatant)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[5]);
writer.Write((byte)0xAA); // Packet ID
writer.Write(combatant);
ns.Send(writer.Span);
return;
}
var writer = new SpanWriter(stackalloc byte[5]);
writer.Write((byte)0xAA); // Packet ID
writer.Write(combatant);
ns.Send(writer.Span);
}
}

View file

@ -17,194 +17,193 @@ using System;
using System.Buffers;
using System.IO;
namespace Server.Network
namespace Server.Network;
public static class OutgoingContainerPackets
{
public static class OutgoingContainerPackets
public static void SendDisplaySpellbook(this NetState ns, Serial book) => ns.SendDisplayContainer(book, -1);
public static void SendSpellbookContent(this NetState ns, Serial book, int graphic, int offset, ulong content)
{
public static void SendDisplaySpellbook(this NetState ns, Serial book) => ns.SendDisplayContainer(book, -1);
public static void SendSpellbookContent(this NetState ns, Serial book, int graphic, int offset, ulong content)
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
if (ObjectPropertyList.Enabled && ns.NewSpellbook)
{
ns.SendNewSpellbookContent(book, graphic, offset, content);
}
else
{
ns.SendOldSpellbookContent(book, offset, content);
}
return;
}
public static void SendNewSpellbookContent(this NetState ns, Serial book, int graphic, int offset, ulong content)
if (ObjectPropertyList.Enabled && ns.NewSpellbook)
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[23]);
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)23); // Length
writer.Write((short)0x1B); // Subpacket
writer.Write((short)0x01); // Command
writer.Write(book);
writer.Write((short)graphic);
writer.Write((short)offset);
for (var i = 0; i < 8; ++i)
{
writer.Write((byte)(content >> (i * 8)));
}
ns.Send(writer.Span);
ns.SendNewSpellbookContent(book, graphic, offset, content);
}
public static void SendOldSpellbookContent(this NetState ns, Serial book, int offset, ulong content)
else
{
if (ns == null)
{
return;
}
var count = content.NumberOfSetBits();
var length = 5 + count * (ns.ContainerGridLines ? 20 : 19);
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0x3C); // Packet ID
writer.Write((ushort)length);
writer.Write((ushort)count);
ulong mask = 1;
for (var i = 0; i < 64; ++i, mask <<= 1)
{
if ((content & mask) != 0)
{
writer.Write(0x7FFFFFFF - i);
writer.Write((ushort)0); // child ItemID
writer.Write((byte)0); // ItemID offset
writer.Write((ushort)(i + offset)); // Amount
writer.Write(0); // X, Y
if (ns.ContainerGridLines)
{
writer.Write((byte)0); // Grid Location
}
writer.Write(book);
writer.Write((short)0); // Quest Hue
}
}
ns.Send(writer.Span);
}
public static void SendDisplayContainer(this NetState ns, Serial cont, int gumpId)
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[ns.HighSeas ? 9 : 7]);
writer.Write((byte)0x24); // Packet ID
writer.Write(cont);
writer.Write((ushort)gumpId);
if (ns.HighSeas)
{
writer.Write((short)0x7D);
}
ns.Send(writer.Span);
}
public static void SendContainerContentUpdate(this NetState ns, Item item)
{
if (ns == null)
{
return;
}
Serial parentSerial;
if (item.Parent is Item parentItem)
{
parentSerial = parentItem.Serial;
}
else
{
Console.WriteLine("Warning: ContainerContentUpdate on item with !(parent is Item)");
parentSerial = Serial.Zero;
}
var writer = new SpanWriter(stackalloc byte[ns.ContainerGridLines ? 21 : 20]);
writer.Write((byte)0x25); // Packet ID
writer.Write(item.Serial);
writer.Write((ushort)item.ItemID);
writer.Write((byte)0); // signed, itemID offset
writer.Write((ushort)Math.Min(item.Amount, ushort.MaxValue));
writer.Write((short)item.X);
writer.Write((short)item.Y);
if (ns.ContainerGridLines)
{
writer.Write((byte)0); // Grid Location?
}
writer.Write(parentSerial);
writer.Write((ushort)(item.QuestItem ? Item.QuestItemHue : item.Hue));
ns.Send(writer.Span);
}
public static void SendContainerContent(this NetState ns, Mobile beholder, Item beheld)
{
if (ns == null)
{
return;
}
var items = beheld.Items;
var count = items.Count;
var writer = new SpanWriter(stackalloc byte[5 + items.Count * (ns.ContainerGridLines ? 20 : 19)]);
writer.Write((byte)0x3C); // Packet ID
writer.Seek(4, SeekOrigin.Current); // Length & written count
var written = 0;
for (var i = 0; i < count; ++i)
{
var child = items[i];
if (!child.Deleted && beholder.CanSee(child))
{
var loc = child.Location;
writer.Write(child.Serial);
writer.Write((ushort)child.ItemID);
writer.Write((byte)0); // signed, itemID offset
writer.Write((ushort)Math.Min(child.Amount, ushort.MaxValue));
writer.Write((short)loc.X);
writer.Write((short)loc.Y);
if (ns.ContainerGridLines)
{
writer.Write((byte)0); // Grid Location?
}
writer.Write(beheld.Serial);
writer.Write((ushort)(child.QuestItem ? Item.QuestItemHue : child.Hue));
++written;
}
}
writer.Seek(1, SeekOrigin.Begin);
writer.Write((ushort)writer.BytesWritten);
writer.Write((ushort)written);
writer.Seek(0, SeekOrigin.End);
ns.Send(writer.Span);
ns.SendOldSpellbookContent(book, offset, content);
}
}
public static void SendNewSpellbookContent(this NetState ns, Serial book, int graphic, int offset, ulong content)
{
if (ns.CannotSendPackets())
{
return;
}
var writer = new SpanWriter(stackalloc byte[23]);
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)23); // Length
writer.Write((short)0x1B); // Subpacket
writer.Write((short)0x01); // Command
writer.Write(book);
writer.Write((short)graphic);
writer.Write((short)offset);
for (var i = 0; i < 8; ++i)
{
writer.Write((byte)(content >> (i * 8)));
}
ns.Send(writer.Span);
}
public static void SendOldSpellbookContent(this NetState ns, Serial book, int offset, ulong content)
{
if (ns.CannotSendPackets())
{
return;
}
var count = content.NumberOfSetBits();
var length = 5 + count * (ns.ContainerGridLines ? 20 : 19);
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0x3C); // Packet ID
writer.Write((ushort)length);
writer.Write((ushort)count);
ulong mask = 1;
for (var i = 0; i < 64; ++i, mask <<= 1)
{
if ((content & mask) != 0)
{
writer.Write(0x7FFFFFFF - i);
writer.Write((ushort)0); // child ItemID
writer.Write((byte)0); // ItemID offset
writer.Write((ushort)(i + offset)); // Amount
writer.Write(0); // X, Y
if (ns.ContainerGridLines)
{
writer.Write((byte)0); // Grid Location
}
writer.Write(book);
writer.Write((short)0); // Quest Hue
}
}
ns.Send(writer.Span);
}
public static void SendDisplayContainer(this NetState ns, Serial cont, int gumpId)
{
if (ns.CannotSendPackets())
{
return;
}
var writer = new SpanWriter(stackalloc byte[ns.HighSeas ? 9 : 7]);
writer.Write((byte)0x24); // Packet ID
writer.Write(cont);
writer.Write((ushort)gumpId);
if (ns.HighSeas)
{
writer.Write((short)0x7D);
}
ns.Send(writer.Span);
}
public static void SendContainerContentUpdate(this NetState ns, Item item)
{
if (ns.CannotSendPackets())
{
return;
}
Serial parentSerial;
if (item.Parent is Item parentItem)
{
parentSerial = parentItem.Serial;
}
else
{
Console.WriteLine("Warning: ContainerContentUpdate on item with !(parent is Item)");
parentSerial = Serial.Zero;
}
var writer = new SpanWriter(stackalloc byte[ns.ContainerGridLines ? 21 : 20]);
writer.Write((byte)0x25); // Packet ID
writer.Write(item.Serial);
writer.Write((ushort)item.ItemID);
writer.Write((byte)0); // signed, itemID offset
writer.Write((ushort)Math.Min(item.Amount, ushort.MaxValue));
writer.Write((short)item.X);
writer.Write((short)item.Y);
if (ns.ContainerGridLines)
{
writer.Write((byte)0); // Grid Location?
}
writer.Write(parentSerial);
writer.Write((ushort)(item.QuestItem ? Item.QuestItemHue : item.Hue));
ns.Send(writer.Span);
}
public static void SendContainerContent(this NetState ns, Mobile beholder, Item beheld)
{
if (ns.CannotSendPackets())
{
return;
}
var items = beheld.Items;
var count = items.Count;
var writer = new SpanWriter(stackalloc byte[5 + items.Count * (ns.ContainerGridLines ? 20 : 19)]);
writer.Write((byte)0x3C); // Packet ID
writer.Seek(4, SeekOrigin.Current); // Length & written count
var written = 0;
for (var i = 0; i < count; ++i)
{
var child = items[i];
if (!child.Deleted && beholder.CanSee(child))
{
var loc = child.Location;
writer.Write(child.Serial);
writer.Write((ushort)child.ItemID);
writer.Write((byte)0); // signed, itemID offset
writer.Write((ushort)Math.Min(child.Amount, ushort.MaxValue));
writer.Write((short)loc.X);
writer.Write((short)loc.Y);
if (ns.ContainerGridLines)
{
writer.Write((byte)0); // Grid Location?
}
writer.Write(beheld.Serial);
writer.Write((ushort)(child.QuestItem ? Item.QuestItemHue : child.Hue));
++written;
}
}
writer.Seek(1, SeekOrigin.Begin);
writer.Write((ushort)writer.BytesWritten);
writer.Write((ushort)written);
writer.Seek(0, SeekOrigin.End);
ns.Send(writer.Span);
}
}

View file

@ -16,36 +16,35 @@
using System;
using System.Buffers;
namespace Server.Network
namespace Server.Network;
public static class OutgoingDamagePackets
{
public static class OutgoingDamagePackets
public static void SendDamage(this NetState ns, Serial serial, int amount)
{
public static void SendDamage(this NetState ns, Serial serial, int amount)
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[ns.DamagePacket ? 7 : 11]);
if (ns.DamagePacket)
{
writer.Write((byte)0x0B); // Packet ID
writer.Write(serial);
writer.Write((ushort)Math.Clamp(amount, 0, 0xFFFF));
}
else
{
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)11); // Length
writer.Write((ushort)0x22);
writer.Write((byte)1);
writer.Write(serial);
writer.Write((byte)Math.Clamp(amount, 0, 0xFF));
}
ns.Send(writer.Span);
return;
}
var writer = new SpanWriter(stackalloc byte[ns.DamagePacket ? 7 : 11]);
if (ns.DamagePacket)
{
writer.Write((byte)0x0B); // Packet ID
writer.Write(serial);
writer.Write((ushort)Math.Clamp(amount, 0, 0xFFFF));
}
else
{
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)11); // Length
writer.Write((ushort)0x22);
writer.Write((byte)1);
writer.Write(serial);
writer.Write((byte)Math.Clamp(amount, 0, 0xFF));
}
ns.Send(writer.Span);
}
}

View file

@ -16,325 +16,324 @@
using System;
using System.Buffers;
namespace Server.Network
namespace Server.Network;
public static class OutgoingEffectPackets
{
public static class OutgoingEffectPackets
public const int SoundPacketLength = 12;
public const int ParticleEffectLength = 49;
public const int HuedEffectLength = 36;
public const int BoltEffectLength = 36;
public static void SendSoundEffect(this NetState ns, int soundID, IPoint3D target)
{
public const int SoundPacketLength = 12;
public const int ParticleEffectLength = 49;
public const int HuedEffectLength = 36;
public const int BoltEffectLength = 36;
public static void SendSoundEffect(this NetState ns, int soundID, IPoint3D target)
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
Span<byte> buffer = stackalloc byte[SoundPacketLength].InitializePacket();
CreateSoundEffect(buffer, soundID, target);
ns.Send(buffer);
return;
}
public static void CreateSoundEffect(Span<byte> buffer, int soundID, IPoint3D target)
{
if (buffer[0] != 0)
{
return;
}
Span<byte> buffer = stackalloc byte[SoundPacketLength].InitializePacket();
CreateSoundEffect(buffer, soundID, target);
var writer = new SpanWriter(buffer);
writer.Write((byte)0x54); // Packet ID
writer.Write((byte)1); // flags
writer.Write((short)soundID);
writer.Write((short)0); // volume
writer.Write((short)target.X);
writer.Write((short)target.Y);
writer.Write((short)target.Z);
ns.Send(buffer);
}
public static void CreateSoundEffect(Span<byte> buffer, int soundID, IPoint3D target)
{
if (buffer[0] != 0)
{
return;
}
public static void CreateParticleEffect(
Span<byte> buffer,
EffectType type, Serial from, Serial to, int itemID, IPoint3D fromPoint, IPoint3D toPoint,
int speed, int duration, bool fixedDirection, bool explode, int hue, int renderMode, int effect,
int explodeEffect, int explodeSound, Serial serial, int layer, int unknown
)
{
if (buffer[0] != 0)
{
return;
}
var writer = new SpanWriter(buffer);
writer.Write((byte)0x54); // Packet ID
writer.Write((byte)1); // flags
writer.Write((short)soundID);
writer.Write((short)0); // volume
writer.Write((short)target.X);
writer.Write((short)target.Y);
writer.Write((short)target.Z);
}
var writer = new SpanWriter(buffer);
writer.Write((byte)0xC7); // Packet ID
writer.Write((byte)type);
writer.Write(from);
writer.Write(to);
writer.Write((short)itemID);
writer.Write((short)fromPoint.X);
writer.Write((short)fromPoint.Y);
writer.Write((sbyte)fromPoint.Z);
writer.Write((short)toPoint.X);
writer.Write((short)toPoint.Y);
writer.Write((sbyte)toPoint.Z);
writer.Write((byte)speed);
writer.Write((byte)duration);
writer.Write((byte)0);
writer.Write((byte)0);
writer.Write(fixedDirection);
writer.Write(explode);
writer.Write(hue);
writer.Write(renderMode);
writer.Write((short)effect);
writer.Write((short)explodeEffect);
writer.Write((short)explodeSound);
writer.Write(serial);
writer.Write((byte)layer);
writer.Write((short)unknown);
public static void CreateParticleEffect(
Span<byte> buffer,
EffectType type, Serial from, Serial to, int itemID, IPoint3D fromPoint, IPoint3D toPoint,
int speed, int duration, bool fixedDirection, bool explode, int hue, int renderMode, int effect,
int explodeEffect, int explodeSound, Serial serial, int layer, int unknown
)
{
if (buffer[0] != 0)
{
return;
}
public static void CreateTargetParticleEffect(
Span<byte> buffer,
IEntity e, int itemID, int speed, int duration, int hue, int renderMode, int effect, int layer, int unknown
) => CreateParticleEffect(
buffer,
EffectType.FixedFrom,
e.Serial,
Serial.Zero,
itemID,
e.Location,
e.Location,
speed,
duration,
true,
false,
hue,
renderMode,
effect,
1,
0,
e.Serial,
layer,
unknown
);
var writer = new SpanWriter(buffer);
writer.Write((byte)0xC7); // Packet ID
writer.Write((byte)type);
writer.Write(from);
writer.Write(to);
writer.Write((short)itemID);
writer.Write((short)fromPoint.X);
writer.Write((short)fromPoint.Y);
writer.Write((sbyte)fromPoint.Z);
writer.Write((short)toPoint.X);
writer.Write((short)toPoint.Y);
writer.Write((sbyte)toPoint.Z);
writer.Write((byte)speed);
writer.Write((byte)duration);
writer.Write((byte)0);
writer.Write((byte)0);
writer.Write(fixedDirection);
writer.Write(explode);
writer.Write(hue);
writer.Write(renderMode);
writer.Write((short)effect);
writer.Write((short)explodeEffect);
writer.Write((short)explodeSound);
writer.Write(serial);
writer.Write((byte)layer);
writer.Write((short)unknown);
}
public static void CreateLocationParticleEffect(
Span<byte> buffer,
IEntity e, int itemID, int speed, int duration, int hue, int renderMode, int effect, int unknown
) => CreateParticleEffect(
buffer,
EffectType.FixedXYZ,
e.Serial,
Serial.Zero,
itemID,
e.Location,
e.Location,
speed,
duration,
true,
false,
hue,
renderMode,
effect,
1,
0,
e.Serial,
255,
unknown
);
public static void CreateTargetParticleEffect(
Span<byte> buffer,
IEntity e, int itemID, int speed, int duration, int hue, int renderMode, int effect, int layer, int unknown
) => CreateParticleEffect(
buffer,
EffectType.FixedFrom,
e.Serial,
Serial.Zero,
itemID,
e.Location,
e.Location,
speed,
duration,
true,
false,
hue,
renderMode,
effect,
1,
0,
e.Serial,
layer,
unknown
);
public static void CreateMovingParticleEffect(
Span<byte> buffer,
IEntity from, IEntity to, int itemID, int speed, int duration, bool fixedDirection,
bool explodes, int hue, int renderMode, int effect, int explodeEffect, int explodeSound, EffectLayer layer,
int unknown
) => CreateParticleEffect(
buffer,
EffectType.Moving,
from.Serial,
to.Serial,
itemID,
from.Location,
to.Location,
speed,
duration,
fixedDirection,
explodes,
hue,
renderMode,
effect,
explodeEffect,
explodeSound,
Serial.Zero,
(int)layer,
unknown
);
public static void CreateLocationParticleEffect(
Span<byte> buffer,
IEntity e, int itemID, int speed, int duration, int hue, int renderMode, int effect, int unknown
) => CreateParticleEffect(
buffer,
EffectType.FixedXYZ,
e.Serial,
Serial.Zero,
itemID,
e.Location,
e.Location,
speed,
duration,
true,
false,
hue,
renderMode,
effect,
1,
0,
e.Serial,
255,
unknown
);
public static void CreateHuedEffect(
Span<byte> buffer,
EffectType type, Serial from, Serial to, int itemID, Point3D fromPoint, Point3D toPoint,
int speed, int duration, bool fixedDirection, bool explode, int hue, int renderMode
)
public static void CreateMovingParticleEffect(
Span<byte> buffer,
IEntity from, IEntity to, int itemID, int speed, int duration, bool fixedDirection,
bool explodes, int hue, int renderMode, int effect, int explodeEffect, int explodeSound, EffectLayer layer,
int unknown
) => CreateParticleEffect(
buffer,
EffectType.Moving,
from.Serial,
to.Serial,
itemID,
from.Location,
to.Location,
speed,
duration,
fixedDirection,
explodes,
hue,
renderMode,
effect,
explodeEffect,
explodeSound,
Serial.Zero,
(int)layer,
unknown
);
public static void CreateHuedEffect(
Span<byte> buffer,
EffectType type, Serial from, Serial to, int itemID, Point3D fromPoint, Point3D toPoint,
int speed, int duration, bool fixedDirection, bool explode, int hue, int renderMode
)
{
if (buffer[0] != 0)
{
if (buffer[0] != 0)
{
return;
}
var writer = new SpanWriter(buffer);
writer.Write((byte)0xC0); // Packet ID
writer.Write((byte)type);
writer.Write(from);
writer.Write(to);
writer.Write((short)itemID);
writer.Write((short)fromPoint.X);
writer.Write((short)fromPoint.Y);
writer.Write((sbyte)fromPoint.Z);
writer.Write((short)toPoint.X);
writer.Write((short)toPoint.Y);
writer.Write((sbyte)toPoint.Z);
writer.Write((byte)speed);
writer.Write((byte)duration);
writer.Write((byte)0);
writer.Write((byte)0);
writer.Write(fixedDirection);
writer.Write(explode);
writer.Write(hue);
writer.Write(renderMode);
return;
}
public static void CreateTargetHuedEffect(
Span<byte> buffer,
IEntity e, int itemID, int speed, int duration, int hue, int renderMode
) => CreateHuedEffect(
buffer,
EffectType.FixedFrom,
e.Serial,
Serial.Zero,
itemID,
e.Location,
e.Location,
speed,
duration,
true,
false,
hue,
renderMode
);
var writer = new SpanWriter(buffer);
writer.Write((byte)0xC0); // Packet ID
writer.Write((byte)type);
writer.Write(from);
writer.Write(to);
writer.Write((short)itemID);
writer.Write((short)fromPoint.X);
writer.Write((short)fromPoint.Y);
writer.Write((sbyte)fromPoint.Z);
writer.Write((short)toPoint.X);
writer.Write((short)toPoint.Y);
writer.Write((sbyte)toPoint.Z);
writer.Write((byte)speed);
writer.Write((byte)duration);
writer.Write((byte)0);
writer.Write((byte)0);
writer.Write(fixedDirection);
writer.Write(explode);
writer.Write(hue);
writer.Write(renderMode);
}
public static void CreateLocationHuedEffect(
Span<byte> buffer,
Point3D p, int itemID, int speed, int duration, int hue, int renderMode
) => CreateHuedEffect(
buffer,
EffectType.FixedXYZ,
Serial.Zero,
Serial.Zero,
itemID,
p,
p,
speed,
duration,
true,
false,
hue,
renderMode
);
public static void CreateTargetHuedEffect(
Span<byte> buffer,
IEntity e, int itemID, int speed, int duration, int hue, int renderMode
) => CreateHuedEffect(
buffer,
EffectType.FixedFrom,
e.Serial,
Serial.Zero,
itemID,
e.Location,
e.Location,
speed,
duration,
true,
false,
hue,
renderMode
);
public static void CreateMovingHuedEffect(
Span<byte> buffer,
IEntity from, IEntity to, int itemID, int speed, int duration, bool fixedDirection,
bool explodes, int hue, int renderMode
) => CreateHuedEffect(
buffer,
EffectType.Moving,
from.Serial,
to.Serial,
itemID,
from.Location,
to.Location,
speed,
duration,
fixedDirection,
explodes,
hue,
renderMode
);
public static void CreateLocationHuedEffect(
Span<byte> buffer,
Point3D p, int itemID, int speed, int duration, int hue, int renderMode
) => CreateHuedEffect(
buffer,
EffectType.FixedXYZ,
Serial.Zero,
Serial.Zero,
itemID,
p,
p,
speed,
duration,
true,
false,
hue,
renderMode
);
public static void CreateMovingHuedEffect(
Span<byte> buffer,
int itemID, Point3D fromLocation, Point3D toLocation, int speed, int duration,
bool fixedDirection, bool explodes, int hue, int renderMode
) => CreateHuedEffect(
buffer,
EffectType.Moving,
Serial.Zero,
Serial.Zero,
itemID,
fromLocation,
toLocation,
speed,
duration,
fixedDirection,
explodes,
hue,
renderMode
);
public static void CreateMovingHuedEffect(
Span<byte> buffer,
IEntity from, IEntity to, int itemID, int speed, int duration, bool fixedDirection,
bool explodes, int hue, int renderMode
) => CreateHuedEffect(
buffer,
EffectType.Moving,
from.Serial,
to.Serial,
itemID,
from.Location,
to.Location,
speed,
duration,
fixedDirection,
explodes,
hue,
renderMode
);
public static void CreateMovingHuedEffect(
Span<byte> buffer,
Serial from, Serial to, int itemID, Point3D fromLocation, Point3D toLocation, int speed, int duration,
bool fixedDirection, bool explodes, int hue, int renderMode
) => CreateHuedEffect(
buffer,
EffectType.Moving,
from,
to,
itemID,
fromLocation,
toLocation,
speed,
duration,
fixedDirection,
explodes,
hue,
renderMode
);
public static void CreateMovingHuedEffect(
Span<byte> buffer,
int itemID, Point3D fromLocation, Point3D toLocation, int speed, int duration,
bool fixedDirection, bool explodes, int hue, int renderMode
) => CreateHuedEffect(
buffer,
EffectType.Moving,
Serial.Zero,
Serial.Zero,
itemID,
fromLocation,
toLocation,
speed,
duration,
fixedDirection,
explodes,
hue,
renderMode
);
public static void CreateBoltEffect(Span<byte> buffer, IEntity target, int hue) => CreateHuedEffect(
buffer,
EffectType.Lightning,
target.Serial,
Serial.Zero,
0,
target.Location,
target.Location,
0,
0,
false,
false,
hue,
0
);
public static void CreateMovingHuedEffect(
Span<byte> buffer,
Serial from, Serial to, int itemID, Point3D fromLocation, Point3D toLocation, int speed, int duration,
bool fixedDirection, bool explodes, int hue, int renderMode
) => CreateHuedEffect(
buffer,
EffectType.Moving,
from,
to,
itemID,
fromLocation,
toLocation,
speed,
duration,
fixedDirection,
explodes,
hue,
renderMode
);
public static void SendScreenEffect(this NetState ns, ScreenEffectType type)
public static void CreateBoltEffect(Span<byte> buffer, IEntity target, int hue) => CreateHuedEffect(
buffer,
EffectType.Lightning,
target.Serial,
Serial.Zero,
0,
target.Location,
target.Location,
0,
0,
false,
false,
hue,
0
);
public static void SendScreenEffect(this NetState ns, ScreenEffectType type)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[28]);
writer.Write((byte)0x70); // Packet ID
writer.Write((byte)0x4);
writer.Clear(8);
writer.Write((ushort)type);
writer.Clear(16);
ns.Send(writer.Span);
return;
}
var writer = new SpanWriter(stackalloc byte[28]);
writer.Write((byte)0x70); // Packet ID
writer.Write((byte)0x4);
writer.Clear(8);
writer.Write((ushort)type);
writer.Clear(16);
ns.Send(writer.Span);
}
}

View file

@ -17,135 +17,134 @@ using System;
using System.Buffers;
using Server.Items;
namespace Server.Network
namespace Server.Network;
public static class OutgoingEntityPackets
{
public static class OutgoingEntityPackets
public const int OPLPacketLength = 9;
public const int RemoveEntityLength = 5;
public const int MaxWorldEntityPacketLength = 26;
public static void CreateOPLInfo(Span<byte> buffer, Item item) =>
CreateOPLInfo(buffer, item.Serial, item.PropertyList.Hash);
public static void CreateOPLInfo(Span<byte> buffer, Serial serial, int hash)
{
public const int OPLPacketLength = 9;
public const int RemoveEntityLength = 5;
public const int MaxWorldEntityPacketLength = 26;
public static void CreateOPLInfo(Span<byte> buffer, Item item) =>
CreateOPLInfo(buffer, item.Serial, item.PropertyList.Hash);
public static void CreateOPLInfo(Span<byte> buffer, Serial serial, int hash)
if (buffer[0] != 0)
{
if (buffer[0] != 0)
{
return;
}
var writer = new SpanWriter(buffer);
writer.Write((byte)0xDC); // Packet ID
writer.Write(serial);
writer.Write(hash);
return;
}
public static void SendOPLInfo(this NetState ns, IPropertyListObject obj) =>
ns.SendOPLInfo(obj.Serial, obj.PropertyList.Hash);
var writer = new SpanWriter(buffer);
writer.Write((byte)0xDC); // Packet ID
writer.Write(serial);
writer.Write(hash);
}
public static void SendOPLInfo(this NetState ns, Serial serial, int hash)
public static void SendOPLInfo(this NetState ns, IPropertyListObject obj) =>
ns.SendOPLInfo(obj.Serial, obj.PropertyList.Hash);
public static void SendOPLInfo(this NetState ns, Serial serial, int hash)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
Span<byte> buffer = stackalloc byte[OPLPacketLength].InitializePacket();
CreateOPLInfo(buffer, serial, hash);
ns.Send(buffer);
return;
}
public static void CreateRemoveEntity(Span<byte> buffer, Serial serial)
{
if (buffer[0] != 0)
{
return;
}
Span<byte> buffer = stackalloc byte[OPLPacketLength].InitializePacket();
CreateOPLInfo(buffer, serial, hash);
var writer = new SpanWriter(buffer);
writer.Write((byte)0x1D); // Packet ID
writer.Write(serial);
ns.Send(buffer);
}
public static void CreateRemoveEntity(Span<byte> buffer, Serial serial)
{
if (buffer[0] != 0)
{
return;
}
public static void SendRemoveEntity(this NetState ns, Serial serial)
var writer = new SpanWriter(buffer);
writer.Write((byte)0x1D); // Packet ID
writer.Write(serial);
}
public static void SendRemoveEntity(this NetState ns, Serial serial)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
Span<byte> buffer = stackalloc byte[RemoveEntityLength].InitializePacket();
CreateRemoveEntity(buffer, serial);
ns.Send(buffer);
return;
}
public static int CreateWorldEntity(Span<byte> buffer, IEntity entity, bool isHS)
Span<byte> buffer = stackalloc byte[RemoveEntityLength].InitializePacket();
CreateRemoveEntity(buffer, serial);
ns.Send(buffer);
}
public static int CreateWorldEntity(Span<byte> buffer, IEntity entity, bool isHS)
{
if (buffer[0] != 0)
{
if (buffer[0] != 0)
{
return buffer.Length;
}
var writer = new SpanWriter(buffer);
writer.Write((byte)0xF3); // Packet ID
writer.Write((short)0x1); // command
int type = 0;
int gfx = 0;
int amount = 1;
int hue = 0;
byte light = 0;
int flags = 0;
if (entity is BaseMulti multi)
{
type = 2;
gfx = multi.ItemID & (isHS ? 0xFFFF : 0x7FFF);
hue = multi.Hue;
amount = multi.Amount;
}
else if (entity is Item item)
{
// type = 3 if is damageable
gfx = item.ItemID & (isHS ? 0xFFFF : 0x7FFF);
hue = item.Hue;
amount = item.Amount;
light = (byte)item.Light;
flags = item.GetPacketFlags();
}
else if (entity is Mobile mobile)
{
type = 1;
gfx = mobile.Body;
hue = mobile.Hue;
flags = mobile.GetPacketFlags(true);
}
writer.Write((byte)type);
writer.Write(entity.Serial);
writer.Write((ushort)gfx);
writer.Write((byte)0);
writer.Write((short)amount); // Min
writer.Write((short)amount); // Max
writer.Write((short)(entity.X & 0x7FFF));
writer.Write((short)(entity.Y & 0x3FFF));
writer.Write((sbyte)entity.Z);
writer.Write(light);
writer.Write((short)hue);
writer.Write((byte)flags);
if (isHS)
{
writer.Write((short)0);
}
return writer.Position;
return buffer.Length;
}
var writer = new SpanWriter(buffer);
writer.Write((byte)0xF3); // Packet ID
writer.Write((short)0x1); // command
int type = 0;
int gfx = 0;
int amount = 1;
int hue = 0;
byte light = 0;
int flags = 0;
if (entity is BaseMulti multi)
{
type = 2;
gfx = multi.ItemID & (isHS ? 0xFFFF : 0x7FFF);
hue = multi.Hue;
amount = multi.Amount;
}
else if (entity is Item item)
{
// type = 3 if is damageable
gfx = item.ItemID & (isHS ? 0xFFFF : 0x7FFF);
hue = item.Hue;
amount = item.Amount;
light = (byte)item.Light;
flags = item.GetPacketFlags();
}
else if (entity is Mobile mobile)
{
type = 1;
gfx = mobile.Body;
hue = mobile.Hue;
flags = mobile.GetPacketFlags(true);
}
writer.Write((byte)type);
writer.Write(entity.Serial);
writer.Write((ushort)gfx);
writer.Write((byte)0);
writer.Write((short)amount); // Min
writer.Write((short)amount); // Max
writer.Write((short)(entity.X & 0x7FFF));
writer.Write((short)(entity.Y & 0x3FFF));
writer.Write((sbyte)entity.Z);
writer.Write(light);
writer.Write((short)hue);
writer.Write((byte)flags);
if (isHS)
{
writer.Write((short)0);
}
return writer.Position;
}
}

View file

@ -17,108 +17,107 @@ using System;
using System.Buffers;
using System.Collections.Generic;
namespace Server.Network
namespace Server.Network;
public class EquipInfoAttribute
{
public class EquipInfoAttribute
public EquipInfoAttribute(int number, int charges = -1)
{
public EquipInfoAttribute(int number, int charges = -1)
{
Number = number;
Charges = charges;
}
public int Number { get; }
public int Charges { get; }
Number = number;
Charges = charges;
}
public static class OutgoingEquipmentPackets
public int Number { get; }
public int Charges { get; }
}
public static class OutgoingEquipmentPackets
{
public static void SendDisplayEquipmentInfo(
this NetState ns,
Serial serial, int number, string crafterName, bool unidentified, List<EquipInfoAttribute> attrs
)
{
public static void SendDisplayEquipmentInfo(
this NetState ns,
Serial serial, int number, string crafterName, bool unidentified, List<EquipInfoAttribute> attrs
)
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
crafterName = crafterName.DefaultIfNullOrEmpty("");
var length = 17 +
(crafterName.Length > 0 ? 6 + crafterName.Length : 0) +
(unidentified ? 4 : 0) +
attrs.Count * 6;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)length);
writer.Write((ushort)0x10); // Subpacket
writer.Write(serial);
writer.Write(number);
if (crafterName.Length > 0)
{
writer.Write(-3); // crafted by
writer.Write((ushort)crafterName.Length);
writer.WriteAscii(crafterName);
}
if (unidentified)
{
writer.Write(-4);
}
for (var i = 0; i < attrs.Count; ++i)
{
var attr = attrs[i];
writer.Write(attr.Number);
writer.Write((short)attr.Charges);
}
writer.Write(-1);
ns.Send(writer.Span);
return;
}
public static void SendEquipUpdate(this NetState ns, Item item)
crafterName = crafterName.DefaultIfNullOrEmpty("");
var length = 17 +
(crafterName.Length > 0 ? 6 + crafterName.Length : 0) +
(unidentified ? 4 : 0) +
attrs.Count * 6;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)length);
writer.Write((ushort)0x10); // Subpacket
writer.Write(serial);
writer.Write(number);
if (crafterName.Length > 0)
{
if (ns == null)
{
return;
}
writer.Write(-3); // crafted by
Serial parentSerial;
var parent = item.Parent as Mobile;
var hue = item.Hue;
if (parent != null)
{
parentSerial = parent.Serial;
if (parent.SolidHueOverride >= 0)
{
hue = parent.SolidHueOverride;
}
}
else
{
Console.WriteLine("Warning: EquipUpdate on item with !(parent is Mobile)");
parentSerial = Serial.Zero;
}
var writer = new SpanWriter(stackalloc byte[15]);
writer.Write((byte)0x2E); // Packet ID
writer.Write(item.Serial);
writer.Write((short)item.ItemID);
writer.Write((ushort)item.Layer);
writer.Write(parentSerial);
writer.Write((short)hue);
ns.Send(writer.Span);
writer.Write((ushort)crafterName.Length);
writer.WriteAscii(crafterName);
}
if (unidentified)
{
writer.Write(-4);
}
for (var i = 0; i < attrs.Count; ++i)
{
var attr = attrs[i];
writer.Write(attr.Number);
writer.Write((short)attr.Charges);
}
writer.Write(-1);
ns.Send(writer.Span);
}
public static void SendEquipUpdate(this NetState ns, Item item)
{
if (ns.CannotSendPackets())
{
return;
}
Serial parentSerial;
var parent = item.Parent as Mobile;
var hue = item.Hue;
if (parent != null)
{
parentSerial = parent.Serial;
if (parent.SolidHueOverride >= 0)
{
hue = parent.SolidHueOverride;
}
}
else
{
Console.WriteLine("Warning: EquipUpdate on item with !(parent is Mobile)");
parentSerial = Serial.Zero;
}
var writer = new SpanWriter(stackalloc byte[15]);
writer.Write((byte)0x2E); // Packet ID
writer.Write(item.Serial);
writer.Write((short)item.ItemID);
writer.Write((ushort)item.Layer);
writer.Write(parentSerial);
writer.Write((short)hue);
ns.Send(writer.Span);
}
}

View file

@ -22,200 +22,199 @@ using Server.Collections;
using Server.Gumps;
using Server.Logging;
namespace Server.Network
namespace Server.Network;
public static class OutgoingGumpPackets
{
public static class OutgoingGumpPackets
private static readonly ILogger logger = LogFactory.GetLogger(typeof(OutgoingGumpPackets));
public static void SendCloseGump(this NetState ns, int typeId, int buttonId)
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(OutgoingGumpPackets));
public static void SendCloseGump(this NetState ns, int typeId, int buttonId)
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[13]);
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)13);
writer.Write((short)0x04);
writer.Write(typeId);
writer.Write(buttonId);
ns.Send(writer.Span);
return;
}
private static readonly byte[] _layoutBuffer = GC.AllocateUninitializedArray<byte>(0x20000);
private static readonly byte[] _stringsBuffer = GC.AllocateUninitializedArray<byte>(0x20000);
private static readonly byte[] _packBuffer = GC.AllocateUninitializedArray<byte>(0x20000);
private static readonly OrderedHashSet<string> _stringsList = new(32);
var writer = new SpanWriter(stackalloc byte[13]);
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)13);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WritePacked(ReadOnlySpan<byte> span, ref SpanWriter writer)
writer.Write((short)0x04);
writer.Write(typeId);
writer.Write(buttonId);
ns.Send(writer.Span);
}
private static readonly byte[] _layoutBuffer = GC.AllocateUninitializedArray<byte>(0x20000);
private static readonly byte[] _stringsBuffer = GC.AllocateUninitializedArray<byte>(0x20000);
private static readonly byte[] _packBuffer = GC.AllocateUninitializedArray<byte>(0x20000);
private static readonly OrderedHashSet<string> _stringsList = new(32);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WritePacked(ReadOnlySpan<byte> span, ref SpanWriter writer)
{
var length = span.Length;
if (length == 0)
{
var length = span.Length;
if (length == 0)
{
writer.Write(0);
return;
}
var wantLength = Zlib.MaxPackSize(length);
var packBuffer = _packBuffer;
byte[] rentedBuffer = null;
if (wantLength > packBuffer.Length)
{
packBuffer = rentedBuffer = ArrayPool<byte>.Shared.Rent(wantLength);
}
var packLength = wantLength;
var error = Zlib.Pack(packBuffer, ref packLength, span, length, ZlibQuality.Default);
if (error != ZlibError.Okay)
{
logger.Warning($"Gump compression failed {error}");
writer.Write(4);
writer.Write(0);
return;
}
writer.Write(4 + packLength);
writer.Write(length);
writer.Write(packBuffer.AsSpan(0, packLength));
if (rentedBuffer != null)
{
ArrayPool<byte>.Shared.Return(rentedBuffer);
}
writer.Write(0);
return;
}
public static void SendDisplayGump(this NetState ns, Gump gump, out int switches, out int entries)
var wantLength = Zlib.MaxPackSize(length);
var packBuffer = _packBuffer;
byte[] rentedBuffer = null;
if (wantLength > packBuffer.Length)
{
switches = 0;
entries = 0;
if (ns == null)
{
return;
}
var packed = ns.Unpack;
var layoutWriter = new SpanWriter(_layoutBuffer);
if (!gump.Draggable)
{
layoutWriter.Write(Gump.NoMove);
}
if (!gump.Closable)
{
layoutWriter.Write(Gump.NoClose);
}
if (!gump.Disposable)
{
layoutWriter.Write(Gump.NoDispose);
}
if (!gump.Resizable)
{
layoutWriter.Write(Gump.NoResize);
}
foreach (var entry in gump.Entries)
{
entry.AppendTo(ref layoutWriter, _stringsList, ref entries, ref switches);
}
var stringsWriter = new SpanWriter(_stringsBuffer);
foreach (var str in _stringsList)
{
var s = str ?? "";
stringsWriter.Write((ushort)s.Length);
stringsWriter.WriteBigUni(s);
}
int maxLength;
if (packed)
{
var worstLayoutLength = Zlib.MaxPackSize(layoutWriter.BytesWritten);
var worstStringsLength = Zlib.MaxPackSize(stringsWriter.BytesWritten);
maxLength = 40 + worstLayoutLength + worstStringsLength;
}
else
{
maxLength = 23 + layoutWriter.BytesWritten + stringsWriter.BytesWritten;
}
var writer = new SpanWriter(maxLength);
writer.Write((byte)(packed ? 0xDD : 0xB0)); // Packet ID
writer.Seek(2, SeekOrigin.Current);
writer.Write(gump.Serial);
writer.Write(gump.TypeID);
writer.Write(gump.X);
writer.Write(gump.Y);
if (packed)
{
layoutWriter.Write((byte)0); // Layout text terminator
WritePacked(layoutWriter.Span, ref writer);
writer.Write(_stringsList.Count);
WritePacked(stringsWriter.Span, ref writer);
}
else
{
writer.Write((ushort)layoutWriter.BytesWritten);
writer.Write(layoutWriter.Span);
writer.Write((ushort)_stringsList.Count);
writer.Write(stringsWriter.Span);
}
writer.WritePacketLength();
ns.Send(writer.Span);
layoutWriter.Dispose(); // Just in case
stringsWriter.Dispose(); // Just in case
if (_stringsList.Count > 0)
{
_stringsList.Clear();
}
packBuffer = rentedBuffer = ArrayPool<byte>.Shared.Rent(wantLength);
}
public static void SendDisplaySignGump(this NetState ns, Serial serial, int gumpId, string unknown, string caption)
var packLength = wantLength;
var error = Zlib.Pack(packBuffer, ref packLength, span, length, ZlibQuality.Default);
if (error != ZlibError.Okay)
{
if (ns == null)
{
return;
}
logger.Warning($"Gump compression failed {error}");
unknown ??= "";
caption ??= "";
writer.Write(4);
writer.Write(0);
return;
}
var length = 15 + unknown.Length + caption.Length;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0x8B); // Packet ID
writer.Write((ushort)length);
writer.Write(4 + packLength);
writer.Write(length);
writer.Write(packBuffer.AsSpan(0, packLength));
writer.Write(serial);
writer.Write((short)gumpId);
writer.Write((short)(unknown.Length + 1));
writer.WriteAsciiNull(unknown);
writer.Write((short)(caption.Length + 1));
writer.WriteAsciiNull(caption);
ns.Send(writer.Span);
if (rentedBuffer != null)
{
ArrayPool<byte>.Shared.Return(rentedBuffer);
}
}
public static void SendDisplayGump(this NetState ns, Gump gump, out int switches, out int entries)
{
switches = 0;
entries = 0;
if (ns.CannotSendPackets())
{
return;
}
var packed = ns.Unpack;
var layoutWriter = new SpanWriter(_layoutBuffer);
if (!gump.Draggable)
{
layoutWriter.Write(Gump.NoMove);
}
if (!gump.Closable)
{
layoutWriter.Write(Gump.NoClose);
}
if (!gump.Disposable)
{
layoutWriter.Write(Gump.NoDispose);
}
if (!gump.Resizable)
{
layoutWriter.Write(Gump.NoResize);
}
foreach (var entry in gump.Entries)
{
entry.AppendTo(ref layoutWriter, _stringsList, ref entries, ref switches);
}
var stringsWriter = new SpanWriter(_stringsBuffer);
foreach (var str in _stringsList)
{
var s = str ?? "";
stringsWriter.Write((ushort)s.Length);
stringsWriter.WriteBigUni(s);
}
int maxLength;
if (packed)
{
var worstLayoutLength = Zlib.MaxPackSize(layoutWriter.BytesWritten);
var worstStringsLength = Zlib.MaxPackSize(stringsWriter.BytesWritten);
maxLength = 40 + worstLayoutLength + worstStringsLength;
}
else
{
maxLength = 23 + layoutWriter.BytesWritten + stringsWriter.BytesWritten;
}
var writer = new SpanWriter(maxLength);
writer.Write((byte)(packed ? 0xDD : 0xB0)); // Packet ID
writer.Seek(2, SeekOrigin.Current);
writer.Write(gump.Serial);
writer.Write(gump.TypeID);
writer.Write(gump.X);
writer.Write(gump.Y);
if (packed)
{
layoutWriter.Write((byte)0); // Layout text terminator
WritePacked(layoutWriter.Span, ref writer);
writer.Write(_stringsList.Count);
WritePacked(stringsWriter.Span, ref writer);
}
else
{
writer.Write((ushort)layoutWriter.BytesWritten);
writer.Write(layoutWriter.Span);
writer.Write((ushort)_stringsList.Count);
writer.Write(stringsWriter.Span);
}
writer.WritePacketLength();
ns.Send(writer.Span);
layoutWriter.Dispose(); // Just in case
stringsWriter.Dispose(); // Just in case
if (_stringsList.Count > 0)
{
_stringsList.Clear();
}
}
public static void SendDisplaySignGump(this NetState ns, Serial serial, int gumpId, string unknown, string caption)
{
if (ns.CannotSendPackets())
{
return;
}
unknown ??= "";
caption ??= "";
var length = 15 + unknown.Length + caption.Length;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0x8B); // Packet ID
writer.Write((ushort)length);
writer.Write(serial);
writer.Write((short)gumpId);
writer.Write((short)(unknown.Length + 1));
writer.WriteAsciiNull(unknown);
writer.Write((short)(caption.Length + 1));
writer.WriteAsciiNull(caption);
ns.Send(writer.Span);
}
}

View file

@ -17,84 +17,83 @@ using System;
using System.Buffers;
using Server.Items;
namespace Server.Network
namespace Server.Network;
public static class OutgoingItemPackets
{
public static class OutgoingItemPackets
public static int CreateWorldItem(Span<byte> buffer, Item item)
{
public static int CreateWorldItem(Span<byte> buffer, Item item)
if (buffer[0] != 0)
{
if (buffer[0] != 0)
{
// This assumes the packet was sliced properly
return buffer.Length;
}
var itemID = item is BaseMulti ? item.ItemID | 0x4000 : item.ItemID & 0x3FFF;
var amount = item.Amount;
var hasAmount = amount != 0;
var serial = hasAmount ? item.Serial.Value | 0x80000000 : item.Serial.Value & 0x7FFFFFFF;
var loc = item.Location;
var hue = item.Hue;
var flags = item.GetPacketFlags();
var direction = (int)item.Direction;
var hasDirection = direction != 0;
var hasHue = hue != 0;
var hasFlags = flags != 0;
var x = hasDirection ? loc.X | 0x8000 : loc.X & 0x7FFF;
var y = (loc.Y & 0x3FFF) | (hasHue ? 0x8000 : 0) | (hasFlags ? 0x4000 : 0);
var length = 14 + (hasAmount ? 2 : 0) +
(hasDirection ? 1 : 0) +
(hasHue ? 2 : 0) +
(hasFlags ? 1 : 0);
var writer = new SpanWriter(buffer);
writer.Write((byte)0x1A); // Packet ID
writer.Write((ushort)length);
writer.Write(serial);
writer.Write((ushort)itemID);
if (hasAmount)
{
writer.Write((ushort)amount);
}
writer.Write((ushort)x);
writer.Write((ushort)y);
if (hasDirection)
{
writer.Write((byte)direction);
}
writer.Write((sbyte)loc.Z);
if (hasHue)
{
writer.Write((ushort)hue);
}
if (hasFlags)
{
writer.Write((byte)flags);
}
return writer.BytesWritten;
// This assumes the packet was sliced properly
return buffer.Length;
}
public static void SendWorldItem(this NetState ns, Item item)
var itemID = item is BaseMulti ? item.ItemID | 0x4000 : item.ItemID & 0x3FFF;
var amount = item.Amount;
var hasAmount = amount != 0;
var serial = hasAmount ? item.Serial.Value | 0x80000000 : item.Serial.Value & 0x7FFFFFFF;
var loc = item.Location;
var hue = item.Hue;
var flags = item.GetPacketFlags();
var direction = (int)item.Direction;
var hasDirection = direction != 0;
var hasHue = hue != 0;
var hasFlags = flags != 0;
var x = hasDirection ? loc.X | 0x8000 : loc.X & 0x7FFF;
var y = (loc.Y & 0x3FFF) | (hasHue ? 0x8000 : 0) | (hasFlags ? 0x4000 : 0);
var length = 14 + (hasAmount ? 2 : 0) +
(hasDirection ? 1 : 0) +
(hasHue ? 2 : 0) +
(hasFlags ? 1 : 0);
var writer = new SpanWriter(buffer);
writer.Write((byte)0x1A); // Packet ID
writer.Write((ushort)length);
writer.Write(serial);
writer.Write((ushort)itemID);
if (hasAmount)
{
if (ns == null)
{
return;
}
Span<byte> buffer = stackalloc byte[OutgoingEntityPackets.MaxWorldEntityPacketLength].InitializePacket();
var length = ns.StygianAbyss ?
OutgoingEntityPackets.CreateWorldEntity(buffer, item, ns.HighSeas) :
CreateWorldItem(buffer, item);
ns.Send(buffer[..length]);
writer.Write((ushort)amount);
}
writer.Write((ushort)x);
writer.Write((ushort)y);
if (hasDirection)
{
writer.Write((byte)direction);
}
writer.Write((sbyte)loc.Z);
if (hasHue)
{
writer.Write((ushort)hue);
}
if (hasFlags)
{
writer.Write((byte)flags);
}
return writer.BytesWritten;
}
public static void SendWorldItem(this NetState ns, Item item)
{
if (ns.CannotSendPackets())
{
return;
}
Span<byte> buffer = stackalloc byte[OutgoingEntityPackets.MaxWorldEntityPacketLength].InitializePacket();
var length = ns.StygianAbyss ?
OutgoingEntityPackets.CreateWorldEntity(buffer, item, ns.HighSeas) :
CreateWorldItem(buffer, item);
ns.Send(buffer[..length]);
}
}

View file

@ -16,27 +16,26 @@
using System.Buffers;
using System.Runtime.CompilerServices;
namespace Server.Network
namespace Server.Network;
public static class OutgoingLightPackets
{
public static class OutgoingLightPackets
public static void SendPersonalLightLevel(this NetState ns, Serial serial, int level)
{
public static void SendPersonalLightLevel(this NetState ns, Serial serial, int level)
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[6]);
writer.Write((byte)0x4E); // Packet ID
writer.Write(serial);
writer.Write((byte)level);
ns.Send(writer.Span);
return;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendGlobalLightLevel(this NetState ns, int level = 0) =>
ns?.Send(stackalloc byte[] { 0x4F, (byte)level });
var writer = new SpanWriter(stackalloc byte[6]);
writer.Write((byte)0x4E); // Packet ID
writer.Write(serial);
writer.Write((byte)level);
ns.Send(writer.Span);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendGlobalLightLevel(this NetState ns, int level = 0) =>
ns?.Send(stackalloc byte[] { 0x4F, (byte)level });
}

View file

@ -16,50 +16,49 @@
using System.Buffers;
using System.Runtime.CompilerServices;
namespace Server.Network
namespace Server.Network;
public static class OutgoingMapPackets
{
public static class OutgoingMapPackets
private static byte[] _mapPatchesPacket = new byte[41];
public static void SendMapPatches(this NetState ns)
{
private static byte[] _mapPatchesPacket = new byte[41];
public static void SendMapPatches(this NetState ns)
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
if (_mapPatchesPacket[0] == 0)
{
var writer = new SpanWriter(_mapPatchesPacket);
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)41); // Length
writer.Write((ushort)0x18); // Subpacket
writer.Write(4);
for (int i = 0; i < 4; i++)
{
var map = Map.Maps[i];
writer.Write(map?.Tiles.Patch.StaticBlocks ?? 0);
writer.Write(map?.Tiles.Patch.LandBlocks ?? 0);
}
}
ns.Send(_mapPatchesPacket);
return;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendInvalidMap(this NetState ns) => ns?.Send(stackalloc byte[] { 0xC6 });
public static void SendMapChange(this NetState ns, Map map)
if (_mapPatchesPacket[0] == 0)
{
if (map == null)
{
return;
}
var writer = new SpanWriter(_mapPatchesPacket);
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)41); // Length
writer.Write((ushort)0x18); // Subpacket
writer.Write(4);
ns?.Send(stackalloc byte[] { 0xBF, 0x00, 0x06, 0x00, 0x08, (byte)map.MapID });
for (int i = 0; i < 4; i++)
{
var map = Map.Maps[i];
writer.Write(map?.Tiles.Patch.StaticBlocks ?? 0);
writer.Write(map?.Tiles.Patch.LandBlocks ?? 0);
}
}
ns.Send(_mapPatchesPacket);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendInvalidMap(this NetState ns) => ns?.Send(stackalloc byte[] { 0xC6 });
public static void SendMapChange(this NetState ns, Map map)
{
if (map == null)
{
return;
}
ns?.Send(stackalloc byte[] { 0xBF, 0x00, 0x06, 0x00, 0x08, (byte)map.MapID });
}
}

View file

@ -5,210 +5,209 @@ using Server.ContextMenus;
using Server.Menus.ItemLists;
using Server.Menus.Questions;
namespace Server.Network
namespace Server.Network;
[Flags]
public enum CMEFlags
{
[Flags]
public enum CMEFlags
None = 0x00,
Disabled = 0x01,
Arrow = 0x02,
Highlighted = 0x04,
Colored = 0x20
}
public static class OutgoingMenuPackets
{
public static void SendDisplayItemListMenu(this NetState ns, ItemListMenu menu)
{
None = 0x00,
Disabled = 0x01,
Arrow = 0x02,
Highlighted = 0x04,
Colored = 0x20
if (ns == null || menu == null)
{
return;
}
var question = menu.Question?.Trim();
var questionLength = question?.Length ?? 0;
var entries = menu.Entries;
int entriesLength = (byte)entries.Length;
var maxLength = 11 + questionLength;
for (int i = 0; i < entriesLength; i++)
{
maxLength += 5 + entries[i].Name?.Length ?? 0; // could be trimmed
}
var writer = new SpanWriter(stackalloc byte[maxLength]);
writer.Write((byte)0x7C); // Packet ID
writer.Seek(2, SeekOrigin.Current);
writer.Write(menu.Serial);
writer.Write((ushort)0);
writer.Write((byte)questionLength);
if (question != null)
{
writer.WriteAscii(question);
}
writer.Write((byte)entriesLength);
for (var i = 0; i < entriesLength; ++i)
{
var e = entries[i];
writer.Write((ushort)e.ItemID);
writer.Write((short)e.Hue);
var name = e.Name?.Trim();
if (name == null)
{
writer.Write((byte)0);
}
else
{
var nameLength = name.Length;
writer.Write((byte)nameLength);
writer.WriteAscii(name);
}
}
writer.WritePacketLength();
ns.Send(writer.Span);
}
public static class OutgoingMenuPackets
public static void SendDisplayQuestionMenu(this NetState ns, QuestionMenu menu)
{
public static void SendDisplayItemListMenu(this NetState ns, ItemListMenu menu)
if (ns == null || menu == null)
{
if (ns == null || menu == null)
{
return;
}
var question = menu.Question?.Trim();
var questionLength = question?.Length ?? 0;
var entries = menu.Entries;
int entriesLength = (byte)entries.Length;
var maxLength = 11 + questionLength;
for (int i = 0; i < entriesLength; i++)
{
maxLength += 5 + entries[i].Name?.Length ?? 0; // could be trimmed
}
var writer = new SpanWriter(stackalloc byte[maxLength]);
writer.Write((byte)0x7C); // Packet ID
writer.Seek(2, SeekOrigin.Current);
writer.Write(menu.Serial);
writer.Write((ushort)0);
writer.Write((byte)questionLength);
if (question != null)
{
writer.WriteAscii(question);
}
writer.Write((byte)entriesLength);
for (var i = 0; i < entriesLength; ++i)
{
var e = entries[i];
writer.Write((ushort)e.ItemID);
writer.Write((short)e.Hue);
var name = e.Name?.Trim();
if (name == null)
{
writer.Write((byte)0);
}
else
{
var nameLength = name.Length;
writer.Write((byte)nameLength);
writer.WriteAscii(name);
}
}
writer.WritePacketLength();
ns.Send(writer.Span);
return;
}
public static void SendDisplayQuestionMenu(this NetState ns, QuestionMenu menu)
var question = menu.Question?.Trim();
var questionLength = question?.Length ?? 0;
var answers = menu.Answers;
int answersLength = (byte)answers.Length;
var maxLength = 11 + questionLength;
for (int i = 0; i < answersLength; i++)
{
if (ns == null || menu == null)
{
return;
}
var question = menu.Question?.Trim();
var questionLength = question?.Length ?? 0;
var answers = menu.Answers;
int answersLength = (byte)answers.Length;
var maxLength = 11 + questionLength;
for (int i = 0; i < answersLength; i++)
{
maxLength += 5 + answers[i]?.Length ?? 0; // could be trimmed
}
var writer = new SpanWriter(stackalloc byte[maxLength]);
writer.Write((byte)0x7C); // Packet ID
writer.Seek(2, SeekOrigin.Current);
writer.Write(menu.Serial);
writer.Write((ushort)0);
writer.Write((byte)questionLength);
if (question != null)
{
writer.WriteAscii(question);
}
writer.Write((byte)answersLength);
for (var i = 0; i < answersLength; ++i)
{
writer.Write(0);
var answer = answers[i]?.Trim();
if (answer == null)
{
writer.Write((byte)0);
}
else
{
var nameLength = answer.Length;
writer.Write((byte)nameLength);
writer.WriteAscii(answer);
}
}
writer.WritePacketLength();
ns.Send(writer.Span);
maxLength += 5 + answers[i]?.Length ?? 0; // could be trimmed
}
public static void SendDisplayContextMenu(this NetState ns, ContextMenu menu)
var writer = new SpanWriter(stackalloc byte[maxLength]);
writer.Write((byte)0x7C); // Packet ID
writer.Seek(2, SeekOrigin.Current);
writer.Write(menu.Serial);
writer.Write((ushort)0);
writer.Write((byte)questionLength);
if (question != null)
{
if (ns == null || menu == null)
{
return;
}
var newCommand = ns.NewHaven && menu.RequiresNewPacket;
var entries = menu.Entries;
var entriesLength = (byte)entries.Length;
var maxLength = 12 + entriesLength * 8;
var writer = new SpanWriter(stackalloc byte[maxLength]);
writer.Write((byte)0xBF); // Packet ID
writer.Seek(2, SeekOrigin.Current); // Length
writer.Write((short)0x14); // Subpacket
writer.Write((short)(newCommand ? 0x02 : 0x01)); // Command
var target = menu.Target;
writer.Write(target.Serial);
writer.Write(entriesLength);
var p = target switch
{
Mobile _ => target.Location,
Item item => item.GetWorldLocation(),
_ => Point3D.Zero
};
for (var i = 0; i < entriesLength; ++i)
{
var e = entries[i];
var range = e.Range;
if (range == -1)
{
range = Core.GlobalUpdateRange;
}
var flags = e.Flags;
if (!(e.Enabled && menu.From.InRange(p, range)))
{
flags |= CMEFlags.Disabled;
}
if (newCommand)
{
writer.Write(e.Number);
writer.Write((short)i);
writer.Write((short)flags);
}
else
{
writer.Write((short)i);
writer.Write((ushort)(e.Number - 3000000));
var color = e.Color & 0xFFFF;
if (color != 0xFFFF)
{
flags |= CMEFlags.Colored;
}
writer.Write((short)flags);
if ((flags & CMEFlags.Colored) != 0)
{
writer.Write((short)color);
}
}
}
writer.WritePacketLength();
ns.Send(writer.Span);
writer.WriteAscii(question);
}
writer.Write((byte)answersLength);
for (var i = 0; i < answersLength; ++i)
{
writer.Write(0);
var answer = answers[i]?.Trim();
if (answer == null)
{
writer.Write((byte)0);
}
else
{
var nameLength = answer.Length;
writer.Write((byte)nameLength);
writer.WriteAscii(answer);
}
}
writer.WritePacketLength();
ns.Send(writer.Span);
}
public static void SendDisplayContextMenu(this NetState ns, ContextMenu menu)
{
if (ns == null || menu == null)
{
return;
}
var newCommand = ns.NewHaven && menu.RequiresNewPacket;
var entries = menu.Entries;
var entriesLength = (byte)entries.Length;
var maxLength = 12 + entriesLength * 8;
var writer = new SpanWriter(stackalloc byte[maxLength]);
writer.Write((byte)0xBF); // Packet ID
writer.Seek(2, SeekOrigin.Current); // Length
writer.Write((short)0x14); // Subpacket
writer.Write((short)(newCommand ? 0x02 : 0x01)); // Command
var target = menu.Target;
writer.Write(target.Serial);
writer.Write(entriesLength);
var p = target switch
{
Mobile _ => target.Location,
Item item => item.GetWorldLocation(),
_ => Point3D.Zero
};
for (var i = 0; i < entriesLength; ++i)
{
var e = entries[i];
var range = e.Range;
if (range == -1)
{
range = Core.GlobalUpdateRange;
}
var flags = e.Flags;
if (!(e.Enabled && menu.From.InRange(p, range)))
{
flags |= CMEFlags.Disabled;
}
if (newCommand)
{
writer.Write(e.Number);
writer.Write((short)i);
writer.Write((short)flags);
}
else
{
writer.Write((short)i);
writer.Write((ushort)(e.Number - 3000000));
var color = e.Color & 0xFFFF;
if (color != 0xFFFF)
{
flags |= CMEFlags.Colored;
}
writer.Write((short)flags);
if ((flags & CMEFlags.Colored) != 0)
{
writer.Write((short)color);
}
}
}
writer.WritePacketLength();
ns.Send(writer.Span);
}
}

View file

@ -19,264 +19,263 @@ using System.IO;
using System.Runtime.CompilerServices;
using Server.Prompts;
namespace Server.Network
namespace Server.Network;
[Flags]
public enum AffixType : byte
{
[Flags]
public enum AffixType : byte
Append = 0x00,
Prepend = 0x01,
System = 0x02
}
public static class OutgoingMessagePackets
{
public static void SendMessageLocalized(
this NetState ns,
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name = "", string args = ""
)
{
Append = 0x00,
Prepend = 0x01,
System = 0x02
if (ns.CannotSendPackets())
{
return;
}
Span<byte> buffer = stackalloc byte[GetMaxMessageLocalizedLength(args)];
var length = CreateMessageLocalized(
buffer, serial, graphic, type, hue, font, number, name, args
);
ns.Send(buffer[..length]);
}
public static class OutgoingMessagePackets
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetMaxMessageLocalizedLength(string args) => 50 + (args?.Length ?? 0) * 2;
public static int CreateMessageLocalized(
Span<byte> buffer,
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name = "", string args = ""
)
{
public static void SendMessageLocalized(
this NetState ns,
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name = "", string args = ""
)
if (buffer[0] != 0)
{
if (ns == null)
{
return;
}
Span<byte> buffer = stackalloc byte[GetMaxMessageLocalizedLength(args)];
var length = CreateMessageLocalized(
buffer, serial, graphic, type, hue, font, number, name, args
);
ns.Send(buffer[..length]);
return buffer.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetMaxMessageLocalizedLength(string args) => 50 + (args?.Length ?? 0) * 2;
name ??= "";
args ??= "";
public static int CreateMessageLocalized(
Span<byte> buffer,
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name = "", string args = ""
)
if (hue == 0)
{
if (buffer[0] != 0)
{
return buffer.Length;
}
hue = 0x3B2;
}
name ??= "";
args ??= "";
var writer = new SpanWriter(buffer);
writer.Write((byte)0xC1);
writer.Seek(2, SeekOrigin.Current);
writer.Write(serial);
writer.Write((short)graphic);
writer.Write((byte)type);
writer.Write((short)hue);
writer.Write((short)font);
writer.Write(number);
writer.WriteAscii(name, 30);
writer.WriteLittleUniNull(args);
if (hue == 0)
{
hue = 0x3B2;
}
writer.WritePacketLength();
return writer.Position;
}
var writer = new SpanWriter(buffer);
writer.Write((byte)0xC1);
writer.Seek(2, SeekOrigin.Current);
writer.Write(serial);
writer.Write((short)graphic);
writer.Write((byte)type);
writer.Write((short)hue);
writer.Write((short)font);
writer.Write(number);
public static void SendMessageLocalizedAffix(
this NetState ns,
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name,
AffixType affixType, string affix = "", string args = ""
)
{
if (ns.CannotSendPackets())
{
return;
}
Span<byte> buffer = stackalloc byte[GetMaxMessageLocalizedAffixLength(affix, args)].InitializePacket();
var length = CreateMessageLocalizedAffix(
buffer, serial, graphic, type, hue, font, number, name, affixType, affix, args
);
ns.Send(buffer[..length]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetMaxMessageLocalizedAffixLength(string affix, string args) =>
52 + (affix?.Length ?? 0) + (args?.Length ?? 0) * 2;
public static int CreateMessageLocalizedAffix(
Span<byte> buffer,
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name,
AffixType affixType, string affix = "", string args = ""
)
{
if (buffer[0] != 0)
{
return buffer.Length;
}
name ??= "";
affix ??= "";
args ??= "";
if (hue == 0)
{
hue = 0x3B2;
}
var writer = new SpanWriter(buffer);
writer.Write((byte)0xCC);
writer.Seek(2, SeekOrigin.Current);
writer.Write(serial);
writer.Write((short)graphic);
writer.Write((byte)type);
writer.Write((short)hue);
writer.Write((short)font);
writer.Write(number);
writer.Write((byte)affixType);
writer.WriteAscii(name, 30);
writer.WriteAsciiNull(affix);
writer.WriteBigUniNull(args);
writer.WritePacketLength();
return writer.Position;
}
public static void SendMessage(
this NetState ns,
Serial serial, int graphic, MessageType type, int hue, int font, bool ascii, string lang, string name, string text
)
{
if (ns.CannotSendPackets())
{
return;
}
Span<byte> buffer = stackalloc byte[GetMaxMessageLength(text)].InitializePacket();
var length = CreateMessage(
buffer,
serial,
graphic,
type,
hue,
font,
ascii,
lang,
name,
text
);
ns.Send(buffer[..length]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetMaxMessageLength(string text) => 50 + (text?.Length ?? 0) * 2;
public static int CreateMessage(
Span<byte> buffer,
Serial serial,
int graphic,
MessageType type,
int hue,
int font,
bool ascii,
string lang,
string name,
string text
)
{
if (buffer[0] != 0)
{
return buffer.Length;
}
name ??= "";
text ??= "";
lang ??= "ENU";
if (hue == 0)
{
hue = 0x3B2;
}
var writer = new SpanWriter(buffer);
writer.Write((byte)(ascii ? 0x1C : 0xAE)); // Packet ID
writer.Seek(2, SeekOrigin.Current);
writer.Write(serial);
writer.Write((short)graphic);
writer.Write((byte)type);
writer.Write((short)hue);
writer.Write((short)font);
if (ascii)
{
writer.WriteAscii(name, 30);
writer.WriteLittleUniNull(args);
writer.WritePacketLength();
return writer.Position;
writer.WriteAsciiNull(text);
}
public static void SendMessageLocalizedAffix(
this NetState ns,
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name,
AffixType affixType, string affix = "", string args = ""
)
else
{
if (ns == null)
{
return;
}
Span<byte> buffer = stackalloc byte[GetMaxMessageLocalizedAffixLength(affix, args)].InitializePacket();
var length = CreateMessageLocalizedAffix(
buffer, serial, graphic, type, hue, font, number, name, affixType, affix, args
);
ns.Send(buffer[..length]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetMaxMessageLocalizedAffixLength(string affix, string args) =>
52 + (affix?.Length ?? 0) + (args?.Length ?? 0) * 2;
public static int CreateMessageLocalizedAffix(
Span<byte> buffer,
Serial serial, int graphic, MessageType type, int hue, int font, int number, string name,
AffixType affixType, string affix = "", string args = ""
)
{
if (buffer[0] != 0)
{
return buffer.Length;
}
name ??= "";
affix ??= "";
args ??= "";
if (hue == 0)
{
hue = 0x3B2;
}
var writer = new SpanWriter(buffer);
writer.Write((byte)0xCC);
writer.Seek(2, SeekOrigin.Current);
writer.Write(serial);
writer.Write((short)graphic);
writer.Write((byte)type);
writer.Write((short)hue);
writer.Write((short)font);
writer.Write(number);
writer.Write((byte)affixType);
writer.WriteAscii(lang, 4);
writer.WriteAscii(name, 30);
writer.WriteAsciiNull(affix);
writer.WriteBigUniNull(args);
writer.WritePacketLength();
return writer.Position;
}
public static void SendMessage(
this NetState ns,
Serial serial, int graphic, MessageType type, int hue, int font, bool ascii, string lang, string name, string text
)
{
if (ns == null)
{
return;
}
Span<byte> buffer = stackalloc byte[GetMaxMessageLength(text)].InitializePacket();
var length = CreateMessage(
buffer,
serial,
graphic,
type,
hue,
font,
ascii,
lang,
name,
text
);
ns.Send(buffer[..length]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetMaxMessageLength(string text) => 50 + (text?.Length ?? 0) * 2;
public static int CreateMessage(
Span<byte> buffer,
Serial serial,
int graphic,
MessageType type,
int hue,
int font,
bool ascii,
string lang,
string name,
string text
)
{
if (buffer[0] != 0)
{
return buffer.Length;
}
name ??= "";
text ??= "";
lang ??= "ENU";
if (hue == 0)
{
hue = 0x3B2;
}
var writer = new SpanWriter(buffer);
writer.Write((byte)(ascii ? 0x1C : 0xAE)); // Packet ID
writer.Seek(2, SeekOrigin.Current);
writer.Write(serial);
writer.Write((short)graphic);
writer.Write((byte)type);
writer.Write((short)hue);
writer.Write((short)font);
if (ascii)
{
writer.WriteAscii(name, 30);
writer.WriteAsciiNull(text);
}
else
{
writer.WriteAscii(lang, 4);
writer.WriteAscii(name, 30);
writer.WriteBigUniNull(text);
}
writer.WritePacketLength();
return writer.Position;
}
public static void SendFollowMessage(this NetState ns, Serial s1, Serial s2)
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[9]);
writer.Write((byte)0x15); // Packet ID
writer.Write(s1);
writer.Write(s2);
ns.Send(writer.Span);
}
public static void SendPrompt(this NetState ns, Prompt prompt)
{
if (ns == null || prompt == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[21]);
writer.Write((byte)0xC2); // Packet ID
writer.Write((ushort)21);
writer.Write(prompt.Serial);
writer.Write(prompt.Serial);
writer.Clear(10);
ns.Send(writer.Span);
}
public static void SendHelpResponse(this NetState ns, Serial s, string text)
{
text = text?.Trim() ?? "";
if (ns == null || text.Length == 0)
{
return;
}
var length = 9 + text.Length * 2;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0xB7);
writer.Write((ushort)length);
writer.Write(s);
writer.WriteBigUniNull(text);
ns.Send(writer.Span);
}
writer.WritePacketLength();
return writer.Position;
}
public static void SendFollowMessage(this NetState ns, Serial s1, Serial s2)
{
if (ns.CannotSendPackets())
{
return;
}
var writer = new SpanWriter(stackalloc byte[9]);
writer.Write((byte)0x15); // Packet ID
writer.Write(s1);
writer.Write(s2);
ns.Send(writer.Span);
}
public static void SendPrompt(this NetState ns, Prompt prompt)
{
if (ns == null || prompt == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[21]);
writer.Write((byte)0xC2); // Packet ID
writer.Write((ushort)21);
writer.Write(prompt.Serial);
writer.Write(prompt.Serial);
writer.Clear(10);
ns.Send(writer.Span);
}
public static void SendHelpResponse(this NetState ns, Serial s, string text)
{
text = text?.Trim() ?? "";
if (ns == null || text.Length == 0)
{
return;
}
var length = 9 + text.Length * 2;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0xB7);
writer.Write((ushort)length);
writer.Write(s);
writer.WriteBigUniNull(text);
ns.Send(writer.Span);
}
}

File diff suppressed because it is too large Load diff

View file

@ -16,102 +16,101 @@
using System.Buffers;
using System.Runtime.CompilerServices;
namespace Server.Network
namespace Server.Network;
public enum SpeedControlSetting
{
public enum SpeedControlSetting
Disable,
Mount,
Walk
}
public static class OutgoingMovementPackets
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendSpeedControl(this NetState ns, SpeedControlSetting speedControl) =>
ns?.Send(stackalloc byte[] { 0xBF, 0x00, 0x6, 0x00, 0x26, (byte)speedControl });
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendMovePlayer(this NetState ns, Direction d) => ns?.Send(stackalloc byte[] { 0x97, (byte)d });
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendMovementAck(this NetState ns, int seq, Mobile m) =>
ns.SendMovementAck(seq, Notoriety.Compute(m, m));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendMovementAck(this NetState ns, int seq, int noto) =>
ns?.Send(stackalloc byte[] { 0x22, (byte)seq, (byte)noto });
public static void SendMovementRej(this NetState ns, int seq, Mobile m)
{
Disable,
Mount,
Walk
if (ns.CannotSendPackets())
{
return;
}
var writer = new SpanWriter(stackalloc byte[8]);
writer.Write((byte)0x21); // Packet ID
writer.Write((byte)seq);
writer.Write((short)m.X);
writer.Write((short)m.Y);
writer.Write((byte)m.Direction);
writer.Write((sbyte)m.Z);
ns.Send(writer.Span);
}
public static class OutgoingMovementPackets
public static void SendInitialFastwalkStack(this NetState ns, uint[] keys)
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendSpeedControl(this NetState ns, SpeedControlSetting speedControl) =>
ns?.Send(stackalloc byte[] { 0xBF, 0x00, 0x6, 0x00, 0x26, (byte)speedControl });
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendMovePlayer(this NetState ns, Direction d) => ns?.Send(stackalloc byte[] { 0x97, (byte)d });
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendMovementAck(this NetState ns, int seq, Mobile m) =>
ns.SendMovementAck(seq, Notoriety.Compute(m, m));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendMovementAck(this NetState ns, int seq, int noto) =>
ns?.Send(stackalloc byte[] { 0x22, (byte)seq, (byte)noto });
public static void SendMovementRej(this NetState ns, int seq, Mobile m)
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[8]);
writer.Write((byte)0x21); // Packet ID
writer.Write((byte)seq);
writer.Write((short)m.X);
writer.Write((short)m.Y);
writer.Write((byte)m.Direction);
writer.Write((sbyte)m.Z);
ns.Send(writer.Span);
return;
}
public static void SendInitialFastwalkStack(this NetState ns, uint[] keys)
var writer = new SpanWriter(stackalloc byte[29]);
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)29);
writer.Write((ushort)0x1); // Subpacket
writer.Write(keys[0]);
writer.Write(keys[1]);
writer.Write(keys[2]);
writer.Write(keys[3]);
writer.Write(keys[4]);
writer.Write(keys[5]);
ns.Send(writer.Span);
}
public static void SendFastwalkStackKey(this NetState ns, uint key = 0)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[29]);
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)29);
writer.Write((ushort)0x1); // Subpacket
writer.Write(keys[0]);
writer.Write(keys[1]);
writer.Write(keys[2]);
writer.Write(keys[3]);
writer.Write(keys[4]);
writer.Write(keys[5]);
ns.Send(writer.Span);
return;
}
public static void SendFastwalkStackKey(this NetState ns, uint key = 0)
var writer = new SpanWriter(stackalloc byte[9]);
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)9);
writer.Write((ushort)0x2); // Subpacket
writer.Write(key);
ns.Send(writer.Span);
}
public static void SendTimeSyncResponse(this NetState ns)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[9]);
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)9);
writer.Write((ushort)0x2); // Subpacket
writer.Write(key);
ns.Send(writer.Span);
return;
}
public static void SendTimeSyncResponse(this NetState ns)
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[25]);
writer.Write((byte)0xF2); // Packet ID
var writer = new SpanWriter(stackalloc byte[25]);
writer.Write((byte)0xF2); // Packet ID
writer.Write(Core.TickCount); // ??
writer.Write(Core.TickCount); // ??
writer.Write(Core.TickCount); // ??
writer.Write(Core.TickCount); // ??
writer.Write(Core.TickCount); // ??
writer.Write(Core.TickCount); // ??
ns.Send(writer.Span);
}
ns.Send(writer.Span);
}
}

View file

@ -0,0 +1,9 @@
using System.Runtime.CompilerServices;
namespace Server.Network;
public static class OutgoingPackets
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool CannotSendPackets(this NetState ns) => ns?.Connection == null || ns.BlockAllPackets;
}

View file

@ -17,338 +17,337 @@ using System;
using System.Buffers;
using System.Runtime.CompilerServices;
namespace Server.Network
namespace Server.Network;
public enum LRReason : byte
{
public enum LRReason : byte
CannotLift,
OutOfRange,
OutOfSight,
TryToSteal,
AreHolding,
Inspecific
}
public static class OutgoingPlayerPackets
{
public const int DragEffectPacketLength = 26;
public static void SendStatLockInfo(this NetState ns, Mobile m)
{
CannotLift,
OutOfRange,
OutOfSight,
TryToSteal,
AreHolding,
Inspecific
if (ns.CannotSendPackets())
{
return;
}
var writer = new SpanWriter(stackalloc byte[12]);
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)12);
writer.Write((short)0x19);
writer.Write((byte)2);
writer.Write(m.Serial);
writer.Write((byte)0);
var lockBits = ((int)m.StrLock << 4) | ((int)m.DexLock << 2) | (int)m.IntLock;
writer.Write((byte)lockBits);
ns.Send(writer.Span);
}
public static class OutgoingPlayerPackets
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendChangeUpdateRange(this NetState ns, byte range) =>
ns?.Send(stackalloc byte[] { 0xC8, range });
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendDeathStatus(this NetState ns, bool dead) =>
ns?.Send(stackalloc byte[] { 0x2C, dead ? (byte)0 : (byte)2 });
public static void SendDisplayProfile(this NetState ns, Serial m, string header, string body, string footer)
{
public const int DragEffectPacketLength = 26;
public static void SendStatLockInfo(this NetState ns, Mobile m)
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[12]);
writer.Write((byte)0xBF); // Packet ID
writer.Write((ushort)12);
writer.Write((short)0x19);
writer.Write((byte)2);
writer.Write(m.Serial);
writer.Write((byte)0);
var lockBits = ((int)m.StrLock << 4) | ((int)m.DexLock << 2) | (int)m.IntLock;
writer.Write((byte)lockBits);
ns.Send(writer.Span);
return;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendChangeUpdateRange(this NetState ns, byte range) =>
ns?.Send(stackalloc byte[] { 0xC8, range });
header ??= "";
body ??= "";
footer ??= "";
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendDeathStatus(this NetState ns, bool dead) =>
ns?.Send(stackalloc byte[] { 0x2C, dead ? (byte)0 : (byte)2 });
var length = 12 + header.Length + footer.Length * 2 + body.Length * 2;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0xB8); // Packet ID
writer.Write((ushort)length);
writer.Write(m);
writer.WriteAsciiNull(header);
writer.WriteBigUniNull(footer);
writer.WriteBigUniNull(body);
public static void SendDisplayProfile(this NetState ns, Serial m, string header, string body, string footer)
ns.Send(writer.Span);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendLiftReject(this NetState ns, LRReason reason) =>
ns?.Send(stackalloc byte[] { 0x27, (byte)reason });
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendLogoutAck(this NetState ns) => ns?.Send(stackalloc byte[] { 0xD1, 0x01 });
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendWeather(this NetState ns, byte type, byte density, byte temp) =>
ns?.Send(stackalloc byte[] { 0x65, type, density, temp });
public static void SendServerChange(this NetState ns, Point3D p, Map map)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
header ??= "";
body ??= "";
footer ??= "";
var length = 12 + header.Length + footer.Length * 2 + body.Length * 2;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0xB8); // Packet ID
writer.Write((ushort)length);
writer.Write(m);
writer.WriteAsciiNull(header);
writer.WriteBigUniNull(footer);
writer.WriteBigUniNull(body);
ns.Send(writer.Span);
return;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendLiftReject(this NetState ns, LRReason reason) =>
ns?.Send(stackalloc byte[] { 0x27, (byte)reason });
var writer = new SpanWriter(stackalloc byte[16]);
writer.Write((byte)0x76); // Packet ID
writer.Write((short)p.X);
writer.Write((short)p.Y);
writer.Write((short)p.Z);
writer.Write((byte)0);
writer.Write((short)0);
writer.Write((short)0);
writer.Write((short)map.Width);
writer.Write((short)map.Height);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendLogoutAck(this NetState ns) => ns?.Send(stackalloc byte[] { 0xD1, 0x01 });
ns.Send(writer.Span);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendWeather(this NetState ns, byte type, byte density, byte temp) =>
ns?.Send(stackalloc byte[] { 0x65, type, density, temp });
public static void SendServerChange(this NetState ns, Point3D p, Map map)
public static void SendSkillsUpdate(this NetState ns, Skills skills)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[16]);
writer.Write((byte)0x76); // Packet ID
writer.Write((short)p.X);
writer.Write((short)p.Y);
writer.Write((short)p.Z);
writer.Write((byte)0);
writer.Write((short)0);
writer.Write((short)0);
writer.Write((short)map.Width);
writer.Write((short)map.Height);
ns.Send(writer.Span);
return;
}
public static void SendSkillsUpdate(this NetState ns, Skills skills)
var length = 6 + 9 * skills.Length;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0x3A); // Packet ID
writer.Write((ushort)length);
writer.Write((byte)0x02); // type: absolute, capped
for (var i = 0; i < skills.Length; ++i)
{
if (ns == null)
{
return;
}
var s = skills[i];
var length = 6 + 9 * skills.Length;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0x3A); // Packet ID
writer.Write((ushort)length);
writer.Write((byte)0x02); // type: absolute, capped
for (var i = 0; i < skills.Length; ++i)
{
var s = skills[i];
var v = s.NonRacialValue;
var uv = Math.Clamp((int)(v * 10), 0, 0xFFFF);
writer.Write((ushort)(s.Info.SkillID + 1));
writer.Write((ushort)uv);
writer.Write((ushort)s.BaseFixedPoint);
writer.Write((byte)s.Lock);
writer.Write((ushort)s.CapFixedPoint);
}
writer.Write((short)0); // terminate
ns.Send(writer.Span);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendSequence(this NetState ns, byte sequence) => ns?.Send(stackalloc byte[] { 0x7B, sequence });
public static void SendSkillChange(this NetState ns, Skill skill)
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[13]);
writer.Write((byte)0x3A); // Packet ID
writer.Write((ushort)13);
var v = skill.NonRacialValue;
var v = s.NonRacialValue;
var uv = Math.Clamp((int)(v * 10), 0, 0xFFFF);
writer.Write((byte)0xDF); // type: delta, capped
writer.Write((ushort)skill.Info.SkillID);
writer.Write((ushort)(s.Info.SkillID + 1));
writer.Write((ushort)uv);
writer.Write((ushort)skill.BaseFixedPoint);
writer.Write((byte)skill.Lock);
writer.Write((ushort)skill.CapFixedPoint);
ns.Send(writer.Span);
writer.Write((ushort)s.BaseFixedPoint);
writer.Write((byte)s.Lock);
writer.Write((ushort)s.CapFixedPoint);
}
public static void SendLaunchBrowser(this NetState ns, string uri)
writer.Write((short)0); // terminate
ns.Send(writer.Span);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendSequence(this NetState ns, byte sequence) => ns?.Send(stackalloc byte[] { 0x7B, sequence });
public static void SendSkillChange(this NetState ns, Skill skill)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
uri ??= "";
var length = 4 + uri.Length;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0xA5); // Packet ID
writer.Write((ushort)length);
writer.WriteAsciiNull(uri);
ns.Send(writer.Span);
return;
}
public static void CreateDragEffect(
Span<byte> buffer,
Serial srcSerial, Point3D srcLocation,
Serial trgSerial, Point3D trgLocation,
int itemID, int hue, int amount
)
var writer = new SpanWriter(stackalloc byte[13]);
writer.Write((byte)0x3A); // Packet ID
writer.Write((ushort)13);
var v = skill.NonRacialValue;
var uv = Math.Clamp((int)(v * 10), 0, 0xFFFF);
writer.Write((byte)0xDF); // type: delta, capped
writer.Write((ushort)skill.Info.SkillID);
writer.Write((ushort)uv);
writer.Write((ushort)skill.BaseFixedPoint);
writer.Write((byte)skill.Lock);
writer.Write((ushort)skill.CapFixedPoint);
ns.Send(writer.Span);
}
public static void SendLaunchBrowser(this NetState ns, string uri)
{
if (ns.CannotSendPackets())
{
if (buffer[0] != 0)
{
return;
}
var writer = new SpanWriter(buffer);
writer.Write((byte)0x23); // Packet ID
writer.Write((short)itemID);
writer.Write((byte)0);
writer.Write((short)hue);
writer.Write((short)amount);
writer.Write(srcSerial);
writer.Write((short)srcLocation.X);
writer.Write((short)srcLocation.Y);
writer.Write((sbyte)srcLocation.Z);
writer.Write(trgSerial);
writer.Write((short)trgLocation.X);
writer.Write((short)trgLocation.Y);
writer.Write((sbyte)trgLocation.Z);
return;
}
public static void SendDragEffect(
this NetState ns,
Serial srcSerial, Point3D srcLocation,
Serial trgSerial, Point3D trgLocation,
int itemID, int hue, int amount
)
uri ??= "";
var length = 4 + uri.Length;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0xA5); // Packet ID
writer.Write((ushort)length);
writer.WriteAsciiNull(uri);
ns.Send(writer.Span);
}
public static void CreateDragEffect(
Span<byte> buffer,
Serial srcSerial, Point3D srcLocation,
Serial trgSerial, Point3D trgLocation,
int itemID, int hue, int amount
)
{
if (buffer[0] != 0)
{
if (ns == null)
{
return;
}
Span<byte> buffer = stackalloc byte[DragEffectPacketLength].InitializePacket();
CreateDragEffect(buffer, srcSerial, srcLocation, trgSerial, trgLocation, itemID, hue, amount);
ns.Send(buffer);
return;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void SendSeasonChange(this NetState ns, byte season, bool playSound) =>
ns?.Send(stackalloc byte[]{ 0xBC, season, *(byte*)&playSound });
var writer = new SpanWriter(buffer);
writer.Write((byte)0x23); // Packet ID
writer.Write((short)itemID);
writer.Write((byte)0);
writer.Write((short)hue);
writer.Write((short)amount);
writer.Write(srcSerial);
writer.Write((short)srcLocation.X);
writer.Write((short)srcLocation.Y);
writer.Write((sbyte)srcLocation.Z);
writer.Write(trgSerial);
writer.Write((short)trgLocation.X);
writer.Write((short)trgLocation.Y);
writer.Write((sbyte)trgLocation.Z);
}
public static void SendDisplayPaperdoll(this NetState ns, Serial m, string title, bool warmode, bool canLift)
public static void SendDragEffect(
this NetState ns,
Serial srcSerial, Point3D srcLocation,
Serial trgSerial, Point3D trgLocation,
int itemID, int hue, int amount
)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
byte flags = 0x00;
if (warmode)
{
flags |= 0x01;
}
if (canLift)
{
flags |= 0x02;
}
var writer = new SpanWriter(stackalloc byte[66]);
writer.Write((byte)0x88); // Packet ID
writer.Write(m);
writer.WriteAscii(title, 60);
writer.Write(flags);
ns.Send(writer.Span);
return;
}
public static void SendPlayMusic(this NetState ns, MusicName music)
Span<byte> buffer = stackalloc byte[DragEffectPacketLength].InitializePacket();
CreateDragEffect(buffer, srcSerial, srcLocation, trgSerial, trgLocation, itemID, hue, amount);
ns.Send(buffer);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void SendSeasonChange(this NetState ns, byte season, bool playSound) =>
ns?.Send(stackalloc byte[]{ 0xBC, season, *(byte*)&playSound });
public static void SendDisplayPaperdoll(this NetState ns, Serial m, string title, bool warmode, bool canLift)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[3]);
writer.Write((byte)0x6D); // Packet ID
writer.Write((short)music);
ns.Send(writer.Span);
return;
}
public static void SendStopMusic(this NetState ns) => ns?.Send(stackalloc byte[] { 0x6D, 0x1F, 0xFF });
byte flags = 0x00;
public static void SendScrollMessage(this NetState ns, int type, int tip, string text)
if (warmode)
{
if (ns == null)
{
return;
}
text ??= "";
var length = 10 + text.Length;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0xA6); // Packet ID
writer.Write((ushort)length);
writer.Write((byte)type);
writer.Write(tip);
writer.Write((ushort)text.Length);
writer.WriteAscii(text);
ns.Send(writer.Span);
flags |= 0x01;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendCurrentTime(this NetState ns) => ns.SendCurrentTime(Core.Now);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendCurrentTime(this NetState ns, DateTime date) =>
ns?.Send(stackalloc byte[] { 0x5B, (byte)date.Hour, (byte)date.Minute, (byte)date.Second });
public static void SendPathfindMessage(this NetState ns, Point3D p)
if (canLift)
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[7]);
writer.Write((byte)0x38); // Packet ID
writer.Write((short)p.X);
writer.Write((short)p.Y);
writer.Write((short)p.Z);
ns.Send(writer.Span);
flags |= 0x02;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendPingAck(this NetState ns, byte ping) => ns?.Send(stackalloc byte[] { 0x73, ping });
var writer = new SpanWriter(stackalloc byte[66]);
writer.Write((byte)0x88); // Packet ID
writer.Write(m);
writer.WriteAscii(title, 60);
writer.Write(flags);
public static void SendDisplayHuePicker(this NetState ns, Serial huePickerSerial, int huePickerItemID)
ns.Send(writer.Span);
}
public static void SendPlayMusic(this NetState ns, MusicName music)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[9]);
writer.Write((byte)0x95); // Packet ID
writer.Write(huePickerSerial);
writer.Write((short)0);
writer.Write((short)huePickerItemID);
ns.Send(writer.Span);
return;
}
var writer = new SpanWriter(stackalloc byte[3]);
writer.Write((byte)0x6D); // Packet ID
writer.Write((short)music);
ns.Send(writer.Span);
}
public static void SendStopMusic(this NetState ns) => ns?.Send(stackalloc byte[] { 0x6D, 0x1F, 0xFF });
public static void SendScrollMessage(this NetState ns, int type, int tip, string text)
{
if (ns.CannotSendPackets())
{
return;
}
text ??= "";
var length = 10 + text.Length;
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0xA6); // Packet ID
writer.Write((ushort)length);
writer.Write((byte)type);
writer.Write(tip);
writer.Write((ushort)text.Length);
writer.WriteAscii(text);
ns.Send(writer.Span);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendCurrentTime(this NetState ns) => ns.SendCurrentTime(Core.Now);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendCurrentTime(this NetState ns, DateTime date) =>
ns?.Send(stackalloc byte[] { 0x5B, (byte)date.Hour, (byte)date.Minute, (byte)date.Second });
public static void SendPathfindMessage(this NetState ns, Point3D p)
{
if (ns.CannotSendPackets())
{
return;
}
var writer = new SpanWriter(stackalloc byte[7]);
writer.Write((byte)0x38); // Packet ID
writer.Write((short)p.X);
writer.Write((short)p.Y);
writer.Write((short)p.Z);
ns.Send(writer.Span);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendPingAck(this NetState ns, byte ping) => ns?.Send(stackalloc byte[] { 0x73, ping });
public static void SendDisplayHuePicker(this NetState ns, Serial huePickerSerial, int huePickerItemID)
{
if (ns.CannotSendPackets())
{
return;
}
var writer = new SpanWriter(stackalloc byte[9]);
writer.Write((byte)0x95); // Packet ID
writer.Write(huePickerSerial);
writer.Write((short)0);
writer.Write((short)huePickerItemID);
ns.Send(writer.Span);
}
}

View file

@ -16,106 +16,105 @@
using System.Buffers;
using Server.Items;
namespace Server.Network
namespace Server.Network;
public enum TradeFlag : byte
{
public enum TradeFlag : byte
Display,
Close,
Update,
UpdateGold,
UpdateLedger
}
public static class OutgoingSecureTradePackets
{
public static void SendDisplaySecureTrade(
this NetState ns, Mobile them, Container first, Container second, string name
)
{
Display,
Close,
Update,
UpdateGold,
UpdateLedger
if (ns.CannotSendPackets())
{
return;
}
var writer = new SpanWriter(stackalloc byte[47]);
writer.Write((byte)0x6F); // Packet ID
writer.Write((ushort)47); // Length
writer.Write((byte)TradeFlag.Display);
writer.Write(them.Serial);
writer.Write(first.Serial);
writer.Write(second.Serial);
writer.Write(true);
writer.WriteAscii(name ?? "", 30);
ns.Send(writer.Span);
}
public static class OutgoingSecureTradePackets
public static void SendCloseSecureTrade(this NetState ns, Container cont)
{
public static void SendDisplaySecureTrade(
this NetState ns, Mobile them, Container first, Container second, string name
)
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[47]);
writer.Write((byte)0x6F); // Packet ID
writer.Write((ushort)47); // Length
writer.Write((byte)TradeFlag.Display);
writer.Write(them.Serial);
writer.Write(first.Serial);
writer.Write(second.Serial);
writer.Write(true);
writer.WriteAscii(name ?? "", 30);
ns.Send(writer.Span);
return;
}
public static void SendCloseSecureTrade(this NetState ns, Container cont)
var writer = new SpanWriter(stackalloc byte[17]);
writer.Write((byte)0x6F); // Packet ID
writer.Write((ushort)17); // Length
writer.Write((byte)TradeFlag.Close);
writer.Write(cont.Serial);
writer.Write(0);
writer.Write(0);
writer.Write(false);
ns.Send(writer.Span);
}
public static void SendUpdateSecureTrade(this NetState ns, Container cont, bool first, bool second) =>
ns.SendUpdateSecureTrade(cont, TradeFlag.Update, first ? 1 : 0, second ? 1 : 0);
public static void SendUpdateSecureTrade(this NetState ns, Container cont, TradeFlag flag, int first, int second)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[17]);
writer.Write((byte)0x6F); // Packet ID
writer.Write((ushort)17); // Length
writer.Write((byte)TradeFlag.Close);
writer.Write(cont.Serial);
writer.Write(0);
writer.Write(0);
writer.Write(false);
ns.Send(writer.Span);
return;
}
public static void SendUpdateSecureTrade(this NetState ns, Container cont, bool first, bool second) =>
ns.SendUpdateSecureTrade(cont, TradeFlag.Update, first ? 1 : 0, second ? 1 : 0);
var writer = new SpanWriter(stackalloc byte[17]);
writer.Write((byte)0x6F); // Packet ID
writer.Write((ushort)17); // Length
writer.Write((byte)flag);
writer.Write(cont.Serial);
writer.Write(first);
writer.Write(second);
writer.Write(false);
public static void SendUpdateSecureTrade(this NetState ns, Container cont, TradeFlag flag, int first, int second)
ns.Send(writer.Span);
}
public static void SendSecureTradeEquip(this NetState ns, Item item, Mobile m)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[17]);
writer.Write((byte)0x6F); // Packet ID
writer.Write((ushort)17); // Length
writer.Write((byte)flag);
writer.Write(cont.Serial);
writer.Write(first);
writer.Write(second);
writer.Write(false);
ns.Send(writer.Span);
return;
}
public static void SendSecureTradeEquip(this NetState ns, Item item, Mobile m)
var writer = new SpanWriter(stackalloc byte[ns.ContainerGridLines ? 21 : 20]);
writer.Write((byte)0x25); // Packet ID
writer.Write(item.Serial);
writer.Write((short)item.ItemID);
writer.Write((byte)0);
writer.Write((short)item.Amount);
writer.Write((short)item.X);
writer.Write((short)item.Y);
if (ns.ContainerGridLines)
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[ns.ContainerGridLines ? 21 : 20]);
writer.Write((byte)0x25); // Packet ID
writer.Write(item.Serial);
writer.Write((short)item.ItemID);
writer.Write((byte)0);
writer.Write((short)item.Amount);
writer.Write((short)item.X);
writer.Write((short)item.Y);
if (ns.ContainerGridLines)
{
writer.Write((byte)0);
}
writer.Write(m.Serial);
writer.Write((short)item.Hue);
ns.Send(writer.Span);
}
writer.Write(m.Serial);
writer.Write((short)item.Hue);
ns.Send(writer.Span);
}
}

View file

@ -16,56 +16,55 @@
using System.Buffers;
using Server.Targeting;
namespace Server.Network
namespace Server.Network;
public static class OutgoingTargetPackets
{
public static class OutgoingTargetPackets
public static void SendMultiTargetReq(this NetState ns, MultiTarget t)
{
public static void SendMultiTargetReq(this NetState ns, MultiTarget t)
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[ns.HighSeas ? 30 : 26]);
writer.Write((byte)0x99); // Packet ID
writer.Write(t.AllowGround);
writer.Write(t.TargetID);
writer.Write((byte)t.Flags);
writer.Clear(11);
writer.Write((short)t.MultiID);
writer.Write((short)t.Offset.X);
writer.Write((short)t.Offset.Y);
writer.Write((short)t.Offset.Z);
if (ns.HighSeas)
{
writer.Write(0);
}
ns.Send(writer.Span);
return;
}
public static void SendCancelTarget(this NetState ns) =>
ns?.Send(stackalloc byte[]
{
0x6C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
});
public static void SendTargetReq(this NetState ns, Target t)
var writer = new SpanWriter(stackalloc byte[ns.HighSeas ? 30 : 26]);
writer.Write((byte)0x99); // Packet ID
writer.Write(t.AllowGround);
writer.Write(t.TargetID);
writer.Write((byte)t.Flags);
writer.Clear(11);
writer.Write((short)t.MultiID);
writer.Write((short)t.Offset.X);
writer.Write((short)t.Offset.Y);
writer.Write((short)t.Offset.Z);
if (ns.HighSeas)
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[19]);
writer.Write((byte)0x6C); // Packet ID
writer.Write(t.AllowGround);
writer.Write(t.TargetID);
writer.Write((byte)t.Flags);
writer.Clear(12);
ns.Send(writer.Span);
writer.Write(0);
}
ns.Send(writer.Span);
}
public static void SendCancelTarget(this NetState ns) =>
ns?.Send(stackalloc byte[]
{
0x6C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
});
public static void SendTargetReq(this NetState ns, Target t)
{
if (ns.CannotSendPackets())
{
return;
}
var writer = new SpanWriter(stackalloc byte[19]);
writer.Write((byte)0x6C); // Packet ID
writer.Write(t.AllowGround);
writer.Write(t.TargetID);
writer.Write((byte)t.Flags);
writer.Clear(12);
ns.Send(writer.Span);
}
}

View file

@ -17,111 +17,110 @@ using System.Buffers;
using System.Collections.Generic;
using Server.Items;
namespace Server.Network
namespace Server.Network;
public static class OutgoingVendorBuyPackets
{
public static class OutgoingVendorBuyPackets
public static void SendVendorBuyContent(this NetState ns, List<BuyItemState> list)
{
public static void SendVendorBuyContent(this NetState ns, List<BuyItemState> list)
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var length = 5 + list.Count * (ns.ContainerGridLines ? 20 : 19);
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0x3C); // Packet ID
writer.Write((ushort)length);
writer.Write((short)list.Count);
for (var i = list.Count - 1; i >= 0; --i)
{
var bis = list[i];
writer.Write(bis.MySerial);
writer.Write((ushort)bis.ItemID);
writer.Write((byte)0); // itemID offset
writer.Write((ushort)bis.Amount);
writer.Write((short)(i + 1)); // x
writer.Write((short)1); // y
if (ns.ContainerGridLines)
{
writer.Write((byte)0); // Grid Location?
}
writer.Write(bis.ContainerSerial);
writer.Write((ushort)bis.Hue);
}
ns.Send(writer.Span);
return;
}
public static void SendDisplayBuyList(this NetState ns, Serial vendor)
var length = 5 + list.Count * (ns.ContainerGridLines ? 20 : 19);
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0x3C); // Packet ID
writer.Write((ushort)length);
writer.Write((short)list.Count);
for (var i = list.Count - 1; i >= 0; --i)
{
if (ns == null)
{
return;
}
var bis = list[i];
var writer = new SpanWriter(stackalloc byte[ns.HighSeas ? 9 : 7]);
writer.Write((byte)0x24); // Packet ID
writer.Write(vendor);
writer.Write((short)0x30); // Vendor Buy Window
if (ns.HighSeas)
writer.Write(bis.MySerial);
writer.Write((ushort)bis.ItemID);
writer.Write((byte)0); // itemID offset
writer.Write((ushort)bis.Amount);
writer.Write((short)(i + 1)); // x
writer.Write((short)1); // y
if (ns.ContainerGridLines)
{
writer.Write((short)0x0);
writer.Write((byte)0); // Grid Location?
}
ns.Send(writer.Span);
writer.Write(bis.ContainerSerial);
writer.Write((ushort)bis.Hue);
}
public static void SendVendorBuyList(this NetState ns, Mobile vendor, List<BuyItemState> list)
ns.Send(writer.Span);
}
public static void SendDisplayBuyList(this NetState ns, Serial vendor)
{
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var length = 8;
for (int i = 0; i < list.Count; i++)
{
length += 6 + list[i].Description?.Length ?? 0;
}
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0x74); // Packet ID
writer.Write((ushort)length);
writer.Write((vendor.FindItemOnLayer(Layer.ShopBuy) as Container)?.Serial ?? Serial.MinusOne);
writer.Write((byte)list.Count);
for (var i = 0; i < list.Count; ++i)
{
var bis = list[i];
writer.Write(bis.Price);
var desc = bis.Description ?? "";
writer.Write((byte)(desc.Length + 1));
writer.WriteAsciiNull(desc);
}
ns.Send(writer.Span);
return;
}
public static void SendEndVendorBuy(this NetState ns, Serial vendor)
var writer = new SpanWriter(stackalloc byte[ns.HighSeas ? 9 : 7]);
writer.Write((byte)0x24); // Packet ID
writer.Write(vendor);
writer.Write((short)0x30); // Vendor Buy Window
if (ns.HighSeas)
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[8]);
writer.Write((byte)0x3B); // Packet ID
writer.Write((ushort)8);
writer.Write(vendor);
writer.Write((byte)0); // Buy count
ns.Send(writer.Span);
writer.Write((short)0x0);
}
ns.Send(writer.Span);
}
public static void SendVendorBuyList(this NetState ns, Mobile vendor, List<BuyItemState> list)
{
if (ns.CannotSendPackets())
{
return;
}
var length = 8;
for (int i = 0; i < list.Count; i++)
{
length += 6 + list[i].Description?.Length ?? 0;
}
var writer = new SpanWriter(stackalloc byte[length]);
writer.Write((byte)0x74); // Packet ID
writer.Write((ushort)length);
writer.Write((vendor.FindItemOnLayer(Layer.ShopBuy) as Container)?.Serial ?? Serial.MinusOne);
writer.Write((byte)list.Count);
for (var i = 0; i < list.Count; ++i)
{
var bis = list[i];
writer.Write(bis.Price);
var desc = bis.Description ?? "";
writer.Write((byte)(desc.Length + 1));
writer.WriteAsciiNull(desc);
}
ns.Send(writer.Span);
}
public static void SendEndVendorBuy(this NetState ns, Serial vendor)
{
if (ns.CannotSendPackets())
{
return;
}
var writer = new SpanWriter(stackalloc byte[8]);
writer.Write((byte)0x3B); // Packet ID
writer.Write((ushort)8);
writer.Write(vendor);
writer.Write((byte)0); // Buy count
ns.Send(writer.Span);
}
}

View file

@ -18,66 +18,65 @@ using System.Buffers;
using System.Collections.Generic;
using System.IO;
namespace Server.Network
namespace Server.Network;
public static class OutgoingVendorSellPackets
{
public static class OutgoingVendorSellPackets
public static void SendVendorSellList(this NetState ns, Serial vendor, List<SellItemState> list)
{
public static void SendVendorSellList(this NetState ns, Serial vendor, List<SellItemState> list)
if (ns.CannotSendPackets())
{
if (ns == null)
{
return;
}
var maxLength = 9;
for (int i = 0; i < list.Count; i++)
{
var sis = list[i];
var item = sis.Item;
maxLength += 14 + Math.Max(item.Name?.Length ?? 0, sis.Name?.Length ?? 0);
}
var writer = new SpanWriter(stackalloc byte[maxLength]);
writer.Write((byte)0x9E); // Packet ID
writer.Seek(2, SeekOrigin.Current);
writer.Write(vendor);
writer.Write((ushort)list.Count);
for (var i = 0; i < list.Count; i++)
{
var sis = list[i];
var item = sis.Item;
writer.Write(item.Serial);
writer.Write((ushort)item.ItemID);
writer.Write((ushort)item.Hue);
writer.Write((ushort)item.Amount);
writer.Write((ushort)sis.Price);
var name = (item.Name?.Trim()).DefaultIfNullOrEmpty(sis.Name ?? "");
writer.Write((ushort)name.Length);
writer.WriteAscii(name);
}
writer.WritePacketLength();
ns.Send(writer.Span);
return;
}
public static void SendEndVendorSell(this NetState ns, Serial vendor)
var maxLength = 9;
for (int i = 0; i < list.Count; i++)
{
if (ns == null)
{
return;
}
var writer = new SpanWriter(stackalloc byte[8]);
writer.Write((byte)0x3B); // Packet ID
writer.Write((ushort)8);
writer.Write(vendor);
writer.Write((byte)0);
ns.Send(writer.Span);
var sis = list[i];
var item = sis.Item;
maxLength += 14 + Math.Max(item.Name?.Length ?? 0, sis.Name?.Length ?? 0);
}
var writer = new SpanWriter(stackalloc byte[maxLength]);
writer.Write((byte)0x9E); // Packet ID
writer.Seek(2, SeekOrigin.Current);
writer.Write(vendor);
writer.Write((ushort)list.Count);
for (var i = 0; i < list.Count; i++)
{
var sis = list[i];
var item = sis.Item;
writer.Write(item.Serial);
writer.Write((ushort)item.ItemID);
writer.Write((ushort)item.Hue);
writer.Write((ushort)item.Amount);
writer.Write((ushort)sis.Price);
var name = (item.Name?.Trim()).DefaultIfNullOrEmpty(sis.Name ?? "");
writer.Write((ushort)name.Length);
writer.WriteAscii(name);
}
writer.WritePacketLength();
ns.Send(writer.Span);
}
public static void SendEndVendorSell(this NetState ns, Serial vendor)
{
if (ns.CannotSendPackets())
{
return;
}
var writer = new SpanWriter(stackalloc byte[8]);
writer.Write((byte)0x3B); // Packet ID
writer.Write((ushort)8);
writer.Write(vendor);
writer.Write((byte)0);
ns.Send(writer.Span);
}
}

View file

@ -18,98 +18,97 @@ using System.Buffers;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
namespace Server.Network
namespace Server.Network;
public ref struct PacketContainerBuilder
{
public ref struct PacketContainerBuilder
public const int MinPacketLength = 5;
private bool _finished;
private int _count;
private byte[] _arrayToReturnToPool;
private Span<byte> _bytes;
public PacketContainerBuilder(Span<byte> initialBuffer)
{
public const int MinPacketLength = 5;
_arrayToReturnToPool = null;
_finished = false;
_count = 0;
private bool _finished;
private int _count;
_bytes = initialBuffer;
_bytes[0] = 0xF7; // Packet ID
Length = MinPacketLength; // Length + Count
}
private byte[] _arrayToReturnToPool;
private Span<byte> _bytes;
public int Length { get; set; }
public PacketContainerBuilder(Span<byte> initialBuffer)
public int Capacity => _bytes.Length;
[MethodImpl(MethodImplOptions.NoInlining)]
public ReadOnlySpan<byte> Finalize()
{
if (!_finished)
{
_arrayToReturnToPool = null;
_finished = false;
_count = 0;
_bytes = initialBuffer;
_bytes[0] = 0xF7; // Packet ID
Length = MinPacketLength; // Length + Count
BinaryPrimitives.WriteUInt16BigEndian(_bytes[1..3], (ushort)Length);
BinaryPrimitives.WriteUInt16BigEndian(_bytes[3..5], (ushort)_count);
}
public int Length { get; set; }
return _bytes[..Length];
}
public int Capacity => _bytes.Length;
[MethodImpl(MethodImplOptions.NoInlining)]
public ReadOnlySpan<byte> Finalize()
[MethodImpl(MethodImplOptions.NoInlining)]
public Span<byte> GetSpan(int bytesNeeded)
{
if (_finished)
{
if (!_finished)
{
BinaryPrimitives.WriteUInt16BigEndian(_bytes[1..3], (ushort)Length);
BinaryPrimitives.WriteUInt16BigEndian(_bytes[3..5], (ushort)_count);
}
return _bytes[..Length];
throw new InvalidOperationException("Attempted to use PacketContainerBuilder after finalize");
}
[MethodImpl(MethodImplOptions.NoInlining)]
public Span<byte> GetSpan(int bytesNeeded)
if (Length > _bytes.Length - bytesNeeded)
{
if (_finished)
{
throw new InvalidOperationException("Attempted to use PacketContainerBuilder after finalize");
}
if (Length > _bytes.Length - bytesNeeded)
{
Grow(bytesNeeded);
}
return _bytes[Length..];
Grow(bytesNeeded);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public void Advance(int bytesWritten)
{
if (_finished)
{
throw new InvalidOperationException("Attempted to use PacketContainerBuilder after finalize");
}
return _bytes[Length..];
}
_count++;
Length += bytesWritten;
[MethodImpl(MethodImplOptions.NoInlining)]
public void Advance(int bytesWritten)
{
if (_finished)
{
throw new InvalidOperationException("Attempted to use PacketContainerBuilder after finalize");
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void Grow(int additionalCapacityBeyondPos)
_count++;
Length += bytesWritten;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void Grow(int additionalCapacityBeyondPos)
{
var newLength = Math.Max(Length + additionalCapacityBeyondPos, _bytes.Length * 2);
byte[] poolArray = ArrayPool<byte>.Shared.Rent(newLength);
_bytes[..Length].CopyTo(poolArray);
byte[] toReturn = _arrayToReturnToPool;
_bytes = _arrayToReturnToPool = poolArray;
if (toReturn != null)
{
var newLength = Math.Max(Length + additionalCapacityBeyondPos, _bytes.Length * 2);
byte[] poolArray = ArrayPool<byte>.Shared.Rent(newLength);
_bytes[..Length].CopyTo(poolArray);
byte[] toReturn = _arrayToReturnToPool;
_bytes = _arrayToReturnToPool = poolArray;
if (toReturn != null)
{
ArrayPool<byte>.Shared.Return(toReturn);
}
ArrayPool<byte>.Shared.Return(toReturn);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
byte[] toReturn = _arrayToReturnToPool;
this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
if (toReturn != null)
{
byte[] toReturn = _arrayToReturnToPool;
this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
if (toReturn != null)
{
ArrayPool<byte>.Shared.Return(toReturn);
}
ArrayPool<byte>.Shared.Return(toReturn);
}
}
}

View file

@ -103,7 +103,7 @@ namespace Server.Engines.Chat
public static void SendChatMessage(this NetState ns, string lang, int number, string param1, string param2)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -464,12 +464,7 @@ namespace Server.Engines.Harvest
var ns = from.NetState;
if (ns == null)
{
return;
}
if (number == 1043297 || ns.HighSeas)
if (number == 1043297 || ns?.HighSeas == true)
{
from.SendLocalizedMessage(number, name);
}

View file

@ -35,7 +35,7 @@ namespace Server.Engines.Help
public static void SendDisplayHelpTopic(this NetState ns, int topicID, bool display = true)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -48,7 +48,7 @@ namespace Server.Engines.PartySystem
public static void SendPartyMemberList(this NetState ns, Party p)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -64,7 +64,7 @@ namespace Server.Engines.PartySystem
public static void SendPartyRemoveMember(this NetState ns, Serial m, Party p = null)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -104,7 +104,7 @@ namespace Server.Engines.PartySystem
public static void SendPartyTextMessage(this NetState ns, Serial m, string text, bool toAll)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -134,7 +134,7 @@ namespace Server.Engines.PartySystem
public static void SendPartyInvitation(this NetState ns, Serial leader)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -47,7 +47,7 @@ namespace Server.Engines.VeteranRewards
public static void SendStatueAnimation(this NetState ns, Serial serial, int status, int anim, int frame)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -135,7 +135,7 @@ namespace Server.Items
public static void SendBookContent(this NetState ns, BaseBook book)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -183,7 +183,7 @@ namespace Server.Items
public static void SendBookCover(this NetState ns, Mobile from, BaseBook book)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -168,7 +168,7 @@ namespace Server.Network
public static void SendBBDisplayBoard(this NetState ns, BaseBulletinBoard board)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -203,7 +203,7 @@ namespace Server.Network
public static void SendBBMessage(this NetState ns, BaseBulletinBoard board, BulletinMessage msg, bool content = false)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -287,7 +287,7 @@ namespace Server.Engines.Mahjong
public static void SendMahjongJoinGame(this NetState ns, Serial game)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -303,7 +303,7 @@ namespace Server.Engines.Mahjong
public static void SendMahjongPlayersInfo(this NetState ns, MahjongGame game, Mobile to)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -361,7 +361,7 @@ namespace Server.Engines.Mahjong
public static void SendMahjongTileInfo(this NetState ns, MahjongTile tile, Mobile to)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -407,7 +407,7 @@ namespace Server.Engines.Mahjong
public static void SendMahjongTilesInfo(this NetState ns, MahjongGame game, Mobile to)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -460,7 +460,7 @@ namespace Server.Engines.Mahjong
public static void SendMahjongGeneralInfo(this NetState ns, MahjongGame game)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -503,7 +503,7 @@ namespace Server.Engines.Mahjong
public static void SendMahjongRelieve(this NetState ns, Serial game)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -65,7 +65,7 @@ namespace Server.Network
public static void SendMapDetails(this NetState ns, MapItem map)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -93,7 +93,7 @@ namespace Server.Network
public static void SendMapCommand(this NetState ns, MapItem map, int command, int x = 0, int y = 0, bool editable = false)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -23,7 +23,7 @@ namespace Server.Network
{
public static void SendCorpseEquip(this NetState ns, Mobile beholder, Corpse beheld)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -67,7 +67,7 @@ namespace Server.Network
public static void SendCorpseContent(this NetState ns, Mobile beholder, Corpse beheld)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -671,7 +671,7 @@ namespace Server.Items
// The client must know about the spellbook or it will crash!
var ns = to.NetState;
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -32,7 +32,7 @@ namespace Server.Items
public static void SendToggleSpecialAbility(this NetState ns, int abilityId, bool active)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -162,7 +162,7 @@ namespace Server
long ticks
)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -207,7 +207,7 @@ namespace Server
public static void SendRemoveBuffPacket(NetState ns, Serial mob, BuffIcon iconID)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -15,19 +15,21 @@ namespace Server
return true;
}
var lowerName = name.ToLowerInvariant().Replace(" ", "");
var lowerName = name?.ToLowerInvariant().Replace(" ", "");
foreach (var so in SkillInfo.Table)
if (!string.IsNullOrEmpty(lowerName))
{
if (lowerName == so.ProfessionSkillName.ToLowerInvariant())
foreach (var so in SkillInfo.Table)
{
skillName = (SkillName)so.SkillID;
return true;
if (lowerName == so.ProfessionSkillName.ToLowerInvariant())
{
skillName = (SkillName)so.SkillID;
return true;
}
}
}
return false;
}
static ProfessionInfo()

View file

@ -969,7 +969,7 @@ namespace Server.Mobiles
var ns = from.NetState;
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -29,7 +29,7 @@ namespace Server.Multis
public static void SendBeginHouseCustomization(this NetState ns, Serial house)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -50,7 +50,7 @@ namespace Server.Multis
public static void SendEndHouseCustomization(this NetState ns, Serial house)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -71,7 +71,7 @@ namespace Server.Multis
public static void SendDesignStateGeneral(this NetState ns, Serial house, int revision)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -102,7 +102,7 @@ namespace Server.Network
public static void SendServerPollInfo(this NetState ns)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -56,7 +56,7 @@ namespace Server.Network
public static void SendGuildMemberLocations(this NetState ns, Mobile from, Guild guild, bool sendLocations)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -116,7 +116,7 @@ namespace Server.Network
public static void SendPartyMemberLocations(this NetState ns, Mobile from, Party party)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -61,7 +61,7 @@ namespace Server.Network
this NetState ns, uint age, int clients, int items, int mobiles, long mem
)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}
@ -82,7 +82,7 @@ namespace Server.Network
this NetState ns, string name, int age, int clients, int items, int mobiles, int mem
)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}

View file

@ -28,7 +28,7 @@ namespace Server.Network
public static void SendArrow(this NetState ns, byte command, int x, int y, Serial s)
{
if (ns == null)
if (ns.CannotSendPackets())
{
return;
}