From 1fca6de6f36501ea37e8956f838a89a36950ed00 Mon Sep 17 00:00:00 2001 From: VladimirMangos Date: Fri, 27 Feb 2009 11:03:09 +0300 Subject: [PATCH] [7349] Implement spell cast depenences from area/quest.aura state store in new table `spell_area`. * It allow store requirenments: area, active or rewarded quest (until possible another quest not rewarded), aura present at character, character race/gender. * Listed spell can marked as auto-casted when fit requirents. In this case spell requirements checked at zone/subzone update (and then resurraction also), quest start/reward, dummy aura apply. * Old hardcoded lines for similar check removed from sources and required DB support for work now. --- sql/mangos.sql | 29 +- sql/updates/7349_01_mangos_spell_area.sql | 15 ++ sql/updates/Makefile.am | 2 + src/game/Chat.cpp | 1 + src/game/Chat.h | 1 + src/game/Level3.cpp | 9 + src/game/Player.cpp | 103 +++---- src/game/Spell.cpp | 4 +- src/game/SpellAuras.cpp | 31 +++ src/game/SpellMgr.cpp | 315 +++++++++++++++++----- src/game/SpellMgr.h | 65 ++++- src/game/World.cpp | 3 + src/shared/revision_nr.h | 2 +- 13 files changed, 458 insertions(+), 122 deletions(-) create mode 100644 sql/updates/7349_01_mangos_spell_area.sql diff --git a/sql/mangos.sql b/sql/mangos.sql index 0b46f3f9e..829a7586d 100644 --- a/sql/mangos.sql +++ b/sql/mangos.sql @@ -22,7 +22,7 @@ DROP TABLE IF EXISTS `db_version`; CREATE TABLE `db_version` ( `version` varchar(120) default NULL, - `required_7332_01_mangos_command` bit(1) default NULL + `required_7349_01_mangos_spell_area` bit(1) default NULL ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Used DB version notes'; -- @@ -13288,6 +13288,33 @@ LOCK TABLES `spell_affect` WRITE; /*!40000 ALTER TABLE `spell_affect` ENABLE KEYS */; UNLOCK TABLES; +-- +-- Table structure for table `spell_area` +-- + +DROP TABLE IF EXISTS `spell_area`; +CREATE TABLE `spell_area` ( + `spell` mediumint(8) unsigned NOT NULL default '0', + `area` mediumint(8) unsigned NOT NULL default '0', + `quest_start` mediumint(8) unsigned NOT NULL default '0', + `quest_start_active` tinyint(1) unsigned NOT NULL default '0', + `quest_end` mediumint(8) unsigned NOT NULL default '0', + `aura_spell` mediumint(8) unsigned NOT NULL default '0', + `racemask` mediumint(8) unsigned NOT NULL default '0', + `gender` tinyint(1) unsigned NOT NULL default '2', + `autocast` tinyint(1) unsigned NOT NULL default '0', + PRIMARY KEY (`spell`,`area`,`quest_start`,`quest_start_active`,`aura_spell`,`racemask`,`gender`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- +-- Dumping data for table `spell_area` +-- + +LOCK TABLES `spell_area` WRITE; +/*!40000 ALTER TABLE `spell_area` DISABLE KEYS */; +/*!40000 ALTER TABLE `spell_area` ENABLE KEYS */; +UNLOCK TABLES; + -- -- Table structure for table `spell_chain` -- diff --git a/sql/updates/7349_01_mangos_spell_area.sql b/sql/updates/7349_01_mangos_spell_area.sql new file mode 100644 index 000000000..969fff950 --- /dev/null +++ b/sql/updates/7349_01_mangos_spell_area.sql @@ -0,0 +1,15 @@ +ALTER TABLE db_version CHANGE COLUMN required_7332_01_mangos_command required_7349_01_mangos_spell_area bit; + +DROP TABLE IF EXISTS `spell_area`; +CREATE TABLE `spell_area` ( + `spell` mediumint(8) unsigned NOT NULL default '0', + `area` mediumint(8) unsigned NOT NULL default '0', + `quest_start` mediumint(8) unsigned NOT NULL default '0', + `quest_start_active` tinyint(1) unsigned NOT NULL default '0', + `quest_end` mediumint(8) unsigned NOT NULL default '0', + `aura_spell` mediumint(8) unsigned NOT NULL default '0', + `racemask` mediumint(8) unsigned NOT NULL default '0', + `gender` tinyint(1) unsigned NOT NULL default '2', + `autocast` tinyint(1) unsigned NOT NULL default '0', + PRIMARY KEY (`spell`,`area`,`quest_start`,`quest_start_active`,`aura_spell`,`racemask`,`gender`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; diff --git a/sql/updates/Makefile.am b/sql/updates/Makefile.am index 899b7b430..6f9ec4dd4 100644 --- a/sql/updates/Makefile.am +++ b/sql/updates/Makefile.am @@ -187,6 +187,7 @@ pkgdata_DATA = \ 7324_02_characters_character_aura.sql \ 7331_01_mangos_command.sql \ 7332_01_mangos_command.sql \ + 7349_01_mangos_spell_area.sql \ README ## Additional files to include when running 'make dist' @@ -354,4 +355,5 @@ EXTRA_DIST = \ 7324_02_characters_character_aura.sql \ 7331_01_mangos_command.sql \ 7332_01_mangos_command.sql \ + 7349_01_mangos_spell_area.sql \ README diff --git a/src/game/Chat.cpp b/src/game/Chat.cpp index 4cf9cdbc5..6ed450502 100644 --- a/src/game/Chat.cpp +++ b/src/game/Chat.cpp @@ -309,6 +309,7 @@ ChatCommand * ChatHandler::getCommandTable() { "skill_fishing_base_level", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadSkillFishingBaseLevelCommand, "", NULL }, { "skinning_loot_template", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadLootTemplatesSkinningCommand, "", NULL }, { "spell_affect", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadSpellAffectCommand, "", NULL }, + { "spell_area", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadSpellLearnSpellCommand, "", NULL }, { "spell_chain", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadSpellChainCommand, "", NULL }, { "spell_elixir", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadSpellElixirCommand, "", NULL }, { "spell_learn_spell", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadSpellLearnSpellCommand, "", NULL }, diff --git a/src/game/Chat.h b/src/game/Chat.h index 87dd31610..0ab3bd902 100644 --- a/src/game/Chat.h +++ b/src/game/Chat.h @@ -259,6 +259,7 @@ class ChatHandler bool HandleReloadSkillExtraItemTemplateCommand(const char* args); bool HandleReloadSkillFishingBaseLevelCommand(const char* args); bool HandleReloadSpellAffectCommand(const char* args); + bool HandleReloadSpellAreaCommand(const char* args); bool HandleReloadSpellChainCommand(const char* args); bool HandleReloadSpellElixirCommand(const char* args); bool HandleReloadSpellLearnSpellCommand(const char* args); diff --git a/src/game/Level3.cpp b/src/game/Level3.cpp index 4a89e25d8..51e840dbe 100644 --- a/src/game/Level3.cpp +++ b/src/game/Level3.cpp @@ -134,6 +134,7 @@ bool ChatHandler::HandleReloadAllSpellCommand(const char*) HandleReloadSkillDiscoveryTemplateCommand("a"); HandleReloadSkillExtraItemTemplateCommand("a"); HandleReloadSpellAffectCommand("a"); + HandleReloadSpellAreaCommand("a"); HandleReloadSpellChainCommand("a"); HandleReloadSpellElixirCommand("a"); HandleReloadSpellLearnSpellCommand("a"); @@ -444,6 +445,14 @@ bool ChatHandler::HandleReloadSpellAffectCommand(const char*) return true; } +bool ChatHandler::HandleReloadSpellAreaCommand(const char*) +{ + sLog.outString( "Re-Loading SpellArea Data..." ); + spellmgr.LoadSpellAreas(); + SendGlobalSysMessage("DB table `spell_area` (spell dependences from area/quest/auras state) reloaded."); + return true; +} + bool ChatHandler::HandleReloadSpellChainCommand(const char*) { sLog.outString( "Re-Loading Spell Chain Data... " ); diff --git a/src/game/Player.cpp b/src/game/Player.cpp index d3f63d42a..aa97aaef2 100644 --- a/src/game/Player.cpp +++ b/src/game/Player.cpp @@ -12652,6 +12652,19 @@ void Player::AddQuest( Quest const *pQuest, Object *questGiver ) if(questGiver && pQuest->GetQuestStartScript()!=0) sWorld.ScriptsStart(sQuestStartScripts, pQuest->GetQuestStartScript(), questGiver, this); + // Some spells applied at quest activation + SpellAreaForQuestMapBounds saBounds = spellmgr.GetSpellAreaForQuestMapBounds(quest_id,true); + if(saBounds.first != saBounds.second) + { + uint32 zone = GetZoneId(); + uint32 area = GetAreaId(); + + for(SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) + if(itr->second->autocast && itr->second->IsFitToRequirements(this,zone,area)) + if( !HasAura(itr->second->spellId,0) ) + CastSpell(this,itr->second->spellId,true); + } + UpdateForQuestsGO(); } @@ -12842,6 +12855,34 @@ void Player::RewardQuest( Quest const *pQuest, uint32 reward, Object* questGiver if (q_status.uState != QUEST_NEW) q_status.uState = QUEST_CHANGED; GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST_COUNT); GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST); + + uint32 zone = 0; + uint32 area = 0; + + // remove auras from spells with quest reward state limitations + SpellAreaForQuestMapBounds saEndBounds = spellmgr.GetSpellAreaForQuestEndMapBounds(quest_id); + if(saEndBounds.first != saEndBounds.second) + { + uint32 zone = GetZoneId(); + uint32 area = GetAreaId(); + + for(SpellAreaForAreaMap::const_iterator itr = saEndBounds.first; itr != saEndBounds.second; ++itr) + if(!itr->second->IsFitToRequirements(this,zone,area)) + RemoveAurasDueToSpell(itr->second->spellId); + } + + // Some spells applied at quest reward + SpellAreaForQuestMapBounds saBounds = spellmgr.GetSpellAreaForQuestMapBounds(quest_id,false); + if(saBounds.first != saBounds.second) + { + if(!zone) zone = GetZoneId(); + if(!area) area = GetAreaId(); + + for(SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) + if(itr->second->autocast && itr->second->IsFitToRequirements(this,zone,area)) + if( !HasAura(itr->second->spellId,0) ) + CastSpell(this,itr->second->spellId,true); + } } void Player::FailQuest( uint32 quest_id ) @@ -18982,26 +19023,12 @@ void Player::UpdateZoneDependentAuras( uint32 newZone ) RemoveSpellsCausingAura(SPELL_AURA_FLY); } - // Some spells applied at enter into zone (with subzones) - switch(newZone) - { - case 2367: // Old Hillsbrad Foothills - { - // Human Illusion - // NOTE: these are removed by RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_CHANGE_MAP); - uint32 spellid = 0; - // all horde races - if( GetTeam() == HORDE ) - spellid = getGender() == GENDER_FEMALE ? 35481 : 35480; - // and some alliance races - else if( getRace() == RACE_NIGHTELF || getRace() == RACE_DRAENEI ) - spellid = getGender() == GENDER_FEMALE ? 35483 : 35482; - - if(spellid && !HasAura(spellid,0) ) - CastSpell(this,spellid,true); - break; - } - } + // Some spells applied at enter into zone (with subzones), aura removed in UpdateAreaDependentAuras that called always at zone->area update + SpellAreaForAreaMapBounds saBounds = spellmgr.GetSpellAreaForAreaMapBounds(newZone); + for(SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) + if(itr->second->autocast && itr->second->IsFitToRequirements(this,newZone,0)) + if( !HasAura(itr->second->spellId,0) ) + CastSpell(this,itr->second->spellId,true); } void Player::UpdateAreaDependentAuras( uint32 newArea ) @@ -19010,42 +19037,18 @@ void Player::UpdateAreaDependentAuras( uint32 newArea ) for(AuraMap::iterator iter = m_Auras.begin(); iter != m_Auras.end();) { // use m_zoneUpdateId for speed: UpdateArea called from UpdateZone or instead UpdateZone in both cases m_zoneUpdateId up-to-date - if(GetSpellAllowedInLocationError(iter->second->GetSpellProto(),GetMapId(),m_zoneUpdateId,newArea,GetBattleGroundId())!=0) + if(spellmgr.GetSpellAllowedInLocationError(iter->second->GetSpellProto(),GetMapId(),m_zoneUpdateId,newArea,this)!=0) RemoveAura(iter); else ++iter; } // some auras applied at subzone enter - switch(newArea) - { - // Dragonmaw Illusion - case 3759: // Netherwing Ledge - case 3939: // Dragonmaw Fortress - case 3966: // Dragonmaw Base Camp - if( GetDummyAura(40214) ) - { - if( !HasAura(40216,0) ) - CastSpell(this,40216,true); - if( !HasAura(42016,0) ) - CastSpell(this,42016,true); - } - break; - // Dominion Over Acherus - case 4281: // Acherus: The Ebon Hold - case 4342: // Acherus: The Ebon Hold - if( HasSpell(51721) ) - if( !HasAura(51721,0) ) - CastSpell(this,51721,true); - break; - // Mist of the Kvaldir - case 4028: //Riplash Strand - case 4029: //Riplash Ruins - case 4106: //Garrosh's Landing - case 4031: //Pal'ea - CastSpell(this,54119,true); - break; - } + SpellAreaForAreaMapBounds saBounds = spellmgr.GetSpellAreaForAreaMapBounds(newArea); + for(SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) + if(itr->second->autocast && itr->second->IsFitToRequirements(this,m_zoneUpdateId,newArea)) + if( !HasAura(itr->second->spellId,0) ) + CastSpell(this,itr->second->spellId,true); } uint32 Player::GetCorpseReclaimDelay(bool pvp) const diff --git a/src/game/Spell.cpp b/src/game/Spell.cpp index 99b3df7b4..9ac30695c 100644 --- a/src/game/Spell.cpp +++ b/src/game/Spell.cpp @@ -3825,8 +3825,8 @@ uint8 Spell::CanCast(bool strict) return SPELL_FAILED_NOT_IN_ARENA; // zone check - if (uint8 res= GetSpellAllowedInLocationError(m_spellInfo,m_caster->GetMapId(),m_caster->GetZoneId(),m_caster->GetAreaId(), - m_caster->GetTypeId()==TYPEID_PLAYER ? ((Player*)m_caster)->GetBattleGroundId() : 0)) + if (uint8 res= spellmgr.GetSpellAllowedInLocationError(m_spellInfo,m_caster->GetMapId(),m_caster->GetZoneId(),m_caster->GetAreaId(), + m_caster->GetTypeId()==TYPEID_PLAYER ? ((Player*)m_caster) : NULL)) return res; // not let players cast spells at mount (and let do it to creatures) diff --git a/src/game/SpellAuras.cpp b/src/game/SpellAuras.cpp index 01ff8aa57..8f713f38b 100644 --- a/src/game/SpellAuras.cpp +++ b/src/game/SpellAuras.cpp @@ -2062,6 +2062,22 @@ void Aura::HandleAuraDummy(bool apply, bool Real) m_modifier.m_amount = caster->SpellHealingBonus(m_target, GetSpellProto(), m_modifier.m_amount, SPELL_DIRECT_DAMAGE); return; } + + // some auras applied at aura apply + if(GetEffIndex()==0 && m_target->GetTypeId()==TYPEID_PLAYER) + { + SpellAreaForAreaMapBounds saBounds = spellmgr.GetSpellAreaForAuraMapBounds(GetId()); + if(saBounds.first != saBounds.second) + { + uint32 zone = m_target->GetZoneId(); + uint32 area = m_target->GetAreaId(); + + for(SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) + if(itr->second->autocast && itr->second->IsFitToRequirements((Player*)m_target,zone,area)) + if( !m_target->HasAura(itr->second->spellId,0) ) + m_target->CastSpell(m_target,itr->second->spellId,true); + } + } } // AT REMOVE else @@ -2151,6 +2167,21 @@ void Aura::HandleAuraDummy(bool apply, bool Real) } } + + // some auras remove at aura remove + if(GetEffIndex()==0 && m_target->GetTypeId()==TYPEID_PLAYER) + { + SpellAreaForAreaMapBounds saBounds = spellmgr.GetSpellAreaForAuraMapBounds(GetId()); + if(saBounds.first != saBounds.second) + { + uint32 zone = m_target->GetZoneId(); + uint32 area = m_target->GetAreaId(); + + for(SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) + if(!itr->second->IsFitToRequirements((Player*)m_target,zone,area)) + m_target->RemoveAurasDueToSpell(itr->second->spellId); + } + } } // AT APPLY & REMOVE diff --git a/src/game/SpellMgr.cpp b/src/game/SpellMgr.cpp index fcaf501fa..e164f8d05 100644 --- a/src/game/SpellMgr.cpp +++ b/src/game/SpellMgr.cpp @@ -2348,7 +2348,192 @@ bool SpellMgr::IsSpellValid(SpellEntry const* spellInfo, Player* pl, bool msg) return true; } -uint8 GetSpellAllowedInLocationError(SpellEntry const *spellInfo,uint32 map_id,uint32 zone_id,uint32 area_id, uint32 bgInstanceId) +void SpellMgr::LoadSpellAreas() +{ + mSpellAreaMap.clear(); // need for reload case + mSpellAreaForQuestMap.clear(); + mSpellAreaForActiveQuestMap.clear(); + mSpellAreaForQuestEndMap.clear(); + mSpellAreaForAuraMap.clear(); + + uint32 count = 0; + + // 0 1 2 3 4 5 6 7 8 + QueryResult *result = WorldDatabase.Query("SELECT spell, area, quest_start, quest_start_active, quest_end, aura_spell, racemask, gender, autocast FROM spell_area"); + + if( !result ) + { + barGoLink bar( 1 ); + + bar.step(); + + sLog.outString(); + sLog.outString( ">> Loaded %u spell area requirements", count ); + return; + } + + barGoLink bar( result->GetRowCount() ); + + do + { + Field *fields = result->Fetch(); + + bar.step(); + + uint32 spell = fields[0].GetUInt32(); + SpellArea spellArea; + spellArea.spellId = spell; + spellArea.areaId = fields[1].GetUInt32(); + spellArea.questStart = fields[2].GetUInt32(); + spellArea.questStartCanActive = fields[3].GetBool(); + spellArea.questEnd = fields[4].GetUInt32(); + spellArea.auraSpell = fields[5].GetUInt32(); + spellArea.raceMask = fields[6].GetUInt32(); + spellArea.gender = Gender(fields[7].GetUInt8()); + + if(!sSpellStore.LookupEntry(spell)) + { + sLog.outErrorDb("Spell %u listed in `spell_area` does not exist", spell); + continue; + } + + if(mSpellAreaForAuraMap.find(spellArea.spellId)!=mSpellAreaForAuraMap.end()) + { + sLog.outErrorDb("Spell %u listed in `spell_area` have aura spell (%u) requirement that already listed in table itself", spell,spellArea.auraSpell); + continue; + } + + if(spellArea.areaId && !GetAreaEntryByAreaID(spellArea.areaId)) + { + sLog.outErrorDb("Spell %u listed in `spell_area` have wrong area (%u) requirement", spell,spellArea.areaId); + continue; + } + + if(spellArea.questStart && !objmgr.GetQuestTemplate(spellArea.questStart)) + { + sLog.outErrorDb("Spell %u listed in `spell_area` have wrong start quest (%u) requirement", spell,spellArea.questStart); + continue; + } + + if(spellArea.questEnd) + { + if(!objmgr.GetQuestTemplate(spellArea.questEnd)) + { + sLog.outErrorDb("Spell %u listed in `spell_area` have wrong end quest (%u) requirement", spell,spellArea.questEnd); + continue; + } + + if(spellArea.questEnd==spellArea.questStart && !spellArea.questStartCanActive) + { + sLog.outErrorDb("Spell %u listed in `spell_area` have quest (%u) requirement for start and end in same time", spell,spellArea.questEnd); + continue; + } + } + + if(spellArea.auraSpell) + { + SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellArea.auraSpell); + if(!spellInfo) + { + sLog.outErrorDb("Spell %u listed in `spell_area` have wrong aura spell (%u) requirement", spell,spellArea.auraSpell); + continue; + } + + if(spellInfo->EffectApplyAuraName[0]!=SPELL_AURA_DUMMY) + { + sLog.outErrorDb("Spell %u listed in `spell_area` have aura spell requirement (%u) without dummy aura in effect 0", spell,spellArea.auraSpell); + continue; + } + + if(spellArea.auraSpell==spellArea.spellId) + { + sLog.outErrorDb("Spell %u listed in `spell_area` have aura spell (%u) requirement for itself", spell,spellArea.auraSpell); + continue; + } + + // not allow autocast chains by auraSpell field + if(spellArea.autocast) + { + bool chain = false; + SpellAreaForAuraMapBounds saBound = GetSpellAreaForAuraMapBounds(spellArea.spellId); + for(SpellAreaForAuraMap::const_iterator itr = saBound.first; itr != saBound.second; ++itr) + { + if(itr->second->autocast) + { + chain = true; + break; + } + } + + if(chain) + { + sLog.outErrorDb("Spell %u listed in `spell_area` have aura spell (%u) requirement that itself autocast from aura", spell,spellArea.auraSpell); + continue; + } + + SpellAreaMapBounds saBound2 = GetSpellAreaMapBounds(spellArea.auraSpell); + for(SpellAreaMap::const_iterator itr2 = saBound2.first; itr2 != saBound2.second; ++itr2) + { + if(itr2->second.autocast && itr2->second.auraSpell) + { + chain = true; + break; + } + } + + if(chain) + { + sLog.outErrorDb("Spell %u listed in `spell_area` have aura spell (%u) requirement that itself autocast from aura", spell,spellArea.auraSpell); + continue; + } + } + } + + if(spellArea.raceMask && (spellArea.raceMask & RACEMASK_ALL_PLAYABLE)==0) + { + sLog.outErrorDb("Spell %u listed in `spell_area` have wrong race mask (%u) requirement", spell,spellArea.raceMask); + continue; + } + + if(spellArea.gender!=GENDER_NONE && spellArea.gender!=GENDER_FEMALE && spellArea.gender!=GENDER_MALE) + { + sLog.outErrorDb("Spell %u listed in `spell_area` have wrong gender (%u) requirement", spell,spellArea.gender); + continue; + } + + SpellArea const* sa = &mSpellAreaMap.insert(SpellAreaMap::value_type(spell,spellArea))->second; + + // for search by current zone/subzone at zone/subzone change + if(spellArea.areaId) + mSpellAreaForAreaMap.insert(SpellAreaForAreaMap::value_type(spellArea.areaId,sa)); + + // for search at quest start/reward + if(spellArea.questStart) + { + if(spellArea.questStartCanActive) + mSpellAreaForActiveQuestMap.insert(SpellAreaForQuestMap::value_type(spellArea.questStart,sa)); + else + mSpellAreaForQuestMap.insert(SpellAreaForQuestMap::value_type(spellArea.questStart,sa)); + } + + // for search at quest start/reward + if(spellArea.questEnd) + mSpellAreaForQuestEndMap.insert(SpellAreaForQuestMap::value_type(spellArea.questEnd,sa)); + + // for search at aura apply + if(spellArea.auraSpell) + mSpellAreaForAuraMap.insert(SpellAreaForAuraMap::value_type(spellArea.auraSpell,sa)); + + ++count; + } while( result->NextRow() ); + + delete result; + + sLog.outString(); + sLog.outString( ">> Loaded %u spell area requirements", count ); +} + +uint8 SpellMgr::GetSpellAllowedInLocationError(SpellEntry const *spellInfo, uint32 map_id, uint32 zone_id, uint32 area_id, Player const* player) { // normal case if( spellInfo->AreaGroupId > 0) @@ -2367,75 +2552,26 @@ uint8 GetSpellAllowedInLocationError(SpellEntry const *spellInfo,uint32 map_id,u return SPELL_FAILED_INCORRECT_AREA; } - // elixirs (all area dependent elixirs have family SPELLFAMILY_POTION, use this for speedup) - if(spellInfo->SpellFamilyName==SPELLFAMILY_POTION) + // DB base check (if non empty then must fit at least single for allow) + SpellAreaMapBounds saBounds = spellmgr.GetSpellAreaMapBounds(spellInfo->Id); + if(saBounds.first != saBounds.second) { - if(uint32 mask = spellmgr.GetSpellElixirMask(spellInfo->Id)) + for(SpellAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr) { - if(mask & ELIXIR_BATTLE_MASK) - { - if(spellInfo->Id==45373) // Bloodberry Elixir - return zone_id==4075 ? 0 : SPELL_FAILED_REQUIRES_AREA; - } - if(mask & ELIXIR_UNSTABLE_MASK) - { - // in the Blade's Edge Mountains Plateaus and Gruul's Lair. - return zone_id ==3522 || map_id==565 ? 0 : SPELL_FAILED_INCORRECT_AREA; - } - if(mask & ELIXIR_SHATTRATH_MASK) - { - // in Tempest Keep, Serpentshrine Cavern, Caverns of Time: Mount Hyjal, Black Temple, Sunwell Plateau - if(zone_id ==3607 || map_id==534 || map_id==564 || zone_id==4075) - return 0; - - MapEntry const* mapEntry = sMapStore.LookupEntry(map_id); - if(!mapEntry) - return SPELL_FAILED_INCORRECT_AREA; - - return mapEntry->multimap_id==206 ? 0 : SPELL_FAILED_INCORRECT_AREA; - } - - // elixirs not have another limitations - return 0; + if(itr->second.IsFitToRequirements(player,zone_id,area_id)) + return 0; } + return SPELL_FAILED_INCORRECT_AREA; } - // special cases zone check (maps checked by multimap common id) + // bg spell checks switch(spellInfo->Id) { - case 41618: // Bottled Nethergon Energy - case 41620: // Bottled Nethergon Vapor - { - MapEntry const* mapEntry = sMapStore.LookupEntry(map_id); - if(!mapEntry) - return SPELL_FAILED_INCORRECT_AREA; - - return mapEntry->multimap_id==206 ? 0 : SPELL_FAILED_REQUIRES_AREA; - } - case 41617: // Cenarion Mana Salve - case 41619: // Cenarion Healing Salve - { - MapEntry const* mapEntry = sMapStore.LookupEntry(map_id); - if(!mapEntry) - return SPELL_FAILED_INCORRECT_AREA; - - return mapEntry->multimap_id==207 ? 0 : SPELL_FAILED_REQUIRES_AREA; - } - case 40216: // Dragonmaw Illusion - case 42016: // Dragonmaw Illusion - return area_id == 3759 || area_id == 3966 || area_id == 3939 ? 0 : SPELL_FAILED_INCORRECT_AREA; - case 51721: // Dominion Over Acherus - case 54055: // Dominion Over Acherus - return area_id == 4281 || area_id == 4342 ? 0 : SPELL_FAILED_INCORRECT_AREA; - case 51852: // The Eye of Acherus - return map_id == 609 ? 0 : SPELL_FAILED_REQUIRES_AREA; - case 54119: // Mist of the Kvaldir - return area_id == 4028 || area_id == 4029 || area_id == 4106 || area_id == 4031 ? 0 : SPELL_FAILED_INCORRECT_AREA; case 23333: // Warsong Flag case 23335: // Silverwing Flag - return map_id == 489 && bgInstanceId ? 0 : SPELL_FAILED_REQUIRES_AREA; + return map_id == 489 && player && player->InBattleGround() ? 0 : SPELL_FAILED_REQUIRES_AREA; case 34976: // Netherstorm Flag - return map_id == 566 && bgInstanceId ? 0 : SPELL_FAILED_REQUIRES_AREA; + return map_id == 566 && player && player->InBattleGround() ? 0 : SPELL_FAILED_REQUIRES_AREA; case 2584: // Waiting to Resurrect case 22011: // Spirit Heal Channel case 22012: // Spirit Heal @@ -2448,11 +2584,11 @@ uint8 GetSpellAllowedInLocationError(SpellEntry const *spellInfo,uint32 map_id,u if(!mapEntry) return SPELL_FAILED_INCORRECT_AREA; - return mapEntry->IsBattleGround() && bgInstanceId ? 0 : SPELL_FAILED_REQUIRES_AREA; + return mapEntry->IsBattleGround() && player && player->InBattleGround() ? 0 : SPELL_FAILED_REQUIRES_AREA; } case 44521: // Preparation { - if(!bgInstanceId) + if(!player) return SPELL_FAILED_REQUIRES_AREA; MapEntry const* mapEntry = sMapStore.LookupEntry(map_id); @@ -2462,7 +2598,7 @@ uint8 GetSpellAllowedInLocationError(SpellEntry const *spellInfo,uint32 map_id,u if(!mapEntry->IsBattleGround()) return SPELL_FAILED_REQUIRES_AREA; - BattleGround* bg = sBattleGroundMgr.GetBattleGround(bgInstanceId); + BattleGround* bg = player->GetBattleGround(); return bg && bg->GetStatus()==STATUS_WAIT_JOIN ? 0 : SPELL_FAILED_REQUIRES_AREA; } case 32724: // Gold Team (Alliance) @@ -2474,11 +2610,11 @@ uint8 GetSpellAllowedInLocationError(SpellEntry const *spellInfo,uint32 map_id,u if(!mapEntry) return SPELL_FAILED_INCORRECT_AREA; - return mapEntry->IsBattleArena() && bgInstanceId ? 0 : SPELL_FAILED_REQUIRES_AREA; + return mapEntry->IsBattleArena() && player && player->InBattleGround() ? 0 : SPELL_FAILED_REQUIRES_AREA; } case 32727: // Arena Preparation { - if(!bgInstanceId) + if(!player) return SPELL_FAILED_REQUIRES_AREA; MapEntry const* mapEntry = sMapStore.LookupEntry(map_id); @@ -2488,7 +2624,7 @@ uint8 GetSpellAllowedInLocationError(SpellEntry const *spellInfo,uint32 map_id,u if(!mapEntry->IsBattleArena()) return SPELL_FAILED_REQUIRES_AREA; - BattleGround* bg = sBattleGroundMgr.GetBattleGround(bgInstanceId); + BattleGround* bg = player->GetBattleGround(); return bg && bg->GetStatus()==STATUS_WAIT_JOIN ? 0 : SPELL_FAILED_REQUIRES_AREA; } } @@ -2632,3 +2768,50 @@ DiminishingReturnsType GetDiminishingReturnsGroupType(DiminishingGroup group) return DRTYPE_NONE; } + +bool SpellArea::IsFitToRequirements(Player const* player, uint32 newZone, uint32 newArea) const +{ + if(gender!=GENDER_NONE) + { + // not in expected gender + if(!player || gender != player->getGender()) + return false; + } + + if(raceMask) + { + // not in expected race + if(!player || !(raceMask & player->getRaceMask())) + return false; + } + + if(areaId) + { + // not in expected zone + if(newZone!=areaId && newArea!=areaId) + return false; + } + + if(questStart) + { + // not in expected required quest state + if(!player || (!questStartCanActive || !player->IsActiveQuest(questStart)) && !player->GetQuestRewardStatus(questStart)) + return false; + } + + if(questEnd) + { + // not in expected forbidden quest state + if(!player || player->GetQuestRewardStatus(questEnd)) + return false; + } + + if(auraSpell) + { + // not have expected aura + if(!player || !player->HasAura(auraSpell,0)) + return false; + } + + return true; +} diff --git a/src/game/SpellMgr.h b/src/game/SpellMgr.h index 5522337fa..157981d5c 100644 --- a/src/game/SpellMgr.h +++ b/src/game/SpellMgr.h @@ -355,8 +355,6 @@ bool IsSingleTargetSpells(SpellEntry const *spellInfo1, SpellEntry const *spellI bool IsAuraAddedBySpell(uint32 auraType, uint32 spellId); -uint8 GetSpellAllowedInLocationError(SpellEntry const *spellInfo,uint32 map_id,uint32 zone_id,uint32 area_id,uint32 bgInstanceId); - inline bool IsAreaEffectTarget( Targets target ) { switch (target ) @@ -679,6 +677,32 @@ class PetAura }; typedef std::map SpellPetAuraMap; +struct SpellArea +{ + uint32 spellId; + uint32 areaId; // zone/subzone/or 0 is not limited to zone + uint32 questStart; // quest start (quest must be active or rewarded for spell apply) + uint32 questEnd; // quest end (quest don't must be rewarded for spell apply) + uint32 auraSpell; // spell aura must be applied for spell apply + uint32 raceMask; // can be applied only to races + Gender gender; // can be applied only to gender + bool questStartCanActive; // if true then quest start can be active (not only rewarded) + bool autocast; // if true then auto applied at area enter, in other case just allowed to cast + + // helpers + bool IsFitToRequirements(Player const* player, uint32 newZone, uint32 newArea) const; +}; + +typedef std::multimap SpellAreaMap; +typedef std::multimap SpellAreaForQuestMap; +typedef std::multimap SpellAreaForAuraMap; +typedef std::multimap SpellAreaForAreaMap; +typedef std::pair SpellAreaMapBounds; +typedef std::pair SpellAreaForQuestMapBounds; +typedef std::pair SpellAreaForAuraMapBounds; +typedef std::pair SpellAreaForAreaMapBounds; + + // Spell rank chain (accessed using SpellMgr functions) struct SpellChainNode { @@ -965,6 +989,36 @@ class SpellMgr return NULL; } + uint8 GetSpellAllowedInLocationError(SpellEntry const *spellInfo, uint32 map_id, uint32 zone_id, uint32 area_id, Player const* player = NULL); + + SpellAreaMapBounds GetSpellAreaMapBounds(uint32 spell_id) const + { + return SpellAreaMapBounds(mSpellAreaMap.lower_bound(spell_id),mSpellAreaMap.upper_bound(spell_id)); + } + + SpellAreaForQuestMapBounds GetSpellAreaForQuestMapBounds(uint32 quest_id, bool active) const + { + if(active) + return SpellAreaForQuestMapBounds(mSpellAreaForActiveQuestMap.lower_bound(quest_id),mSpellAreaForActiveQuestMap.upper_bound(quest_id)); + else + return SpellAreaForQuestMapBounds(mSpellAreaForQuestMap.lower_bound(quest_id),mSpellAreaForQuestMap.upper_bound(quest_id)); + } + + SpellAreaForQuestMapBounds GetSpellAreaForQuestEndMapBounds(uint32 quest_id) const + { + return SpellAreaForQuestMapBounds(mSpellAreaForQuestEndMap.lower_bound(quest_id),mSpellAreaForQuestEndMap.upper_bound(quest_id)); + } + + SpellAreaForAuraMapBounds GetSpellAreaForAuraMapBounds(uint32 spell_id) const + { + return SpellAreaForAuraMapBounds(mSpellAreaForAuraMap.lower_bound(spell_id),mSpellAreaForAuraMap.upper_bound(spell_id)); + } + + SpellAreaForAreaMapBounds GetSpellAreaForAreaMapBounds(uint32 area_id) const + { + return SpellAreaForAreaMapBounds(mSpellAreaForAreaMap.lower_bound(area_id),mSpellAreaForAreaMap.upper_bound(area_id)); + } + // Modifiers public: static SpellMgr& Instance(); @@ -983,6 +1037,7 @@ class SpellMgr void LoadSkillLineAbilityMap(); void LoadSpellPetAuras(); void LoadPetLevelupSpellMap(); + void LoadSpellAreas(); private: SpellScriptTarget mSpellScriptTarget; @@ -998,6 +1053,12 @@ class SpellMgr SkillLineAbilityMap mSkillLineAbilityMap; SpellPetAuraMap mSpellPetAuraMap; PetLevelupSpellMap mPetLevelupSpellMap; + SpellAreaMap mSpellAreaMap; + SpellAreaForQuestMap mSpellAreaForQuestMap; + SpellAreaForQuestMap mSpellAreaForActiveQuestMap; + SpellAreaForQuestMap mSpellAreaForQuestEndMap; + SpellAreaForAuraMap mSpellAreaForAuraMap; + SpellAreaForAreaMap mSpellAreaForAreaMap; }; #define spellmgr SpellMgr::Instance() diff --git a/src/game/World.cpp b/src/game/World.cpp index 38d0e9f87..8b04917a7 100644 --- a/src/game/World.cpp +++ b/src/game/World.cpp @@ -1202,6 +1202,9 @@ void World::SetInitialWorldSettings() sLog.outString( ">>> Quests Relations loaded" ); sLog.outString(); + sLog.outString( "Loading SpellArea Data..." ); // must be after quest load + spellmgr.LoadSpellAreas(); + sLog.outString( "Loading AreaTrigger definitions..." ); objmgr.LoadAreaTriggerTeleports(); // must be after item template load diff --git a/src/shared/revision_nr.h b/src/shared/revision_nr.h index ede9681cf..201f685b2 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 "7348" + #define REVISION_NR "7349" #endif // __REVISION_NR_H__