diff --git a/sql/mangos.sql b/sql/mangos.sql index e7be4b4c6..d6d6d210c 100644 --- a/sql/mangos.sql +++ b/sql/mangos.sql @@ -23,7 +23,7 @@ DROP TABLE IF EXISTS `db_version`; CREATE TABLE `db_version` ( `version` varchar(120) default NULL, `creature_ai_version` varchar(120) default NULL, - `required_7945_01_mangos_quest_template` bit(1) default NULL + `required_7980_01_mangos_item_required_target` bit(1) default NULL ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Used DB version notes'; -- @@ -1659,6 +1659,27 @@ LOCK TABLES `item_loot_template` WRITE; /*!40000 ALTER TABLE `item_loot_template` ENABLE KEYS */; UNLOCK TABLES; +-- +-- Table structure for table `item_required_target` +-- + +DROP TABLE IF EXISTS `item_required_target`; +CREATE TABLE `item_required_target` ( + `entry` mediumint(8) unsigned NOT NULL, + `type` tinyint(3) unsigned NOT NULL default '0', + `targetEntry` mediumint(8) unsigned NOT NULL default '0', + UNIQUE KEY `entry_type_target` (`entry`,`type`,`targetEntry`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED + +-- +-- Dumping data for table `item_required_target` +-- + +LOCK TABLES `item_required_target` WRITE; +/*!40000 ALTER TABLE `item_required_target` DISABLE KEYS */; +/*!40000 ALTER TABLE `item_required_target` ENABLE KEYS */; +UNLOCK TABLES; + -- -- Table structure for table `item_template` -- diff --git a/sql/updates/7980_01_mangos_item_required_target.sql b/sql/updates/7980_01_mangos_item_required_target.sql new file mode 100644 index 000000000..816702de5 --- /dev/null +++ b/sql/updates/7980_01_mangos_item_required_target.sql @@ -0,0 +1,9 @@ +ALTER TABLE db_version CHANGE COLUMN required_7945_01_mangos_quest_template required_7980_01_mangos_item_required_target bit; + +DROP TABLE IF EXISTS `item_required_target`; +CREATE TABLE `item_required_target` ( + `entry` mediumint(8) unsigned NOT NULL, + `type` tinyint(3) unsigned NOT NULL default '0', + `targetEntry` mediumint(8) unsigned NOT NULL default '0', + UNIQUE KEY `entry_type_target` (`entry`,`type`,`targetEntry`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED diff --git a/sql/updates/Makefile.am b/sql/updates/Makefile.am index f8f1332a6..1aaea9366 100644 --- a/sql/updates/Makefile.am +++ b/sql/updates/Makefile.am @@ -211,6 +211,7 @@ pkgdata_DATA = \ 7932_01_characters_character_pet.sql \ 7938_01_realmd_account.sql \ 7945_01_mangos_quest_template.sql \ + 7980_01_mangos_item_required_target.sql \ README ## Additional files to include when running 'make dist' @@ -402,4 +403,5 @@ EXTRA_DIST = \ 7932_01_characters_character_pet.sql \ 7938_01_realmd_account.sql \ 7945_01_mangos_quest_template.sql \ + 7980_01_mangos_item_required_target.sql \ README diff --git a/src/game/Chat.cpp b/src/game/Chat.cpp index 78b5741ba..0645b3a0e 100644 --- a/src/game/Chat.cpp +++ b/src/game/Chat.cpp @@ -411,6 +411,7 @@ ChatCommand * ChatHandler::getCommandTable() { "gameobject_scripts", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadGameObjectScriptsCommand, "", NULL }, { "item_enchantment_template", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadItemEnchantementsCommand, "", NULL }, { "item_loot_template", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadLootTemplatesItemCommand, "", NULL }, + { "item_required_target", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadItemRequiredTragetCommand, "", NULL }, { "locales_achievement_reward", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadLocalesAchievementRewardCommand,"", NULL }, { "locales_creature", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadLocalesCreatureCommand, "", NULL }, { "locales_gameobject", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadLocalesGameobjectCommand, "", NULL }, diff --git a/src/game/Chat.h b/src/game/Chat.h index 7ba20206f..18ef9ccf2 100644 --- a/src/game/Chat.h +++ b/src/game/Chat.h @@ -331,6 +331,7 @@ class ChatHandler bool HandleReloadGOQuestRelationsCommand(const char* args); bool HandleReloadGOQuestInvRelationsCommand(const char* args); bool HandleReloadItemEnchantementsCommand(const char* args); + bool HandleReloadItemRequiredTragetCommand(const char* args); bool HandleReloadLocalesAchievementRewardCommand(const char* args); bool HandleReloadLocalesCreatureCommand(const char* args); bool HandleReloadLocalesGameobjectCommand(const char* args); diff --git a/src/game/Item.cpp b/src/game/Item.cpp index 5d595e323..3c8bebef6 100644 --- a/src/game/Item.cpp +++ b/src/game/Item.cpp @@ -762,6 +762,23 @@ bool Item::IsFitToSpellRequirements(SpellEntry const* spellInfo) const return true; } +bool Item::IsTargetValidForItemUse(Unit* pUnitTarget) +{ + ItemRequiredTargetMapBounds bounds = objmgr.GetItemRequiredTargetMapBounds(GetProto()->ItemId); + + if (bounds.first == bounds.second) + return true; + + if (!pUnitTarget) + return false; + + for(ItemRequiredTargetMap::const_iterator itr = bounds.first; itr != bounds.second; ++itr) + if(itr->second.IsFitToRequirements(pUnitTarget)) + return true; + + return false; +} + void Item::SetEnchantment(EnchantmentSlot slot, uint32 id, uint32 duration, uint32 charges) { // Better lost small time at check in comparison lost time at item save to DB. @@ -967,4 +984,17 @@ bool Item::IsBindedNotWith( Player const* player ) const { return objmgr.GetPlayerAccountIdByGUID(GetOwnerGUID()) != player->GetSession()->GetAccountId(); } +} + +bool ItemRequiredTarget::IsFitToRequirements( Unit* pUnitTarget ) const +{ + switch(m_uiType) + { + case ITEM_TARGET_TYPE_CREATURE: + return pUnitTarget->isAlive(); + case ITEM_TARGET_TYPE_DEAD: + return !pUnitTarget->isAlive(); + default: + return false; + } } \ No newline at end of file diff --git a/src/game/Item.h b/src/game/Item.h index b13fcf568..0ad45f3d0 100644 --- a/src/game/Item.h +++ b/src/game/Item.h @@ -27,6 +27,7 @@ struct SpellEntry; class Bag; class QueryResult; +class Unit; struct ItemSetEffect { @@ -195,6 +196,24 @@ enum ItemUpdateState ITEM_REMOVED = 3 }; +enum ItemRequiredTargetType +{ + ITEM_TARGET_TYPE_CREATURE = 1, + ITEM_TARGET_TYPE_DEAD = 2 +}; + +#define MAX_ITEM_REQ_TARGET_TYPE 2 + +struct ItemRequiredTarget +{ + ItemRequiredTarget(ItemRequiredTargetType uiType, uint32 uiTargetEntry) : m_uiType(uiType), m_uiTargetEntry(uiTargetEntry) {} + ItemRequiredTargetType m_uiType; + uint32 m_uiTargetEntry; + + // helpers + bool IsFitToRequirements(Unit* pUnitTarget) const; +}; + bool ItemCanGoIntoBag(ItemPrototype const *proto, ItemPrototype const *pBagProto); class MANGOS_DLL_SPEC Item : public Object @@ -230,6 +249,7 @@ class MANGOS_DLL_SPEC Item : public Object bool IsInTrade() const { return mb_in_trade; } bool IsFitToSpellRequirements(SpellEntry const* spellInfo) const; + bool IsTargetValidForItemUse(Unit* pUnitTarget); bool IsLimitedToAnotherMapOrZone( uint32 cur_mapId, uint32 cur_zoneId) const; bool GemsFitSockets() const; @@ -292,6 +312,7 @@ class MANGOS_DLL_SPEC Item : public Object bool hasInvolvedQuest(uint32 /*quest_id*/) const { return false; } bool IsPotion() const { return GetProto()->IsPotion(); } bool IsConjuredConsumable() const { return GetProto()->IsConjuredConsumable(); } + private: uint8 m_slot; Bag *m_container; diff --git a/src/game/Level3.cpp b/src/game/Level3.cpp index 115f3341a..6eeaff488 100644 --- a/src/game/Level3.cpp +++ b/src/game/Level3.cpp @@ -169,6 +169,7 @@ bool ChatHandler::HandleReloadAllItemCommand(const char*) { HandleReloadPageTextsCommand("a"); HandleReloadItemEnchantementsCommand("a"); + HandleReloadItemRequiredTragetCommand("a"); return true; } @@ -584,6 +585,14 @@ bool ChatHandler::HandleReloadItemEnchantementsCommand(const char*) return true; } +bool ChatHandler::HandleReloadItemRequiredTragetCommand(const char*) +{ + sLog.outString( "Re-Loading Item Required Targets Table..." ); + objmgr.LoadItemRequiredTarget(); + SendGlobalSysMessage("DB table `item_required_target` reloaded."); + return true; +} + bool ChatHandler::HandleReloadGameObjectScriptsCommand(const char* arg) { if(sWorld.IsScriptScheduled()) diff --git a/src/game/ObjectMgr.cpp b/src/game/ObjectMgr.cpp index 219d138d5..de564b6c9 100644 --- a/src/game/ObjectMgr.cpp +++ b/src/game/ObjectMgr.cpp @@ -1809,6 +1809,106 @@ void ObjectMgr::LoadItemPrototypes() } } +void ObjectMgr::LoadItemRequiredTarget() +{ + m_ItemRequiredTarget.clear(); // needed for reload case + + uint32 count = 0; + + QueryResult *result = WorldDatabase.Query("SELECT entry,type,targetEntry FROM item_required_target"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded 0 ItemRequiredTarget. DB table `item_required_target` is empty."); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 uiItemId = fields[0].GetUInt32(); + uint32 uiType = fields[1].GetUInt32(); + uint32 uiTargetEntry = fields[2].GetUInt32(); + + ItemPrototype const* pItemProto = sItemStorage.LookupEntry(uiItemId); + + if (!pItemProto) + { + sLog.outErrorDb("Table `item_required_target`: Entry %u listed for TargetEntry %u does not exist in `item_template`.",uiItemId,uiTargetEntry); + continue; + } + + bool bIsItemSpellValid = false; + + for(int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) + { + if (SpellEntry const* pSpellInfo = sSpellStore.LookupEntry(pItemProto->Spells[i].SpellId)) + { + if (pItemProto->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE || + pItemProto->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_NO_DELAY_USE) + { + SpellScriptTarget::const_iterator lower = spellmgr.GetBeginSpellScriptTarget(pSpellInfo->Id); + SpellScriptTarget::const_iterator upper = spellmgr.GetEndSpellScriptTarget(pSpellInfo->Id); + + if (lower != upper) + break; + + if (pSpellInfo->EffectImplicitTargetA[i] == TARGET_CHAIN_DAMAGE || + pSpellInfo->EffectImplicitTargetB[i] == TARGET_CHAIN_DAMAGE || + pSpellInfo->EffectImplicitTargetA[i] == TARGET_DUELVSPLAYER || + pSpellInfo->EffectImplicitTargetB[i] == TARGET_DUELVSPLAYER) + { + bIsItemSpellValid = true; + break; + } + } + } + } + + if (!bIsItemSpellValid) + { + sLog.outErrorDb("Table `item_required_target`: Spell used by item %u does not have implicit target TARGET_CHAIN_DAMAGE(6), TARGET_DUELVSPLAYER(25), already listed in `spell_script_target` or doesn't have item spelltrigger.",uiItemId); + continue; + } + + if (!uiType || uiType > MAX_ITEM_REQ_TARGET_TYPE) + { + sLog.outErrorDb("Table `item_required_target`: Type %u for TargetEntry %u is incorrect.",uiType,uiTargetEntry); + continue; + } + + if (!uiTargetEntry) + { + sLog.outErrorDb("Table `item_required_target`: TargetEntry == 0 for Type (%u).",uiType); + continue; + } + + if (!sCreatureStorage.LookupEntry(uiTargetEntry)) + { + sLog.outErrorDb("Table `item_required_target`: creature template entry %u does not exist.",uiTargetEntry); + continue; + } + + m_ItemRequiredTarget.insert(ItemRequiredTargetMap::value_type(uiItemId,ItemRequiredTarget(ItemRequiredTargetType(uiType),uiTargetEntry))); + + ++count; + } while (result->NextRow()); + + delete result; + + sLog.outString(); + sLog.outString(">> Loaded %u Item required targets", count); +} + void ObjectMgr::LoadPetLevelInfo() { // Loading levels data diff --git a/src/game/ObjectMgr.h b/src/game/ObjectMgr.h index bb58b723b..72465c48a 100644 --- a/src/game/ObjectMgr.h +++ b/src/game/ObjectMgr.h @@ -161,6 +161,8 @@ typedef UNORDERED_MAP NpcOptionLocaleMap; typedef UNORDERED_MAP PointOfInterestLocaleMap; typedef std::multimap QuestRelations; +typedef std::multimap ItemRequiredTargetMap; +typedef std::pair ItemRequiredTargetMapBounds; struct PetLevelInfo { @@ -498,6 +500,7 @@ class ObjectMgr void LoadGameobjects(); void LoadGameobjectRespawnTimes(); void LoadItemPrototypes(); + void LoadItemRequiredTarget(); void LoadItemLocales(); void LoadQuestLocales(); void LoadNpcTextLocales(); @@ -751,6 +754,12 @@ class ObjectMgr uint32 GetScriptId(const char *name); int GetOrNewIndexForLocale(LocaleConstant loc); + + ItemRequiredTargetMapBounds GetItemRequiredTargetMapBounds(uint32 uiItemEntry) const + { + return ItemRequiredTargetMapBounds(m_ItemRequiredTarget.lower_bound(uiItemEntry),m_ItemRequiredTarget.upper_bound(uiItemEntry)); + } + protected: // first free id for selected id type @@ -808,6 +817,8 @@ class ObjectMgr ScriptNameMap m_scriptNames; + ItemRequiredTargetMap m_ItemRequiredTarget; + typedef std::vector LocalForIndex; LocalForIndex m_LocalForIndex; diff --git a/src/game/PetHandler.cpp b/src/game/PetHandler.cpp index 3080f6f13..4125b64a9 100644 --- a/src/game/PetHandler.cpp +++ b/src/game/PetHandler.cpp @@ -245,19 +245,7 @@ void WorldSession::HandlePetAction( WorldPacket & recv_data ) else { if(pet->HasAuraType(SPELL_AURA_MOD_POSSESS)) - { - WorldPacket data(SMSG_CAST_FAILED, (4+1+1)); - data << uint8(0) << uint32(spellid) << uint8(result); - switch (result) - { - case SPELL_FAILED_REQUIRES_SPELL_FOCUS: - data << uint32(spellInfo->RequiresSpellFocus); - break; - default: - break; - } - SendPacket(&data); - } + Spell::SendCastResult(GetPlayer(),spellInfo,0,result); else pet->SendPetCastFail(spellid, result); diff --git a/src/game/Spell.cpp b/src/game/Spell.cpp index 053ad0f1c..2019c39b2 100644 --- a/src/game/Spell.cpp +++ b/src/game/Spell.cpp @@ -2862,18 +2862,26 @@ void Spell::SendCastResult(SpellCastResult result) if(((Player*)m_caster)->GetSession()->PlayerLoading()) // don't send cast results at loading time return; + SendCastResult((Player*)m_caster,m_spellInfo,m_cast_count,result); +} + +void Spell::SendCastResult(Player* caster, SpellEntry const* spellInfo, uint8 cast_count, SpellCastResult result) +{ + if(result == SPELL_CAST_OK) + return; + WorldPacket data(SMSG_CAST_FAILED, (4+1+1)); - data << uint8(m_cast_count); // single cast or multi 2.3 (0/1) - data << uint32(m_spellInfo->Id); + data << uint8(cast_count); // single cast or multi 2.3 (0/1) + data << uint32(spellInfo->Id); data << uint8(result); // problem switch (result) { case SPELL_FAILED_REQUIRES_SPELL_FOCUS: - data << uint32(m_spellInfo->RequiresSpellFocus); + data << uint32(spellInfo->RequiresSpellFocus); break; case SPELL_FAILED_REQUIRES_AREA: // hardcode areas limitation case - switch(m_spellInfo->Id) + switch(spellInfo->Id) { case 41617: // Cenarion Mana Salve case 41619: // Cenarion Healing Salve @@ -2892,26 +2900,26 @@ void Spell::SendCastResult(SpellCastResult result) } break; case SPELL_FAILED_TOTEMS: - if(m_spellInfo->Totem[0]) - data << uint32(m_spellInfo->Totem[0]); - if(m_spellInfo->Totem[1]) - data << uint32(m_spellInfo->Totem[1]); + if(spellInfo->Totem[0]) + data << uint32(spellInfo->Totem[0]); + if(spellInfo->Totem[1]) + data << uint32(spellInfo->Totem[1]); break; case SPELL_FAILED_TOTEM_CATEGORY: - if(m_spellInfo->TotemCategory[0]) - data << uint32(m_spellInfo->TotemCategory[0]); - if(m_spellInfo->TotemCategory[1]) - data << uint32(m_spellInfo->TotemCategory[1]); + if(spellInfo->TotemCategory[0]) + data << uint32(spellInfo->TotemCategory[0]); + if(spellInfo->TotemCategory[1]) + data << uint32(spellInfo->TotemCategory[1]); break; case SPELL_FAILED_EQUIPPED_ITEM_CLASS: - data << uint32(m_spellInfo->EquippedItemClass); - data << uint32(m_spellInfo->EquippedItemSubClassMask); - //data << uint32(m_spellInfo->EquippedItemInventoryTypeMask); + data << uint32(spellInfo->EquippedItemClass); + data << uint32(spellInfo->EquippedItemSubClassMask); + //data << uint32(spellInfo->EquippedItemInventoryTypeMask); break; default: break; } - ((Player*)m_caster)->GetSession()->SendPacket(&data); + caster->GetSession()->SendPacket(&data); } void Spell::SendSpellStart() diff --git a/src/game/Spell.h b/src/game/Spell.h index 3ffa368eb..e734a5b5d 100644 --- a/src/game/Spell.h +++ b/src/game/Spell.h @@ -386,6 +386,7 @@ class Spell bool CheckTarget( Unit* target, uint32 eff ); bool CanAutoCast(Unit* target); + static void SendCastResult(Player* caster, SpellEntry const* spellInfo, uint8 cast_count, SpellCastResult result); void SendCastResult(SpellCastResult result); void SendSpellStart(); void SendSpellGo(); diff --git a/src/game/SpellHandler.cpp b/src/game/SpellHandler.cpp index a38e7eec0..ed09f671a 100644 --- a/src/game/SpellHandler.cpp +++ b/src/game/SpellHandler.cpp @@ -119,9 +119,30 @@ void WorldSession::HandleUseItemOpcode(WorldPacket& recvPacket) } SpellCastTargets targets; - if(!targets.read(&recvPacket, pUser)) + if (!targets.read(&recvPacket, pUser)) return; + targets.Update(pUser); + + if (!pItem->IsTargetValidForItemUse(targets.getUnitTarget())) + { + // free greay item aftre use faul + pUser->SendEquipError(EQUIP_ERR_NONE, pItem, NULL); + + // send spell error + if (SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellid)) + { + // for implicit area/coord target spells + if (IsPointEffectTarget(Targets(spellInfo->EffectImplicitTargetA[0])) || + IsAreaEffectTarget(Targets(spellInfo->EffectImplicitTargetA[0]))) + Spell::SendCastResult(_player,spellInfo,cast_count,SPELL_FAILED_NO_VALID_TARGETS); + // for explicit target spells + else + Spell::SendCastResult(_player,spellInfo,cast_count,SPELL_FAILED_BAD_TARGETS); + } + return; + } + //Note: If script stop casting it must send appropriate data to client to prevent stuck item in gray state. if(!Script->ItemUse(pUser,pItem,targets)) { diff --git a/src/game/SpellMgr.h b/src/game/SpellMgr.h index 81393d980..64e4c6acd 100644 --- a/src/game/SpellMgr.h +++ b/src/game/SpellMgr.h @@ -189,6 +189,24 @@ bool IsSingleTargetSpells(SpellEntry const *spellInfo1, SpellEntry const *spellI bool IsAuraAddedBySpell(uint32 auraType, uint32 spellId); +inline bool IsPointEffectTarget( Targets target ) +{ + switch (target ) + { + case TARGET_INNKEEPER_COORDINATES: + case TARGET_TABLE_X_Y_Z_COORDINATES: + case TARGET_CASTER_COORDINATES: + case TARGET_SCRIPT_COORDINATES: + case TARGET_CURRENT_ENEMY_COORDINATES: + case TARGET_DUELVSPLAYER_COORDINATES: + case TARGET_DYNAMIC_OBJECT_COORDINATES: + return true; + default: + break; + } + return false; +} + inline bool IsAreaEffectTarget( Targets target ) { switch (target ) diff --git a/src/game/World.cpp b/src/game/World.cpp index 82c046ecb..ca617075a 100644 --- a/src/game/World.cpp +++ b/src/game/World.cpp @@ -1152,6 +1152,9 @@ void World::SetInitialWorldSettings() sLog.outString( "Loading SpellsScriptTarget..."); spellmgr.LoadSpellScriptTarget(); // must be after LoadCreatureTemplates and LoadGameobjectInfo + sLog.outString( "Loading ItemRequiredTarget..."); + objmgr.LoadItemRequiredTarget(); + sLog.outString( "Loading Creature Reputation OnKill Data..." ); objmgr.LoadReputationOnKill(); diff --git a/src/game/debugcmds.cpp b/src/game/debugcmds.cpp index b09e90256..a3b4627b6 100644 --- a/src/game/debugcmds.cpp +++ b/src/game/debugcmds.cpp @@ -37,7 +37,7 @@ bool ChatHandler::HandleDebugSendSpellFailCommand(const char* args) return false; char* px = strtok((char*)args, " "); - if(!px) + if (!px) return false; uint8 failnum = (uint8)atoi(px); @@ -50,14 +50,13 @@ bool ChatHandler::HandleDebugSendSpellFailCommand(const char* args) char* p2 = strtok(NULL, " "); uint8 failarg2 = p2 ? (uint8)atoi(p2) : 0; - WorldPacket data(SMSG_CAST_FAILED, 5); data << uint8(0); data << uint32(133); data << uint8(failnum); - if(p1 || p2) + if (p1 || p2) data << uint32(failarg1); - if(p2) + if (p2) data << uint32(failarg2); m_session->SendPacket(&data); diff --git a/src/shared/revision_nr.h b/src/shared/revision_nr.h index de401d6a8..1e3653cc2 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 "7979" + #define REVISION_NR "7980" #endif // __REVISION_NR_H__