Files
ClassicAssist.Avalonia/ClassicAssist.Shared/UO/Network/IncomingPacketHandlers.cs
T
Beyhan Oğur d5f52cc3d3 first commit
2026-09-03 04:45:36 +03:00

1762 lines
58 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ClassicAssist.Shared;
using ClassicAssist.Data;
using ClassicAssist.Data.Abilities;
using ClassicAssist.Data.Chat;
using ClassicAssist.Data.Counters;
using ClassicAssist.Data.Macros.Commands;
using ClassicAssist.Data.Skills;
using ClassicAssist.Data.Vendors;
using ClassicAssist.Shared.Resources;
using ClassicAssist.Shared.UO;
using ClassicAssist.UO.Data;
using ClassicAssist.UO.Network.Packets;
using ClassicAssist.UO.Objects;
using ClassicAssist.UO.Objects.Gumps;
using Sentry;
namespace ClassicAssist.UO.Network
{
public static class IncomingPacketHandlers
{
public delegate void dBufficonEnabledDisabled( int type, bool enabled, int duration );
public delegate void dContainerContents( int serial, ItemCollection container );
public delegate void dCorpseContainerDisplay( int serial );
public delegate void dGump( uint gumpId, int serial, Gump gump );
public delegate void dJournalEntryAdded( JournalEntry je );
public delegate void dMapChanged( Map newMap );
public delegate void dMenuAdded( int gumpId, Menu menu );
public delegate void dMobileIncoming( Mobile mobile, ItemCollection equipment );
public delegate void dMobileUpdated( Mobile mobile );
public delegate void dNewWorldItem( Item item );
public delegate void dSkillList( SkillInfo[] skills );
public delegate void dSkillUpdated( int id, float value, float baseValue, LockStatus lockStatus,
float skillCap );
public delegate void dToggleSpecialMove( int spellID, bool enabled );
public delegate void dVendorBuyDisplay( int serial, ShopListEntry[] entries );
public delegate void dVendorSellDisplay( int serial, SellListEntry[] entries );
private const int HUE_RED = 36;
private static PacketHandler[] _handlers;
private static PacketHandler[] _extendedHandlers;
public static ConcurrentDictionary<int, Property[]> PropertyCache { get; set; } =
new ConcurrentDictionary<int, Property[]>();
public static event dVendorSellDisplay VendorSellDisplayEvent;
public static event dJournalEntryAdded JournalEntryAddedEvent;
public static event dSkillUpdated SkillUpdatedEvent;
public static event dSkillList SkillsListEvent;
public static event dMobileUpdated MobileUpdatedEvent;
public static event dMobileIncoming MobileIncomingEvent;
public static event dNewWorldItem NewWorldItemEvent;
public static event dContainerContents ContainerContentsEvent;
public static event dGump GumpEvent;
public static event dBufficonEnabledDisabled BufficonEnabledDisabledEvent;
public static event dCorpseContainerDisplay CorpseContainerDisplayEvent;
public static event dMenuAdded MenuAddedEvent;
public static void Initialize()
{
_handlers = new PacketHandler[0x100];
_extendedHandlers = new PacketHandler[0x10000]; // Sphere id > 255
Register( 0x11, 0, OnMobileStatus );
Register( 0x1A, 0, OnWorldItem );
Register( 0x17, 0, OnHealthbarColour );
Register( 0x1B, 37, OnInitializePlayer );
Register( 0x1C, 0, OnASCIIText );
Register( 0x1D, 5, OnItemDeleted );
Register( 0x20, 19, OnMobileUpdated );
Register( 0x21, 8, OnMoveRejected );
Register( 0x22, 3, OnMoveAccepted );
Register( 0x24, 9, OnContainerDisplay );
Register( 0x25, 21, OnItemAddedToContainer );
Register( 0x27, 2, OnResetHolding );
Register( 0x28, 5, OnResetHolding );
Register( 0x29, 1, OnResetHolding );
Register( 0x2E, 15, OnItemEquipped );
Register( 0x31, 0, OnUO3DPetWindow );
Register( 0x3A, 0, OnSkillsList );
Register( 0x3C, 0, OnContainerContents );
Register( 0x6C, 19, OnTarget );
Register( 0x74, 0, OnShopList );
Register( 0x77, 17, OnMobileMoving );
Register( 0x7C, 0, OnDisplayItemListMenu );
Register( 0x78, 0, OnMobileIncoming );
Register( 0x98, 0, OnMobileName );
Register( 0x99, 30, OnTarget );
Register( 0x9E, 0, OnShopSell );
Register( 0xA1, 9, OnMobileHits );
Register( 0xA2, 9, OnMobileMana );
Register( 0xA3, 9, OnMobileStamina );
Register( 0xA8, 0, OnShardList );
Register( 0xAE, 0, OnUnicodeText );
Register( 0xB2, 0, OnChatMessage );
Register( 0xB9, 5, OnSupportedFeatures );
Register( 0xBF, 0, OnExtendedCommand );
Register( 0xC1, 0, OnLocalizedText );
Register( 0xC2, 0, OnUnicodePrompt );
Register( 0xCC, 0, OnLocalizedTextAffix );
Register( 0xD6, 0, OnProperties );
Register( 0xDD, 0, OnCompressedGump );
Register( 0xDF, 0, OnBuffAndAttributes );
Register( 0xE5, 0, OnDisplayWaypoint );
Register( 0xE6, 0, OnHideWaypoint );
Register( 0xF3, 26, OnSAWorldItem );
RegisterExtended( 0x04, 0, OnCloseGump );
RegisterExtended( 0x06, 0, OnPartyCommand );
RegisterExtended( 0x08, 0, OnMapChange );
RegisterExtended( 0x10, 0, OnDisplayEquipmentInfo );
RegisterExtended( 0x21, 0, OnClearWeaponAbility );
RegisterExtended( 0x25, 0, OnToggleSpecialMoves );
}
private static void OnUO3DPetWindow( PacketReader reader )
{
int serial = reader.ReadInt32();
int count = reader.ReadByte();
List<int> pets = new List<int>();
for ( int i = 0; i < count; i++ )
{
int petSerial = reader.ReadInt32();
reader.ReadByte();
pets.Add( petSerial );
}
Mobile mobile = Engine.Mobiles.GetMobile( serial );
if ( mobile != null )
{
mobile.Pets = pets.ToArray();
}
}
private static void OnHideWaypoint( PacketReader reader )
{
int serial = reader.ReadInt32();
QuestPointer pointer = Engine.QuestPointers.FirstOrDefault( p => p.Serial == serial );
if ( pointer != null )
{
Engine.QuestPointers.Remove( pointer );
}
}
private static void OnDisplayWaypoint( PacketReader reader )
{
if ( !Options.CurrentOptions.ShowResurrectionWaypoints )
{
return;
}
int serial = reader.ReadInt32();
int x = reader.ReadInt16();
int y = reader.ReadInt16();
int z = reader.ReadSByte();
Map map = (Map) reader.ReadByte();
int type = reader.ReadInt16();
int ignoreObjectSerial = reader.ReadInt16();
int clilocDescription = reader.ReadInt32();
string clilocArguments = reader.ReadUnicodeStringLE();
string cliloc = Cliloc.GetLocalString( clilocDescription, new[] { clilocArguments } );
switch ( type )
{
case 0x01:
{
Engine.QuestPointers.Add( new QuestPointer
{
Serial = serial, Type = QuestPointerType.Corpse, X = x, Y = y
} );
break;
}
case 0x06:
{
if ( Engine.Player != null )
{
int distance = Math.Max( Math.Abs( x - Engine.Player.X ), Math.Abs( y - Engine.Player.Y ) );
Direction direction = UOMath.MapDirection( Engine.Player.X, Engine.Player.Y, x, y );
if ( distance < 250 )
{
Commands.SystemMessage( string.Format(
Strings.Resurrection_Point___0____Direction___1____Distance___2_, cliloc,
direction.ToString(), distance ) );
}
}
Engine.QuestPointers.Add( new QuestPointer
{
Serial = serial, Type = QuestPointerType.Resurrection, X = x, Y = y
} );
break;
}
}
}
private static void OnDisplayItemListMenu( PacketReader reader )
{
int serial = reader.ReadInt32();
int gumpId = reader.ReadInt16();
string title = reader.ReadString( reader.ReadByte() );
int numLines = reader.ReadByte();
List<MenuEntry> entries = new List<MenuEntry>();
for ( int i = 0; i < numLines; i++ )
{
int id = reader.ReadInt16();
int hue = reader.ReadInt16();
string line = reader.ReadString( reader.ReadByte() );
entries.Add( new MenuEntry { Index = i + 1, ID = id, Hue = hue, Title = line } );
}
Menu menu = new Menu { Serial = serial, ID = gumpId, Entries = entries.ToArray(), Title = title };
Engine.Menus.Add( menu );
MenuAddedEvent?.Invoke( gumpId, menu );
}
private static void OnWorldItem( PacketReader reader )
{
byte[] packet = reader.GetData();
Item item;
uint serial = (uint) ( ( packet[3] << 24 ) | ( packet[4] << 16 ) | ( packet[5] << 8 ) | packet[6] );
int offset = 9;
if ( ( serial & 0x80000000 ) != 0 )
{
serial ^= 0x80000000;
item = Engine.GetOrCreateItem( (int) serial );
item.Count = ( packet[offset] << 8 ) | packet[offset + 1];
offset += 2;
}
else
{
item = Engine.GetOrCreateItem( (int) serial );
}
int id = ( packet[7] << 8 ) | packet[8];
if ( ( id & 0x8000 ) != 0 )
{
id ^= 0x8000;
id += packet[offset]; // stack id
offset++;
}
item.ID = id;
int x = ( packet[offset] << 8 ) | packet[offset + 1];
int y = ( packet[offset + 2] << 8 ) | packet[offset + 3];
offset += 4;
if ( ( x & 0x8000 ) != 0 )
{
x ^= 0x8000;
item.Direction = (Direction) packet[offset];
offset++;
}
item.X = x;
item.Z = (sbyte) packet[offset];
offset++;
if ( ( y & 0x8000 ) != 0 )
{
y ^= 0x8000;
item.Hue = ( packet[offset] << 8 ) | packet[offset + 1];
offset += 2;
}
if ( ( y & 0x4000 ) != 0 )
{
y ^= 0x4000;
item.Flags = packet[offset]; // ???
}
item.Y = y;
item.Owner = 0;
Engine.Items.Add( item );
NewWorldItemEvent?.Invoke( item );
}
private static void OnResetHolding( PacketReader reader )
{
Engine.Player.Holding = 0;
Engine.Player.HoldingAmount = 0;
CountersManager.GetInstance().RecountAll?.Invoke();
}
private static void OnChatMessage( PacketReader reader )
{
ChatManager.GetInstance().OnChatPacket( reader );
}
private static void OnShardList( PacketReader reader )
{
reader.ReadByte();
int count = reader.ReadInt16();
List<ShardEntry> shards = new List<ShardEntry>();
for ( int i = 0; i < count; i++ )
{
int index = reader.ReadInt16();
string name = reader.ReadStringSafe( 32 );
int full = reader.ReadByte();
int timezone = reader.ReadSByte();
int ip = reader.ReadInt32();
shards.Add( new ShardEntry
{
Index = index,
IP = ip,
Name = name,
PercentFull = full,
Timezone = timezone
} );
}
Engine.Shards = shards;
}
private static void OnDisplayEquipmentInfo( PacketReader reader )
{
int serial = reader.ReadInt32();
int cliloc = reader.ReadInt32();
uint next = reader.ReadUInt32();
List<Property> properties =
new List<Property> { new Property { Cliloc = cliloc, Text = Cliloc.GetProperty( cliloc ) } };
string journalOutput = Cliloc.GetProperty( cliloc );
do
{
string attrName;
int charges;
uint number;
switch ( next )
{
case 0xFFFFFFFF:
break;
case 0xFFFFFFFD:
{
int nameLen = reader.ReadUInt16();
string craftedBy = reader.ReadString( nameLen );
properties.Add( new Property { Cliloc = -3, Text = craftedBy } );
journalOutput += $" {craftedBy}";
break;
}
case 0xFFFFFFFC:
{
number = reader.ReadUInt32();
charges = reader.ReadUInt16();
attrName = Cliloc.GetProperty( (int) number );
properties.Add( new Property
{
Cliloc = (int) number, Text = attrName, Arguments = new[] { charges.ToString() }
} );
journalOutput += $" {attrName}";
break;
}
default:
{
number = next;
charges = reader.ReadUInt16();
attrName = Cliloc.GetProperty( (int) number );
properties.Add( new Property
{
Cliloc = (int) number, Text = attrName, Arguments = new[] { charges.ToString() }
} );
journalOutput += $" {attrName}";
break;
}
}
if ( next == 0xFFFFFFFF )
{
break;
}
next = reader.ReadUInt32();
}
while ( next != 0xFFFFFFFF );
Engine.Journal.Write( new JournalEntry
{
Serial = -1, SpeechType = JournalSpeech.Label, Name = "System", Text = journalOutput
} );
Item item = Engine.Items.GetItem( serial );
if ( item != null )
{
item.Properties = properties.ToArray();
}
}
private static void OnUnicodePrompt( PacketReader reader )
{
int senderSerial = reader.ReadInt32();
int promptId = reader.ReadInt32();
Engine.LastPromptSerial = senderSerial;
Engine.LastPromptID = promptId;
}
private static void OnShopSell( PacketReader reader )
{
int vendorSerial = reader.ReadInt32();
int itemCount = reader.ReadInt16();
List<SellListEntry> shopList = new List<SellListEntry>();
for ( int i = 0; i < itemCount; i++ )
{
int serial = reader.ReadInt32();
int id = reader.ReadInt16();
int hue = reader.ReadInt16();
int amount = reader.ReadInt16();
int price = reader.ReadInt16();
string name = reader.ReadString( reader.ReadInt16() );
shopList.Add( new SellListEntry
{
Serial = serial,
ID = id,
Hue = hue,
Amount = amount,
Price = price,
Name = name
} );
}
if ( shopList.Count > 0 )
{
VendorSellDisplayEvent?.Invoke( vendorSerial, shopList.ToArray() );
}
}
public static event dVendorBuyDisplay VendorBuyDisplayEvent;
private static void OnContainerDisplay( PacketReader reader )
{
int serial = reader.ReadInt32();
int gumpId = reader.ReadUInt16();
int ctype = reader.ReadUInt16();
if ( ctype != 0 && gumpId == 0x09 )
{
Task.Run( () => CorpseContainerDisplayEvent?.Invoke( serial ) );
}
if ( ctype != 0 )
{
return;
}
Mobile entity = Engine.Mobiles.GetMobile( serial );
if ( entity?.ShopBuy != null )
{
VendorBuyDisplayEvent?.Invoke( serial, entity.ShopBuy );
}
}
private static void OnClearWeaponAbility( PacketReader reader )
{
AbilitiesManager manager = AbilitiesManager.GetInstance();
if ( manager.Enabled != AbilityType.None )
{
Commands.SystemMessage( Strings.Current_Ability_Cleared, HUE_RED );
}
manager.Enabled = AbilityType.None;
manager.ResendGump( AbilityType.None );
}
private static void OnShopList( PacketReader reader )
{
int serial = reader.ReadInt32();
int count = reader.ReadByte();
List<ShopListEntry> shopList = new List<ShopListEntry>();
for ( int i = 0; i < count; i++ )
{
int price = reader.ReadInt32();
string name = reader.ReadString( reader.ReadByte() );
shopList.Add( new ShopListEntry { Name = name, Price = price, VendorSerial = serial } );
}
Mobile mobile = Engine.Mobiles.SelectEntity( m => m.Equipment.GetItems().Any( i => i.Serial == serial ) );
if ( mobile == null )
{
return;
}
Item containerItem = Engine.Items.GetItem( serial );
if ( containerItem?.Container == null )
{
return;
}
List<Item> containerItems = new List<Item>( containerItem.Container.GetItems() );
containerItems.Sort( new XYComparer() );
for ( int i = 0; i < shopList.Count; i++ )
{
shopList[i].VendorSerial = mobile.Serial;
if ( shopList[i] != null )
{
shopList[i].Item = containerItems[i];
shopList[i].Amount = containerItems[i].Count;
}
}
mobile.ShopBuy = shopList.ToArray();
}
public static event dToggleSpecialMove ToggleSpecialMoveEvent;
private static void OnToggleSpecialMoves( PacketReader reader )
{
int spellID = reader.ReadInt16();
bool enabled = reader.ReadByte() == 1;
ToggleSpecialMoveEvent?.Invoke( spellID, enabled );
}
private static void OnBuffAndAttributes( PacketReader reader )
{
reader.ReadInt32(); // serial
int type = reader.ReadUInt16();
int count = reader.ReadInt16();
if ( type < 0x3e9 )
{
return;
}
bool enabled = count > 0;
int duration = 0;
if ( count == 1 )
{
reader.Seek( 12, SeekOrigin.Current );
duration = reader.ReadInt16();
}
BufficonEnabledDisabledEvent?.Invoke( type, enabled, duration );
}
private static void OnLocalizedText( PacketReader reader )
{
JournalEntry journalEntry = new JournalEntry
{
Serial = reader.ReadInt32(),
ID = reader.ReadInt16(),
SpeechType = (JournalSpeech) reader.ReadByte(),
SpeechHue = reader.ReadInt16(),
SpeechFont = reader.ReadInt16(),
Cliloc = reader.ReadInt32(),
Name = reader.ReadString( 30 ),
Arguments = reader.ReadUnicodeString( (int) reader.Size - 50 )
.Split( new[] { '\t' }, StringSplitOptions.RemoveEmptyEntries )
};
journalEntry.Text = Cliloc.GetLocalString( journalEntry.Cliloc, journalEntry.Arguments );
if ( journalEntry.SpeechType == JournalSpeech.Label )
{
if ( Engine.Player?.LastTargetSerial == journalEntry.Serial )
{
MsgCommands.HeadMsg( Options.CurrentOptions.LastTargetMessage, journalEntry.Serial );
}
if ( Engine.Player?.EnemyTargetSerial == journalEntry.Serial )
{
MsgCommands.HeadMsg( Options.CurrentOptions.EnemyTargetMessage, journalEntry.Serial );
}
if ( Engine.Player?.FriendTargetSerial == journalEntry.Serial )
{
MsgCommands.HeadMsg( Options.CurrentOptions.FriendTargetMessage, journalEntry.Serial );
}
}
AddToJournal( journalEntry );
}
private static void OnLocalizedTextAffix( PacketReader reader )
{
int serial = reader.ReadInt32();
int graphic = reader.ReadInt16();
JournalSpeech messageType = (JournalSpeech) reader.ReadByte();
int hue = reader.ReadInt16();
int font = reader.ReadInt16();
int cliloc = reader.ReadInt32();
MessageAffixType affixType = (MessageAffixType) reader.ReadByte();
string name = reader.ReadString( 30 );
string affix = reader.ReadString();
string[] arguments = reader.ReadUnicodeString()
.Split( new[] { '\t' }, StringSplitOptions.RemoveEmptyEntries );
JournalEntry journalEntry = new JournalEntry
{
Serial = serial,
ID = graphic,
SpeechType = messageType,
SpeechHue = hue,
SpeechFont = font,
Cliloc = cliloc,
Name = name,
Arguments = arguments
};
string text = Cliloc.GetLocalString( journalEntry.Cliloc, journalEntry.Arguments );
if ( affixType.HasFlag( MessageAffixType.Prepend ) )
{
journalEntry.Text = $"{affix}{text}";
}
else if ( affixType.HasFlag( MessageAffixType.Append ) )
{
journalEntry.Text = $"{text}{affix}";
}
AddToJournal( journalEntry );
}
private static void OnPartyCommand( PacketReader reader )
{
int command = reader.ReadByte();
switch ( command )
{
case 1:
{
int count = reader.ReadByte();
List<int> partyMembers = new List<int>();
for ( int i = 0; i < count; i++ )
{
int serial = reader.ReadInt32();
partyMembers.Add( serial );
}
if ( Engine.Player == null )
{
return;
}
Engine.Player.Party = partyMembers.ToArray();
break;
}
case 2:
{
int count = reader.ReadByte();
reader.ReadInt32(); // removed member serial
List<int> partyMembers = new List<int>();
for ( int i = 0; i < count; i++ )
{
int serial = reader.ReadInt32();
partyMembers.Add( serial );
}
if ( Engine.Player == null )
{
return;
}
Engine.Player.Party = partyMembers.ToArray();
break;
}
case 7:
{
int leaderSerial = reader.ReadInt32();
if ( Options.CurrentOptions.AutoAcceptPartyInvite )
{
if ( !Options.CurrentOptions.AutoAcceptPartyOnlyFromFriends ||
Options.CurrentOptions.AutoAcceptPartyOnlyFromFriends &&
MobileCommands.InFriendList( leaderSerial ) )
{
Engine.SendPacketToServer( new AcceptPartyInvitation( leaderSerial ) );
}
}
break;
}
}
}
private static void OnHealthbarColour( PacketReader reader )
{
int serial = reader.ReadInt32();
int count = reader.ReadInt16();
Mobile mobile = Engine.Mobiles.GetMobile( serial );
if ( mobile == null )
{
return;
}
HealthbarColour healthbar = HealthbarColour.None;
if ( Engine.ClientVersion < new Version( 7, 0, 0, 0 ) )
{
for ( int i = 0; i < count; i++ )
{
int type = reader.ReadInt16();
bool enabled = reader.ReadBoolean();
switch ( type )
{
case 1 when enabled:
healthbar |= HealthbarColour.Green;
break;
case 1:
healthbar &= ~HealthbarColour.Green;
break;
case 2 when enabled:
healthbar |= HealthbarColour.Yellow;
break;
case 2:
healthbar &= ~HealthbarColour.Yellow;
break;
case 3 when enabled:
healthbar |= HealthbarColour.Red;
break;
case 3:
healthbar &= ~HealthbarColour.Red;
break;
}
}
mobile.HealthbarColour = healthbar;
}
else
{
int status = reader.ReadInt16();
int flags = reader.ReadByte();
switch ( status )
{
case 0x01:
healthbar = HealthbarColour.Green;
break;
case 0x02:
healthbar = HealthbarColour.Yellow;
break;
case 0x03:
healthbar = HealthbarColour.Red;
break;
}
if ( flags >= 1 )
{
mobile.HealthbarColour |= healthbar;
}
else
{
mobile.HealthbarColour &= ~healthbar;
}
}
}
private static void OnCloseGump( PacketReader reader )
{
uint gumpID = reader.ReadUInt32();
int buttonID = reader.ReadInt32();
Engine.GumpList.TryRemove( gumpID, out int _ );
Engine.Gumps.Remove( gumpID, buttonID );
}
private static void OnASCIIText( PacketReader reader )
{
JournalEntry journalEntry = new JournalEntry
{
Serial = reader.ReadInt32(),
ID = reader.ReadInt16(),
SpeechType = (JournalSpeech) reader.ReadByte(),
SpeechHue = reader.ReadInt16(),
SpeechFont = reader.ReadInt16(),
Name = reader.ReadString( 30 ),
Text = reader.ReadString()
};
AddToJournal( journalEntry );
}
private static void OnUnicodeText( PacketReader reader )
{
JournalEntry journalEntry = new JournalEntry
{
Serial = reader.ReadInt32(),
ID = reader.ReadInt16(),
SpeechType = (JournalSpeech) reader.ReadByte(),
SpeechHue = reader.ReadInt16(),
SpeechFont = reader.ReadInt16(),
SpeechLanguage = reader.ReadString( 4 ),
Name = reader.ReadString( 30 ),
Text = reader.ReadUnicodeString()
};
AddToJournal( journalEntry );
}
private static void OnMoveAccepted( PacketReader reader )
{
Direction direction = Engine.GetSequence( reader.ReadByte() );
if ( Engine.Player != null )
{
Engine.Player.Direction = direction;
}
}
private static void OnMoveRejected( PacketReader reader )
{
int sequence = reader.ReadByte();
int x = reader.ReadInt16();
int y = reader.ReadInt16();
Direction direction = (Direction) reader.ReadByte();
int z = reader.ReadSByte();
if ( Engine.Player != null )
{
Engine.Player.Direction = direction;
}
}
private static void OnCompressedGump( PacketReader reader )
{
int senderSerial = reader.ReadInt32();
uint gumpId = reader.ReadUInt32();
Engine.GumpList.AddOrUpdate( gumpId, senderSerial, ( k, v ) => senderSerial );
int x = reader.ReadInt32();
int y = reader.ReadInt32();
int compressedLength = reader.ReadInt32();
int decompressedLength = reader.ReadInt32() + 1;
if ( compressedLength <= 4 )
{
return;
}
compressedLength -= 4;
byte[] decompressedBuffer = new byte[decompressedLength];
byte[] compressedBuffer = reader.ReadByteArray( compressedLength );
bool success = Compression.Uncompress( ref decompressedBuffer, ref decompressedLength, compressedBuffer,
compressedLength );
if ( !success )
{
return;
}
string layout = Encoding.ASCII.GetString( decompressedBuffer ).TrimEnd( '\0' );
if ( string.IsNullOrEmpty( layout ) )
{
return;
}
int linesCount = reader.ReadInt32();
compressedLength = reader.ReadInt32();
decompressedLength = reader.ReadInt32() + 1;
string[] text = new string[linesCount];
if ( compressedLength > 4 )
{
compressedLength -= 4;
decompressedBuffer = new byte[decompressedLength];
compressedBuffer = reader.ReadByteArray( compressedLength );
success = Compression.Uncompress( ref decompressedBuffer, ref decompressedLength, compressedBuffer,
compressedLength );
if ( !success )
{
return;
}
int offset = 0;
for ( int i = 0; i < linesCount; i++ )
{
int length = ( ( decompressedBuffer[offset] << 8 ) | decompressedBuffer[offset + 1] ) * 2;
offset += 2;
text[i] = Encoding.BigEndianUnicode.GetString( decompressedBuffer, offset, length );
offset += length;
}
}
try
{
Gump gump = GumpParser.Parse( senderSerial, gumpId, x, y, layout, text );
Engine.Gumps.Add( gump );
GumpEvent?.Invoke( gumpId, senderSerial, gump );
}
catch ( Exception e )
{
SentrySdk.WithScope( scope =>
{
scope.SetExtra( "Serial", senderSerial );
scope.SetExtra( "GumpID", gumpId );
scope.SetExtra( "Layout", layout );
scope.SetExtra( "Text", text );
scope.SetExtra( "Packet", reader.GetData() );
scope.SetExtra( "Player", Engine.Player.ToString() );
scope.SetExtra( "WorldItemCount", Engine.Items.Count() );
scope.SetExtra( "WorldMobileCount", Engine.Mobiles.Count() );
SentrySdk.CaptureException( e );
} );
}
}
private static void OnMobileName( PacketReader reader )
{
int serial = reader.ReadInt32();
string name = reader.ReadString( 30 );
Mobile mobile = Engine.GetOrCreateMobile( serial );
mobile.Name = name;
}
private static void OnMobileMoving( PacketReader reader )
{
int serial = reader.ReadInt32();
int id = reader.ReadInt16();
int x = reader.ReadInt16();
int y = reader.ReadInt16();
int z = reader.ReadSByte();
int direction = reader.ReadByte() & 0x07;
int hue = reader.ReadUInt16();
int status = reader.ReadByte();
int notoriety = reader.ReadByte();
Mobile mobile = Engine.GetOrCreateMobile( serial );
mobile.ID = id;
mobile.X = x;
mobile.Y = y;
mobile.Z = z;
mobile.Direction = (Direction) direction;
mobile.Hue = hue;
mobile.Status = (MobileStatus) status;
mobile.Notoriety = (Notoriety) notoriety;
Engine.Mobiles.Add( mobile );
}
private static void OnMobileUpdated( PacketReader reader )
{
int serial = reader.ReadInt32();
int id = reader.ReadInt16();
reader.ReadByte(); // BYTE 0x00;
int hue = reader.ReadUInt16();
int status = reader.ReadByte();
int x = reader.ReadInt16();
int y = reader.ReadInt16();
reader.ReadInt16(); // WORD 0x00;
int direction = reader.ReadByte() & 0x07;
int z = reader.ReadSByte();
Mobile mobile = Engine.GetOrCreateMobile( serial );
mobile.ID = id;
mobile.Hue = hue;
mobile.Status = (MobileStatus) status;
mobile.X = x;
mobile.Y = y;
mobile.Direction = (Direction) direction;
mobile.Z = z;
Engine.Mobiles.Add( mobile );
MobileUpdatedEvent?.Invoke( mobile );
}
private static void OnTarget( PacketReader reader )
{
byte type = reader.ReadByte();
int tid = reader.ReadInt32();
TargetFlags flags = (TargetFlags) reader.ReadByte();
Engine.TargetType = (TargetType) type;
Engine.TargetSerial = tid;
Engine.TargetFlags = flags;
Engine.TargetExists = flags != TargetFlags.Cancel;
if ( Engine.TargetExists )
{
Engine.WaitingForTarget = false;
}
if ( !Options.CurrentOptions.QueueLastTarget || (TargetType) type != TargetType.Object ||
Engine.LastTargetQueue.Count == 0 )
{
return;
}
if ( flags == TargetFlags.Cancel )
{
Engine.TargetExists = false;
}
else
{
object obj = Engine.LastTargetQueue.Dequeue();
if ( obj == null )
{
return;
}
TargetCommands.Target( obj, Options.CurrentOptions.RangeCheckLastTarget );
}
}
private static void OnMobileStamina( PacketReader reader )
{
int serial = reader.ReadInt32();
int staminaMax = reader.ReadInt16();
int stamina = reader.ReadInt16();
Mobile mobile = Engine.GetOrCreateMobile( serial );
mobile.Stamina = stamina;
mobile.StaminaMax = staminaMax;
}
private static void OnMobileMana( PacketReader reader )
{
int serial = reader.ReadInt32();
int manaMax = reader.ReadInt16();
int mana = reader.ReadInt16();
Mobile mobile = Engine.GetOrCreateMobile( serial );
mobile.Mana = mana;
mobile.ManaMax = manaMax;
}
private static void OnMobileHits( PacketReader reader )
{
int serial = reader.ReadInt32();
int hitsMax = reader.ReadInt16();
int hits = reader.ReadInt16();
Mobile mobile = Engine.GetOrCreateMobile( serial );
mobile.Hits = hits;
mobile.HitsMax = hitsMax;
}
private static void OnSupportedFeatures( PacketReader reader )
{
Engine.Features = (FeatureFlags) reader.ReadInt32();
}
private static void OnSkillsList( PacketReader reader )
{
byte type = reader.ReadByte();
int id = reader.ReadInt16();
int value = reader.ReadInt16();
int baseValue = reader.ReadInt16();
LockStatus lockStatus = (LockStatus) reader.ReadByte();
bool haveCap = (((type != 0u) && type <= 0x03) || type == 0xDF);
int skillCap = haveCap ? reader.ReadInt16() : 1000;
if ( reader.Size <= 13 )
{
SkillUpdatedEvent?.Invoke( id, (float) value / 10, (float) baseValue / 10, lockStatus,
(float) skillCap / 10 );
}
else
{
SkillInfo si = new SkillInfo
{
Value = (float) value / 10,
BaseValue = (float) baseValue / 10,
LockStatus = lockStatus,
SkillCap = (float) skillCap / 10,
ID = id - 1
};
List<SkillInfo> skillInfoList = new List<SkillInfo>( 128 ) { si };
for ( ;; )
{
id = reader.ReadInt16();
if ( id == 0 )
{
break;
}
value = reader.ReadInt16();
baseValue = reader.ReadInt16();
lockStatus = (LockStatus) reader.ReadByte();
skillCap = haveCap ? reader.ReadInt16() : 1000;
si = new SkillInfo
{
Value = (float) value / 10,
BaseValue = (float) baseValue / 10,
LockStatus = lockStatus,
SkillCap = (float) skillCap / 10,
ID = id - 1
};
skillInfoList.Add( si );
}
SkillsListEvent?.Invoke( skillInfoList.ToArray() );
}
}
private static void OnExtendedCommand( PacketReader reader )
{
int command = reader.ReadInt16();
PacketHandler handler = GetExtendedHandler( command );
handler?.OnReceive( reader );
}
private static void OnMapChange( PacketReader reader )
{
Map map = (Map) reader.ReadByte();
if ( Engine.Player != null )
{
Engine.Player.Map = map;
}
MapChangedEvent?.Invoke( map );
}
public static event dMapChanged MapChangedEvent;
private static void OnItemAddedToContainer( PacketReader reader )
{
int serial = reader.ReadInt32();
int id = reader.ReadUInt16();
reader.ReadByte(); // offset
int count = reader.ReadUInt16();
int x = reader.ReadInt16();
int y = reader.ReadInt16();
int grid = Engine.ClientVersion == null || Engine.ClientVersion >= new Version( 6, 0, 1, 7 )
? reader.ReadByte()
: 0;
int containerSerial = reader.ReadInt32();
int hue = reader.ReadUInt16();
Item item = Engine.GetOrCreateItem( serial, containerSerial );
item.ID = id;
item.Count = count;
item.X = x;
item.Y = y;
item.Grid = grid;
item.Owner = containerSerial;
item.Hue = hue;
// Check mobile layer items if container is not a mobile
if ( !UOMath.IsMobile( containerSerial ) )
{
Layer layer = Engine.Player?.GetAllLayers().Select( ( s, i ) => new { i, s } )
.Where( t => t.s == serial ).Select( t => (Layer) t.i ).FirstOrDefault() ??
Layer.Invalid;
if ( layer != Layer.Invalid )
{
Engine.Player?.SetLayer( layer, 0 );
}
else
{
Mobile mobile = Engine.Mobiles.SelectEntity( m => m.GetAllLayers().Contains( serial ) );
layer = mobile?.GetAllLayers().Select( ( s, i ) => new { i, s } ).Where( t => t.s == serial )
.Select( t => (Layer) t.i ).FirstOrDefault() ?? Layer.Invalid;
if ( layer != Layer.Invalid )
{
mobile?.SetLayer( layer, 0 );
}
}
}
if ( UOMath.IsMobile( containerSerial ) )
{
item.Owner = containerSerial;
Engine.Items.Add( item );
}
else
{
Item container = Engine.GetOrCreateItem( containerSerial );
item.Owner = container.Serial;
if ( container.Container == null )
{
container.Container = new ItemCollection( containerSerial );
}
container.Container.Add( item );
}
//rehueList
int backpack = Engine.Player?.Backpack?.Serial ?? 0;
if ( item.IsDescendantOf( backpack ) )
{
Engine.RehueList.CheckItem( item );
}
//rehueList
}
private static void OnItemDeleted( PacketReader reader )
{
int serial = reader.ReadInt32();
Mobile mobile = Engine.Mobiles.FirstOrDefault( m => m.GetEquippedItems().Any( i => i.Serial == serial ) );
if ( mobile != null )
{
Item item = Engine.Items.GetItem( serial );
if ( item != null )
{
mobile.SetLayer( item.Layer, 0 );
mobile.Equipment.Remove( serial );
}
}
if ( UOMath.IsMobile( serial ) )
{
Engine.Mobiles.Remove( serial );
Engine.Items.RemoveByOwner( serial );
}
else
{
Engine.Items.Remove( serial );
}
}
private static void OnItemEquipped( PacketReader reader )
{
int serial = reader.ReadInt32();
int id = reader.ReadInt16();
reader.ReadByte(); // BYTE 0x00
int layer = reader.ReadByte();
int mobileSerial = reader.ReadInt32();
int hue = reader.ReadUInt16();
Item item = Engine.GetOrCreateItem( serial );
item.Owner = mobileSerial;
item.Layer = (Layer) layer;
item.Hue = hue;
item.ID = id;
if ( item.Properties == null && PropertyCache.TryGetValue( serial, out Property[] properties ) )
{
item.Properties = properties;
}
Mobile mobile = Engine.GetOrCreateMobile( mobileSerial );
Item existing = mobile.Equipment.FirstOrDefault( i => i.Layer == (Layer) layer );
if ( existing != null && item.Layer != Layer.Backpack )
{
mobile.Equipment.Remove( existing );
}
mobile.Equipment.Add( item );
Engine.Items.Add( item );
mobile.SetLayer( item.Layer, item.Serial );
}
private static void OnProperties( PacketReader reader )
{
reader.Seek( 2, SeekOrigin.Current ); // word 1
int serial = reader.ReadInt32();
reader.Seek( 2, SeekOrigin.Current ); // word 0
int hash = reader.ReadInt32();
List<Property> list = new List<Property>();
int cliloc;
bool first = true;
string name = "";
while ( ( cliloc = reader.ReadInt32() ) != 0 )
{
Property property = new Property { Cliloc = cliloc };
int length = reader.ReadInt16();
property.Arguments = reader.ReadUnicodeString( length )
.Split( new[] { '\t' }, StringSplitOptions.RemoveEmptyEntries );
property.Text = Cliloc.GetLocalString( cliloc, property.Arguments );
if ( first )
{
name = property.Text;
first = false;
}
list.Add( property );
}
Property[] propertyArray = list.ToArray();
PropertyCache.AddOrUpdate( serial, v => propertyArray, ( k, v ) => propertyArray );
if ( Engine.Player?.Serial == serial )
{
Engine.Player.Name = name;
Engine.Player.Properties = propertyArray;
Engine.UpdateWindowTitle();
}
else if ( UOMath.IsMobile( serial ) )
{
Mobile mobile = Engine.GetOrCreateMobile( serial );
mobile.Name = name;
mobile.Properties = propertyArray;
}
else
{
Item item = Engine.GetOrCreateItem( serial );
item.Name = name;
item.Properties = propertyArray;
}
}
private static void OnMobileStatus( PacketReader reader )
{
long length = reader.Size;
int serial = reader.ReadInt32();
string name = reader.ReadString( 30 );
int hits = reader.ReadInt16();
int hitsMax = reader.ReadInt16();
bool allowNameChange = reader.ReadBoolean(); // Allow Name Change
byte features = reader.ReadByte();
Mobile mobile = serial == Engine.Player?.Serial ? Engine.Player : Engine.GetOrCreateMobile( serial );
mobile.Name = name;
mobile.Hits = hits;
mobile.HitsMax = hitsMax;
mobile.IsRenamable = allowNameChange;
if ( features <= 0 )
{
return;
}
int sex = reader.ReadByte();
if ( serial != Engine.Player?.Serial )
{
return;
}
PlayerMobile player = Engine.Player;
player.Strength = reader.ReadInt16();
player.Dex = reader.ReadInt16();
player.Int = reader.ReadInt16();
player.Stamina = reader.ReadInt16();
player.StaminaMax = reader.ReadInt16();
player.Mana = reader.ReadInt16();
player.ManaMax = reader.ReadInt16();
player.Gold = reader.ReadInt32();
player.PhysicalResistance = reader.ReadInt16();
player.Weight = reader.ReadInt16();
if ( features >= 5 )
{
player.WeightMax = reader.ReadInt16();
player.Race = (MobileRace) reader.ReadByte();
}
if ( features >= 3 )
{
player.StatCap = reader.ReadInt16();
player.Followers = reader.ReadByte();
player.FollowersMax = reader.ReadByte();
}
if ( features >= 4 )
{
player.FireResistance = reader.ReadInt16();
player.ColdResistance = reader.ReadInt16();
player.PoisonResistance = reader.ReadInt16();
player.EnergyResistance = reader.ReadInt16();
player.Luck = reader.ReadInt16();
player.Damage = reader.ReadInt16();
player.DamageMax = reader.ReadInt16();
player.TithingPoints = reader.ReadInt32();
}
// ReSharper disable once InvertIf
if ( features >= 6 )
{
player.PhysicalResistanceMax = reader.ReadInt16();
player.FireResistanceMax = reader.ReadInt16();
player.ColdResistanceMax = reader.ReadInt16();
player.PoisonResistanceMax = reader.ReadInt16();
player.EnergyResistanceMax = reader.ReadInt16();
player.DefenseChanceIncrease = reader.ReadInt16();
player.DefenseChanceIncreaseMax = reader.ReadInt16();
player.HitChanceIncrease = reader.ReadInt16();
player.SwingSpeedIncrease = reader.ReadInt16();
player.DamageIncrease = reader.ReadInt16();
player.LowerReagentCost = reader.ReadInt16();
player.SpellDamageIncrease = reader.ReadInt16();
player.FasterCastRecovery = reader.ReadInt16();
player.FasterCasting = reader.ReadInt16();
player.LowerManaCost = reader.ReadInt16();
}
Engine.UpdateWindowTitle();
}
private static void OnMobileIncoming( PacketReader reader )
{
int serial = reader.ReadInt32();
ItemCollection container = new ItemCollection( serial );
Mobile mobile = serial == Engine.Player?.Serial ? Engine.Player : Engine.GetOrCreateMobile( serial );
mobile.ID = reader.ReadInt16();
mobile.X = reader.ReadInt16();
mobile.Y = reader.ReadInt16();
mobile.Z = reader.ReadSByte();
mobile.Direction = (Direction) ( reader.ReadByte() & 0x07 );
mobile.Hue = reader.ReadUInt16();
mobile.Status = (MobileStatus) reader.ReadByte();
mobile.Notoriety = (Notoriety) reader.ReadByte();
bool useNewIncoming = Engine.ClientVersion == null || Engine.ClientVersion >= new Version( 7, 0, 33, 1 );
for ( ;; )
{
int itemSerial = reader.ReadInt32();
if ( itemSerial == 0 )
{
break;
}
Item item = Engine.GetOrCreateItem( itemSerial );
item.Owner = serial;
item.ID = reader.ReadUInt16();
item.Layer = (Layer) reader.ReadByte();
if ( useNewIncoming )
{
item.Hue = reader.ReadUInt16();
}
else
{
if ( ( item.ID & 0x8000 ) != 0 )
{
item.ID ^= 0x8000;
item.Hue = reader.ReadUInt16();
}
}
container.Add( item );
}
mobile.Equipment.Clear();
mobile.Equipment.Add( container.GetItems() );
foreach ( Item item in container.GetItems() )
{
mobile.SetLayer( item.Layer, item.Serial );
}
Engine.Items.Add( container.GetItems() );
if ( !( mobile is PlayerMobile ) )
{
Engine.Mobiles.Add( mobile );
}
else
{
AbilitiesManager manager = AbilitiesManager.GetInstance();
manager.ResendGump( manager.Enabled );
}
MobileIncomingEvent?.Invoke( mobile, container );
}
private static void OnContainerContents( PacketReader reader )
{
Item containerItem = null;
int count = 0;
try
{
if ( reader.Size == 5 )
{
return;
}
bool oldStyle = false;
count = reader.ReadInt16();
if ( ( reader.Size - 5 ) / 20 != count )
{
oldStyle = true;
}
ItemCollection container = null;
for ( int i = 0; i < count; i++ )
{
int serial = reader.ReadInt32();
int id = reader.ReadUInt16();
reader.ReadByte(); // Item ID Offset
int amount = reader.ReadUInt16();
int x = reader.ReadInt16();
int y = reader.ReadInt16();
int grid = 0;
if ( !oldStyle )
{
grid = reader.ReadByte();
}
int containerSerial = reader.ReadInt32();
int hue = reader.ReadUInt16();
if ( container == null )
{
container = new ItemCollection( containerSerial );
}
Item item = Engine.GetOrCreateItem( serial, containerSerial );
item.ID = id;
item.Count = amount;
item.Owner = containerSerial;
item.Hue = hue;
item.Grid = grid;
item.X = x;
item.Y = y;
container.Add( item );
}
if ( container == null )
{
return;
}
containerItem = Engine.GetOrCreateItem( container.Serial );
if ( containerItem.Container == null )
{
containerItem.Container = container;
}
else
{
containerItem.Container.Clear();
containerItem.Container.Add( container.GetItems() );
}
Engine.Items.Add( containerItem );
ContainerContentsEvent?.Invoke( container.Serial, container );
Engine.RehueList.CheckContainer( container );
}
catch ( Exception e )
{
SentrySdk.WithScope( scope =>
{
scope.SetExtra( "ContainerSerial", containerItem?.Serial );
scope.SetExtra( "Count", count );
scope.SetExtra( "Packet", reader.GetData() );
scope.SetExtra( "WorldItemCount", Engine.Items.Count() );
scope.SetExtra( "WorldMobileCount", Engine.Mobiles.Count() );
SentrySdk.CaptureException( e );
} );
}
}
private static void OnSAWorldItem( PacketReader reader )
{
reader.ReadInt16(); // WORD 0x01
byte type = reader.ReadByte(); // Data Type (0x00 = use TileData, 0x01 = use BodyData, 0x02 = use MultiData)
int serialf3 = reader.ReadInt32();
//Log.Info( "P Adding 0x{0:x}", serialf3 );
Item item = Engine.GetOrCreateItem( serialf3 );
item.ArtDataID = type;
item.ID = reader.ReadUInt16();
item.Direction = (Direction) reader.ReadByte();
item.Count = reader.ReadUInt16();
reader.ReadInt16(); // Second Amount?
item.X = reader.ReadInt16();
item.Y = reader.ReadInt16();
item.Z = reader.ReadSByte();
item.Light = reader.ReadByte();
item.Hue = reader.ReadUInt16();
item.Flags = reader.ReadByte();
item.Owner = 0;
Engine.Items.Add( item );
NewWorldItemEvent?.Invoke( item );
}
private static void OnInitializePlayer( PacketReader reader )
{
int serial = reader.ReadInt32();
PlayerMobile mobile = new PlayerMobile( serial );
// ClassicUO (or ServUO) seems to send other packets before InitializePlayer that can cause the mobile to already exist before we
// know it's the player, if already in collection, copy and delete.
if ( Engine.Mobiles.GetMobile( serial, out Mobile m ) )
{
mobile.Equipment = m.Equipment;
Engine.Mobiles.Remove( m );
}
reader.ReadInt32(); // DWORD 0
short id = reader.ReadInt16();
short x = reader.ReadInt16();
short y = reader.ReadInt16();
short z = reader.ReadInt16();
byte direction = reader.ReadByte();
mobile.ID = id;
mobile.X = x;
mobile.Y = y;
mobile.Z = z;
mobile.Direction = (Direction) direction;
Engine.SetPlayer( mobile );
}
private static void Register( int packetId, int length, OnPacketReceive onReceive )
{
_handlers[packetId] = new PacketHandler( packetId, length, onReceive );
}
private static void RegisterExtended( int packetId, int length, OnPacketReceive onReceive )
{
_extendedHandlers[packetId] = new PacketHandler( packetId, length, onReceive );
}
internal static PacketHandler GetHandler( int packetId )
{
return _handlers[packetId];
}
private static PacketHandler GetExtendedHandler( int packetId )
{
return _extendedHandlers[packetId];
}
public static void AddToJournal( JournalEntry entry )
{
Engine.Journal.Write( entry );
JournalEntryAddedEvent?.Invoke( entry );
}
}
}