From 0ab0c5c1179ade639e39d8da863d091d34f6ca9e Mon Sep 17 00:00:00 2001 From: Uleat Date: Sat, 3 Dec 2016 18:17:10 -0500 Subject: [PATCH 01/29] Added trade hack detection code --- changelog.txt | 9 +++++++-- common/item_instance.cpp | 26 ++++++++++++++++++++++++++ common/item_instance.h | 2 ++ zone/client.h | 1 + zone/client_packet.cpp | 21 ++++++++++++++++++++- zone/string_ids.h | 1 + zone/trading.cpp | 40 +++++++++++++++++++++++++++------------- 7 files changed, 84 insertions(+), 16 deletions(-) diff --git a/changelog.txt b/changelog.txt index a1a4e264c..33bf2de1a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,7 +1,12 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- -== 21/01/2016 == -Uleat: Disabled RoF+ clients from augmentation items not in their possessions slots (0-29, 9999, 251-330) to abate an exploit in the current code +== 12/03/2016 == +Uleat: Added hack detection to trade code + - If illegal items are found in trade slots when the 'trade' button is clicked, the trade is cancelled and a message is sent to the offending player + - Future revisions will, at a minimum, log the player as a hacker once the quirks have been worked out + +== 12/01/2016 == +Uleat: Disabled RoF+ clients from augmenting items not in their possessions slots (0-29, 9999, 251-330) to abate an exploit in the current code == 10/17/2016 == Uleat: Moved namespace ItemField from item_instance.h to shareddb.cpp - the only place it is used diff --git a/common/item_instance.cpp b/common/item_instance.cpp index 268440dc8..d8d23064e 100644 --- a/common/item_instance.cpp +++ b/common/item_instance.cpp @@ -819,6 +819,32 @@ bool EQEmu::ItemInstance::IsSlotAllowed(int16 slot_id) const { else { return false; } } +bool EQEmu::ItemInstance::IsDroppable(bool recurse) const +{ + if (!m_item) + return false; + /*if (m_ornamentidfile) // not implemented + return false;*/ + if (m_attuned) + return false; + /*if (m_item->FVNoDrop != 0) // not implemented + return false;*/ + if (m_item->NoDrop == 0) + return false; + + if (recurse) { + for (auto iter : m_contents) { + if (!iter.second) + continue; + + if (!iter.second->IsDroppable(recurse)) + return false; + } + } + + return true; +} + void EQEmu::ItemInstance::Initialize(SharedDatabase *db) { // if there's no actual item, don't do anything if (!m_item) diff --git a/common/item_instance.h b/common/item_instance.h index 90dd1403f..6566fd77e 100644 --- a/common/item_instance.h +++ b/common/item_instance.h @@ -188,6 +188,8 @@ namespace EQEmu bool IsSlotAllowed(int16 slot_id) const; + bool IsDroppable(bool recurse = true) const; + bool IsScaling() const { return m_scaling; } bool IsEvolving() const { return (m_evolveLvl >= 1); } uint32 GetExp() const { return m_exp; } diff --git a/zone/client.h b/zone/client.h index 5b2c0a939..8bb996bdf 100644 --- a/zone/client.h +++ b/zone/client.h @@ -855,6 +855,7 @@ public: void SetConsumption(int32 in_hunger, int32 in_thirst); bool CheckTradeLoreConflict(Client* other); + bool CheckTradeNonDroppable(); void LinkDead(); void Insight(uint32 t_id); bool CheckDoubleAttack(); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 0d4294132..0ba79d15f 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -13287,7 +13287,6 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app) other->trade->state = TradeCompleting; trade->state = TradeCompleting; - // should we do this for NoDrop items as well? if (CheckTradeLoreConflict(other) || other->CheckTradeLoreConflict(this)) { Message_StringID(13, TRADE_CANCEL_LORE); other->Message_StringID(13, TRADE_CANCEL_LORE); @@ -13296,6 +13295,26 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app) other->trade->Reset(); trade->Reset(); } + else if (CheckTradeNonDroppable()) { + Message_StringID(13, TRADE_HAS_BEEN_CANCELLED); + other->Message_StringID(13, TRADE_HAS_BEEN_CANCELLED); + this->FinishTrade(this); + other->FinishTrade(other); + other->trade->Reset(); + trade->Reset(); + Message(15, "Hacking activity detected in trade transaction."); + // TODO: query (this) as a hacker + } + else if (other->CheckTradeNonDroppable()) { + Message_StringID(13, TRADE_HAS_BEEN_CANCELLED); + other->Message_StringID(13, TRADE_HAS_BEEN_CANCELLED); + this->FinishTrade(this); + other->FinishTrade(other); + other->trade->Reset(); + trade->Reset(); + other->Message(15, "Hacking activity detected in trade transaction."); + // TODO: query (other) as a hacker + } else { // Audit trade to database for both trade streams other->trade->LogTrade(); diff --git a/zone/string_ids.h b/zone/string_ids.h index e1fb4de7c..b9eba66df 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -255,6 +255,7 @@ #define MEMBER_OF_YOUR_GUILD 1429 #define OFFICER_OF_YOUR_GUILD 1430 #define LEADER_OF_YOUR_GUILD 1431 +#define TRADE_HAS_BEEN_CANCELLED 1449 #define RECEIVED_PLATINUM 1452 //You receive %1 Platinum from %2. #define RECEIVED_GOLD 1453 //You receive %1 Gold from %2. #define RECEIVED_SILVER 1454 //You receive %1 Silver from %2. diff --git a/zone/trading.cpp b/zone/trading.cpp index e150ceb12..85ed824b2 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -967,23 +967,37 @@ bool Client::CheckTradeLoreConflict(Client* other) { if (!other) return true; - // Move each trade slot into free inventory slot - for (int16 i = EQEmu::legacy::TRADE_BEGIN; i <= EQEmu::legacy::TRADE_END; i++){ - const EQEmu::ItemInstance* inst = m_inv[i]; - if (inst && inst->GetItem()) { - if (other->CheckLoreConflict(inst->GetItem())) - return true; - } + for (int16 index = EQEmu::legacy::TRADE_BEGIN; index <= EQEmu::legacy::TRADE_END; ++index) { + const EQEmu::ItemInstance* inst = m_inv[index]; + if (!inst || !inst->GetItem()) + continue; + + if (other->CheckLoreConflict(inst->GetItem())) + return true; } - for (int16 i = EQEmu::legacy::TRADE_BAGS_BEGIN; i <= EQEmu::legacy::TRADE_BAGS_END; i++){ - const EQEmu::ItemInstance* inst = m_inv[i]; + for (int16 index = EQEmu::legacy::TRADE_BAGS_BEGIN; index <= EQEmu::legacy::TRADE_BAGS_END; ++index) { + const EQEmu::ItemInstance* inst = m_inv[index]; + if (!inst || !inst->GetItem()) + continue; - if (inst && inst->GetItem()) { - if (other->CheckLoreConflict(inst->GetItem())) - return true; - } + if (other->CheckLoreConflict(inst->GetItem())) + return true; + } + + return false; +} + +bool Client::CheckTradeNonDroppable() +{ + for (int16 index = EQEmu::legacy::TRADE_BEGIN; index <= EQEmu::legacy::TRADE_END; ++index){ + const EQEmu::ItemInstance* inst = m_inv[index]; + if (!inst) + continue; + + if (!inst->IsDroppable()) + return true; } return false; From baf9336617e329a2d7922675b625413b1752b32e Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Tue, 6 Dec 2016 14:12:11 -0500 Subject: [PATCH 02/29] Fix RoF+ OP_InterruptCast --- common/patches/rof.cpp | 11 ----------- common/patches/rof2.cpp | 11 ----------- common/patches/rof2_ops.h | 1 - common/patches/rof_ops.h | 1 - 4 files changed, 24 deletions(-) diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index 980a4f4ed..2916046be 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -1492,17 +1492,6 @@ namespace RoF FINISH_ENCODE(); } - ENCODE(OP_InterruptCast) - { - ENCODE_LENGTH_EXACT(InterruptCast_Struct); - SETUP_DIRECT_ENCODE(InterruptCast_Struct, structs::InterruptCast_Struct); - - OUT(spawnid); - OUT(messageid); - - FINISH_ENCODE(); - } - ENCODE(OP_ItemLinkResponse) { ENCODE_FORWARD(OP_ItemPacket); } ENCODE(OP_ItemPacket) diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index d14008e93..d56791bef 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -1560,17 +1560,6 @@ namespace RoF2 FINISH_ENCODE(); } - ENCODE(OP_InterruptCast) - { - ENCODE_LENGTH_EXACT(InterruptCast_Struct); - SETUP_DIRECT_ENCODE(InterruptCast_Struct, structs::InterruptCast_Struct); - - OUT(spawnid); - OUT(messageid); - - FINISH_ENCODE(); - } - ENCODE(OP_ItemLinkResponse) { ENCODE_FORWARD(OP_ItemPacket); } ENCODE(OP_ItemPacket) diff --git a/common/patches/rof2_ops.h b/common/patches/rof2_ops.h index 1ca7063f4..2cd81aaee 100644 --- a/common/patches/rof2_ops.h +++ b/common/patches/rof2_ops.h @@ -85,7 +85,6 @@ E(OP_HPUpdate) E(OP_Illusion) E(OP_InspectBuffs) E(OP_InspectRequest) -E(OP_InterruptCast) E(OP_ItemLinkResponse) E(OP_ItemPacket) E(OP_ItemVerifyReply) diff --git a/common/patches/rof_ops.h b/common/patches/rof_ops.h index 45221cfc5..9030a480b 100644 --- a/common/patches/rof_ops.h +++ b/common/patches/rof_ops.h @@ -70,7 +70,6 @@ E(OP_HPUpdate) E(OP_Illusion) E(OP_InspectBuffs) E(OP_InspectRequest) -E(OP_InterruptCast) E(OP_ItemLinkResponse) E(OP_ItemPacket) E(OP_ItemVerifyReply) From 95efc3a66c2c5937867e077905d2a5c66a8b1559 Mon Sep 17 00:00:00 2001 From: Paul Coene Date: Thu, 8 Dec 2016 16:06:32 -0500 Subject: [PATCH 03/29] Undo changes --- zone/attack.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/zone/attack.cpp b/zone/attack.cpp index 475aee7b3..3b69380f0 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -1217,8 +1217,14 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b CommonBreakInvisibleFromCombat(); + // Defensive procs from opponent happen here. + // We, the attacker check for them and make them happen. + // Added 2nd check for spell based defensive procs on opponent. if(GetTarget()) + { TriggerDefensiveProcs(other, Hand, true, damage); + TriggerDefensiveProcs(other, Hand, false); + } if (damage > 0) return true; @@ -1783,8 +1789,14 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool TrySkillProc(other, skillinuse, 0, true, Hand); } + // Defensive procs from opponent happen here. + // We, the attacker check for them and make them happen. + // Added 2nd check for spell based defensive procs on opponent. if(GetHP() > 0 && !other->HasDied()) + { TriggerDefensiveProcs(other, Hand, true, damage); + TriggerDefensiveProcs(other, Hand, false); + } if (damage > 0) return true; From 246f770e8f80865a2d93b6ccd4b34d6069e93300 Mon Sep 17 00:00:00 2001 From: Paul Coene Date: Sun, 27 Nov 2016 16:43:43 -0500 Subject: [PATCH 04/29] Fix fishing messages so when actual fish are caught, name is in message. --- zone/forage.cpp | 14 +++++++++++++- zone/string_ids.h | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/zone/forage.cpp b/zone/forage.cpp index aec4d6b73..8331560fd 100644 --- a/zone/forage.cpp +++ b/zone/forage.cpp @@ -221,6 +221,8 @@ bool Client::CanFish() { void Client::GoFish() { + bool junk=false; + //TODO: generate a message if we're already fishing /*if (!fishing_timer.Check()) { //this isn't the right check, may need to add something to the Client class like 'bool is_fishing' Message_StringID(0, ALREADY_FISHING); //You are already fishing! @@ -302,11 +304,21 @@ void Client::GoFish() if(food_id == 0) { int index = zone->random.Int(0, MAX_COMMON_FISH_IDS-1); food_id = common_fish_ids[index]; + if (food_id != 13019) { + junk=true; // Alters the message to "caught something..." + } + } const EQEmu::ItemData* food_item = database.GetItem(food_id); - Message_StringID(MT_Skills, FISHING_SUCCESS); + if (junk == true) { + Message_StringID(MT_Skills, FISHING_SUCCESS); + } + else { + Message_StringID(MT_Skills, FISHING_SUCCESS_FISH_NAME, food_item->Name); + } + EQEmu::ItemInstance* inst = database.CreateItem(food_item, 1); if(inst != nullptr) { if(CheckLoreConflict(inst->GetItem())) diff --git a/zone/string_ids.h b/zone/string_ids.h index b9eba66df..67dd808ab 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -46,6 +46,7 @@ #define FISHING_FAILED 168 //You didn't catch anything. #define FISHING_POLE_BROKE 169 //Your fishing pole broke! #define FISHING_SUCCESS 170 //You caught, something... +#define FISHING_SUCCESS_FISH_NAME 421 //You caught %1! #define FISHING_SPILL_BEER 171 //You spill your beer while bringing in your line. #define FISHING_LOST_BAIT 172 //You lost your bait! #define SPELL_FIZZLE 173 //Your spell fizzles! From f5a0b994dc422b46c9e19e0802fd74d1ec11caa7 Mon Sep 17 00:00:00 2001 From: Paul Coene Date: Sun, 27 Nov 2016 17:02:19 -0500 Subject: [PATCH 05/29] Make message based on item type. Learned that non fish items can come from both tables. --- zone/forage.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/zone/forage.cpp b/zone/forage.cpp index 8331560fd..92fde80b5 100644 --- a/zone/forage.cpp +++ b/zone/forage.cpp @@ -221,8 +221,6 @@ bool Client::CanFish() { void Client::GoFish() { - bool junk=false; - //TODO: generate a message if we're already fishing /*if (!fishing_timer.Check()) { //this isn't the right check, may need to add something to the Client class like 'bool is_fishing' Message_StringID(0, ALREADY_FISHING); //You are already fishing! @@ -304,15 +302,13 @@ void Client::GoFish() if(food_id == 0) { int index = zone->random.Int(0, MAX_COMMON_FISH_IDS-1); food_id = common_fish_ids[index]; - if (food_id != 13019) { - junk=true; // Alters the message to "caught something..." } } const EQEmu::ItemData* food_item = database.GetItem(food_id); - if (junk == true) { + if (food_item->ItemType != ItemTypeFood) { // non-fish oddity Message_StringID(MT_Skills, FISHING_SUCCESS); } else { From d99df2540d11c33bd01822e15e70a2f8e08d13a3 Mon Sep 17 00:00:00 2001 From: Paul Coene Date: Sun, 27 Nov 2016 17:17:26 -0500 Subject: [PATCH 06/29] Fix typo --- zone/forage.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/zone/forage.cpp b/zone/forage.cpp index 92fde80b5..7bb87bf6c 100644 --- a/zone/forage.cpp +++ b/zone/forage.cpp @@ -303,12 +303,10 @@ void Client::GoFish() int index = zone->random.Int(0, MAX_COMMON_FISH_IDS-1); food_id = common_fish_ids[index]; } - - } const EQEmu::ItemData* food_item = database.GetItem(food_id); - if (food_item->ItemType != ItemTypeFood) { // non-fish oddity + if (food_item->ItemType != EQEmu::item::ItemTypeFood) { Message_StringID(MT_Skills, FISHING_SUCCESS); } else { From 6cbb4bcf478f2e10c22c1e4b1606783503ab81f7 Mon Sep 17 00:00:00 2001 From: Paul Coene Date: Thu, 8 Dec 2016 16:15:58 -0500 Subject: [PATCH 07/29] Remove defensive proc changes --- zone/attack.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index 3b69380f0..3359f4eed 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -1223,7 +1223,6 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b if(GetTarget()) { TriggerDefensiveProcs(other, Hand, true, damage); - TriggerDefensiveProcs(other, Hand, false); } if (damage > 0) @@ -1795,7 +1794,6 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool if(GetHP() > 0 && !other->HasDied()) { TriggerDefensiveProcs(other, Hand, true, damage); - TriggerDefensiveProcs(other, Hand, false); } if (damage > 0) From d0e6bb6e072a19ce5d71a27604700ad2d913afd1 Mon Sep 17 00:00:00 2001 From: Paul Coene Date: Thu, 8 Dec 2016 16:17:10 -0500 Subject: [PATCH 08/29] more undos --- zone/attack.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index 3359f4eed..475aee7b3 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -1217,13 +1217,8 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b CommonBreakInvisibleFromCombat(); - // Defensive procs from opponent happen here. - // We, the attacker check for them and make them happen. - // Added 2nd check for spell based defensive procs on opponent. if(GetTarget()) - { TriggerDefensiveProcs(other, Hand, true, damage); - } if (damage > 0) return true; @@ -1788,13 +1783,8 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool TrySkillProc(other, skillinuse, 0, true, Hand); } - // Defensive procs from opponent happen here. - // We, the attacker check for them and make them happen. - // Added 2nd check for spell based defensive procs on opponent. if(GetHP() > 0 && !other->HasDied()) - { TriggerDefensiveProcs(other, Hand, true, damage); - } if (damage > 0) return true; From 538ff873ee73a447786af834e75837343b7e2a87 Mon Sep 17 00:00:00 2001 From: Paul Coene Date: Thu, 8 Dec 2016 16:19:17 -0500 Subject: [PATCH 09/29] Fix alignment --- zone/forage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/forage.cpp b/zone/forage.cpp index 7bb87bf6c..cce6d9694 100644 --- a/zone/forage.cpp +++ b/zone/forage.cpp @@ -302,7 +302,7 @@ void Client::GoFish() if(food_id == 0) { int index = zone->random.Int(0, MAX_COMMON_FISH_IDS-1); food_id = common_fish_ids[index]; - } + } const EQEmu::ItemData* food_item = database.GetItem(food_id); From 26985496d1177d69c0141029ac017644f87aa033 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Thu, 8 Dec 2016 22:36:47 -0500 Subject: [PATCH 10/29] Fix Shield Specialist related SPAs --- common/spdat.h | 4 ++-- zone/attack.cpp | 10 +++++----- zone/bonuses.cpp | 28 +++++----------------------- zone/common.h | 3 +-- zone/mob.cpp | 3 --- zone/spell_effects.cpp | 2 +- 6 files changed, 14 insertions(+), 36 deletions(-) diff --git a/common/spdat.h b/common/spdat.h index ab00451e0..6ca588f04 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -502,7 +502,7 @@ typedef enum { #define SE_HeadShotLevel 346 // implemented[AA] - HeadShot max level to kill #define SE_DoubleRangedAttack 347 // implemented - chance at an additional archery attack (consumes arrow) #define SE_LimitManaMin 348 // implemented -#define SE_ShieldEquipHateMod 349 // implemented[AA] Increase melee hate when wearing a shield. +#define SE_ShieldEquipDmgMod 349 // implemented[AA] Increase melee base damage (indirectly increasing hate) when wearing a shield. #define SE_ManaBurn 350 // implemented - Drains mana for damage/heal at a defined ratio up to a defined maximum amount of mana. //#define SE_PersistentEffect 351 // *not implemented. creates a trap/totem that casts a spell (spell id + base1?) when anything comes near it. can probably make a beacon for this //#define SE_IncreaseTrapCount 352 // *not implemented - looks to be some type of invulnerability? Test ITC (8755) @@ -519,7 +519,7 @@ typedef enum { #define SE_BandolierSlots 363 // *not implemented[AA] 'Battle Ready' expands the bandolier by one additional save slot per rank. #define SE_TripleAttackChance 364 // implemented #define SE_ProcOnSpellKillShot 365 // implemented - chance to trigger a spell on kill when the kill is caused by a specific spell with this effect in it (10470 Venin) -#define SE_ShieldEquipDmgMod 366 // implemented[AA] Damage modifier to melee if shield equiped. (base1 = dmg mod , base2 = ?) ie Shield Specialist AA +#define SE_GroupShielding 366 // *not implemented[AA] This gives you /shieldgroup #define SE_SetBodyType 367 // implemented - set body type of base1 so it can be affected by spells that are limited to that type (Plant, Animal, Undead, etc) //#define SE_FactionMod 368 // *not implemented - increases faction with base1 (faction id, live won't match up w/ ours) by base2 #define SE_CorruptionCounter 369 // implemented diff --git a/zone/attack.cpp b/zone/attack.cpp index 475aee7b3..40de941dc 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -1072,6 +1072,11 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b //if weapon damage > 0 then we know we can hit the target with this weapon //otherwise we cannot and we set the damage to -5 later on if(weapon_damage > 0){ + auto shield_inc = spellbonuses.ShieldEquipDmgMod + itembonuses.ShieldEquipDmgMod + aabonuses.ShieldEquipDmgMod; + if (shield_inc > 0 && HasShieldEquiped() && Hand == EQEmu::inventory::slotPrimary) { + weapon_damage = weapon_damage * (100 + shield_inc) / 100; + hate = hate * (100 + shield_inc) / 100; + } //Berserker Berserk damage bonus if(IsBerserk() && GetClass() == BERSERKER){ @@ -2291,11 +2296,6 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b // Spell Casting Subtlety etc int hatemod = 100 + other->spellbonuses.hatemod + other->itembonuses.hatemod + other->aabonuses.hatemod; - int32 shieldhatemod = other->spellbonuses.ShieldEquipHateMod + other->itembonuses.ShieldEquipHateMod + other->aabonuses.ShieldEquipHateMod; - - if (shieldhatemod && other->HasShieldEquiped()) - hatemod += shieldhatemod; - if(hatemod < 1) hatemod = 1; hate = ((hate * (hatemod))/100); diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 760ca52dc..beae3995d 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -940,12 +940,8 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) case SE_ShieldBlock: newbon->ShieldBlock += base1; break; - case SE_ShieldEquipHateMod: - newbon->ShieldEquipHateMod += base1; - break; case SE_ShieldEquipDmgMod: - newbon->ShieldEquipDmgMod[0] += base1; - newbon->ShieldEquipDmgMod[1] += base2; + newbon->ShieldEquipDmgMod += base1; break; case SE_SecondaryDmgInc: newbon->SecondaryDmgInc = true; @@ -2655,13 +2651,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne new_bonus->ShieldBlock += effect_value; break; - case SE_ShieldEquipHateMod: - new_bonus->ShieldEquipHateMod += effect_value; - break; - case SE_ShieldEquipDmgMod: - new_bonus->ShieldEquipDmgMod[0] += effect_value; - new_bonus->ShieldEquipDmgMod[1] += base2; + new_bonus->ShieldEquipDmgMod += effect_value; break; case SE_BlockBehind: @@ -4555,19 +4546,10 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) itembonuses.DoubleRangedAttack = effect_value; break; - case SE_ShieldEquipHateMod: - spellbonuses.ShieldEquipHateMod = effect_value; - aabonuses.ShieldEquipHateMod = effect_value; - itembonuses.ShieldEquipHateMod = effect_value; - break; - case SE_ShieldEquipDmgMod: - spellbonuses.ShieldEquipDmgMod[0] = effect_value; - spellbonuses.ShieldEquipDmgMod[1] = effect_value; - aabonuses.ShieldEquipDmgMod[0] = effect_value; - aabonuses.ShieldEquipDmgMod[1] = effect_value; - itembonuses.ShieldEquipDmgMod[0] = effect_value; - itembonuses.ShieldEquipDmgMod[1] = effect_value; + spellbonuses.ShieldEquipDmgMod = effect_value; + aabonuses.ShieldEquipDmgMod = effect_value; + itembonuses.ShieldEquipDmgMod = effect_value; break; case SE_TriggerMeleeThreshold: diff --git a/zone/common.h b/zone/common.h index fd4ea0a89..e621c336b 100644 --- a/zone/common.h +++ b/zone/common.h @@ -457,8 +457,7 @@ struct StatBonuses { int32 ItemATKCap; // Raise item attack cap int32 FinishingBlow[2]; // Chance to do a finishing blow for specified damage amount. uint32 FinishingBlowLvl[2]; // Sets max level an NPC can be affected by FB. (base1 = lv, base2= ???) - int32 ShieldEquipHateMod; // Hate mod when shield equiped. - int32 ShieldEquipDmgMod[2]; // Damage mod when shield equiped. 0 = damage modifier 1 = Unknown + int32 ShieldEquipDmgMod; // Increases weapon's base damage by base1 % when shield is equipped (indirectly increasing hate) bool TriggerOnValueAmount; // Triggers off various different conditions, bool to check if client has effect. int8 StunBashChance; // chance to stun with bash. int8 IncreaseChanceMemwipe; // increases chance to memory wipe diff --git a/zone/mob.cpp b/zone/mob.cpp index a39dcfa99..c47788ee5 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -4668,9 +4668,6 @@ int16 Mob::GetMeleeDamageMod_SE(uint16 skill) dmg_mod += itembonuses.DamageModifier2[EQEmu::skills::HIGHEST_SKILL + 1] + spellbonuses.DamageModifier2[EQEmu::skills::HIGHEST_SKILL + 1] + aabonuses.DamageModifier2[EQEmu::skills::HIGHEST_SKILL + 1] + itembonuses.DamageModifier2[skill] + spellbonuses.DamageModifier2[skill] + aabonuses.DamageModifier2[skill]; - if (HasShieldEquiped() && !IsOffHandAtk()) - dmg_mod += itembonuses.ShieldEquipDmgMod[0] + spellbonuses.ShieldEquipDmgMod[0] + aabonuses.ShieldEquipDmgMod[0]; - if(dmg_mod < -100) dmg_mod = -100; diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index a2a66f9ae..43833328c 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -2982,8 +2982,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_FcHealAmtIncoming: case SE_LimitManaMax: case SE_DoubleRangedAttack: - case SE_ShieldEquipHateMod: case SE_ShieldEquipDmgMod: + case SE_GroupShielding: case SE_TriggerOnReqTarget: case SE_LimitRace: case SE_FcLimitUse: From fddb6f67abcc6fb6424e3a3b5fdf9a570bc8f5a6 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Fri, 9 Dec 2016 13:29:46 -0500 Subject: [PATCH 11/29] Update IsPartialCapableSpell based on Torven's findings --- common/spdat.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/common/spdat.cpp b/common/spdat.cpp index f22a82000..fd4e3c5ea 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -409,8 +409,19 @@ bool IsPartialCapableSpell(uint16 spell_id) if (spells[spell_id].no_partial_resist) return false; - if (IsPureNukeSpell(spell_id)) - return true; + // spell uses 600 (partial) scale if first effect is damage, else it uses 200 scale. + // this includes DoTs. no_partial_resist excludes spells like necro snares + for (int o = 0; o < EFFECT_COUNT; o++) { + auto tid = spells[spell_id].effectid[o]; + + if (IsBlankSpellEffect(spell_id, o)) + continue; + + if ((tid == SE_CurrentHPOnce || tid == SE_CurrentHP) && spells[spell_id].base[o] < 0) + return true; + + return false; + } return false; } From 491cabfe8b170546784687a4fd86bdfc6ed24e5f Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Fri, 9 Dec 2016 13:36:09 -0500 Subject: [PATCH 12/29] Fix SE_Destroy breaking respawns --- zone/spell_effects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 43833328c..a2841ee8e 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -2038,7 +2038,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #endif if(IsNPC()) { if(GetLevel() <= 52) - CastToNPC()->Depop(); + CastToNPC()->Depop(true); else Message(13, "Your target is too high level to be affected by this spell."); } From 6311d82095a596d3b4a331dda354c950a6eb6203 Mon Sep 17 00:00:00 2001 From: Paul Coene Date: Fri, 9 Dec 2016 18:59:44 -0500 Subject: [PATCH 13/29] Add check for mob spell based defensive procs (as per DB entry) for attacking clients. Got coaching and testing from demonstar55. --- zone/client_process.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 632f953d7..bb2db4777 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -393,6 +393,7 @@ bool Client::Process() { { EQEmu::ItemInstance *wpn = GetInv().GetItem(EQEmu::inventory::slotPrimary); TryWeaponProc(wpn, auto_attack_target, EQEmu::inventory::slotPrimary); + TriggerDefensiveProcs(auto_attack_target, EQEmu::inventory::slotPrimary, false); DoAttackRounds(auto_attack_target, EQEmu::inventory::slotPrimary); if (CheckAATimer(aaTimerRampage)) From 69941571842a663405b73ea2a4d8f5203848d8d1 Mon Sep 17 00:00:00 2001 From: Uleat Date: Fri, 9 Dec 2016 20:12:08 -0500 Subject: [PATCH 14/29] Added optional bots rule 'CasterStopMeleeLevel' --- common/ruletypes.h | 3 ++- .../git/bots/optional/2014_03_31_BotLevelsWithOwnerRule.sql | 4 ---- .../optional/2014_03_31_bots_bot_levels_with_owner_rule.sql | 1 + .../optional/2016_12_09_bots_caster_stop_melee_level_rule.sql | 2 ++ zone/bot.cpp | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 utils/sql/git/bots/optional/2014_03_31_BotLevelsWithOwnerRule.sql create mode 100644 utils/sql/git/bots/optional/2014_03_31_bots_bot_levels_with_owner_rule.sql create mode 100644 utils/sql/git/bots/optional/2016_12_09_bots_caster_stop_melee_level_rule.sql diff --git a/common/ruletypes.h b/common/ruletypes.h index 9a18ff063..dc1a31c1f 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -556,12 +556,13 @@ RULE_REAL(Bots, ManaRegen, 2.0) // Adjust mana regen for bots, 1 is fast and hig RULE_BOOL(Bots, PreferNoManaCommandSpells, true) // Give sorting priority to newer no-mana spells (i.e., 'Bind Affinity') RULE_BOOL(Bots, QuestableSpawnLimit, false) // Optional quest method to manage bot spawn limits using the quest_globals name bot_spawn_limit, see: /bazaar/Aediles_Thrall.pl RULE_BOOL(Bots, QuestableSpells, false) // Anita Thrall's (Anita_Thrall.pl) Bot Spell Scriber quests. -RULE_INT(Bots, SpawnLimit, 71) // Number of bots a character can have spawned at one time, You + 71 bots is a 12 group raid +RULE_INT(Bots, SpawnLimit, 71) // Number of bots a character can have spawned at one time, You + 71 bots is a 12 group pseudo-raid (bots are not raidable at this time) RULE_BOOL(Bots, BotGroupXP, false) // Determines whether client gets xp for bots outside their group. RULE_BOOL(Bots, BotBardUseOutOfCombatSongs, true) // Determines whether bard bots use additional out of combat songs (optional script) RULE_BOOL(Bots, BotLevelsWithOwner, false) // Auto-updates spawned bots as owner levels/de-levels (false is original behavior) RULE_BOOL(Bots, BotCharacterLevelEnabled, false) // Enables required level to spawn bots RULE_INT(Bots, BotCharacterLevel, 0) // 0 as default (if level > this value you can spawn bots if BotCharacterLevelEnabled is true) +RULE_INT(Bots, CasterStopMeleeLevel, 13) // Level at which caster bots stop melee attacks RULE_CATEGORY_END() #endif diff --git a/utils/sql/git/bots/optional/2014_03_31_BotLevelsWithOwnerRule.sql b/utils/sql/git/bots/optional/2014_03_31_BotLevelsWithOwnerRule.sql deleted file mode 100644 index 4700d9abf..000000000 --- a/utils/sql/git/bots/optional/2014_03_31_BotLevelsWithOwnerRule.sql +++ /dev/null @@ -1,4 +0,0 @@ -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Bots:BotLevelsWithOwner', 'true', 'Auto-updates bots with ding.'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (2, 'Bots:BotLevelsWithOwner', 'true', 'Auto-updates bots with ding.'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (4, 'Bots:BotLevelsWithOwner', 'true', 'Auto-updates bots with ding.'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (10, 'Bots:BotLevelsWithOwner', 'true', 'Auto-updates bots with ding.'); diff --git a/utils/sql/git/bots/optional/2014_03_31_bots_bot_levels_with_owner_rule.sql b/utils/sql/git/bots/optional/2014_03_31_bots_bot_levels_with_owner_rule.sql new file mode 100644 index 000000000..73a3c5b4c --- /dev/null +++ b/utils/sql/git/bots/optional/2014_03_31_bots_bot_levels_with_owner_rule.sql @@ -0,0 +1 @@ +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Bots:BotLevelsWithOwner', 'true', 'Auto-updates bots with ding.'); diff --git a/utils/sql/git/bots/optional/2016_12_09_bots_caster_stop_melee_level_rule.sql b/utils/sql/git/bots/optional/2016_12_09_bots_caster_stop_melee_level_rule.sql new file mode 100644 index 000000000..1e50c659e --- /dev/null +++ b/utils/sql/git/bots/optional/2016_12_09_bots_caster_stop_melee_level_rule.sql @@ -0,0 +1,2 @@ +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Bots:CasterStopMeleeLevel', '13', 'Level at which caster bots stop melee attacks'); + diff --git a/zone/bot.cpp b/zone/bot.cpp index 133932df7..056a55289 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2391,7 +2391,7 @@ void Bot::AI_Process() { } } atCombatRange = true; - } else if(IsBotCaster() && GetLevel() > 12) { + } else if(IsBotCaster() && GetLevel() >= RuleI(Bots, CasterStopMeleeLevel)) { if(IsBotCasterCombatRange(GetTarget())) atCombatRange = true; } @@ -2440,7 +2440,7 @@ void Bot::AI_Process() { if(GetTarget()->GetHPRatio() <= 99.0f) BotRangedAttack(GetTarget()); } - else if(!IsBotArcher() && (!(IsBotCaster() && GetLevel() > 12)) && GetTarget() && !IsStunned() && !IsMezzed() && (GetAppearance() != eaDead)) { + else if(!IsBotArcher() && (!(IsBotCaster() && GetLevel() >= RuleI(Bots, CasterStopMeleeLevel))) && GetTarget() && !IsStunned() && !IsMezzed() && (GetAppearance() != eaDead)) { // we can't fight if we don't have a target, are stun/mezzed or dead.. // Stop attacking if the target is enraged if((IsEngaged() && !BehindMob(GetTarget(), GetX(), GetY()) && GetTarget()->IsEnraged()) || GetBotStance() == BotStancePassive) From 3cc7d0db6345821190901aeecc64c7768e39257a Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Wed, 14 Dec 2016 22:06:05 -0500 Subject: [PATCH 15/29] Fix fizzle message being the wrong color --- zone/spells.cpp | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/zone/spells.cpp b/zone/spells.cpp index 814f01be6..409a07f47 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -230,23 +230,6 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, } } - // check for fizzle - // note that CheckFizzle itself doesn't let NPCs fizzle, - // but this code allows for it. - if(slot < CastingSlot::MaxGems && !CheckFizzle(spell_id)) - { - int fizzle_msg = IsBardSong(spell_id) ? MISS_NOTE : SPELL_FIZZLE; - InterruptSpell(fizzle_msg, 0x121, spell_id); - - uint32 use_mana = ((spells[spell_id].mana) / 4); - Log.Out(Logs::Detail, Logs::Spells, "Spell casting canceled: fizzled. %d mana has been consumed", use_mana); - - // fizzle 1/4 the mana away - SetMana(GetMana() - use_mana); - TryTriggerOnValueAmount(false, true); - return(false); - } - if (HasActiveSong() && IsBardSong(spell_id)) { Log.Out(Logs::Detail, Logs::Spells, "Casting a new song while singing a song. Killing old song %d.", bardsong); //Note: this does NOT tell the client @@ -366,6 +349,28 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, } casting_spell_aa_id = aa_id; + // check for fizzle + // note that CheckFizzle itself doesn't let NPCs fizzle, + // but this code allows for it. + if (slot < CastingSlot::MaxGems && !CheckFizzle(spell_id)) { + int fizzle_msg = IsBardSong(spell_id) ? MISS_NOTE : SPELL_FIZZLE; + + uint32 use_mana = ((spells[spell_id].mana) / 4); + Log.Out(Logs::Detail, Logs::Spells, "Spell casting canceled: fizzled. %d mana has been consumed", use_mana); + + // fizzle 1/4 the mana away + Mob::SetMana(GetMana() - use_mana); // We send StopCasting which will update mana + StopCasting(); + + Message_StringID(MT_SpellFailure, fizzle_msg); + entity_list.FilteredMessageClose_StringID( + this, true, 200, MT_SpellFailure, IsClient() ? FilterPCSpells : FilterNPCSpells, + fizzle_msg == MISS_NOTE ? MISSED_NOTE_OTHER : SPELL_FIZZLE_OTHER, GetName()); + + TryTriggerOnValueAmount(false, true); + return(false); + } + SaveSpellLoc(); Log.Out(Logs::Detail, Logs::Spells, "Casting %d Started at (%.3f,%.3f,%.3f)", spell_id, m_SpellLocation.x, m_SpellLocation.y, m_SpellLocation.z); From d305d6727932ce969f3e4e9cf389b5c0af0876f7 Mon Sep 17 00:00:00 2001 From: SCMcLaughlin Date: Thu, 15 Dec 2016 14:47:58 -0800 Subject: [PATCH 16/29] Fix potential infinite loop in loginserver's config file reader --- loginserver/config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loginserver/config.cpp b/loginserver/config.cpp index ee17bdbed..ecffb00f7 100644 --- a/loginserver/config.cpp +++ b/loginserver/config.cpp @@ -144,7 +144,7 @@ void Config::Parse(const char *file_name) */ void Config::Tokenize(FILE *input, std::list &tokens) { - char c = fgetc(input); + int c = fgetc(input); std::string lexeme; while(c != EOF) From c1fbfc0f44160a494393d2f21efdfab9d167bb36 Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Thu, 15 Dec 2016 18:31:08 -0500 Subject: [PATCH 17/29] Add support for kicking epic loot locks Returning non-0 (no return in a lua/perl function = return 0 so this is best) will prevent the client from looting the item I still need to figure out how to make it so we don't have to kick the player from the corpse, but maybe that's just a difference on live --- zone/corpse.cpp | 10 +++++++++- zone/string_ids.h | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/zone/corpse.cpp b/zone/corpse.cpp index 34a2191b2..6a26f33ab 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -1170,7 +1170,15 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { std::vector args; args.push_back(inst); args.push_back(this); - parse->EventPlayer(EVENT_LOOT, client, buf, 0, &args); + if (parse->EventPlayer(EVENT_LOOT, client, buf, 0, &args) != 0) { + lootitem->auto_loot = 0xFFFFFFFF; + client->Message_StringID(CC_Red, LOOT_NOT_ALLOWED, inst->GetItem()->Name); + client->QueuePacket(app); + SendEndLootErrorPacket(client); // shouldn't need this, but it will work for now + being_looted_by = 0; + delete inst; + return; + } parse->EventItem(EVENT_LOOT, client, inst, this, buf, 0); if (!IsPlayerCorpse() && RuleB(Character, EnableDiscoveredItems)) { diff --git a/zone/string_ids.h b/zone/string_ids.h index 67dd808ab..f03d8f44d 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -279,6 +279,7 @@ #define TRADESKILL_MISSING_COMPONENTS 3456 //Sorry, but you don't have everything you need for this recipe in your general inventory. #define TRADESKILL_LEARN_RECIPE 3457 //You have learned the recipe %1! #define EXPEDITION_MIN_REMAIN 3551 //You only have %1 minutes remaining before this expedition comes to an end. +#define LOOT_NOT_ALLOWED 3562 //You are not allowed to loot the item: %1. #define NO_CAST_ON_PET 4045 //You cannot cast this spell on your pet. #define REWIND_WAIT 4059 //You must wait a bit longer before using the rewind command again. #define CORPSEDRAG_LIMIT 4061 //You are already dragging as much as you can! From 343c23cc6c0868a22c2d5c77247c6d87dff8011a Mon Sep 17 00:00:00 2001 From: SCMcLaughlin Date: Thu, 15 Dec 2016 20:58:53 -0800 Subject: [PATCH 18/29] Additional LS config parser fixes: * use auto * fix some questionable uses of string.append() that were broken by the use of int/auto --- loginserver/config.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/loginserver/config.cpp b/loginserver/config.cpp index ecffb00f7..5582e9754 100644 --- a/loginserver/config.cpp +++ b/loginserver/config.cpp @@ -144,7 +144,7 @@ void Config::Parse(const char *file_name) */ void Config::Tokenize(FILE *input, std::list &tokens) { - int c = fgetc(input); + auto c = fgetc(input); std::string lexeme; while(c != EOF) @@ -162,7 +162,7 @@ void Config::Tokenize(FILE *input, std::list &tokens) if(isalnum(c)) { - lexeme.append((const char *)&c, 1); + lexeme += c; c = fgetc(input); continue; } @@ -193,14 +193,14 @@ void Config::Tokenize(FILE *input, std::list &tokens) lexeme.clear(); } - lexeme.append((const char *)&c, 1); + lexeme += c; tokens.push_back(lexeme); lexeme.clear(); break; } default: { - lexeme.append((const char *)&c, 1); + lexeme += c; } } From 8f5ba05e75a64848e13d0957559b258a09d1292f Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Fri, 16 Dec 2016 16:02:42 -0500 Subject: [PATCH 19/29] Minor Corpse::LootItem refactoring --- zone/corpse.cpp | 22 ++++++++++------------ zone/corpse.h | 3 ++- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/zone/corpse.cpp b/zone/corpse.cpp index 6a26f33ab..e83631252 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -36,7 +36,6 @@ Child of the Mob class. #include "../common/string_util.h" #include "../common/say_link.h" -#include "client.h" #include "corpse.h" #include "entity.h" #include "groups.h" @@ -1070,9 +1069,8 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { if (!loot_cooldown_timer.Check()) { SendEndLootErrorPacket(client); //unlock corpse for others - if (this->being_looted_by = client->GetID()) { - being_looted_by = 0xFFFFFFFF; - } + if (IsBeingLootedBy(client)) + ResetLooter(); return; } @@ -1081,15 +1079,14 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { client->Message(13, "You may not loot an item while you have an item on your cursor."); SendEndLootErrorPacket(client); /* Unlock corpse for others */ - if (this->being_looted_by = client->GetID()) { - being_looted_by = 0xFFFFFFFF; - } + if (IsBeingLootedBy(client)) + ResetLooter(); return; } LootingItem_Struct* lootitem = (LootingItem_Struct*)app->pBuffer; - if (this->being_looted_by != client->GetID()) { + if (!IsBeingLootedBy(client)) { client->Message(13, "Error: Corpse::LootItem: BeingLootedBy != client"); SendEndLootErrorPacket(client); return; @@ -1107,9 +1104,10 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { if (IsPlayerCorpse() && (char_id != client->CharacterID()) && CanPlayerLoot(client->CharacterID()) && GetPlayerKillItem() == 0){ client->Message(13, "Error: You cannot loot any more items from this corpse."); SendEndLootErrorPacket(client); - being_looted_by = 0xFFFFFFFF; + ResetLooter(); return; } + const EQEmu::ItemData* item = 0; EQEmu::ItemInstance *inst = 0; ServerLootItem_Struct* item_data = nullptr, *bag_item_data[10]; @@ -1142,7 +1140,7 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { if (client->CheckLoreConflict(item)) { client->Message_StringID(0, LOOT_LORE_ERROR); SendEndLootErrorPacket(client); - being_looted_by = 0; + ResetLooter(); delete inst; return; } @@ -1154,7 +1152,7 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { if (client->CheckLoreConflict(itm->GetItem())) { client->Message_StringID(0, LOOT_LORE_ERROR); SendEndLootErrorPacket(client); - being_looted_by = 0; + ResetLooter(); delete inst; return; } @@ -1175,7 +1173,7 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { client->Message_StringID(CC_Red, LOOT_NOT_ALLOWED, inst->GetItem()->Name); client->QueuePacket(app); SendEndLootErrorPacket(client); // shouldn't need this, but it will work for now - being_looted_by = 0; + ResetLooter(); delete inst; return; } diff --git a/zone/corpse.h b/zone/corpse.h index e83235c1e..49b1cba39 100644 --- a/zone/corpse.h +++ b/zone/corpse.h @@ -20,8 +20,8 @@ #define CORPSE_H #include "mob.h" +#include "client.h" -class Client; class EQApplicationPacket; class Group; class NPC; @@ -118,6 +118,7 @@ class Corpse : public Mob { inline bool IsLocked() { return is_locked; } inline void ResetLooter() { being_looted_by = 0xFFFFFFFF; } inline bool IsBeingLooted() { return (being_looted_by != 0xFFFFFFFF); } + inline bool IsBeingLootedBy(Client *c) { return being_looted_by == c->GetID(); } /* Mob */ void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho); From 019586abbd0b42ecc09ccc77bbe3a566a7dd603a Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Fri, 16 Dec 2016 16:03:44 -0500 Subject: [PATCH 20/29] Clang-format Corpse::LootItem --- zone/corpse.cpp | 98 ++++++++++++++++++++++++++----------------------- 1 file changed, 53 insertions(+), 45 deletions(-) diff --git a/zone/corpse.cpp b/zone/corpse.cpp index e83631252..216bf2299 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -1062,13 +1062,14 @@ void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* a SendLootReqErrorPacket(client, LootResponse::LootAll); } -void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { +void Corpse::LootItem(Client *client, const EQApplicationPacket *app) +{ /* This gets sent no matter what as a sort of ACK */ client->QueuePacket(app); if (!loot_cooldown_timer.Check()) { SendEndLootErrorPacket(client); - //unlock corpse for others + // unlock corpse for others if (IsBeingLootedBy(client)) ResetLooter(); return; @@ -1084,14 +1085,15 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { return; } - LootingItem_Struct* lootitem = (LootingItem_Struct*)app->pBuffer; + LootingItem_Struct *lootitem = (LootingItem_Struct *)app->pBuffer; if (!IsBeingLootedBy(client)) { client->Message(13, "Error: Corpse::LootItem: BeingLootedBy != client"); SendEndLootErrorPacket(client); return; } - if (IsPlayerCorpse() && !CanPlayerLoot(client->CharacterID()) && !become_npc && (char_id != client->CharacterID() && client->Admin() < 150)) { + if (IsPlayerCorpse() && !CanPlayerLoot(client->CharacterID()) && !become_npc && + (char_id != client->CharacterID() && client->Admin() < 150)) { client->Message(13, "Error: This is a player corpse and you dont own it."); SendEndLootErrorPacket(client); return; @@ -1101,37 +1103,39 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { client->Message(13, "Error: Corpse locked by GM."); return; } - if (IsPlayerCorpse() && (char_id != client->CharacterID()) && CanPlayerLoot(client->CharacterID()) && GetPlayerKillItem() == 0){ + if (IsPlayerCorpse() && (char_id != client->CharacterID()) && CanPlayerLoot(client->CharacterID()) && + GetPlayerKillItem() == 0) { client->Message(13, "Error: You cannot loot any more items from this corpse."); SendEndLootErrorPacket(client); ResetLooter(); return; } - const EQEmu::ItemData* item = 0; + const EQEmu::ItemData *item = 0; EQEmu::ItemInstance *inst = 0; - ServerLootItem_Struct* item_data = nullptr, *bag_item_data[10]; + ServerLootItem_Struct *item_data = nullptr, *bag_item_data[10]; memset(bag_item_data, 0, sizeof(bag_item_data)); - if (GetPlayerKillItem() > 1){ + if (GetPlayerKillItem() > 1) { item = database.GetItem(GetPlayerKillItem()); - } - else if (GetPlayerKillItem() == -1 || GetPlayerKillItem() == 1){ - item_data = GetItem(lootitem->slot_id - EQEmu::legacy::CORPSE_BEGIN); //dont allow them to loot entire bags of items as pvp reward - } - else{ + } else if (GetPlayerKillItem() == -1 || GetPlayerKillItem() == 1) { + item_data = + GetItem(lootitem->slot_id - + EQEmu::legacy::CORPSE_BEGIN); // dont allow them to loot entire bags of items as pvp reward + } else { item_data = GetItem(lootitem->slot_id - EQEmu::legacy::CORPSE_BEGIN, bag_item_data); } - if (GetPlayerKillItem()<=1 && item_data != 0) { + if (GetPlayerKillItem() <= 1 && item_data != 0) { item = database.GetItem(item_data->item_id); } if (item != 0) { - if (item_data){ - inst = database.CreateItem(item, item_data ? item_data->charges : 0, item_data->aug_1, item_data->aug_2, item_data->aug_3, item_data->aug_4, item_data->aug_5, item_data->aug_6, item_data->attuned); - } - else { + if (item_data) { + inst = database.CreateItem(item, item_data ? item_data->charges : 0, item_data->aug_1, + item_data->aug_2, item_data->aug_3, item_data->aug_4, + item_data->aug_5, item_data->aug_6, item_data->attuned); + } else { inst = database.CreateItem(item); } } @@ -1163,7 +1167,8 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { char buf[88]; char q_corpse_name[64]; strcpy(q_corpse_name, corpse_name); - snprintf(buf, 87, "%d %d %s", inst->GetItem()->ID, inst->GetCharges(), EntityList::RemoveNumbers(q_corpse_name)); + snprintf(buf, 87, "%d %d %s", inst->GetItem()->ID, inst->GetCharges(), + EntityList::RemoveNumbers(q_corpse_name)); buf[87] = '\0'; std::vector args; args.push_back(inst); @@ -1185,7 +1190,7 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { } if (zone->adv_data) { - ServerZoneAdventureDataReply_Struct *ad = (ServerZoneAdventureDataReply_Struct*)zone->adv_data; + ServerZoneAdventureDataReply_Struct *ad = (ServerZoneAdventureDataReply_Struct *)zone->adv_data; if (ad->type == Adventure_Collect && !IsPlayerCorpse()) { if (ad->data_id == inst->GetItem()->ID) { zone->DoAdventureCountIncrease(); @@ -1197,8 +1202,7 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { if (lootitem->auto_loot) { if (!client->AutoPutLootInInventory(*inst, true, true, bag_item_data)) client->PutLootInInventory(EQEmu::inventory::slotCursor, *inst, bag_item_data); - } - else { + } else { client->PutLootInInventory(EQEmu::inventory::slotCursor, *inst, bag_item_data); } @@ -1207,9 +1211,11 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { client->UpdateTasksForItem(ActivityLoot, item->ID); /* Remove it from Corpse */ - if (item_data){ - /* Delete needs to be before RemoveItem because its deletes the pointer for item_data/bag_item_data */ - database.DeleteItemOffCharacterCorpse(this->corpse_db_id, item_data->equip_slot, item_data->item_id); + if (item_data) { + /* Delete needs to be before RemoveItem because its deletes the pointer for + * item_data/bag_item_data */ + database.DeleteItemOffCharacterCorpse(this->corpse_db_id, item_data->equip_slot, + item_data->item_id); /* Delete Item Instance */ RemoveItem(item_data->lootslot); } @@ -1218,8 +1224,11 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { if (item->IsClassBag() && (GetPlayerKillItem() != -1 || GetPlayerKillItem() != 1)) { for (int i = EQEmu::inventory::containerBegin; i < EQEmu::inventory::ContainerCount; i++) { if (bag_item_data[i]) { - /* Delete needs to be before RemoveItem because its deletes the pointer for item_data/bag_item_data */ - database.DeleteItemOffCharacterCorpse(this->corpse_db_id, bag_item_data[i]->equip_slot, bag_item_data[i]->item_id); + /* Delete needs to be before RemoveItem because its deletes the pointer for + * item_data/bag_item_data */ + database.DeleteItemOffCharacterCorpse(this->corpse_db_id, + bag_item_data[i]->equip_slot, + bag_item_data[i]->item_id); /* Delete Item Instance */ RemoveItem(bag_item_data[i]); } @@ -1230,38 +1239,37 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { SetPlayerKillItemID(0); } - /* Send message with item link to groups and such */ - EQEmu::SayLinkEngine linker; - linker.SetLinkType(EQEmu::saylink::SayLinkItemInst); - linker.SetItemInst(inst); + /* Send message with item link to groups and such */ + EQEmu::SayLinkEngine linker; + linker.SetLinkType(EQEmu::saylink::SayLinkItemInst); + linker.SetItemInst(inst); - auto item_link = linker.GenerateLink(); + auto item_link = linker.GenerateLink(); - client->Message_StringID(MT_LootMessages, LOOTED_MESSAGE, item_link.c_str()); + client->Message_StringID(MT_LootMessages, LOOTED_MESSAGE, item_link.c_str()); - if (!IsPlayerCorpse()) { + if (!IsPlayerCorpse()) { Group *g = client->GetGroup(); - if(g != nullptr) { - g->GroupMessage_StringID(client, MT_LootMessages, OTHER_LOOTED_MESSAGE, client->GetName(), item_link.c_str()); - } - else { + if (g != nullptr) { + g->GroupMessage_StringID(client, MT_LootMessages, OTHER_LOOTED_MESSAGE, + client->GetName(), item_link.c_str()); + } else { Raid *r = client->GetRaid(); - if(r != nullptr) { - r->RaidMessage_StringID(client, MT_LootMessages, OTHER_LOOTED_MESSAGE, client->GetName(), item_link.c_str()); + if (r != nullptr) { + r->RaidMessage_StringID(client, MT_LootMessages, OTHER_LOOTED_MESSAGE, + client->GetName(), item_link.c_str()); } } } - } - else { + } else { SendEndLootErrorPacket(client); safe_delete(inst); return; } - if (IsPlayerCorpse()){ + if (IsPlayerCorpse()) { client->SendItemLink(inst); - } - else{ + } else { client->SendItemLink(inst, true); } From e680a0f70429d4e355c4ab029b0ba1987634818f Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Fri, 16 Dec 2016 16:09:31 -0500 Subject: [PATCH 21/29] Change LottingItem_Struct::auto_loot to signed --- common/eq_packet_structs.h | 2 +- common/patches/rof_structs.h | 2 +- common/patches/sod_structs.h | 2 +- common/patches/sof_structs.h | 2 +- common/patches/titanium_structs.h | 2 +- common/patches/uf_structs.h | 2 +- zone/corpse.cpp | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 1d25e4d66..ffbcead70 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -1643,7 +1643,7 @@ struct LootingItem_Struct { /*002*/ uint32 looter; /*004*/ uint16 slot_id; /*006*/ uint8 unknown3[2]; -/*008*/ uint32 auto_loot; +/*008*/ int32 auto_loot; }; struct GuildManageStatus_Struct{ diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index 879afc1d4..32c7c0d28 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -2021,7 +2021,7 @@ struct LootingItem_Struct { /*004*/ uint32 looter; /*008*/ uint16 slot_id; /*010*/ uint16 unknown10; -/*012*/ uint32 auto_loot; +/*012*/ int32 auto_loot; /*016*/ uint32 unknown16; /*020*/ }; diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index cd76218e0..fdcb03658 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -1666,7 +1666,7 @@ struct LootingItem_Struct { /*000*/ uint32 lootee; /*004*/ uint32 looter; /*008*/ uint32 slot_id; -/*012*/ uint32 auto_loot; +/*012*/ int32 auto_loot; /*016*/ uint32 unknown16; /*020*/ }; diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index 9be93d49f..00fcd4ecf 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -1648,7 +1648,7 @@ struct LootingItem_Struct { /*002*/ uint32 looter; /*004*/ uint16 slot_id; /*006*/ uint8 unknown3[2]; -/*008*/ uint32 auto_loot; +/*008*/ int32 auto_loot; }; struct GuildManageStatus_Struct{ diff --git a/common/patches/titanium_structs.h b/common/patches/titanium_structs.h index 91921da2e..69988dd7c 100644 --- a/common/patches/titanium_structs.h +++ b/common/patches/titanium_structs.h @@ -1420,7 +1420,7 @@ struct LootingItem_Struct { /*002*/ uint32 looter; /*004*/ uint16 slot_id; /*006*/ uint8 unknown3[2]; -/*008*/ uint32 auto_loot; +/*008*/ int32 auto_loot; }; struct GuildManageStatus_Struct{ diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index ec279e56c..560a9378d 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -1707,7 +1707,7 @@ struct LootingItem_Struct { /*000*/ uint32 lootee; /*004*/ uint32 looter; /*008*/ uint32 slot_id; -/*012*/ uint32 auto_loot; +/*012*/ int32 auto_loot; /*016*/ uint32 unknown16; /*020*/ }; diff --git a/zone/corpse.cpp b/zone/corpse.cpp index 216bf2299..8219577de 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -1174,7 +1174,7 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app) args.push_back(inst); args.push_back(this); if (parse->EventPlayer(EVENT_LOOT, client, buf, 0, &args) != 0) { - lootitem->auto_loot = 0xFFFFFFFF; + lootitem->auto_loot = -1; client->Message_StringID(CC_Red, LOOT_NOT_ALLOWED, inst->GetItem()->Name); client->QueuePacket(app); SendEndLootErrorPacket(client); // shouldn't need this, but it will work for now @@ -1199,7 +1199,7 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app) } /* First add it to the looter - this will do the bag contents too */ - if (lootitem->auto_loot) { + if (lootitem->auto_loot > 0) { if (!client->AutoPutLootInInventory(*inst, true, true, bag_item_data)) client->PutLootInInventory(EQEmu::inventory::slotCursor, *inst, bag_item_data); } else { From f53b95d141689aab8150ddb968325097a7a8118f Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Fri, 16 Dec 2016 17:11:44 -0500 Subject: [PATCH 22/29] Rework look acking a bit We should no longer kick from corpse if the quest says to not loot an item Need to investigate autoloot behavior with respect to everything still So other cases will still kick you from the corpse (lore conflict etc) --- zone/corpse.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/zone/corpse.cpp b/zone/corpse.cpp index 8219577de..24c4b8220 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -1064,10 +1064,10 @@ void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* a void Corpse::LootItem(Client *client, const EQApplicationPacket *app) { - /* This gets sent no matter what as a sort of ACK */ - client->QueuePacket(app); + auto lootitem = (LootingItem_Struct *)app->pBuffer; if (!loot_cooldown_timer.Check()) { + client->QueuePacket(app); SendEndLootErrorPacket(client); // unlock corpse for others if (IsBeingLootedBy(client)) @@ -1078,6 +1078,7 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app) /* To prevent item loss for a player using 'Loot All' who doesn't have inventory space for all their items. */ if (RuleB(Character, CheckCursorEmptyWhenLooting) && !client->GetInv().CursorEmpty()) { client->Message(13, "You may not loot an item while you have an item on your cursor."); + client->QueuePacket(app); SendEndLootErrorPacket(client); /* Unlock corpse for others */ if (IsBeingLootedBy(client)) @@ -1085,27 +1086,32 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app) return; } - LootingItem_Struct *lootitem = (LootingItem_Struct *)app->pBuffer; - if (!IsBeingLootedBy(client)) { client->Message(13, "Error: Corpse::LootItem: BeingLootedBy != client"); + client->QueuePacket(app); SendEndLootErrorPacket(client); return; } + if (IsPlayerCorpse() && !CanPlayerLoot(client->CharacterID()) && !become_npc && (char_id != client->CharacterID() && client->Admin() < 150)) { client->Message(13, "Error: This is a player corpse and you dont own it."); + client->QueuePacket(app); SendEndLootErrorPacket(client); return; } + if (is_locked && client->Admin() < 100) { + client->QueuePacket(app); SendLootReqErrorPacket(client, LootResponse::SomeoneElse); client->Message(13, "Error: Corpse locked by GM."); return; } + if (IsPlayerCorpse() && (char_id != client->CharacterID()) && CanPlayerLoot(client->CharacterID()) && GetPlayerKillItem() == 0) { client->Message(13, "Error: You cannot loot any more items from this corpse."); + client->QueuePacket(app); SendEndLootErrorPacket(client); ResetLooter(); return; @@ -1143,6 +1149,7 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app) if (client && inst) { if (client->CheckLoreConflict(item)) { client->Message_StringID(0, LOOT_LORE_ERROR); + client->QueuePacket(app); SendEndLootErrorPacket(client); ResetLooter(); delete inst; @@ -1155,6 +1162,7 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app) if (itm) { if (client->CheckLoreConflict(itm->GetItem())) { client->Message_StringID(0, LOOT_LORE_ERROR); + client->QueuePacket(app); SendEndLootErrorPacket(client); ResetLooter(); delete inst; @@ -1177,13 +1185,15 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app) lootitem->auto_loot = -1; client->Message_StringID(CC_Red, LOOT_NOT_ALLOWED, inst->GetItem()->Name); client->QueuePacket(app); - SendEndLootErrorPacket(client); // shouldn't need this, but it will work for now - ResetLooter(); delete inst; return; } + // do we want this to have a fail option too? parse->EventItem(EVENT_LOOT, client, inst, this, buf, 0); + // safe to ACK now + client->QueuePacket(app); + if (!IsPlayerCorpse() && RuleB(Character, EnableDiscoveredItems)) { if (client && !client->GetGM() && !client->IsDiscovered(inst->GetItem()->ID)) client->DiscoverItem(inst->GetItem()->ID); From 06279b18a3e5fde8ebc198742e6e94eb53609338 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sun, 18 Dec 2016 05:36:30 -0600 Subject: [PATCH 23/29] Fix Hero Forge model not showing up at character select --- common/shareddb.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/shareddb.cpp b/common/shareddb.cpp index fc628ea6f..5205c97da 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -594,7 +594,7 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQEmu::InventoryProfile *inv) inst->SetOrnamentIcon(ornament_icon); inst->SetOrnamentationIDFile(ornament_idfile); - inst->SetOrnamentHeroModel(ornament_hero_model); + inst->SetOrnamentHeroModel(item->HerosForgeModel); if (instnodrop || (((slot_id >= EQEmu::legacy::EQUIPMENT_BEGIN && slot_id <= EQEmu::legacy::EQUIPMENT_END) || @@ -730,7 +730,7 @@ bool SharedDatabase::GetInventory(uint32 account_id, char *name, EQEmu::Inventor inst->SetOrnamentIcon(ornament_icon); inst->SetOrnamentationIDFile(ornament_idfile); - inst->SetOrnamentHeroModel(ornament_hero_model); + inst->SetOrnamentHeroModel(item->HerosForgeModel); if (color > 0) inst->SetColor(color); From 648078d76c50be0f5e1b99e0cbad3b2cfcef6348 Mon Sep 17 00:00:00 2001 From: Uleat Date: Sun, 18 Dec 2016 20:20:27 -0500 Subject: [PATCH 24/29] More NoDrop-related hack abatement --- common/inventory_profile.cpp | 17 ++++++----------- common/inventory_profile.h | 2 +- zone/client.h | 2 +- zone/inventory.cpp | 4 ++-- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/common/inventory_profile.cpp b/common/inventory_profile.cpp index 03b1df4e0..ae17a2a17 100644 --- a/common/inventory_profile.cpp +++ b/common/inventory_profile.cpp @@ -293,18 +293,13 @@ bool EQEmu::InventoryProfile::DeleteItem(int16 slot_id, uint8 quantity) } // Checks All items in a bag for No Drop -bool EQEmu::InventoryProfile::CheckNoDrop(int16 slot_id) { +bool EQEmu::InventoryProfile::CheckNoDrop(int16 slot_id, bool recurse) +{ ItemInstance* inst = GetItem(slot_id); - if (!inst) return false; - if (!inst->GetItem()->NoDrop) return true; - if (inst->GetItem()->ItemClass == 1) { - for (uint8 i = inventory::containerBegin; i < inventory::ContainerCount; i++) { - ItemInstance* bagitem = GetItem(InventoryProfile::CalcSlotId(slot_id, i)); - if (bagitem && !bagitem->GetItem()->NoDrop) - return true; - } - } - return false; + if (!inst) + return false; + + return (!inst->IsDroppable(recurse)); } // Remove item from bucket without memory delete diff --git a/common/inventory_profile.h b/common/inventory_profile.h index 336bb47d8..b9ae1db8c 100644 --- a/common/inventory_profile.h +++ b/common/inventory_profile.h @@ -133,7 +133,7 @@ namespace EQEmu bool DeleteItem(int16 slot_id, uint8 quantity = 0); // Checks All items in a bag for No Drop - bool CheckNoDrop(int16 slot_id); + bool CheckNoDrop(int16 slot_id, bool recurse = true); // Remove item from inventory (and take control of memory) ItemInstance* PopItem(int16 slot_id); diff --git a/zone/client.h b/zone/client.h index 8bb996bdf..803540ffb 100644 --- a/zone/client.h +++ b/zone/client.h @@ -825,7 +825,7 @@ public: bool SummonItem(uint32 item_id, int16 charges = -1, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0, uint32 aug6 = 0, bool attuned = false, uint16 to_slot = EQEmu::inventory::slotCursor, uint32 ornament_icon = 0, uint32 ornament_idfile = 0, uint32 ornament_hero_model = 0); void SetStats(uint8 type,int16 set_val); void IncStats(uint8 type,int16 increase_val); - void DropItem(int16 slot_id); + void DropItem(int16 slot_id, bool recurse = true); int GetItemLinkHash(const EQEmu::ItemInstance* inst); // move to ItemData..or make use of the pre-calculated database field diff --git a/zone/inventory.cpp b/zone/inventory.cpp index c5d7de0fe..d86de7fd8 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -594,9 +594,9 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, } // Drop item from inventory to ground (generally only dropped from SLOT_CURSOR) -void Client::DropItem(int16 slot_id) +void Client::DropItem(int16 slot_id, bool recurse) { - if(GetInv().CheckNoDrop(slot_id) && RuleI(World, FVNoDropFlag) == 0 || + if(GetInv().CheckNoDrop(slot_id, recurse) && RuleI(World, FVNoDropFlag) == 0 || RuleI(Character, MinStatusForNoDropExemptions) < Admin() && RuleI(World, FVNoDropFlag) == 2) { database.SetHackerFlag(this->AccountName(), this->GetCleanName(), "Tried to drop an item on the ground that was nodrop!"); GetInv().DeleteItem(slot_id); From 908a7061cfcddc39379da1db37459435533ca497 Mon Sep 17 00:00:00 2001 From: Drajor Date: Mon, 19 Dec 2016 21:00:55 +1000 Subject: [PATCH 25/29] Hero forge ID in OP_WearChange originating from a client is now set to the correct value prior to being broadcast to other clients. --- zone/client_packet.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 0ba79d15f..1de41d6b3 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -14049,6 +14049,10 @@ void Client::Handle_OP_WearChange(const EQApplicationPacket *app) if (wc->spawn_id != GetID()) return; + // Hero Forge ID needs to be fixed here as RoF2 appears to send an incorrect value. + if (wc->hero_forge_model != 0 && wc->wear_slot_id >= 0 && wc->wear_slot_id < EQEmu::textures::weaponPrimary) + wc->hero_forge_model = GetHerosForgeModel(wc->wear_slot_id); + // we could maybe ignore this and just send our own from moveitem entity_list.QueueClients(this, app, true); return; From 18693998b9762ccf0c01627b0bd82fa5840362fd Mon Sep 17 00:00:00 2001 From: Uleat Date: Mon, 19 Dec 2016 20:58:38 -0500 Subject: [PATCH 26/29] Added logging code to DropItem() --- zone/client_packet.cpp | 3 +++ zone/inventory.cpp | 58 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 1de41d6b3..56936147d 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -5003,6 +5003,9 @@ void Client::Handle_OP_CrashDump(const EQApplicationPacket *app) void Client::Handle_OP_CreateObject(const EQApplicationPacket *app) { + if (Log.log_settings[Logs::Inventory].is_category_enabled) + Log.Out(Logs::Detail, Logs::Inventory, "Handle_OP_CreateObject() [psize: %u] %s", app->size, DumpPacketToString(app).c_str()); + DropItem(EQEmu::inventory::slotCursor); return; } diff --git a/zone/inventory.cpp b/zone/inventory.cpp index d86de7fd8..416a97e28 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -596,8 +596,35 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, // Drop item from inventory to ground (generally only dropped from SLOT_CURSOR) void Client::DropItem(int16 slot_id, bool recurse) { + Log.Out(Logs::General, Logs::Inventory, "'%s' (char_id: %u) Attempting to drop item from slot %i on the ground", + GetCleanName(), CharacterID(), slot_id); + if(GetInv().CheckNoDrop(slot_id, recurse) && RuleI(World, FVNoDropFlag) == 0 || - RuleI(Character, MinStatusForNoDropExemptions) < Admin() && RuleI(World, FVNoDropFlag) == 2) { + RuleI(Character, MinStatusForNoDropExemptions) < Admin() && RuleI(World, FVNoDropFlag) == 2) + { + auto invalid_drop = m_inv.GetItem(slot_id); + if (!invalid_drop) { + Log.Out(Logs::General, Logs::Inventory, "Error in InventoryProfile::CheckNoDrop() - returned 'true' for empty slot"); + } + else { + if (Log.log_settings[Logs::Inventory].is_category_enabled) { + Log.Out(Logs::General, Logs::Inventory, "DropItem() Hack detected - full item parse:"); + Log.Out(Logs::General, Logs::Inventory, "depth: 0, Item: '%s' (id: %u), IsDroppable: %s", + (invalid_drop->GetItem() ? invalid_drop->GetItem()->Name : "null data"), invalid_drop->GetID(), invalid_drop->IsDroppable(false)); + + for (auto iter1 : *invalid_drop->GetContents()) { // depth 1 + Log.Out(Logs::General, Logs::Inventory, "-depth: 1, Item: '%s' (id: %u), IsDroppable: %s", + (iter1.second->GetItem() ? iter1.second->GetItem()->Name : "null data"), iter1.second->GetID(), iter1.second->IsDroppable(false)); + + for (auto iter2 : *iter1.second->GetContents()) { // depth 2 + Log.Out(Logs::General, Logs::Inventory, "--depth: 2, Item: '%s' (id: %u), IsDroppable: %s", + (iter2.second->GetItem() ? iter2.second->GetItem()->Name : "null data"), iter2.second->GetID(), iter2.second->IsDroppable(false)); + } + } + } + } + invalid_drop = nullptr; + database.SetHackerFlag(this->AccountName(), this->GetCleanName(), "Tried to drop an item on the ground that was nodrop!"); GetInv().DeleteItem(slot_id); return; @@ -606,12 +633,39 @@ void Client::DropItem(int16 slot_id, bool recurse) // Take control of item in client inventory EQEmu::ItemInstance *inst = m_inv.PopItem(slot_id); if(inst) { + if (Log.log_settings[Logs::Inventory].is_category_enabled) { + Log.Out(Logs::General, Logs::Inventory, "DropItem() Processing - full item parse:"); + Log.Out(Logs::General, Logs::Inventory, "depth: 0, Item: '%s' (id: %u), IsDroppable: %s", + (inst->GetItem() ? inst->GetItem()->Name : "null data"), inst->GetID(), inst->IsDroppable(false)); + + if (!inst->IsDroppable(false)) + Log.Out(Logs::General, Logs::Error, "Non-droppable item being processed for drop by '%s'", GetCleanName()); + + for (auto iter1 : *inst->GetContents()) { // depth 1 + Log.Out(Logs::General, Logs::Inventory, "-depth: 1, Item: '%s' (id: %u), IsDroppable: %s", + (iter1.second->GetItem() ? iter1.second->GetItem()->Name : "null data"), iter1.second->GetID(), iter1.second->IsDroppable(false)); + + if (!iter1.second->IsDroppable(false)) + Log.Out(Logs::General, Logs::Error, "Non-droppable item being processed for drop by '%s'", GetCleanName()); + + for (auto iter2 : *iter1.second->GetContents()) { // depth 2 + Log.Out(Logs::General, Logs::Inventory, "--depth: 2, Item: '%s' (id: %u), IsDroppable: %s", + (iter2.second->GetItem() ? iter2.second->GetItem()->Name : "null data"), iter2.second->GetID(), iter2.second->IsDroppable(false)); + + if (!iter2.second->IsDroppable(false)) + Log.Out(Logs::General, Logs::Error, "Non-droppable item being processed for drop by '%s'", GetCleanName()); + } + } + } + int i = parse->EventItem(EVENT_DROP_ITEM, this, inst, nullptr, "", slot_id); if(i != 0) { + Log.Out(Logs::General, Logs::Inventory, "Item drop handled by [EVENT_DROP_ITEM]"); safe_delete(inst); } } else { // Item doesn't exist in inventory! + Log.Out(Logs::General, Logs::Inventory, "DropItem() - No item found in slot %i", slot_id); Message(13, "Error: Item not found in slot %i", slot_id); return; } @@ -633,6 +687,8 @@ void Client::DropItem(int16 slot_id, bool recurse) entity_list.AddObject(object, true); object->StartDecay(); + Log.Out(Logs::General, Logs::Inventory, "Item drop handled ut assolet"); + safe_delete(inst); } From a13e32498a639aa93a6541fbb7da69b0147a7faa Mon Sep 17 00:00:00 2001 From: Drajor Date: Wed, 21 Dec 2016 13:03:19 +1000 Subject: [PATCH 27/29] Hero Forge robes are now visible at character select. Items using a robe HF ID need use the actual ID in the DB i.e. 11607-12107. WearChange command modified to allow both shorthand HF IDs and explicit IDs. --- common/item_instance.cpp | 20 ++++++++++---------- zone/command.cpp | 17 ++++------------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/common/item_instance.cpp b/common/item_instance.cpp index d8d23064e..ffec614b4 100644 --- a/common/item_instance.cpp +++ b/common/item_instance.cpp @@ -539,16 +539,16 @@ EQEmu::ItemInstance* EQEmu::ItemInstance::GetOrnamentationAug(int32 ornamentatio } uint32 EQEmu::ItemInstance::GetOrnamentHeroModel(int32 material_slot) const { - uint32 HeroModel = 0; - if (m_ornament_hero_model > 0) - { - HeroModel = m_ornament_hero_model; - if (material_slot >= 0) - { - HeroModel = (m_ornament_hero_model * 100) + material_slot; - } - } - return HeroModel; + // Not a Hero Forge item. + if (m_ornament_hero_model == 0) + return 0; + + // Item is using an explicit Hero Forge ID + if (m_ornament_hero_model >= 1000) + return m_ornament_hero_model; + + // Item is using a shorthand ID + return (m_ornament_hero_model * 100) + material_slot; } bool EQEmu::ItemInstance::UpdateOrnamentationInfo() { diff --git a/zone/command.cpp b/zone/command.cpp index 718feedcc..307b4e949 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -745,24 +745,15 @@ void command_wc(Client *c, const Seperator *sep) uint32 hero_forge_model = 0; uint32 wearslot = atoi(sep->arg[1]); + // Hero Forge if (sep->argnum > 2) { hero_forge_model = atoi(sep->arg[3]); - if (hero_forge_model > 0) - { - // Conversion to simplify the command arguments - // Hero's Forge model is actually model * 1000 + texture * 100 + wearslot - hero_forge_model *= 1000; - hero_forge_model += (atoi(sep->arg[2]) * 100); - hero_forge_model += wearslot; - // For Hero's Forge, slot 7 is actually for Robes, but it still needs to use slot 1 in the packet - if (wearslot == 7) - { - wearslot = 1; - } + if (hero_forge_model != 0 && hero_forge_model < 1000) { + // Shorthand Hero Forge ID. Otherwise use the value the user entered. + hero_forge_model = (hero_forge_model * 100) + wearslot; } - } /* // Leaving here to add color option to the #wc command eventually From 55e78cd8e9ffbcaab3cb28713a34a5860a5d478d Mon Sep 17 00:00:00 2001 From: Drajor Date: Wed, 21 Dec 2016 13:28:05 +1000 Subject: [PATCH 28/29] Fix for error in previous change. ItemInstance::GetOrnamentHeroModel will return zero again when parameter material_slot is the default -1. --- common/item_instance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/item_instance.cpp b/common/item_instance.cpp index ffec614b4..a2860f3e5 100644 --- a/common/item_instance.cpp +++ b/common/item_instance.cpp @@ -540,7 +540,7 @@ EQEmu::ItemInstance* EQEmu::ItemInstance::GetOrnamentationAug(int32 ornamentatio uint32 EQEmu::ItemInstance::GetOrnamentHeroModel(int32 material_slot) const { // Not a Hero Forge item. - if (m_ornament_hero_model == 0) + if (m_ornament_hero_model == 0 || material_slot < 0) return 0; // Item is using an explicit Hero Forge ID From 630ea0d3c63c7f8ecd79610efd56b1eb68db126e Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Wed, 21 Dec 2016 13:45:18 -0500 Subject: [PATCH 29/29] Fix RoF2 OP_GMHideMe --- utils/patches/patch_RoF2.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 19ef8d842..20111e850 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -150,7 +150,7 @@ OP_GMZoneRequest=0x62ac OP_GMZoneRequest2=0x7e1a OP_GMGoto=0x7d8e OP_GMSearchCorpse=0x357c -OP_GMHideMe=0x79c5 +OP_GMHideMe=0x2fab OP_GMDelCorpse=0x607e OP_GMApproval=0x6db5 OP_GMToggle=0x2097