[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.
This commit is contained in:
VladimirMangos 2009-02-27 11:03:09 +03:00
parent 9e7e374077
commit 1fca6de6f3
13 changed files with 458 additions and 122 deletions

View file

@ -22,7 +22,7 @@
DROP TABLE IF EXISTS `db_version`; DROP TABLE IF EXISTS `db_version`;
CREATE TABLE `db_version` ( CREATE TABLE `db_version` (
`version` varchar(120) default NULL, `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'; ) 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 */; /*!40000 ALTER TABLE `spell_affect` ENABLE KEYS */;
UNLOCK TABLES; 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` -- Table structure for table `spell_chain`
-- --

View file

@ -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;

View file

@ -187,6 +187,7 @@ pkgdata_DATA = \
7324_02_characters_character_aura.sql \ 7324_02_characters_character_aura.sql \
7331_01_mangos_command.sql \ 7331_01_mangos_command.sql \
7332_01_mangos_command.sql \ 7332_01_mangos_command.sql \
7349_01_mangos_spell_area.sql \
README README
## Additional files to include when running 'make dist' ## Additional files to include when running 'make dist'
@ -354,4 +355,5 @@ EXTRA_DIST = \
7324_02_characters_character_aura.sql \ 7324_02_characters_character_aura.sql \
7331_01_mangos_command.sql \ 7331_01_mangos_command.sql \
7332_01_mangos_command.sql \ 7332_01_mangos_command.sql \
7349_01_mangos_spell_area.sql \
README README

View file

@ -309,6 +309,7 @@ ChatCommand * ChatHandler::getCommandTable()
{ "skill_fishing_base_level", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadSkillFishingBaseLevelCommand, "", NULL }, { "skill_fishing_base_level", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadSkillFishingBaseLevelCommand, "", NULL },
{ "skinning_loot_template", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadLootTemplatesSkinningCommand, "", NULL }, { "skinning_loot_template", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadLootTemplatesSkinningCommand, "", NULL },
{ "spell_affect", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadSpellAffectCommand, "", 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_chain", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadSpellChainCommand, "", NULL },
{ "spell_elixir", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadSpellElixirCommand, "", NULL }, { "spell_elixir", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadSpellElixirCommand, "", NULL },
{ "spell_learn_spell", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadSpellLearnSpellCommand, "", NULL }, { "spell_learn_spell", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadSpellLearnSpellCommand, "", NULL },

View file

@ -259,6 +259,7 @@ class ChatHandler
bool HandleReloadSkillExtraItemTemplateCommand(const char* args); bool HandleReloadSkillExtraItemTemplateCommand(const char* args);
bool HandleReloadSkillFishingBaseLevelCommand(const char* args); bool HandleReloadSkillFishingBaseLevelCommand(const char* args);
bool HandleReloadSpellAffectCommand(const char* args); bool HandleReloadSpellAffectCommand(const char* args);
bool HandleReloadSpellAreaCommand(const char* args);
bool HandleReloadSpellChainCommand(const char* args); bool HandleReloadSpellChainCommand(const char* args);
bool HandleReloadSpellElixirCommand(const char* args); bool HandleReloadSpellElixirCommand(const char* args);
bool HandleReloadSpellLearnSpellCommand(const char* args); bool HandleReloadSpellLearnSpellCommand(const char* args);

View file

@ -134,6 +134,7 @@ bool ChatHandler::HandleReloadAllSpellCommand(const char*)
HandleReloadSkillDiscoveryTemplateCommand("a"); HandleReloadSkillDiscoveryTemplateCommand("a");
HandleReloadSkillExtraItemTemplateCommand("a"); HandleReloadSkillExtraItemTemplateCommand("a");
HandleReloadSpellAffectCommand("a"); HandleReloadSpellAffectCommand("a");
HandleReloadSpellAreaCommand("a");
HandleReloadSpellChainCommand("a"); HandleReloadSpellChainCommand("a");
HandleReloadSpellElixirCommand("a"); HandleReloadSpellElixirCommand("a");
HandleReloadSpellLearnSpellCommand("a"); HandleReloadSpellLearnSpellCommand("a");
@ -444,6 +445,14 @@ bool ChatHandler::HandleReloadSpellAffectCommand(const char*)
return true; 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*) bool ChatHandler::HandleReloadSpellChainCommand(const char*)
{ {
sLog.outString( "Re-Loading Spell Chain Data... " ); sLog.outString( "Re-Loading Spell Chain Data... " );

View file

@ -12652,6 +12652,19 @@ void Player::AddQuest( Quest const *pQuest, Object *questGiver )
if(questGiver && pQuest->GetQuestStartScript()!=0) if(questGiver && pQuest->GetQuestStartScript()!=0)
sWorld.ScriptsStart(sQuestStartScripts, pQuest->GetQuestStartScript(), questGiver, this); 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(); 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; 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_COUNT);
GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_COMPLETE_QUEST); 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 ) void Player::FailQuest( uint32 quest_id )
@ -18982,26 +19023,12 @@ void Player::UpdateZoneDependentAuras( uint32 newZone )
RemoveSpellsCausingAura(SPELL_AURA_FLY); RemoveSpellsCausingAura(SPELL_AURA_FLY);
} }
// Some spells applied at enter into zone (with subzones) // Some spells applied at enter into zone (with subzones), aura removed in UpdateAreaDependentAuras that called always at zone->area update
switch(newZone) SpellAreaForAreaMapBounds saBounds = spellmgr.GetSpellAreaForAreaMapBounds(newZone);
{ for(SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr)
case 2367: // Old Hillsbrad Foothills if(itr->second->autocast && itr->second->IsFitToRequirements(this,newZone,0))
{ if( !HasAura(itr->second->spellId,0) )
// Human Illusion CastSpell(this,itr->second->spellId,true);
// 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;
}
}
} }
void Player::UpdateAreaDependentAuras( uint32 newArea ) 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();) 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 // 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); RemoveAura(iter);
else else
++iter; ++iter;
} }
// some auras applied at subzone enter // some auras applied at subzone enter
switch(newArea) SpellAreaForAreaMapBounds saBounds = spellmgr.GetSpellAreaForAreaMapBounds(newArea);
{ for(SpellAreaForAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr)
// Dragonmaw Illusion if(itr->second->autocast && itr->second->IsFitToRequirements(this,m_zoneUpdateId,newArea))
case 3759: // Netherwing Ledge if( !HasAura(itr->second->spellId,0) )
case 3939: // Dragonmaw Fortress CastSpell(this,itr->second->spellId,true);
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;
}
} }
uint32 Player::GetCorpseReclaimDelay(bool pvp) const uint32 Player::GetCorpseReclaimDelay(bool pvp) const

View file

@ -3825,8 +3825,8 @@ uint8 Spell::CanCast(bool strict)
return SPELL_FAILED_NOT_IN_ARENA; return SPELL_FAILED_NOT_IN_ARENA;
// zone check // zone check
if (uint8 res= GetSpellAllowedInLocationError(m_spellInfo,m_caster->GetMapId(),m_caster->GetZoneId(),m_caster->GetAreaId(), if (uint8 res= spellmgr.GetSpellAllowedInLocationError(m_spellInfo,m_caster->GetMapId(),m_caster->GetZoneId(),m_caster->GetAreaId(),
m_caster->GetTypeId()==TYPEID_PLAYER ? ((Player*)m_caster)->GetBattleGroundId() : 0)) m_caster->GetTypeId()==TYPEID_PLAYER ? ((Player*)m_caster) : NULL))
return res; return res;
// not let players cast spells at mount (and let do it to creatures) // not let players cast spells at mount (and let do it to creatures)

View file

@ -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); m_modifier.m_amount = caster->SpellHealingBonus(m_target, GetSpellProto(), m_modifier.m_amount, SPELL_DIRECT_DAMAGE);
return; 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 // AT REMOVE
else 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 // AT APPLY & REMOVE

View file

@ -2348,7 +2348,192 @@ bool SpellMgr::IsSpellValid(SpellEntry const* spellInfo, Player* pl, bool msg)
return true; 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 // normal case
if( spellInfo->AreaGroupId > 0) if( spellInfo->AreaGroupId > 0)
@ -2367,75 +2552,26 @@ uint8 GetSpellAllowedInLocationError(SpellEntry const *spellInfo,uint32 map_id,u
return SPELL_FAILED_INCORRECT_AREA; return SPELL_FAILED_INCORRECT_AREA;
} }
// elixirs (all area dependent elixirs have family SPELLFAMILY_POTION, use this for speedup) // DB base check (if non empty then must fit at least single for allow)
if(spellInfo->SpellFamilyName==SPELLFAMILY_POTION) 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(itr->second.IsFitToRequirements(player,zone_id,area_id))
{ return 0;
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;
} }
return SPELL_FAILED_INCORRECT_AREA;
} }
// special cases zone check (maps checked by multimap common id) // bg spell checks
switch(spellInfo->Id) 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 23333: // Warsong Flag
case 23335: // Silverwing 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 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 2584: // Waiting to Resurrect
case 22011: // Spirit Heal Channel case 22011: // Spirit Heal Channel
case 22012: // Spirit Heal case 22012: // Spirit Heal
@ -2448,11 +2584,11 @@ uint8 GetSpellAllowedInLocationError(SpellEntry const *spellInfo,uint32 map_id,u
if(!mapEntry) if(!mapEntry)
return SPELL_FAILED_INCORRECT_AREA; 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 case 44521: // Preparation
{ {
if(!bgInstanceId) if(!player)
return SPELL_FAILED_REQUIRES_AREA; return SPELL_FAILED_REQUIRES_AREA;
MapEntry const* mapEntry = sMapStore.LookupEntry(map_id); MapEntry const* mapEntry = sMapStore.LookupEntry(map_id);
@ -2462,7 +2598,7 @@ uint8 GetSpellAllowedInLocationError(SpellEntry const *spellInfo,uint32 map_id,u
if(!mapEntry->IsBattleGround()) if(!mapEntry->IsBattleGround())
return SPELL_FAILED_REQUIRES_AREA; 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; return bg && bg->GetStatus()==STATUS_WAIT_JOIN ? 0 : SPELL_FAILED_REQUIRES_AREA;
} }
case 32724: // Gold Team (Alliance) case 32724: // Gold Team (Alliance)
@ -2474,11 +2610,11 @@ uint8 GetSpellAllowedInLocationError(SpellEntry const *spellInfo,uint32 map_id,u
if(!mapEntry) if(!mapEntry)
return SPELL_FAILED_INCORRECT_AREA; 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 case 32727: // Arena Preparation
{ {
if(!bgInstanceId) if(!player)
return SPELL_FAILED_REQUIRES_AREA; return SPELL_FAILED_REQUIRES_AREA;
MapEntry const* mapEntry = sMapStore.LookupEntry(map_id); MapEntry const* mapEntry = sMapStore.LookupEntry(map_id);
@ -2488,7 +2624,7 @@ uint8 GetSpellAllowedInLocationError(SpellEntry const *spellInfo,uint32 map_id,u
if(!mapEntry->IsBattleArena()) if(!mapEntry->IsBattleArena())
return SPELL_FAILED_REQUIRES_AREA; 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; return bg && bg->GetStatus()==STATUS_WAIT_JOIN ? 0 : SPELL_FAILED_REQUIRES_AREA;
} }
} }
@ -2632,3 +2768,50 @@ DiminishingReturnsType GetDiminishingReturnsGroupType(DiminishingGroup group)
return DRTYPE_NONE; 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;
}

View file

@ -355,8 +355,6 @@ bool IsSingleTargetSpells(SpellEntry const *spellInfo1, SpellEntry const *spellI
bool IsAuraAddedBySpell(uint32 auraType, uint32 spellId); 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 ) inline bool IsAreaEffectTarget( Targets target )
{ {
switch (target ) switch (target )
@ -679,6 +677,32 @@ class PetAura
}; };
typedef std::map<uint16, PetAura> SpellPetAuraMap; typedef std::map<uint16, PetAura> 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<uint32,SpellArea> SpellAreaMap;
typedef std::multimap<uint32,SpellArea const*> SpellAreaForQuestMap;
typedef std::multimap<uint32,SpellArea const*> SpellAreaForAuraMap;
typedef std::multimap<uint32,SpellArea const*> SpellAreaForAreaMap;
typedef std::pair<SpellAreaMap::const_iterator,SpellAreaMap::const_iterator> SpellAreaMapBounds;
typedef std::pair<SpellAreaForQuestMap::const_iterator,SpellAreaForQuestMap::const_iterator> SpellAreaForQuestMapBounds;
typedef std::pair<SpellAreaForAuraMap::const_iterator, SpellAreaForAuraMap::const_iterator> SpellAreaForAuraMapBounds;
typedef std::pair<SpellAreaForAreaMap::const_iterator, SpellAreaForAreaMap::const_iterator> SpellAreaForAreaMapBounds;
// Spell rank chain (accessed using SpellMgr functions) // Spell rank chain (accessed using SpellMgr functions)
struct SpellChainNode struct SpellChainNode
{ {
@ -965,6 +989,36 @@ class SpellMgr
return NULL; 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 // Modifiers
public: public:
static SpellMgr& Instance(); static SpellMgr& Instance();
@ -983,6 +1037,7 @@ class SpellMgr
void LoadSkillLineAbilityMap(); void LoadSkillLineAbilityMap();
void LoadSpellPetAuras(); void LoadSpellPetAuras();
void LoadPetLevelupSpellMap(); void LoadPetLevelupSpellMap();
void LoadSpellAreas();
private: private:
SpellScriptTarget mSpellScriptTarget; SpellScriptTarget mSpellScriptTarget;
@ -998,6 +1053,12 @@ class SpellMgr
SkillLineAbilityMap mSkillLineAbilityMap; SkillLineAbilityMap mSkillLineAbilityMap;
SpellPetAuraMap mSpellPetAuraMap; SpellPetAuraMap mSpellPetAuraMap;
PetLevelupSpellMap mPetLevelupSpellMap; PetLevelupSpellMap mPetLevelupSpellMap;
SpellAreaMap mSpellAreaMap;
SpellAreaForQuestMap mSpellAreaForQuestMap;
SpellAreaForQuestMap mSpellAreaForActiveQuestMap;
SpellAreaForQuestMap mSpellAreaForQuestEndMap;
SpellAreaForAuraMap mSpellAreaForAuraMap;
SpellAreaForAreaMap mSpellAreaForAreaMap;
}; };
#define spellmgr SpellMgr::Instance() #define spellmgr SpellMgr::Instance()

View file

@ -1202,6 +1202,9 @@ void World::SetInitialWorldSettings()
sLog.outString( ">>> Quests Relations loaded" ); sLog.outString( ">>> Quests Relations loaded" );
sLog.outString(); sLog.outString();
sLog.outString( "Loading SpellArea Data..." ); // must be after quest load
spellmgr.LoadSpellAreas();
sLog.outString( "Loading AreaTrigger definitions..." ); sLog.outString( "Loading AreaTrigger definitions..." );
objmgr.LoadAreaTriggerTeleports(); // must be after item template load objmgr.LoadAreaTriggerTeleports(); // must be after item template load

View file

@ -1,4 +1,4 @@
#ifndef __REVISION_NR_H__ #ifndef __REVISION_NR_H__
#define __REVISION_NR_H__ #define __REVISION_NR_H__
#define REVISION_NR "7348" #define REVISION_NR "7349"
#endif // __REVISION_NR_H__ #endif // __REVISION_NR_H__