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 PropertyCache { get; set; } = new ConcurrentDictionary(); 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 pets = new List(); 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 entries = new List(); 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 shards = new List(); 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 properties = new List { 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 shopList = new List(); 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 shopList = new List(); 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 containerItems = new List( 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 partyMembers = new List(); 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 partyMembers = new List(); 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 skillInfoList = new List( 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 list = new List(); 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 ); } } }