diff --git a/src/game/Bag.cpp b/src/game/Bag.cpp index a67e66ea0..d263ad929 100644 --- a/src/game/Bag.cpp +++ b/src/game/Bag.cpp @@ -203,6 +203,17 @@ uint32 Bag::GetItemCount( uint32 item, Item* eItem ) const return count; } +uint32 Bag::GetItemCountWithLimitCategory(uint32 limitCategory) const +{ + uint32 count = 0; + for(uint32 i = 0; i < GetBagSize(); ++i) + if (m_bagslot[i]) + if (m_bagslot[i]->GetProto()->ItemLimitCategory == limitCategory ) + count += m_bagslot[i]->GetCount(); + + return count; +} + uint8 Bag::GetSlotByItemGUID(uint64 guid) const { for(uint32 i = 0; i < GetBagSize(); ++i) diff --git a/src/game/Bag.h b/src/game/Bag.h index bbbfd7ca3..53ec788bf 100644 --- a/src/game/Bag.h +++ b/src/game/Bag.h @@ -43,6 +43,7 @@ class Bag : public Item Item* GetItemByPos( uint8 slot ) const; uint32 GetItemCount( uint32 item, Item* eItem = NULL ) const; + uint32 GetItemCountWithLimitCategory(uint32 limitCategory) const; uint8 GetSlotByItemGUID(uint64 guid) const; bool IsEmpty() const; diff --git a/src/game/DBCEnums.h b/src/game/DBCEnums.h index ed1af0d7f..a6112d537 100644 --- a/src/game/DBCEnums.h +++ b/src/game/DBCEnums.h @@ -323,6 +323,12 @@ enum ItemEnchantmentType ITEM_ENCHANTMENT_TYPE_PRISMATIC_SOCKET = 8 }; +enum ItemLimitCategoryMode +{ + ITEM_LIMIT_CATEGORY_MODE_HAVE = 0, // limit applied to amount items in inventory/bank + ITEM_LIMIT_CATEGORY_MODE_EQUIP = 1, // limit applied to amount equipped items (including used gems) +}; + enum TotemCategoryType { TOTEM_CATEGORY_TYPE_KNIFE = 1, diff --git a/src/game/DBCStructure.h b/src/game/DBCStructure.h index efc818bf7..3df6e72a5 100644 --- a/src/game/DBCStructure.h +++ b/src/game/DBCStructure.h @@ -1020,7 +1020,7 @@ struct ItemLimitCategoryEntry //char* name[16] // 1-16 m_name_lang // 17 name flags uint32 maxCount; // 18, max allowed equipped as item or in gem slot - //uint32 unk; // 19, 1 for gems only... + uint32 mode; // 19, 0 = have, 1 = equip (enum ItemLimitCategoryMode) }; struct ItemRandomPropertiesEntry diff --git a/src/game/DBCfmt.h b/src/game/DBCfmt.h index d16e15c89..474347669 100644 --- a/src/game/DBCfmt.h +++ b/src/game/DBCfmt.h @@ -66,7 +66,7 @@ const char ItemBagFamilyfmt[]="nxxxxxxxxxxxxxxxxx"; //const char ItemDisplayTemplateEntryfmt[]="nxxxxxxxxxxixxxxxxxxxxx"; //const char ItemCondExtCostsEntryfmt[]="xiii"; const char ItemExtendedCostEntryfmt[]="niiiiiiiiiiiiiix"; -const char ItemLimitCategoryEntryfmt[]="nxxxxxxxxxxxxxxxxxix"; +const char ItemLimitCategoryEntryfmt[]="nxxxxxxxxxxxxxxxxxii"; const char ItemRandomPropertiesfmt[]="nxiiiiissssssssssssssssx"; const char ItemRandomSuffixfmt[]="nssssssssssssssssxxiiiiiiiiii"; const char ItemSetEntryfmt[]="dssssssssssssssssxxxxxxxxxxxxxxxxxxiiiiiiiiiiiiiiiiii"; diff --git a/src/game/Group.cpp b/src/game/Group.cpp index 4090d91df..34eba5a51 100644 --- a/src/game/Group.cpp +++ b/src/game/Group.cpp @@ -796,7 +796,7 @@ void Group::CountTheRoll(Rolls::iterator rollI, uint32 NumberOfPlayers) else { item->is_blocked = false; - player->SendEquipError( msg, NULL, NULL ); + player->SendEquipError( msg, NULL, NULL, roll->itemid ); } } } @@ -848,7 +848,7 @@ void Group::CountTheRoll(Rolls::iterator rollI, uint32 NumberOfPlayers) else { item->is_blocked = false; - player->SendEquipError( msg, NULL, NULL ); + player->SendEquipError( msg, NULL, NULL, roll->itemid ); } } else if(rollvote == DISENCHANT) diff --git a/src/game/ItemHandler.cpp b/src/game/ItemHandler.cpp index c0001a3db..2e454418c 100644 --- a/src/game/ItemHandler.cpp +++ b/src/game/ItemHandler.cpp @@ -1230,6 +1230,8 @@ void WorldSession::HandleSocketOpcode(WorldPacket& recv_data) { if(ItemLimitCategoryEntry const* limitEntry = sItemLimitCategoryStore.LookupEntry(iGemProto->ItemLimitCategory)) { + // NOTE: limitEntry->mode not checked because if item have have-limit then it applied and to equip case + for (int j = 0; j < MAX_GEM_SOCKETS; ++j) { if (Gems[j]) diff --git a/src/game/Level3.cpp b/src/game/Level3.cpp index 73918b927..ff510c81b 100644 --- a/src/game/Level3.cpp +++ b/src/game/Level3.cpp @@ -2262,7 +2262,7 @@ bool ChatHandler::HandleAddItemSetCommand(const char* args) } else { - pl->SendEquipError( msg, NULL, NULL ); + pl->SendEquipError( msg, NULL, NULL, pProto->ItemId ); PSendSysMessage(LANG_ITEM_CANNOT_CREATE, pProto->ItemId, 1); } } diff --git a/src/game/LootHandler.cpp b/src/game/LootHandler.cpp index 91baf4223..6e7cc1e1c 100644 --- a/src/game/LootHandler.cpp +++ b/src/game/LootHandler.cpp @@ -154,7 +154,7 @@ void WorldSession::HandleAutostoreLootItemOpcode( WorldPacket & recv_data ) player->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_EPIC_ITEM, item->itemid, item->count); } else - player->SendEquipError( msg, NULL, NULL ); + player->SendEquipError( msg, NULL, NULL, item->itemid ); } void WorldSession::HandleLootMoneyOpcode( WorldPacket & /*recv_data*/ ) @@ -496,8 +496,10 @@ void WorldSession::HandleLootMasterGiveOpcode( WorldPacket & recv_data ) uint8 msg = target->CanStoreNewItem( NULL_BAG, NULL_SLOT, dest, item.itemid, item.count ); if ( msg != EQUIP_ERR_OK ) { - target->SendEquipError( msg, NULL, NULL ); - _player->SendEquipError( msg, NULL, NULL ); // send duplicate of error massage to master looter + target->SendEquipError( msg, NULL, NULL, item.itemid ); + + // send duplicate of error massage to master looter + _player->SendEquipError( msg, NULL, NULL, item.itemid ); return; } diff --git a/src/game/Player.cpp b/src/game/Player.cpp index 65d63ce2c..5403725c3 100644 --- a/src/game/Player.cpp +++ b/src/game/Player.cpp @@ -7242,10 +7242,10 @@ void Player::CastItemUseSpell(Item *item,SpellCastTargets const& targets,uint8 c uint32 learning_spell_id = proto->Spells[1].SpellId; SpellEntry const *spellInfo = sSpellStore.LookupEntry(learn_spell_id); - if(!spellInfo) + if (!spellInfo) { sLog.outError("Player::CastItemUseSpell: Item (Entry: %u) in have wrong spell id %u, ignoring ",proto->ItemId, learn_spell_id); - SendEquipError(EQUIP_ERR_NONE,item,NULL); + SendEquipError(EQUIP_ERR_NONE, item); return; } @@ -8593,6 +8593,35 @@ uint32 Player::GetItemCount( uint32 item, bool inBankAlso, Item* skipItem ) cons return count; } +uint32 Player::GetItemCountWithLimitCategory( uint32 limitCategory ) const +{ + uint32 count = 0; + for(int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) + if (Item *pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (pItem->GetProto()->ItemLimitCategory == limitCategory) + count += pItem->GetCount(); + + for(int i = KEYRING_SLOT_START; i < CURRENCYTOKEN_SLOT_END; ++i) + if (Item *pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (pItem->GetProto()->ItemLimitCategory == limitCategory) + count += pItem->GetCount(); + + for(int i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + count += pBag->GetItemCountWithLimitCategory(limitCategory); + + for(int i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i) + if (Item *pItem = GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (pItem->GetProto()->ItemLimitCategory == limitCategory) + count += pItem->GetCount(); + + for(int i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) + if (Bag* pBag = (Bag*)GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + count += pBag->GetItemCountWithLimitCategory(limitCategory); + + return count; +} + Item* Player::GetItemByGuid( uint64 guid ) const { for(int i = EQUIPMENT_SLOT_START; i < INVENTORY_SLOT_ITEM_END; ++i) @@ -8981,16 +9010,40 @@ uint8 Player::_CanTakeMoreSimilarItems(uint32 entry, uint32 count, Item* pItem, } // no maximum - if(pProto->MaxCount <= 0) - return EQUIP_ERR_OK; - - uint32 curcount = GetItemCount(pProto->ItemId,true,pItem); - - if (curcount + count > uint32(pProto->MaxCount)) + if(pProto->MaxCount > 0) { - if(no_space_count) - *no_space_count = count +curcount - pProto->MaxCount; - return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS; + uint32 curcount = GetItemCount(pProto->ItemId,true,pItem); + + if (curcount + count > uint32(pProto->MaxCount)) + { + if(no_space_count) + *no_space_count = count +curcount - pProto->MaxCount; + return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS; + } + } + + // check unique-equipped limit + if (pProto->ItemLimitCategory) + { + ItemLimitCategoryEntry const* limitEntry = sItemLimitCategoryStore.LookupEntry(pProto->ItemLimitCategory); + if (!limitEntry) + { + if(no_space_count) + *no_space_count = count; + return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED; + } + + if (limitEntry->mode == ITEM_LIMIT_CATEGORY_MODE_HAVE) + { + uint32 curcount = GetItemCountWithLimitCategory(pProto->ItemLimitCategory); + + if (curcount + count > uint32(limitEntry->maxCount)) + { + if(no_space_count) + *no_space_count = count + curcount - limitEntry->maxCount; + return EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_COUNT_EXCEEDED_IS; + } + } } return EQUIP_ERR_OK; @@ -10429,12 +10482,12 @@ void Player::SetAmmo( uint32 item ) return; // check ammo - if(item) + if (item) { uint8 msg = CanUseAmmo( item ); - if( msg != EQUIP_ERR_OK ) + if (msg != EQUIP_ERR_OK) { - SendEquipError( msg, NULL, NULL ); + SendEquipError(msg, NULL, NULL, item); return; } } @@ -10450,7 +10503,7 @@ void Player::RemoveAmmo() m_ammoDPS = 0.0f; - if(CanModifyStats()) + if (CanModifyStats()) UpdateDamagePhysical(RANGED_ATTACK); } @@ -11674,7 +11727,7 @@ void Player::RemoveItemFromBuyBackSlot( uint32 slot, bool del ) } } -void Player::SendEquipError( uint8 msg, Item* pItem, Item *pItem2 ) +void Player::SendEquipError( uint8 msg, Item* pItem, Item *pItem2, uint32 itemid /*= 0*/ ) { sLog.outDebug( "WORLD: Sent SMSG_INVENTORY_CHANGE_FAILURE (%u)", msg); WorldPacket data(SMSG_INVENTORY_CHANGE_FAILURE, 1+8+8+1); @@ -11690,25 +11743,28 @@ void Player::SendEquipError( uint8 msg, Item* pItem, Item *pItem2 ) { case EQUIP_ERR_CANT_EQUIP_LEVEL_I: case EQUIP_ERR_PURCHASE_LEVEL_TOO_LOW: - { - ItemPrototype const* proto = pItem ? pItem->GetProto() : NULL; - data << uint32(proto ? proto->RequiredLevel : 0); - } break; + { + ItemPrototype const* proto = pItem ? pItem->GetProto() : sObjectMgr.GetItemPrototype(itemid); + data << uint32(proto ? proto->RequiredLevel : 0); + break; + } case EQUIP_ERR_EVENT_AUTOEQUIP_BIND_CONFIRM: // no idea about this one... - { - data << uint64(0); - data << uint32(0); - data << uint64(0); - } break; + { + data << uint64(0); + data << uint32(0); + data << uint64(0); + break; + } case EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_COUNT_EXCEEDED_IS: case EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_SOCKETED_EXCEEDED_IS: case EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_EQUIPPED_EXCEEDED_IS: - { - ItemPrototype const* proto = pItem ? pItem->GetProto() : NULL; - data << uint32(proto ? proto->ItemLimitCategory : 0); - } break; - default: - break; + { + ItemPrototype const* proto = pItem ? pItem->GetProto() : sObjectMgr.GetItemPrototype(itemid); + data << uint32(proto ? proto->ItemLimitCategory : 0); + break; + } + default: + break; } } GetSession()->SendPacket(&data); @@ -12888,7 +12944,7 @@ bool Player::CanAddQuest( Quest const *pQuest, bool msg ) return true; else if( msg2 != EQUIP_ERR_OK ) { - SendEquipError( msg2, NULL, NULL ); + SendEquipError(msg2, NULL, NULL, srcitem); return false; } } @@ -13000,7 +13056,7 @@ bool Player::CanRewardQuest( Quest const *pQuest, bool msg ) GetItemCount(pQuest->ReqItemId[i]) < pQuest->ReqItemCount[i] ) { if(msg) - SendEquipError( EQUIP_ERR_ITEM_NOT_FOUND, NULL, NULL ); + SendEquipError( EQUIP_ERR_ITEM_NOT_FOUND, NULL, NULL, pQuest->ReqItemId[i]); return false; } } @@ -13027,7 +13083,7 @@ bool Player::CanRewardQuest( Quest const *pQuest, uint32 reward, bool msg ) uint8 res = CanStoreNewItem( NULL_BAG, NULL_SLOT, dest, pQuest->RewChoiceItemId[reward], pQuest->RewChoiceItemCount[reward] ); if( res != EQUIP_ERR_OK ) { - SendEquipError( res, NULL, NULL ); + SendEquipError( res, NULL, NULL, pQuest->RewChoiceItemId[reward] ); return false; } } @@ -13725,7 +13781,7 @@ bool Player::GiveQuestSourceItem( Quest const *pQuest ) else if( msg == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS ) return true; else - SendEquipError( msg, NULL, NULL ); + SendEquipError( msg, NULL, NULL, srcitem ); return false; } @@ -13750,7 +13806,7 @@ bool Player::TakeQuestSourceItem( uint32 quest_id, bool msg ) if(res != EQUIP_ERR_OK) { if(msg) - SendEquipError( res, NULL, NULL ); + SendEquipError( res, NULL, NULL, srcitem ); return false; } @@ -18056,7 +18112,7 @@ bool Player::BuyItemFromVendor(uint64 vendorguid, uint32 item, uint8 count, uint uint8 msg = CanStoreNewItem( bag, slot, dest, item, pProto->BuyCount * count ); if (msg != EQUIP_ERR_OK) { - SendEquipError( msg, NULL, NULL ); + SendEquipError( msg, NULL, NULL, item ); return false; } @@ -18101,7 +18157,7 @@ bool Player::BuyItemFromVendor(uint64 vendorguid, uint32 item, uint8 count, uint uint8 msg = CanEquipNewItem( slot, dest, item, false ); if (msg != EQUIP_ERR_OK) { - SendEquipError( msg, NULL, NULL ); + SendEquipError( msg, NULL, NULL, item ); return false; } @@ -20472,7 +20528,7 @@ void Player::AutoStoreLoot(uint8 bag, uint8 slot, uint32 loot_id, LootStore cons msg = CanStoreNewItem( NULL_BAG, NULL_SLOT,dest,lootItem->itemid,lootItem->count); if(msg != EQUIP_ERR_OK) { - SendEquipError( msg, NULL, NULL ); + SendEquipError( msg, NULL, NULL, lootItem->itemid ); continue; } @@ -20689,14 +20745,16 @@ uint8 Player::CanEquipUniqueItem( ItemPrototype const* itemProto, uint8 except_s { ItemLimitCategoryEntry const* limitEntry = sItemLimitCategoryStore.LookupEntry(itemProto->ItemLimitCategory); if(!limitEntry) - return EQUIP_ERR_ITEM_UNIQUE_EQUIPABLE; + return EQUIP_ERR_ITEM_CANT_BE_EQUIPPED; + + // NOTE: limitEntry->mode not checked because if item have have-limit then it applied and to equip case if(limit_count > limitEntry->maxCount) - return EQUIP_ERR_ITEM_UNIQUE_EQUIPABLE; // attempt add too many limit category items (gems) + return EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_EQUIPPED_EXCEEDED_IS; // there is an equip limit on this item if(HasItemOrGemWithLimitCategoryEquipped(itemProto->ItemLimitCategory,limitEntry->maxCount-limit_count+1,except_slot)) - return EQUIP_ERR_ITEM_UNIQUE_EQUIPABLE; + return EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_EQUIPPED_EXCEEDED_IS; } return EQUIP_ERR_OK; diff --git a/src/game/Player.h b/src/game/Player.h index b2ff7f219..abebe7384 100644 --- a/src/game/Player.h +++ b/src/game/Player.h @@ -1148,6 +1148,7 @@ class MANGOS_DLL_SPEC Player : public Unit void SetSheath( SheathState sheathed ); // overwrite Unit version uint8 FindEquipSlot( ItemPrototype const* proto, uint32 slot, bool swap ) const; uint32 GetItemCount( uint32 item, bool inBankAlso = false, Item* skipItem = NULL ) const; + uint32 GetItemCountWithLimitCategory(uint32 limitCategory) const; Item* GetItemByGuid( uint64 guid ) const; Item* GetItemByPos( uint16 pos ) const; Item* GetItemByPos( uint8 bag, uint8 slot ) const; @@ -1243,7 +1244,7 @@ class MANGOS_DLL_SPEC Player : public Unit Item* GetItemFromBuyBackSlot( uint32 slot ); void RemoveItemFromBuyBackSlot( uint32 slot, bool del ); uint32 GetMaxKeyringSize() const { return KEYRING_SLOT_END-KEYRING_SLOT_START; } - void SendEquipError( uint8 msg, Item* pItem, Item *pItem2 ); + void SendEquipError( uint8 msg, Item* pItem, Item *pItem2 = NULL, uint32 itemid = 0 ); void SendBuyError( uint8 msg, Creature* pCreature, uint32 item, uint32 param ); void SendSellError( uint8 msg, Creature* pCreature, uint64 guid, uint32 param ); void AddWeaponProficiency(uint32 newflag) { m_WeaponProficiency |= newflag; } diff --git a/src/game/Spell.cpp b/src/game/Spell.cpp index 0eada1763..97f5a81ce 100644 --- a/src/game/Spell.cpp +++ b/src/game/Spell.cpp @@ -5616,7 +5616,7 @@ SpellCastResult Spell::CheckItems() uint8 msg = p_caster->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, m_spellInfo->EffectItemType[i], 1 ); if (msg != EQUIP_ERR_OK ) { - p_caster->SendEquipError( msg, NULL, NULL ); + p_caster->SendEquipError( msg, NULL, NULL, m_spellInfo->EffectItemType[i] ); return SPELL_FAILED_DONT_REPORT; } } diff --git a/src/game/SpellAuras.cpp b/src/game/SpellAuras.cpp index 1f9001bcb..de92bd275 100644 --- a/src/game/SpellAuras.cpp +++ b/src/game/SpellAuras.cpp @@ -3485,7 +3485,7 @@ void Aura::HandleChannelDeathItem(bool apply, bool Real) if( msg != EQUIP_ERR_OK ) { count-=noSpaceForCount; - ((Player*)caster)->SendEquipError( msg, NULL, NULL ); + ((Player*)caster)->SendEquipError( msg, NULL, NULL, spellInfo->EffectItemType[m_effIndex] ); if (count==0) return; } diff --git a/src/game/SpellEffects.cpp b/src/game/SpellEffects.cpp index 5d67741ca..532d6bc08 100644 --- a/src/game/SpellEffects.cpp +++ b/src/game/SpellEffects.cpp @@ -3192,7 +3192,7 @@ void Spell::DoCreateItem(SpellEffectIndex eff_idx, uint32 itemtype) else { // if not created by another reason from full inventory or unique items amount limitation - player->SendEquipError( msg, NULL, NULL ); + player->SendEquipError( msg, NULL, NULL, newitemid ); return; } } diff --git a/src/game/debugcmds.cpp b/src/game/debugcmds.cpp index 1596c7093..c803cb4e3 100644 --- a/src/game/debugcmds.cpp +++ b/src/game/debugcmds.cpp @@ -98,7 +98,7 @@ bool ChatHandler::HandleDebugSendEquipErrorCommand(const char* args) return false; uint8 msg = atoi(args); - m_session->GetPlayer()->SendEquipError(msg, 0, 0); + m_session->GetPlayer()->SendEquipError(msg, NULL, NULL); return true; } diff --git a/src/shared/revision_nr.h b/src/shared/revision_nr.h index 93e9953c4..564d0fa0e 100644 --- a/src/shared/revision_nr.h +++ b/src/shared/revision_nr.h @@ -1,4 +1,4 @@ #ifndef __REVISION_NR_H__ #define __REVISION_NR_H__ - #define REVISION_NR "9500" + #define REVISION_NR "9501" #endif // __REVISION_NR_H__