Merge branch 'master' into Playerbot

This commit is contained in:
Yunfan Li
2025-06-28 10:46:23 +08:00
63 changed files with 998 additions and 449 deletions
@@ -79,7 +79,7 @@ void LoginDatabaseConnection::DoPrepareStatements()
PrepareStatement(LOGIN_DEL_REALM_CHARACTERS, "DELETE FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_REP_REALM_CHARACTERS, "REPLACE INTO realmcharacters (numchars, acctid, realmid) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_SUM_REALM_CHARACTERS, "SELECT SUM(numchars) FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_ACCOUNT, "INSERT INTO account(username, salt, verifier, expansion, joindate) VALUES(?, ?, ?, ?, NOW())", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_ACCOUNT, "INSERT INTO account(username, salt, verifier, expansion, reg_mail, email, joindate) VALUES(?, ?, ?, ?, ?, ?, NOW())", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_REALM_CHARACTERS_INIT, "INSERT INTO realmcharacters (realmid, acctid, numchars) SELECT realmlist.id, account.id, 0 FROM realmlist, account LEFT JOIN realmcharacters ON acctid=account.id WHERE acctid IS NULL", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_EXPANSION, "UPDATE account SET expansion = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_ACCOUNT_LOCK, "UPDATE account SET locked = ? WHERE id = ?", CONNECTION_ASYNC);
+42 -6
View File
@@ -191,8 +191,26 @@ SpellCastResult UnitAI::DoCast(uint32 spellId)
{
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId))
{
bool playerOnly = spellInfo->HasAttribute(SPELL_ATTR3_ONLY_ON_PLAYER);
target = SelectTarget(SelectTargetMethod::Random, 0, spellInfo->GetMaxRange(false), playerOnly);
DefaultTargetSelector targetSelector(me, spellInfo->GetMaxRange(false), false, true, 0);
target = SelectTarget(SelectTargetMethod::Random, 0, [&](Unit* target) {
if (!target)
return false;
if (target->IsPlayer())
{
if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER))
return false;
}
else
{
if (spellInfo->HasAttribute(SPELL_ATTR3_ONLY_ON_PLAYER))
return false;
if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER_CONTROLLED_NPC) && target->IsControlledByPlayer())
return false;
}
return targetSelector(target);
});
}
break;
}
@@ -206,12 +224,30 @@ SpellCastResult UnitAI::DoCast(uint32 spellId)
{
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId))
{
bool playerOnly = spellInfo->HasAttribute(SPELL_ATTR3_ONLY_ON_PLAYER);
float range = spellInfo->GetMaxRange(false);
DefaultTargetSelector targetSelector(me, range, playerOnly, true, -(int32)spellId);
if (!(spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_VICTIM)
&& targetSelector(me->GetVictim()))
DefaultTargetSelector defaultTargetSelector(me, range, false, true, -(int32)spellId);
auto targetSelector = [&](Unit* target) {
if (!target)
return false;
if (target->IsPlayer())
{
if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER))
return false;
}
else
{
if (spellInfo->HasAttribute(SPELL_ATTR3_ONLY_ON_PLAYER))
return false;
if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER_CONTROLLED_NPC) && target->IsControlledByPlayer())
return false;
}
return defaultTargetSelector(target);
};
if (!(spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_VICTIM) && targetSelector(me->GetVictim()))
target = me->GetVictim();
else
target = SelectTarget(SelectTargetMethod::Random, 0, targetSelector);
+3 -2
View File
@@ -165,9 +165,10 @@ struct PowerUsersSelector : public Acore::unary_function<Unit*, bool>
}
};
struct FarthestTargetSelector : public Acore::unary_function<Unit*, bool>
// Simple selector based on range and Los
struct RangeSelector : public Acore::unary_function<Unit*, bool>
{
FarthestTargetSelector(Unit const* unit, float maxDist, bool playerOnly, bool inLos, float minDist = 0.f) : _me(unit), _minDist(minDist), _maxDist(maxDist), _playerOnly(playerOnly), _inLos(inLos) {}
RangeSelector(Unit const* unit, float maxDist, bool playerOnly, bool inLos, float minDist = 0.f) : _me(unit), _minDist(minDist), _maxDist(maxDist), _playerOnly(playerOnly), _inLos(inLos) {}
bool operator()(Unit const* target) const
{
+31 -25
View File
@@ -2506,12 +2506,8 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
}
case SMART_ACTION_START_CLOSEST_WAYPOINT:
{
std::vector<uint32> waypoints;
std::copy_if(e.action.closestWaypointFromList.wps.begin(), e.action.closestWaypointFromList.wps.end(),
std::back_inserter(waypoints), [](uint32 wp) { return wp != 0; });
float distanceToClosest = std::numeric_limits<float>::max();
WayPoint* closestWp = nullptr;
uint32 closestWpId = 0;
for (WorldObject* target : targets)
{
@@ -2519,29 +2515,34 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
{
if (IsSmart(creature))
{
for (uint32 wp : waypoints)
for (uint32 wp = e.action.startClosestWaypoint.pathId1; wp <= e.action.startClosestWaypoint.pathId2; ++wp)
{
WPPath* path = sSmartWaypointMgr->GetPath(wp);
if (!path || path->empty())
continue;
auto itrWp = path->find(0);
auto itrWp = path->find(1);
if (itrWp != path->end())
{
if (WayPoint* wp = itrWp->second)
if (WayPoint* wpData = itrWp->second)
{
float distToThisPath = creature->GetDistance(wp->x, wp->y, wp->z);
float distToThisPath = creature->GetExactDistSq(wpData->x, wpData->y, wpData->z);
if (distToThisPath < distanceToClosest)
{
distanceToClosest = distToThisPath;
closestWp = wp;
closestWpId = wp;
}
}
}
}
if (closestWp)
CAST_AI(SmartAI, creature->AI())->StartPath(false, closestWp->id, true);
if (closestWpId)
{
bool repeat = e.action.startClosestWaypoint.repeat;
bool run = e.action.startClosestWaypoint.run;
CAST_AI(SmartAI, creature->AI())->StartPath(repeat, closestWpId, run);
}
}
}
}
@@ -3303,10 +3304,16 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
if (e.link && e.link != e.event_id)
{
auto linked = FindLinkedEvent(e.link);
if (linked.has_value() && linked.value().get().GetEventType() == SMART_EVENT_LINK)
executionStack.emplace_back(SmartScriptFrame{ linked.value(), unit, var0, var1, bvar, spell, gob });
if (linked.has_value())
{
auto& linkedEvent = linked.value().get();
if (linkedEvent.GetEventType() == SMART_EVENT_LINK)
executionStack.emplace_back(SmartScriptFrame{ linkedEvent, unit, var0, var1, bvar, spell, gob });
else
LOG_ERROR("sql.sql", "SmartScript::ProcessAction: Entry {} SourceType {}, Event {}, Link Event {} found but has wrong type (should be 61, is {}).", e.entryOrGuid, e.GetScriptType(), e.event_id, e.link, linkedEvent.GetEventType());
}
else
LOG_ERROR("sql.sql", "SmartScript::ProcessAction: Entry {} SourceType {}, Event {}, Link Event {} not found or invalid, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.link);
LOG_ERROR("sql.sql", "SmartScript::ProcessAction: Entry {} SourceType {}, Event {}, Link Event {} not found, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.link);
}
}
@@ -3509,7 +3516,7 @@ void SmartScript::GetTargets(ObjectVector& targets, SmartScriptHolder const& e,
case SMART_TARGET_FARTHEST:
if (me)
{
if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::MinDistance, 0, FarthestTargetSelector(me, e.target.farthest.maxDist, e.target.farthest.playerOnly, e.target.farthest.isInLos, e.target.farthest.minDist)))
if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::MinDistance, 0, RangeSelector(me, e.target.farthest.maxDist, e.target.farthest.playerOnly, e.target.farthest.isInLos, e.target.farthest.minDist)))
targets.push_back(u);
}
break;
@@ -3701,10 +3708,12 @@ void SmartScript::GetTargets(ObjectVector& targets, SmartScriptHolder const& e,
if (!units.empty() && baseObject)
for (WorldObject* unit : units)
if (IsPlayer(unit) && baseObject->IsInRange(unit, float(e.target.playerRange.minDist), float(e.target.playerRange.maxDist)))
if (IsPlayer(unit) && !unit->ToPlayer()->IsGameMaster() && baseObject->IsInRange(unit, float(e.target.playerRange.minDist), float(e.target.playerRange.maxDist)))
targets.push_back(unit);
if (e.target.playerRange.maxCount)
Acore::Containers::RandomResize(targets, e.target.playerRange.maxCount);
break;
}
case SMART_TARGET_PLAYER_DISTANCE:
@@ -4658,12 +4667,11 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui
if (!targets.empty())
{
for (WorldObject* target : targets)
{
if (IsPlayer(target))
if (IsPlayer(target) && !target->ToPlayer()->IsGameMaster())
playerCount++;
}
if (playerCount >= e.event.nearPlayer.minCount)
ProcessAction(e, unit);
if (playerCount >= e.event.nearPlayer.minCount)
ProcessAction(e, unit);
}
RecalcTimer(e, e.event.nearPlayer.repeatMin, e.event.nearPlayer.repeatMax);
break;
@@ -4677,10 +4685,8 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui
if (!targets.empty())
{
for (WorldObject* target : targets)
{
if (IsPlayer(target))
if (IsPlayer(target) && !target->ToPlayer()->IsGameMaster())
playerCount++;
}
if (playerCount < e.event.nearPlayerNegation.maxCount)
ProcessAction(e, unit);
@@ -806,7 +806,7 @@ bool SmartAIMgr::CheckUnusedActionParams(SmartScriptHolder const& e)
case SMART_ACTION_REMOVE_POWER: return sizeof(SmartAction::power);
case SMART_ACTION_GAME_EVENT_STOP: return sizeof(SmartAction::gameEventStop);
case SMART_ACTION_GAME_EVENT_START: return sizeof(SmartAction::gameEventStart);
case SMART_ACTION_START_CLOSEST_WAYPOINT: return sizeof(SmartAction::closestWaypointFromList);
case SMART_ACTION_START_CLOSEST_WAYPOINT: return sizeof(SmartAction::startClosestWaypoint);
case SMART_ACTION_RISE_UP: return sizeof(SmartAction::moveRandom);
case SMART_ACTION_RANDOM_SOUND: return sizeof(SmartAction::randomSound);
case SMART_ACTION_SET_CORPSE_DELAY: return sizeof(SmartAction::corpseDelay);
@@ -1536,15 +1536,22 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e)
break;
}
case SMART_ACTION_START_CLOSEST_WAYPOINT:
{
if (e.action.startClosestWaypoint.pathId1 == 0 || e.action.startClosestWaypoint.pathId2 == 0 || e.action.startClosestWaypoint.pathId2 < e.action.startClosestWaypoint.pathId1)
{
if (std::all_of(e.action.closestWaypointFromList.wps.begin(), e.action.closestWaypointFromList.wps.end(), [](uint32 wp) { return wp == 0; }))
{
LOG_ERROR("sql.sql", "SmartAIMgr: Entry {} SourceType {} Event {} Action {} does not have any non-zero waypoint id",
e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType());
return false;
}
break;
LOG_ERROR("sql.sql", "SmartAIMgr: Entry {} SourceType {} Event {} Action {} has invalid pathId1 or pathId2, it must be greater than 0 and pathId1 > pathId2",
e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType());
return false;
}
if (e.action.startClosestWaypoint.repeat > 1 || e.action.startClosestWaypoint.run > 1)
{
LOG_ERROR("sql.sql", "SmartAIMgr: Entry {} SourceType {} Event {} Action {} has invalid run ({}) or repeat ({}) parameter, must be 0 or 1.",
e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(),
e.action.startClosestWaypoint.repeat, e.action.startClosestWaypoint.run);
return false;
}
break;
}
case SMART_ACTION_INVOKER_CAST:
if (e.GetScriptType() != SMART_SCRIPT_TYPE_TIMED_ACTIONLIST && e.GetEventType() != SMART_EVENT_LINK && !EventHasInvoker(e.event.type))
{
@@ -1284,8 +1284,11 @@ struct SmartAction
struct
{
std::array<uint32, SMART_ACTION_PARAM_COUNT> wps;
} closestWaypointFromList;
uint32 pathId1;
uint32 pathId2;
uint32 repeat;
uint32 run;
} startClosestWaypoint;
struct
{
+7 -1
View File
@@ -27,7 +27,7 @@
namespace AccountMgr
{
AccountOpResult CreateAccount(std::string username, std::string password)
AccountOpResult CreateAccount(std::string username, std::string password, std::string email /*= ""*/)
{
if (utf8length(username) > MAX_ACCOUNT_STR)
return AOR_NAME_TOO_LONG; // username's too long
@@ -35,8 +35,12 @@ namespace AccountMgr
if (utf8length(password) > MAX_PASS_STR)
return AOR_PASS_TOO_LONG; // password's too long
if (utf8length(email) > MAX_EMAIL_STR)
return AOR_EMAIL_TOO_LONG; // email is too long
Utf8ToUpperOnlyLatin(username);
Utf8ToUpperOnlyLatin(password);
Utf8ToUpperOnlyLatin(email);
if (GetId(username))
return AOR_NAME_ALREADY_EXIST; // username does already exist
@@ -48,6 +52,8 @@ namespace AccountMgr
stmt->SetData(1, salt);
stmt->SetData(2, verifier);
stmt->SetData(3, uint8(sWorld->getIntConfig(CONFIG_EXPANSION)));
stmt->SetData(4, email);
stmt->SetData(5, email);
LoginDatabase.Execute(stmt);
+1 -1
View File
@@ -38,7 +38,7 @@ enum AccountOpResult
namespace AccountMgr
{
AccountOpResult CreateAccount(std::string username, std::string password);
AccountOpResult CreateAccount(std::string username, std::string password, std::string email = "");
AccountOpResult DeleteAccount(uint32 accountId);
AccountOpResult ChangeUsername(uint32 accountId, std::string newUsername, std::string newPassword);
AccountOpResult ChangePassword(uint32 accountId, std::string newPassword);
@@ -788,9 +788,7 @@ void Creature::Update(uint32 diff)
m_moveBackwardsMovementTime = urand(MOVE_BACKWARDS_CHECK_INTERVAL, MOVE_BACKWARDS_CHECK_INTERVAL * 3);
}
else
{
m_moveBackwardsMovementTime -= diff;
}
// Circling the target
if (diff >= m_moveCircleMovementTime)
@@ -799,9 +797,17 @@ void Creature::Update(uint32 diff)
m_moveCircleMovementTime = urand(MOVE_CIRCLE_CHECK_INTERVAL, MOVE_CIRCLE_CHECK_INTERVAL * 2);
}
else
{
m_moveCircleMovementTime -= diff;
// Periodically check if able to move, if not, extend leash timer
if (diff >= m_extendLeashTime)
{
if (!CanFreeMove())
UpdateLeashExtensionTime();
m_extendLeashTime = EXTEND_LEASH_CHECK_INTERVAL;
}
else
m_extendLeashTime -= diff;
}
// Call for assistance if not disabled
@@ -396,8 +396,10 @@ public:
bool IsFreeToMove();
static constexpr uint32 MOVE_CIRCLE_CHECK_INTERVAL = 3000;
static constexpr uint32 MOVE_BACKWARDS_CHECK_INTERVAL = 2000;
static constexpr uint32 EXTEND_LEASH_CHECK_INTERVAL = 3000;
uint32 m_moveCircleMovementTime = MOVE_CIRCLE_CHECK_INTERVAL;
uint32 m_moveBackwardsMovementTime = MOVE_BACKWARDS_CHECK_INTERVAL;
uint32 m_extendLeashTime = EXTEND_LEASH_CHECK_INTERVAL;
[[nodiscard]] bool HasSwimmingFlagOutOfCombat() const
{
+7 -2
View File
@@ -13642,7 +13642,11 @@ void Player::_LoadSkills(PreparedQueryResult result)
SkillRaceClassInfoEntry const* rcEntry = GetSkillRaceClassInfo(skill, getRace(), getClass());
if (!rcEntry)
{
LOG_ERROR("entities.player", "Character {} has skill {} that does not exist.", GetGUID().ToString(), skill);
LOG_ERROR("entities.player", "Player {} (GUID: {}), has skill ({}) that is invalid for the race/class combination (Race: {}, Class: {}). Will be deleted.",
GetName(), GetGUID().GetCounter(), skill, getRace(), getClass());
// Mark skill for deletion in the database
mSkillStatus.insert(SkillStatusMap::value_type(skill, SkillStatusData(0, SKILL_DELETED)));
continue;
}
@@ -13663,7 +13667,8 @@ void Player::_LoadSkills(PreparedQueryResult result)
if (value == 0)
{
LOG_ERROR("entities.player", "Character {} has skill {} with value 0. Will be deleted.", GetGUID().ToString(), skill);
LOG_ERROR("entities.player", "Player {} (GUID: {}), has skill ({}) with value 0. Will be deleted.",
GetName(), GetGUID().GetCounter(), skill);
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_SKILL);
@@ -65,6 +65,9 @@ public:
[[nodiscard]] uint32 GetCurrentTaxiPath() const;
uint32 NextTaxiDestination()
{
if (m_TaxiDestinations.empty())
return 0;
m_TaxiDestinations.pop_front();
return GetTaxiDestination();
}
+36
View File
@@ -20114,6 +20114,24 @@ private:
AuraType _auraType;
};
class ResetToHomeOrientation : public BasicEvent
{
public:
ResetToHomeOrientation(Creature& self) : _self(self) { }
bool Execute(uint64 /*eventTime*/, uint32 /*updateTime*/) override
{
if (_self.IsInWorld() && _self.FindMap() && _self.IsAlive() && !_self.IsInCombat())
{
_self.SetFacingTo(_self.GetHomePosition().GetOrientation());
}
return true;
}
private:
Creature& _self;
};
void Unit::CastDelayedSpellWithPeriodicAmount(Unit* caster, uint32 spellId, AuraType auraType, int32 addAmount, uint8 effectIndex)
{
AuraEffect* aurEff = nullptr;
@@ -20360,6 +20378,24 @@ void Unit::SetFacingToObject(WorldObject* object)
init.Launch();
}
void Unit::SetTimedFacingToObject(WorldObject* object, uint32 time)
{
// never face when already moving
if (!IsStopped() || !time)
return;
/// @todo figure out under what conditions creature will move towards object instead of facing it where it currently is.
Movement::MoveSplineInit init(this);
init.MoveTo(GetPositionX(), GetPositionY(), GetPositionZ());
init.SetFacing(GetAngle(object)); // when on transport, GetAngle will still return global coordinates (and angle) that needs transforming
init.Launch();
if (Creature* c = ToCreature())
c->m_Events.AddEvent(new ResetToHomeOrientation(*c), c->m_Events.CalculateTime(time));
else
LOG_ERROR("entities.unit", "Unit::SetTimedFacingToObject called on non-creature unit {}. This should never happen.", GetEntry());
}
bool Unit::SetWalk(bool enable)
{
if (enable == IsWalking())
+1
View File
@@ -1894,6 +1894,7 @@ public:
void SetInFront(WorldObject const* target);
void SetFacingTo(float ori);
void SetFacingToObject(WorldObject* object);
void SetTimedFacingToObject(WorldObject* object, uint32 time); // Reset to home orientation after given time
bool isInAccessiblePlaceFor(Creature const* c) const;
bool isInFrontInMap(Unit const* target, float distance, float arc = M_PI) const;
+3 -3
View File
@@ -512,7 +512,7 @@ void GameEventMgr::LoadEventCreatureData()
Field* fields = result->Fetch();
ObjectGuid::LowType guid = fields[0].Get<uint32>();
int16 eventId = fields[1].Get<int8>();
int16 eventId = fields[1].Get<int16>();
CreatureData const* data = sObjectMgr->GetCreatureData(guid);
if (!data)
@@ -562,7 +562,7 @@ void GameEventMgr::LoadEventGameObjectData()
Field* fields = result->Fetch();
ObjectGuid::LowType guid = fields[0].Get<uint32>();
int16 eventId = fields[1].Get<int8>();
int16 eventId = fields[1].Get<int16>();
int32 internal_event_id = _gameEvent.size() + eventId - 1;
@@ -1020,7 +1020,7 @@ void GameEventMgr::LoadEventPoolData()
Field* fields = result->Fetch();
uint32 entry = fields[0].Get<uint32>();
int16 eventId = fields[1].Get<int8>();
int16 eventId = fields[1].Get<int16>();
int32 internal_event_id = _gameEvent.size() + eventId - 1;
+2 -2
View File
@@ -2317,7 +2317,7 @@ void ObjectMgr::LoadCreatures()
data.movementType = fields[15].Get<uint8>();
data.spawnMask = fields[16].Get<uint8>();
data.phaseMask = fields[17].Get<uint32>();
int16 gameEvent = fields[18].Get<int8>();
int16 gameEvent = fields[18].Get<int16>();
uint32 PoolId = fields[19].Get<uint32>();
data.npcflag = fields[20].Get<uint32>();
data.unit_flags = fields[21].Get<uint32>();
@@ -2709,7 +2709,7 @@ void ObjectMgr::LoadGameobjects()
LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: {} Entry: {}) that has wrong spawn mask {} including not supported difficulty modes for map (Id: {}), skip", guid, data.id, data.spawnMask, data.mapid);
data.phaseMask = fields[15].Get<uint32>();
int16 gameEvent = fields[16].Get<int8>();
int16 gameEvent = fields[16].Get<int16>();
uint32 PoolId = fields[17].Get<uint32>();
if (data.rotation.x < -1.0f || data.rotation.x > 1.0f)
+2 -2
View File
@@ -172,8 +172,8 @@ namespace Acore
template<class RET_TYPE, int CENTER_VAL>
inline RET_TYPE Compute(float x, float y, float size)
{
int gx = (int)(CENTER_VAL - x / size);
int gy = (int)(CENTER_VAL - y / size);
int gx = std::max<int>(0, (CENTER_VAL - x / size));
int gy = std::max<int>(0, (CENTER_VAL - y / size));
return RET_TYPE(gx, gy);
}
+2 -1
View File
@@ -331,7 +331,8 @@ enum AcoreStrings
LANG_COMMAND_WHISPERON = 285,
LANG_COMMAND_WHISPEROFF = 286,
LANG_COMMAND_CREATGUIDNOTFOUND = 287,
// TICKET STRINGS NEED REWRITE // 288-296 FREE
LANG_COMMAND_GONOTENOUGHSPAWNS = 288,
// TICKET STRINGS NEED REWRITE // 289-296 FREE
// END
LANG_COMMAND_WANDER_DISTANCE = 297,
@@ -153,18 +153,20 @@ bool ChaseMovementGenerator<T>::DoUpdate(T* owner, uint32 time_diff)
MovementInform(owner);
}
if (owner->movespline->Finalized())
{ // Mobs should chase you infinitely if you stop and wait every few seconds.
i_leashExtensionTimer.Update(time_diff);
if (i_leashExtensionTimer.Passed())
{
i_leashExtensionTimer.Reset(5000);
if (cOwner)
if (cOwner)
{
if (owner->movespline->Finalized() && cOwner->IsWithinMeleeRange(target))
{ // Mobs should chase you infinitely if you stop and wait every few seconds.
i_leashExtensionTimer.Update(time_diff);
if (i_leashExtensionTimer.Passed())
{
i_leashExtensionTimer.Reset(cOwner->GetAttackTime(BASE_ATTACK));
cOwner->UpdateLeashExtensionTime();
}
}
else if (i_recalculateTravel)
i_leashExtensionTimer.Reset(cOwner->GetAttackTime(BASE_ATTACK));
}
else if (i_recalculateTravel)
i_leashExtensionTimer.Reset(5000);
// if the target moved, we have to consider whether to adjust
if (!_lastTargetPosition || target->GetPosition() != _lastTargetPosition.value() || mutualChase != _mutualChase || !owner->IsWithinLOSInMap(target))
@@ -298,6 +300,7 @@ void ChaseMovementGenerator<Creature>::DoInitialize(Creature* owner)
i_path = nullptr;
_lastTargetPosition.reset();
i_recheckDistance.Reset(0);
i_leashExtensionTimer.Reset(owner->GetAttackTime(BASE_ATTACK));
owner->SetWalk(false);
owner->AddUnitState(UNIT_STATE_CHASE);
}
+1 -1
View File
@@ -24,7 +24,6 @@
class AchievementGlobalMgr;
class AchievementMgr;
class ArenaTeam;
class AuctionEntry;
class AuctionHouseMgr;
class AuctionHouseObject;
class Aura;
@@ -98,6 +97,7 @@ enum WeatherState : uint32;
struct AchievementCriteriaEntry;
struct AchievementEntry;
struct AreaTrigger;
struct AuctionEntry;
struct CompletedAchievementData;
struct Condition;
struct ConditionSourceInfo;
@@ -110,6 +110,7 @@ public:
uint8 GetCasterLevel() const { return m_casterLevel; }
bool CanApplyResilience() const { return m_applyResilience; }
float GetPctMods() const { return m_pctMods; }
void SetPctMods(float pctMods) { m_pctMods = pctMods; }
// xinef: stacking
uint32 GetAuraGroup() const { return m_auraGroup; }
+2
View File
@@ -2147,6 +2147,8 @@ uint32 Spell::GetSearcherTypeMask(SpellTargetObjectTypes objType, ConditionList*
retMask &= GRID_MAP_TYPE_MASK_CORPSE | GRID_MAP_TYPE_MASK_PLAYER;
if (m_spellInfo->HasAttribute(SPELL_ATTR3_ONLY_ON_GHOSTS))
retMask &= GRID_MAP_TYPE_MASK_PLAYER;
if (m_spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER))
retMask &= ~GRID_MAP_TYPE_MASK_PLAYER;
if (condList)
retMask &= sConditionMgr->GetSearcherTypeMaskForConditionList(*condList);
+13 -2
View File
@@ -1863,8 +1863,19 @@ SpellCastResult SpellInfo::CheckTarget(Unit const* caster, WorldObject const* ta
else return SPELL_CAST_OK;
// corpseOwner and unit specific target checks
if (AttributesEx3 & SPELL_ATTR3_ONLY_ON_PLAYER && !unitTarget->ToPlayer())
return SPELL_FAILED_TARGET_NOT_PLAYER;
if (unitTarget->IsPlayer())
{
if (HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER))
return SPELL_FAILED_TARGET_IS_PLAYER;
}
else
{
if (HasAttribute(SPELL_ATTR3_ONLY_ON_PLAYER))
return SPELL_FAILED_TARGET_NOT_PLAYER;
if (HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER_CONTROLLED_NPC) && unitTarget->IsControlledByPlayer())
return SPELL_FAILED_TARGET_IS_PLAYER_CONTROLLED;
}
if (!IsAllowingDeadTarget() && !unitTarget->IsAlive())
return SPELL_FAILED_TARGETS_DEAD;
+6 -1
View File
@@ -271,10 +271,15 @@ public:
///- %Parse the command line arguments
char* accountName = strtok((char*)args, " ");
char* password = strtok(nullptr, " ");
char* email = strtok(nullptr, " ");
if (!accountName || !password)
return false;
AccountOpResult result = AccountMgr::CreateAccount(std::string(accountName), std::string(password));
// if email is not specified, use empty string
std::string emailStr = email ? email : "";
AccountOpResult result = AccountMgr::CreateAccount(std::string(accountName), std::string(password), emailStr);
switch (result)
{
case AOR_OK:
+76 -6
View File
@@ -86,16 +86,35 @@ public:
return true;
}
static bool HandleGoCreatureCIdCommand(ChatHandler* handler, Variant<Hyperlink<creature_entry>, uint32> cId)
static bool HandleGoCreatureCIdCommand(ChatHandler* handler, Variant<Hyperlink<creature_entry>, uint32> cId, Optional<uint32> _pos)
{
CreatureData const* spawnpoint = GetCreatureData(handler, *cId);
uint32 pos = 1;
if (_pos)
{
pos = *_pos;
if (pos < 1)
{
handler->SendErrorMessage(LANG_COMMAND_FACTION_INVPARAM, pos);
return false;
}
}
if (!spawnpoint)
std::vector<CreatureData const*> spawnpoints = GetCreatureDataList(*cId);
if (spawnpoints.empty())
{
handler->SendErrorMessage(LANG_COMMAND_GOCREATNOTFOUND);
return false;
}
if (spawnpoints.size() < pos)
{
handler->SendErrorMessage(LANG_COMMAND_GONOTENOUGHSPAWNS, pos, spawnpoints.size());
return false;
}
CreatureData const* spawnpoint = spawnpoints[--pos];
return DoTeleport(handler, { spawnpoint->posX, spawnpoint->posY, spawnpoint->posZ }, spawnpoint->mapid);
}
@@ -153,16 +172,35 @@ public:
return DoTeleport(handler, { spawnpoint->posX, spawnpoint->posY, spawnpoint->posZ }, spawnpoint->mapid);
}
static bool HandleGoGameObjectGOIdCommand(ChatHandler* handler, uint32 goId)
static bool HandleGoGameObjectGOIdCommand(ChatHandler* handler, uint32 goId, Optional<uint32> _pos)
{
GameObjectData const* spawnpoint = GetGameObjectData(handler, goId);
uint32 pos = 1;
if (_pos)
{
pos = *_pos;
if (pos < 1)
{
handler->SendErrorMessage(LANG_COMMAND_FACTION_INVPARAM, pos);
return false;
}
}
if (!spawnpoint)
std::vector<GameObjectData const*> spawnpoints = GetGameObjectDataList(goId);
if (spawnpoints.empty())
{
handler->SendErrorMessage(LANG_COMMAND_GOOBJNOTFOUND);
return false;
}
if (spawnpoints.size() < pos)
{
handler->SendErrorMessage(LANG_COMMAND_GONOTENOUGHSPAWNS, pos, spawnpoints.size());
return false;
}
GameObjectData const* spawnpoint = spawnpoints[--pos];
return DoTeleport(handler, { spawnpoint->posX, spawnpoint->posY, spawnpoint->posZ }, spawnpoint->mapid);
}
@@ -509,6 +547,22 @@ public:
return spawnpoint;
}
static std::vector<CreatureData const*> GetCreatureDataList(uint32 entry)
{
std::vector<CreatureData const*> spawnpoints;
for (auto const& pair : sObjectMgr->GetAllCreatureData())
{
if (pair.second.id1 != entry)
{
continue;
}
spawnpoints.emplace_back(&pair.second);
}
return spawnpoints;
}
static GameObjectData const* GetGameObjectData(ChatHandler* handler, uint32 entry)
{
GameObjectData const* spawnpoint = nullptr;
@@ -532,6 +586,22 @@ public:
return spawnpoint;
}
static std::vector<GameObjectData const*> GetGameObjectDataList(uint32 entry)
{
std::vector<GameObjectData const*> spawnpoints;
for (auto const& pair : sObjectMgr->GetAllGOData())
{
if (pair.second.id != entry)
{
continue;
}
spawnpoints.emplace_back(&pair.second);
}
return spawnpoints;
}
};
void AddSC_go_commandscript()
@@ -74,7 +74,7 @@ struct boss_servant_quarters : public BossAI
context.Repeat(12s, 18s);
}).Schedule(10s, [this](TaskContext context)
{
if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, FarthestTargetSelector(me, 40.0f, false, true)))
if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, RangeSelector(me, 40.0f, false, true)))
{
me->CastSpell(target, SPELL_DIVE);
}
@@ -18,6 +18,7 @@
#include "CombatAI.h"
#include "CreatureScript.h"
#include "CreatureTextMgr.h"
#include "ScriptedGossip.h"
#include "Player.h"
#include "ScriptedCreature.h"
#include "ScriptedEscortAI.h"
@@ -185,16 +186,23 @@ public:
enum Koltira
{
SAY_BREAKOUT1 = 0,
SAY_BREAKOUT2 = 1,
SAY_BREAKOUT3 = 2,
SAY_BREAKOUT4 = 3,
SAY_BREAKOUT5 = 4,
SAY_BREAKOUT6 = 5,
SAY_BREAKOUT7 = 6,
SAY_BREAKOUT8 = 7,
SAY_BREAKOUT9 = 8,
SAY_BREAKOUT10 = 9,
SAY_BREAKOUT0 = 0,
SAY_BREAKOUT1 = 1,
SAY_BREAKOUT2 = 2,
SAY_BREAKOUT3 = 3,
SAY_BREAKOUT4 = 4,
SAY_BREAKOUT5 = 5,
SAY_BREAKOUT6 = 6,
SAY_BREAKOUT7 = 7,
SAY_BREAKOUT8 = 8,
SAY_BREAKOUT9 = 9,
SAY_BREAKOUT10 = 10,
EMOTE_KOLTIRA_COLLAPSES = 11,
SAY_VALROTH_WAVE3 = 0,
SAY_VALROTH_AGGRO = 1,
SAY_VALROTH_WAVE1 = 4,
SAY_VALROTH_WAVE2 = 5,
SPELL_KOLTIRA_TRANSFORM = 52899,
SPELL_ANTI_MAGIC_ZONE = 52894,
@@ -206,7 +214,14 @@ enum Koltira
//not sure about this id
//NPC_DEATH_KNIGHT_MOUNT = 29201,
MODEL_DEATH_KNIGHT_MOUNT = 25278
MODEL_DEATH_KNIGHT_MOUNT = 25278,
POINT_STAND_UP = 0,
POINT_BOX = 1,
POINT_ANTI_MAGIC_ZONE = 2,
POINT_MOUNT = 0,
POINT_DESPAWN = 1
};
class npc_koltira_deathweaver : public CreatureScript
@@ -214,205 +229,203 @@ class npc_koltira_deathweaver : public CreatureScript
public:
npc_koltira_deathweaver() : CreatureScript("npc_koltira_deathweaver") { }
bool OnQuestAccept(Player* player, Creature* creature, const Quest* quest) override
{
if (quest->GetQuestId() == QUEST_BREAKOUT)
{
creature->SetStandState(UNIT_STAND_STATE_STAND);
creature->setActive(true);
if (npc_escortAI* pEscortAI = CAST_AI(npc_koltira_deathweaver::npc_koltira_deathweaverAI, creature->AI()))
pEscortAI->Start(false, false, player->GetGUID());
}
return true;
}
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_koltira_deathweaverAI(creature);
}
struct npc_koltira_deathweaverAI : public npc_escortAI
struct npc_koltira_deathweaverAI : public ScriptedAI
{
npc_koltira_deathweaverAI(Creature* creature) : npc_escortAI(creature), summons(me)
{
me->SetReactState(REACT_DEFENSIVE);
}
uint32 m_uiWave;
uint32 m_uiWave_Timer;
ObjectGuid m_uiValrothGUID;
SummonList summons;
npc_koltira_deathweaverAI(Creature* creature) : ScriptedAI(creature) { }
void Reset() override
{
if (!HasEscortState(STATE_ESCORT_ESCORTING))
{
m_uiWave = 0;
m_uiWave_Timer = 3000;
m_uiValrothGUID.Clear();
me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
me->LoadEquipment(0, true);
me->RemoveAllAuras();
summons.DespawnAll();
}
scheduler.CancelAll();
me->m_Events.KillAllEvents(false);
me->SetUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC);
me->setActive(false);
}
void EnterEvadeMode(EvadeReason /*why*/) override
void StartEvent()
{
me->GetThreatMgr().ClearAllThreat();
me->CombatStop(false);
me->SetLootRecipient(nullptr);
if (HasEscortState(STATE_ESCORT_ESCORTING))
{
AddEscortState(STATE_ESCORT_RETURNING);
ReturnToLastPoint();
LOG_DEBUG("scripts.ai", "EscortAI has left combat and is now returning to last point");
}
else
{
me->GetMotionMaster()->MoveTargetedHome();
me->SetImmuneToNPC(true);
Reset();
}
}
void AttackStart(Unit* who) override
{
if (HasEscortState(STATE_ESCORT_PAUSED))
if (!me->HasNpcFlag(UNIT_NPC_FLAG_GOSSIP)) // Already in progress
return;
npc_escortAI::AttackStart(who);
me->SetStandState(UNIT_STAND_STATE_SIT);
me->RemoveNpcFlag(UNIT_NPC_FLAG_GOSSIP);
me->setActive(true);
Talk(SAY_BREAKOUT0);
me->m_Events.AddEventAtOffset([&] {
me->GetMotionMaster()->MovePath(me->GetEntry() * 10, false);
}, 5s);
}
void WaypointReached(uint32 waypointId) override
void sQuestAccept(Player* /*player*/, Quest const* quest) override
{
switch (waypointId)
if (quest->GetQuestId() == QUEST_BREAKOUT)
StartEvent();
}
void sGossipSelect(Player* player, uint32 /*menuId*/, uint32 /*gossipListId*/) override
{
if (player->GetQuestStatus(QUEST_BREAKOUT) == QUEST_STATUS_INCOMPLETE)
{
case 0:
Talk(SAY_BREAKOUT1);
me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
break;
case 1:
me->SetStandState(UNIT_STAND_STATE_KNEEL);
break;
case 2:
me->SetStandState(UNIT_STAND_STATE_STAND);
//me->UpdateEntry(NPC_KOLTIRA_ALT); //unclear if we must update or not
DoCast(me, SPELL_KOLTIRA_TRANSFORM);
me->LoadEquipment();
break;
case 3:
SetEscortPaused(true);
me->SetStandState(UNIT_STAND_STATE_KNEEL);
Talk(SAY_BREAKOUT2);
DoCast(me, SPELL_ANTI_MAGIC_ZONE); // cast again that makes bubble up
break;
case 4:
me->ApplySpellImmune(0, IMMUNITY_DAMAGE, SPELL_SCHOOL_MASK_ALL, false);
SetRun(true);
break;
case 9:
me->Mount(MODEL_DEATH_KNIGHT_MOUNT);
break;
case 10:
me->Dismount();
break;
CloseGossipMenuFor(player);
StartEvent();
}
}
void JustSummoned(Creature* summoned) override
void MovementInform(uint32 type, uint32 id) override
{
if (Player* player = GetPlayerForEscort())
summoned->AI()->AttackStart(player);
if (type != WAYPOINT_MOTION_TYPE)
return;
if (summoned->GetEntry() == NPC_HIGH_INQUISITOR_VALROTH)
m_uiValrothGUID = summoned->GetGUID();
summoned->AddThreat(me, 0.0f);
summoned->SetImmuneToPC(false);
summons.Summon(summoned);
}
void SummonAcolyte(uint32 uiAmount)
{
for (uint32 i = 0; i < uiAmount; ++i)
me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1642.329f, -6045.818f, 127.583f, 0.0f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 5000);
}
void UpdateAI(uint32 uiDiff) override
{
npc_escortAI::UpdateAI(uiDiff);
if (HasEscortState(STATE_ESCORT_PAUSED))
if (!me->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC))
{
if (m_uiWave_Timer <= uiDiff)
if (id == POINT_MOUNT)
me->Mount(MODEL_DEATH_KNIGHT_MOUNT);
else if (id == POINT_DESPAWN)
{
switch (m_uiWave)
me->Dismount();
me->DespawnOrUnsummon();
}
return;
}
switch (id)
{
case POINT_STAND_UP:
Talk(SAY_BREAKOUT1);
break;
case POINT_BOX:
me->SetStandState(UNIT_STAND_STATE_KNEEL);
scheduler.Schedule(5s, [this](TaskContext context)
{
switch (context.GetRepeatCounter())
{
case 0:
Talk(SAY_BREAKOUT3);
SummonAcolyte(3);
m_uiWave_Timer = 20000;
// Shouldn't actually be spawned at this point, but no way to send his yells otherwise?
if (Creature* valroth = me->SummonCreature(NPC_HIGH_INQUISITOR_VALROTH, 1640.8596f, -6030.834f, 134.82211f, 4.606426715850830078f, TEMPSUMMON_MANUAL_DESPAWN))
{
_valrothGUID = valroth->GetGUID();
valroth->AI()->Talk(SAY_VALROTH_WAVE1);
valroth->SetReactState(REACT_PASSIVE);
}
if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1640.6724f, -6032.0527f, 134.82213f, 4.654973506927490234f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000))
acolyte->GetMotionMaster()->MovePath(NPC_CRIMSON_ACOLYTE * 10, false);
if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1641.0055f, -6031.893f, 134.82211f, 0.401425719261169433f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000))
acolyte->GetMotionMaster()->MovePath((NPC_CRIMSON_ACOLYTE + 1) * 10, false);
if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1639.7053f, -6031.7373f, 134.82213f, 2.443460941314697265f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000))
acolyte->GetMotionMaster()->MovePath((NPC_CRIMSON_ACOLYTE + 2) * 10, false);
break;
case 1:
Talk(SAY_BREAKOUT4);
SummonAcolyte(3);
m_uiWave_Timer = 20000;
if (Creature* valroth = ObjectAccessor::GetCreature(*me, _valrothGUID))
valroth->AI()->Talk(SAY_VALROTH_WAVE2);
if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1640.7958f, -6030.307f, 134.82211f, 4.65355682373046875f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000))
acolyte->GetMotionMaster()->MovePath((NPC_CRIMSON_ACOLYTE + 3) * 10, false);
if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1641.7305f, -6030.751f, 134.82211f, 6.143558979034423828f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000))
acolyte->GetMotionMaster()->MovePath((NPC_CRIMSON_ACOLYTE + 4) * 10, false);
if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1639.4657f, -6030.404f, 134.82211f, 4.502949237823486328f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000))
acolyte->GetMotionMaster()->MovePath((NPC_CRIMSON_ACOLYTE + 5) * 10, false);
break;
case 2:
Talk(SAY_BREAKOUT5);
SummonAcolyte(4);
m_uiWave_Timer = 20000;
if (Creature* valroth = ObjectAccessor::GetCreature(*me, _valrothGUID))
valroth->AI()->Talk(SAY_VALROTH_WAVE3);
if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1641.3405f, -6031.436f, 134.82211f, 4.612849712371826171f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000))
acolyte->GetMotionMaster()->MovePath((NPC_CRIMSON_ACOLYTE + 6) * 10, false);
if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1642.0404f, -6030.3843f, 134.82211f, 1.378810048103332519f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000))
acolyte->GetMotionMaster()->MovePath((NPC_CRIMSON_ACOLYTE + 7) * 10, false);
if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1640.1162f, -6029.7817f, 134.82211f, 5.707226753234863281f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000))
acolyte->GetMotionMaster()->MovePath((NPC_CRIMSON_ACOLYTE + 8) * 10, false);
if (Creature* acolyte = me->SummonCreature(NPC_CRIMSON_ACOLYTE, 1640.9948f, -6029.8027f, 134.82211f, 1.605702877044677734f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 15000))
acolyte->GetMotionMaster()->MovePath((NPC_CRIMSON_ACOLYTE + 9) * 10, false);
break;
case 3:
Talk(SAY_BREAKOUT6);
me->SummonCreature(NPC_HIGH_INQUISITOR_VALROTH, 1642.329f, -6045.818f, 127.583f, 0.0f, TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT, 1000);
m_uiWave_Timer = 1000;
break;
case 4:
me->m_Events.AddEventAtOffset([this]
{
Creature* temp = ObjectAccessor::GetCreature(*me, m_uiValrothGUID);
Talk(EMOTE_KOLTIRA_COLLAPSES, me);
me->KillSelf();
if (!temp || !temp->IsAlive())
{
Talk(SAY_BREAKOUT8);
m_uiWave_Timer = 5000;
}
else
{
// xinef: despawn check
Player* player = GetPlayerForEscort();
if (!player || me->GetDistance(player) > 60.0f)
{
me->DespawnOrUnsummon();
return;
}
if (Creature* valroth = ObjectAccessor::GetCreature(*me, _valrothGUID))
valroth->DespawnOrUnsummon();
}, 2min);
m_uiWave_Timer = 2500;
return; //return, we don't want m_uiWave to increment now
}
break;
if (Creature* valroth = ObjectAccessor::GetCreature(*me, _valrothGUID))
{
valroth->AI()->Talk(SAY_VALROTH_AGGRO);
valroth->SetReactState(REACT_AGGRESSIVE);
valroth->GetMotionMaster()->MovePath(NPC_HIGH_INQUISITOR_VALROTH * 10, false);
}
case 5:
Talk(SAY_BREAKOUT9);
me->RemoveAurasDueToSpell(SPELL_ANTI_MAGIC_ZONE);
// i do not know why the armor will also be removed
m_uiWave_Timer = 2500;
return;
default:
break;
case 6:
Talk(SAY_BREAKOUT10);
SetEscortPaused(false);
break;
}
}
++m_uiWave;
}
else
m_uiWave_Timer -= uiDiff;
context.Repeat(20s);
});
scheduler.Schedule(3s, [this](TaskContext)
{
DoCastSelf(SPELL_KOLTIRA_TRANSFORM);
me->LoadEquipment();
});
break;
case POINT_ANTI_MAGIC_ZONE:
me->SetStandState(UNIT_STAND_STATE_KNEEL);
Talk(SAY_BREAKOUT2);
DoCastSelf(SPELL_ANTI_MAGIC_ZONE);
break;
}
}
void SummonedCreatureDies(Creature* summon, Unit*) override
{
if (summon->GetEntry() == NPC_HIGH_INQUISITOR_VALROTH)
{
me->m_Events.KillAllEvents(false);
me->RemoveAurasDueToSpell(SPELL_ANTI_MAGIC_ZONE);
me->SetStandState(UNIT_STAND_STATE_STAND);
Talk(SAY_BREAKOUT8, 3s);
Talk(SAY_BREAKOUT9, 8s);
scheduler.Schedule(11s, [this](TaskContext)
{
Talk(SAY_BREAKOUT10);
SetInvincibility(true);
me->SetReactState(REACT_PASSIVE);
me->RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC);
me->GetMotionMaster()->MovePath((me->GetEntry() + 1) * 10, false);
});
}
}
void UpdateAI(uint32 diff) override
{
scheduler.Update(diff);
}
private:
ObjectGuid _valrothGUID;
};
};
@@ -504,98 +517,6 @@ public:
};
};
//Koltira & Valroth- Breakout
enum valroth
{
//SAY_VALROTH1 = 0, Unused
SAY_VALROTH_AGGRO = 1,
SAY_VALROTH_RAND = 2,
SAY_VALROTH_DEATH = 3,
SPELL_RENEW = 38210,
SPELL_INQUISITOR_PENANCE = 52922,
SPELL_VALROTH_SMITE = 52926,
SPELL_SUMMON_VALROTH_REMAINS = 52929
};
class npc_high_inquisitor_valroth : public CreatureScript
{
public:
npc_high_inquisitor_valroth() : CreatureScript("npc_high_inquisitor_valroth") { }
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_high_inquisitor_valrothAI(creature);
}
struct npc_high_inquisitor_valrothAI : public ScriptedAI
{
npc_high_inquisitor_valrothAI(Creature* creature) : ScriptedAI(creature) { }
uint32 uiRenew_timer;
uint32 uiInquisitor_Penance_timer;
uint32 uiValroth_Smite_timer;
void Reset() override
{
uiRenew_timer = 1000;
uiInquisitor_Penance_timer = 2000;
uiValroth_Smite_timer = 1000;
}
void JustEngagedWith(Unit* who) override
{
Talk(SAY_VALROTH_AGGRO);
DoCast(who, SPELL_VALROTH_SMITE);
}
void UpdateAI(uint32 diff) override
{
if (uiRenew_timer <= diff)
{
Shout();
DoCast(me, SPELL_RENEW);
uiRenew_timer = urand(1000, 6000);
}
else uiRenew_timer -= diff;
if (uiInquisitor_Penance_timer <= diff)
{
Shout();
DoCastVictim(SPELL_INQUISITOR_PENANCE);
uiInquisitor_Penance_timer = urand(2000, 7000);
}
else uiInquisitor_Penance_timer -= diff;
if (uiValroth_Smite_timer <= diff)
{
Shout();
DoCastVictim(SPELL_VALROTH_SMITE);
uiValroth_Smite_timer = urand(1000, 6000);
}
else uiValroth_Smite_timer -= diff;
DoMeleeAttackIfReady();
}
void Shout()
{
if (rand() % 100 < 15)
Talk(SAY_VALROTH_RAND);
}
void JustDied(Unit* killer) override
{
Talk(SAY_VALROTH_DEATH);
if (killer)
{
killer->CastSpell(me, SPELL_SUMMON_VALROTH_REMAINS, true);
}
}
};
};
/*######
## npc_a_special_surprise
######*/
@@ -785,6 +706,5 @@ void AddSC_the_scarlet_enclave_c2()
new npc_crusade_persuaded();
new npc_scarlet_courier();
new npc_koltira_deathweaver();
new npc_high_inquisitor_valroth();
new npc_a_special_surprise();
}
@@ -121,7 +121,7 @@ enum LightOfDawnEncounter
EVENT_SPELL_DEATH_STRIKE,
EVENT_SPELL_DEATH_EMBRACE,
EVENT_SPELL_UNHOLY_BLIGHT,
EVENT_SPELL_TALK,
EVENT_SPELL_DARION_MOD_DAMAGE,
// Positioning
EVENT_FINISH_FIGHT_1,
EVENT_FINISH_FIGHT_2,
@@ -253,6 +253,7 @@ enum LightOfDawnSpells
SPELL_DEATH_EMBRACE = 53635,
SPELL_ICY_TOUCH1 = 49723,
SPELL_UNHOLY_BLIGHT = 53640,
SPELL_DARION_MOD_DAMAGE = 53645,
// Outro
SPELL_THE_LIGHT_OF_DAWN = 53658,
@@ -524,7 +525,7 @@ public:
events.RescheduleEvent(EVENT_SPELL_DEATH_STRIKE, 8000);
events.RescheduleEvent(EVENT_SPELL_DEATH_EMBRACE, 5000);
events.RescheduleEvent(EVENT_SPELL_UNHOLY_BLIGHT, 10000);
events.RescheduleEvent(EVENT_SPELL_TALK, 10000);
events.RescheduleEvent(EVENT_SPELL_DARION_MOD_DAMAGE, 500);
}
void Reset() override
@@ -661,7 +662,7 @@ public:
me->SetHomePosition(pos);
me->SetWalk(false);
me->GetMotionMaster()->MovePoint(1, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), true, true);
me->CastSpell(me, SPELL_THE_MIGHT_OF_MOGRAINE, true);
DoCastSelf(SPELL_THE_MIGHT_OF_MOGRAINE, true);
break;
}
case EVENT_START_COUNTDOWN_14:
@@ -1146,23 +1147,24 @@ public:
{
case EVENT_SPELL_ANTI_MAGIC_ZONE:
DoCast(me, SPELL_ANTI_MAGIC_ZONE1);
events.RescheduleEvent(eventId, 25s, 30s);
events.RescheduleEvent(eventId, 30s, 45s);
break;
case EVENT_SPELL_DEATH_STRIKE:
DoCastVictim(SPELL_DEATH_STRIKE);
events.RescheduleEvent(eventId, 5s, 10s);
events.RescheduleEvent(eventId, 5s, 35s);
break;
case EVENT_SPELL_DEATH_EMBRACE:
DoCastVictim(SPELL_DEATH_EMBRACE);
events.RescheduleEvent(eventId, 15s, 20s);
events.RescheduleEvent(eventId, 45s, 60s);
break;
case EVENT_SPELL_UNHOLY_BLIGHT:
DoCast(me, SPELL_UNHOLY_BLIGHT);
events.RescheduleEvent(eventId, 60s);
break;
case EVENT_SPELL_TALK:
case EVENT_SPELL_DARION_MOD_DAMAGE:
DoCast(me, SPELL_DARION_MOD_DAMAGE);
Talk(SAY_LIGHT_OF_DAWN09);
events.RescheduleEvent(eventId, 15s, 20s);
events.RescheduleEvent(eventId, 15s, 25s);
break;
}
@@ -1215,9 +1217,106 @@ class spell_chapter5_rebuke : public SpellScript
}
};
// 58552 - Return to Orgrimmar
// 58533 - Return to Stormwind
enum ReturnToCapital
{
SPELL_RETURN_TO_ORGRIMMAR_APPLE = 58509,
SPELL_RETURN_TO_ORGRIMMAR_BANANA = 58513,
SPELL_RETURN_TO_ORGRIMMAR_SPIT = 58520,
EMOTE_THROW_APPLE = 2,
EMOTE_THROW_BANANA = 3,
EMOTE_THROW_SPIT = 4,
SAY_INSULT_TO_DK = 5,
NPC_SW_GUARD = 68,
NPC_ROYAL_GUARD = 1756,
NPC_CITY_PATROLLER = 1976,
NPC_OG_GUARD = 3296,
NPC_KOR_ELITE = 14304,
TEXT_BROADCAST_COWER = 31670 // "%s cowers in fear."
};
uint32 ReturnToCapitalSpells[3] =
{
58509, // Apple
58513, // Banana
58520 // Spit
};
class spell_chapter5_return_to_capital : public SpellScript
{
PrepareSpellScript(spell_chapter5_return_to_capital);
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_RETURN_TO_ORGRIMMAR_APPLE, SPELL_RETURN_TO_ORGRIMMAR_BANANA, SPELL_RETURN_TO_ORGRIMMAR_SPIT});
}
void HandleHit(SpellEffIndex /*effIndex*/)
{
Creature* creature = GetHitUnit()->ToCreature();
Player* player = GetCaster()->ToPlayer();
uint32 spellId = GetSpellInfo()->Id;
if (!spellId || !creature || !player || player->IsGameMaster() || !player->IsAlive() || !creature->IsAlive() || creature->IsInCombat())
return;
if (creature->HasSpellCooldown(spellId))
return;
if (creature->GetEntry() == NPC_SW_GUARD || creature->GetEntry() == NPC_ROYAL_GUARD || creature->GetEntry() == NPC_CITY_PATROLLER || creature->GetEntry() == NPC_OG_GUARD || creature->GetEntry() == NPC_KOR_ELITE)
{
_emote = urand(2,4);
if (creature)
{
creature->PauseMovement(5000);
creature->SetTimedFacingToObject(player, 30000);
if (roll_chance_i(30))
{
creature->AI()->Talk(_emote, player);
creature->CastSpell(player, ReturnToCapitalSpells[_emote - 2]);
}
else
{
creature->AI()->Talk(SAY_INSULT_TO_DK, player);
creature->HandleEmoteCommand(RAND(EMOTE_ONESHOT_POINT,EMOTE_ONESHOT_RUDE));
}
}
}
/*/// @todo: This needs to be further investigated as there are some "guard" npcs, that have civilian flags and non guard npcs should also insult the dk.
else
if (creature->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_CIVILIAN)
{
creature->HandleEmoteCommand(EMOTE_STATE_COWER); // from sniff, emote 431 for a while, then reset (with "%s cowers in fear." text)
creature->PlayDirectSound(14556); // from sniff
if (player)
{
LocaleConstant loc_idx = player->GetSession()->GetSessionDbLocaleIndex();
if (BroadcastText const* bct = sObjectMgr->GetBroadcastText(TEXT_BROADCAST_COWER))
creature->TextEmote(bct->GetText(loc_idx, creature->getGender()), creature);
}
}
*/
creature->AddSpellCooldown(spellId, 0, 30000);
}
void Register() override
{
OnEffectHitTarget += SpellEffectFn(spell_chapter5_return_to_capital::HandleHit, EFFECT_0, SPELL_EFFECT_DUMMY);
}
private:
uint8 _emote;
};
void AddSC_the_scarlet_enclave_c5()
{
new npc_highlord_darion_mograine();
RegisterSpellScript(spell_chapter5_light_of_dawn_aura);
RegisterSpellScript(spell_chapter5_rebuke);
RegisterSpellScript(spell_chapter5_return_to_capital);
}
@@ -381,14 +381,8 @@ struct boss_kiljaeden : public BossAI
if (Creature* anveena = instance->GetCreature(DATA_ANVEENA))
{
anveena->RemoveAllAuras();
anveena->DespawnOrUnsummon(3500);
}
}, 34s);
me->m_Events.AddEventAtOffset([&] {
if (Creature* anveena = instance->GetCreature(DATA_ANVEENA))
{
anveena->CastSpell(anveena, SPELL_SACRIFICE_OF_ANVEENA, true);
anveena->DespawnOrUnsummon(1500);
DoCastSelf(SPELL_CUSTOM_08_STATE, true);
me->SetUnitFlag(UNIT_FLAG_PACIFIED);
scheduler.CancelAll();
@@ -213,7 +213,7 @@ struct npc_dark_fiend : public ScriptedAI
Unit* target = nullptr;
if (InstanceScript* instance = me->GetInstanceScript())
if (Creature* muru = instance->GetCreature(DATA_MURU))
target = muru->GetAI()->SelectTarget(SelectTargetMethod::Random, 0, FarthestTargetSelector(me, 50.0f, true, true));
target = muru->GetAI()->SelectTarget(SelectTargetMethod::Random, 0, RangeSelector(me, 50.0f, true, true));
if (target)
{
@@ -18,6 +18,8 @@
#include "CreatureScript.h"
#include "Player.h"
#include "ScriptedCreature.h"
#include "SpellScript.h"
#include "SpellScriptLoader.h"
#include "SpellInfo.h"
#include "halls_of_lightning.h"
@@ -27,6 +29,7 @@ enum IonarSpells
SPELL_BALL_LIGHTNING_H = 59800,
SPELL_STATIC_OVERLOAD_N = 52658,
SPELL_STATIC_OVERLOAD_H = 59795,
SPELL_STATIC_OVERLOAD_KNOCK = 53337,
SPELL_DISPERSE = 52770,
SPELL_SUMMON_SPARK = 52746,
@@ -272,8 +275,34 @@ public:
};
};
// 52658, 59795 - Static Overload
class spell_ionar_static_overload : public AuraScript
{
PrepareAuraScript(spell_ionar_static_overload);
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_STATIC_OVERLOAD_KNOCK });
}
void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_EXPIRE)
return;
if (Unit* target = GetTarget())
target->CastSpell(target, SPELL_STATIC_OVERLOAD_KNOCK, true);
}
void Register() override
{
OnEffectRemove += AuraEffectRemoveFn(spell_ionar_static_overload::OnRemove, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL, AURA_EFFECT_HANDLE_REAL);
}
};
void AddSC_boss_ionar()
{
new boss_ionar();
new npc_spark_of_ionar();
RegisterSpellScript(spell_ionar_static_overload);
}
@@ -819,54 +819,6 @@ public:
}
};
enum StormCloud
{
STORM_COULD = 29939,
HEALING_WINDS = 55549,
STORM_VISUAL = 55708,
GYMERS_GRAB = 55516,
RIDE_VEHICLE = 43671
};
class npc_storm_cloud : public CreatureScript
{
public:
npc_storm_cloud() : CreatureScript("npc_storm_cloud") { }
struct npc_storm_cloudAI : public ScriptedAI
{
npc_storm_cloudAI(Creature* creature) : ScriptedAI(creature) { }
void Reset() override
{
me->CastSpell(me, STORM_VISUAL, true);
}
void JustRespawned() override
{
Reset();
}
void SpellHit(Unit* caster, SpellInfo const* spell) override
{
if (spell->Id != GYMERS_GRAB)
return;
if (Vehicle* veh = caster->GetVehicleKit())
if (veh->GetAvailableSeatCount() != 0)
{
me->CastSpell(caster, RIDE_VEHICLE, true);
me->CastSpell(caster, HEALING_WINDS, true);
}
}
};
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_storm_cloudAI(creature);
}
};
enum ScourgeDisguiseInstability
{
SCOURGE_DISGUISE_FAILING_MESSAGE_1 = 28552, // Scourge Disguise Failing! Find a safe place!
@@ -922,7 +874,6 @@ void AddSC_zuldrak()
new npc_released_offspring_harkoa();
new npc_crusade_recruit();
new go_scourge_enclosure();
new npc_storm_cloud();
RegisterSpellScript(spell_scourge_disguise_instability);
}
+41 -4
View File
@@ -1727,6 +1727,16 @@ class spell_dk_pestilence : public SpellScript
{
PrepareSpellScript(spell_dk_pestilence);
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo(
{
SPELL_DK_GLYPH_OF_DISEASE,
SPELL_DK_BLOOD_PLAGUE,
SPELL_DK_FROST_FEVER
});
}
void HandleScriptEffect(SpellEffIndex /*effIndex*/)
{
Unit* caster = GetCaster();
@@ -1743,11 +1753,38 @@ class spell_dk_pestilence : public SpellScript
// And spread them on target
// Blood Plague
if (target->GetAura(SPELL_DK_BLOOD_PLAGUE, caster->GetGUID()))
caster->CastSpell(hitUnit, SPELL_DK_BLOOD_PLAGUE, true);
if (Aura* disOld = target->GetAura(SPELL_DK_BLOOD_PLAGUE, caster->GetGUID()))
if (AuraEffect* effOld = disOld->GetEffect(EFFECT_0))
{
float pctMods = effOld->GetPctMods();
float crit = effOld->GetCritChance();
caster->CastSpell(hitUnit, SPELL_DK_BLOOD_PLAGUE, true);
if (Aura* disNew = hitUnit->GetAura(SPELL_DK_BLOOD_PLAGUE, caster->GetGUID()))
if (AuraEffect* effNew = disNew->GetEffect(EFFECT_0))
{
effNew->SetPctMods(pctMods);
effNew->SetCritChance(crit);
effNew->SetAmount(effNew->CalculateAmount(effNew->GetCaster()));
}
}
// Frost Fever
if (target->GetAura(SPELL_DK_FROST_FEVER, caster->GetGUID()))
caster->CastSpell(hitUnit, SPELL_DK_FROST_FEVER, true);
if (Aura* disOld = target->GetAura(SPELL_DK_FROST_FEVER, caster->GetGUID()))
if (AuraEffect* effOld = disOld->GetEffect(EFFECT_0))
{
float pctMods = effOld->GetPctMods();
float crit = effOld->GetCritChance();
caster->CastSpell(hitUnit, SPELL_DK_FROST_FEVER, true);
if (Aura* disNew = hitUnit->GetAura(SPELL_DK_FROST_FEVER, caster->GetGUID()))
if (AuraEffect* effNew = disNew->GetEffect(EFFECT_0))
{
effNew->SetPctMods(pctMods);
effNew->SetCritChance(crit);
effNew->SetAmount(effNew->CalculateAmount(effNew->GetCaster()));
}
}
}
}
+1 -1
View File
@@ -606,7 +606,6 @@ class spell_gen_black_magic_enchant : public AuraScript
}
};
// 53642 - The Might of Mograine
// 64174 - Protective Gaze
class spell_gen_area_aura_select_players : public AuraScript
{
@@ -622,6 +621,7 @@ class spell_gen_area_aura_select_players : public AuraScript
}
};
// 53642 - The Might of Mograine
// 62650 - Fortitude of Frost
// 62670 - Resilience of Nature
// 62671 - Speed of Invention
+23
View File
@@ -4187,6 +4187,28 @@ class spell_item_multiphase_goggles : public AuraScript
}
};
// 60244 - Blood Parrot Despawn Aura (Item: 12185 - Bloodsail Admiral's Hat)
enum BloodsailAdmiralHat
{
NPC_ADMIRAL_HAT_PARROT = 11236, // Blood Parrot
};
class spell_item_bloodsail_admiral_hat : public AuraScript
{
PrepareAuraScript(spell_item_bloodsail_admiral_hat);
void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
if (Player* player = GetCaster()->ToPlayer())
player->RemoveAllMinionsByEntry(NPC_ADMIRAL_HAT_PARROT);
}
void Register() override
{
OnEffectRemove += AuraEffectRemoveFn(spell_item_bloodsail_admiral_hat::OnRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
}
};
void AddSC_item_spell_scripts()
{
RegisterSpellScript(spell_item_massive_seaforium_charge);
@@ -4315,4 +4337,5 @@ void AddSC_item_spell_scripts()
RegisterSpellScript(spell_item_luffa);
RegisterSpellScript(spell_item_spell_reflectors);
RegisterSpellScript(spell_item_multiphase_goggles);
RegisterSpellScript(spell_item_bloodsail_admiral_hat);
}
+12 -5
View File
@@ -2269,7 +2269,10 @@ class spell_q12619_emblazon_runeblade_effect : public SpellScript
enum Quest_The_Storm_King
{
SPELL_RIDE_GYMER = 43671,
SPELL_GRABBED = 55424
SPELL_GRABBED = 55424,
SPELL_HEALING_WINDS = 55549,
NPC_STORM_CLOUD = 29939
};
class spell_q12919_gymers_grab : public SpellScript
@@ -2284,10 +2287,14 @@ class spell_q12919_gymers_grab : public SpellScript
void HandleScript(SpellEffIndex /*effIndex*/)
{
int8 seatId = 2;
if (!GetHitCreature())
return;
GetHitCreature()->CastCustomSpell(SPELL_RIDE_GYMER, SPELLVALUE_BASE_POINT0, seatId, GetCaster(), true);
GetHitCreature()->CastSpell(GetHitCreature(), SPELL_GRABBED, true);
if (Creature* creature = GetHitCreature())
{
creature->CastCustomSpell(SPELL_RIDE_GYMER, SPELLVALUE_BASE_POINT0, seatId, GetCaster(), true);
creature->CastSpell(creature, SPELL_GRABBED, true);
if (creature->GetEntry() == NPC_STORM_CLOUD)
creature->CastSpell(GetCaster(), SPELL_HEALING_WINDS, true);
}
}
void Register() override
+10 -10
View File
@@ -391,8 +391,8 @@ enum SpellAttr0 : uint32
SPELL_ATTR0_HELD_ITEM_ONLY = 0x00000200, // TITLE Auto-target mainhand item (client only) DESCRIPTION Client will automatically select main-hand item as cast target
SPELL_ATTR0_ON_NEXT_SWING = 0x00000400, // TITLE On next melee (type 2) DESCRIPTION Both "on next swing" attributes have identical handling in server & client
SPELL_ATTR0_WEARER_CASTS_PROC_TRIGGER = 0x00000800, // TITLE Unknown attribute 11@Attr0
SPELL_ATTR0_SERVER_ONLY = 0x00001000, // TITLE Only usable during daytime (unused)
SPELL_ATTR0_ALLOW_ITEM_SPELL_IN_PVP = 0x00002000, // TITLE Only usable during nighttime (unused)
SPELL_ATTR0_SERVER_ONLY = 0x00001000, // TITLE Unused attribute 12@Attr0 DESCRIPTION not set in 3.3.5a
SPELL_ATTR0_ALLOW_ITEM_SPELL_IN_PVP = 0x00002000, // TITLE Only usable during nighttime
SPELL_ATTR0_ONLY_INDOORS = 0x00004000, // TITLE Only usable indoors
SPELL_ATTR0_ONLY_OUTDOORS = 0x00008000, // TITLE Only usable outdoors
SPELL_ATTR0_NOT_SHAPESHIFTED = 0x00010000, // TITLE Not usable while shapeshifted
@@ -461,14 +461,14 @@ enum SpellAttr2 : uint32
SPELL_ATTR2_AUTO_REPEAT = 0x00000020, // TITLE Ranged auto-attack spell
SPELL_ATTR2_CANNOT_CAST_ON_TAPPED = 0x00000040, // TITLE Cannot target others' tapped units DESCRIPTION Can only target untapped units, or those tapped by caster
SPELL_ATTR2_DO_NOT_REPORT_SPELL_FAILURE = 0x00000080, // TITLE Unknown attribute 7@Attr2
SPELL_ATTR2_INCLUDE_IN_ADVANCED_COMBAT_LOG = 0x00000100, // TITLE Unknown attribute 8@Attr2 DESCRIPTION not set in 3.0.3
SPELL_ATTR2_INCLUDE_IN_ADVANCED_COMBAT_LOG = 0x00000100, // TITLE Unused attribute 8@Attr2 DESCRIPTION not set in 3.3.5a
SPELL_ATTR2_ALWAYS_CAST_AS_UNIT = 0x00000200, // TITLE Unknown attribute 9@Attr2
SPELL_ATTR2_SPECIAL_TAMING_FLAG = 0x00000400, // TITLE Unknown attribute 10@Attr2 DESCRIPTION Related to taming?
SPELL_ATTR2_NO_TARGET_PER_SECOND_COST = 0x00000800, // TITLE Health Funnel
SPELL_ATTR2_CHAIN_FROM_CASTER = 0x00001000, // TITLE Unknown attribute 12@Attr2 DESCRIPTION Cleave, Heart Strike, Maul, Sunder Armor, Swipe
SPELL_ATTR2_ENCHANT_OWN_ITEM_ONLY = 0x00002000, // TITLE Enchant persists when entering arena
SPELL_ATTR2_ALLOW_WHILE_INVISIBLE = 0x00004000, // TITLE Unknown attribute 14@Attr2
SPELL_ATTR2_DO_NOT_CONSUME_IF_GAINED_DURING_CAST = 0x00008000, // TITLE Unknown attribute 15@Attr2 DESCRIPTION not set in 3.0.3
SPELL_ATTR2_DO_NOT_CONSUME_IF_GAINED_DURING_CAST = 0x00008000, // TITLE Unused attribute 15@Attr2 DESCRIPTION not set in 3.3.5a
SPELL_ATTR2_NO_ACTIVE_PETS = 0x00010000, // TITLE Tame Beast
SPELL_ATTR2_DO_NOT_RESET_COMBAT_TIMERS = 0x00020000, // TITLE Don't reset swing timer DESCRIPTION Does not reset melee/ranged autoattack timer on cast
SPELL_ATTR2_NO_JUMP_WHILE_CAST_PENDING = 0x00040000, // TITLE Requires dead pet
@@ -511,8 +511,8 @@ enum SpellAttr3 : uint32
SPELL_ATTR3_ALWAYS_HIT = 0x00040000, // TITLE Ignore hit result DESCRIPTION Spell cannot miss, or be dodged/parried/blocked
SPELL_ATTR3_INSTANT_TARGET_PROCS = 0x00080000, // TITLE Cannot trigger spells during aura proc
SPELL_ATTR3_ALLOW_AURA_WHILE_DEAD = 0x00100000, // TITLE Persists through death
SPELL_ATTR3_ONLY_PROC_OUTDOORS = 0x00200000, // TITLE Unknown attribute 21@Attr3
SPELL_ATTR3_CASTING_CANCELS_AUTOREPEAT = 0x00400000, // TITLE Requires equipped Wand (Mainline: Do Not Trigger Target Stand)
SPELL_ATTR3_ONLY_PROC_OUTDOORS = 0x00200000, // TITLE Unused attribute 21@Attr3 DESCRIPTION Not set in 3.3.5a
SPELL_ATTR3_CASTING_CANCELS_AUTOREPEAT = 0x00400000, // TITLE Unused attribute 22@Attr3 DESCRIPTION Not set in 3.3.5a
SPELL_ATTR3_NO_DAMAGE_HISTORY = 0x00800000, // TITLE Unknown attribute 23@Attr3
SPELL_ATTR3_REQUIRES_OFF_HAND_WEAPON = 0x01000000, // TITLE Requires offhand weapon
SPELL_ATTR3_TREAT_AS_PERIODIC = 0x02000000, // TITLE Treat as periodic effect
@@ -571,7 +571,7 @@ enum SpellAttr5 : uint32
SPELL_ATTR5_TRIGGERS_CHANNELING = 0x00000010, // TITLE Unknown attribute 4@Attr5
SPELL_ATTR5_LIMIT_N = 0x00000020, // TITLE Single-target aura DESCRIPTION Remove previous application to another unit if applied
SPELL_ATTR5_IGNORE_AREA_EFFECT_PVP_CHECK = 0x00000040, // TITLE Unknown attribute 6@Attr5
SPELL_ATTR5_NOT_ON_PLAYER = 0x00000080, // TITLE Unknown attribute 7@Attr5
SPELL_ATTR5_NOT_ON_PLAYER = 0x00000080, // TITLE Cannot target players
SPELL_ATTR5_NOT_ON_PLAYER_CONTROLLED_NPC = 0x00000100, // TITLE Cannot target player controlled units but can target players
SPELL_ATTR5_EXTRA_INITIAL_PERIOD = 0x00000200, // TITLE Immediately do periodic tick on apply
SPELL_ATTR5_DO_NOT_DISPLAY_DURATION = 0x00000400, // TITLE Do not send aura duration to client
@@ -650,8 +650,8 @@ enum SpellAttr7 : uint32
SPELL_ATTR7_ALLIANCE_SPECIFIC_SPELL = 0x00000200, // TITLE Alliance only
SPELL_ATTR7_DISPEL_REMOVES_CHARGES = 0x00000400, // TITLE Dispel/Spellsteal remove individual charges
SPELL_ATTR7_CAN_CAUSE_INTERRUPT = 0x00000800, // TITLE Only interrupt non-player casting
SPELL_ATTR7_CAN_CAUSE_SILENCE = 0x00001000, // TITLE Unknown attribute 12@Attr7 DESCRIPTION Not set in 3.2.2a.
SPELL_ATTR7_NO_UI_NOT_INTERRUPTIBLE = 0x00002000, // TITLE Unknown attribute 13@Attr7 DESCRIPTION Not set in 3.2.2a.
SPELL_ATTR7_CAN_CAUSE_SILENCE = 0x00001000, // TITLE Unused attribute 12@Attr7 DESCRIPTION Not set in 3.3.5a.
SPELL_ATTR7_NO_UI_NOT_INTERRUPTIBLE = 0x00002000, // TITLE Unused attribute 13@Attr7 DESCRIPTION Not set in 3.3.5a.
SPELL_ATTR7_RECAST_ON_RESUMMON = 0x00004000, // TITLE Unknown attribute 14@Attr7 DESCRIPTION Only 52150 (Raise Dead - Pet) spell.
SPELL_ATTR7_RESET_SWING_TIMER_AT_SPELL_START = 0x00008000, // TITLE Unknown attribute 15@Attr7 DESCRIPTION Exorcism - guaranteed crit vs families?
SPELL_ATTR7_ONLY_IN_SPELLBOOK_UNTIL_LEARNED = 0x00010000, // TITLE Can restore secondary power DESCRIPTION Only spells with this attribute can replenish a non-active power type
@@ -659,7 +659,7 @@ enum SpellAttr7 : uint32
SPELL_ATTR7_ATTACK_ON_CHARGE_TO_UNIT = 0x00040000, // TITLE Has charge effect
SPELL_ATTR7_REPORT_SPELL_FAILURE_TO_UNIT_TARGET = 0x00080000, // TITLE Is zone teleport
SPELL_ATTR7_NO_CLIENT_FAIL_WHILE_STUNNED_FLEEING_CONFUSED = 0x00100000, // TITLE Unknown attribute 20@Attr7 DESCRIPTION Invulnerability related?
SPELL_ATTR7_RETAIN_COOLDOWN_THROUGH_LOAD = 0x00200000, // TITLE Unknown attribute 21@Attr7
SPELL_ATTR7_RETAIN_COOLDOWN_THROUGH_LOAD = 0x00200000, // TITLE Unused attribute 21@Attr7 DESCRPIPTION Not set in 3.3.5a
SPELL_ATTR7_IGNORES_COLD_WEATHER_FLYING_REQUIREMENT = 0x00400000, // TITLE Ignore cold weather flying restriction DESCRIPTION Set for loaner mounts, allows them to be used despite lacking required flight skill
SPELL_ATTR7_NO_ATTACK_DODGE = 0x00800000, // TITLE Spell cannot be dodged 23@Attr7 DESCRIPTION Motivate, Mutilate, Shattering Throw
SPELL_ATTR7_NO_ATTACK_PARRY = 0x01000000, // TITLE Spell cannot be parried 24@Attr7 DESCRIPTION Motivate, Mutilate, Perform Speech, Shattering Throw