From 9251e6efd253d2380658747ca566218b75af9a0b Mon Sep 17 00:00:00 2001 From: Trust Date: Sat, 21 Jul 2018 19:16:19 -0400 Subject: [PATCH 1/3] Disarm Support --- common/eq_packet_structs.h | 10 ++++ zone/client.cpp | 56 +++++++++++++++++++++++ zone/client.h | 1 + zone/client_packet.cpp | 93 ++++++++++++++++++++++++++++++++++++++ zone/client_packet.h | 1 + zone/npc.cpp | 60 ++++++++++++++++++++++++ zone/npc.h | 1 + 7 files changed, 222 insertions(+) diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index ced99f573..a7a2b8626 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -1783,6 +1783,16 @@ struct CombatAbility_Struct { uint32 m_skill; }; +// Disarm Struct incoming from Client [Size: 16] +// Haynar - 24 Jan 2011 +struct Disarm_Struct +{ + uint32 source; + uint32 target; + uint32 skill; + uint32 unknown; +}; + //Instill Doubt struct Instill_Doubt_Struct { uint8 i_id; diff --git a/zone/client.cpp b/zone/client.cpp index 3339deb9c..3627149f3 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -2682,6 +2682,62 @@ void Client::LogMerchant(Client* player, Mob* merchant, uint32 quantity, uint32 } } +void Client::Disarm(Client* disarmer, int chance) { + int16 slot = -1; + const EQEmu::ItemInstance *inst = this->GetInv().GetItem(EQEmu::invslot::slotPrimary); + if (inst && inst->IsWeapon()) { + slot = EQEmu::invslot::slotPrimary; + } + else { + inst = this->GetInv().GetItem(EQEmu::invslot::slotSecondary); + if (inst && inst->IsWeapon()) + slot = EQEmu::invslot::slotSecondary; + } + if (slot != -1 && inst->IsClassCommon()) { + // We have an item that can be disarmed. + if (zone->random.Int(0, 1000) <= chance) { + // Find a free inventory slot + int16 slot_id = -1; + slot_id = m_inv.FindFreeSlot(false, true, inst->GetItem()->Size, inst->GetItem()->ItemType); + if (slot_id != -1) + { + EQEmu::ItemInstance *InvItem = m_inv.PopItem(slot); + if (InvItem) { // there should be no way it is not there, but check anyway + EQApplicationPacket* outapp = new EQApplicationPacket(OP_MoveItem, sizeof(MoveItem_Struct)); + MoveItem_Struct* mi = (MoveItem_Struct*)outapp->pBuffer; + mi->from_slot = slot; + mi->to_slot = 0xFFFFFFFF; + if (inst->IsStackable()) // it should not be stackable + mi->number_in_stack = inst->GetCharges(); + else + mi->number_in_stack = 0; + FastQueuePacket(&outapp); // this deletes item from the weapon slot on the client + if (PutItemInInventory(slot_id, *InvItem, true)) + database.SaveInventory(this->CharacterID(), NULL, slot); + int matslot = slot == EQEmu::invslot::slotPrimary ? EQEmu::textures::weaponPrimary : EQEmu::textures::weaponSecondary; + if (matslot != -1) + SendWearChange(matslot); + } + Message(MT_Skills, "You have been disarmed!"); + if (disarmer != this) + disarmer->Message(MT_Skills, StringFormat("You have successfully disarmed %s", this->GetCleanName()).c_str()); + // Message_StringID(MT_Skills, DISARM_SUCCESS, this->GetCleanName()); + if (chance != 1000) + disarmer->CheckIncreaseSkill(EQEmu::skills::SkillDisarm, nullptr, 4); + CalcBonuses(); + // CalcEnduranceWeightFactor(); + return; + } + disarmer->Message(MT_Skills, StringFormat("You have failed to disarm your target").c_str()); + //disarmer->Message_StringID(MT_Skills, DISARM_FAILED); + if (chance != 1000) + disarmer->CheckIncreaseSkill(EQEmu::skills::SkillDisarm, nullptr, 2); + return; + } + } + disarmer->Message(MT_Skills, StringFormat("You have failed to disarm your target").c_str()); +} + bool Client::BindWound(Mob *bindmob, bool start, bool fail) { EQApplicationPacket *outapp = nullptr; diff --git a/zone/client.h b/zone/client.h index 77b509e4f..ead552f73 100644 --- a/zone/client.h +++ b/zone/client.h @@ -803,6 +803,7 @@ public: void NPCSpawn(NPC *target_npc, const char *identifier, uint32 extra = 0); + void Disarm(Client* disarmer, int chance); bool BindWound(Mob* bindmob, bool start, bool fail = false); void SetTradeskillObject(Object* object) { m_tradeskill_object = object; } Object* GetTradeskillObject() { return m_tradeskill_object; } diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 43e10b424..754d9d9cd 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -181,6 +181,7 @@ void MapOpcodes() ConnectedOpcodes[OP_DeleteItem] = &Client::Handle_OP_DeleteItem; ConnectedOpcodes[OP_DeleteSpawn] = &Client::Handle_OP_DeleteSpawn; ConnectedOpcodes[OP_DeleteSpell] = &Client::Handle_OP_DeleteSpell; + ConnectedOpcodes[OP_Disarm] = &Client::Handle_OP_Disarm; ConnectedOpcodes[OP_DisarmTraps] = &Client::Handle_OP_DisarmTraps; ConnectedOpcodes[OP_DoGroupLeadershipAbility] = &Client::Handle_OP_DoGroupLeadershipAbility; ConnectedOpcodes[OP_DuelResponse] = &Client::Handle_OP_DuelResponse; @@ -5324,6 +5325,98 @@ void Client::Handle_OP_DeleteSpawn(const EQApplicationPacket *app) return; } +void Client::Handle_OP_Disarm(const EQApplicationPacket *app) { + if (dead || bZoning) return; + if (!HasSkill(EQEmu::skills::SkillDisarm)) + return; + + if (app->size != sizeof(Disarm_Struct)) { + Log(Logs::General, Logs::Skills, "Size mismatch for Disarm_Struct packet"); + return; + } + + Disarm_Struct *disarm = (Disarm_Struct *)app->pBuffer; + + if (!p_timers.Expired(&database, pTimerCombatAbility2, false)) { + Message(13, "Ability recovery time not yet met."); + return; + } + + p_timers.Start(pTimerCombatAbility2, 8); + + BreakInvis(); + Mob* pmob = entity_list.GetMob(disarm->source); + Mob* tmob = entity_list.GetMob(disarm->target); + if (!pmob || !tmob) + return; + if (pmob->GetID() != GetID()) { + // Client sent a disarm request with an originator ID not matching their own ID. + char *hack_str = NULL; + MakeAnyLenString(&hack_str, "Player %s (%d) sent OP_Disarm with source ID of: %d", GetCleanName(), GetID(), pmob->GetID()); + database.SetMQDetectionFlag(this->account_name, this->name, hack_str, zone->GetShortName()); + safe_delete_array(hack_str); + return; + } + // No disarm on corpses + if (tmob->IsCorpse()) + return; + // No target + if (!GetTarget()) + return; + // Targets don't match (possible hack, but not flagging) + if (GetTarget() != tmob) { + return; + } + // Too far away + if (pmob->CalculateDistance(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ()) > 400) + return; + + // Can't see mob + //if (tmob->BehindMob(pmob)) + // return; + // How can we disarm someone if we are feigned. + if (GetFeigned()) + return; + // We can't disarm someone who is feigned. + if (tmob->IsClient() && tmob->CastToClient()->GetFeigned()) + return; + if (GetTarget() == tmob && pmob == this->CastToMob() && + disarm->skill == GetSkill(EQEmu::skills::SkillDisarm) && IsAttackAllowed(tmob)) { + int p_level = pmob->GetLevel() ? pmob->GetLevel() : 1; + int t_level = tmob->GetLevel() ? tmob->GetLevel() : 1; + // We have a disarmable target - sucess or fail, we always aggro the mob + if (tmob->IsNPC()) { + if (!tmob->CheckAggro(pmob)) { + zone->AddAggroMob(); + tmob->AddToHateList(pmob, p_level); + } + else { + tmob->AddToHateList(pmob, p_level / 3); + } + } + int chance = GetSkill(EQEmu::skills::SkillDisarm); // (1% @ 0 skill) (11% @ 200 skill) - against even con + chance /= 2; + chance += 10; + // Modify chance based on level difference + float lvl_mod = p_level / t_level; + chance *= lvl_mod; + if (chance > 300) + chance = 300; // max chance of 30% + if (tmob->IsNPC()) { + tmob->CastToNPC()->Disarm(this, chance); + } + else if (tmob->IsClient()) { + tmob->CastToClient()->Disarm(this, chance); + } + return; + } + // Trying to disarm something we can't disarm + Message(13, "Your attempt to disarm your target has failed."); + // Message_StringID(MT_Skills, DISARM_FAILED); + + return; +} + void Client::Handle_OP_DeleteSpell(const EQApplicationPacket *app) { if (app->size != sizeof(DeleteSpell_Struct)) diff --git a/zone/client_packet.h b/zone/client_packet.h index 4f9902599..9605dc8cf 100644 --- a/zone/client_packet.h +++ b/zone/client_packet.h @@ -94,6 +94,7 @@ void Handle_OP_DeleteItem(const EQApplicationPacket *app); void Handle_OP_DeleteSpawn(const EQApplicationPacket *app); void Handle_OP_DeleteSpell(const EQApplicationPacket *app); + void Handle_OP_Disarm(const EQApplicationPacket *app); void Handle_OP_DisarmTraps(const EQApplicationPacket *app); void Handle_OP_DoGroupLeadershipAbility(const EQApplicationPacket *app); void Handle_OP_DuelResponse(const EQApplicationPacket *app); diff --git a/zone/npc.cpp b/zone/npc.cpp index 917dafe9b..af5f8da54 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -1691,6 +1691,66 @@ void NPC::PickPocket(Client* thief) thief->SendPickPocketResponse(this, 0, PickPocketFailed); } +void NPC::Disarm(Client* client, int chance) { + // disarm primary if available, otherwise disarm secondary + const EQEmu::ItemData* weapon = NULL; + uint8 eslot = 0xFF; + if (equipment[EQEmu::invslot::slotPrimary] != 0) + eslot = EQEmu::invslot::slotPrimary; + else if (equipment[EQEmu::invslot::slotSecondary] != 0) + eslot = EQEmu::invslot::slotSecondary; + if (eslot != 0xFF) { + if (zone->random.Int(0, 1000) <= chance) { + weapon = database.GetItem(equipment[eslot]); + if (weapon) { + if (!weapon->Magic && weapon->NoDrop == 255) { + int16 charges = -1; + ItemList::iterator cur, end; + cur = itemlist.begin(); + end = itemlist.end(); + // Get charges for the item in the loot table + for (; cur != end; cur++) { + ServerLootItem_Struct* citem = *cur; + if (citem->item_id == weapon->ID) { + charges = citem->charges; + break; + } + } + EQEmu::ItemInstance *inst = NULL; + inst = database.CreateItem(weapon->ID, charges); + // Remove item from loot table + RemoveItem(weapon->ID); + CalcBonuses(); + if (inst) { + // create a ground item + Object* object = new Object(inst, this->GetX(), this->GetY(), this->GetZ(), 0.0f, 300000); + entity_list.AddObject(object, true); + object->StartDecay(); + safe_delete(inst); + } + } + } + // Update Appearance + equipment[eslot] = 0; + int matslot = eslot == EQEmu::invslot::slotPrimary ? EQEmu::textures::weaponPrimary : EQEmu::textures::weaponSecondary; + if (matslot != -1) + SendWearChange(matslot); + if ((CastToMob()->GetBodyType() == BT_Humanoid || CastToMob()->GetBodyType() == BT_Summoned) && eslot == EQEmu::invslot::slotPrimary) + Say("Ahh! My weapon!"); + client->Message(MT_Skills, "You have successfully disarmed your target."); + //client->Message_StringID(MT_Skills, DISARM_SUCCESS, this->GetCleanName()); + if (chance != 1000) + client->CheckIncreaseSkill(EQEmu::skills::SkillDisarm, nullptr, 4); + return; + } + client->Message(MT_Skills, StringFormat("You have failed to disarm your target").c_str()); + if (chance != 1000) + client->CheckIncreaseSkill(EQEmu::skills::SkillDisarm, nullptr, 2); + return; + } + client->Message(MT_Skills, StringFormat("You have failed to disarm your target").c_str()); +} + void Mob::NPCSpecialAttacks(const char* parse, int permtag, bool reset, bool remove) { if(reset) { diff --git a/zone/npc.h b/zone/npc.h index 0201d3b9f..cba67c698 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -278,6 +278,7 @@ public: void SetTaunting(bool tog) {taunting = tog;} bool IsTaunting() const { return taunting; } void PickPocket(Client* thief); + void Disarm(Client* client, int chance); void StartSwarmTimer(uint32 duration) { swarm_timer.Start(duration); } void AddLootDrop(const EQEmu::ItemData*dbitem, ItemList* itemlistconst, int16 charges, uint8 minlevel, uint8 maxlevel, bool equipit, bool wearchange = false, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0, uint32 aug6 = 0); virtual void DoClassAttacks(Mob *target); From 9fa377303e9c9a95e3197dbcb8672cefd97bc28d Mon Sep 17 00:00:00 2001 From: Trust Date: Sat, 21 Jul 2018 22:20:53 -0400 Subject: [PATCH 2/3] Corrected Disarm StringID's --- common/eq_packet_structs.h | 1 - zone/client.cpp | 8 +++----- zone/string_ids.h | 3 +++ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index a7a2b8626..a35c264e0 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -1784,7 +1784,6 @@ struct CombatAbility_Struct { }; // Disarm Struct incoming from Client [Size: 16] -// Haynar - 24 Jan 2011 struct Disarm_Struct { uint32 source; diff --git a/zone/client.cpp b/zone/client.cpp index 3627149f3..e2b3b2496 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -2718,18 +2718,16 @@ void Client::Disarm(Client* disarmer, int chance) { if (matslot != -1) SendWearChange(matslot); } - Message(MT_Skills, "You have been disarmed!"); + Message_StringID(MT_Skills, DISARMED); if (disarmer != this) - disarmer->Message(MT_Skills, StringFormat("You have successfully disarmed %s", this->GetCleanName()).c_str()); - // Message_StringID(MT_Skills, DISARM_SUCCESS, this->GetCleanName()); + disarmer->Message_StringID(MT_Skills, DISARM_SUCCESS, this->GetCleanName()); if (chance != 1000) disarmer->CheckIncreaseSkill(EQEmu::skills::SkillDisarm, nullptr, 4); CalcBonuses(); // CalcEnduranceWeightFactor(); return; } - disarmer->Message(MT_Skills, StringFormat("You have failed to disarm your target").c_str()); - //disarmer->Message_StringID(MT_Skills, DISARM_FAILED); + disarmer->Message_StringID(MT_Skills, DISARM_FAILED); if (chance != 1000) disarmer->CheckIncreaseSkill(EQEmu::skills::SkillDisarm, nullptr, 2); return; diff --git a/zone/string_ids.h b/zone/string_ids.h index a760f8bbe..7ebbbf874 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -446,6 +446,9 @@ #define TRY_ATTACKING_SOMEONE 12696 //Try attacking someone other than yourself, it's more productive #define RANGED_TOO_CLOSE 12698 //Your target is too close to use a ranged weapon! #define BACKSTAB_WEAPON 12874 //You need a piercing weapon as your primary weapon in order to backstab +#define DISARMED 12889 //You have been disarmed! +#define DISARM_SUCCESS 12890 //You disarmed %1! +#define DISARM_FAILED 12891 //Your attempt to disarm failed. #define MORE_SKILLED_THAN_I 12931 //%1 tells you, 'You are more skilled than I! What could I possibly teach you?' #define SURNAME_EXISTS 12939 //You already have a surname. Operation failed. #define SURNAME_LEVEL 12940 //You can only submit a surname upon reaching the 20th level. Operation failed. From 2fe923457b080e171c11d59f85f611fe4f77c780 Mon Sep 17 00:00:00 2001 From: Trust Date: Sat, 21 Jul 2018 23:22:14 -0400 Subject: [PATCH 3/3] Fixed more Message_StringID --- zone/client.cpp | 2 +- zone/client_packet.cpp | 3 +-- zone/npc.cpp | 7 +++---- zone/string_ids.h | 1 + 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/zone/client.cpp b/zone/client.cpp index e2b3b2496..6acd203f4 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -2733,7 +2733,7 @@ void Client::Disarm(Client* disarmer, int chance) { return; } } - disarmer->Message(MT_Skills, StringFormat("You have failed to disarm your target").c_str()); + disarmer->Message_StringID(MT_Skills, DISARM_FAILED); } bool Client::BindWound(Mob *bindmob, bool start, bool fail) diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 754d9d9cd..58ea34e32 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -5411,8 +5411,7 @@ void Client::Handle_OP_Disarm(const EQApplicationPacket *app) { return; } // Trying to disarm something we can't disarm - Message(13, "Your attempt to disarm your target has failed."); - // Message_StringID(MT_Skills, DISARM_FAILED); + Message_StringID(MT_Skills, DISARM_NO_TARGET); return; } diff --git a/zone/npc.cpp b/zone/npc.cpp index af5f8da54..e77ae0618 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -1737,18 +1737,17 @@ void NPC::Disarm(Client* client, int chance) { SendWearChange(matslot); if ((CastToMob()->GetBodyType() == BT_Humanoid || CastToMob()->GetBodyType() == BT_Summoned) && eslot == EQEmu::invslot::slotPrimary) Say("Ahh! My weapon!"); - client->Message(MT_Skills, "You have successfully disarmed your target."); - //client->Message_StringID(MT_Skills, DISARM_SUCCESS, this->GetCleanName()); + client->Message_StringID(MT_Skills, DISARM_SUCCESS, this->GetCleanName()); if (chance != 1000) client->CheckIncreaseSkill(EQEmu::skills::SkillDisarm, nullptr, 4); return; } - client->Message(MT_Skills, StringFormat("You have failed to disarm your target").c_str()); + client->Message_StringID(MT_Skills, DISARM_FAILED); if (chance != 1000) client->CheckIncreaseSkill(EQEmu::skills::SkillDisarm, nullptr, 2); return; } - client->Message(MT_Skills, StringFormat("You have failed to disarm your target").c_str()); + client->Message_StringID(MT_Skills, DISARM_FAILED); } void Mob::NPCSpecialAttacks(const char* parse, int permtag, bool reset, bool remove) { diff --git a/zone/string_ids.h b/zone/string_ids.h index 7ebbbf874..2c223ffbd 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -301,6 +301,7 @@ #define CORPSEDRAG_STOPALL 4065 //You stop dragging the corpses. #define CORPSEDRAG_STOP 4066 //You stop dragging the corpse. #define SOS_KEEPS_HIDDEN 4086 //Your Shroud of Stealth keeps you hidden from watchful eyes.␣␣ +#define DISARM_NO_TARGET 4583 //You can't use disarm on that. #define TARGET_TOO_CLOSE 4602 //You are too close to your target. Get farther away. #define WHOALL_NO_RESULTS 5029 //There are no players in EverQuest that match those who filters. #define TELL_QUEUED_MESSAGE 5045 //You told %1 '%T2. %3'