From f29d87aced50d38ed7f65901c4b636f35f5a1836 Mon Sep 17 00:00:00 2001 From: dannuic Date: Thu, 16 Apr 2026 15:59:54 -0600 Subject: [PATCH 01/17] Validated up to OP_WorldObjectsSent --- common/patches/tob.cpp | 63 +- common/patches/tob_structs.h | 34 +- common/ruletypes.h | 2 +- tob/opcodes.md | 1256 +++++++++++++++++----------------- 4 files changed, 680 insertions(+), 675 deletions(-) diff --git a/common/patches/tob.cpp b/common/patches/tob.cpp index 90529b045..64ef78c77 100644 --- a/common/patches/tob.cpp +++ b/common/patches/tob.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "world/sof_char_create_data.h" @@ -217,19 +218,22 @@ namespace TOB ENCODE(OP_BlockedBuffs) { - ENCODE_LENGTH_EXACT(BlockedBuffs_Struct); - SETUP_DIRECT_ENCODE(BlockedBuffs_Struct, structs::BlockedBuffs_Struct); + // Blocked buffs are a major change. They are stored in a resizable array in TOB, so this sends size, then + // spells, then the final two bools -- see 0x140202750 + SETUP_VAR_ENCODE(BlockedBuffs_Struct); - for (uint32 i = 0; i < BLOCKED_BUFF_COUNT; ++i) - eq->SpellID[i] = emu->SpellID[i]; + // size is uint32 + count * int32 + uint8 + uint8 + uint32 sz = 6 + emu->Count * 4; + __packet->size = sz; + __packet->pBuffer = new unsigned char[sz]; + memset(__packet->pBuffer, 0, sz); - for (uint32 i = BLOCKED_BUFF_COUNT; i < structs::BLOCKED_BUFF_COUNT; ++i) - eq->SpellID[i] = -1; + __packet->WriteUInt32(emu->Count); + for (int i = 0; i < emu->Count; i++) + __packet->WriteSInt32(emu->SpellID[i]); - OUT(Count); - OUT(Pet); - OUT(Initialise); - OUT(Flags); + __packet->WriteUInt8(emu->Pet); + __packet->WriteUInt8(emu->Initialise); FINISH_ENCODE(); } @@ -981,6 +985,7 @@ namespace TOB ENCODE(OP_NewSpawn) { ENCODE_FORWARD(OP_ZoneSpawns); } ENCODE(OP_NewZone) { + // zoneHeader EQApplicationPacket* in = *p; *p = nullptr; @@ -2903,7 +2908,7 @@ namespace TOB buf.WriteString(new_message); - auto outapp = new EQApplicationPacket(OP_SpecialMesg, buf); + auto outapp = new EQApplicationPacket(OP_SpecialMesg, std::move(buf)); dest->FastQueuePacket(&outapp, ack_req); delete in; @@ -2934,14 +2939,14 @@ namespace TOB return; } - auto outapp = new EQApplicationPacket(OP_ChangeSize, sizeof(ChangeSize_Struct)); + auto outapp = new EQApplicationPacket(OP_ChangeSize, sizeof(structs::ChangeSize_Struct)); - ChangeSize_Struct* css = (ChangeSize_Struct*)outapp->pBuffer; + structs::ChangeSize_Struct* css = (structs::ChangeSize_Struct*)outapp->pBuffer; css->EntityID = sas->spawn_id; css->Size = (float)sas->parameter; - css->Unknown08 = 0; - css->Unknown12 = 1.0f; + css->CameraOffset = 0; + css->AnimationSpeedRelated = 1.0f; dest->FastQueuePacket(&outapp, ack_req); delete in; @@ -3574,18 +3579,28 @@ namespace TOB DECODE(OP_BlockedBuffs) { - DECODE_LENGTH_EXACT(structs::BlockedBuffs_Struct); - SETUP_DIRECT_DECODE(BlockedBuffs_Struct, structs::BlockedBuffs_Struct); + uint32 count = __packet->ReadUInt32(); + std::vector blocked_spell_ids; + blocked_spell_ids.reserve(count); + for (int i = 0; i < count; ++i) + blocked_spell_ids.push_back(static_cast(__packet->ReadUInt32())); - for (uint32 i = 0; i < BLOCKED_BUFF_COUNT; ++i) - emu->SpellID[i] = eq->SpellID[i]; + bool pet = __packet->ReadUInt8() == 1; + bool init = __packet->ReadUInt8() == 1; - IN(Count); - IN(Pet); - IN(Initialise); - IN(Flags); + __packet->SetReadPosition(0); // reset the packet read to pass it along - FINISH_DIRECT_DECODE(); + __packet->size = sizeof(BlockedBuffs_Struct); + __packet->pBuffer = new unsigned char[__packet->size]{}; + BlockedBuffs_Struct* emu = (BlockedBuffs_Struct*)__packet->pBuffer; + + memset(emu->SpellID, -1, sizeof(emu->SpellID)); + for (int i = 0; i < count; ++i) + emu->SpellID[i] = blocked_spell_ids[i]; + + emu->Count = count; + emu->Pet = pet; + emu->Initialise = init; } DECODE(OP_CastSpell) diff --git a/common/patches/tob_structs.h b/common/patches/tob_structs.h index 5f9803f3c..928e758e6 100644 --- a/common/patches/tob_structs.h +++ b/common/patches/tob_structs.h @@ -12,7 +12,7 @@ namespace TOB { static const uint32 MAX_PP_UNKNOWN_ABILITIES = 25; static const uint32 MAX_RECAST_TYPES = 25; static const uint32 MAX_ITEM_RECAST_TYPES = 100; - static const uint32 BLOCKED_BUFF_COUNT = 40; + static const uint32 BLOCKED_BUFF_COUNT = 60; // this might not be needed? static const uint32 BUFF_COUNT = 62; static const uint32 MAX_PP_LANGUAGE = 32; #pragma pack(1) @@ -274,7 +274,6 @@ namespace TOB { Unknown39, Unknown40, Unknown41, - Unknown42, Birthdate, EncounterLock }; @@ -288,6 +287,15 @@ namespace TOB { /*0024*/ }; + struct ChangeSize_Struct + { + /*00*/ uint32 EntityID; + /*04*/ float Size; + /*08*/ float CameraOffset; + /*12*/ float AnimationSpeedRelated; + /*16*/ + }; + struct Spawn_Struct_Bitfields { union { @@ -400,14 +408,14 @@ namespace TOB { /*056*/ float X; /*060*/ float Z; /*064*/ float Heading; - /*068*/ float DoorAngle; //not sure if this is actually a float; it might be a uint32 like DefaultDoorAngle + /*068*/ float DoorAngle; /*072*/ uint32 ScaleFactor; //rof2's size /*076*/ uint32 Unknown76; //client doesn't seem to read this /*080*/ uint8 Id; //doorid /*081*/ uint8 Type; //opentype /*082*/ uint8 State; //state_at_spawn /*083*/ uint8 DefaultState; //invert_state - /*084*/ int32 Param; //door_param + /*084*/ int32 Param; //door_param (spell id?) /*088*/ uint32 AdventureDoorId; /*092*/ uint32 DynDoorID; /*096*/ uint32 RealEstateDoorID; @@ -549,15 +557,6 @@ namespace TOB { /*024*/ }; - struct ChangeSize_Struct - { - /*00*/ uint32 EntityID; - /*04*/ float Size; - /*08*/ uint32 Unknown08; // Observed 0 - /*12*/ float Unknown12; // Observed 1.0f - /*16*/ - }; - struct SpawnHPUpdate_Struct { /*00*/ int16 spawn_id; @@ -837,15 +836,6 @@ namespace TOB { /*009*/ uint8 unknown009[3]; }; - struct BlockedBuffs_Struct - { - /*000*/ int32 SpellID[BLOCKED_BUFF_COUNT]; - /*120*/ uint32 Count; - /*124*/ uint8 Pet; - /*125*/ uint8 Initialise; - /*126*/ uint16 Flags; - }; - struct ZonePlayerToBind_Struct { //Same structure as the binds in PlayerProfile_Struct //Assembly calls the same function diff --git a/common/ruletypes.h b/common/ruletypes.h index fad7e2a96..131355c2c 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -349,7 +349,7 @@ RULE_STRING(World, MOTD, "", "Server MOTD sent on login, change from empty to ha RULE_STRING(World, Rules, "", "Server Rules, change from empty to have this be used instead of variables table 'rules' value, lines are pipe (|) separated, example: A|B|C") RULE_BOOL(World, EnableAutoLogin, false, "Enables or disables auto login of characters, allowing people to log characters in directly from loginserver to ingame") RULE_BOOL(World, EnablePVPRegions, true, "Enables or disables PVP Regions automatically setting your PVP flag") -RULE_STRING(World, SupportedClients, "RoF2", "Comma-delimited list of clients to restrict to. Supported values are Titanium | SoF | SoD | UF | RoF | RoF2. Example: Titanium,RoF2") +RULE_STRING(World, SupportedClients, "RoF2,TOB", "Comma-delimited list of clients to restrict to. Supported values are Titanium | SoF | SoD | UF | RoF | RoF2 | TOB. Example: Titanium,RoF2,TOB") RULE_STRING(World, CustomFilesKey, "", "Enable if the server requires custom files and sends a key to validate. Empty string to disable. Example: eqcustom_v1") RULE_STRING(World, CustomFilesUrl, "github.com/knervous/eqnexus/releases", "URL to display at character select if client is missing custom files") RULE_INT(World, CustomFilesAdminLevel, 20, "Admin level at which custom file key is not required when CustomFilesKey is specified") diff --git a/tob/opcodes.md b/tob/opcodes.md index 5337728e6..d21ad856f 100644 --- a/tob/opcodes.md +++ b/tob/opcodes.md @@ -6,631 +6,631 @@ Below is a status list for the 450 opcodes we currently use on the server for th ### World/Zone Opcode Implementation Status -| Opcode | Status | Notes | Working On | -|:----------------------------------|:--------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------| -| `OP_AAAction` | 🟡 Unverified | | | -| `OP_AAExpUpdate` | 🟡 Unverified | | | -| `OP_AcceptNewTask` | 🔴 Not-Set | | | -| `OP_AckPacket` | 🟢 Verified | | | -| `OP_Action` | 🟡 Unverified | | | -| `OP_Action2` | 🔴 Not-Set | | | -| `OP_AddNimbusEffect` | 🟡 Unverified | | | -| `OP_AdventureData` | 🔴 Not-Set | | | -| `OP_AdventureDetails` | 🔴 Not-Set | | | -| `OP_AdventureFinish` | 🔴 Not-Set | | | -| `OP_AdventureInfo` | 🔴 Not-Set | | | -| `OP_AdventureInfoRequest` | 🔴 Not-Set | | | -| `OP_AdventureLeaderboardReply` | 🔴 Not-Set | | | -| `OP_AdventureLeaderboardRequest` | 🔴 Not-Set | | | -| `OP_AdventureMerchantPurchase` | 🔴 Not-Set | | | -| `OP_AdventureMerchantRequest` | 🔴 Not-Set | | | -| `OP_AdventureMerchantResponse` | 🔴 Not-Set | | | -| `OP_AdventureMerchantSell` | 🔴 Not-Set | | | -| `OP_AdventurePointsUpdate` | 🔴 Not-Set | | | -| `OP_AdventureRequest` | 🔴 Not-Set | | | -| `OP_AdventureStatsReply` | 🔴 Not-Set | | | -| `OP_AdventureStatsRequest` | 🔴 Not-Set | | | -| `OP_AdventureUpdate` | 🔴 Not-Set | | | -| `OP_AggroMeterLockTarget` | 🔴 Not-Set | | | -| `OP_AggroMeterTargetInfo` | 🔴 Not-Set | | | -| `OP_AggroMeterUpdate` | 🔴 Not-Set | | | -| `OP_AltCurrency` | 🔴 Not-Set | | | -| `OP_AltCurrencyMerchantReply` | 🔴 Not-Set | | | -| `OP_AltCurrencyMerchantRequest` | 🔴 Not-Set | | | -| `OP_AltCurrencyPurchase` | 🔴 Not-Set | | | -| `OP_AltCurrencyReclaim` | 🔴 Not-Set | | | -| `OP_AltCurrencySell` | 🔴 Not-Set | | | -| `OP_AltCurrencySellSelection` | 🔴 Not-Set | | | -| `OP_Animation` | 🟡 Unverified | | | -| `OP_AnnoyingZoneUnknown` | 🔴 Not-Set | | | -| `OP_ApplyPoison` | 🟡 Unverified | | | -| `OP_ApproveName` | 🟡 Unverified | This takes multiple parameters from the client, and it can take multiple integer values from the server | | -| `OP_ApproveWorld` | 🔴 Not-Set | | | -| `OP_ApproveZone` | 🔴 Not-Set | | | -| `OP_Assist` | 🟡 Unverified | | | -| `OP_AssistGroup` | 🟡 Unverified | | | -| `OP_AugmentInfo` | 🟡 Unverified | | | -| `OP_AugmentItem` | 🟡 Unverified | | | -| `OP_AutoAttack` | 🟡 Unverified | | | -| `OP_AutoAttack2` | 🟡 Unverified | | | -| `OP_AutoFire` | 🟡 Unverified | | | -| `OP_Bandolier` | 🔴 Not-Set | | | -| `OP_BankerChange` | 🟡 Unverified | | | -| `OP_Barter` | 🔴 Not-Set | | | -| `OP_Bazaar` | 🔴 Not-Set | | | -| `OP_BazaarInspect` | 🔴 Not-Set | | | -| `OP_BazaarSearch` | 🔴 Not-Set | | | -| `OP_BecomeCorpse` | 🔴 Not-Set | | | -| `OP_BecomeTrader` | 🔴 Not-Set | | | -| `OP_Begging` | 🟡 Unverified | | | -| `OP_BeginCast` | 🟡 Unverified | | | -| `OP_Bind_Wound` | 🟡 Unverified | | | -| `OP_BlockedBuffs` | 🟡 Unverified | | | -| `OP_BoardBoat` | 🟡 Unverified | | | -| `OP_BookButton` | 🟡 Unverified | | | -| `OP_Buff` | 🟡 Unverified | | | -| `OP_BuffCreate` | 🟡 Unverified | | | -| `OP_BuffRemoveRequest` | 🟡 Unverified | | | -| `OP_Bug` | 🟡 Unverified | | | -| `OP_BuyerItems` | 🔴 Not-Set | | | -| `OP_CameraEffect` | 🟡 Unverified | | | -| `OP_Camp` | 🟡 Unverified | | | -| `OP_CancelSneakHide` | 🟡 Unverified | | | -| `OP_CancelTask` | 🔴 Not-Set | | | -| `OP_CancelTrade` | 🟡 Unverified | | | -| `OP_CashReward` | 🟡 Unverified | | | -| `OP_CastSpell` | 🟡 Unverified | | | -| `OP_ChangeSize` | 🟡 Unverified | | | -| `OP_ChannelMessage` | 🟡 Unverified | | | -| `OP_ChangePetName` | 🔴 Not-Set | | | -| `OP_CharacterCreate` | 🟢 Verified | Sends heroic type, can be used for something? | | -| `OP_CharacterCreateRequest` | 🟢 Verified | | | -| `OP_CharInventory` | 🟡 Unverified | | | -| `OP_Charm` | 🟡 Unverified | | | -| `OP_ChatMessage` | 🔴 Not-Set | | | -| `OP_ClearAA` | 🟡 Unverified | | | -| `OP_ClearBlockedBuffs` | 🟡 Unverified | | | -| `OP_ClearLeadershipAbilities` | 🔴 Not-Set | | | -| `OP_ClearNPCMarks` | 🔴 Not-Set | | | -| `OP_ClearObject` | 🟡 Unverified | | | -| `OP_ClearSurname` | 🔴 Not-Set | | | -| `OP_ClickDoor` | 🟡 Unverified | | | -| `OP_ClickObject` | 🟡 Unverified | | | -| `OP_ClickObjectAction` | 🟡 Unverified | | | -| `OP_ClientError` | 🔴 Not-Set | | | -| `OP_ClientReady` | 🟡 Unverified | | | -| `OP_ClientTimeStamp` | 🔴 Not-Set | | | -| `OP_ClientUpdate` | 🟡 Unverified | | | -| `OP_CloseContainer` | 🔴 Not-Set | | | -| `OP_CloseTributeMaster` | 🔴 Not-Set | | | -| `OP_ColoredText` | 🟡 Unverified | | | -| `OP_CombatAbility` | 🟡 Unverified | | | -| `OP_Command` | 🔴 Not-Set | | | -| `OP_CompletedTasks` | 🔴 Not-Set | | | -| `OP_ConfirmDelete` | 🟡 Unverified | | | -| `OP_Consent` | 🟡 Unverified | | | -| `OP_ConsentDeny` | 🟡 Unverified | | | -| `OP_ConsentResponse` | 🟡 Unverified | | | -| `OP_Consider` | 🟡 Unverified | | | -| `OP_ConsiderCorpse` | 🟡 Unverified | | | -| `OP_Consume` | 🟡 Unverified | | | -| `OP_ControlBoat` | 🟡 Unverified | | | -| `OP_CorpseDrag` | 🟡 Unverified | | | -| `OP_CorpseDrop` | 🟡 Unverified | | | -| `OP_CrashDump` | 🔴 Not-Set | | | -| `OP_CrystalCountUpdate` | 🔴 Not-Set | | | -| `OP_CrystalCreate` | 🔴 Not-Set | | | -| `OP_CrystalReclaim` | 🔴 Not-Set | | | -| `OP_CustomTitles` | 🔴 Not-Set | | | -| `OP_Damage` | 🟡 Unverified | | | -| `OP_Death` | 🟡 Unverified | | | -| `OP_DelegateAbility` | 🔴 Not-Set | | | -| `OP_DeleteCharacter` | 🟢 Verified | | | -| `OP_DeleteCharge` | 🟡 Unverified | | | -| `OP_DeleteItem` | 🟡 Unverified | | | -| `OP_DeletePetition` | 🔴 Not-Set | | | -| `OP_DeleteSpawn` | 🟡 Unverified | | | -| `OP_DeleteSpell` | 🟡 Unverified | | | -| `OP_DenyResponse` | 🟡 Unverified | | | -| `OP_Disarm` | 🟡 Unverified | | | -| `OP_DisarmTraps` | 🟡 Unverified | | | -| `OP_DisciplineTimer` | 🟡 Unverified | | | -| `OP_DisciplineUpdate` | 🟡 Unverified | | | -| `OP_DiscordMerchantInventory` | 🔴 Not-Set | | | -| `OP_DoGroupLeadershipAbility` | 🔴 Not-Set | | | -| `OP_DuelDecline` | 🔴 Not-Set | | | -| `OP_DuelAccept` | 🔴 Not-Set | | | -| `OP_DumpName` | 🔴 Not-Set | | | -| `OP_Dye` | 🔴 Not-Set | | | -| `OP_DynamicWall` | 🔴 Not-Set | | | -| `OP_DzAddPlayer` | 🔴 Not-Set | | | -| `OP_DzChooseZone` | 🔴 Not-Set | | | -| `OP_DzChooseZoneReply` | 🔴 Not-Set | | | -| `OP_DzCompass` | 🔴 Not-Set | | | -| `OP_DzExpeditionEndsWarning` | 🔴 Not-Set | | | -| `OP_DzExpeditionInfo` | 🔴 Not-Set | | | -| `OP_DzExpeditionInvite` | 🔴 Not-Set | | | -| `OP_DzExpeditionInviteResponse` | 🔴 Not-Set | | | -| `OP_DzExpeditionLockoutTimers` | 🔴 Not-Set | | | -| `OP_DzListTimers` | 🔴 Not-Set | | | -| `OP_DzMakeLeader` | 🔴 Not-Set | | | -| `OP_DzMemberList` | 🔴 Not-Set | | | -| `OP_DzMemberListName` | 🔴 Not-Set | | | -| `OP_DzMemberListStatus` | 🔴 Not-Set | | | -| `OP_DzPlayerList` | 🔴 Not-Set | | | -| `OP_DzQuit` | 🔴 Not-Set | | | -| `OP_DzRemovePlayer` | 🔴 Not-Set | | | -| `OP_DzSetLeaderName` | 🔴 Not-Set | | | -| `OP_DzSwapPlayer` | 🔴 Not-Set | | | -| `OP_Emote` | 🔴 Not-Set | | | -| `OP_EndLootRequest` | 🟡 Unverified | | | -| `OP_EnduranceUpdate` | 🔴 Not-Set | | | -| `OP_EnterChat` | 🔴 Not-Set | | | -| `OP_EnterWorld` | 🟢 Verified | | | -| `OP_EnvDamage` | 🟡 Unverified | | | -| `OP_EvolveItem` | 🔴 Not-Set | | | -| `OP_ExpansionInfo` | 🟢 Verified | Updated from u32 to u64 and works now | | -| `OP_ExpUpdate` | 🟡 Unverified | | | -| `OP_FaceChange` | 🔴 Not-Set | | | -| `OP_Feedback` | 🔴 Not-Set | | | -| `OP_FeignDeath` | 🟡 Unverified | | | -| `OP_FellowshipUpdate` | 🔴 Not-Set | | | -| `OP_FindPersonReply` | 🔴 Not-Set | | | -| `OP_FindPersonRequest` | 🔴 Not-Set | | | -| `OP_FinishTrade` | 🟡 Unverified | | | -| `OP_FinishWindow` | 🟡 Unverified | | | -| `OP_FinishWindow2` | 🟡 Unverified | | | -| `OP_Fishing` | 🟡 Unverified | | | -| `OP_Fling` | 🟡 Unverified | | | -| `OP_FloatListThing` | 🟢 Verified | Movement History. Sent from client, but emu doesn't use it so setting it as varified. Reference is 0x1402FFAD0 | | -| `OP_Forage` | 🟡 Unverified | | | -| `OP_ForceFindPerson` | 🔴 Not-Set | | | -| `OP_FormattedMessage` | 🟡 Unverified | | | -| `OP_FriendsWho` | 🟡 Unverified | | | -| `OP_GetGuildMOTD` | 🔴 Not-Set | | | -| `OP_GetGuildMOTDReply` | 🔴 Not-Set | | | -| `OP_GetGuildsList` | 🔴 Not-Set | | | -| `OP_GiveMoney` | 🔴 Not-Set | | | -| `OP_GMApproval` | 🔴 Not-Set | | | -| `OP_GMBecomeNPC` | 🔴 Not-Set | | | -| `OP_GMDelCorpse` | 🔴 Not-Set | | | -| `OP_GMEmoteZone` | 🔴 Not-Set | | | -| `OP_GMEndTraining` | 🟡 Unverified | | | -| `OP_GMEndTrainingResponse` | 🔴 Not-Set | | | -| `OP_GMFind` | 🔴 Not-Set | | | -| `OP_GMGoto` | 🔴 Not-Set | | | -| `OP_GMHideMe` | 🔴 Not-Set | | | -| `OP_GMKick` | 🔴 Not-Set | | | -| `OP_GMKill` | 🔴 Not-Set | | | -| `OP_GMLastName` | 🔴 Not-Set | | | -| `OP_GMNameChange` | 🔴 Not-Set | | | -| `OP_GMSearchCorpse` | 🔴 Not-Set | | | -| `OP_GMServers` | 🔴 Not-Set | | | -| `OP_GMSummon` | 🔴 Not-Set | | | -| `OP_GMToggle` | 🔴 Not-Set | | | -| `OP_GMTraining` | 🟡 Unverified | | | -| `OP_GMTrainSkill` | 🟡 Unverified | | | -| `OP_GMTrainSkillConfirm` | 🟡 Unverified | | | -| `OP_GMZoneRequest` | 🔴 Not-Set | | | -| `OP_GMZoneRequest2` | 🔴 Not-Set | | | -| `OP_GroundSpawn` | 🟡 Unverified | | | -| `OP_GroupAcknowledge` | 🔴 Not-Set | | | -| `OP_GroupCancelInvite` | 🔴 Not-Set | | | -| `OP_GroupDelete` | 🔴 Not-Set | | | -| `OP_GroupDisband` | 🟡 Unverified | | | -| `OP_GroupDisbandOther` | 🔴 Not-Set | | | -| `OP_GroupDisbandYou` | 🔴 Not-Set | | | -| `OP_GroupFollow` | 🔴 Not-Set | | | -| `OP_GroupFollow2` | 🔴 Not-Set | | | -| `OP_GroupInvite` | 🟡 Unverified | | | -| `OP_GroupInvite2` | 🔴 Not-Set | | | -| `OP_GroupLeaderChange` | 🔴 Not-Set | | | -| `OP_GroupLeadershipAAUpdate` | 🔴 Not-Set | | | -| `OP_GroupMakeLeader` | 🔴 Not-Set | | | -| `OP_GroupMentor` | 🔴 Not-Set | | | -| `OP_GroupRoles` | 🔴 Not-Set | | | -| `OP_GroupUpdate` | 🔴 Not-Set | | | -| `OP_GroupUpdateB` | 🔴 Not-Set | | | -| `OP_GroupUpdateLeaderAA` | 🔴 Not-Set | | | -| `OP_GuildBank` | 🔴 Not-Set | | | -| `OP_GuildBankItemList` | 🔴 Not-Set | | | -| `OP_GuildCreate` | 🔴 Not-Set | | | -| `OP_GuildDelete` | 🔴 Not-Set | | | -| `OP_GuildDeleteGuild` | 🔴 Not-Set | | | -| `OP_GuildDemote` | 🔴 Not-Set | | | -| `OP_GuildInvite` | 🔴 Not-Set | | | -| `OP_GuildInviteAccept` | 🔴 Not-Set | | | -| `OP_GuildLeader` | 🔴 Not-Set | | | -| `OP_GuildManageAdd` | 🔴 Not-Set | | | -| `OP_GuildManageBanker` | 🔴 Not-Set | | | -| `OP_GuildManageRemove` | 🔴 Not-Set | | | -| `OP_GuildManageStatus` | 🔴 Not-Set | | | -| `OP_GuildMemberLevelUpdate` | 🔴 Not-Set | | | -| `OP_GuildMemberList` | 🔴 Not-Set | | | -| `OP_GuildMemberUpdate` | 🔴 Not-Set | | | -| `OP_GuildMemberLevel` | 🔴 Not-Set | | | -| `OP_GuildMemberRankAltBanker` | 🔴 Not-Set | | | -| `OP_GuildMemberPublicNote` | 🔴 Not-Set | | | -| `OP_GuildMemberAdd` | 🔴 Not-Set | | | -| `OP_GuildMemberRename` | 🔴 Not-Set | | | -| `OP_GuildMemberDelete` | 🔴 Not-Set | | | -| `OP_GuildMemberDetails` | 🔴 Not-Set | | | -| `OP_GuildRenameGuild` | 🔴 Not-Set | | | -| `OP_GuildMOTD` | 🔴 Not-Set | | | -| `OP_GuildPeace` | 🔴 Not-Set | | | -| `OP_GuildPromote` | 🔴 Not-Set | | | -| `OP_GuildPublicNote` | 🔴 Not-Set | | | -| `OP_GuildRemove` | 🔴 Not-Set | | | -| `OP_GuildSelectTribute` | 🔴 Not-Set | | | -| `OP_GuildModifyBenefits` | 🔴 Not-Set | | | -| `OP_GuildTributeToggleReq` | 🔴 Not-Set | | | -| `OP_GuildTributeToggleReply` | 🔴 Not-Set | | | -| `OP_GuildOptInOut` | 🔴 Not-Set | | | -| `OP_GuildSaveActiveTributes` | 🔴 Not-Set | | | -| `OP_GuildSendActiveTributes` | 🔴 Not-Set | | | -| `OP_GuildTributeFavorAndTimer` | 🔴 Not-Set | | | -| `OP_GuildsList` | 🔴 Not-Set | | | -| `OP_GuildStatus` | 🔴 Not-Set | | | -| `OP_GuildTributeInfo` | 🔴 Not-Set | | | -| `OP_GuildUpdate` | 🔴 Not-Set | | | -| `OP_GuildTributeDonateItem` | 🔴 Not-Set | | | -| `OP_GuildTributeDonatePlat` | 🔴 Not-Set | | | -| `OP_GuildWar` | 🔴 Not-Set | | | -| `OP_Heartbeat` | 🔴 Not-Set | | | -| `OP_Hide` | 🟡 Unverified | | | -| `OP_HideCorpse` | 🟡 Unverified | | | -| `OP_HPUpdate` | 🟡 Unverified | | | -| `OP_Illusion` | 🟡 Unverified | | | -| `OP_IncreaseStats` | 🟡 Unverified | | | -| `OP_InitialHPUpdate` | 🔴 Not-Set | | | -| `OP_InitialMobHealth` | 🔴 Not-Set | | | -| `OP_InspectAnswer` | 🔴 Not-Set | | | -| `OP_InspectBuffs` | 🔴 Not-Set | | | -| `OP_InspectMessageUpdate` | 🔴 Not-Set | | | -| `OP_InspectRequest` | 🔴 Not-Set | | | -| `OP_InstillDoubt` | 🟡 Unverified | | | -| `OP_InterruptCast` | 🟡 Unverified | | | -| `OP_InvokeChangePetName` | 🔴 Not-Set | | | -| `OP_InvokeChangePetNameImmediate` | 🔴 Not-Set | | | -| `OP_InvokeNameChangeImmediate` | 🔴 Not-Set | | | -| `OP_InvokeNameChangeLazy` | 🔴 Not-Set | | | -| `OP_ItemLinkClick` | 🔴 Not-Set | | | -| `OP_ItemLinkResponse` | 🔴 Not-Set | | | -| `OP_ItemLinkText` | 🔴 Not-Set | | | -| `OP_ItemName` | 🔴 Not-Set | | | -| `OP_ItemPacket` | 🟡 Unverified | | | -| `OP_ItemPreview` | 🔴 Not-Set | | | -| `OP_ItemPreviewRequest` | 🔴 Not-Set | | | -| `OP_ItemRecastDelay` | 🟡 Unverified | | | -| `OP_ItemVerifyReply` | 🟡 Unverified | | | -| `OP_ItemVerifyRequest` | 🟡 Unverified | | | -| `OP_ItemViewUnknown` | 🔴 Not-Set | | | -| `OP_Jump` | 🟡 Unverified | | | -| `OP_KeyRing` | 🔴 Not-Set | | | -| `OP_KickPlayers` | 🟡 Unverified | | | -| `OP_KnowledgeBase` | 🔴 Not-Set | | | -| `OP_LDoNButton` | 🔴 Not-Set | | | -| `OP_LDoNDisarmTraps` | 🔴 Not-Set | | | -| `OP_LDoNInspect` | 🔴 Not-Set | | | -| `OP_LDoNOpen` | 🟡 Unverified | | | -| `OP_LDoNPickLock` | 🟡 Unverified | | | -| `OP_LDoNSenseTraps` | 🟡 Unverified | | | -| `OP_LeadershipExpToggle` | 🔴 Not-Set | | | -| `OP_LeadershipExpUpdate` | 🔴 Not-Set | | | -| `OP_LeaveAdventure` | 🔴 Not-Set | | | -| `OP_LeaveBoat` | 🟡 Unverified | | | -| `OP_LevelAppearance` | 🟡 Unverified | | | -| `OP_LevelUpdate` | 🟢 Verified | | | -| `OP_LFGAppearance` | 🔴 Not-Set | | | -| `OP_LFGCommand` | 🔴 Not-Set | | | -| `OP_LFGGetMatchesRequest` | 🔴 Not-Set | | | -| `OP_LFGGetMatchesResponse` | 🔴 Not-Set | | | -| `OP_LFGResponse` | 🔴 Not-Set | | | -| `OP_LFGuild` | 🔴 Not-Set | | | -| `OP_LFPCommand` | 🔴 Not-Set | | | -| `OP_LFPGetMatchesRequest` | 🔴 Not-Set | | | -| `OP_LFPGetMatchesResponse` | 🔴 Not-Set | | | -| `OP_LinkedReuse` | 🟡 Unverified | | | -| `OP_LoadSpellSet` | 🔴 Not-Set | | | -| `OP_LocInfo` | 🔴 Not-Set | | | -| `OP_LockoutTimerInfo` | 🔴 Not-Set | | | -| `OP_Login` | 🔴 Not-Set | | | -| `OP_LoginAccepted` | 🔴 Not-Set | | | -| `OP_LoginComplete` | 🔴 Not-Set | | | -| `OP_LoginExpansionPacketData` | 🔴 Not-Set | | | -| `OP_LoginUnknown1` | 🔴 Not-Set | | | -| `OP_LoginUnknown2` | 🔴 Not-Set | | | -| `OP_Logout` | 🟡 Unverified | | | -| `OP_LogoutReply` | 🔴 Not-Set | | | -| `OP_LogServer` | 🟢 Verified | Mostly unused values | | -| `OP_LootComplete` | 🟡 Unverified | | | -| `OP_LootItem` | 🟡 Unverified | | | -| `OP_LootRequest` | 🟡 Unverified | | | -| `OP_ManaChange` | 🟡 Unverified | | | -| `OP_ManaUpdate` | 🔴 Not-Set | | | -| `OP_MarkNPC` | 🔴 Not-Set | | | -| `OP_MarkRaidNPC` | 🔴 Not-Set | | | -| `OP_Marquee` | 🟡 Unverified | | | -| `OP_MemorizeSpell` | 🟡 Unverified | | | -| `OP_Mend` | 🟡 Unverified | | | -| `OP_MendHPUpdate` | 🔴 Not-Set | | | -| `OP_MercenaryAssign` | 🔴 Not-Set | | | -| `OP_MercenaryCommand` | 🔴 Not-Set | | | -| `OP_MercenaryDataRequest` | 🔴 Not-Set | | | -| `OP_MercenaryDataResponse` | 🔴 Not-Set | | | -| `OP_MercenaryDataUpdate` | 🔴 Not-Set | | | -| `OP_MercenaryDataUpdateRequest` | 🔴 Not-Set | | | -| `OP_MercenaryDismiss` | 🔴 Not-Set | | | -| `OP_MercenaryHire` | 🔴 Not-Set | | | -| `OP_MercenarySuspendRequest` | 🔴 Not-Set | | | -| `OP_MercenarySuspendResponse` | 🔴 Not-Set | | | -| `OP_MercenaryTimer` | 🔴 Not-Set | | | -| `OP_MercenaryTimerRequest` | 🔴 Not-Set | | | -| `OP_MercenaryUnknown1` | 🔴 Not-Set | | | -| `OP_MercenaryUnsuspendResponse` | 🔴 Not-Set | | | -| `OP_MerchantBulkItems` | 🔴 Not-Set | | | -| `OP_MobEnduranceUpdate` | 🔴 Not-Set | | | -| `OP_MobHealth` | 🟡 Unverified | | | -| `OP_MobManaUpdate` | 🔴 Not-Set | | | -| `OP_MobRename` | 🔴 Not-Set | | | -| `OP_MobUpdate` | 🔴 Not-Set | | | -| `OP_MoneyOnCorpse` | 🟡 Unverified | | | -| `OP_MoneyUpdate` | 🟡 Unverified | | | -| `OP_MOTD` | 🟢 Verified | | | -| `OP_MoveCoin` | 🟡 Unverified | | | -| `OP_MoveDoor` | 🟡 Unverified | | | -| `OP_MoveItem` | 🟡 Unverified | | | -| `OP_MoveMultipleItems` | 🟡 Unverified | | | -| `OP_MoveLogDisregard` | 🔴 Not-Set | | | -| `OP_MoveLogRequest` | 🔴 Not-Set | | | -| `OP_MultiLineMsg` | 🔴 Not-Set | | | -| `OP_NewSpawn` | 🟢 Verified | Deprecated in the client, already handled in emu | | -| `OP_NewTitlesAvailable` | 🔴 Not-Set | | | -| `OP_NewZone` | 🟢 Verified | | | -| `OP_NPCMoveUpdate` | 🔴 Not-Set | | | -| `OP_OnLevelMessage` | 🟡 Unverified | | | -| `OP_OpenContainer` | 🟡 Unverified | | | -| `OP_OpenDiscordMerchant` | 🔴 Not-Set | | | -| `OP_OpenGuildTributeMaster` | 🔴 Not-Set | | | -| `OP_OpenInventory` | 🔴 Not-Set | | | -| `OP_OpenTributeMaster` | 🔴 Not-Set | | | -| `OP_PDeletePetition` | 🔴 Not-Set | | | -| `OP_PetBuffWindow` | 🔴 Not-Set | | | -| `OP_PetCommands` | 🔴 Not-Set | | | -| `OP_PetCommandState` | 🔴 Not-Set | | | -| `OP_PetHoTT` | 🔴 Not-Set | | | -| `OP_Petition` | 🔴 Not-Set | | | -| `OP_PetitionBug` | 🔴 Not-Set | | | -| `OP_PetitionCheckIn` | 🔴 Not-Set | | | -| `OP_PetitionCheckout` | 🔴 Not-Set | | | -| `OP_PetitionCheckout2` | 🔴 Not-Set | | | -| `OP_PetitionDelete` | 🔴 Not-Set | | | -| `OP_PetitionQue` | 🔴 Not-Set | | | -| `OP_PetitionRefresh` | 🔴 Not-Set | | | -| `OP_PetitionResolve` | 🔴 Not-Set | | | -| `OP_PetitionSearch` | 🔴 Not-Set | | | -| `OP_PetitionSearchResults` | 🔴 Not-Set | | | -| `OP_PetitionSearchText` | 🔴 Not-Set | | | -| `OP_PetitionUnCheckout` | 🔴 Not-Set | | | -| `OP_PetitionUpdate` | 🔴 Not-Set | | | -| `OP_PickPocket` | 🟡 Unverified | | | -| `OP_PickZone` | 🔴 Not-Set | | | -| `OP_PickZoneWindow` | 🔴 Not-Set | | | -| `OP_PlayerProfile` | 🟢 Verified | | | -| `OP_PlayerStateAdd` | 🟡 Unverified | | | -| `OP_PlayerStateRemove` | 🟡 Unverified | | | -| `OP_PlayEverquestRequest` | 🔴 Not-Set | | | -| `OP_PlayEverquestResponse` | 🔴 Not-Set | | | -| `OP_PlayMP3` | 🟡 Unverified | | | -| `OP_Poll` | 🔴 Not-Set | | | -| `OP_PollResponse` | 🔴 Not-Set | | | -| `OP_PopupResponse` | 🟡 Unverified | | | -| `OP_PostEnterWorld` | 🟢 Verified | | | -| `OP_PotionBelt` | 🔴 Not-Set | | | -| `OP_PreLogoutReply` | 🔴 Not-Set | | | -| `OP_PurchaseLeadershipAA` | 🔴 Not-Set | | | -| `OP_PVPLeaderBoardDetailsReply` | 🔴 Not-Set | | | -| `OP_PVPLeaderBoardDetailsRequest` | 🔴 Not-Set | | | -| `OP_PVPLeaderBoardReply` | 🔴 Not-Set | | | -| `OP_PVPLeaderBoardRequest` | 🔴 Not-Set | | | -| `OP_PVPStats` | 🔴 Not-Set | | | -| `OP_QueryResponseThing` | 🔴 Not-Set | | | -| `OP_QueryUCSServerStatus` | 🟡 Unverified | | | -| `OP_RaidDelegateAbility` | 🔴 Not-Set | | | -| `OP_RaidClearNPCMarks` | 🔴 Not-Set | | | -| `OP_RaidInvite` | 🔴 Not-Set | | | -| `OP_RaidJoin` | 🔴 Not-Set | | | -| `OP_RaidUpdate` | 🔴 Not-Set | | | -| `OP_RandomNameGenerator` | 🟢 Verified | The client no longer sends this packet (random name generation is done entirely in the client). The client will still accept this packet to set name (emu doesn't do this, but it's always been supported) | | -| `OP_RandomReply` | 🟡 Unverified | | | -| `OP_RandomReq` | 🟡 Unverified | | | -| `OP_ReadBook` | 🟡 Unverified | | | -| `OP_RecipeAutoCombine` | 🟡 Unverified | | | -| `OP_RecipeDetails` | 🟡 Unverified | | | -| `OP_RecipeReply` | 🟡 Unverified | | | -| `OP_RecipesFavorite` | 🟡 Unverified | | | -| `OP_RecipesSearch` | 🟡 Unverified | | | -| `OP_ReclaimCrystals` | 🔴 Not-Set | | | -| `OP_ReloadUI` | 🔴 Not-Set | | | -| `OP_RemoveAllDoors` | 🟡 Unverified | | | -| `OP_RemoveBlockedBuffs` | 🟡 Unverified | | | -| `OP_RemoveNimbusEffect` | 🟡 Unverified | | | -| `OP_RemoveTrap` | 🔴 Not-Set | | | -| `OP_Report` | 🟡 Unverified | | | -| `OP_ReqClientSpawn` | 🟢 Verified | | | -| `OP_ReqNewZone` | 🟢 Verified | Client does not send this (in LS or TOB), but it does receive it. emu does not send it | | -| `OP_RequestClientZoneChange` | 🟢 Verified | parity with RoF2, there's a string that gets passed to teleport at the end that's not known | | -| `OP_RequestDuel` | 🔴 Not-Set | | | -| `OP_RequestGuildTributes` | 🔴 Not-Set | | | -| `OP_RequestKnowledgeBase` | 🔴 Not-Set | | | -| `OP_RequestTitles` | 🔴 Not-Set | | | -| `OP_RespawnWindow` | 🟡 Unverified | | | -| `OP_RespondAA` | 🟡 Unverified | | | -| `OP_RestState` | 🟡 Unverified | | | -| `OP_Rewind` | 🟡 Unverified | | | -| `OP_RezzAnswer` | 🔴 Not-Set | | | -| `OP_RezzComplete` | 🔴 Not-Set | | | -| `OP_RezzRequest` | 🔴 Not-Set | | | -| `OP_Sacrifice` | 🟡 Unverified | | | -| `OP_SafeFallSuccess` | 🟡 Unverified | | | -| `OP_SafePoint` | 🔴 Not-Set | | | -| `OP_Save` | 🟡 Unverified | | | -| `OP_SaveOnZoneReq` | 🟡 Unverified | | | -| `OP_SelectTribute` | 🔴 Not-Set | | | -| `OP_SendAAStats` | 🟡 Unverified | | | -| `OP_SendAATable` | 🟡 Unverified | | | -| `OP_SendCharInfo` | 🟢 Verified | | | -| `OP_SendExpZonein` | 🟡 Unverified | | | -| `OP_SendFindableNPCs` | 🔴 Not-Set | | | -| `OP_SendGuildTributes` | 🔴 Not-Set | | | -| `OP_SendLoginInfo` | 🟢 Verified | | | -| `OP_SendMaxCharacters` | 🟢 Verified | | | -| `OP_SendMembership` | 🟢 Verified | | | -| `OP_SendMembershipDetails` | 🟢 Verified | The struct is correct, will need reversing for actual option keys/values | | -| `OP_SendSystemStats` | 🔴 Not-Set | | | -| `OP_SendTitleList` | 🔴 Not-Set | | | -| `OP_SendTributes` | 🔴 Not-Set | | | -| `OP_SendZonepoints` | 🟡 Unverified | | | -| `OP_SenseHeading` | 🟡 Unverified | | | -| `OP_SenseTraps` | 🟡 Unverified | | | -| `OP_ServerListRequest` | 🔴 Not-Set | | | -| `OP_ServerListResponse` | 🔴 Not-Set | | | -| `OP_SessionReady` | 🔴 Not-Set | | | -| `OP_SetChatServer` | 🔴 Not-Set | | | -| `OP_SetChatServer2` | 🟢 Verified | | | -| `OP_SetFace` | 🔴 Not-Set | | | -| `OP_SetGroupTarget` | 🔴 Not-Set | | | -| `OP_SetGuildMOTD` | 🔴 Not-Set | | | -| `OP_SetGuildRank` | 🔴 Not-Set | | | -| `OP_SetRunMode` | 🟡 Unverified | | | -| `OP_SetServerFilter` | 🟡 Unverified | | | -| `OP_SetStartCity` | 🔴 Not-Set | | | -| `OP_SetTitle` | 🔴 Not-Set | | | -| `OP_SetTitleReply` | 🔴 Not-Set | | | -| `OP_SharedTaskMemberList` | 🔴 Not-Set | | | -| `OP_SharedTaskAddPlayer` | 🔴 Not-Set | | | -| `OP_SharedTaskRemovePlayer` | 🔴 Not-Set | | | -| `OP_SharedTaskMakeLeader` | 🔴 Not-Set | | | -| `OP_SharedTaskMemberInvite` | 🔴 Not-Set | | | -| `OP_SharedTaskInvite` | 🔴 Not-Set | | | -| `OP_SharedTaskInviteResponse` | 🔴 Not-Set | | | -| `OP_SharedTaskAcceptNew` | 🔴 Not-Set | | | -| `OP_SharedTaskMemberChange` | 🔴 Not-Set | | | -| `OP_SharedTaskPlayerList` | 🔴 Not-Set | | | -| `OP_SharedTaskSelectWindow` | 🔴 Not-Set | | | -| `OP_SharedTaskQuit` | 🔴 Not-Set | | | -| `OP_TaskTimers` | 🔴 Not-Set | | | -| `OP_Shielding` | 🔴 Not-Set | | | -| `OP_ShopDelItem` | 🟡 Unverified | | | -| `OP_ShopEnd` | 🟡 Unverified | | | -| `OP_ShopEndConfirm` | 🟡 Unverified | | | -| `OP_ShopItem` | 🔴 Not-Set | | | -| `OP_ShopPlayerBuy` | 🟡 Unverified | | | -| `OP_ShopPlayerSell` | 🟡 Unverified | | | -| `OP_ShopSendParcel` | 🟡 Unverified | | | -| `OP_ShopDeleteParcel` | 🟡 Unverified | | | -| `OP_ShopRespondParcel` | 🔴 Not-Set | | | -| `OP_ShopRetrieveParcel` | 🟡 Unverified | | | -| `OP_ShopParcelIcon` | 🟡 Unverified | | | -| `OP_ShopRequest` | 🟡 Unverified | | | -| `OP_SimpleMessage` | 🟡 Unverified | | | -| `OP_SkillUpdate` | 🟡 Unverified | | | -| `OP_Sneak` | 🟡 Unverified | | | -| `OP_Some3ByteHPUpdate` | 🔴 Not-Set | | | -| `OP_Some6ByteHPUpdate` | 🔴 Not-Set | | | -| `OP_SomeItemPacketMaybe` | 🔴 Not-Set | | | -| `OP_Sound` | 🟡 Unverified | | | -| `OP_SpawnAppearance` | 🟡 Unverified | | | -| `OP_SpawnDoor` | 🟡 Unverified | | | -| `OP_SpawnPositionUpdate` | 🔴 Not-Set | | | -| `OP_SpecialMesg` | 🟡 Unverified | | | -| `OP_SpellEffect` | 🟡 Unverified | | | -| `OP_Split` | 🟡 Unverified | | | -| `OP_Stamina` | 🟢 Verified | These values are 0-32k instead of 0-127 | | -| `OP_Stun` | 🟡 Unverified | | | -| `OP_Surname` | 🔴 Not-Set | | | -| `OP_SwapSpell` | 🟡 Unverified | | | -| `OP_SystemFingerprint` | 🔴 Not-Set | | | -| `OP_TargetBuffs` | 🔴 Not-Set | | | -| `OP_TargetCommand` | 🟡 Unverified | | | -| `OP_TargetHoTT` | 🔴 Not-Set | | | -| `OP_TargetMouse` | 🟡 Unverified | | | -| `OP_TargetReject` | 🔴 Not-Set | | | -| `OP_TaskActivity` | 🔴 Not-Set | | | -| `OP_TaskActivityComplete` | 🔴 Not-Set | | | -| `OP_TaskDescription` | 🔴 Not-Set | | | -| `OP_TaskHistoryReply` | 🔴 Not-Set | | | -| `OP_TaskHistoryRequest` | 🔴 Not-Set | | | -| `OP_TaskRequestTimer` | 🔴 Not-Set | | | -| `OP_TaskSelectWindow` | 🔴 Not-Set | | | -| `OP_Taunt` | 🟡 Unverified | | | -| `OP_TestBuff` | 🔴 Not-Set | | | -| `OP_TGB` | 🔴 Not-Set | | | -| `OP_TimeOfDay` | 🟢 Verified | | | -| `OP_Track` | 🟡 Unverified | | | -| `OP_TrackTarget` | 🟡 Unverified | | | -| `OP_TrackUnknown` | 🟡 Unverified | | | -| `OP_TradeAcceptClick` | 🟡 Unverified | | | -| `OP_TradeBusy` | 🟡 Unverified | | | -| `OP_TradeCoins` | 🟡 Unverified | | | -| `OP_TradeMoneyUpdate` | 🟡 Unverified | | | -| `OP_Trader` | 🔴 Not-Set | | | -| `OP_TraderBulkSend` | 🔴 Not-Set | | | -| `OP_TraderBuy` | 🔴 Not-Set | | | -| `OP_TraderDelItem` | 🔴 Not-Set | | | -| `OP_TradeRequest` | 🟡 Unverified | | | -| `OP_TradeRequestAck` | 🟡 Unverified | | | -| `OP_TraderItemUpdate` | 🔴 Not-Set | | | -| `OP_TraderShop` | 🔴 Not-Set | | | -| `OP_TradeSkillCombine` | 🟡 Unverified | | | -| `OP_TradeSkillRecipeInspect` | 🔴 Not-Set | | | -| `OP_Translocate` | 🟡 Unverified | | | -| `OP_TributeInfo` | 🔴 Not-Set | | | -| `OP_TributeItem` | 🔴 Not-Set | | | -| `OP_TributeMoney` | 🔴 Not-Set | | | -| `OP_TributeNPC` | 🔴 Not-Set | | | -| `OP_TributePointUpdate` | 🔴 Not-Set | | | -| `OP_TributeTimer` | 🔴 Not-Set | | | -| `OP_TributeToggle` | 🔴 Not-Set | | | -| `OP_TributeUpdate` | 🔴 Not-Set | | | -| `OP_Untargetable` | 🟡 Unverified | | | -| `OP_UpdateAA` | 🟡 Unverified | | | -| `OP_UpdateAura` | 🔴 Not-Set | | | -| `OP_UpdateLeadershipAA` | 🔴 Not-Set | | | -| `OP_VetClaimReply` | 🔴 Not-Set | | | -| `OP_VetClaimRequest` | 🔴 Not-Set | | | -| `OP_VetRewardsAvaliable` | 🔴 Not-Set | | | -| `OP_VoiceMacroIn` | 🟡 Unverified | | | -| `OP_VoiceMacroOut` | 🟡 Unverified | | | -| `OP_WeaponEquip1` | 🔴 Not-Set | | | -| `OP_WearChange` | 🟡 Unverified | | | -| `OP_Weather` | 🟡 Unverified | | | -| `OP_Weblink` | 🟡 Unverified | | | -| `OP_WhoAllRequest` | 🟡 Unverified | | | -| `OP_WhoAllResponse` | 🟡 Unverified | | | -| `OP_World_Client_CRC1` | 🟢 Verified | | | -| `OP_World_Client_CRC2` | 🟢 Verified | | | -| `OP_World_Client_CRC3` | 🟢 Verified | | | -| `OP_WorldClientReady` | 🟢 Verified | | | -| `OP_WorldComplete` | 🟢 Verified | | | -| `OP_WorldLogout` | 🔴 Not-Set | | | -| `OP_WorldObjectsSent` | 🟡 Unverified | | | -| `OP_WorldUnknown001` | 🟢 Verified | SetServerTime. emu doesn't currently send it so setting it to verified, but the reference is 0x140292550 | | -| `OP_XTargetAutoAddHaters` | 🔴 Not-Set | | | -| `OP_XTargetOpen` | 🔴 Not-Set | | | -| `OP_XTargetOpenResponse` | 🔴 Not-Set | | | -| `OP_XTargetRequest` | 🔴 Not-Set | | | -| `OP_XTargetResponse` | 🔴 Not-Set | | | -| `OP_YellForHelp` | 🟡 Unverified | | | -| `OP_ZoneChange` | 🟢 Verified | | | -| `OP_ZoneComplete` | 🔴 Not-Set | | | -| `OP_ZoneEntry` | 🟢 Verified | unknown fields in C->S struct are various CRCs, emu doesn't use them | | -| `OP_ZoneGuildList` | 🔴 Not-Set | | | -| `OP_ZoneInUnknown` | 🔴 Not-Set | | | -| `OP_ZonePlayerToBind` | 🟡 Unverified | | | -| `OP_ZoneServerInfo` | 🟢 Verified | | | -| `OP_ZoneServerReady` | 🔴 Not-Set | | | -| `OP_ZoneSpawns` | 🟢 Verified | This is deprecated in the client (and emu never sends it directly) | | -| `OP_ZoneUnavail` | 🟢 Verified | The client discards all content of this packet | | -| `OP_ResetAA` | 🟡 Unverified | | | -| `OP_UnderWorld` | 🟡 Unverified | | | +| Opcode | Status | Notes | Working On | +|:----------------------------------|:---------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------| +| `OP_AAAction` | 🟡 Unverified | | | +| `OP_AAExpUpdate` | 🟡 Unverified | | | +| `OP_AcceptNewTask` | 🔴 Not-Set | | | +| `OP_AckPacket` | 🟢 Verified | | | +| `OP_Action` | 🟡 Unverified | | | +| `OP_Action2` | 🔴 Not-Set | | | +| `OP_AddNimbusEffect` | 🟡 Unverified | | | +| `OP_AdventureData` | 🔴 Not-Set | | | +| `OP_AdventureDetails` | 🔴 Not-Set | | | +| `OP_AdventureFinish` | 🔴 Not-Set | | | +| `OP_AdventureInfo` | 🔴 Not-Set | | | +| `OP_AdventureInfoRequest` | 🔴 Not-Set | | | +| `OP_AdventureLeaderboardReply` | 🔴 Not-Set | | | +| `OP_AdventureLeaderboardRequest` | 🔴 Not-Set | | | +| `OP_AdventureMerchantPurchase` | 🔴 Not-Set | | | +| `OP_AdventureMerchantRequest` | 🔴 Not-Set | | | +| `OP_AdventureMerchantResponse` | 🔴 Not-Set | | | +| `OP_AdventureMerchantSell` | 🔴 Not-Set | | | +| `OP_AdventurePointsUpdate` | 🔴 Not-Set | | | +| `OP_AdventureRequest` | 🔴 Not-Set | | | +| `OP_AdventureStatsReply` | 🔴 Not-Set | | | +| `OP_AdventureStatsRequest` | 🔴 Not-Set | | | +| `OP_AdventureUpdate` | 🔴 Not-Set | | | +| `OP_AggroMeterLockTarget` | 🔴 Not-Set | | | +| `OP_AggroMeterTargetInfo` | 🔴 Not-Set | | | +| `OP_AggroMeterUpdate` | 🔴 Not-Set | | | +| `OP_AltCurrency` | 🔴 Not-Set | | | +| `OP_AltCurrencyMerchantReply` | 🔴 Not-Set | | | +| `OP_AltCurrencyMerchantRequest` | 🔴 Not-Set | | | +| `OP_AltCurrencyPurchase` | 🔴 Not-Set | | | +| `OP_AltCurrencyReclaim` | 🔴 Not-Set | | | +| `OP_AltCurrencySell` | 🔴 Not-Set | | | +| `OP_AltCurrencySellSelection` | 🔴 Not-Set | | | +| `OP_Animation` | 🟡 Unverified | | | +| `OP_AnnoyingZoneUnknown` | 🔴 Not-Set | | | +| `OP_ApplyPoison` | 🟡 Unverified | | | +| `OP_ApproveName` | 🟡 Unverified | This takes multiple parameters from the client, and it can take multiple integer values from the server | | +| `OP_ApproveWorld` | 🔴 Not-Set | | | +| `OP_ApproveZone` | 🔴 Not-Set | | | +| `OP_Assist` | 🟡 Unverified | | | +| `OP_AssistGroup` | 🟡 Unverified | | | +| `OP_AugmentInfo` | 🟡 Unverified | | | +| `OP_AugmentItem` | 🟡 Unverified | | | +| `OP_AutoAttack` | 🟡 Unverified | | | +| `OP_AutoAttack2` | 🟡 Unverified | | | +| `OP_AutoFire` | 🟡 Unverified | | | +| `OP_Bandolier` | 🔴 Not-Set | | | +| `OP_BankerChange` | 🟡 Unverified | | | +| `OP_Barter` | 🔴 Not-Set | | | +| `OP_Bazaar` | 🔴 Not-Set | | | +| `OP_BazaarInspect` | 🔴 Not-Set | | | +| `OP_BazaarSearch` | 🔴 Not-Set | | | +| `OP_BecomeCorpse` | 🔴 Not-Set | | | +| `OP_BecomeTrader` | 🔴 Not-Set | | | +| `OP_Begging` | 🟡 Unverified | | | +| `OP_BeginCast` | 🟡 Unverified | | | +| `OP_Bind_Wound` | 🟡 Unverified | | | +| `OP_BlockedBuffs` | 🟢 Verified | | | +| `OP_BoardBoat` | 🟡 Unverified | | | +| `OP_BookButton` | 🟡 Unverified | | | +| `OP_Buff` | 🟡 Unverified | | | +| `OP_BuffCreate` | 🟡 Unverified | | | +| `OP_BuffRemoveRequest` | 🟡 Unverified | | | +| `OP_Bug` | 🟡 Unverified | | | +| `OP_BuyerItems` | 🔴 Not-Set | | | +| `OP_CameraEffect` | 🟡 Unverified | | | +| `OP_Camp` | 🟡 Unverified | | | +| `OP_CancelSneakHide` | 🟡 Unverified | | | +| `OP_CancelTask` | 🔴 Not-Set | | | +| `OP_CancelTrade` | 🟡 Unverified | | | +| `OP_CashReward` | 🟡 Unverified | | | +| `OP_CastSpell` | 🟡 Unverified | | | +| `OP_ChangeSize` | 🟢 Verified | | | +| `OP_ChannelMessage` | 🟡 Unverified | | | +| `OP_ChangePetName` | 🔴 Not-Set | | | +| `OP_CharacterCreate` | 🟢 Verified | Sends heroic type, can be used for something? | | +| `OP_CharacterCreateRequest` | 🟢 Verified | | | +| `OP_CharInventory` | 🟡 Unverified | | | +| `OP_Charm` | 🟡 Unverified | | | +| `OP_ChatMessage` | 🔴 Not-Set | | | +| `OP_ClearAA` | 🟡 Unverified | | | +| `OP_ClearBlockedBuffs` | 🟢 Verified | | | +| `OP_ClearLeadershipAbilities` | 🔴 Not-Set | | | +| `OP_ClearNPCMarks` | 🔴 Not-Set | | | +| `OP_ClearObject` | 🟡 Unverified | | | +| `OP_ClearSurname` | 🔴 Not-Set | | | +| `OP_ClickDoor` | 🟡 Unverified | | | +| `OP_ClickObject` | 🟡 Unverified | | | +| `OP_ClickObjectAction` | 🟡 Unverified | | | +| `OP_ClientError` | 🔴 Not-Set | | | +| `OP_ClientReady` | 🟡 Unverified | | | +| `OP_ClientTimeStamp` | 🔴 Not-Set | | | +| `OP_ClientUpdate` | 🟡 Unverified | | | +| `OP_CloseContainer` | 🔴 Not-Set | | | +| `OP_CloseTributeMaster` | 🔴 Not-Set | | | +| `OP_ColoredText` | 🟡 Unverified | | | +| `OP_CombatAbility` | 🟡 Unverified | | | +| `OP_Command` | 🔴 Not-Set | | | +| `OP_CompletedTasks` | 🔴 Not-Set | | | +| `OP_ConfirmDelete` | 🟡 Unverified | | | +| `OP_Consent` | 🟡 Unverified | | | +| `OP_ConsentDeny` | 🟡 Unverified | | | +| `OP_ConsentResponse` | 🟡 Unverified | | | +| `OP_Consider` | 🟡 Unverified | | | +| `OP_ConsiderCorpse` | 🟡 Unverified | | | +| `OP_Consume` | 🟡 Unverified | | | +| `OP_ControlBoat` | 🟡 Unverified | | | +| `OP_CorpseDrag` | 🟡 Unverified | | | +| `OP_CorpseDrop` | 🟡 Unverified | | | +| `OP_CrashDump` | 🔴 Not-Set | | | +| `OP_CrystalCountUpdate` | 🔴 Not-Set | | | +| `OP_CrystalCreate` | 🔴 Not-Set | | | +| `OP_CrystalReclaim` | 🔴 Not-Set | | | +| `OP_CustomTitles` | 🔴 Not-Set | | | +| `OP_Damage` | 🟡 Unverified | | | +| `OP_Death` | 🟡 Unverified | | | +| `OP_DelegateAbility` | 🔴 Not-Set | | | +| `OP_DeleteCharacter` | 🟢 Verified | | | +| `OP_DeleteCharge` | 🟡 Unverified | | | +| `OP_DeleteItem` | 🟡 Unverified | | | +| `OP_DeletePetition` | 🔴 Not-Set | | | +| `OP_DeleteSpawn` | 🟡 Unverified | | | +| `OP_DeleteSpell` | 🟡 Unverified | | | +| `OP_DenyResponse` | 🟡 Unverified | | | +| `OP_Disarm` | 🟡 Unverified | | | +| `OP_DisarmTraps` | 🟡 Unverified | | | +| `OP_DisciplineTimer` | 🟡 Unverified | | | +| `OP_DisciplineUpdate` | 🟡 Unverified | | | +| `OP_DiscordMerchantInventory` | 🔴 Not-Set | | | +| `OP_DoGroupLeadershipAbility` | 🔴 Not-Set | | | +| `OP_DuelDecline` | 🔴 Not-Set | | | +| `OP_DuelAccept` | 🔴 Not-Set | | | +| `OP_DumpName` | 🔴 Not-Set | | | +| `OP_Dye` | 🔴 Not-Set | | | +| `OP_DynamicWall` | 🔴 Not-Set | | | +| `OP_DzAddPlayer` | 🔴 Not-Set | | | +| `OP_DzChooseZone` | 🔴 Not-Set | | | +| `OP_DzChooseZoneReply` | 🔴 Not-Set | | | +| `OP_DzCompass` | 🔴 Not-Set | | | +| `OP_DzExpeditionEndsWarning` | 🔴 Not-Set | | | +| `OP_DzExpeditionInfo` | 🔴 Not-Set | | | +| `OP_DzExpeditionInvite` | 🔴 Not-Set | | | +| `OP_DzExpeditionInviteResponse` | 🔴 Not-Set | | | +| `OP_DzExpeditionLockoutTimers` | 🔴 Not-Set | | | +| `OP_DzListTimers` | 🔴 Not-Set | | | +| `OP_DzMakeLeader` | 🔴 Not-Set | | | +| `OP_DzMemberList` | 🔴 Not-Set | | | +| `OP_DzMemberListName` | 🔴 Not-Set | | | +| `OP_DzMemberListStatus` | 🔴 Not-Set | | | +| `OP_DzPlayerList` | 🔴 Not-Set | | | +| `OP_DzQuit` | 🔴 Not-Set | | | +| `OP_DzRemovePlayer` | 🔴 Not-Set | | | +| `OP_DzSetLeaderName` | 🔴 Not-Set | | | +| `OP_DzSwapPlayer` | 🔴 Not-Set | | | +| `OP_Emote` | 🔴 Not-Set | | | +| `OP_EndLootRequest` | 🟡 Unverified | | | +| `OP_EnduranceUpdate` | 🔴 Not-Set | | | +| `OP_EnterChat` | 🔴 Not-Set | | | +| `OP_EnterWorld` | 🟢 Verified | | | +| `OP_EnvDamage` | 🟡 Unverified | | | +| `OP_EvolveItem` | 🔴 Not-Set | | | +| `OP_ExpansionInfo` | 🟢 Verified | Updated from u32 to u64 and works now | | +| `OP_ExpUpdate` | 🟡 Unverified | | | +| `OP_FaceChange` | 🔴 Not-Set | | | +| `OP_Feedback` | 🔴 Not-Set | | | +| `OP_FeignDeath` | 🟡 Unverified | | | +| `OP_FellowshipUpdate` | 🔴 Not-Set | | | +| `OP_FindPersonReply` | 🔴 Not-Set | | | +| `OP_FindPersonRequest` | 🔴 Not-Set | | | +| `OP_FinishTrade` | 🟡 Unverified | | | +| `OP_FinishWindow` | 🟡 Unverified | | | +| `OP_FinishWindow2` | 🟡 Unverified | | | +| `OP_Fishing` | 🟡 Unverified | | | +| `OP_Fling` | 🟡 Unverified | | | +| `OP_FloatListThing` | 🟢 Verified | Movement History. Sent from client, but emu doesn't use it so setting it as varified. Reference is 0x1402FFAD0 | | +| `OP_Forage` | 🟡 Unverified | | | +| `OP_ForceFindPerson` | 🔴 Not-Set | | | +| `OP_FormattedMessage` | 🟡 Unverified | | | +| `OP_FriendsWho` | 🟡 Unverified | | | +| `OP_GetGuildMOTD` | 🔴 Not-Set | | | +| `OP_GetGuildMOTDReply` | 🔴 Not-Set | | | +| `OP_GetGuildsList` | 🔴 Not-Set | | | +| `OP_GiveMoney` | 🔴 Not-Set | | | +| `OP_GMApproval` | 🔴 Not-Set | | | +| `OP_GMBecomeNPC` | 🔴 Not-Set | | | +| `OP_GMDelCorpse` | 🔴 Not-Set | | | +| `OP_GMEmoteZone` | 🔴 Not-Set | | | +| `OP_GMEndTraining` | 🟡 Unverified | | | +| `OP_GMEndTrainingResponse` | 🔴 Not-Set | | | +| `OP_GMFind` | 🔴 Not-Set | | | +| `OP_GMGoto` | 🔴 Not-Set | | | +| `OP_GMHideMe` | 🔴 Not-Set | | | +| `OP_GMKick` | 🔴 Not-Set | | | +| `OP_GMKill` | 🔴 Not-Set | | | +| `OP_GMLastName` | 🔴 Not-Set | | | +| `OP_GMNameChange` | 🔴 Not-Set | | | +| `OP_GMSearchCorpse` | 🔴 Not-Set | | | +| `OP_GMServers` | 🔴 Not-Set | | | +| `OP_GMSummon` | 🔴 Not-Set | | | +| `OP_GMToggle` | 🔴 Not-Set | | | +| `OP_GMTraining` | 🟡 Unverified | | | +| `OP_GMTrainSkill` | 🟡 Unverified | | | +| `OP_GMTrainSkillConfirm` | 🟡 Unverified | | | +| `OP_GMZoneRequest` | 🔴 Not-Set | | | +| `OP_GMZoneRequest2` | 🔴 Not-Set | | | +| `OP_GroundSpawn` | 🟢 Verified | | | +| `OP_GroupAcknowledge` | 🔴 Not-Set | | | +| `OP_GroupCancelInvite` | 🔴 Not-Set | | | +| `OP_GroupDelete` | 🔴 Not-Set | | | +| `OP_GroupDisband` | 🟡 Unverified | | | +| `OP_GroupDisbandOther` | 🔴 Not-Set | | | +| `OP_GroupDisbandYou` | 🔴 Not-Set | | | +| `OP_GroupFollow` | 🔴 Not-Set | | | +| `OP_GroupFollow2` | 🔴 Not-Set | | | +| `OP_GroupInvite` | 🟡 Unverified | | | +| `OP_GroupInvite2` | 🔴 Not-Set | | | +| `OP_GroupLeaderChange` | 🔴 Not-Set | | | +| `OP_GroupLeadershipAAUpdate` | 🔴 Not-Set | | | +| `OP_GroupMakeLeader` | 🔴 Not-Set | | | +| `OP_GroupMentor` | 🔴 Not-Set | | | +| `OP_GroupRoles` | 🔴 Not-Set | | | +| `OP_GroupUpdate` | 🔴 Not-Set | | | +| `OP_GroupUpdateB` | 🔴 Not-Set | | | +| `OP_GroupUpdateLeaderAA` | 🔴 Not-Set | | | +| `OP_GuildBank` | 🔴 Not-Set | | | +| `OP_GuildBankItemList` | 🔴 Not-Set | | | +| `OP_GuildCreate` | 🔴 Not-Set | | | +| `OP_GuildDelete` | 🔴 Not-Set | | | +| `OP_GuildDeleteGuild` | 🔴 Not-Set | | | +| `OP_GuildDemote` | 🔴 Not-Set | | | +| `OP_GuildInvite` | 🔴 Not-Set | | | +| `OP_GuildInviteAccept` | 🔴 Not-Set | | | +| `OP_GuildLeader` | 🔴 Not-Set | | | +| `OP_GuildManageAdd` | 🔴 Not-Set | | | +| `OP_GuildManageBanker` | 🔴 Not-Set | | | +| `OP_GuildManageRemove` | 🔴 Not-Set | | | +| `OP_GuildManageStatus` | 🔴 Not-Set | | | +| `OP_GuildMemberLevelUpdate` | 🔴 Not-Set | | | +| `OP_GuildMemberList` | 🔴 Not-Set | | | +| `OP_GuildMemberUpdate` | 🔴 Not-Set | | | +| `OP_GuildMemberLevel` | 🔴 Not-Set | | | +| `OP_GuildMemberRankAltBanker` | 🔴 Not-Set | | | +| `OP_GuildMemberPublicNote` | 🔴 Not-Set | | | +| `OP_GuildMemberAdd` | 🔴 Not-Set | | | +| `OP_GuildMemberRename` | 🔴 Not-Set | | | +| `OP_GuildMemberDelete` | 🔴 Not-Set | | | +| `OP_GuildMemberDetails` | 🔴 Not-Set | | | +| `OP_GuildRenameGuild` | 🔴 Not-Set | | | +| `OP_GuildMOTD` | 🔴 Not-Set | | | +| `OP_GuildPeace` | 🔴 Not-Set | | | +| `OP_GuildPromote` | 🔴 Not-Set | | | +| `OP_GuildPublicNote` | 🔴 Not-Set | | | +| `OP_GuildRemove` | 🔴 Not-Set | | | +| `OP_GuildSelectTribute` | 🔴 Not-Set | | | +| `OP_GuildModifyBenefits` | 🔴 Not-Set | | | +| `OP_GuildTributeToggleReq` | 🔴 Not-Set | | | +| `OP_GuildTributeToggleReply` | 🔴 Not-Set | | | +| `OP_GuildOptInOut` | 🔴 Not-Set | | | +| `OP_GuildSaveActiveTributes` | 🔴 Not-Set | | | +| `OP_GuildSendActiveTributes` | 🔴 Not-Set | | | +| `OP_GuildTributeFavorAndTimer` | 🔴 Not-Set | | | +| `OP_GuildsList` | 🔴 Not-Set | | | +| `OP_GuildStatus` | 🔴 Not-Set | | | +| `OP_GuildTributeInfo` | 🔴 Not-Set | | | +| `OP_GuildUpdate` | 🔴 Not-Set | | | +| `OP_GuildTributeDonateItem` | 🔴 Not-Set | | | +| `OP_GuildTributeDonatePlat` | 🔴 Not-Set | | | +| `OP_GuildWar` | 🔴 Not-Set | | | +| `OP_Heartbeat` | 🔴 Not-Set | | | +| `OP_Hide` | 🟡 Unverified | | | +| `OP_HideCorpse` | 🟡 Unverified | | | +| `OP_HPUpdate` | 🟡 Unverified | | | +| `OP_Illusion` | 🟡 Unverified | | | +| `OP_IncreaseStats` | 🟡 Unverified | | | +| `OP_InitialHPUpdate` | 🔴 Not-Set | | | +| `OP_InitialMobHealth` | 🔴 Not-Set | | | +| `OP_InspectAnswer` | 🔴 Not-Set | | | +| `OP_InspectBuffs` | 🔴 Not-Set | | | +| `OP_InspectMessageUpdate` | 🔴 Not-Set | | | +| `OP_InspectRequest` | 🔴 Not-Set | | | +| `OP_InstillDoubt` | 🟡 Unverified | | | +| `OP_InterruptCast` | 🟡 Unverified | | | +| `OP_InvokeChangePetName` | 🔴 Not-Set | | | +| `OP_InvokeChangePetNameImmediate` | 🔴 Not-Set | | | +| `OP_InvokeNameChangeImmediate` | 🔴 Not-Set | | | +| `OP_InvokeNameChangeLazy` | 🔴 Not-Set | | | +| `OP_ItemLinkClick` | 🔴 Not-Set | | | +| `OP_ItemLinkResponse` | 🔴 Not-Set | | | +| `OP_ItemLinkText` | 🔴 Not-Set | | | +| `OP_ItemName` | 🔴 Not-Set | | | +| `OP_ItemPacket` | 🟡 Unverified | | | +| `OP_ItemPreview` | 🔴 Not-Set | | | +| `OP_ItemPreviewRequest` | 🔴 Not-Set | | | +| `OP_ItemRecastDelay` | 🟡 Unverified | | | +| `OP_ItemVerifyReply` | 🟡 Unverified | | | +| `OP_ItemVerifyRequest` | 🟡 Unverified | | | +| `OP_ItemViewUnknown` | 🔴 Not-Set | | | +| `OP_Jump` | 🟡 Unverified | | | +| `OP_KeyRing` | 🔴 Not-Set | | | +| `OP_KickPlayers` | 🟡 Unverified | | | +| `OP_KnowledgeBase` | 🔴 Not-Set | | | +| `OP_LDoNButton` | 🔴 Not-Set | | | +| `OP_LDoNDisarmTraps` | 🔴 Not-Set | | | +| `OP_LDoNInspect` | 🔴 Not-Set | | | +| `OP_LDoNOpen` | 🟡 Unverified | | | +| `OP_LDoNPickLock` | 🟡 Unverified | | | +| `OP_LDoNSenseTraps` | 🟡 Unverified | | | +| `OP_LeadershipExpToggle` | 🔴 Not-Set | | | +| `OP_LeadershipExpUpdate` | 🔴 Not-Set | | | +| `OP_LeaveAdventure` | 🔴 Not-Set | | | +| `OP_LeaveBoat` | 🟡 Unverified | | | +| `OP_LevelAppearance` | 🟡 Unverified | | | +| `OP_LevelUpdate` | 🟢 Verified | | | +| `OP_LFGAppearance` | 🔴 Not-Set | | | +| `OP_LFGCommand` | 🔴 Not-Set | | | +| `OP_LFGGetMatchesRequest` | 🔴 Not-Set | | | +| `OP_LFGGetMatchesResponse` | 🔴 Not-Set | | | +| `OP_LFGResponse` | 🔴 Not-Set | | | +| `OP_LFGuild` | 🔴 Not-Set | | | +| `OP_LFPCommand` | 🔴 Not-Set | | | +| `OP_LFPGetMatchesRequest` | 🔴 Not-Set | | | +| `OP_LFPGetMatchesResponse` | 🔴 Not-Set | | | +| `OP_LinkedReuse` | 🟡 Unverified | | | +| `OP_LoadSpellSet` | 🔴 Not-Set | | | +| `OP_LocInfo` | 🔴 Not-Set | | | +| `OP_LockoutTimerInfo` | 🔴 Not-Set | | | +| `OP_Login` | 🔴 Not-Set | | | +| `OP_LoginAccepted` | 🔴 Not-Set | | | +| `OP_LoginComplete` | 🔴 Not-Set | | | +| `OP_LoginExpansionPacketData` | 🔴 Not-Set | | | +| `OP_LoginUnknown1` | 🔴 Not-Set | | | +| `OP_LoginUnknown2` | 🔴 Not-Set | | | +| `OP_Logout` | 🟡 Unverified | | | +| `OP_LogoutReply` | 🔴 Not-Set | | | +| `OP_LogServer` | 🟢 Verified | Mostly unused values | | +| `OP_LootComplete` | 🟡 Unverified | | | +| `OP_LootItem` | 🟡 Unverified | | | +| `OP_LootRequest` | 🟡 Unverified | | | +| `OP_ManaChange` | 🟡 Unverified | | | +| `OP_ManaUpdate` | 🔴 Not-Set | | | +| `OP_MarkNPC` | 🔴 Not-Set | | | +| `OP_MarkRaidNPC` | 🔴 Not-Set | | | +| `OP_Marquee` | 🟡 Unverified | | | +| `OP_MemorizeSpell` | 🟡 Unverified | | | +| `OP_Mend` | 🟡 Unverified | | | +| `OP_MendHPUpdate` | 🔴 Not-Set | | | +| `OP_MercenaryAssign` | 🔴 Not-Set | | | +| `OP_MercenaryCommand` | 🔴 Not-Set | | | +| `OP_MercenaryDataRequest` | 🔴 Not-Set | | | +| `OP_MercenaryDataResponse` | 🔴 Not-Set | | | +| `OP_MercenaryDataUpdate` | 🔴 Not-Set | | | +| `OP_MercenaryDataUpdateRequest` | 🔴 Not-Set | | | +| `OP_MercenaryDismiss` | 🔴 Not-Set | | | +| `OP_MercenaryHire` | 🔴 Not-Set | | | +| `OP_MercenarySuspendRequest` | 🔴 Not-Set | | | +| `OP_MercenarySuspendResponse` | 🔴 Not-Set | | | +| `OP_MercenaryTimer` | 🔴 Not-Set | | | +| `OP_MercenaryTimerRequest` | 🔴 Not-Set | | | +| `OP_MercenaryUnknown1` | 🔴 Not-Set | | | +| `OP_MercenaryUnsuspendResponse` | 🔴 Not-Set | | | +| `OP_MerchantBulkItems` | 🔴 Not-Set | | | +| `OP_MobEnduranceUpdate` | 🔴 Not-Set | | | +| `OP_MobHealth` | 🟡 Unverified | | | +| `OP_MobManaUpdate` | 🔴 Not-Set | | | +| `OP_MobRename` | 🔴 Not-Set | | | +| `OP_MobUpdate` | 🔴 Not-Set | | | +| `OP_MoneyOnCorpse` | 🟡 Unverified | | | +| `OP_MoneyUpdate` | 🟡 Unverified | | | +| `OP_MOTD` | 🟢 Verified | | | +| `OP_MoveCoin` | 🟡 Unverified | | | +| `OP_MoveDoor` | 🟡 Unverified | | | +| `OP_MoveItem` | 🟡 Unverified | | | +| `OP_MoveMultipleItems` | 🟡 Unverified | | | +| `OP_MoveLogDisregard` | 🔴 Not-Set | | | +| `OP_MoveLogRequest` | 🔴 Not-Set | | | +| `OP_MultiLineMsg` | 🔴 Not-Set | | | +| `OP_NewSpawn` | 🟢 Verified | Deprecated in the client, already handled in emu | | +| `OP_NewTitlesAvailable` | 🔴 Not-Set | | | +| `OP_NewZone` | 🟢 Verified | | | +| `OP_NPCMoveUpdate` | 🔴 Not-Set | | | +| `OP_OnLevelMessage` | 🟡 Unverified | | | +| `OP_OpenContainer` | 🟡 Unverified | | | +| `OP_OpenDiscordMerchant` | 🔴 Not-Set | | | +| `OP_OpenGuildTributeMaster` | 🔴 Not-Set | | | +| `OP_OpenInventory` | 🔴 Not-Set | | | +| `OP_OpenTributeMaster` | 🔴 Not-Set | | | +| `OP_PDeletePetition` | 🔴 Not-Set | | | +| `OP_PetBuffWindow` | 🔴 Not-Set | | | +| `OP_PetCommands` | 🔴 Not-Set | | | +| `OP_PetCommandState` | 🔴 Not-Set | | | +| `OP_PetHoTT` | 🔴 Not-Set | | | +| `OP_Petition` | 🔴 Not-Set | | | +| `OP_PetitionBug` | 🔴 Not-Set | | | +| `OP_PetitionCheckIn` | 🔴 Not-Set | | | +| `OP_PetitionCheckout` | 🔴 Not-Set | | | +| `OP_PetitionCheckout2` | 🔴 Not-Set | | | +| `OP_PetitionDelete` | 🔴 Not-Set | | | +| `OP_PetitionQue` | 🔴 Not-Set | | | +| `OP_PetitionRefresh` | 🔴 Not-Set | | | +| `OP_PetitionResolve` | 🔴 Not-Set | | | +| `OP_PetitionSearch` | 🔴 Not-Set | | | +| `OP_PetitionSearchResults` | 🔴 Not-Set | | | +| `OP_PetitionSearchText` | 🔴 Not-Set | | | +| `OP_PetitionUnCheckout` | 🔴 Not-Set | | | +| `OP_PetitionUpdate` | 🔴 Not-Set | | | +| `OP_PickPocket` | 🟡 Unverified | | | +| `OP_PickZone` | 🔴 Not-Set | | | +| `OP_PickZoneWindow` | 🔴 Not-Set | | | +| `OP_PlayerProfile` | 🟢 Verified | | | +| `OP_PlayerStateAdd` | 🟡 Unverified | | | +| `OP_PlayerStateRemove` | 🟡 Unverified | | | +| `OP_PlayEverquestRequest` | 🔴 Not-Set | | | +| `OP_PlayEverquestResponse` | 🔴 Not-Set | | | +| `OP_PlayMP3` | 🟡 Unverified | | | +| `OP_Poll` | 🔴 Not-Set | | | +| `OP_PollResponse` | 🔴 Not-Set | | | +| `OP_PopupResponse` | 🟡 Unverified | | | +| `OP_PostEnterWorld` | 🟢 Verified | | | +| `OP_PotionBelt` | 🔴 Not-Set | | | +| `OP_PreLogoutReply` | 🔴 Not-Set | | | +| `OP_PurchaseLeadershipAA` | 🔴 Not-Set | | | +| `OP_PVPLeaderBoardDetailsReply` | 🔴 Not-Set | | | +| `OP_PVPLeaderBoardDetailsRequest` | 🔴 Not-Set | | | +| `OP_PVPLeaderBoardReply` | 🔴 Not-Set | | | +| `OP_PVPLeaderBoardRequest` | 🔴 Not-Set | | | +| `OP_PVPStats` | 🔴 Not-Set | | | +| `OP_QueryResponseThing` | 🔴 Not-Set | | | +| `OP_QueryUCSServerStatus` | 🟡 Unverified | | | +| `OP_RaidDelegateAbility` | 🔴 Not-Set | | | +| `OP_RaidClearNPCMarks` | 🔴 Not-Set | | | +| `OP_RaidInvite` | 🔴 Not-Set | | | +| `OP_RaidJoin` | 🔴 Not-Set | | | +| `OP_RaidUpdate` | 🔴 Not-Set | | | +| `OP_RandomNameGenerator` | 🟢 Verified | The client no longer sends this packet (random name generation is done entirely in the client). The client will still accept this packet to set name (emu doesn't do this, but it's always been supported) | | +| `OP_RandomReply` | 🟡 Unverified | | | +| `OP_RandomReq` | 🟡 Unverified | | | +| `OP_ReadBook` | 🟡 Unverified | | | +| `OP_RecipeAutoCombine` | 🟡 Unverified | | | +| `OP_RecipeDetails` | 🟡 Unverified | | | +| `OP_RecipeReply` | 🟡 Unverified | | | +| `OP_RecipesFavorite` | 🟡 Unverified | | | +| `OP_RecipesSearch` | 🟡 Unverified | | | +| `OP_ReclaimCrystals` | 🔴 Not-Set | | | +| `OP_ReloadUI` | 🔴 Not-Set | | | +| `OP_RemoveAllDoors` | 🟡 Unverified | | | +| `OP_RemoveBlockedBuffs` | 🟢 Verified | | | +| `OP_RemoveNimbusEffect` | 🟡 Unverified | | | +| `OP_RemoveTrap` | 🔴 Not-Set | | | +| `OP_Report` | 🟡 Unverified | | | +| `OP_ReqClientSpawn` | 🟢 Verified | | | +| `OP_ReqNewZone` | 🟢 Verified | Client does not send this (in LS or TOB), but it does receive it. emu does not send it | | +| `OP_RequestClientZoneChange` | 🟢 Verified | parity with RoF2, there's a string that gets passed to teleport at the end that's not known | | +| `OP_RequestDuel` | 🔴 Not-Set | | | +| `OP_RequestGuildTributes` | 🔴 Not-Set | | | +| `OP_RequestKnowledgeBase` | 🔴 Not-Set | | | +| `OP_RequestTitles` | 🔴 Not-Set | | | +| `OP_RespawnWindow` | 🟡 Unverified | | | +| `OP_RespondAA` | 🟡 Unverified | | | +| `OP_RestState` | 🟡 Unverified | | | +| `OP_Rewind` | 🟡 Unverified | | | +| `OP_RezzAnswer` | 🔴 Not-Set | | | +| `OP_RezzComplete` | 🔴 Not-Set | | | +| `OP_RezzRequest` | 🔴 Not-Set | | | +| `OP_Sacrifice` | 🟡 Unverified | | | +| `OP_SafeFallSuccess` | 🟡 Unverified | | | +| `OP_SafePoint` | 🔴 Not-Set | | | +| `OP_Save` | 🟡 Unverified | | | +| `OP_SaveOnZoneReq` | 🟡 Unverified | | | +| `OP_SelectTribute` | 🔴 Not-Set | | | +| `OP_SendAAStats` | 🟡 Unverified | | | +| `OP_SendAATable` | 🟡 Unverified | | | +| `OP_SendCharInfo` | 🟢 Verified | | | +| `OP_SendExpZonein` | 🟡 Unverified | | | +| `OP_SendFindableNPCs` | 🔴 Not-Set | | | +| `OP_SendGuildTributes` | 🔴 Not-Set | | | +| `OP_SendLoginInfo` | 🟢 Verified | | | +| `OP_SendMaxCharacters` | 🟢 Verified | | | +| `OP_SendMembership` | 🟢 Verified | | | +| `OP_SendMembershipDetails` | 🟢 Verified | The struct is correct, will need reversing for actual option keys/values | | +| `OP_SendSystemStats` | 🔴 Not-Set | | | +| `OP_SendTitleList` | 🔴 Not-Set | | | +| `OP_SendTributes` | 🔴 Not-Set | | | +| `OP_SendZonepoints` | 🟢 Verified | | | +| `OP_SenseHeading` | 🟡 Unverified | | | +| `OP_SenseTraps` | 🟡 Unverified | | | +| `OP_ServerListRequest` | 🔴 Not-Set | | | +| `OP_ServerListResponse` | 🔴 Not-Set | | | +| `OP_SessionReady` | 🔴 Not-Set | | | +| `OP_SetChatServer` | 🔴 Not-Set | | | +| `OP_SetChatServer2` | 🟢 Verified | | | +| `OP_SetFace` | 🔴 Not-Set | | | +| `OP_SetGroupTarget` | 🔴 Not-Set | | | +| `OP_SetGuildMOTD` | 🔴 Not-Set | | | +| `OP_SetGuildRank` | 🔴 Not-Set | | | +| `OP_SetRunMode` | 🟡 Unverified | | | +| `OP_SetServerFilter` | 🟡 Unverified | | | +| `OP_SetStartCity` | 🔴 Not-Set | | | +| `OP_SetTitle` | 🔴 Not-Set | | | +| `OP_SetTitleReply` | 🔴 Not-Set | | | +| `OP_SharedTaskMemberList` | 🔴 Not-Set | | | +| `OP_SharedTaskAddPlayer` | 🔴 Not-Set | | | +| `OP_SharedTaskRemovePlayer` | 🔴 Not-Set | | | +| `OP_SharedTaskMakeLeader` | 🔴 Not-Set | | | +| `OP_SharedTaskMemberInvite` | 🔴 Not-Set | | | +| `OP_SharedTaskInvite` | 🔴 Not-Set | | | +| `OP_SharedTaskInviteResponse` | 🔴 Not-Set | | | +| `OP_SharedTaskAcceptNew` | 🔴 Not-Set | | | +| `OP_SharedTaskMemberChange` | 🔴 Not-Set | | | +| `OP_SharedTaskPlayerList` | 🔴 Not-Set | | | +| `OP_SharedTaskSelectWindow` | 🔴 Not-Set | | | +| `OP_SharedTaskQuit` | 🔴 Not-Set | | | +| `OP_TaskTimers` | 🔴 Not-Set | | | +| `OP_Shielding` | 🔴 Not-Set | | | +| `OP_ShopDelItem` | 🟡 Unverified | | | +| `OP_ShopEnd` | 🟡 Unverified | | | +| `OP_ShopEndConfirm` | 🟡 Unverified | | | +| `OP_ShopItem` | 🔴 Not-Set | | | +| `OP_ShopPlayerBuy` | 🟡 Unverified | | | +| `OP_ShopPlayerSell` | 🟡 Unverified | | | +| `OP_ShopSendParcel` | 🟡 Unverified | | | +| `OP_ShopDeleteParcel` | 🟡 Unverified | | | +| `OP_ShopRespondParcel` | 🔴 Not-Set | | | +| `OP_ShopRetrieveParcel` | 🟡 Unverified | | | +| `OP_ShopParcelIcon` | 🟡 Unverified | | | +| `OP_ShopRequest` | 🟡 Unverified | | | +| `OP_SimpleMessage` | 🟡 Unverified | | | +| `OP_SkillUpdate` | 🟡 Unverified | | | +| `OP_Sneak` | 🟡 Unverified | | | +| `OP_Some3ByteHPUpdate` | 🔴 Not-Set | | | +| `OP_Some6ByteHPUpdate` | 🔴 Not-Set | | | +| `OP_SomeItemPacketMaybe` | 🔴 Not-Set | | | +| `OP_Sound` | 🟡 Unverified | | | +| `OP_SpawnAppearance` | 🟢 Verified | | | +| `OP_SpawnDoor` | 🟢 Verified | | | +| `OP_SpawnPositionUpdate` | 🔴 Not-Set | | | +| `OP_SpecialMesg` | 🟡 Unverified | | | +| `OP_SpellEffect` | 🟡 Unverified | | | +| `OP_Split` | 🟡 Unverified | | | +| `OP_Stamina` | 🟢 Verified | These values are 0-32k instead of 0-127 | | +| `OP_Stun` | 🟡 Unverified | | | +| `OP_Surname` | 🔴 Not-Set | | | +| `OP_SwapSpell` | 🟡 Unverified | | | +| `OP_SystemFingerprint` | 🔴 Not-Set | | | +| `OP_TargetBuffs` | 🔴 Not-Set | | | +| `OP_TargetCommand` | 🟡 Unverified | | | +| `OP_TargetHoTT` | 🔴 Not-Set | | | +| `OP_TargetMouse` | 🟡 Unverified | | | +| `OP_TargetReject` | 🔴 Not-Set | | | +| `OP_TaskActivity` | 🔴 Not-Set | | | +| `OP_TaskActivityComplete` | 🔴 Not-Set | | | +| `OP_TaskDescription` | 🔴 Not-Set | | | +| `OP_TaskHistoryReply` | 🔴 Not-Set | | | +| `OP_TaskHistoryRequest` | 🔴 Not-Set | | | +| `OP_TaskRequestTimer` | 🔴 Not-Set | | | +| `OP_TaskSelectWindow` | 🔴 Not-Set | | | +| `OP_Taunt` | 🟡 Unverified | | | +| `OP_TestBuff` | 🔴 Not-Set | | | +| `OP_TGB` | 🔴 Not-Set | | | +| `OP_TimeOfDay` | 🟢 Verified | | | +| `OP_Track` | 🟡 Unverified | | | +| `OP_TrackTarget` | 🟡 Unverified | | | +| `OP_TrackUnknown` | 🟡 Unverified | | | +| `OP_TradeAcceptClick` | 🟡 Unverified | | | +| `OP_TradeBusy` | 🟡 Unverified | | | +| `OP_TradeCoins` | 🟡 Unverified | | | +| `OP_TradeMoneyUpdate` | 🟡 Unverified | | | +| `OP_Trader` | 🔴 Not-Set | | | +| `OP_TraderBulkSend` | 🔴 Not-Set | | | +| `OP_TraderBuy` | 🔴 Not-Set | | | +| `OP_TraderDelItem` | 🔴 Not-Set | | | +| `OP_TradeRequest` | 🟡 Unverified | | | +| `OP_TradeRequestAck` | 🟡 Unverified | | | +| `OP_TraderItemUpdate` | 🔴 Not-Set | | | +| `OP_TraderShop` | 🔴 Not-Set | | | +| `OP_TradeSkillCombine` | 🟡 Unverified | | | +| `OP_TradeSkillRecipeInspect` | 🔴 Not-Set | | | +| `OP_Translocate` | 🟡 Unverified | | | +| `OP_TributeInfo` | 🔴 Not-Set | | | +| `OP_TributeItem` | 🔴 Not-Set | | | +| `OP_TributeMoney` | 🔴 Not-Set | | | +| `OP_TributeNPC` | 🔴 Not-Set | | | +| `OP_TributePointUpdate` | 🔴 Not-Set | | | +| `OP_TributeTimer` | 🔴 Not-Set | | | +| `OP_TributeToggle` | 🔴 Not-Set | | | +| `OP_TributeUpdate` | 🔴 Not-Set | | | +| `OP_Untargetable` | 🟡 Unverified | | | +| `OP_UpdateAA` | 🟡 Unverified | | | +| `OP_UpdateAura` | 🔴 Not-Set | | | +| `OP_UpdateLeadershipAA` | 🔴 Not-Set | | | +| `OP_VetClaimReply` | 🔴 Not-Set | | | +| `OP_VetClaimRequest` | 🔴 Not-Set | | | +| `OP_VetRewardsAvaliable` | 🔴 Not-Set | | | +| `OP_VoiceMacroIn` | 🟡 Unverified | | | +| `OP_VoiceMacroOut` | 🟡 Unverified | | | +| `OP_WeaponEquip1` | 🔴 Not-Set | | | +| `OP_WearChange` | 🟡 Unverified | | | +| `OP_Weather` | 🟢 Verified | | | +| `OP_Weblink` | 🟡 Unverified | | | +| `OP_WhoAllRequest` | 🟡 Unverified | | | +| `OP_WhoAllResponse` | 🟡 Unverified | | | +| `OP_World_Client_CRC1` | 🟢 Verified | | | +| `OP_World_Client_CRC2` | 🟢 Verified | | | +| `OP_World_Client_CRC3` | 🟢 Verified | | | +| `OP_WorldClientReady` | 🟢 Verified | | | +| `OP_WorldComplete` | 🟢 Verified | | | +| `OP_WorldLogout` | 🔴 Not-Set | | | +| `OP_WorldObjectsSent` | 🟢 Verified | | | +| `OP_WorldUnknown001` | 🟢 Verified | SetServerTime. emu doesn't currently send it so setting it to verified, but the reference is 0x140292550 | | +| `OP_XTargetAutoAddHaters` | 🔴 Not-Set | | | +| `OP_XTargetOpen` | 🔴 Not-Set | | | +| `OP_XTargetOpenResponse` | 🔴 Not-Set | | | +| `OP_XTargetRequest` | 🔴 Not-Set | | | +| `OP_XTargetResponse` | 🔴 Not-Set | | | +| `OP_YellForHelp` | 🟡 Unverified | | | +| `OP_ZoneChange` | 🟢 Verified | | | +| `OP_ZoneComplete` | 🔴 Not-Set | | | +| `OP_ZoneEntry` | 🟢 Verified | unknown fields in C->S struct are various CRCs, emu doesn't use them | | +| `OP_ZoneGuildList` | 🔴 Not-Set | | | +| `OP_ZoneInUnknown` | 🔴 Not-Set | | | +| `OP_ZonePlayerToBind` | 🟡 Unverified | | | +| `OP_ZoneServerInfo` | 🟢 Verified | | | +| `OP_ZoneServerReady` | 🔴 Not-Set | | | +| `OP_ZoneSpawns` | 🟢 Verified | This is deprecated in the client (and emu never sends it directly) | | +| `OP_ZoneUnavail` | 🟢 Verified | The client discards all content of this packet | | +| `OP_ResetAA` | 🟡 Unverified | | | +| `OP_UnderWorld` | 🟡 Unverified | | | From 36ea94625589db7315ce85f921b702a1cbe8c243 Mon Sep 17 00:00:00 2001 From: dannuic Date: Thu, 16 Apr 2026 16:28:08 -0600 Subject: [PATCH 02/17] Attempt to fix opcodes formatting --- common/patches/tob.cpp | 2 +- tob/opcodes.md | 1256 ++++++++++++++++++++-------------------- 2 files changed, 629 insertions(+), 629 deletions(-) diff --git a/common/patches/tob.cpp b/common/patches/tob.cpp index 64ef78c77..cc18ae534 100644 --- a/common/patches/tob.cpp +++ b/common/patches/tob.cpp @@ -2391,7 +2391,7 @@ namespace TOB s32 Desc; */ - buffer.WriteUInt32(emu->id); + buffer.WriteUInt32(emu->id); // Index buffer.WriteUInt8(1); buffer.WriteInt32(emu->upper_hotkey_sid); buffer.WriteInt32(emu->lower_hotkey_sid); diff --git a/tob/opcodes.md b/tob/opcodes.md index d21ad856f..73567da2a 100644 --- a/tob/opcodes.md +++ b/tob/opcodes.md @@ -6,631 +6,631 @@ Below is a status list for the 450 opcodes we currently use on the server for th ### World/Zone Opcode Implementation Status -| Opcode | Status | Notes | Working On | -|:----------------------------------|:---------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------| -| `OP_AAAction` | 🟡 Unverified | | | -| `OP_AAExpUpdate` | 🟡 Unverified | | | -| `OP_AcceptNewTask` | 🔴 Not-Set | | | -| `OP_AckPacket` | 🟢 Verified | | | -| `OP_Action` | 🟡 Unverified | | | -| `OP_Action2` | 🔴 Not-Set | | | -| `OP_AddNimbusEffect` | 🟡 Unverified | | | -| `OP_AdventureData` | 🔴 Not-Set | | | -| `OP_AdventureDetails` | 🔴 Not-Set | | | -| `OP_AdventureFinish` | 🔴 Not-Set | | | -| `OP_AdventureInfo` | 🔴 Not-Set | | | -| `OP_AdventureInfoRequest` | 🔴 Not-Set | | | -| `OP_AdventureLeaderboardReply` | 🔴 Not-Set | | | -| `OP_AdventureLeaderboardRequest` | 🔴 Not-Set | | | -| `OP_AdventureMerchantPurchase` | 🔴 Not-Set | | | -| `OP_AdventureMerchantRequest` | 🔴 Not-Set | | | -| `OP_AdventureMerchantResponse` | 🔴 Not-Set | | | -| `OP_AdventureMerchantSell` | 🔴 Not-Set | | | -| `OP_AdventurePointsUpdate` | 🔴 Not-Set | | | -| `OP_AdventureRequest` | 🔴 Not-Set | | | -| `OP_AdventureStatsReply` | 🔴 Not-Set | | | -| `OP_AdventureStatsRequest` | 🔴 Not-Set | | | -| `OP_AdventureUpdate` | 🔴 Not-Set | | | -| `OP_AggroMeterLockTarget` | 🔴 Not-Set | | | -| `OP_AggroMeterTargetInfo` | 🔴 Not-Set | | | -| `OP_AggroMeterUpdate` | 🔴 Not-Set | | | -| `OP_AltCurrency` | 🔴 Not-Set | | | -| `OP_AltCurrencyMerchantReply` | 🔴 Not-Set | | | -| `OP_AltCurrencyMerchantRequest` | 🔴 Not-Set | | | -| `OP_AltCurrencyPurchase` | 🔴 Not-Set | | | -| `OP_AltCurrencyReclaim` | 🔴 Not-Set | | | -| `OP_AltCurrencySell` | 🔴 Not-Set | | | -| `OP_AltCurrencySellSelection` | 🔴 Not-Set | | | -| `OP_Animation` | 🟡 Unverified | | | -| `OP_AnnoyingZoneUnknown` | 🔴 Not-Set | | | -| `OP_ApplyPoison` | 🟡 Unverified | | | -| `OP_ApproveName` | 🟡 Unverified | This takes multiple parameters from the client, and it can take multiple integer values from the server | | -| `OP_ApproveWorld` | 🔴 Not-Set | | | -| `OP_ApproveZone` | 🔴 Not-Set | | | -| `OP_Assist` | 🟡 Unverified | | | -| `OP_AssistGroup` | 🟡 Unverified | | | -| `OP_AugmentInfo` | 🟡 Unverified | | | -| `OP_AugmentItem` | 🟡 Unverified | | | -| `OP_AutoAttack` | 🟡 Unverified | | | -| `OP_AutoAttack2` | 🟡 Unverified | | | -| `OP_AutoFire` | 🟡 Unverified | | | -| `OP_Bandolier` | 🔴 Not-Set | | | -| `OP_BankerChange` | 🟡 Unverified | | | -| `OP_Barter` | 🔴 Not-Set | | | -| `OP_Bazaar` | 🔴 Not-Set | | | -| `OP_BazaarInspect` | 🔴 Not-Set | | | -| `OP_BazaarSearch` | 🔴 Not-Set | | | -| `OP_BecomeCorpse` | 🔴 Not-Set | | | -| `OP_BecomeTrader` | 🔴 Not-Set | | | -| `OP_Begging` | 🟡 Unverified | | | -| `OP_BeginCast` | 🟡 Unverified | | | -| `OP_Bind_Wound` | 🟡 Unverified | | | -| `OP_BlockedBuffs` | 🟢 Verified | | | -| `OP_BoardBoat` | 🟡 Unverified | | | -| `OP_BookButton` | 🟡 Unverified | | | -| `OP_Buff` | 🟡 Unverified | | | -| `OP_BuffCreate` | 🟡 Unverified | | | -| `OP_BuffRemoveRequest` | 🟡 Unverified | | | -| `OP_Bug` | 🟡 Unverified | | | -| `OP_BuyerItems` | 🔴 Not-Set | | | -| `OP_CameraEffect` | 🟡 Unverified | | | -| `OP_Camp` | 🟡 Unverified | | | -| `OP_CancelSneakHide` | 🟡 Unverified | | | -| `OP_CancelTask` | 🔴 Not-Set | | | -| `OP_CancelTrade` | 🟡 Unverified | | | -| `OP_CashReward` | 🟡 Unverified | | | -| `OP_CastSpell` | 🟡 Unverified | | | -| `OP_ChangeSize` | 🟢 Verified | | | -| `OP_ChannelMessage` | 🟡 Unverified | | | -| `OP_ChangePetName` | 🔴 Not-Set | | | -| `OP_CharacterCreate` | 🟢 Verified | Sends heroic type, can be used for something? | | -| `OP_CharacterCreateRequest` | 🟢 Verified | | | -| `OP_CharInventory` | 🟡 Unverified | | | -| `OP_Charm` | 🟡 Unverified | | | -| `OP_ChatMessage` | 🔴 Not-Set | | | -| `OP_ClearAA` | 🟡 Unverified | | | -| `OP_ClearBlockedBuffs` | 🟢 Verified | | | -| `OP_ClearLeadershipAbilities` | 🔴 Not-Set | | | -| `OP_ClearNPCMarks` | 🔴 Not-Set | | | -| `OP_ClearObject` | 🟡 Unverified | | | -| `OP_ClearSurname` | 🔴 Not-Set | | | -| `OP_ClickDoor` | 🟡 Unverified | | | -| `OP_ClickObject` | 🟡 Unverified | | | -| `OP_ClickObjectAction` | 🟡 Unverified | | | -| `OP_ClientError` | 🔴 Not-Set | | | -| `OP_ClientReady` | 🟡 Unverified | | | -| `OP_ClientTimeStamp` | 🔴 Not-Set | | | -| `OP_ClientUpdate` | 🟡 Unverified | | | -| `OP_CloseContainer` | 🔴 Not-Set | | | -| `OP_CloseTributeMaster` | 🔴 Not-Set | | | -| `OP_ColoredText` | 🟡 Unverified | | | -| `OP_CombatAbility` | 🟡 Unverified | | | -| `OP_Command` | 🔴 Not-Set | | | -| `OP_CompletedTasks` | 🔴 Not-Set | | | -| `OP_ConfirmDelete` | 🟡 Unverified | | | -| `OP_Consent` | 🟡 Unverified | | | -| `OP_ConsentDeny` | 🟡 Unverified | | | -| `OP_ConsentResponse` | 🟡 Unverified | | | -| `OP_Consider` | 🟡 Unverified | | | -| `OP_ConsiderCorpse` | 🟡 Unverified | | | -| `OP_Consume` | 🟡 Unverified | | | -| `OP_ControlBoat` | 🟡 Unverified | | | -| `OP_CorpseDrag` | 🟡 Unverified | | | -| `OP_CorpseDrop` | 🟡 Unverified | | | -| `OP_CrashDump` | 🔴 Not-Set | | | -| `OP_CrystalCountUpdate` | 🔴 Not-Set | | | -| `OP_CrystalCreate` | 🔴 Not-Set | | | -| `OP_CrystalReclaim` | 🔴 Not-Set | | | -| `OP_CustomTitles` | 🔴 Not-Set | | | -| `OP_Damage` | 🟡 Unverified | | | -| `OP_Death` | 🟡 Unverified | | | -| `OP_DelegateAbility` | 🔴 Not-Set | | | -| `OP_DeleteCharacter` | 🟢 Verified | | | -| `OP_DeleteCharge` | 🟡 Unverified | | | -| `OP_DeleteItem` | 🟡 Unverified | | | -| `OP_DeletePetition` | 🔴 Not-Set | | | -| `OP_DeleteSpawn` | 🟡 Unverified | | | -| `OP_DeleteSpell` | 🟡 Unverified | | | -| `OP_DenyResponse` | 🟡 Unverified | | | -| `OP_Disarm` | 🟡 Unverified | | | -| `OP_DisarmTraps` | 🟡 Unverified | | | -| `OP_DisciplineTimer` | 🟡 Unverified | | | -| `OP_DisciplineUpdate` | 🟡 Unverified | | | -| `OP_DiscordMerchantInventory` | 🔴 Not-Set | | | -| `OP_DoGroupLeadershipAbility` | 🔴 Not-Set | | | -| `OP_DuelDecline` | 🔴 Not-Set | | | -| `OP_DuelAccept` | 🔴 Not-Set | | | -| `OP_DumpName` | 🔴 Not-Set | | | -| `OP_Dye` | 🔴 Not-Set | | | -| `OP_DynamicWall` | 🔴 Not-Set | | | -| `OP_DzAddPlayer` | 🔴 Not-Set | | | -| `OP_DzChooseZone` | 🔴 Not-Set | | | -| `OP_DzChooseZoneReply` | 🔴 Not-Set | | | -| `OP_DzCompass` | 🔴 Not-Set | | | -| `OP_DzExpeditionEndsWarning` | 🔴 Not-Set | | | -| `OP_DzExpeditionInfo` | 🔴 Not-Set | | | -| `OP_DzExpeditionInvite` | 🔴 Not-Set | | | -| `OP_DzExpeditionInviteResponse` | 🔴 Not-Set | | | -| `OP_DzExpeditionLockoutTimers` | 🔴 Not-Set | | | -| `OP_DzListTimers` | 🔴 Not-Set | | | -| `OP_DzMakeLeader` | 🔴 Not-Set | | | -| `OP_DzMemberList` | 🔴 Not-Set | | | -| `OP_DzMemberListName` | 🔴 Not-Set | | | -| `OP_DzMemberListStatus` | 🔴 Not-Set | | | -| `OP_DzPlayerList` | 🔴 Not-Set | | | -| `OP_DzQuit` | 🔴 Not-Set | | | -| `OP_DzRemovePlayer` | 🔴 Not-Set | | | -| `OP_DzSetLeaderName` | 🔴 Not-Set | | | -| `OP_DzSwapPlayer` | 🔴 Not-Set | | | -| `OP_Emote` | 🔴 Not-Set | | | -| `OP_EndLootRequest` | 🟡 Unverified | | | -| `OP_EnduranceUpdate` | 🔴 Not-Set | | | -| `OP_EnterChat` | 🔴 Not-Set | | | -| `OP_EnterWorld` | 🟢 Verified | | | -| `OP_EnvDamage` | 🟡 Unverified | | | -| `OP_EvolveItem` | 🔴 Not-Set | | | -| `OP_ExpansionInfo` | 🟢 Verified | Updated from u32 to u64 and works now | | -| `OP_ExpUpdate` | 🟡 Unverified | | | -| `OP_FaceChange` | 🔴 Not-Set | | | -| `OP_Feedback` | 🔴 Not-Set | | | -| `OP_FeignDeath` | 🟡 Unverified | | | -| `OP_FellowshipUpdate` | 🔴 Not-Set | | | -| `OP_FindPersonReply` | 🔴 Not-Set | | | -| `OP_FindPersonRequest` | 🔴 Not-Set | | | -| `OP_FinishTrade` | 🟡 Unverified | | | -| `OP_FinishWindow` | 🟡 Unverified | | | -| `OP_FinishWindow2` | 🟡 Unverified | | | -| `OP_Fishing` | 🟡 Unverified | | | -| `OP_Fling` | 🟡 Unverified | | | -| `OP_FloatListThing` | 🟢 Verified | Movement History. Sent from client, but emu doesn't use it so setting it as varified. Reference is 0x1402FFAD0 | | -| `OP_Forage` | 🟡 Unverified | | | -| `OP_ForceFindPerson` | 🔴 Not-Set | | | -| `OP_FormattedMessage` | 🟡 Unverified | | | -| `OP_FriendsWho` | 🟡 Unverified | | | -| `OP_GetGuildMOTD` | 🔴 Not-Set | | | -| `OP_GetGuildMOTDReply` | 🔴 Not-Set | | | -| `OP_GetGuildsList` | 🔴 Not-Set | | | -| `OP_GiveMoney` | 🔴 Not-Set | | | -| `OP_GMApproval` | 🔴 Not-Set | | | -| `OP_GMBecomeNPC` | 🔴 Not-Set | | | -| `OP_GMDelCorpse` | 🔴 Not-Set | | | -| `OP_GMEmoteZone` | 🔴 Not-Set | | | -| `OP_GMEndTraining` | 🟡 Unverified | | | -| `OP_GMEndTrainingResponse` | 🔴 Not-Set | | | -| `OP_GMFind` | 🔴 Not-Set | | | -| `OP_GMGoto` | 🔴 Not-Set | | | -| `OP_GMHideMe` | 🔴 Not-Set | | | -| `OP_GMKick` | 🔴 Not-Set | | | -| `OP_GMKill` | 🔴 Not-Set | | | -| `OP_GMLastName` | 🔴 Not-Set | | | -| `OP_GMNameChange` | 🔴 Not-Set | | | -| `OP_GMSearchCorpse` | 🔴 Not-Set | | | -| `OP_GMServers` | 🔴 Not-Set | | | -| `OP_GMSummon` | 🔴 Not-Set | | | -| `OP_GMToggle` | 🔴 Not-Set | | | -| `OP_GMTraining` | 🟡 Unverified | | | -| `OP_GMTrainSkill` | 🟡 Unverified | | | -| `OP_GMTrainSkillConfirm` | 🟡 Unverified | | | -| `OP_GMZoneRequest` | 🔴 Not-Set | | | -| `OP_GMZoneRequest2` | 🔴 Not-Set | | | -| `OP_GroundSpawn` | 🟢 Verified | | | -| `OP_GroupAcknowledge` | 🔴 Not-Set | | | -| `OP_GroupCancelInvite` | 🔴 Not-Set | | | -| `OP_GroupDelete` | 🔴 Not-Set | | | -| `OP_GroupDisband` | 🟡 Unverified | | | -| `OP_GroupDisbandOther` | 🔴 Not-Set | | | -| `OP_GroupDisbandYou` | 🔴 Not-Set | | | -| `OP_GroupFollow` | 🔴 Not-Set | | | -| `OP_GroupFollow2` | 🔴 Not-Set | | | -| `OP_GroupInvite` | 🟡 Unverified | | | -| `OP_GroupInvite2` | 🔴 Not-Set | | | -| `OP_GroupLeaderChange` | 🔴 Not-Set | | | -| `OP_GroupLeadershipAAUpdate` | 🔴 Not-Set | | | -| `OP_GroupMakeLeader` | 🔴 Not-Set | | | -| `OP_GroupMentor` | 🔴 Not-Set | | | -| `OP_GroupRoles` | 🔴 Not-Set | | | -| `OP_GroupUpdate` | 🔴 Not-Set | | | -| `OP_GroupUpdateB` | 🔴 Not-Set | | | -| `OP_GroupUpdateLeaderAA` | 🔴 Not-Set | | | -| `OP_GuildBank` | 🔴 Not-Set | | | -| `OP_GuildBankItemList` | 🔴 Not-Set | | | -| `OP_GuildCreate` | 🔴 Not-Set | | | -| `OP_GuildDelete` | 🔴 Not-Set | | | -| `OP_GuildDeleteGuild` | 🔴 Not-Set | | | -| `OP_GuildDemote` | 🔴 Not-Set | | | -| `OP_GuildInvite` | 🔴 Not-Set | | | -| `OP_GuildInviteAccept` | 🔴 Not-Set | | | -| `OP_GuildLeader` | 🔴 Not-Set | | | -| `OP_GuildManageAdd` | 🔴 Not-Set | | | -| `OP_GuildManageBanker` | 🔴 Not-Set | | | -| `OP_GuildManageRemove` | 🔴 Not-Set | | | -| `OP_GuildManageStatus` | 🔴 Not-Set | | | -| `OP_GuildMemberLevelUpdate` | 🔴 Not-Set | | | -| `OP_GuildMemberList` | 🔴 Not-Set | | | -| `OP_GuildMemberUpdate` | 🔴 Not-Set | | | -| `OP_GuildMemberLevel` | 🔴 Not-Set | | | -| `OP_GuildMemberRankAltBanker` | 🔴 Not-Set | | | -| `OP_GuildMemberPublicNote` | 🔴 Not-Set | | | -| `OP_GuildMemberAdd` | 🔴 Not-Set | | | -| `OP_GuildMemberRename` | 🔴 Not-Set | | | -| `OP_GuildMemberDelete` | 🔴 Not-Set | | | -| `OP_GuildMemberDetails` | 🔴 Not-Set | | | -| `OP_GuildRenameGuild` | 🔴 Not-Set | | | -| `OP_GuildMOTD` | 🔴 Not-Set | | | -| `OP_GuildPeace` | 🔴 Not-Set | | | -| `OP_GuildPromote` | 🔴 Not-Set | | | -| `OP_GuildPublicNote` | 🔴 Not-Set | | | -| `OP_GuildRemove` | 🔴 Not-Set | | | -| `OP_GuildSelectTribute` | 🔴 Not-Set | | | -| `OP_GuildModifyBenefits` | 🔴 Not-Set | | | -| `OP_GuildTributeToggleReq` | 🔴 Not-Set | | | -| `OP_GuildTributeToggleReply` | 🔴 Not-Set | | | -| `OP_GuildOptInOut` | 🔴 Not-Set | | | -| `OP_GuildSaveActiveTributes` | 🔴 Not-Set | | | -| `OP_GuildSendActiveTributes` | 🔴 Not-Set | | | -| `OP_GuildTributeFavorAndTimer` | 🔴 Not-Set | | | -| `OP_GuildsList` | 🔴 Not-Set | | | -| `OP_GuildStatus` | 🔴 Not-Set | | | -| `OP_GuildTributeInfo` | 🔴 Not-Set | | | -| `OP_GuildUpdate` | 🔴 Not-Set | | | -| `OP_GuildTributeDonateItem` | 🔴 Not-Set | | | -| `OP_GuildTributeDonatePlat` | 🔴 Not-Set | | | -| `OP_GuildWar` | 🔴 Not-Set | | | -| `OP_Heartbeat` | 🔴 Not-Set | | | -| `OP_Hide` | 🟡 Unverified | | | -| `OP_HideCorpse` | 🟡 Unverified | | | -| `OP_HPUpdate` | 🟡 Unverified | | | -| `OP_Illusion` | 🟡 Unverified | | | -| `OP_IncreaseStats` | 🟡 Unverified | | | -| `OP_InitialHPUpdate` | 🔴 Not-Set | | | -| `OP_InitialMobHealth` | 🔴 Not-Set | | | -| `OP_InspectAnswer` | 🔴 Not-Set | | | -| `OP_InspectBuffs` | 🔴 Not-Set | | | -| `OP_InspectMessageUpdate` | 🔴 Not-Set | | | -| `OP_InspectRequest` | 🔴 Not-Set | | | -| `OP_InstillDoubt` | 🟡 Unverified | | | -| `OP_InterruptCast` | 🟡 Unverified | | | -| `OP_InvokeChangePetName` | 🔴 Not-Set | | | -| `OP_InvokeChangePetNameImmediate` | 🔴 Not-Set | | | -| `OP_InvokeNameChangeImmediate` | 🔴 Not-Set | | | -| `OP_InvokeNameChangeLazy` | 🔴 Not-Set | | | -| `OP_ItemLinkClick` | 🔴 Not-Set | | | -| `OP_ItemLinkResponse` | 🔴 Not-Set | | | -| `OP_ItemLinkText` | 🔴 Not-Set | | | -| `OP_ItemName` | 🔴 Not-Set | | | -| `OP_ItemPacket` | 🟡 Unverified | | | -| `OP_ItemPreview` | 🔴 Not-Set | | | -| `OP_ItemPreviewRequest` | 🔴 Not-Set | | | -| `OP_ItemRecastDelay` | 🟡 Unverified | | | -| `OP_ItemVerifyReply` | 🟡 Unverified | | | -| `OP_ItemVerifyRequest` | 🟡 Unverified | | | -| `OP_ItemViewUnknown` | 🔴 Not-Set | | | -| `OP_Jump` | 🟡 Unverified | | | -| `OP_KeyRing` | 🔴 Not-Set | | | -| `OP_KickPlayers` | 🟡 Unverified | | | -| `OP_KnowledgeBase` | 🔴 Not-Set | | | -| `OP_LDoNButton` | 🔴 Not-Set | | | -| `OP_LDoNDisarmTraps` | 🔴 Not-Set | | | -| `OP_LDoNInspect` | 🔴 Not-Set | | | -| `OP_LDoNOpen` | 🟡 Unverified | | | -| `OP_LDoNPickLock` | 🟡 Unverified | | | -| `OP_LDoNSenseTraps` | 🟡 Unverified | | | -| `OP_LeadershipExpToggle` | 🔴 Not-Set | | | -| `OP_LeadershipExpUpdate` | 🔴 Not-Set | | | -| `OP_LeaveAdventure` | 🔴 Not-Set | | | -| `OP_LeaveBoat` | 🟡 Unverified | | | -| `OP_LevelAppearance` | 🟡 Unverified | | | -| `OP_LevelUpdate` | 🟢 Verified | | | -| `OP_LFGAppearance` | 🔴 Not-Set | | | -| `OP_LFGCommand` | 🔴 Not-Set | | | -| `OP_LFGGetMatchesRequest` | 🔴 Not-Set | | | -| `OP_LFGGetMatchesResponse` | 🔴 Not-Set | | | -| `OP_LFGResponse` | 🔴 Not-Set | | | -| `OP_LFGuild` | 🔴 Not-Set | | | -| `OP_LFPCommand` | 🔴 Not-Set | | | -| `OP_LFPGetMatchesRequest` | 🔴 Not-Set | | | -| `OP_LFPGetMatchesResponse` | 🔴 Not-Set | | | -| `OP_LinkedReuse` | 🟡 Unverified | | | -| `OP_LoadSpellSet` | 🔴 Not-Set | | | -| `OP_LocInfo` | 🔴 Not-Set | | | -| `OP_LockoutTimerInfo` | 🔴 Not-Set | | | -| `OP_Login` | 🔴 Not-Set | | | -| `OP_LoginAccepted` | 🔴 Not-Set | | | -| `OP_LoginComplete` | 🔴 Not-Set | | | -| `OP_LoginExpansionPacketData` | 🔴 Not-Set | | | -| `OP_LoginUnknown1` | 🔴 Not-Set | | | -| `OP_LoginUnknown2` | 🔴 Not-Set | | | -| `OP_Logout` | 🟡 Unverified | | | -| `OP_LogoutReply` | 🔴 Not-Set | | | -| `OP_LogServer` | 🟢 Verified | Mostly unused values | | -| `OP_LootComplete` | 🟡 Unverified | | | -| `OP_LootItem` | 🟡 Unverified | | | -| `OP_LootRequest` | 🟡 Unverified | | | -| `OP_ManaChange` | 🟡 Unverified | | | -| `OP_ManaUpdate` | 🔴 Not-Set | | | -| `OP_MarkNPC` | 🔴 Not-Set | | | -| `OP_MarkRaidNPC` | 🔴 Not-Set | | | -| `OP_Marquee` | 🟡 Unverified | | | -| `OP_MemorizeSpell` | 🟡 Unverified | | | -| `OP_Mend` | 🟡 Unverified | | | -| `OP_MendHPUpdate` | 🔴 Not-Set | | | -| `OP_MercenaryAssign` | 🔴 Not-Set | | | -| `OP_MercenaryCommand` | 🔴 Not-Set | | | -| `OP_MercenaryDataRequest` | 🔴 Not-Set | | | -| `OP_MercenaryDataResponse` | 🔴 Not-Set | | | -| `OP_MercenaryDataUpdate` | 🔴 Not-Set | | | -| `OP_MercenaryDataUpdateRequest` | 🔴 Not-Set | | | -| `OP_MercenaryDismiss` | 🔴 Not-Set | | | -| `OP_MercenaryHire` | 🔴 Not-Set | | | -| `OP_MercenarySuspendRequest` | 🔴 Not-Set | | | -| `OP_MercenarySuspendResponse` | 🔴 Not-Set | | | -| `OP_MercenaryTimer` | 🔴 Not-Set | | | -| `OP_MercenaryTimerRequest` | 🔴 Not-Set | | | -| `OP_MercenaryUnknown1` | 🔴 Not-Set | | | -| `OP_MercenaryUnsuspendResponse` | 🔴 Not-Set | | | -| `OP_MerchantBulkItems` | 🔴 Not-Set | | | -| `OP_MobEnduranceUpdate` | 🔴 Not-Set | | | -| `OP_MobHealth` | 🟡 Unverified | | | -| `OP_MobManaUpdate` | 🔴 Not-Set | | | -| `OP_MobRename` | 🔴 Not-Set | | | -| `OP_MobUpdate` | 🔴 Not-Set | | | -| `OP_MoneyOnCorpse` | 🟡 Unverified | | | -| `OP_MoneyUpdate` | 🟡 Unverified | | | -| `OP_MOTD` | 🟢 Verified | | | -| `OP_MoveCoin` | 🟡 Unverified | | | -| `OP_MoveDoor` | 🟡 Unverified | | | -| `OP_MoveItem` | 🟡 Unverified | | | -| `OP_MoveMultipleItems` | 🟡 Unverified | | | -| `OP_MoveLogDisregard` | 🔴 Not-Set | | | -| `OP_MoveLogRequest` | 🔴 Not-Set | | | -| `OP_MultiLineMsg` | 🔴 Not-Set | | | -| `OP_NewSpawn` | 🟢 Verified | Deprecated in the client, already handled in emu | | -| `OP_NewTitlesAvailable` | 🔴 Not-Set | | | -| `OP_NewZone` | 🟢 Verified | | | -| `OP_NPCMoveUpdate` | 🔴 Not-Set | | | -| `OP_OnLevelMessage` | 🟡 Unverified | | | -| `OP_OpenContainer` | 🟡 Unverified | | | -| `OP_OpenDiscordMerchant` | 🔴 Not-Set | | | -| `OP_OpenGuildTributeMaster` | 🔴 Not-Set | | | -| `OP_OpenInventory` | 🔴 Not-Set | | | -| `OP_OpenTributeMaster` | 🔴 Not-Set | | | -| `OP_PDeletePetition` | 🔴 Not-Set | | | -| `OP_PetBuffWindow` | 🔴 Not-Set | | | -| `OP_PetCommands` | 🔴 Not-Set | | | -| `OP_PetCommandState` | 🔴 Not-Set | | | -| `OP_PetHoTT` | 🔴 Not-Set | | | -| `OP_Petition` | 🔴 Not-Set | | | -| `OP_PetitionBug` | 🔴 Not-Set | | | -| `OP_PetitionCheckIn` | 🔴 Not-Set | | | -| `OP_PetitionCheckout` | 🔴 Not-Set | | | -| `OP_PetitionCheckout2` | 🔴 Not-Set | | | -| `OP_PetitionDelete` | 🔴 Not-Set | | | -| `OP_PetitionQue` | 🔴 Not-Set | | | -| `OP_PetitionRefresh` | 🔴 Not-Set | | | -| `OP_PetitionResolve` | 🔴 Not-Set | | | -| `OP_PetitionSearch` | 🔴 Not-Set | | | -| `OP_PetitionSearchResults` | 🔴 Not-Set | | | -| `OP_PetitionSearchText` | 🔴 Not-Set | | | -| `OP_PetitionUnCheckout` | 🔴 Not-Set | | | -| `OP_PetitionUpdate` | 🔴 Not-Set | | | -| `OP_PickPocket` | 🟡 Unverified | | | -| `OP_PickZone` | 🔴 Not-Set | | | -| `OP_PickZoneWindow` | 🔴 Not-Set | | | -| `OP_PlayerProfile` | 🟢 Verified | | | -| `OP_PlayerStateAdd` | 🟡 Unverified | | | -| `OP_PlayerStateRemove` | 🟡 Unverified | | | -| `OP_PlayEverquestRequest` | 🔴 Not-Set | | | -| `OP_PlayEverquestResponse` | 🔴 Not-Set | | | -| `OP_PlayMP3` | 🟡 Unverified | | | -| `OP_Poll` | 🔴 Not-Set | | | -| `OP_PollResponse` | 🔴 Not-Set | | | -| `OP_PopupResponse` | 🟡 Unverified | | | -| `OP_PostEnterWorld` | 🟢 Verified | | | -| `OP_PotionBelt` | 🔴 Not-Set | | | -| `OP_PreLogoutReply` | 🔴 Not-Set | | | -| `OP_PurchaseLeadershipAA` | 🔴 Not-Set | | | -| `OP_PVPLeaderBoardDetailsReply` | 🔴 Not-Set | | | -| `OP_PVPLeaderBoardDetailsRequest` | 🔴 Not-Set | | | -| `OP_PVPLeaderBoardReply` | 🔴 Not-Set | | | -| `OP_PVPLeaderBoardRequest` | 🔴 Not-Set | | | -| `OP_PVPStats` | 🔴 Not-Set | | | -| `OP_QueryResponseThing` | 🔴 Not-Set | | | -| `OP_QueryUCSServerStatus` | 🟡 Unverified | | | -| `OP_RaidDelegateAbility` | 🔴 Not-Set | | | -| `OP_RaidClearNPCMarks` | 🔴 Not-Set | | | -| `OP_RaidInvite` | 🔴 Not-Set | | | -| `OP_RaidJoin` | 🔴 Not-Set | | | -| `OP_RaidUpdate` | 🔴 Not-Set | | | -| `OP_RandomNameGenerator` | 🟢 Verified | The client no longer sends this packet (random name generation is done entirely in the client). The client will still accept this packet to set name (emu doesn't do this, but it's always been supported) | | -| `OP_RandomReply` | 🟡 Unverified | | | -| `OP_RandomReq` | 🟡 Unverified | | | -| `OP_ReadBook` | 🟡 Unverified | | | -| `OP_RecipeAutoCombine` | 🟡 Unverified | | | -| `OP_RecipeDetails` | 🟡 Unverified | | | -| `OP_RecipeReply` | 🟡 Unverified | | | -| `OP_RecipesFavorite` | 🟡 Unverified | | | -| `OP_RecipesSearch` | 🟡 Unverified | | | -| `OP_ReclaimCrystals` | 🔴 Not-Set | | | -| `OP_ReloadUI` | 🔴 Not-Set | | | -| `OP_RemoveAllDoors` | 🟡 Unverified | | | -| `OP_RemoveBlockedBuffs` | 🟢 Verified | | | -| `OP_RemoveNimbusEffect` | 🟡 Unverified | | | -| `OP_RemoveTrap` | 🔴 Not-Set | | | -| `OP_Report` | 🟡 Unverified | | | -| `OP_ReqClientSpawn` | 🟢 Verified | | | -| `OP_ReqNewZone` | 🟢 Verified | Client does not send this (in LS or TOB), but it does receive it. emu does not send it | | -| `OP_RequestClientZoneChange` | 🟢 Verified | parity with RoF2, there's a string that gets passed to teleport at the end that's not known | | -| `OP_RequestDuel` | 🔴 Not-Set | | | -| `OP_RequestGuildTributes` | 🔴 Not-Set | | | -| `OP_RequestKnowledgeBase` | 🔴 Not-Set | | | -| `OP_RequestTitles` | 🔴 Not-Set | | | -| `OP_RespawnWindow` | 🟡 Unverified | | | -| `OP_RespondAA` | 🟡 Unverified | | | -| `OP_RestState` | 🟡 Unverified | | | -| `OP_Rewind` | 🟡 Unverified | | | -| `OP_RezzAnswer` | 🔴 Not-Set | | | -| `OP_RezzComplete` | 🔴 Not-Set | | | -| `OP_RezzRequest` | 🔴 Not-Set | | | -| `OP_Sacrifice` | 🟡 Unverified | | | -| `OP_SafeFallSuccess` | 🟡 Unverified | | | -| `OP_SafePoint` | 🔴 Not-Set | | | -| `OP_Save` | 🟡 Unverified | | | -| `OP_SaveOnZoneReq` | 🟡 Unverified | | | -| `OP_SelectTribute` | 🔴 Not-Set | | | -| `OP_SendAAStats` | 🟡 Unverified | | | -| `OP_SendAATable` | 🟡 Unverified | | | -| `OP_SendCharInfo` | 🟢 Verified | | | -| `OP_SendExpZonein` | 🟡 Unverified | | | -| `OP_SendFindableNPCs` | 🔴 Not-Set | | | -| `OP_SendGuildTributes` | 🔴 Not-Set | | | -| `OP_SendLoginInfo` | 🟢 Verified | | | -| `OP_SendMaxCharacters` | 🟢 Verified | | | -| `OP_SendMembership` | 🟢 Verified | | | -| `OP_SendMembershipDetails` | 🟢 Verified | The struct is correct, will need reversing for actual option keys/values | | -| `OP_SendSystemStats` | 🔴 Not-Set | | | -| `OP_SendTitleList` | 🔴 Not-Set | | | -| `OP_SendTributes` | 🔴 Not-Set | | | -| `OP_SendZonepoints` | 🟢 Verified | | | -| `OP_SenseHeading` | 🟡 Unverified | | | -| `OP_SenseTraps` | 🟡 Unverified | | | -| `OP_ServerListRequest` | 🔴 Not-Set | | | -| `OP_ServerListResponse` | 🔴 Not-Set | | | -| `OP_SessionReady` | 🔴 Not-Set | | | -| `OP_SetChatServer` | 🔴 Not-Set | | | -| `OP_SetChatServer2` | 🟢 Verified | | | -| `OP_SetFace` | 🔴 Not-Set | | | -| `OP_SetGroupTarget` | 🔴 Not-Set | | | -| `OP_SetGuildMOTD` | 🔴 Not-Set | | | -| `OP_SetGuildRank` | 🔴 Not-Set | | | -| `OP_SetRunMode` | 🟡 Unverified | | | -| `OP_SetServerFilter` | 🟡 Unverified | | | -| `OP_SetStartCity` | 🔴 Not-Set | | | -| `OP_SetTitle` | 🔴 Not-Set | | | -| `OP_SetTitleReply` | 🔴 Not-Set | | | -| `OP_SharedTaskMemberList` | 🔴 Not-Set | | | -| `OP_SharedTaskAddPlayer` | 🔴 Not-Set | | | -| `OP_SharedTaskRemovePlayer` | 🔴 Not-Set | | | -| `OP_SharedTaskMakeLeader` | 🔴 Not-Set | | | -| `OP_SharedTaskMemberInvite` | 🔴 Not-Set | | | -| `OP_SharedTaskInvite` | 🔴 Not-Set | | | -| `OP_SharedTaskInviteResponse` | 🔴 Not-Set | | | -| `OP_SharedTaskAcceptNew` | 🔴 Not-Set | | | -| `OP_SharedTaskMemberChange` | 🔴 Not-Set | | | -| `OP_SharedTaskPlayerList` | 🔴 Not-Set | | | -| `OP_SharedTaskSelectWindow` | 🔴 Not-Set | | | -| `OP_SharedTaskQuit` | 🔴 Not-Set | | | -| `OP_TaskTimers` | 🔴 Not-Set | | | -| `OP_Shielding` | 🔴 Not-Set | | | -| `OP_ShopDelItem` | 🟡 Unverified | | | -| `OP_ShopEnd` | 🟡 Unverified | | | -| `OP_ShopEndConfirm` | 🟡 Unverified | | | -| `OP_ShopItem` | 🔴 Not-Set | | | -| `OP_ShopPlayerBuy` | 🟡 Unverified | | | -| `OP_ShopPlayerSell` | 🟡 Unverified | | | -| `OP_ShopSendParcel` | 🟡 Unverified | | | -| `OP_ShopDeleteParcel` | 🟡 Unverified | | | -| `OP_ShopRespondParcel` | 🔴 Not-Set | | | -| `OP_ShopRetrieveParcel` | 🟡 Unverified | | | -| `OP_ShopParcelIcon` | 🟡 Unverified | | | -| `OP_ShopRequest` | 🟡 Unverified | | | -| `OP_SimpleMessage` | 🟡 Unverified | | | -| `OP_SkillUpdate` | 🟡 Unverified | | | -| `OP_Sneak` | 🟡 Unverified | | | -| `OP_Some3ByteHPUpdate` | 🔴 Not-Set | | | -| `OP_Some6ByteHPUpdate` | 🔴 Not-Set | | | -| `OP_SomeItemPacketMaybe` | 🔴 Not-Set | | | -| `OP_Sound` | 🟡 Unverified | | | -| `OP_SpawnAppearance` | 🟢 Verified | | | -| `OP_SpawnDoor` | 🟢 Verified | | | -| `OP_SpawnPositionUpdate` | 🔴 Not-Set | | | -| `OP_SpecialMesg` | 🟡 Unverified | | | -| `OP_SpellEffect` | 🟡 Unverified | | | -| `OP_Split` | 🟡 Unverified | | | -| `OP_Stamina` | 🟢 Verified | These values are 0-32k instead of 0-127 | | -| `OP_Stun` | 🟡 Unverified | | | -| `OP_Surname` | 🔴 Not-Set | | | -| `OP_SwapSpell` | 🟡 Unverified | | | -| `OP_SystemFingerprint` | 🔴 Not-Set | | | -| `OP_TargetBuffs` | 🔴 Not-Set | | | -| `OP_TargetCommand` | 🟡 Unverified | | | -| `OP_TargetHoTT` | 🔴 Not-Set | | | -| `OP_TargetMouse` | 🟡 Unverified | | | -| `OP_TargetReject` | 🔴 Not-Set | | | -| `OP_TaskActivity` | 🔴 Not-Set | | | -| `OP_TaskActivityComplete` | 🔴 Not-Set | | | -| `OP_TaskDescription` | 🔴 Not-Set | | | -| `OP_TaskHistoryReply` | 🔴 Not-Set | | | -| `OP_TaskHistoryRequest` | 🔴 Not-Set | | | -| `OP_TaskRequestTimer` | 🔴 Not-Set | | | -| `OP_TaskSelectWindow` | 🔴 Not-Set | | | -| `OP_Taunt` | 🟡 Unverified | | | -| `OP_TestBuff` | 🔴 Not-Set | | | -| `OP_TGB` | 🔴 Not-Set | | | -| `OP_TimeOfDay` | 🟢 Verified | | | -| `OP_Track` | 🟡 Unverified | | | -| `OP_TrackTarget` | 🟡 Unverified | | | -| `OP_TrackUnknown` | 🟡 Unverified | | | -| `OP_TradeAcceptClick` | 🟡 Unverified | | | -| `OP_TradeBusy` | 🟡 Unverified | | | -| `OP_TradeCoins` | 🟡 Unverified | | | -| `OP_TradeMoneyUpdate` | 🟡 Unverified | | | -| `OP_Trader` | 🔴 Not-Set | | | -| `OP_TraderBulkSend` | 🔴 Not-Set | | | -| `OP_TraderBuy` | 🔴 Not-Set | | | -| `OP_TraderDelItem` | 🔴 Not-Set | | | -| `OP_TradeRequest` | 🟡 Unverified | | | -| `OP_TradeRequestAck` | 🟡 Unverified | | | -| `OP_TraderItemUpdate` | 🔴 Not-Set | | | -| `OP_TraderShop` | 🔴 Not-Set | | | -| `OP_TradeSkillCombine` | 🟡 Unverified | | | -| `OP_TradeSkillRecipeInspect` | 🔴 Not-Set | | | -| `OP_Translocate` | 🟡 Unverified | | | -| `OP_TributeInfo` | 🔴 Not-Set | | | -| `OP_TributeItem` | 🔴 Not-Set | | | -| `OP_TributeMoney` | 🔴 Not-Set | | | -| `OP_TributeNPC` | 🔴 Not-Set | | | -| `OP_TributePointUpdate` | 🔴 Not-Set | | | -| `OP_TributeTimer` | 🔴 Not-Set | | | -| `OP_TributeToggle` | 🔴 Not-Set | | | -| `OP_TributeUpdate` | 🔴 Not-Set | | | -| `OP_Untargetable` | 🟡 Unverified | | | -| `OP_UpdateAA` | 🟡 Unverified | | | -| `OP_UpdateAura` | 🔴 Not-Set | | | -| `OP_UpdateLeadershipAA` | 🔴 Not-Set | | | -| `OP_VetClaimReply` | 🔴 Not-Set | | | -| `OP_VetClaimRequest` | 🔴 Not-Set | | | -| `OP_VetRewardsAvaliable` | 🔴 Not-Set | | | -| `OP_VoiceMacroIn` | 🟡 Unverified | | | -| `OP_VoiceMacroOut` | 🟡 Unverified | | | -| `OP_WeaponEquip1` | 🔴 Not-Set | | | -| `OP_WearChange` | 🟡 Unverified | | | -| `OP_Weather` | 🟢 Verified | | | -| `OP_Weblink` | 🟡 Unverified | | | -| `OP_WhoAllRequest` | 🟡 Unverified | | | -| `OP_WhoAllResponse` | 🟡 Unverified | | | -| `OP_World_Client_CRC1` | 🟢 Verified | | | -| `OP_World_Client_CRC2` | 🟢 Verified | | | -| `OP_World_Client_CRC3` | 🟢 Verified | | | -| `OP_WorldClientReady` | 🟢 Verified | | | -| `OP_WorldComplete` | 🟢 Verified | | | -| `OP_WorldLogout` | 🔴 Not-Set | | | -| `OP_WorldObjectsSent` | 🟢 Verified | | | -| `OP_WorldUnknown001` | 🟢 Verified | SetServerTime. emu doesn't currently send it so setting it to verified, but the reference is 0x140292550 | | -| `OP_XTargetAutoAddHaters` | 🔴 Not-Set | | | -| `OP_XTargetOpen` | 🔴 Not-Set | | | -| `OP_XTargetOpenResponse` | 🔴 Not-Set | | | -| `OP_XTargetRequest` | 🔴 Not-Set | | | -| `OP_XTargetResponse` | 🔴 Not-Set | | | -| `OP_YellForHelp` | 🟡 Unverified | | | -| `OP_ZoneChange` | 🟢 Verified | | | -| `OP_ZoneComplete` | 🔴 Not-Set | | | -| `OP_ZoneEntry` | 🟢 Verified | unknown fields in C->S struct are various CRCs, emu doesn't use them | | -| `OP_ZoneGuildList` | 🔴 Not-Set | | | -| `OP_ZoneInUnknown` | 🔴 Not-Set | | | -| `OP_ZonePlayerToBind` | 🟡 Unverified | | | -| `OP_ZoneServerInfo` | 🟢 Verified | | | -| `OP_ZoneServerReady` | 🔴 Not-Set | | | -| `OP_ZoneSpawns` | 🟢 Verified | This is deprecated in the client (and emu never sends it directly) | | -| `OP_ZoneUnavail` | 🟢 Verified | The client discards all content of this packet | | -| `OP_ResetAA` | 🟡 Unverified | | | -| `OP_UnderWorld` | 🟡 Unverified | | | +| Opcode | Status | Notes | Working On | +|:----------------------------------|:--------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------| +| `OP_AAAction` | 🟡 Unverified | | | +| `OP_AAExpUpdate` | 🟡 Unverified | | | +| `OP_AcceptNewTask` | 🔴 Not-Set | | | +| `OP_AckPacket` | 🟢 Verified | | | +| `OP_Action` | 🟡 Unverified | | | +| `OP_Action2` | 🔴 Not-Set | | | +| `OP_AddNimbusEffect` | 🟡 Unverified | | | +| `OP_AdventureData` | 🔴 Not-Set | | | +| `OP_AdventureDetails` | 🔴 Not-Set | | | +| `OP_AdventureFinish` | 🔴 Not-Set | | | +| `OP_AdventureInfo` | 🔴 Not-Set | | | +| `OP_AdventureInfoRequest` | 🔴 Not-Set | | | +| `OP_AdventureLeaderboardReply` | 🔴 Not-Set | | | +| `OP_AdventureLeaderboardRequest` | 🔴 Not-Set | | | +| `OP_AdventureMerchantPurchase` | 🔴 Not-Set | | | +| `OP_AdventureMerchantRequest` | 🔴 Not-Set | | | +| `OP_AdventureMerchantResponse` | 🔴 Not-Set | | | +| `OP_AdventureMerchantSell` | 🔴 Not-Set | | | +| `OP_AdventurePointsUpdate` | 🔴 Not-Set | | | +| `OP_AdventureRequest` | 🔴 Not-Set | | | +| `OP_AdventureStatsReply` | 🔴 Not-Set | | | +| `OP_AdventureStatsRequest` | 🔴 Not-Set | | | +| `OP_AdventureUpdate` | 🔴 Not-Set | | | +| `OP_AggroMeterLockTarget` | 🔴 Not-Set | | | +| `OP_AggroMeterTargetInfo` | 🔴 Not-Set | | | +| `OP_AggroMeterUpdate` | 🔴 Not-Set | | | +| `OP_AltCurrency` | 🔴 Not-Set | | | +| `OP_AltCurrencyMerchantReply` | 🔴 Not-Set | | | +| `OP_AltCurrencyMerchantRequest` | 🔴 Not-Set | | | +| `OP_AltCurrencyPurchase` | 🔴 Not-Set | | | +| `OP_AltCurrencyReclaim` | 🔴 Not-Set | | | +| `OP_AltCurrencySell` | 🔴 Not-Set | | | +| `OP_AltCurrencySellSelection` | 🔴 Not-Set | | | +| `OP_Animation` | 🟡 Unverified | | | +| `OP_AnnoyingZoneUnknown` | 🔴 Not-Set | | | +| `OP_ApplyPoison` | 🟡 Unverified | | | +| `OP_ApproveName` | 🟡 Unverified | This takes multiple parameters from the client, and it can take multiple integer values from the server | | +| `OP_ApproveWorld` | 🔴 Not-Set | | | +| `OP_ApproveZone` | 🔴 Not-Set | | | +| `OP_Assist` | 🟡 Unverified | | | +| `OP_AssistGroup` | 🟡 Unverified | | | +| `OP_AugmentInfo` | 🟡 Unverified | | | +| `OP_AugmentItem` | 🟡 Unverified | | | +| `OP_AutoAttack` | 🟡 Unverified | | | +| `OP_AutoAttack2` | 🟡 Unverified | | | +| `OP_AutoFire` | 🟡 Unverified | | | +| `OP_Bandolier` | 🔴 Not-Set | | | +| `OP_BankerChange` | 🟡 Unverified | | | +| `OP_Barter` | 🔴 Not-Set | | | +| `OP_Bazaar` | 🔴 Not-Set | | | +| `OP_BazaarInspect` | 🔴 Not-Set | | | +| `OP_BazaarSearch` | 🔴 Not-Set | | | +| `OP_BecomeCorpse` | 🔴 Not-Set | | | +| `OP_BecomeTrader` | 🔴 Not-Set | | | +| `OP_Begging` | 🟡 Unverified | | | +| `OP_BeginCast` | 🟡 Unverified | | | +| `OP_Bind_Wound` | 🟡 Unverified | | | +| `OP_BlockedBuffs` | 🟢 Verified | | | +| `OP_BoardBoat` | 🟡 Unverified | | | +| `OP_BookButton` | 🟡 Unverified | | | +| `OP_Buff` | 🟡 Unverified | | | +| `OP_BuffCreate` | 🟡 Unverified | | | +| `OP_BuffRemoveRequest` | 🟡 Unverified | | | +| `OP_Bug` | 🟡 Unverified | | | +| `OP_BuyerItems` | 🔴 Not-Set | | | +| `OP_CameraEffect` | 🟡 Unverified | | | +| `OP_Camp` | 🟡 Unverified | | | +| `OP_CancelSneakHide` | 🟡 Unverified | | | +| `OP_CancelTask` | 🔴 Not-Set | | | +| `OP_CancelTrade` | 🟡 Unverified | | | +| `OP_CashReward` | 🟡 Unverified | | | +| `OP_CastSpell` | 🟡 Unverified | | | +| `OP_ChangeSize` | 🟢 Verified | | | +| `OP_ChannelMessage` | 🟡 Unverified | | | +| `OP_ChangePetName` | 🔴 Not-Set | | | +| `OP_CharacterCreate` | 🟢 Verified | Sends heroic type, can be used for something? | | +| `OP_CharacterCreateRequest` | 🟢 Verified | | | +| `OP_CharInventory` | 🟡 Unverified | | | +| `OP_Charm` | 🟡 Unverified | | | +| `OP_ChatMessage` | 🔴 Not-Set | | | +| `OP_ClearAA` | 🟢 Verified | | | +| `OP_ClearBlockedBuffs` | 🟢 Verified | | | +| `OP_ClearLeadershipAbilities` | 🔴 Not-Set | | | +| `OP_ClearNPCMarks` | 🔴 Not-Set | | | +| `OP_ClearObject` | 🟡 Unverified | | | +| `OP_ClearSurname` | 🔴 Not-Set | | | +| `OP_ClickDoor` | 🟡 Unverified | | | +| `OP_ClickObject` | 🟡 Unverified | | | +| `OP_ClickObjectAction` | 🟡 Unverified | | | +| `OP_ClientError` | 🔴 Not-Set | | | +| `OP_ClientReady` | 🟡 Unverified | | | +| `OP_ClientTimeStamp` | 🔴 Not-Set | | | +| `OP_ClientUpdate` | 🟡 Unverified | | | +| `OP_CloseContainer` | 🔴 Not-Set | | | +| `OP_CloseTributeMaster` | 🔴 Not-Set | | | +| `OP_ColoredText` | 🟡 Unverified | | | +| `OP_CombatAbility` | 🟡 Unverified | | | +| `OP_Command` | 🔴 Not-Set | | | +| `OP_CompletedTasks` | 🔴 Not-Set | | | +| `OP_ConfirmDelete` | 🟡 Unverified | | | +| `OP_Consent` | 🟡 Unverified | | | +| `OP_ConsentDeny` | 🟡 Unverified | | | +| `OP_ConsentResponse` | 🟡 Unverified | | | +| `OP_Consider` | 🟡 Unverified | | | +| `OP_ConsiderCorpse` | 🟡 Unverified | | | +| `OP_Consume` | 🟡 Unverified | | | +| `OP_ControlBoat` | 🟡 Unverified | | | +| `OP_CorpseDrag` | 🟡 Unverified | | | +| `OP_CorpseDrop` | 🟡 Unverified | | | +| `OP_CrashDump` | 🔴 Not-Set | | | +| `OP_CrystalCountUpdate` | 🔴 Not-Set | | | +| `OP_CrystalCreate` | 🔴 Not-Set | | | +| `OP_CrystalReclaim` | 🔴 Not-Set | | | +| `OP_CustomTitles` | 🔴 Not-Set | | | +| `OP_Damage` | 🟡 Unverified | | | +| `OP_Death` | 🟡 Unverified | | | +| `OP_DelegateAbility` | 🔴 Not-Set | | | +| `OP_DeleteCharacter` | 🟢 Verified | | | +| `OP_DeleteCharge` | 🟡 Unverified | | | +| `OP_DeleteItem` | 🟡 Unverified | | | +| `OP_DeletePetition` | 🔴 Not-Set | | | +| `OP_DeleteSpawn` | 🟡 Unverified | | | +| `OP_DeleteSpell` | 🟡 Unverified | | | +| `OP_DenyResponse` | 🟡 Unverified | | | +| `OP_Disarm` | 🟡 Unverified | | | +| `OP_DisarmTraps` | 🟡 Unverified | | | +| `OP_DisciplineTimer` | 🟡 Unverified | | | +| `OP_DisciplineUpdate` | 🟡 Unverified | | | +| `OP_DiscordMerchantInventory` | 🔴 Not-Set | | | +| `OP_DoGroupLeadershipAbility` | 🔴 Not-Set | | | +| `OP_DuelDecline` | 🔴 Not-Set | | | +| `OP_DuelAccept` | 🔴 Not-Set | | | +| `OP_DumpName` | 🔴 Not-Set | | | +| `OP_Dye` | 🔴 Not-Set | | | +| `OP_DynamicWall` | 🔴 Not-Set | | | +| `OP_DzAddPlayer` | 🔴 Not-Set | | | +| `OP_DzChooseZone` | 🔴 Not-Set | | | +| `OP_DzChooseZoneReply` | 🔴 Not-Set | | | +| `OP_DzCompass` | 🔴 Not-Set | | | +| `OP_DzExpeditionEndsWarning` | 🔴 Not-Set | | | +| `OP_DzExpeditionInfo` | 🔴 Not-Set | | | +| `OP_DzExpeditionInvite` | 🔴 Not-Set | | | +| `OP_DzExpeditionInviteResponse` | 🔴 Not-Set | | | +| `OP_DzExpeditionLockoutTimers` | 🔴 Not-Set | | | +| `OP_DzListTimers` | 🔴 Not-Set | | | +| `OP_DzMakeLeader` | 🔴 Not-Set | | | +| `OP_DzMemberList` | 🔴 Not-Set | | | +| `OP_DzMemberListName` | 🔴 Not-Set | | | +| `OP_DzMemberListStatus` | 🔴 Not-Set | | | +| `OP_DzPlayerList` | 🔴 Not-Set | | | +| `OP_DzQuit` | 🔴 Not-Set | | | +| `OP_DzRemovePlayer` | 🔴 Not-Set | | | +| `OP_DzSetLeaderName` | 🔴 Not-Set | | | +| `OP_DzSwapPlayer` | 🔴 Not-Set | | | +| `OP_Emote` | 🔴 Not-Set | | | +| `OP_EndLootRequest` | 🟡 Unverified | | | +| `OP_EnduranceUpdate` | 🔴 Not-Set | | | +| `OP_EnterChat` | 🔴 Not-Set | | | +| `OP_EnterWorld` | 🟢 Verified | | | +| `OP_EnvDamage` | 🟡 Unverified | | | +| `OP_EvolveItem` | 🔴 Not-Set | | | +| `OP_ExpansionInfo` | 🟢 Verified | Updated from u32 to u64 and works now | | +| `OP_ExpUpdate` | 🟡 Unverified | | | +| `OP_FaceChange` | 🔴 Not-Set | | | +| `OP_Feedback` | 🔴 Not-Set | | | +| `OP_FeignDeath` | 🟡 Unverified | | | +| `OP_FellowshipUpdate` | 🔴 Not-Set | | | +| `OP_FindPersonReply` | 🔴 Not-Set | | | +| `OP_FindPersonRequest` | 🔴 Not-Set | | | +| `OP_FinishTrade` | 🟡 Unverified | | | +| `OP_FinishWindow` | 🟡 Unverified | | | +| `OP_FinishWindow2` | 🟡 Unverified | | | +| `OP_Fishing` | 🟡 Unverified | | | +| `OP_Fling` | 🟡 Unverified | | | +| `OP_FloatListThing` | 🟢 Verified | Movement History. Sent from client, but emu doesn't use it so setting it as varified. Reference is 0x1402FFAD0 | | +| `OP_Forage` | 🟡 Unverified | | | +| `OP_ForceFindPerson` | 🔴 Not-Set | | | +| `OP_FormattedMessage` | 🟡 Unverified | | | +| `OP_FriendsWho` | 🟡 Unverified | | | +| `OP_GetGuildMOTD` | 🔴 Not-Set | | | +| `OP_GetGuildMOTDReply` | 🔴 Not-Set | | | +| `OP_GetGuildsList` | 🔴 Not-Set | | | +| `OP_GiveMoney` | 🔴 Not-Set | | | +| `OP_GMApproval` | 🔴 Not-Set | | | +| `OP_GMBecomeNPC` | 🔴 Not-Set | | | +| `OP_GMDelCorpse` | 🔴 Not-Set | | | +| `OP_GMEmoteZone` | 🔴 Not-Set | | | +| `OP_GMEndTraining` | 🟡 Unverified | | | +| `OP_GMEndTrainingResponse` | 🔴 Not-Set | | | +| `OP_GMFind` | 🔴 Not-Set | | | +| `OP_GMGoto` | 🔴 Not-Set | | | +| `OP_GMHideMe` | 🔴 Not-Set | | | +| `OP_GMKick` | 🔴 Not-Set | | | +| `OP_GMKill` | 🔴 Not-Set | | | +| `OP_GMLastName` | 🔴 Not-Set | | | +| `OP_GMNameChange` | 🔴 Not-Set | | | +| `OP_GMSearchCorpse` | 🔴 Not-Set | | | +| `OP_GMServers` | 🔴 Not-Set | | | +| `OP_GMSummon` | 🔴 Not-Set | | | +| `OP_GMToggle` | 🔴 Not-Set | | | +| `OP_GMTraining` | 🟡 Unverified | | | +| `OP_GMTrainSkill` | 🟡 Unverified | | | +| `OP_GMTrainSkillConfirm` | 🟡 Unverified | | | +| `OP_GMZoneRequest` | 🔴 Not-Set | | | +| `OP_GMZoneRequest2` | 🔴 Not-Set | | | +| `OP_GroundSpawn` | 🟢 Verified | | | +| `OP_GroupAcknowledge` | 🔴 Not-Set | | | +| `OP_GroupCancelInvite` | 🔴 Not-Set | | | +| `OP_GroupDelete` | 🔴 Not-Set | | | +| `OP_GroupDisband` | 🟡 Unverified | | | +| `OP_GroupDisbandOther` | 🔴 Not-Set | | | +| `OP_GroupDisbandYou` | 🔴 Not-Set | | | +| `OP_GroupFollow` | 🔴 Not-Set | | | +| `OP_GroupFollow2` | 🔴 Not-Set | | | +| `OP_GroupInvite` | 🟡 Unverified | | | +| `OP_GroupInvite2` | 🔴 Not-Set | | | +| `OP_GroupLeaderChange` | 🔴 Not-Set | | | +| `OP_GroupLeadershipAAUpdate` | 🔴 Not-Set | | | +| `OP_GroupMakeLeader` | 🔴 Not-Set | | | +| `OP_GroupMentor` | 🔴 Not-Set | | | +| `OP_GroupRoles` | 🔴 Not-Set | | | +| `OP_GroupUpdate` | 🔴 Not-Set | | | +| `OP_GroupUpdateB` | 🔴 Not-Set | | | +| `OP_GroupUpdateLeaderAA` | 🔴 Not-Set | | | +| `OP_GuildBank` | 🔴 Not-Set | | | +| `OP_GuildBankItemList` | 🔴 Not-Set | | | +| `OP_GuildCreate` | 🔴 Not-Set | | | +| `OP_GuildDelete` | 🔴 Not-Set | | | +| `OP_GuildDeleteGuild` | 🔴 Not-Set | | | +| `OP_GuildDemote` | 🔴 Not-Set | | | +| `OP_GuildInvite` | 🔴 Not-Set | | | +| `OP_GuildInviteAccept` | 🔴 Not-Set | | | +| `OP_GuildLeader` | 🔴 Not-Set | | | +| `OP_GuildManageAdd` | 🔴 Not-Set | | | +| `OP_GuildManageBanker` | 🔴 Not-Set | | | +| `OP_GuildManageRemove` | 🔴 Not-Set | | | +| `OP_GuildManageStatus` | 🔴 Not-Set | | | +| `OP_GuildMemberLevelUpdate` | 🔴 Not-Set | | | +| `OP_GuildMemberList` | 🔴 Not-Set | | | +| `OP_GuildMemberUpdate` | 🔴 Not-Set | | | +| `OP_GuildMemberLevel` | 🔴 Not-Set | | | +| `OP_GuildMemberRankAltBanker` | 🔴 Not-Set | | | +| `OP_GuildMemberPublicNote` | 🔴 Not-Set | | | +| `OP_GuildMemberAdd` | 🔴 Not-Set | | | +| `OP_GuildMemberRename` | 🔴 Not-Set | | | +| `OP_GuildMemberDelete` | 🔴 Not-Set | | | +| `OP_GuildMemberDetails` | 🔴 Not-Set | | | +| `OP_GuildRenameGuild` | 🔴 Not-Set | | | +| `OP_GuildMOTD` | 🔴 Not-Set | | | +| `OP_GuildPeace` | 🔴 Not-Set | | | +| `OP_GuildPromote` | 🔴 Not-Set | | | +| `OP_GuildPublicNote` | 🔴 Not-Set | | | +| `OP_GuildRemove` | 🔴 Not-Set | | | +| `OP_GuildSelectTribute` | 🔴 Not-Set | | | +| `OP_GuildModifyBenefits` | 🔴 Not-Set | | | +| `OP_GuildTributeToggleReq` | 🔴 Not-Set | | | +| `OP_GuildTributeToggleReply` | 🔴 Not-Set | | | +| `OP_GuildOptInOut` | 🔴 Not-Set | | | +| `OP_GuildSaveActiveTributes` | 🔴 Not-Set | | | +| `OP_GuildSendActiveTributes` | 🔴 Not-Set | | | +| `OP_GuildTributeFavorAndTimer` | 🔴 Not-Set | | | +| `OP_GuildsList` | 🔴 Not-Set | | | +| `OP_GuildStatus` | 🔴 Not-Set | | | +| `OP_GuildTributeInfo` | 🔴 Not-Set | | | +| `OP_GuildUpdate` | 🔴 Not-Set | | | +| `OP_GuildTributeDonateItem` | 🔴 Not-Set | | | +| `OP_GuildTributeDonatePlat` | 🔴 Not-Set | | | +| `OP_GuildWar` | 🔴 Not-Set | | | +| `OP_Heartbeat` | 🔴 Not-Set | | | +| `OP_Hide` | 🟡 Unverified | | | +| `OP_HideCorpse` | 🟡 Unverified | | | +| `OP_HPUpdate` | 🟡 Unverified | | | +| `OP_Illusion` | 🟡 Unverified | | | +| `OP_IncreaseStats` | 🟡 Unverified | | | +| `OP_InitialHPUpdate` | 🔴 Not-Set | | | +| `OP_InitialMobHealth` | 🔴 Not-Set | | | +| `OP_InspectAnswer` | 🔴 Not-Set | | | +| `OP_InspectBuffs` | 🔴 Not-Set | | | +| `OP_InspectMessageUpdate` | 🔴 Not-Set | | | +| `OP_InspectRequest` | 🔴 Not-Set | | | +| `OP_InstillDoubt` | 🟡 Unverified | | | +| `OP_InterruptCast` | 🟡 Unverified | | | +| `OP_InvokeChangePetName` | 🔴 Not-Set | | | +| `OP_InvokeChangePetNameImmediate` | 🔴 Not-Set | | | +| `OP_InvokeNameChangeImmediate` | 🔴 Not-Set | | | +| `OP_InvokeNameChangeLazy` | 🔴 Not-Set | | | +| `OP_ItemLinkClick` | 🔴 Not-Set | | | +| `OP_ItemLinkResponse` | 🔴 Not-Set | | | +| `OP_ItemLinkText` | 🔴 Not-Set | | | +| `OP_ItemName` | 🔴 Not-Set | | | +| `OP_ItemPacket` | 🟡 Unverified | | | +| `OP_ItemPreview` | 🔴 Not-Set | | | +| `OP_ItemPreviewRequest` | 🔴 Not-Set | | | +| `OP_ItemRecastDelay` | 🟡 Unverified | | | +| `OP_ItemVerifyReply` | 🟡 Unverified | | | +| `OP_ItemVerifyRequest` | 🟡 Unverified | | | +| `OP_ItemViewUnknown` | 🔴 Not-Set | | | +| `OP_Jump` | 🟡 Unverified | | | +| `OP_KeyRing` | 🔴 Not-Set | | | +| `OP_KickPlayers` | 🟡 Unverified | | | +| `OP_KnowledgeBase` | 🔴 Not-Set | | | +| `OP_LDoNButton` | 🔴 Not-Set | | | +| `OP_LDoNDisarmTraps` | 🔴 Not-Set | | | +| `OP_LDoNInspect` | 🔴 Not-Set | | | +| `OP_LDoNOpen` | 🟡 Unverified | | | +| `OP_LDoNPickLock` | 🟡 Unverified | | | +| `OP_LDoNSenseTraps` | 🟡 Unverified | | | +| `OP_LeadershipExpToggle` | 🔴 Not-Set | | | +| `OP_LeadershipExpUpdate` | 🔴 Not-Set | | | +| `OP_LeaveAdventure` | 🔴 Not-Set | | | +| `OP_LeaveBoat` | 🟡 Unverified | | | +| `OP_LevelAppearance` | 🟡 Unverified | | | +| `OP_LevelUpdate` | 🟢 Verified | | | +| `OP_LFGAppearance` | 🔴 Not-Set | | | +| `OP_LFGCommand` | 🔴 Not-Set | | | +| `OP_LFGGetMatchesRequest` | 🔴 Not-Set | | | +| `OP_LFGGetMatchesResponse` | 🔴 Not-Set | | | +| `OP_LFGResponse` | 🔴 Not-Set | | | +| `OP_LFGuild` | 🔴 Not-Set | | | +| `OP_LFPCommand` | 🔴 Not-Set | | | +| `OP_LFPGetMatchesRequest` | 🔴 Not-Set | | | +| `OP_LFPGetMatchesResponse` | 🔴 Not-Set | | | +| `OP_LinkedReuse` | 🟡 Unverified | | | +| `OP_LoadSpellSet` | 🔴 Not-Set | | | +| `OP_LocInfo` | 🔴 Not-Set | | | +| `OP_LockoutTimerInfo` | 🔴 Not-Set | | | +| `OP_Login` | 🔴 Not-Set | | | +| `OP_LoginAccepted` | 🔴 Not-Set | | | +| `OP_LoginComplete` | 🔴 Not-Set | | | +| `OP_LoginExpansionPacketData` | 🔴 Not-Set | | | +| `OP_LoginUnknown1` | 🔴 Not-Set | | | +| `OP_LoginUnknown2` | 🔴 Not-Set | | | +| `OP_Logout` | 🟡 Unverified | | | +| `OP_LogoutReply` | 🔴 Not-Set | | | +| `OP_LogServer` | 🟢 Verified | Mostly unused values | | +| `OP_LootComplete` | 🟡 Unverified | | | +| `OP_LootItem` | 🟡 Unverified | | | +| `OP_LootRequest` | 🟡 Unverified | | | +| `OP_ManaChange` | 🟡 Unverified | | | +| `OP_ManaUpdate` | 🔴 Not-Set | | | +| `OP_MarkNPC` | 🔴 Not-Set | | | +| `OP_MarkRaidNPC` | 🔴 Not-Set | | | +| `OP_Marquee` | 🟡 Unverified | | | +| `OP_MemorizeSpell` | 🟡 Unverified | | | +| `OP_Mend` | 🟡 Unverified | | | +| `OP_MendHPUpdate` | 🔴 Not-Set | | | +| `OP_MercenaryAssign` | 🔴 Not-Set | | | +| `OP_MercenaryCommand` | 🔴 Not-Set | | | +| `OP_MercenaryDataRequest` | 🔴 Not-Set | | | +| `OP_MercenaryDataResponse` | 🔴 Not-Set | | | +| `OP_MercenaryDataUpdate` | 🔴 Not-Set | | | +| `OP_MercenaryDataUpdateRequest` | 🔴 Not-Set | | | +| `OP_MercenaryDismiss` | 🔴 Not-Set | | | +| `OP_MercenaryHire` | 🔴 Not-Set | | | +| `OP_MercenarySuspendRequest` | 🔴 Not-Set | | | +| `OP_MercenarySuspendResponse` | 🔴 Not-Set | | | +| `OP_MercenaryTimer` | 🔴 Not-Set | | | +| `OP_MercenaryTimerRequest` | 🔴 Not-Set | | | +| `OP_MercenaryUnknown1` | 🔴 Not-Set | | | +| `OP_MercenaryUnsuspendResponse` | 🔴 Not-Set | | | +| `OP_MerchantBulkItems` | 🔴 Not-Set | | | +| `OP_MobEnduranceUpdate` | 🔴 Not-Set | | | +| `OP_MobHealth` | 🟡 Unverified | | | +| `OP_MobManaUpdate` | 🔴 Not-Set | | | +| `OP_MobRename` | 🔴 Not-Set | | | +| `OP_MobUpdate` | 🔴 Not-Set | | | +| `OP_MoneyOnCorpse` | 🟡 Unverified | | | +| `OP_MoneyUpdate` | 🟡 Unverified | | | +| `OP_MOTD` | 🟢 Verified | | | +| `OP_MoveCoin` | 🟡 Unverified | | | +| `OP_MoveDoor` | 🟡 Unverified | | | +| `OP_MoveItem` | 🟡 Unverified | | | +| `OP_MoveMultipleItems` | 🟡 Unverified | | | +| `OP_MoveLogDisregard` | 🔴 Not-Set | | | +| `OP_MoveLogRequest` | 🔴 Not-Set | | | +| `OP_MultiLineMsg` | 🔴 Not-Set | | | +| `OP_NewSpawn` | 🟢 Verified | Deprecated in the client, already handled in emu | | +| `OP_NewTitlesAvailable` | 🔴 Not-Set | | | +| `OP_NewZone` | 🟢 Verified | | | +| `OP_NPCMoveUpdate` | 🔴 Not-Set | | | +| `OP_OnLevelMessage` | 🟡 Unverified | | | +| `OP_OpenContainer` | 🟡 Unverified | | | +| `OP_OpenDiscordMerchant` | 🔴 Not-Set | | | +| `OP_OpenGuildTributeMaster` | 🔴 Not-Set | | | +| `OP_OpenInventory` | 🔴 Not-Set | | | +| `OP_OpenTributeMaster` | 🔴 Not-Set | | | +| `OP_PDeletePetition` | 🔴 Not-Set | | | +| `OP_PetBuffWindow` | 🔴 Not-Set | | | +| `OP_PetCommands` | 🔴 Not-Set | | | +| `OP_PetCommandState` | 🔴 Not-Set | | | +| `OP_PetHoTT` | 🔴 Not-Set | | | +| `OP_Petition` | 🔴 Not-Set | | | +| `OP_PetitionBug` | 🔴 Not-Set | | | +| `OP_PetitionCheckIn` | 🔴 Not-Set | | | +| `OP_PetitionCheckout` | 🔴 Not-Set | | | +| `OP_PetitionCheckout2` | 🔴 Not-Set | | | +| `OP_PetitionDelete` | 🔴 Not-Set | | | +| `OP_PetitionQue` | 🔴 Not-Set | | | +| `OP_PetitionRefresh` | 🔴 Not-Set | | | +| `OP_PetitionResolve` | 🔴 Not-Set | | | +| `OP_PetitionSearch` | 🔴 Not-Set | | | +| `OP_PetitionSearchResults` | 🔴 Not-Set | | | +| `OP_PetitionSearchText` | 🔴 Not-Set | | | +| `OP_PetitionUnCheckout` | 🔴 Not-Set | | | +| `OP_PetitionUpdate` | 🔴 Not-Set | | | +| `OP_PickPocket` | 🟡 Unverified | | | +| `OP_PickZone` | 🔴 Not-Set | | | +| `OP_PickZoneWindow` | 🔴 Not-Set | | | +| `OP_PlayerProfile` | 🟢 Verified | | | +| `OP_PlayerStateAdd` | 🟡 Unverified | | | +| `OP_PlayerStateRemove` | 🟡 Unverified | | | +| `OP_PlayEverquestRequest` | 🔴 Not-Set | | | +| `OP_PlayEverquestResponse` | 🔴 Not-Set | | | +| `OP_PlayMP3` | 🟡 Unverified | | | +| `OP_Poll` | 🔴 Not-Set | | | +| `OP_PollResponse` | 🔴 Not-Set | | | +| `OP_PopupResponse` | 🟡 Unverified | | | +| `OP_PostEnterWorld` | 🟢 Verified | | | +| `OP_PotionBelt` | 🔴 Not-Set | | | +| `OP_PreLogoutReply` | 🔴 Not-Set | | | +| `OP_PurchaseLeadershipAA` | 🔴 Not-Set | | | +| `OP_PVPLeaderBoardDetailsReply` | 🔴 Not-Set | | | +| `OP_PVPLeaderBoardDetailsRequest` | 🔴 Not-Set | | | +| `OP_PVPLeaderBoardReply` | 🔴 Not-Set | | | +| `OP_PVPLeaderBoardRequest` | 🔴 Not-Set | | | +| `OP_PVPStats` | 🔴 Not-Set | | | +| `OP_QueryResponseThing` | 🔴 Not-Set | | | +| `OP_QueryUCSServerStatus` | 🟡 Unverified | | | +| `OP_RaidDelegateAbility` | 🔴 Not-Set | | | +| `OP_RaidClearNPCMarks` | 🔴 Not-Set | | | +| `OP_RaidInvite` | 🔴 Not-Set | | | +| `OP_RaidJoin` | 🔴 Not-Set | | | +| `OP_RaidUpdate` | 🔴 Not-Set | | | +| `OP_RandomNameGenerator` | 🟢 Verified | The client no longer sends this packet (random name generation is done entirely in the client). The client will still accept this packet to set name (emu doesn't do this, but it's always been supported) | | +| `OP_RandomReply` | 🟡 Unverified | | | +| `OP_RandomReq` | 🟡 Unverified | | | +| `OP_ReadBook` | 🟡 Unverified | | | +| `OP_RecipeAutoCombine` | 🟡 Unverified | | | +| `OP_RecipeDetails` | 🟡 Unverified | | | +| `OP_RecipeReply` | 🟡 Unverified | | | +| `OP_RecipesFavorite` | 🟡 Unverified | | | +| `OP_RecipesSearch` | 🟡 Unverified | | | +| `OP_ReclaimCrystals` | 🔴 Not-Set | | | +| `OP_ReloadUI` | 🔴 Not-Set | | | +| `OP_RemoveAllDoors` | 🟡 Unverified | | | +| `OP_RemoveBlockedBuffs` | 🟢 Verified | | | +| `OP_RemoveNimbusEffect` | 🟡 Unverified | | | +| `OP_RemoveTrap` | 🔴 Not-Set | | | +| `OP_Report` | 🟡 Unverified | | | +| `OP_ReqClientSpawn` | 🟢 Verified | | | +| `OP_ReqNewZone` | 🟢 Verified | Client does not send this (in LS or TOB), but it does receive it. emu does not send it | | +| `OP_RequestClientZoneChange` | 🟢 Verified | parity with RoF2, there's a string that gets passed to teleport at the end that's not known | | +| `OP_RequestDuel` | 🔴 Not-Set | | | +| `OP_RequestGuildTributes` | 🔴 Not-Set | | | +| `OP_RequestKnowledgeBase` | 🔴 Not-Set | | | +| `OP_RequestTitles` | 🔴 Not-Set | | | +| `OP_RespawnWindow` | 🟡 Unverified | | | +| `OP_RespondAA` | 🟡 Unverified | | | +| `OP_RestState` | 🟡 Unverified | | | +| `OP_Rewind` | 🟡 Unverified | | | +| `OP_RezzAnswer` | 🔴 Not-Set | | | +| `OP_RezzComplete` | 🔴 Not-Set | | | +| `OP_RezzRequest` | 🔴 Not-Set | | | +| `OP_Sacrifice` | 🟡 Unverified | | | +| `OP_SafeFallSuccess` | 🟡 Unverified | | | +| `OP_SafePoint` | 🔴 Not-Set | | | +| `OP_Save` | 🟡 Unverified | | | +| `OP_SaveOnZoneReq` | 🟡 Unverified | | | +| `OP_SelectTribute` | 🔴 Not-Set | | | +| `OP_SendAAStats` | 🟡 Unverified | | | +| `OP_SendAATable` | 🟢 Verified | | | +| `OP_SendCharInfo` | 🟢 Verified | | | +| `OP_SendExpZonein` | 🟢 Verified | | | +| `OP_SendFindableNPCs` | 🔴 Not-Set | | | +| `OP_SendGuildTributes` | 🔴 Not-Set | | | +| `OP_SendLoginInfo` | 🟢 Verified | | | +| `OP_SendMaxCharacters` | 🟢 Verified | | | +| `OP_SendMembership` | 🟢 Verified | | | +| `OP_SendMembershipDetails` | 🟢 Verified | The struct is correct, will need reversing for actual option keys/values | | +| `OP_SendSystemStats` | 🔴 Not-Set | | | +| `OP_SendTitleList` | 🔴 Not-Set | | | +| `OP_SendTributes` | 🔴 Not-Set | | | +| `OP_SendZonepoints` | 🟢 Verified | | | +| `OP_SenseHeading` | 🟡 Unverified | | | +| `OP_SenseTraps` | 🟡 Unverified | | | +| `OP_ServerListRequest` | 🔴 Not-Set | | | +| `OP_ServerListResponse` | 🔴 Not-Set | | | +| `OP_SessionReady` | 🔴 Not-Set | | | +| `OP_SetChatServer` | 🔴 Not-Set | | | +| `OP_SetChatServer2` | 🟢 Verified | | | +| `OP_SetFace` | 🔴 Not-Set | | | +| `OP_SetGroupTarget` | 🔴 Not-Set | | | +| `OP_SetGuildMOTD` | 🔴 Not-Set | | | +| `OP_SetGuildRank` | 🔴 Not-Set | | | +| `OP_SetRunMode` | 🟡 Unverified | | | +| `OP_SetServerFilter` | 🟡 Unverified | | | +| `OP_SetStartCity` | 🔴 Not-Set | | | +| `OP_SetTitle` | 🔴 Not-Set | | | +| `OP_SetTitleReply` | 🔴 Not-Set | | | +| `OP_SharedTaskMemberList` | 🔴 Not-Set | | | +| `OP_SharedTaskAddPlayer` | 🔴 Not-Set | | | +| `OP_SharedTaskRemovePlayer` | 🔴 Not-Set | | | +| `OP_SharedTaskMakeLeader` | 🔴 Not-Set | | | +| `OP_SharedTaskMemberInvite` | 🔴 Not-Set | | | +| `OP_SharedTaskInvite` | 🔴 Not-Set | | | +| `OP_SharedTaskInviteResponse` | 🔴 Not-Set | | | +| `OP_SharedTaskAcceptNew` | 🔴 Not-Set | | | +| `OP_SharedTaskMemberChange` | 🔴 Not-Set | | | +| `OP_SharedTaskPlayerList` | 🔴 Not-Set | | | +| `OP_SharedTaskSelectWindow` | 🔴 Not-Set | | | +| `OP_SharedTaskQuit` | 🔴 Not-Set | | | +| `OP_TaskTimers` | 🔴 Not-Set | | | +| `OP_Shielding` | 🔴 Not-Set | | | +| `OP_ShopDelItem` | 🟡 Unverified | | | +| `OP_ShopEnd` | 🟡 Unverified | | | +| `OP_ShopEndConfirm` | 🟡 Unverified | | | +| `OP_ShopItem` | 🔴 Not-Set | | | +| `OP_ShopPlayerBuy` | 🟡 Unverified | | | +| `OP_ShopPlayerSell` | 🟡 Unverified | | | +| `OP_ShopSendParcel` | 🟡 Unverified | | | +| `OP_ShopDeleteParcel` | 🟡 Unverified | | | +| `OP_ShopRespondParcel` | 🔴 Not-Set | | | +| `OP_ShopRetrieveParcel` | 🟡 Unverified | | | +| `OP_ShopParcelIcon` | 🟡 Unverified | | | +| `OP_ShopRequest` | 🟡 Unverified | | | +| `OP_SimpleMessage` | 🟡 Unverified | | | +| `OP_SkillUpdate` | 🟡 Unverified | | | +| `OP_Sneak` | 🟡 Unverified | | | +| `OP_Some3ByteHPUpdate` | 🔴 Not-Set | | | +| `OP_Some6ByteHPUpdate` | 🔴 Not-Set | | | +| `OP_SomeItemPacketMaybe` | 🔴 Not-Set | | | +| `OP_Sound` | 🟡 Unverified | | | +| `OP_SpawnAppearance` | 🟢 Verified | | | +| `OP_SpawnDoor` | 🟢 Verified | | | +| `OP_SpawnPositionUpdate` | 🔴 Not-Set | | | +| `OP_SpecialMesg` | 🟡 Unverified | | | +| `OP_SpellEffect` | 🟡 Unverified | | | +| `OP_Split` | 🟡 Unverified | | | +| `OP_Stamina` | 🟢 Verified | These values are 0-32k instead of 0-127 | | +| `OP_Stun` | 🟡 Unverified | | | +| `OP_Surname` | 🔴 Not-Set | | | +| `OP_SwapSpell` | 🟡 Unverified | | | +| `OP_SystemFingerprint` | 🔴 Not-Set | | | +| `OP_TargetBuffs` | 🔴 Not-Set | | | +| `OP_TargetCommand` | 🟡 Unverified | | | +| `OP_TargetHoTT` | 🔴 Not-Set | | | +| `OP_TargetMouse` | 🟡 Unverified | | | +| `OP_TargetReject` | 🔴 Not-Set | | | +| `OP_TaskActivity` | 🔴 Not-Set | | | +| `OP_TaskActivityComplete` | 🔴 Not-Set | | | +| `OP_TaskDescription` | 🔴 Not-Set | | | +| `OP_TaskHistoryReply` | 🔴 Not-Set | | | +| `OP_TaskHistoryRequest` | 🔴 Not-Set | | | +| `OP_TaskRequestTimer` | 🔴 Not-Set | | | +| `OP_TaskSelectWindow` | 🔴 Not-Set | | | +| `OP_Taunt` | 🟡 Unverified | | | +| `OP_TestBuff` | 🔴 Not-Set | | | +| `OP_TGB` | 🔴 Not-Set | | | +| `OP_TimeOfDay` | 🟢 Verified | | | +| `OP_Track` | 🟡 Unverified | | | +| `OP_TrackTarget` | 🟡 Unverified | | | +| `OP_TrackUnknown` | 🟡 Unverified | | | +| `OP_TradeAcceptClick` | 🟡 Unverified | | | +| `OP_TradeBusy` | 🟡 Unverified | | | +| `OP_TradeCoins` | 🟡 Unverified | | | +| `OP_TradeMoneyUpdate` | 🟡 Unverified | | | +| `OP_Trader` | 🔴 Not-Set | | | +| `OP_TraderBulkSend` | 🔴 Not-Set | | | +| `OP_TraderBuy` | 🔴 Not-Set | | | +| `OP_TraderDelItem` | 🔴 Not-Set | | | +| `OP_TradeRequest` | 🟡 Unverified | | | +| `OP_TradeRequestAck` | 🟡 Unverified | | | +| `OP_TraderItemUpdate` | 🔴 Not-Set | | | +| `OP_TraderShop` | 🔴 Not-Set | | | +| `OP_TradeSkillCombine` | 🟡 Unverified | | | +| `OP_TradeSkillRecipeInspect` | 🔴 Not-Set | | | +| `OP_Translocate` | 🟡 Unverified | | | +| `OP_TributeInfo` | 🔴 Not-Set | | | +| `OP_TributeItem` | 🔴 Not-Set | | | +| `OP_TributeMoney` | 🔴 Not-Set | | | +| `OP_TributeNPC` | 🔴 Not-Set | | | +| `OP_TributePointUpdate` | 🔴 Not-Set | | | +| `OP_TributeTimer` | 🔴 Not-Set | | | +| `OP_TributeToggle` | 🔴 Not-Set | | | +| `OP_TributeUpdate` | 🔴 Not-Set | | | +| `OP_Untargetable` | 🟡 Unverified | | | +| `OP_UpdateAA` | 🟡 Unverified | | | +| `OP_UpdateAura` | 🔴 Not-Set | | | +| `OP_UpdateLeadershipAA` | 🔴 Not-Set | | | +| `OP_VetClaimReply` | 🔴 Not-Set | | | +| `OP_VetClaimRequest` | 🔴 Not-Set | | | +| `OP_VetRewardsAvaliable` | 🔴 Not-Set | | | +| `OP_VoiceMacroIn` | 🟡 Unverified | | | +| `OP_VoiceMacroOut` | 🟡 Unverified | | | +| `OP_WeaponEquip1` | 🔴 Not-Set | | | +| `OP_WearChange` | 🟡 Unverified | | | +| `OP_Weather` | 🟢 Verified | | | +| `OP_Weblink` | 🟡 Unverified | | | +| `OP_WhoAllRequest` | 🟡 Unverified | | | +| `OP_WhoAllResponse` | 🟡 Unverified | | | +| `OP_World_Client_CRC1` | 🟢 Verified | | | +| `OP_World_Client_CRC2` | 🟢 Verified | | | +| `OP_World_Client_CRC3` | 🟢 Verified | | | +| `OP_WorldClientReady` | 🟢 Verified | | | +| `OP_WorldComplete` | 🟢 Verified | | | +| `OP_WorldLogout` | 🔴 Not-Set | | | +| `OP_WorldObjectsSent` | 🟢 Verified | | | +| `OP_WorldUnknown001` | 🟢 Verified | SetServerTime. emu doesn't currently send it so setting it to verified, but the reference is 0x140292550 | | +| `OP_XTargetAutoAddHaters` | 🔴 Not-Set | | | +| `OP_XTargetOpen` | 🔴 Not-Set | | | +| `OP_XTargetOpenResponse` | 🔴 Not-Set | | | +| `OP_XTargetRequest` | 🔴 Not-Set | | | +| `OP_XTargetResponse` | 🔴 Not-Set | | | +| `OP_YellForHelp` | 🟡 Unverified | | | +| `OP_ZoneChange` | 🟢 Verified | | | +| `OP_ZoneComplete` | 🔴 Not-Set | | | +| `OP_ZoneEntry` | 🟢 Verified | unknown fields in C->S struct are various CRCs, emu doesn't use them | | +| `OP_ZoneGuildList` | 🔴 Not-Set | | | +| `OP_ZoneInUnknown` | 🔴 Not-Set | | | +| `OP_ZonePlayerToBind` | 🟡 Unverified | | | +| `OP_ZoneServerInfo` | 🟢 Verified | | | +| `OP_ZoneServerReady` | 🔴 Not-Set | | | +| `OP_ZoneSpawns` | 🟢 Verified | This is deprecated in the client (and emu never sends it directly) | | +| `OP_ZoneUnavail` | 🟢 Verified | The client discards all content of this packet | | +| `OP_ResetAA` | 🟡 Unverified | | | +| `OP_UnderWorld` | 🟡 Unverified | | | From fe4146050f6dde1da635a9fc4856424f25b9cf2c Mon Sep 17 00:00:00 2001 From: dannuic Date: Thu, 16 Apr 2026 16:51:41 -0600 Subject: [PATCH 03/17] Validated up to OP_UpdateAA --- common/patches/tob.cpp | 3 +-- common/patches/tob_structs.h | 3 +-- tob/opcodes.md | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/common/patches/tob.cpp b/common/patches/tob.cpp index cc18ae534..9c7cdd7f3 100644 --- a/common/patches/tob.cpp +++ b/common/patches/tob.cpp @@ -2337,12 +2337,11 @@ namespace TOB eq->aapoints_assigned[4] = 0; eq->aapoints_assigned[5] = 0; - for (uint32 i = 0; i < MAX_PP_AA_ARRAY; ++i) + for (uint32 i = 0; i < structs::MAX_PP_AA_ARRAY; ++i) { eq->aa_list[i].AA = emu->aa_list[i].AA; eq->aa_list[i].value = emu->aa_list[i].value; eq->aa_list[i].charges = emu->aa_list[i].charges; - eq->aa_list[i].bUnknown0x0c = false; } FINISH_ENCODE(); diff --git a/common/patches/tob_structs.h b/common/patches/tob_structs.h index 928e758e6..ba065542d 100644 --- a/common/patches/tob_structs.h +++ b/common/patches/tob_structs.h @@ -818,9 +818,8 @@ namespace TOB { struct AA_Array { uint32 AA; - uint32 value; + uint32 value; // points spent uint32 charges; // expendable charges - bool bUnknown0x0c; // added test winter 2024; removed sometime in summer 2024 }; struct AATable_Struct { diff --git a/tob/opcodes.md b/tob/opcodes.md index 73567da2a..320e466dc 100644 --- a/tob/opcodes.md +++ b/tob/opcodes.md @@ -465,7 +465,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_RequestKnowledgeBase` | 🔴 Not-Set | | | | `OP_RequestTitles` | 🔴 Not-Set | | | | `OP_RespawnWindow` | 🟡 Unverified | | | -| `OP_RespondAA` | 🟡 Unverified | | | +| `OP_RespondAA` | 🟢 Verified | | | | `OP_RestState` | 🟡 Unverified | | | | `OP_Rewind` | 🟡 Unverified | | | | `OP_RezzAnswer` | 🔴 Not-Set | | | @@ -594,7 +594,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_TributeToggle` | 🔴 Not-Set | | | | `OP_TributeUpdate` | 🔴 Not-Set | | | | `OP_Untargetable` | 🟡 Unverified | | | -| `OP_UpdateAA` | 🟡 Unverified | | | +| `OP_UpdateAA` | 🟢 Verified | | | | `OP_UpdateAura` | 🔴 Not-Set | | | | `OP_UpdateLeadershipAA` | 🔴 Not-Set | | | | `OP_VetClaimReply` | 🔴 Not-Set | | | From a8e3ab41e10865938b07b0fbc10ce2c254ba5799 Mon Sep 17 00:00:00 2001 From: dannuic Date: Thu, 16 Apr 2026 17:13:17 -0600 Subject: [PATCH 04/17] Validated up to OP_ExpUpdate --- common/patches/tob.cpp | 14 ++++++++++++++ common/patches/tob_ops.h | 1 + common/patches/tob_structs.h | 6 +++--- tob/opcodes.md | 6 +++--- utils/patches/patch_TOB.conf | 2 +- 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/common/patches/tob.cpp b/common/patches/tob.cpp index 9c7cdd7f3..93e9a66f2 100644 --- a/common/patches/tob.cpp +++ b/common/patches/tob.cpp @@ -145,6 +145,20 @@ namespace TOB #include "ss_define.h" // ENCODE methods + ENCODE(OP_AAExpUpdate) { + ENCODE_LENGTH_EXACT(AltAdvStats_Struct); + SETUP_DIRECT_ENCODE(AltAdvStats_Struct, structs::AltAdvStats_Struct); + + //later we should change the underlying server to use this more accurate value + //and encode the 330 in the other patches + eq->experience = emu->experience * 100000 / 330; + + OUT(unspent); + OUT(percentage); + + FINISH_ENCODE(); + } + ENCODE(OP_Action) { ENCODE_LENGTH_EXACT(Action_Struct); SETUP_DIRECT_ENCODE(Action_Struct, structs::MissileHitInfo); diff --git a/common/patches/tob_ops.h b/common/patches/tob_ops.h index afaf305c3..9e8b8e07b 100644 --- a/common/patches/tob_ops.h +++ b/common/patches/tob_ops.h @@ -1,4 +1,5 @@ //list of packets we need to encode on the way out: +E(OP_AAExpUpdate) E(OP_Action) E(OP_Animation) E(OP_ApplyPoison) diff --git a/common/patches/tob_structs.h b/common/patches/tob_structs.h index ba065542d..64642c78b 100644 --- a/common/patches/tob_structs.h +++ b/common/patches/tob_structs.h @@ -503,8 +503,8 @@ namespace TOB { struct ExpUpdate_Struct { - /*000*/ uint64 exp; //This is exp % / 1000 now; eg 69250 = 69.25% - /*008*/ uint64 unknown; //unclear, I didn't see the client actually read this value but i might have missed it + /*000*/ uint64 exp; // This is exp % / 1000 now; eg 69250 = 69.25% + /*008*/ uint64 unknown; // if this is the value "2", it opens up the tip window }; struct DeleteSpawn_Struct @@ -832,7 +832,7 @@ namespace TOB { /*000*/ uint32 experience; /*004*/ uint32 unspent; /*008*/ uint8 percentage; - /*009*/ uint8 unknown009[3]; + /*009*/ uint8 padding[3]; }; struct ZonePlayerToBind_Struct { diff --git a/tob/opcodes.md b/tob/opcodes.md index 320e466dc..8a11d5107 100644 --- a/tob/opcodes.md +++ b/tob/opcodes.md @@ -9,7 +9,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | Opcode | Status | Notes | Working On | |:----------------------------------|:--------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------| | `OP_AAAction` | 🟡 Unverified | | | -| `OP_AAExpUpdate` | 🟡 Unverified | | | +| `OP_AAExpUpdate` | 🟢 Verified | | | | `OP_AcceptNewTask` | 🔴 Not-Set | | | | `OP_AckPacket` | 🟢 Verified | | | | `OP_Action` | 🟡 Unverified | | | @@ -170,7 +170,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_EnvDamage` | 🟡 Unverified | | | | `OP_EvolveItem` | 🔴 Not-Set | | | | `OP_ExpansionInfo` | 🟢 Verified | Updated from u32 to u64 and works now | | -| `OP_ExpUpdate` | 🟡 Unverified | | | +| `OP_ExpUpdate` | 🟢 Verified | | | | `OP_FaceChange` | 🔴 Not-Set | | | | `OP_Feedback` | 🔴 Not-Set | | | | `OP_FeignDeath` | 🟡 Unverified | | | @@ -182,7 +182,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_FinishWindow2` | 🟡 Unverified | | | | `OP_Fishing` | 🟡 Unverified | | | | `OP_Fling` | 🟡 Unverified | | | -| `OP_FloatListThing` | 🟢 Verified | Movement History. Sent from client, but emu doesn't use it so setting it as varified. Reference is 0x1402FFAD0 | | +| `OP_FloatListThing` | 🟢 Verified | Movement History. Sent from client, but emu doesn't use it so setting it as verified. Reference is 0x1402FFAD0 | | | `OP_Forage` | 🟡 Unverified | | | | `OP_ForceFindPerson` | 🔴 Not-Set | | | | `OP_FormattedMessage` | 🟡 Unverified | | | diff --git a/utils/patches/patch_TOB.conf b/utils/patches/patch_TOB.conf index 3102e24a3..c718d5de4 100644 --- a/utils/patches/patch_TOB.conf +++ b/utils/patches/patch_TOB.conf @@ -93,7 +93,7 @@ OP_ClearAA=0x6093 OP_ClearLeadershipAbilities=0x0000 #removed; leadership abilities are baked in and always on OP_RespondAA=0x4449 OP_UpdateAA=0x1655 -OP_SendAAStats=0x7416 #i'll be honest i think this was removed at some point but this is the op at the spot in the list +OP_SendAAStats=0x7416 # Removed in TOB OP_AAExpUpdate=0x04c3 #need to look into whether this has changed; exp did OP_ExpUpdate=0x0e55 OP_HPUpdate=0x2723 From 6a7baf8f1c9c445c6c5116912b578baccc11f3c9 Mon Sep 17 00:00:00 2001 From: dannuic Date: Fri, 17 Apr 2026 11:56:46 -0600 Subject: [PATCH 05/17] Validated through OP_SetServerFilter --- common/patches/tob.cpp | 56 +++++++++++++++++------------------- common/patches/tob_structs.h | 6 ++-- tob/opcodes.md | 16 +++++------ utils/patches/patch_TOB.conf | 2 +- 4 files changed, 39 insertions(+), 41 deletions(-) diff --git a/common/patches/tob.cpp b/common/patches/tob.cpp index 93e9a66f2..c0ea35bda 100644 --- a/common/patches/tob.cpp +++ b/common/patches/tob.cpp @@ -3845,7 +3845,7 @@ namespace TOB int r; for (r = 0; r < 29; r++) { - // Size 68 in TOB + // Size 69 in TOB IN(filters[r]); } @@ -4149,7 +4149,7 @@ namespace TOB //s32 Variation; //s32 NewArmorId; //s32 NewArmorType; - buffer.WriteUInt32(item->Material); + buffer.WriteUInt32(item->Material); // this isn't labeled well, material is material *type* buffer.WriteUInt32(0); //unsupported atm buffer.WriteUInt32(item->EliteMaterial); buffer.WriteUInt32(item->HerosForgeModel); @@ -4267,9 +4267,20 @@ namespace TOB //u8 SpellDataSkillMask[78]; for (int j = 0; j < 78; ++j) { - buffer.WriteUInt8(0); //unsure what this is exactly + buffer.WriteUInt8(0); // TODO: collection of ints for bitfield for each skill required to use. reads 19 ints byte by byte in the client, leave like this for further investigation } + + /* There are a static 7 spell data entries on an item: + Clicky + Proc + Worn + Focus + Scroll + Focus2 + Blessing + */ + /* SpellData: s32 SpellId; u8 RequiredLevel; @@ -4678,55 +4689,42 @@ namespace TOB //ItemDefinition Item; SerializeItemDefinition(buffer, item); - //u32 RealEstateArrayCount; - // buffer.WriteInt32(0); - //s32 RealEstateArray[RealEstateArrayCount]; - - //bool bRealEstateItemPlaceable; - // buffer.WriteInt8(0); - //u32 SubContentSize; - uint32 subitem_count = 0; - int16 SubSlotNumber = EQ::invbag::SLOT_INVALID; if (slot_id_in <= EQ::invslot::GENERAL_END && slot_id_in >= EQ::invslot::GENERAL_BEGIN) - SubSlotNumber = EQ::invbag::GENERAL_BAGS_BEGIN + ((slot_id_in - EQ::invslot::GENERAL_BEGIN) * EQ::invbag::SLOT_COUNT); + SubSlotNumber = EQ::invbag::GENERAL_BAGS_BEGIN + (slot_id_in - EQ::invslot::GENERAL_BEGIN) * EQ::invbag::SLOT_COUNT; else if (slot_id_in == EQ::invslot::slotCursor) SubSlotNumber = EQ::invbag::CURSOR_BAG_BEGIN; else if (slot_id_in <= EQ::invslot::BANK_END && slot_id_in >= EQ::invslot::BANK_BEGIN) - SubSlotNumber = EQ::invbag::BANK_BAGS_BEGIN + ((slot_id_in - EQ::invslot::BANK_BEGIN) * EQ::invbag::SLOT_COUNT); + SubSlotNumber = EQ::invbag::BANK_BAGS_BEGIN + (slot_id_in - EQ::invslot::BANK_BEGIN) * EQ::invbag::SLOT_COUNT; else if (slot_id_in <= EQ::invslot::SHARED_BANK_END && slot_id_in >= EQ::invslot::SHARED_BANK_BEGIN) - SubSlotNumber = EQ::invbag::SHARED_BANK_BAGS_BEGIN + ((slot_id_in - EQ::invslot::SHARED_BANK_BEGIN) * EQ::invbag::SLOT_COUNT); + SubSlotNumber = EQ::invbag::SHARED_BANK_BAGS_BEGIN + (slot_id_in - EQ::invslot::SHARED_BANK_BEGIN) * EQ::invbag::SLOT_COUNT; else SubSlotNumber = slot_id_in; // not sure if this is the best way to handle this..leaving for now if (SubSlotNumber != EQ::invbag::SLOT_INVALID) { + std::vector> subitems; for (uint32 index = EQ::invbag::SLOT_BEGIN; index <= EQ::invbag::SLOT_END; ++index) { EQ::ItemInstance* sub = inst->GetItem(index); - if (!sub) - continue; - - ++subitem_count; + if (sub != nullptr) + subitems.emplace_back(index, sub); } - buffer.WriteUInt32(subitem_count); - - for (uint32 index = EQ::invbag::SLOT_BEGIN; index <= EQ::invbag::SLOT_END; ++index) { - EQ::ItemInstance* sub = inst->GetItem(index); - if (!sub) - continue; + buffer.WriteUInt32(subitems.size()); + // This must be guaranteed to have subitem_count members, where the index is the correct index. The client doesn't loop through all slots here + for (const auto& [index, subitem] : subitems) { buffer.WriteUInt32(index); - - SerializeItem(buffer, sub, SubSlotNumber, (depth + 1), packet_type); + SerializeItem(buffer, subitem, SubSlotNumber, depth + 1, packet_type); } - } + } else + buffer.WriteUInt32(0); // no subitems, client needs to know that //bool bCollected; buffer.WriteInt8(0); //unsupported atm //u64 DontKnow; - buffer.WriteUInt64(0); //unsupported atm + buffer.WriteInt64(0); //unsupported atm //s32 Luck; buffer.WriteInt32(0); //unsupported atm } diff --git a/common/patches/tob_structs.h b/common/patches/tob_structs.h index 64642c78b..c92d271ca 100644 --- a/common/patches/tob_structs.h +++ b/common/patches/tob_structs.h @@ -516,7 +516,7 @@ namespace TOB { //OP_SetServerFilter struct SetServerFilter_Struct { - uint32 filters[68]; + uint32 filters[69]; }; // Was new to RoF2, doesn't look changed @@ -748,10 +748,10 @@ namespace TOB { struct ManaChange_Struct { uint32 new_mana; - uint32 stamina; + uint32 stamina; // endurance uint32 spell_id; uint32 keepcasting; - int32 slot; + int32 slot; // gem slot }; //This is what we call OP_Action diff --git a/tob/opcodes.md b/tob/opcodes.md index 8a11d5107..67cdf20a9 100644 --- a/tob/opcodes.md +++ b/tob/opcodes.md @@ -85,7 +85,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_ChangePetName` | 🔴 Not-Set | | | | `OP_CharacterCreate` | 🟢 Verified | Sends heroic type, can be used for something? | | | `OP_CharacterCreateRequest` | 🟢 Verified | | | -| `OP_CharInventory` | 🟡 Unverified | | | +| `OP_CharInventory` | 🟢 Verified | | | | `OP_Charm` | 🟡 Unverified | | | | `OP_ChatMessage` | 🔴 Not-Set | | | | `OP_ClearAA` | 🟢 Verified | | | @@ -98,9 +98,9 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_ClickObject` | 🟡 Unverified | | | | `OP_ClickObjectAction` | 🟡 Unverified | | | | `OP_ClientError` | 🔴 Not-Set | | | -| `OP_ClientReady` | 🟡 Unverified | | | +| `OP_ClientReady` | 🟢 Verified | | | | `OP_ClientTimeStamp` | 🔴 Not-Set | | | -| `OP_ClientUpdate` | 🟡 Unverified | | | +| `OP_ClientUpdate` | 🟢 Verified | | | | `OP_CloseContainer` | 🔴 Not-Set | | | | `OP_CloseTributeMaster` | 🔴 Not-Set | | | | `OP_ColoredText` | 🟡 Unverified | | | @@ -279,7 +279,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_Heartbeat` | 🔴 Not-Set | | | | `OP_Hide` | 🟡 Unverified | | | | `OP_HideCorpse` | 🟡 Unverified | | | -| `OP_HPUpdate` | 🟡 Unverified | | | +| `OP_HPUpdate` | 🟢 Verified | | | | `OP_Illusion` | 🟡 Unverified | | | | `OP_IncreaseStats` | 🟡 Unverified | | | | `OP_InitialHPUpdate` | 🔴 Not-Set | | | @@ -346,7 +346,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_LootComplete` | 🟡 Unverified | | | | `OP_LootItem` | 🟡 Unverified | | | | `OP_LootRequest` | 🟡 Unverified | | | -| `OP_ManaChange` | 🟡 Unverified | | | +| `OP_ManaChange` | 🟢 Verified | | | | `OP_ManaUpdate` | 🔴 Not-Set | | | | `OP_MarkNPC` | 🔴 Not-Set | | | | `OP_MarkRaidNPC` | 🔴 Not-Set | | | @@ -503,7 +503,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_SetGuildMOTD` | 🔴 Not-Set | | | | `OP_SetGuildRank` | 🔴 Not-Set | | | | `OP_SetRunMode` | 🟡 Unverified | | | -| `OP_SetServerFilter` | 🟡 Unverified | | | +| `OP_SetServerFilter` | 🟢 Verified | | | | `OP_SetStartCity` | 🔴 Not-Set | | | | `OP_SetTitle` | 🔴 Not-Set | | | | `OP_SetTitleReply` | 🔴 Not-Set | | | @@ -543,7 +543,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_SpawnAppearance` | 🟢 Verified | | | | `OP_SpawnDoor` | 🟢 Verified | | | | `OP_SpawnPositionUpdate` | 🔴 Not-Set | | | -| `OP_SpecialMesg` | 🟡 Unverified | | | +| `OP_SpecialMesg` | 🟢 Verified | | | | `OP_SpellEffect` | 🟡 Unverified | | | | `OP_Split` | 🟡 Unverified | | | | `OP_Stamina` | 🟢 Verified | These values are 0-32k instead of 0-127 | | @@ -603,7 +603,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_VoiceMacroIn` | 🟡 Unverified | | | | `OP_VoiceMacroOut` | 🟡 Unverified | | | | `OP_WeaponEquip1` | 🔴 Not-Set | | | -| `OP_WearChange` | 🟡 Unverified | | | +| `OP_WearChange` | 🟢 Verified | | | | `OP_Weather` | 🟢 Verified | | | | `OP_Weblink` | 🟡 Unverified | | | | `OP_WhoAllRequest` | 🟡 Unverified | | | diff --git a/utils/patches/patch_TOB.conf b/utils/patches/patch_TOB.conf index c718d5de4..de96f6841 100644 --- a/utils/patches/patch_TOB.conf +++ b/utils/patches/patch_TOB.conf @@ -222,7 +222,7 @@ OP_KeyRing=0x0000 OP_WhoAllRequest=0x3328 OP_WhoAllResponse=0x4dfd OP_FriendsWho=0x3547 -OP_ConfirmDelete=0x14a8 +OP_ConfirmDelete=0x14a8 # This is sent fromt the client after a movement update (with just spawn ID as the content) OP_Logout=0x46f8 OP_Rewind=0x898a OP_TargetCommand=0x46bf From 2da6d3f37c02f6c50d6b1336c55811644b720901 Mon Sep 17 00:00:00 2001 From: dannuic Date: Fri, 17 Apr 2026 14:57:35 -0600 Subject: [PATCH 06/17] Fixed item index mapping --- common/patches/tob.cpp | 35 +++++++++++++++++------------------ common/patches/tob_limits.h | 4 ++-- common/patches/tob_structs.h | 3 +-- tob/opcodes.md | 2 +- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/common/patches/tob.cpp b/common/patches/tob.cpp index c0ea35bda..159ec2643 100644 --- a/common/patches/tob.cpp +++ b/common/patches/tob.cpp @@ -471,7 +471,7 @@ namespace TOB int item_count = in->size / sizeof(EQ::InternalSerializedItem_Struct); if (!item_count || (in->size % sizeof(EQ::InternalSerializedItem_Struct)) != 0) { - Log(Logs::General, Logs::Netcode, "[STRUCTS] Wrong size on outbound %s: Got %d, expected multiple of %d", + LogNetcode("[STRUCTS] Wrong size on outbound {}: Got {}, expected multiple of {}", opcodes->EmuToName(in->GetOpcode()), in->size, sizeof(EQ::InternalSerializedItem_Struct)); delete in; @@ -2324,7 +2324,6 @@ namespace TOB eq->container_slot = ServerToTOBSlot(emu->unknown1); structs::InventorySlot_Struct TOBSlot; TOBSlot.Type = 8; // Observed - TOBSlot.Padding1 = 0; TOBSlot.Slot = 0xffff; TOBSlot.SubIndex = 0xffff; TOBSlot.AugIndex = 0xffff; @@ -5045,8 +5044,8 @@ namespace TOB TOBSlot.Slot = server_slot - EQ::invslot::WORLD_BEGIN; } - Log(Logs::Detail, Logs::Netcode, "Convert Server Slot %i to TOB Slot [%i, %i, %i, %i]", - server_slot, TOBSlot.Type, TOBSlot.Slot, TOBSlot.SubIndex, TOBSlot.AugIndex); + Log(Logs::Detail, Logs::Netcode, fmt::format("Convert Server Slot {} to TOB Slot [{}, {}, {}, {}]", + server_slot, TOBSlot.Type, TOBSlot.Slot, TOBSlot.SubIndex, TOBSlot.AugIndex).c_str()); return TOBSlot; } @@ -5062,8 +5061,8 @@ namespace TOB if (TOBSlot.Slot != invslot::SLOT_INVALID) TOBSlot.Type = invtype::typeCorpse; - Log(Logs::Detail, Logs::Netcode, "Convert Server Corpse Slot %i to TOB Corpse Slot [%i, %i, %i, %i]", - server_corpse_slot, TOBSlot.Type, TOBSlot.Slot, TOBSlot.SubIndex, TOBSlot.AugIndex); + Log(Logs::Detail, Logs::Netcode, fmt::format("Convert Server Corpse Slot {} to TOB Corpse Slot [{}, {}, {}, {}]", + server_corpse_slot, TOBSlot.Type, TOBSlot.Slot, TOBSlot.SubIndex, TOBSlot.AugIndex).c_str()); return TOBSlot; } @@ -5103,8 +5102,8 @@ namespace TOB } } - Log(Logs::Detail, Logs::Netcode, "Convert Server Slot %i to TOB Typeless Slot [%i, %i, %i] (implied type: %i)", - server_slot, TOBSlot.Slot, TOBSlot.SubIndex, TOBSlot.AugIndex, server_type); + Log(Logs::Detail, Logs::Netcode, fmt::format("Convert Server Slot {} to TOB Typeless Slot [{}, {}, {}] (implied type: {})", + server_slot, TOBSlot.Slot, TOBSlot.SubIndex, TOBSlot.AugIndex, server_type).c_str()); return TOBSlot; } @@ -5112,8 +5111,8 @@ namespace TOB static inline uint32 TOBToServerSlot(structs::InventorySlot_Struct tob_slot) { if (tob_slot.AugIndex < invaug::SOCKET_INVALID || tob_slot.AugIndex >= invaug::SOCKET_COUNT) { - Log(Logs::Detail, Logs::Netcode, "Convert TOB Slot [%i, %i, %i, %i] to Server Slot %i", - tob_slot.Type, tob_slot.Slot, tob_slot.SubIndex, tob_slot.AugIndex, EQ::invslot::SLOT_INVALID); + Log(Logs::Detail, Logs::Netcode, fmt::format("Convert TOB Slot [{}, {}, {}, {}] to Server Slot {}", + tob_slot.Type, tob_slot.Slot, tob_slot.SubIndex, tob_slot.AugIndex, EQ::invslot::SLOT_INVALID).c_str()); return EQ::invslot::SLOT_INVALID; } @@ -5235,8 +5234,8 @@ namespace TOB } } - Log(Logs::Detail, Logs::Netcode, "Convert TOB Slot [%i, %i, %i, %i] to Server Slot %i", - tob_slot.Type, tob_slot.Slot, tob_slot.SubIndex, tob_slot.AugIndex, server_slot); + Log(Logs::Detail, Logs::Netcode, fmt::format("Convert TOB Slot [{}, {}, {}, {}] to Server Slot {}", + tob_slot.Type, tob_slot.Slot, tob_slot.SubIndex, tob_slot.AugIndex, server_slot).c_str()); return server_slot; } @@ -5253,8 +5252,8 @@ namespace TOB ServerSlot = TOBToServerCorpseMainSlot(tob_corpse_slot.Slot); } - Log(Logs::Detail, Logs::Netcode, "Convert TOB Slot [%i, %i, %i, %i] to Server Slot %i", - tob_corpse_slot.Type, tob_corpse_slot.Slot, tob_corpse_slot.SubIndex, tob_corpse_slot.AugIndex, ServerSlot); + Log(Logs::Detail, Logs::Netcode, fmt::format("Convert TOB Slot [{}, {}, {}, {}] to Server Slot {}", + tob_corpse_slot.Type, tob_corpse_slot.Slot, tob_corpse_slot.SubIndex, tob_corpse_slot.AugIndex, ServerSlot).c_str()); return ServerSlot; } @@ -5275,8 +5274,8 @@ namespace TOB static inline uint32 TOBToServerTypelessSlot(structs::TypelessInventorySlot_Struct tob_slot, int16 tob_type) { if (tob_slot.AugIndex < invaug::SOCKET_INVALID || tob_slot.AugIndex >= invaug::SOCKET_COUNT) { - Log(Logs::Detail, Logs::Netcode, "Convert TOB Typeless Slot [%i, %i, %i] (implied type: %i) to Server Slot %i", - tob_slot.Slot, tob_slot.SubIndex, tob_slot.AugIndex, tob_type, EQ::invslot::SLOT_INVALID); + Log(Logs::Detail, Logs::Netcode, fmt::format("Convert TOB Typeless Slot [{}, {}, {}] (implied type: {}) to Server Slot {}", + tob_slot.Slot, tob_slot.SubIndex, tob_slot.AugIndex, tob_type, EQ::invslot::SLOT_INVALID).c_str()); return EQ::invslot::SLOT_INVALID; } @@ -5389,8 +5388,8 @@ namespace TOB } } - Log(Logs::Detail, Logs::Netcode, "Convert TOB Typeless Slot [%i, %i, %i] (implied type: %i) to Server Slot %i", - tob_slot.Slot, tob_slot.SubIndex, tob_slot.AugIndex, tob_type, ServerSlot); + Log(Logs::Detail, Logs::Netcode, fmt::format("Convert TOB Typeless Slot [{}, {}, {}] (implied type: {}) to Server Slot {}", + tob_slot.Slot, tob_slot.SubIndex, tob_slot.AugIndex, tob_type, ServerSlot).c_str()); return ServerSlot; } diff --git a/common/patches/tob_limits.h b/common/patches/tob_limits.h index 1288cf878..beb5e7b90 100644 --- a/common/patches/tob_limits.h +++ b/common/patches/tob_limits.h @@ -200,8 +200,8 @@ namespace TOB const int16 SLOT_INVALID = IINVALID; const int16 SLOT_BEGIN = INULL; - const int16 SLOT_END = 9; //254; - const int16 SLOT_COUNT = 10; //255; // server Size will be 255..unsure what actual client is (test) + const int16 SLOT_END = 199; + const int16 SLOT_COUNT = 200; // server Size will be 200..unsure what actual client is (test) const char* GetInvBagIndexName(int16 bag_index); diff --git a/common/patches/tob_structs.h b/common/patches/tob_structs.h index c92d271ca..b2211c7f0 100644 --- a/common/patches/tob_structs.h +++ b/common/patches/tob_structs.h @@ -523,8 +523,7 @@ namespace TOB { // The padding is because these structs are padded to the default 4 bytes struct InventorySlot_Struct { - /*000*/ int16 Type; - /*002*/ int16 Padding1; + /*000*/ int32 Type; /*004*/ int16 Slot; /*006*/ int16 SubIndex; /*008*/ int16 AugIndex; diff --git a/tob/opcodes.md b/tob/opcodes.md index 67cdf20a9..f8113c535 100644 --- a/tob/opcodes.md +++ b/tob/opcodes.md @@ -379,7 +379,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_MOTD` | 🟢 Verified | | | | `OP_MoveCoin` | 🟡 Unverified | | | | `OP_MoveDoor` | 🟡 Unverified | | | -| `OP_MoveItem` | 🟡 Unverified | | | +| `OP_MoveItem` | 🟢 Verified | | | | `OP_MoveMultipleItems` | 🟡 Unverified | | | | `OP_MoveLogDisregard` | 🔴 Not-Set | | | | `OP_MoveLogRequest` | 🔴 Not-Set | | | From ec5a9d0bd49673da1e47307c093ef7fd19284156 Mon Sep 17 00:00:00 2001 From: dannuic Date: Fri, 17 Apr 2026 18:07:39 -0600 Subject: [PATCH 07/17] Validated up to OP_MemorizeSpell (still needs testing in client) --- common/patches/tob.cpp | 34 ++++++++++++++++++++++++++++++++++ common/patches/tob_ops.h | 2 ++ common/patches/tob_structs.h | 9 ++++++++- tob/opcodes.md | 10 +++++----- zone/client_packet.cpp | 3 +++ 5 files changed, 52 insertions(+), 6 deletions(-) diff --git a/common/patches/tob.cpp b/common/patches/tob.cpp index 159ec2643..83a379b11 100644 --- a/common/patches/tob.cpp +++ b/common/patches/tob.cpp @@ -957,6 +957,23 @@ namespace TOB FINISH_ENCODE(); } + ENCODE(OP_MemorizeSpell) { + ENCODE_LENGTH_EXACT(MemorizeSpell_Struct); + SETUP_DIRECT_ENCODE(MemorizeSpell_Struct, structs::MemorizeSpell_Struct); + + // TODO: TOB has a "finish memming" value (2) here, might be needed to keep client spell gems in sync + if (emu->scribing == 2) + eq->scribing = 3; + else + OUT(scribing); + + OUT(slot); + OUT(spell_id); + OUT(reduction); + + FINISH_ENCODE(); + } + ENCODE(OP_MobHealth) { ENCODE_LENGTH_EXACT(SpawnHPUpdate_Struct2); SETUP_DIRECT_ENCODE(SpawnHPUpdate_Struct2, structs::MobHealth_Struct); @@ -3821,6 +3838,23 @@ namespace TOB DECODE_FORWARD(OP_GroupInvite); } + DECODE(OP_MemorizeSpell) { + DECODE_LENGTH_EXACT(structs::MemorizeSpell_Struct); + SETUP_DIRECT_DECODE(MemorizeSpell_Struct, structs::MemorizeSpell_Struct); + + // TODO: TOB has a "finish memming" value (2) here, might be needed to keep client spell gems in sync + if (emu->scribing == 3) + eq->scribing = 2; + else + IN(scribing); + + IN(slot); + IN(spell_id); + IN(reduction); + + FINISH_DIRECT_DECODE(); + } + DECODE(OP_MoveItem) { DECODE_LENGTH_EXACT(structs::MoveItem_Struct); diff --git a/common/patches/tob_ops.h b/common/patches/tob_ops.h index 9e8b8e07b..40a7c9adf 100644 --- a/common/patches/tob_ops.h +++ b/common/patches/tob_ops.h @@ -33,6 +33,7 @@ E(OP_Illusion) E(OP_ItemPacket) E(OP_LogServer) E(OP_ManaChange) +E(OP_MemorizeSpell) E(OP_MobHealth) E(OP_MoneyOnCorpse) E(OP_MoveItem) @@ -84,6 +85,7 @@ D(OP_GMTraining) D(OP_GroupDisband) D(OP_GroupInvite) D(OP_GroupInvite2) +D(OP_MemorizeSpell) D(OP_MoveItem) D(OP_RemoveBlockedBuffs) D(OP_SetServerFilter) diff --git a/common/patches/tob_structs.h b/common/patches/tob_structs.h index b2211c7f0..e003490ea 100644 --- a/common/patches/tob_structs.h +++ b/common/patches/tob_structs.h @@ -675,11 +675,18 @@ namespace TOB { /*000*/ uint32 spell_id; /*004*/ uint16 caster_id; /*006*/ uint32 cast_time; // in miliseconds - /*010*/ uint32 unknown0a; // I think this is caster effective level but im not sure. live always sends 0 + /*010*/ uint32 unknown0a; // I think this is caster effective level but im not sure. live always sends 0. The client uses this for the spell link /*014*/ uint8 unknown0e; // 0 will short circuit the cast, seen 1 from live usually, maybe related to interrupts or particles or something /*015*/ }; + struct MemorizeSpell_Struct { + uint32 slot; // Spot in the spell book/memorized slot + uint32 spell_id; // Spell id (200 or c8 is minor healing, etc) + uint32 scribing; // -1 refreshes book, 0 scribe to book, 2 end mem, 1 start mem, 3 unmem, 4 set activated item keyring -- client will send back 2 if a 0 operation updated a memorized spell of the same group + subgroup + uint32 reduction; // lower reuse (only used if scribing is 4) + }; + //I've observed 5 s16 that are all -1. //Clicky items don't even trigger this as far as i can tell so not sure what this is for now. //One of these could have changed to a s32 but im not sure. diff --git a/tob/opcodes.md b/tob/opcodes.md index f8113c535..3dbee363b 100644 --- a/tob/opcodes.md +++ b/tob/opcodes.md @@ -63,7 +63,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_BecomeCorpse` | 🔴 Not-Set | | | | `OP_BecomeTrader` | 🔴 Not-Set | | | | `OP_Begging` | 🟡 Unverified | | | -| `OP_BeginCast` | 🟡 Unverified | | | +| `OP_BeginCast` | 🟢 Verified | | | | `OP_Bind_Wound` | 🟡 Unverified | | | | `OP_BlockedBuffs` | 🟢 Verified | | | | `OP_BoardBoat` | 🟡 Unverified | | | @@ -103,14 +103,14 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_ClientUpdate` | 🟢 Verified | | | | `OP_CloseContainer` | 🔴 Not-Set | | | | `OP_CloseTributeMaster` | 🔴 Not-Set | | | -| `OP_ColoredText` | 🟡 Unverified | | | +| `OP_ColoredText` | 🟢 Verified | | | | `OP_CombatAbility` | 🟡 Unverified | | | | `OP_Command` | 🔴 Not-Set | | | | `OP_CompletedTasks` | 🔴 Not-Set | | | | `OP_ConfirmDelete` | 🟡 Unverified | | | | `OP_Consent` | 🟡 Unverified | | | | `OP_ConsentDeny` | 🟡 Unverified | | | -| `OP_ConsentResponse` | 🟡 Unverified | | | +| `OP_ConsentResponse` | 🟢 Verified | | | | `OP_Consider` | 🟡 Unverified | | | | `OP_ConsiderCorpse` | 🟡 Unverified | | | | `OP_Consume` | 🟡 Unverified | | | @@ -351,7 +351,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_MarkNPC` | 🔴 Not-Set | | | | `OP_MarkRaidNPC` | 🔴 Not-Set | | | | `OP_Marquee` | 🟡 Unverified | | | -| `OP_MemorizeSpell` | 🟡 Unverified | | | +| `OP_MemorizeSpell` | 🟢 Verified | | | | `OP_Mend` | 🟡 Unverified | | | | `OP_MendHPUpdate` | 🔴 Not-Set | | | | `OP_MercenaryAssign` | 🔴 Not-Set | | | @@ -435,7 +435,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_PVPLeaderBoardRequest` | 🔴 Not-Set | | | | `OP_PVPStats` | 🔴 Not-Set | | | | `OP_QueryResponseThing` | 🔴 Not-Set | | | -| `OP_QueryUCSServerStatus` | 🟡 Unverified | | | +| `OP_QueryUCSServerStatus` | 🟢 Verified | | | | `OP_RaidDelegateAbility` | 🔴 Not-Set | | | | `OP_RaidClearNPCMarks` | 🔴 Not-Set | | | | `OP_RaidInvite` | 🔴 Not-Set | | | diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index e568265c5..f1f241359 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -12239,6 +12239,9 @@ void Client::Handle_OP_QueryUCSServerStatus(const EQApplicationPacket *app) case EQ::versions::ClientVersion::RoF2: ConnectionType = EQ::versions::ucsRoF2Combined; break; + case EQ::versions::ClientVersion::TOB: + ConnectionType = EQ::versions::ucsTOBCombined; + break; default: ConnectionType = EQ::versions::ucsUnknown; break; From 3bb7f94713f1a9dac204d285b5767693fc80a08f Mon Sep 17 00:00:00 2001 From: dannuic Date: Fri, 17 Apr 2026 23:35:34 -0600 Subject: [PATCH 08/17] Fixed memorization for parity with RoF2 --- common/patches/tob.cpp | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/common/patches/tob.cpp b/common/patches/tob.cpp index 83a379b11..6522b2952 100644 --- a/common/patches/tob.cpp +++ b/common/patches/tob.cpp @@ -25,6 +25,7 @@ #include #include +#include "common/packet_dump.h" #include "world/sof_char_create_data.h" namespace TOB @@ -961,11 +962,13 @@ namespace TOB ENCODE_LENGTH_EXACT(MemorizeSpell_Struct); SETUP_DIRECT_ENCODE(MemorizeSpell_Struct, structs::MemorizeSpell_Struct); - // TODO: TOB has a "finish memming" value (2) here, might be needed to keep client spell gems in sync - if (emu->scribing == 2) + // in TOB, 2 is "finish memming" so that becomes 1 in emu and 3 is "unmem" which becomes 2 + if (emu->scribing == 1) + eq->scribing = 2; + else if (emu->scribing == 2) eq->scribing = 3; else - OUT(scribing); + OUT(scribing); // TODO: can handle 4 here (I assume it's just like 2 or 3 but can have a reduction component) OUT(slot); OUT(spell_id); @@ -3842,11 +3845,21 @@ namespace TOB DECODE_LENGTH_EXACT(structs::MemorizeSpell_Struct); SETUP_DIRECT_DECODE(MemorizeSpell_Struct, structs::MemorizeSpell_Struct); - // TODO: TOB has a "finish memming" value (2) here, might be needed to keep client spell gems in sync - if (emu->scribing == 3) - eq->scribing = 2; + // TOB sends status 1 here to let the server know that it's started memming, but doesn't want a response + if (eq->scribing == 1) { + // TODO: There should be a timer set here to detect short-mem cheats, and then checked when the 2 packet is sent + // The previous detection will still happen on scribing == 2, the new client just handles it better + __packet->SetOpcode(OP_Unknown); + return; + } + + // in TOB, 2 is "finish memming" so that becomes 1 in emu and 3 is "unmem" which becomes 2 + if (eq->scribing == 2) + emu->scribing = 1; + else if (eq->scribing == 3) + emu->scribing = 2; else - IN(scribing); + IN(scribing); // TODO: Handle 4 here (clicky keyring) IN(slot); IN(spell_id); From 08cdd8234dc9e60137a4d9368c73c7aea86fca22 Mon Sep 17 00:00:00 2001 From: dannuic Date: Sat, 18 Apr 2026 00:08:18 -0600 Subject: [PATCH 09/17] Validated to OP_CastSpell -- still causes a spell to unmem after cast --- common/patches/tob.cpp | 3 +++ tob/opcodes.md | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/common/patches/tob.cpp b/common/patches/tob.cpp index 6522b2952..4b2a3dd1d 100644 --- a/common/patches/tob.cpp +++ b/common/patches/tob.cpp @@ -352,6 +352,7 @@ namespace TOB ENCODE(OP_CastSpell) { + // I don't think the client handles this at all, it only sends the cast packet ENCODE_LENGTH_EXACT(CastSpell_Struct); SETUP_DIRECT_ENCODE(CastSpell_Struct, structs::CastSpell_Struct); @@ -363,6 +364,7 @@ namespace TOB //OUT(inventoryslot); OUT(target_id); + LogNetcode("S->C OP_CastSpell {}", DumpPacketToString(__packet)); FINISH_ENCODE(); } @@ -3648,6 +3650,7 @@ namespace TOB IN(y_pos); IN(x_pos); IN(z_pos); + LogNetcode("C->S OP_CastSpell {}", DumpPacketToString(__packet)); FINISH_DIRECT_DECODE(); } diff --git a/tob/opcodes.md b/tob/opcodes.md index 3dbee363b..7e451637a 100644 --- a/tob/opcodes.md +++ b/tob/opcodes.md @@ -330,7 +330,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_LFPCommand` | 🔴 Not-Set | | | | `OP_LFPGetMatchesRequest` | 🔴 Not-Set | | | | `OP_LFPGetMatchesResponse` | 🔴 Not-Set | | | -| `OP_LinkedReuse` | 🟡 Unverified | | | +| `OP_LinkedReuse` | 🟢 Verified | | | | `OP_LoadSpellSet` | 🔴 Not-Set | | | | `OP_LocInfo` | 🔴 Not-Set | | | | `OP_LockoutTimerInfo` | 🔴 Not-Set | | | @@ -549,7 +549,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_Stamina` | 🟢 Verified | These values are 0-32k instead of 0-127 | | | `OP_Stun` | 🟡 Unverified | | | | `OP_Surname` | 🔴 Not-Set | | | -| `OP_SwapSpell` | 🟡 Unverified | | | +| `OP_SwapSpell` | 🟢 Verified | | | | `OP_SystemFingerprint` | 🔴 Not-Set | | | | `OP_TargetBuffs` | 🔴 Not-Set | | | | `OP_TargetCommand` | 🟡 Unverified | | | From b8ee811ac6f21bd33f7eef100eff2dc88ad0e9a2 Mon Sep 17 00:00:00 2001 From: dannuic Date: Sat, 18 Apr 2026 06:53:37 -0600 Subject: [PATCH 10/17] Fixed unmemming spells on cast --- common/patches/tob.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/patches/tob.cpp b/common/patches/tob.cpp index 4b2a3dd1d..78c0c11c6 100644 --- a/common/patches/tob.cpp +++ b/common/patches/tob.cpp @@ -969,6 +969,8 @@ namespace TOB eq->scribing = 2; else if (emu->scribing == 2) eq->scribing = 3; + else if (emu->scribing == 3) + eq->scribing = 4; else OUT(scribing); // TODO: can handle 4 here (I assume it's just like 2 or 3 but can have a reduction component) @@ -3861,6 +3863,8 @@ namespace TOB emu->scribing = 1; else if (eq->scribing == 3) emu->scribing = 2; + else if (eq->scribing == 4) + emu->scribing = 3; else IN(scribing); // TODO: Handle 4 here (clicky keyring) From c5d089de681a3e6e0e9e628d11acfe95d72bfabb Mon Sep 17 00:00:00 2001 From: dannuic Date: Sat, 18 Apr 2026 12:08:54 -0600 Subject: [PATCH 11/17] Validated message and interrupt packets, needs refactor for full functionality --- common/patches/tob.cpp | 8 ++++---- common/patches/tob_structs.h | 7 +++++++ tob/opcodes.md | 8 ++++---- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/common/patches/tob.cpp b/common/patches/tob.cpp index 78c0c11c6..a57195041 100644 --- a/common/patches/tob.cpp +++ b/common/patches/tob.cpp @@ -671,8 +671,8 @@ namespace TOB } SerializeBuffer buffer; - buffer.WriteUInt32(emu->unknown0); - buffer.WriteUInt8(0); // Observed + buffer.WriteUInt32(0); // This is a string written like the message arrays + buffer.WriteUInt8(emu->unknown0); buffer.WriteUInt32(emu->string_id); buffer.WriteUInt32(emu->type); @@ -972,7 +972,7 @@ namespace TOB else if (emu->scribing == 3) eq->scribing = 4; else - OUT(scribing); // TODO: can handle 4 here (I assume it's just like 2 or 3 but can have a reduction component) + OUT(scribing); OUT(slot); OUT(spell_id); @@ -3866,7 +3866,7 @@ namespace TOB else if (eq->scribing == 4) emu->scribing = 3; else - IN(scribing); // TODO: Handle 4 here (clicky keyring) + IN(scribing); IN(slot); IN(spell_id); diff --git a/common/patches/tob_structs.h b/common/patches/tob_structs.h index e003490ea..db3cd4ad7 100644 --- a/common/patches/tob_structs.h +++ b/common/patches/tob_structs.h @@ -713,6 +713,13 @@ namespace TOB { /*39*/ }; + struct InterruptCast_Struct + { + uint32 spawnid; + uint32 messageid; + char message[0]; + }; + struct EQAffectSlot_Struct { /*00*/ int32 slot; /*04*/ int32 padding; diff --git a/tob/opcodes.md b/tob/opcodes.md index 7e451637a..31d2b54d2 100644 --- a/tob/opcodes.md +++ b/tob/opcodes.md @@ -79,7 +79,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_CancelTask` | 🔴 Not-Set | | | | `OP_CancelTrade` | 🟡 Unverified | | | | `OP_CashReward` | 🟡 Unverified | | | -| `OP_CastSpell` | 🟡 Unverified | | | +| `OP_CastSpell` | 🟢 Verified | | | | `OP_ChangeSize` | 🟢 Verified | | | | `OP_ChannelMessage` | 🟡 Unverified | | | | `OP_ChangePetName` | 🔴 Not-Set | | | @@ -185,7 +185,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_FloatListThing` | 🟢 Verified | Movement History. Sent from client, but emu doesn't use it so setting it as verified. Reference is 0x1402FFAD0 | | | `OP_Forage` | 🟡 Unverified | | | | `OP_ForceFindPerson` | 🔴 Not-Set | | | -| `OP_FormattedMessage` | 🟡 Unverified | | | +| `OP_FormattedMessage` | 🟢 Verified | Some major work to do here -- the client now expects a spell link in the packet, will need to refactor the client work to decouple the stream from the internal representation | | | `OP_FriendsWho` | 🟡 Unverified | | | | `OP_GetGuildMOTD` | 🔴 Not-Set | | | | `OP_GetGuildMOTDReply` | 🔴 Not-Set | | | @@ -289,7 +289,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_InspectMessageUpdate` | 🔴 Not-Set | | | | `OP_InspectRequest` | 🔴 Not-Set | | | | `OP_InstillDoubt` | 🟡 Unverified | | | -| `OP_InterruptCast` | 🟡 Unverified | | | +| `OP_InterruptCast` | 🟢 Verified | Some major work to do here -- the client now expects a spell link in the packet, will need to refactor the client work to decouple the stream from the internal representation | | | `OP_InvokeChangePetName` | 🔴 Not-Set | | | | `OP_InvokeChangePetNameImmediate` | 🔴 Not-Set | | | | `OP_InvokeNameChangeImmediate` | 🔴 Not-Set | | | @@ -533,7 +533,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_ShopRetrieveParcel` | 🟡 Unverified | | | | `OP_ShopParcelIcon` | 🟡 Unverified | | | | `OP_ShopRequest` | 🟡 Unverified | | | -| `OP_SimpleMessage` | 🟡 Unverified | | | +| `OP_SimpleMessage` | 🟢 Verified | | | | `OP_SkillUpdate` | 🟡 Unverified | | | | `OP_Sneak` | 🟡 Unverified | | | | `OP_Some3ByteHPUpdate` | 🔴 Not-Set | | | From af06fb703cdeffc4ea50b4e6f7d7ec0541543a80 Mon Sep 17 00:00:00 2001 From: dannuic Date: Sat, 18 Apr 2026 12:37:56 -0600 Subject: [PATCH 12/17] confirmed consider --- tob/opcodes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tob/opcodes.md b/tob/opcodes.md index 31d2b54d2..62db50a03 100644 --- a/tob/opcodes.md +++ b/tob/opcodes.md @@ -111,7 +111,7 @@ Below is a status list for the 450 opcodes we currently use on the server for th | `OP_Consent` | 🟡 Unverified | | | | `OP_ConsentDeny` | 🟡 Unverified | | | | `OP_ConsentResponse` | 🟢 Verified | | | -| `OP_Consider` | 🟡 Unverified | | | +| `OP_Consider` | 🟢 Verified | | | | `OP_ConsiderCorpse` | 🟡 Unverified | | | | `OP_Consume` | 🟡 Unverified | | | | `OP_ControlBoat` | 🟡 Unverified | | | From 0e0162edc0e2699486d118f82a95401c66cafcfc Mon Sep 17 00:00:00 2001 From: dannuic Date: Sun, 19 Apr 2026 23:07:02 -0600 Subject: [PATCH 13/17] Added spell links to interrupt and fizzle messages --- common/CMakeLists.txt | 2 ++ common/links.cpp | 10 ++++++++++ common/links.h | 11 +++++++++++ common/patches/tob.cpp | 4 ++-- zone/spells.cpp | 36 ++++++++++++++++++++++++++++-------- 5 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 common/links.cpp create mode 100644 common/links.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 5293b457f..327aa2ce5 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -734,6 +734,8 @@ set(common_headers util/uuid.h version.h zone_store.h + links.h + links.cpp ) source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source Files" FILES ${common_sources}) diff --git a/common/links.cpp b/common/links.cpp new file mode 100644 index 000000000..b8fa758af --- /dev/null +++ b/common/links.cpp @@ -0,0 +1,10 @@ +// +// Created by dannu on 4/18/2026. +// + +#include "links.h" + +std::string Links::FormatSpellLink(uint32_t SpellID, const std::string& SpellName) +{ + return fmt::format("{}63^{}^0^'{}{}", ITEM_TAG_CHAR, SpellID, SpellName.c_str(), ITEM_TAG_CHAR); +} diff --git a/common/links.h b/common/links.h new file mode 100644 index 000000000..49d2e8fab --- /dev/null +++ b/common/links.h @@ -0,0 +1,11 @@ +// +// Created by dannu on 4/18/2026. +// + +#pragma once + +namespace Links +{ + constexpr char ITEM_TAG_CHAR = '\x12'; + std::string FormatSpellLink(uint32_t SpellID, const std::string& SpellName); +} diff --git a/common/patches/tob.cpp b/common/patches/tob.cpp index a57195041..691ae49e9 100644 --- a/common/patches/tob.cpp +++ b/common/patches/tob.cpp @@ -364,7 +364,6 @@ namespace TOB //OUT(inventoryslot); OUT(target_id); - LogNetcode("S->C OP_CastSpell {}", DumpPacketToString(__packet)); FINISH_ENCODE(); } @@ -3652,7 +3651,6 @@ namespace TOB IN(y_pos); IN(x_pos); IN(z_pos); - LogNetcode("C->S OP_CastSpell {}", DumpPacketToString(__packet)); FINISH_DIRECT_DECODE(); } @@ -4864,7 +4862,9 @@ namespace TOB } default: //unsupported etag right now; just pass it as is + message_out.push_back('\x12'); message_out.append(segments[segment_iter]); + message_out.push_back('\x12'); break; } } diff --git a/zone/spells.cpp b/zone/spells.cpp index d20597b5c..409cbadf3 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -94,6 +94,9 @@ #include #include +#include "common/links.h" +#include "common/packet_dump.h" + extern Zone *zone; extern volatile bool is_zone_loaded; extern WorldServer worldserver; @@ -319,6 +322,10 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, // note that CheckFizzle itself doesn't let NPCs fizzle, // but this code allows for it. if (slot < CastingSlot::MaxGems && !CheckFizzle(spell_id)) { + /* + MessageFormat: You miss a note, bringing your song to a close! (TOB: You miss a note, bringing your %1 to a close!) + MessageFormat: Your spell fizzles! (TOB: Your %1 spell fizzles!) + */ int fizzle_msg = IsBardSong(spell_id) ? MISS_NOTE : SPELL_FIZZLE; uint32 use_mana = ((spells[spell_id].mana) / 4); @@ -328,10 +335,16 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, Mob::SetMana(GetMana() - use_mana); // We send StopCasting which will update mana StopCasting(); - MessageString(Chat::SpellFailure, fizzle_msg); + // TODO: can handle spell name overrides here + std::string spell_name(GetSpellName(spell_id)); + std::string spell_link = Links::FormatSpellLink(spell_id, spell_name); + + // pre-TOB clients will just discard the extra argument here, so don't worry about patching them out in patches + MessageString(Chat::SpellFailure, fizzle_msg, spell_link.c_str()); /** * Song Failure message + * pre-TOB clients will just discard the extra argument here, so don't worry about patching them out in patches */ entity_list.FilteredMessageCloseString( this, @@ -342,11 +355,11 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, (fizzle_msg == MISS_NOTE ? MISSED_NOTE_OTHER : SPELL_FIZZLE_OTHER), 0, /* - MessageFormat: You miss a note, bringing your song to a close! (if missed note) - MessageFormat: A missed note brings %1's song to a close! - MessageFormat: %1's spell fizzles! + MessageFormat: A missed note brings %1's song to a close! (TOB: A missed note brings %1's %2 to a close!) + MessageFormat: %1's spell fizzles! (TOB: %1's %2 spell fizzles!) */ - GetName() + GetName(), + spell_link.c_str() ); TryTriggerOnCastRequirement(); @@ -1299,14 +1312,20 @@ void Mob::InterruptSpell(uint16 message, uint16 color, uint16 spellid) if(!message) message = IsBardSong(spellid) ? SONG_ENDS_ABRUPTLY : INTERRUPT_SPELL; + // TODO: can handle spell name overrides here + std::string spellname(GetSpellName(spellid)); + std::string spelllink = Links::FormatSpellLink(spellid, spellname); + // clients need some packets if (IsClient() && message != SONG_ENDS) { // the interrupt message - outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct)); + outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct) + spelllink.size() + 1); InterruptCast_Struct* ic = (InterruptCast_Struct*) outapp->pBuffer; ic->messageid = message; ic->spawnid = GetID(); + // pre-TOB clients will just discard the extra argument here, so don't worry about patching them out in patches + fmt::format_to_n(ic->message, spelllink.size(), "{}", spelllink); outapp->priority = 5; CastToClient()->QueuePacket(outapp); safe_delete(outapp); @@ -1336,11 +1355,12 @@ void Mob::InterruptSpell(uint16 message, uint16 color, uint16 spellid) } // this is the actual message, it works the same as a formatted message - outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct) + strlen(GetCleanName()) + 1); + outapp = new EQApplicationPacket(OP_InterruptCast, sizeof(InterruptCast_Struct) + strlen(GetCleanName()) + spelllink.size() + 2); InterruptCast_Struct* ic = (InterruptCast_Struct*) outapp->pBuffer; ic->messageid = message_other; ic->spawnid = GetID(); - strcpy(ic->message, GetCleanName()); + // pre-TOB clients will just discard the extra argument here, so don't worry about patching them out in patches + fmt::format_to_n(ic->message, sizeof(GetCleanName()) + spelllink.size() + 1, "{}\x00{}", GetCleanName(), spelllink); entity_list.QueueCloseClients(this, outapp, true, RuleI(Range, SongMessages), 0, true, IsClient() ? FilterPCSpells : FilterNPCSpells); safe_delete(outapp); From 348094b881dfe819d84e563b3ce32f33fbbe377a Mon Sep 17 00:00:00 2001 From: ltroylove Date: Mon, 20 Apr 2026 19:50:36 -0500 Subject: [PATCH 14/17] fix: correct off-by-one in GetSpellLevel for Berserker class (#5060) Co-authored-by: Claude Sonnet 4.6 --- common/spdat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/spdat.cpp b/common/spdat.cpp index 1df6e55e5..01bf12efe 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -999,7 +999,7 @@ uint8 GetSpellLevel(uint16 spell_id, uint8 class_id) return UINT8_MAX; } - if (class_id >= Class::PLAYER_CLASS_COUNT) { + if (class_id < Class::Warrior || class_id > Class::PLAYER_CLASS_COUNT) { return UINT8_MAX; } From 1958a12bc798f169758cf4ba82efbc797a2c780b Mon Sep 17 00:00:00 2001 From: ltroylove Date: Mon, 20 Apr 2026 19:51:17 -0500 Subject: [PATCH 15/17] fix: include base skill damage in FlyingKick, Kick, RoundKick, and Bash (#5061) --- zone/bot.cpp | 6 +++--- zone/special_attacks.cpp | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/zone/bot.cpp b/zone/bot.cpp index 8c3f668a4..00662389c 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -5175,7 +5175,7 @@ int Bot::GetBaseSkillDamage(EQ::skills::SkillType skill, Mob *target) ac_bonus = inst->GetItemArmorClass(true) / 25.0f; if (ac_bonus > skill_bonus) ac_bonus = skill_bonus; - return static_cast(ac_bonus + skill_bonus); + return base + static_cast(ac_bonus + skill_bonus); } case EQ::skills::SkillKick: { float skill_bonus = skill_level / 10.0f; @@ -5185,7 +5185,7 @@ int Bot::GetBaseSkillDamage(EQ::skills::SkillType skill, Mob *target) ac_bonus = inst->GetItemArmorClass(true) / 25.0f; if (ac_bonus > skill_bonus) ac_bonus = skill_bonus; - return static_cast(ac_bonus + skill_bonus); + return base + static_cast(ac_bonus + skill_bonus); } case EQ::skills::SkillBash: { float skill_bonus = skill_level / 10.0f; @@ -5199,7 +5199,7 @@ int Bot::GetBaseSkillDamage(EQ::skills::SkillType skill, Mob *target) ac_bonus = inst->GetItemArmorClass(true) / 25.0f; if (ac_bonus > skill_bonus) ac_bonus = skill_bonus; - return static_cast(ac_bonus + skill_bonus); + return base + static_cast(ac_bonus + skill_bonus); } case EQ::skills::SkillBackstab: { float skill_bonus = static_cast(skill_level) * 0.02f; diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 6c367101c..466ab7d58 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -102,10 +102,10 @@ int Mob::GetBaseSkillDamage(EQ::skills::SkillType skill, Mob *target) } if (RuleB(Character, ItemExtraSkillDamageCalcAsPercent) && GetSkillDmgAmt(skill) > 0) { - return static_cast(ac_bonus + skill_bonus) * std::abs(GetSkillDmgAmt(skill) / 100); + return (base + static_cast(ac_bonus + skill_bonus)) * std::abs(GetSkillDmgAmt(skill) / 100); } - return static_cast(ac_bonus + skill_bonus); + return base + static_cast(ac_bonus + skill_bonus); } case EQ::skills::SkillKick: case EQ::skills::SkillRoundKick: { @@ -128,10 +128,10 @@ int Mob::GetBaseSkillDamage(EQ::skills::SkillType skill, Mob *target) } if (RuleB(Character, ItemExtraSkillDamageCalcAsPercent) && GetSkillDmgAmt(skill) > 0) { - return static_cast(ac_bonus + skill_bonus) * std::abs(GetSkillDmgAmt(skill) / 100); + return (base + static_cast(ac_bonus + skill_bonus)) * std::abs(GetSkillDmgAmt(skill) / 100); } - return static_cast(ac_bonus + skill_bonus); + return base + static_cast(ac_bonus + skill_bonus); } case EQ::skills::SkillBash: { float skill_bonus = skill_level / 10.0f; @@ -160,10 +160,10 @@ int Mob::GetBaseSkillDamage(EQ::skills::SkillType skill, Mob *target) } if (RuleB(Character, ItemExtraSkillDamageCalcAsPercent) && GetSkillDmgAmt(skill) > 0) { - return static_cast(ac_bonus + skill_bonus) * std::abs(GetSkillDmgAmt(skill) / 100); + return (base + static_cast(ac_bonus + skill_bonus)) * std::abs(GetSkillDmgAmt(skill) / 100); } - return static_cast(ac_bonus + skill_bonus); + return base + static_cast(ac_bonus + skill_bonus); } case EQ::skills::SkillBackstab: { float skill_bonus = static_cast(skill_level) * 0.02f; From 758774b0bf1a01f71629c3b81ce094da94e687a0 Mon Sep 17 00:00:00 2001 From: ltroylove Date: Mon, 20 Apr 2026 19:51:47 -0500 Subject: [PATCH 16/17] fix: only dismiss pet summoned by the fading familiar buff (#5063) --- zone/spell_effects.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 7b29ca2c5..4930f1154 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -4394,9 +4394,9 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) case SpellEffect::Familiar: { Mob *mypet = GetPet(); - if (mypet){ - if(mypet->IsNPC()) - mypet->CastToNPC()->Depop(); + if (mypet && mypet->IsNPC() && + mypet->CastToNPC()->GetPetSpellID() == buffs[slot].spellid) { + mypet->CastToNPC()->Depop(); SetPetID(0); } break; From 5dc093fe5e5a51b8fdb3ff0e1c7234b039ea0e51 Mon Sep 17 00:00:00 2001 From: xJeris <85350673+xJeris@users.noreply.github.com> Date: Mon, 20 Apr 2026 20:52:23 -0400 Subject: [PATCH 17/17] Add Multiple Mercenary Hire Functionality (#5059) --- common/emu_oplist.h | 1 + common/eq_packet_structs.h | 6 ++ common/patches/rof2.cpp | 11 ++- common/ruletypes.h | 1 + utils/patches/patch_RoF2.conf | 1 + zone/client.cpp | 166 ++++++++++++++++++++++------------ zone/client.h | 1 + zone/client_packet.cpp | 112 +++++++++++++++++++++-- zone/client_packet.h | 1 + zone/merc.cpp | 105 ++++++++++++++------- zone/merc.h | 2 +- zone/zonedb.cpp | 6 +- 12 files changed, 310 insertions(+), 103 deletions(-) diff --git a/common/emu_oplist.h b/common/emu_oplist.h index 1f8366662..e85c83e9e 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -376,6 +376,7 @@ N(OP_MercenaryDismiss), N(OP_MercenaryHire), N(OP_MercenarySuspendRequest), N(OP_MercenarySuspendResponse), +N(OP_MercenarySwitch), N(OP_MercenaryTimer), N(OP_MercenaryTimerRequest), N(OP_MercenaryUnknown1), diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index c11945cc4..d6736c071 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -6236,6 +6236,12 @@ struct SuspendMercenary_Struct { /*0001*/ }; +// [OPCode: 0x1b37 (RoF2)] [Client->Server] [Size: 4] +struct SwitchMercenary_Struct { +/*0000*/ uint32 MercIndex; // 0-based UI index into owned merc list +/*0004*/ +}; + // [OPCode: 0x2528] On Live as of April 2 2012 [Server->Client] [Size: 4] // Response to suspend merc with timestamp struct SuspendMercenaryResponse_Struct { diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 992f0c538..a2b3ba30e 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -2278,15 +2278,19 @@ namespace RoF2 // There are 2 different sized versions of this packet depending if a merc is hired or not if (emu->MercStatus >= 0) { - PacketSize += sizeof(structs::MercenaryDataUpdate_Struct) + (sizeof(structs::MercenaryData_Struct) - sizeof(structs::MercenaryStance_Struct)) * emu->MercCount; - + // Per-merc size: base struct minus Stances[1] and MercUnk05, + // then add back actual stances and name length per merc. + // MercUnk05 is a single trailing field after all mercs. + PacketSize += sizeof(structs::MercenaryDataUpdate_Struct); uint32 r; uint32 k; for (r = 0; r < emu->MercCount; r++) { + PacketSize += sizeof(structs::MercenaryData_Struct) - sizeof(structs::MercenaryStance_Struct) - sizeof(uint32); // subtract Stances[1] and MercUnk05 PacketSize += sizeof(structs::MercenaryStance_Struct) * emu->MercData[r].StanceCount; PacketSize += strlen(emu->MercData[r].MercName); // Null Terminator size already accounted for in the struct } + PacketSize += sizeof(uint32); // MercUnk05 - trailing field after all mercs outapp = new EQApplicationPacket(OP_MercenaryDataUpdate, PacketSize); Buffer = (char *)outapp->pBuffer; @@ -2312,15 +2316,14 @@ namespace RoF2 VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].StanceCount); VARSTRUCT_ENCODE_TYPE(int32, Buffer, emu->MercData[r].MercUnk03); VARSTRUCT_ENCODE_TYPE(uint8, Buffer, emu->MercData[r].MercUnk04); - //VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // MercName VARSTRUCT_ENCODE_STRING(Buffer, emu->MercData[r].MercName); for (k = 0; k < emu->MercData[r].StanceCount; k++) { VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].Stances[k].StanceIndex); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[r].Stances[k].Stance); } - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 1); // MercUnk05 } + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->MercData[0].MercUnk05); // MercUnk05 - trailing field (unlocked slot count) } else { diff --git a/common/ruletypes.h b/common/ruletypes.h index d9df94afd..97a235e55 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -255,6 +255,7 @@ RULE_BOOL(Mercs, AllowMercSuspendInCombat, true, "Allow merc suspend in combat") RULE_BOOL(Mercs, MercsIgnoreLevelBasedHasteCaps, false, "Ignores hard coded level based haste caps.") RULE_INT(Mercs, MercsHasteCap, 100, "Haste cap for non-v3(over haste) haste") RULE_INT(Mercs, MercsHastev3Cap, 25, "Haste cap for v3(over haste) haste") +RULE_INT(Mercs, MaxMercSlots, 6, "Maximum number of mercenary slots per character (max = MAXMERCS)") RULE_CATEGORY_END() RULE_CATEGORY(Guild) diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 3533aaa87..e5d9173cf 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -418,6 +418,7 @@ OP_MercenaryUnknown1=0x5d26 OP_MercenaryCommand=0x27f2 OP_MercenarySuspendRequest=0x4407 OP_MercenarySuspendResponse=0x6f03 +OP_MercenarySwitch=0x1b37 OP_MercenaryUnsuspendResponse=0x27a0 # Looting diff --git a/zone/client.cpp b/zone/client.cpp index 56d59c4b3..e33650c7e 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -1037,7 +1037,7 @@ bool Client::Save(uint8 iCommitNow) { } if (dead || (!GetMerc() && !GetMercInfo().IsSuspended)) { - memset(&m_mercinfo, 0, sizeof(struct MercInfo)); + memset(&m_mercinfo, 0, sizeof(m_mercinfo)); } m_pp.lastlogin = time(nullptr); @@ -7940,75 +7940,125 @@ void Client::SendWebLink(const char *website) void Client::SendMercPersonalInfo() { - uint32 mercTypeCount = 1; - uint32 mercCount = 1; //TODO: Un-hardcode this and support multiple mercs like in later clients than SoD. - uint32 i = 0; uint32 altCurrentType = 19; //TODO: Implement alternate currency purchases involving mercs! - MercTemplate *mercData = &zone->merc_templates[GetMercInfo().MercTemplateID]; - - int stancecount = 0; - stancecount += zone->merc_stance_list[GetMercInfo().MercTemplateID].size(); - if(stancecount > MAX_MERC_STANCES || mercCount > MAX_MERC || mercTypeCount > MAX_MERC_GRADES) - { - Log(Logs::General, Logs::Mercenaries, "SendMercPersonalInfo canceled: (%i) (%i) (%i) for %s", stancecount, mercCount, mercTypeCount, GetName()); - SendMercMerchantResponsePacket(0); - return; - } - if (ClientVersion() >= EQ::versions::ClientVersion::RoF) { - auto outapp = new EQApplicationPacket(OP_MercenaryDataUpdate, sizeof(MercenaryDataUpdate_Struct)); - auto mdus = (MercenaryDataUpdate_Struct *) outapp->pBuffer; - - mdus->MercStatus = 0; - mdus->MercCount = mercCount; - mdus->MercData[i].MercID = mercData->MercTemplateID; - mdus->MercData[i].MercType = mercData->MercType; - mdus->MercData[i].MercSubType = mercData->MercSubType; - mdus->MercData[i].PurchaseCost = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), 0); - mdus->MercData[i].UpkeepCost = Merc::CalcUpkeepCost(mercData->MercTemplateID, GetLevel(), 0); - mdus->MercData[i].Status = 0; - mdus->MercData[i].AltCurrencyCost = Merc::CalcPurchaseCost( - mercData->MercTemplateID, - GetLevel(), - altCurrentType - ); - mdus->MercData[i].AltCurrencyUpkeep = Merc::CalcPurchaseCost( - mercData->MercTemplateID, - GetLevel(), - altCurrentType - ); - mdus->MercData[i].AltCurrencyType = altCurrentType; - mdus->MercData[i].MercUnk01 = 0; - mdus->MercData[i].TimeLeft = GetMercInfo().MercTimerRemaining; //GetMercTimer().GetRemainingTime(); - mdus->MercData[i].MerchantSlot = i + 1; - mdus->MercData[i].MercUnk02 = 1; - mdus->MercData[i].StanceCount = zone->merc_stance_list[mercData->MercTemplateID].size(); - mdus->MercData[i].MercUnk03 = 0; - mdus->MercData[i].MercUnk04 = 1; - - strn0cpy(mdus->MercData[i].MercName, GetMercInfo().merc_name, sizeof(mdus->MercData[i].MercName)); - - uint32 stanceindex = 0; - if (mdus->MercData[i].StanceCount != 0) { - auto iter = zone->merc_stance_list[mercData->MercTemplateID].begin(); - while (iter != zone->merc_stance_list[mercData->MercTemplateID].end()) { - mdus->MercData[i].Stances[stanceindex].StanceIndex = stanceindex; - mdus->MercData[i].Stances[stanceindex].Stance = (iter->StanceID); - stanceindex++; - ++iter; - } + // Count owned mercs across all slots + uint32 mercCount = GetNumberOfMercenaries(); + if (mercCount == 0) { + SendClearMercInfo(); + return; } - mdus->MercData[i].MercUnk05 = 1; + uint32 packetSize = sizeof(MercenaryDataUpdate_Struct); + auto outapp = new EQApplicationPacket(OP_MercenaryDataUpdate, packetSize); + memset(outapp->pBuffer, 0, packetSize); + auto mdus = (MercenaryDataUpdate_Struct *) outapp->pBuffer; + + mdus->MercStatus = 0; + mdus->MercCount = mercCount; + + // Lambda to populate a single merc entry in the packet + int max_slots = std::min(RuleI(Mercs, MaxMercSlots), MAXMERCS); + uint32 merc_index = 0; + + auto fillMercEntry = [&](int slot) { + auto& info = GetMercInfo(slot); + if (info.mercid == 0 || merc_index >= MAX_MERC) { + return; + } + + auto tmpl_it = zone->merc_templates.find(info.MercTemplateID); + if (tmpl_it == zone->merc_templates.end()) { + return; + } + + MercTemplate *mercData = &tmpl_it->second; + uint32 stancecount = 0; + auto stance_it = zone->merc_stance_list.find(mercData->MercTemplateID); + if (stance_it != zone->merc_stance_list.end()) { + stancecount = stance_it->second.size(); + } + + if (stancecount > MAX_MERC_STANCES) { + Log(Logs::General, Logs::Mercenaries, "SendMercPersonalInfo: stance count %u exceeds max for slot %i, skipping", stancecount, slot); + return; + } + + mdus->MercData[merc_index].MercID = mercData->MercTemplateID; + mdus->MercData[merc_index].MercType = mercData->MercType; + mdus->MercData[merc_index].MercSubType = mercData->MercSubType; + mdus->MercData[merc_index].PurchaseCost = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), 0); + mdus->MercData[merc_index].UpkeepCost = Merc::CalcUpkeepCost(mercData->MercTemplateID, GetLevel(), 0); + mdus->MercData[merc_index].Status = info.IsSuspended ? 0 : 1; + mdus->MercData[merc_index].AltCurrencyCost = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), altCurrentType); + mdus->MercData[merc_index].AltCurrencyUpkeep = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), altCurrentType); + mdus->MercData[merc_index].AltCurrencyType = altCurrentType; + mdus->MercData[merc_index].MercUnk01 = 0; + mdus->MercData[merc_index].TimeLeft = info.MercTimerRemaining; + mdus->MercData[merc_index].MerchantSlot = merc_index + 1; + mdus->MercData[merc_index].MercUnk02 = (slot == GetMercSlot()) ? 1 : 0; + mdus->MercData[merc_index].StanceCount = stancecount; + mdus->MercData[merc_index].MercUnk03 = 0; + mdus->MercData[merc_index].MercUnk04 = 1; + + strn0cpy(mdus->MercData[merc_index].MercName, info.merc_name, sizeof(mdus->MercData[merc_index].MercName)); + + uint32 stanceindex = 0; + if (stance_it != zone->merc_stance_list.end()) { + for (const auto& stance : stance_it->second) { + mdus->MercData[merc_index].Stances[stanceindex].StanceIndex = stanceindex; + mdus->MercData[merc_index].Stances[stanceindex].Stance = stance.StanceID; + stanceindex++; + } + } + + mdus->MercData[merc_index].MercUnk05 = std::min(RuleI(Mercs, MaxMercSlots), MAXMERCS); + merc_index++; + }; + + // Emit the active merc slot first — the client marks the first entry + // in the list with the X (active marker), so order matters. + if (GetMercSlot() < max_slots && GetMercInfo().mercid != 0) { + fillMercEntry(GetMercSlot()); + } + + // Then emit remaining owned mercs in slot order + for (int slot = 0; slot < max_slots; slot++) { + if (slot == GetMercSlot()) { + continue; // already emitted + } + fillMercEntry(slot); + } + + // Update count in case we skipped any invalid entries + mdus->MercCount = merc_index; + FastQueuePacket(&outapp); safe_delete(outapp); return; } else { + // Pre-RoF path (SoD and earlier) — single merc only + if (GetMercInfo().MercTemplateID == 0) { + SendClearMercInfo(); + return; + } + + auto tmpl_it = zone->merc_templates.find(GetMercInfo().MercTemplateID); + if (tmpl_it == zone->merc_templates.end()) { + SendClearMercInfo(); + return; + } + + MercTemplate *mercData = &tmpl_it->second; + uint32 mercTypeCount = 1; + uint32 mercCount = 1; + uint32 i = 0; + auto outapp = new EQApplicationPacket(OP_MercenaryDataResponse, sizeof(MercenaryMerchantList_Struct)); auto mml = (MercenaryMerchantList_Struct *) outapp->pBuffer; - mml->MercTypeCount = mercTypeCount; //We should only have one merc entry. + mml->MercTypeCount = mercTypeCount; mml->MercGrades[i] = 1; mml->MercCount = mercCount; diff --git a/zone/client.h b/zone/client.h index 4e9b5e891..62709bbd0 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1773,6 +1773,7 @@ public: MercInfo& GetMercInfo(uint8 slot) { return m_mercinfo[slot]; } MercInfo& GetMercInfo() { return m_mercinfo[mercSlot]; } uint8 GetNumberOfMercenaries(); + int GetFirstFreeMercSlot(); void SetMerc(Merc* newmerc); void SendMercResponsePackets(uint32 ResponseType); void SendMercMerchantResponsePacket(int32 response_type); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 7fc5174f6..13e55df4d 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -305,6 +305,7 @@ void MapOpcodes() ConnectedOpcodes[OP_MercenaryDismiss] = &Client::Handle_OP_MercenaryDismiss; ConnectedOpcodes[OP_MercenaryHire] = &Client::Handle_OP_MercenaryHire; ConnectedOpcodes[OP_MercenarySuspendRequest] = &Client::Handle_OP_MercenarySuspendRequest; + ConnectedOpcodes[OP_MercenarySwitch] = &Client::Handle_OP_MercenarySwitch; ConnectedOpcodes[OP_MercenaryTimerRequest] = &Client::Handle_OP_MercenaryTimerRequest; ConnectedOpcodes[OP_MoveCoin] = &Client::Handle_OP_MoveCoin; ConnectedOpcodes[OP_MoveItem] = &Client::Handle_OP_MoveItem; @@ -10430,7 +10431,7 @@ void Client::Handle_OP_MercenaryCommand(const EQApplicationPacket *app) } MercenaryCommand_Struct* mc = (MercenaryCommand_Struct*)app->pBuffer; - uint32 merc_command = mc->MercCommand; // Seen 0 (zone in with no merc or suspended), 1 (dismiss merc), 5 (normal state), 20 (unknown), 36 (zone in with merc) + uint32 merc_command = mc->MercCommand; // Seen 0 (zone in with no merc or suspended), 1 (stance/state update), 5 (normal state), 20 (unknown), 36 (zone in with merc) int32 option = mc->Option; // Seen -1 (zone in with no merc), 0 (setting to passive stance), 1 (normal or setting to balanced stance) Log(Logs::General, Logs::Mercenaries, "Command %i, Option %i received from %s.", merc_command, option, GetName()); @@ -10438,9 +10439,6 @@ void Client::Handle_OP_MercenaryCommand(const EQApplicationPacket *app) if (!RuleB(Mercs, AllowMercs)) return; - // Handle the Command here... - // Will need a list of what every type of command is supposed to do - // Unsure if there is a server response to this packet if (option >= 0) { Merc* merc = GetMerc(); @@ -10496,14 +10494,14 @@ void Client::Handle_OP_MercenaryDataRequest(const EQApplicationPacket *app) if (merchant_id == 0) { //send info about your current merc(s) - if (GetMercInfo().mercid) + if (GetNumberOfMercenaries() > 0) { Log(Logs::General, Logs::Mercenaries, "SendMercPersonalInfo Request for %s.", GetName()); SendMercPersonalInfo(); } else { - Log(Logs::General, Logs::Mercenaries, "SendMercPersonalInfo Not Sent - MercID (%i) for %s.", GetMercInfo().mercid, GetName()); + Log(Logs::General, Logs::Mercenaries, "SendMercPersonalInfo Not Sent - no mercs owned for %s.", GetName()); } } @@ -10687,6 +10685,22 @@ void Client::Handle_OP_MercenaryHire(const EQApplicationPacket *app) return; } + // Suspend active merc if one exists before hiring into a new slot + Merc* current_merc = GetMerc(); + if (current_merc) { + current_merc->Suspend(); + current_merc->SetOwnerID(0); + SetMercID(0); + } + + // Select a free slot for the new hire + int free_slot = GetFirstFreeMercSlot(); + if (free_slot < 0) { + SendMercResponsePackets(6); + return; + } + SetMercSlot(static_cast(free_slot)); + // Set time remaining to max on Hire GetMercInfo().MercTimerRemaining = RuleI(Mercs, UpkeepIntervalMS); @@ -10706,6 +10720,10 @@ void Client::Handle_OP_MercenaryHire(const EQApplicationPacket *app) // approved hire request SendMercMerchantResponsePacket(0); + + // Update the client's Manage tab with the newly hired merc info + SendMercPersonalInfo(); + SendMercTimer(merc); } else { @@ -10742,6 +10760,88 @@ void Client::Handle_OP_MercenarySuspendRequest(const EQApplicationPacket *app) SuspendMercCommand(); } +void Client::Handle_OP_MercenarySwitch(const EQApplicationPacket *app) +{ + if (app->size != sizeof(SwitchMercenary_Struct)) { + LogDebug("Size mismatch in OP_MercenarySwitch expected [{}] got [{}]", sizeof(SwitchMercenary_Struct), app->size); + DumpPacket(app); + return; + } + + if (!RuleB(Mercs, AllowMercs)) + return; + + SwitchMercenary_Struct* sm = (SwitchMercenary_Struct*)app->pBuffer; + uint32 merc_ui_index = sm->MercIndex; + + Log(Logs::General, Logs::Mercenaries, "Switch request to UI index %u received from %s.", merc_ui_index, GetName()); + + // The client sends a dense UI index (0, 1, 2...) that corresponds to the Nth + // owned merc in the list, matching the order sent by SendMercPersonalInfo(). + // SendMercPersonalInfo emits the active slot first, then remaining slots in order. + // We must replicate that same ordering to map UI index -> internal slot. + int target_slot = -1; + int max_slots = std::min(RuleI(Mercs, MaxMercSlots), MAXMERCS); + uint32 ui_pos = 0; + + // First: the active merc slot (emitted first in the packet) + if (GetMercSlot() < max_slots && m_mercinfo[GetMercSlot()].mercid != 0) { + if (ui_pos == merc_ui_index) { + target_slot = GetMercSlot(); + } + ui_pos++; + } + + // Then: remaining slots in order (skipping active slot) + if (target_slot < 0) { + for (int slot = 0; slot < max_slots; slot++) { + if (slot == GetMercSlot()) { + continue; + } + if (m_mercinfo[slot].mercid != 0) { + if (ui_pos == merc_ui_index) { + target_slot = slot; + break; + } + ui_pos++; + } + } + } + + if (target_slot < 0) { + Log(Logs::General, Logs::Mercenaries, "Switch request denied — UI index %u has no corresponding merc for %s.", merc_ui_index, GetName()); + SendMercResponsePackets(0); + return; + } + + if (target_slot == GetMercSlot()) { + Log(Logs::General, Logs::Mercenaries, "Switch request ignored — already on slot %i for %s.", target_slot, GetName()); + return; + } + + // Suspend the currently active merc if one is spawned + Merc* current_merc = GetMerc(); + if (current_merc) { + current_merc->Suspend(); + // Clear merc pointer without wiping slot data (SetMerc(nullptr) would zero the slot) + current_merc->SetOwnerID(0); + SetMercID(0); + } + + // Clear the suspend timer so the target merc can be unsuspended immediately. + // The cooldown is meant for rapid suspend/unsuspend of the same merc, not for switching. + if (!GetPTimers().Expired(&database, pTimerMercSuspend, false)) { + GetPTimers().Clear(&database, pTimerMercSuspend); + } + + SetMercSlot(static_cast(target_slot)); + + Log(Logs::General, Logs::Mercenaries, "Switched active merc slot to %i (UI index %u) for %s.", target_slot, merc_ui_index, GetName()); + + // Unsuspend the target merc + SuspendMercCommand(); +} + void Client::Handle_OP_MercenaryTimerRequest(const EQApplicationPacket *app) { // The payload is 0 bytes. diff --git a/zone/client_packet.h b/zone/client_packet.h index 009dd75d6..b2a455b97 100644 --- a/zone/client_packet.h +++ b/zone/client_packet.h @@ -239,6 +239,7 @@ void Handle_OP_MercenaryDismiss(const EQApplicationPacket *app); void Handle_OP_MercenaryHire(const EQApplicationPacket *app); void Handle_OP_MercenarySuspendRequest(const EQApplicationPacket *app); + void Handle_OP_MercenarySwitch(const EQApplicationPacket *app); void Handle_OP_MercenaryTimerRequest(const EQApplicationPacket *app); void Handle_OP_MoveCoin(const EQApplicationPacket *app); void Handle_OP_MoveItem(const EQApplicationPacket *app); diff --git a/zone/merc.cpp b/zone/merc.cpp index 283132a2d..d9212db8d 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -4885,14 +4885,8 @@ bool Client::CheckCanHireMerc(Mob* merchant, uint32 template_id) { MercTemplate* mercTemplate = zone->GetMercTemplate(template_id); - //check for suspended merc - if(GetMercInfo().mercid != 0 && GetMercInfo().IsSuspended) { - SendMercResponsePackets(6); - return false; - } - - // Check if max number of mercs is already reached - if(GetNumberOfMercenaries() >= MAXMERCS) { + // Check if all merc slots are full (counts both active and suspended mercs) + if (GetFirstFreeMercSlot() < 0) { SendMercResponsePackets(6); return false; } @@ -5046,8 +5040,21 @@ void Client::SuspendMercCommand() { return; } + // Suspend any currently active merc before unsuspending this one + Merc* active_merc = GetMerc(); + if (active_merc) { + active_merc->Suspend(); + SetMerc(nullptr); + } + // Get merc, assign it to client & spawn - Merc* merc = Merc::LoadMercenary(this, &zone->merc_templates[GetMercInfo().MercTemplateID], 0, true); + auto tmpl_it = zone->merc_templates.find(GetMercInfo().MercTemplateID); + if (tmpl_it == zone->merc_templates.end()) { + SendMercResponsePackets(3); + Log(Logs::General, Logs::Mercenaries, "SuspendMercCommand Invalid template for %s.", GetName()); + return; + } + Merc* merc = Merc::LoadMercenary(this, &tmpl_it->second, 0, true); if(merc) { SpawnMerc(merc, false); @@ -5119,40 +5126,56 @@ void Client::SpawnMercOnZone() { if(database.LoadMercenaryInfo(this)) { - if(!GetMercInfo().IsSuspended) - { + // Find the active (non-suspended) merc slot, or fall back to the first owned slot + int active_slot = -1; + int first_owned_slot = -1; + int max_slots = std::min(RuleI(Mercs, MaxMercSlots), MAXMERCS); + for (int slot = 0; slot < max_slots; slot++) { + if (GetMercInfo(slot).mercid != 0) { + if (first_owned_slot < 0) { + first_owned_slot = slot; + } + if (!GetMercInfo(slot).IsSuspended) { + active_slot = slot; + break; + } + } + } + + if (active_slot >= 0) { + SetMercSlot(static_cast(active_slot)); GetMercInfo().SuspendedTime = 0; // Get merc, assign it to client & spawn - Merc* merc = Merc::LoadMercenary(this, &zone->merc_templates[GetMercInfo().MercTemplateID], 0, true); - if(merc) - { - SpawnMerc(merc, false); + auto tmpl_it = zone->merc_templates.find(GetMercInfo().MercTemplateID); + if (tmpl_it != zone->merc_templates.end()) { + Merc* merc = Merc::LoadMercenary(this, &tmpl_it->second, 0, true); + if (merc) { + SpawnMerc(merc, false); + } } - Log(Logs::General, Logs::Mercenaries, "SpawnMercOnZone Normal Merc for %s.", GetName()); - } - else - { + Log(Logs::General, Logs::Mercenaries, "SpawnMercOnZone Normal Merc (slot %i) for %s.", active_slot, GetName()); + } else if (first_owned_slot >= 0) { + SetMercSlot(static_cast(first_owned_slot)); int32 TimeDiff = GetMercInfo().SuspendedTime - time(nullptr); - if (TimeDiff > 0) - { - if (!GetPTimers().Enabled(pTimerMercSuspend)) - { - // Start the timer to send the packet that refreshes the Unsuspend Button + if (TimeDiff > 0) { + if (!GetPTimers().Enabled(pTimerMercSuspend)) { GetPTimers().Start(pTimerMercSuspend, TimeDiff); } } - // Send Mercenary Status/Timer packet SendMercTimer(GetMerc()); + Log(Logs::General, Logs::Mercenaries, "SpawnMercOnZone Suspended Merc (slot %i) for %s.", first_owned_slot, GetName()); + } else { + Log(Logs::General, Logs::Mercenaries, "SpawnMercOnZone No valid merc slots found for %s.", GetName()); + } - Log(Logs::General, Logs::Mercenaries, "SpawnMercOnZone Suspended Merc for %s.", GetName()); + // Send merc personal info for all owned mercs (populates the Manage tab) + if (GetNumberOfMercenaries() > 0) { + SendMercPersonalInfo(); } } else { - // No Merc Hired - // RoF+ displays a message from the following packet, which seems useless - //SendClearMercInfo(); - Log(Logs::General, Logs::Mercenaries, "SpawnMercOnZone Failed to load Merc Info from the Database for %s.", GetName()); + Log(Logs::General, Logs::Mercenaries, "SpawnMercOnZone No merc info in database for %s.", GetName()); } } @@ -5323,9 +5346,18 @@ bool Client::DismissMerc(uint32 MercID) { GetMerc()->Depop(); } - SendClearMercInfo(); + // Clear the dismissed merc's slot data so it becomes available + memset(&GetMercInfo(), 0, sizeof(MercInfo)); + SetMerc(nullptr); + // Update the client with remaining mercs or clear if none left + if (GetNumberOfMercenaries() > 0) { + SendMercPersonalInfo(); + } else { + SendClearMercInfo(); + } + return Dismissed; } @@ -5579,6 +5611,17 @@ uint8 Client::GetNumberOfMercenaries() return count; } +int Client::GetFirstFreeMercSlot() +{ + int max_slots = std::min(RuleI(Mercs, MaxMercSlots), MAXMERCS); + for (int slot_id = 0; slot_id < max_slots; slot_id++) { + if (m_mercinfo[slot_id].mercid == 0) { + return slot_id; + } + } + return -1; +} + void Merc::SetMercData( uint32 template_id ) { MercTemplate* merc_template = zone->GetMercTemplate(template_id); diff --git a/zone/merc.h b/zone/merc.h index fb27c9f87..0eba77f17 100644 --- a/zone/merc.h +++ b/zone/merc.h @@ -33,7 +33,7 @@ namespace EQ struct ItemData; } -#define MAXMERCS 1 +constexpr int MAXMERCS = 11; #define TANK 1 #define HEALER 2 #define MELEEDPS 9 diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 703f012ad..24414a2d0 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -2225,7 +2225,7 @@ bool ZoneDatabase::LoadCurrentMercenary(Client* c) { const uint8 mercenary_slot = c->GetMercSlot(); - if (mercenary_slot > MAXMERCS) { + if (mercenary_slot >= MAXMERCS) { return false; } @@ -2277,7 +2277,7 @@ bool ZoneDatabase::SaveMercenary(Merc* m) auto e = MercsRepository::NewEntity(); e.OwnerCharacterID = m->GetMercenaryCharacterID(); - e.Slot = (c->GetNumberOfMercenaries() - 1); + e.Slot = c->GetMercSlot(); e.Name = m->GetCleanName(); e.TemplateID = m->GetMercenaryTemplateID(); e.SuspendedTime = c->GetMercInfo().SuspendedTime; @@ -2318,7 +2318,7 @@ bool ZoneDatabase::SaveMercenary(Merc* m) auto e = MercsRepository::FindOne(*this, m->GetMercenaryID()); e.OwnerCharacterID = m->GetMercenaryCharacterID(); - e.Slot = (c->GetNumberOfMercenaries() - 1); + e.Slot = c->GetMercSlot(); e.Name = m->GetCleanName(); e.TemplateID = m->GetMercenaryTemplateID(); e.SuspendedTime = c->GetMercInfo().SuspendedTime;