Merge branch 'master' into Playerbot

This commit is contained in:
Yunfan Li
2025-03-22 11:15:08 +08:00
29 changed files with 606 additions and 86 deletions

View File

@@ -264,7 +264,7 @@ void FollowerAI::MovementInform(uint32 motionType, uint32 pointId)
}
}
void FollowerAI::StartFollow(Player* player, uint32 factionForFollower, const Quest* quest)
void FollowerAI::StartFollow(Player* player, uint32 factionForFollower, const Quest* quest, bool inheritWalkState, bool inheritSpeed)
{
if (me->GetVictim())
{
@@ -297,7 +297,7 @@ void FollowerAI::StartFollow(Player* player, uint32 factionForFollower, const Qu
AddFollowState(STATE_FOLLOW_INPROGRESS);
me->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
me->GetMotionMaster()->MoveFollow(player, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE, MOTION_SLOT_ACTIVE, inheritWalkState, inheritSpeed);
LOG_DEBUG("scripts.ai", "FollowerAI start follow {} ({})", player->GetName(), m_uiLeaderGUID.ToString());
}

View File

@@ -55,7 +55,7 @@ public:
void UpdateAI(uint32) override; //the "internal" update, calls UpdateFollowerAI()
virtual void UpdateFollowerAI(uint32); //used when it's needed to add code in update (abilities, scripted events, etc)
void StartFollow(Player* player, uint32 factionForFollower = 0, const Quest* quest = nullptr);
void StartFollow(Player* player, uint32 factionForFollower = 0, const Quest* quest = nullptr, bool inheritWalkState = true, bool inheritSpeed = true);
void SetFollowPaused(bool bPaused); //if special event require follow mode to hold/resume during the follow
void SetFollowComplete(bool bWithEndEvent = false);

View File

@@ -277,7 +277,7 @@ void MotionMaster::MoveTargetedHome(bool walk /*= false*/)
if (target)
{
LOG_DEBUG("movement.motionmaster", "Following {} ({})", target->IsPlayer() ? "player" : "creature", target->GetGUID().ToString());
Mutate(new FollowMovementGenerator<Creature>(target, PET_FOLLOW_DIST, _owner->GetFollowAngle(),true), MOTION_SLOT_ACTIVE);
Mutate(new FollowMovementGenerator<Creature>(target, PET_FOLLOW_DIST, _owner->GetFollowAngle(), true, true), MOTION_SLOT_ACTIVE);
}
}
else
@@ -406,7 +406,7 @@ void MotionMaster::MoveCircleTarget(Unit* target)
/**
* @brief The unit will follow this target. Doesn't work with UNIT_FLAG_DISABLE_MOVE
*/
void MotionMaster::MoveFollow(Unit* target, float dist, float angle, MovementSlot slot, bool inheritWalkState)
void MotionMaster::MoveFollow(Unit* target, float dist, float angle, MovementSlot slot, bool inheritWalkState, bool inheritSpeed)
{
// ignore movement request if target not exist
if (!target || target == _owner || _owner->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE))
@@ -419,13 +419,13 @@ void MotionMaster::MoveFollow(Unit* target, float dist, float angle, MovementSlo
{
LOG_DEBUG("movement.motionmaster", "Player ({}) follow to {} ({})",
_owner->GetGUID().ToString(), target->IsPlayer() ? "player" : "creature", target->GetGUID().ToString());
Mutate(new FollowMovementGenerator<Player>(target, dist, angle, inheritWalkState), slot);
Mutate(new FollowMovementGenerator<Player>(target, dist, angle, inheritWalkState, inheritSpeed), slot);
}
else
{
LOG_DEBUG("movement.motionmaster", "Creature ({}) follow to {} ({})",
_owner->GetGUID().ToString(), target->IsPlayer() ? "player" : "creature", target->GetGUID().ToString());
Mutate(new FollowMovementGenerator<Creature>(target, dist, angle, inheritWalkState), slot);
Mutate(new FollowMovementGenerator<Creature>(target, dist, angle, inheritWalkState, inheritSpeed), slot);
}
}

View File

@@ -201,7 +201,7 @@ public:
void MoveIdle();
void MoveTargetedHome(bool walk = false);
void MoveRandom(float wanderDistance = 0.0f);
void MoveFollow(Unit* target, float dist, float angle, MovementSlot slot = MOTION_SLOT_ACTIVE, bool inheritWalkState = true);
void MoveFollow(Unit* target, float dist, float angle, MovementSlot slot = MOTION_SLOT_ACTIVE, bool inheritWalkState = true, bool inheritSpeed = true);
void MoveChase(Unit* target, std::optional<ChaseRange> dist = {}, std::optional<ChaseAngle> angle = {});
void MoveChase(Unit* target, float dist, float angle) { MoveChase(target, ChaseRange(dist), ChaseAngle(angle)); }
void MoveChase(Unit* target, float dist) { MoveChase(target, ChaseRange(dist)); }

View File

@@ -539,8 +539,9 @@ bool FollowMovementGenerator<T>::DoUpdate(T* owner, uint32 time_diff)
if (_inheritWalkState)
init.SetWalk(target->IsWalking() || target->movespline->isWalking());
if (Optional<float> velocity = GetVelocity(owner, target, i_path->GetActualEndPosition(), owner->IsGuardian()))
init.SetVelocity(*velocity);
if (_inheritSpeed)
if (Optional<float> velocity = GetVelocity(owner, target, i_path->GetActualEndPosition(), owner->IsGuardian()))
init.SetVelocity(*velocity);
init.Launch();
}

View File

@@ -75,8 +75,8 @@ template<class T>
class FollowMovementGenerator : public MovementGeneratorMedium<T, FollowMovementGenerator<T>>, public TargetedMovementGeneratorBase
{
public:
FollowMovementGenerator(Unit* target, float range, ChaseAngle angle, bool inheritWalkState)
: TargetedMovementGeneratorBase(target), i_path(nullptr), i_recheckPredictedDistanceTimer(0), i_recheckPredictedDistance(false), _range(range), _angle(angle),_inheritWalkState(inheritWalkState) {}
FollowMovementGenerator(Unit* target, float range, ChaseAngle angle, bool inheritWalkState, bool inheritSpeed)
: TargetedMovementGeneratorBase(target), i_path(nullptr), i_recheckPredictedDistanceTimer(0), i_recheckPredictedDistance(false), _range(range), _angle(angle),_inheritWalkState(inheritWalkState), _inheritSpeed(inheritSpeed) {}
~FollowMovementGenerator() { }
MovementGeneratorType GetMovementGeneratorType() { return FOLLOW_MOTION_TYPE; }
@@ -108,6 +108,7 @@ private:
float _range;
ChaseAngle _angle;
bool _inheritWalkState;
bool _inheritSpeed;
};
#endif

View File

@@ -1431,7 +1431,16 @@ void Spell::SelectImplicitCasterDestTargets(SpellEffIndex effIndex, SpellImplici
float destx = pos.GetPositionX() + distance * cos(pos.GetOrientation());
float desty = pos.GetPositionY() + distance * sin(pos.GetOrientation());
float ground = map->GetHeight(phasemask, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ());
// Added GROUND_HEIGHT_TOLERANCE to account for cases where, during a jump,
// the Z position may be slightly below the vmap ground level.
// Without this tolerance, a ray trace might incorrectly attempt to find ground
// beneath the actual surface.
//
// Example:
// actual vmap ground: -56.342392
// Z position: -56.347195
float searchGroundZPos = pos.GetPositionZ()+GROUND_HEIGHT_TOLERANCE;
float ground = map->GetHeight(phasemask, pos.GetPositionX(), pos.GetPositionY(), searchGroundZPos);
bool isCasterInWater = m_caster->IsInWater();
if (!m_caster->HasUnitMovementFlag(MOVEMENTFLAG_FALLING) || (pos.GetPositionZ() - ground < distance))

View File

@@ -106,20 +106,29 @@ const Position LandingPos = { 1476.77f, 665.094f, 20.6423f };
class CorruptTriggers : public BasicEvent
{
public:
CorruptTriggers(Unit* caster) : _caster(caster) { }
CorruptTriggers(Unit* caster, uint8 currentLane) : _caster(caster), _currentLane(currentLane) { }
bool Execute(uint64 /*execTime*/, uint32 /*diff*/) override
{
std::list<Creature*> creatureList;
_caster->GetCreaturesWithEntryInRange(creatureList, 70.0f, NPC_FOG_TRIGGER);
for (auto const& creature : creatureList)
{
if (_caster->GetExactDist2d(creature) <= 11.0f)
{
creature->CastSpell(creature, SPELL_FOG_OF_CORRUPTION, true);
continue;
}
if (!_currentLane && creature->GetPositionX() > 1510.0f)
creature->CastSpell(creature, SPELL_FOG_OF_CORRUPTION, true);
}
return true;
}
private:
Unit* _caster;
uint8 _currentLane;
};
struct boss_felmyst : public BossAI
@@ -283,20 +292,20 @@ struct boss_felmyst : public BossAI
me->GetMotionMaster()->MovePoint(POINT_LANE, RightSideLanes[_currentLane], false);
else
me->GetMotionMaster()->MovePoint(POINT_LANE, LeftSideLanes[_currentLane], false);
}, 2s);
}, 5s);
break;
case POINT_LANE:
Talk(EMOTE_BREATH);
me->m_Events.AddEventAtOffset([&] {
me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(0));
me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(500));
me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(1000));
me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(1500));
me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(2000));
me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(2500));
me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(3000));
me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(3500));
me->m_Events.AddEvent(new CorruptTriggers(me), me->m_Events.CalculateTime(4000));
me->m_Events.AddEvent(new CorruptTriggers(me, _currentLane), me->m_Events.CalculateTime(0));
me->m_Events.AddEvent(new CorruptTriggers(me, _currentLane), me->m_Events.CalculateTime(500));
me->m_Events.AddEvent(new CorruptTriggers(me, _currentLane), me->m_Events.CalculateTime(1000));
me->m_Events.AddEvent(new CorruptTriggers(me, _currentLane), me->m_Events.CalculateTime(1500));
me->m_Events.AddEvent(new CorruptTriggers(me, _currentLane), me->m_Events.CalculateTime(2000));
me->m_Events.AddEvent(new CorruptTriggers(me, _currentLane), me->m_Events.CalculateTime(2500));
me->m_Events.AddEvent(new CorruptTriggers(me, _currentLane), me->m_Events.CalculateTime(3000));
me->m_Events.AddEvent(new CorruptTriggers(me, _currentLane), me->m_Events.CalculateTime(3500));
me->m_Events.AddEvent(new CorruptTriggers(me, _currentLane), me->m_Events.CalculateTime(4000));
}, 5s);
me->m_Events.AddEventAtOffset([&] {
@@ -360,57 +369,63 @@ struct boss_felmyst : public BossAI
struct npc_demonic_vapor : public NullCreatureAI
{
npc_demonic_vapor(Creature* creature) : NullCreatureAI(creature) { }
npc_demonic_vapor(Creature* creature) : NullCreatureAI(creature), _timer{1} { }
void Reset() override
{
me->CastSpell(me, SPELL_DEMONIC_VAPOR_SPAWN_TRIGGER, true);
me->CastSpell(me, SPELL_DEMONIC_VAPOR_PERIODIC, true);
}
void UpdateAI(uint32 /*diff*/) override
void IsSummonedBy(WorldObject* summoner) override
{
if (me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) == NULL_MOTION_TYPE)
if (!summoner || !summoner->ToUnit())
return;
me->m_Events.AddEventAtOffset([this, summoner] {
me->GetMotionMaster()->MoveFollow(summoner->ToUnit(), 0.0f, 0.0f, MOTION_SLOT_CONTROLLED);
}, 2s);
}
void UpdateAI(uint32 diff) override
{
if (_timer)
{
Map::PlayerList const& players = me->GetMap()->GetPlayers();
for (Map::PlayerList::const_iterator itr = players.begin(); itr != players.end(); ++itr)
if (me->GetDistance2d(itr->GetSource()) < 20.0f && itr->GetSource()->IsAlive())
{
me->GetMotionMaster()->MoveFollow(itr->GetSource(), 0.0f, 0.0f, MOTION_SLOT_CONTROLLED);
break;
}
_timer += diff;
if (_timer >= 2000)
{
me->CastSpell(me, SPELL_DEMONIC_VAPOR_PERIODIC, true);
_timer = 0;
}
}
}
private:
uint32 _timer;
};
struct npc_demonic_vapor_trail : public NullCreatureAI
{
npc_demonic_vapor_trail(Creature* creature) : NullCreatureAI(creature)
{
timer = 1;
}
npc_demonic_vapor_trail(Creature* creature) : NullCreatureAI(creature), _timer{1} { }
uint32 timer;
void Reset() override
{
me->CastSpell(me, SPELL_DEMONIC_VAPOR_TRAIL_PERIODIC, true);
me->DespawnOrUnsummon(20000);
}
void SpellHitTarget(Unit*, SpellInfo const* spellInfo) override
void SpellHitTarget(Unit* /*unit*/, SpellInfo const* spellInfo) override
{
if (spellInfo->Id == SPELL_DEMONIC_VAPOR)
me->CastSpell(me, SPELL_SUMMON_BLAZING_DEAD, true);
if (spellInfo->Id == SPELL_DEMONIC_VAPOR && !_timer)
_timer = 1;
}
void UpdateAI(uint32 diff) override
{
if (timer)
if (_timer)
{
timer += diff;
if (timer >= 6000)
_timer += diff;
if (_timer >= 5000)
{
timer = 0;
_timer = 0;
me->CastSpell(me, SPELL_SUMMON_BLAZING_DEAD, true);
}
}
@@ -421,6 +436,8 @@ struct npc_demonic_vapor_trail : public NullCreatureAI
summon->SetInCombatWithZone();
summon->AI()->AttackStart(summon->AI()->SelectTarget(SelectTargetMethod::Random, 0, 100.0f));
}
private:
uint32 _timer;
};
class spell_felmyst_fog_of_corruption : public SpellScript

View File

@@ -47,32 +47,36 @@ enum Yells
enum Spells
{
SPELL_SPECTRAL_EXHAUSTION = 44867,
SPELL_SPECTRAL_BLAST = 44869,
SPELL_SPECTRAL_BLAST_PORTAL = 44866,
SPELL_SPECTRAL_BLAST_AA = 46648,
SPELL_TELEPORT_SPECTRAL = 46019,
SPELL_SPECTRAL_EXHAUSTION = 44867,
SPELL_SPECTRAL_BLAST = 44869,
SPELL_SPECTRAL_BLAST_PORTAL = 44866,
SPELL_SPECTRAL_BLAST_AA = 46648,
SPELL_TELEPORT_SPECTRAL = 46019,
SPELL_TELEPORT_NORMAL_REALM = 46020,
SPELL_SPECTRAL_REALM = 46021,
SPELL_SPECTRAL_INVISIBILITY = 44801,
SPELL_DEMONIC_VISUAL = 44800,
SPELL_TELEPORT_NORMAL_REALM = 46020,
SPELL_SPECTRAL_REALM = 46021,
SPELL_SPECTRAL_INVISIBILITY = 44801,
SPELL_DEMONIC_VISUAL = 44800,
SPELL_ARCANE_BUFFET = 45018,
SPELL_FROST_BREATH = 44799,
SPELL_TAIL_LASH = 45122,
SPELL_ARCANE_BUFFET = 45018,
SPELL_FROST_BREATH = 44799,
SPELL_TAIL_LASH = 45122,
SPELL_BANISH = 44836,
SPELL_TRANSFORM_KALEC = 44670,
SPELL_CRAZED_RAGE = 44807,
SPELL_BANISH = 44836,
SPELL_TRANSFORM_KALEC = 44670,
SPELL_CRAZED_RAGE = 44807,
SPELL_CORRUPTION_STRIKE = 45029,
SPELL_CURSE_OF_BOUNDLESS_AGONY = 45032,
SPELL_CURSE_OF_BOUNDLESS_AGONY_PLR = 45034,
SPELL_SHADOW_BOLT = 45031,
SPELL_CORRUPTION_STRIKE = 45029,
SPELL_CURSE_OF_BOUNDLESS_AGONY = 45032,
SPELL_CURSE_OF_BOUNDLESS_AGONY_PLR = 45034,
SPELL_CURSE_OF_BOUNDLESS_AGONY_REMOVE = 45050,
SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_1 = 45083,
SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_2 = 45085,
SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_3 = 45084,
SPELL_SHADOW_BOLT = 45031,
SPELL_HEROIC_STRIKE = 45026,
SPELL_REVITALIZE = 45027
SPELL_HEROIC_STRIKE = 45026,
SPELL_REVITALIZE = 45027
};
enum SWPActions
@@ -244,7 +248,7 @@ struct boss_kalecgos : public BossAI
DoCastAOE(SPELL_SPECTRAL_BLAST);
}, 20s, 30s);
scheduler.Schedule(16s, [this](TaskContext)
scheduler.Schedule(9s, [this](TaskContext)
{
if (Creature* kalec = me->SummonCreature(NPC_KALEC, 1702.21f, 931.7f, -74.56f, 5.07f, TEMPSUMMON_MANUAL_DESPAWN))
kalec->CastSpell(kalec, SPELL_SPECTRAL_INVISIBILITY, true);
@@ -388,6 +392,7 @@ struct boss_sathrovarr : public ScriptedAI
void JustDied(Unit* /*killer*/) override
{
DoCastSelf(SPELL_CURSE_OF_BOUNDLESS_AGONY_REMOVE, true);
Talk(SAY_SATH_DEATH);
}
@@ -464,10 +469,10 @@ class spell_kalecgos_curse_of_boundless_agony_aura : public AuraScript
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_CURSE_OF_BOUNDLESS_AGONY_PLR });
return ValidateSpellInfo({ SPELL_CURSE_OF_BOUNDLESS_AGONY_PLR, SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_1, SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_2, SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_3 });
}
void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
if (InstanceScript* instance = GetUnitOwner()->GetInstanceScript())
if (instance->IsEncounterInProgress())
@@ -476,8 +481,18 @@ class spell_kalecgos_curse_of_boundless_agony_aura : public AuraScript
void OnPeriodic(AuraEffect const* aurEff)
{
if (aurEff->GetTickNumber() > 1 && aurEff->GetTickNumber() % 5 == 1)
uint32 tickNumber = aurEff->GetTickNumber();
if (tickNumber > 1 && tickNumber % 5 == 1)
GetAura()->GetEffect(aurEff->GetEffIndex())->SetAmount(aurEff->GetAmount() * 2);
uint32 spellId = 0;
if (tickNumber <= 10)
spellId = SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_1;
else if (tickNumber <= 20)
spellId = SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_2;
else
spellId = SPELL_CURSE_OF_BOUNDLESS_AGONY_DUMMY_3;
GetTarget()->CastSpell(GetTarget(), spellId, true);
}
void Register() override

View File

@@ -184,7 +184,10 @@ struct npc_sunblade_scout : public ScriptedAI
protectors.remove_if([](Creature* trigger) {return !trigger->HasAura(SPELL_COSMETIC_STUN_IMMUNE_PERMANENT);});
protectors.sort(Acore::ObjectDistanceOrderPred(me));
if (protectors.empty())
{
ScheduleCombat();
return;
}
Creature* closestProtector = protectors.front();
me->GetMotionMaster()->MoveFollow(closestProtector, 0.0f, 0.0f);
_protectorGUID = closestProtector->GetGUID();

View File

@@ -757,7 +757,10 @@ struct npc_amanishi_scout : public ScriptedAI
triggers.remove_if([](Creature* trigger) {return !IsDrum(trigger);});
triggers.sort(Acore::ObjectDistanceOrderPred(me));
if (triggers.empty())
{
ScheduleCombat();
return;
}
Creature* closestDrum = triggers.front();
me->GetMotionMaster()->MoveFollow(closestDrum, 0.0f, 0.0f);
_drumGUID = closestDrum->GetGUID();

View File

@@ -33,7 +33,6 @@ enum Spells
// INSANITY
SPELL_INSANITY = 57496, //Dummy
INSANITY_VISUAL = 57561,
SPELL_INSANITY_TARGET = 57508,
SPELL_CLONE_PLAYER = 57507, //casted on player during insanity
SPELL_INSANITY_PHASING_1 = 57508,
SPELL_INSANITY_PHASING_2 = 57509,
@@ -362,15 +361,13 @@ class spell_herald_volzaj_insanity : public SpellScript
{
targets.remove_if([this](WorldObject* targetObj) -> bool
{
return !targetObj || !targetObj->IsPlayer() || !targetObj->ToPlayer()->IsInCombatWith(GetCaster()) ||
return !targetObj || !targetObj->IsPlayer() || !GetCaster()->IsInCombatWith(targetObj->ToPlayer()) ||
targetObj->GetDistance(GetCaster()) >= (MAX_VISIBILITY_DISTANCE * 2);
});
}
if (targets.empty())
{
return;
}
// Start channel visual and set self as unnattackable
caster->ToCreature()->AI()->Talk(SAY_INSANITY);
@@ -387,16 +384,12 @@ class spell_herald_volzaj_insanity : public SpellScript
{
WorldObject* targetObj = *itr;
if (!targetObj)
{
continue;
}
Player* plrTarget = targetObj->ToPlayer();
// This should never happen, spell has attribute SPELL_ATTR3_ONLY_TARGET_PLAYERS
if (!plrTarget)
{
continue;
}
// phase mask
plrTarget->CastSpell(plrTarget, InsanitySpells.at(insanityCounter), true);
@@ -405,19 +398,17 @@ class spell_herald_volzaj_insanity : public SpellScript
for (std::list<WorldObject*>::const_iterator itr2 = targets.begin(); itr2 != targets.end(); ++itr2)
{
// Should not make clone of current player target
Player const* plrClone = *itr2 ? (*itr2)->ToPlayer() : nullptr;
if (!plrClone || plrClone == plrTarget)
{
Player* plrClone = *itr2 ? (*itr2)->ToPlayer() : nullptr;
if (!plrClone || plrClone == plrTarget || !plrClone->IsAlive())
continue;
}
if (Unit* summon = caster->SummonCreature(NPC_TWISTED_VISAGE, plrClone->GetPosition(), TEMPSUMMON_CORPSE_DESPAWN, 0))
{
plrClone->CastSpell(summon, SPELL_CLONE_PLAYER, true);
summon->AddThreat(plrTarget, 0.0f);
summon->SetInCombatWith(plrTarget);
plrTarget->SetInCombatWith(summon);
plrTarget->CastSpell(summon, SPELL_CLONE_PLAYER, true);
summon->SetPhaseMask(1 | (1 << (4 + insanityCounter)), true);
summon->SetUInt32Value(UNIT_FIELD_MINDAMAGE, plrClone->GetUInt32Value(UNIT_FIELD_MINDAMAGE));
summon->SetUInt32Value(UNIT_FIELD_MAXDAMAGE, plrClone->GetUInt32Value(UNIT_FIELD_MAXDAMAGE));

View File

@@ -94,6 +94,8 @@ struct boss_magus_telestra : public BossAI
if (IsHeroic() && sGameEventMgr->IsActiveEvent(GAME_EVENT_WINTER_VEIL) && !me->HasAura(SPELL_WEAR_CHRISTMAS_HAT))
me->AddAura(SPELL_WEAR_CHRISTMAS_HAT, me);
SetInvincibility(false);
}
uint32 GetData(uint32 data) const override
@@ -184,6 +186,7 @@ struct boss_magus_telestra : public BossAI
case EVENT_MAGUS_HEALTH2:
if (me->HealthBelowPct(11))
{
SetInvincibility(true);
me->CastSpell(me, SPELL_START_SUMMON_CLONES, false);
events.ScheduleEvent(EVENT_MAGUS_RELOCATE, 3500ms);
Talk(SAY_SPLIT);
@@ -214,6 +217,7 @@ struct boss_magus_telestra : public BossAI
me->CastSpell(me, SPELL_TELESTRA_BACK, true);
me->RemoveAllAuras();
Talk(SAY_MERGE);
SetInvincibility(false);
break;
}

View File

@@ -474,6 +474,92 @@ public:
}
};
/*######
## Quest 11881: Load'er Up
######*/
// NPC 25969: Jenny
enum Jenny
{
EVENT_JENNY_START_FOLLOW = 1,
EVENT_JENNY_MOVE_TO_FEZZIX = 2,
EVENT_JENNY_DESPAWN = 3,
SPELL_CRATES_CARRIED = 46340,
SPELL_DROP_CRATE = 46342,
SPELL_GIVE_JENNY_CREDIT = 46358,
NPC_FEZZIX_GEARTWIST = 25849
};
struct npc_jenny : public FollowerAI
{
npc_jenny(Creature* creature) : FollowerAI(creature)
{
Initialize();
}
void Initialize()
{
me->SetReactState(REACT_PASSIVE);
me->CastSpell(me, SPELL_CRATES_CARRIED);
// can't update follow here, call later
_events.ScheduleEvent(EVENT_JENNY_START_FOLLOW, 1s);
}
void DamageTaken(Unit* /*attacker*/, uint32& /*damage*/, DamageEffectType /*type*/, SpellSchoolMask /*school*/) override
{
if (me->HasAura(SPELL_CRATES_CARRIED))
me->CastSpell(me, SPELL_DROP_CRATE);
else
me->DespawnOrUnsummon();
}
void UpdateFollowerAI(uint32 diff) override
{
_events.Update(diff);
if (uint32 eventId = _events.ExecuteEvent())
{
switch (eventId)
{
case EVENT_JENNY_START_FOLLOW:
// This NPC only moves at its fixed speed_run rate in the db
// and does not inherit the speed of the target
if (TempSummon* summon = me->ToTempSummon())
if (Unit* summonerUnit = summon->GetSummonerUnit())
if (Player* summoner = summonerUnit->ToPlayer())
StartFollow(summoner, 0, nullptr, true, false);
break;
case EVENT_JENNY_MOVE_TO_FEZZIX:
me->SetWalk(true);
me->GetMotionMaster()->MovePoint(0, _fezzix);
_events.ScheduleEvent(EVENT_JENNY_DESPAWN, 7s);
break;
case EVENT_JENNY_DESPAWN:
me->DespawnOrUnsummon();
break;
}
}
}
void MoveInLineOfSight(Unit* who) override
{
if (who->GetEntry() == NPC_FEZZIX_GEARTWIST && me->IsWithinDistInMap(who, 15.0f))
{
if (TempSummon* s = me->ToTempSummon())
if (Unit* u = s->GetSummonerUnit())
if (Player* p = u->ToPlayer())
me->CastSpell(p, SPELL_GIVE_JENNY_CREDIT);
SetFollowComplete(true);
_fezzix = who->GetPosition();
_events.ScheduleEvent(EVENT_JENNY_MOVE_TO_FEZZIX, 1s);
}
}
private:
EventMap _events;
Position _fezzix;
};
/*######
## Quest 11590: Abduction
######*/
@@ -2059,4 +2145,5 @@ void AddSC_borean_tundra()
new npc_hidden_cultist();
RegisterSpellScript(spell_q11719_bloodspore_ruination_45997);
new npc_bloodmage_laurith();
RegisterCreatureAI(npc_jenny);
}

View File

@@ -23,6 +23,8 @@
#include "ScriptedGossip.h"
#include "SpellAuras.h"
#include "SpellInfo.h"
#include "SpellScript.h"
#include "SpellScriptLoader.h"
#include "Vehicle.h"
// Ours
@@ -240,6 +242,7 @@ enum overlordDrakuru
SPELL_THROW_BRIGHT_CRYSTAL = 54087,
SPELL_TELEPORT_EFFECT = 52096,
SPELL_SCOURGE_DISGUISE = 51966,
SPELL_SCOURGE_DISGUISE_INSTANT_CAST = 52192,
SPELL_BLIGHT_FOG = 54104,
SPELL_THROW_PORTAL_CRYSTAL = 54209,
SPELL_ARTHAS_PORTAL = 51807,
@@ -866,6 +869,51 @@ public:
}
};
enum ScourgeDisguiseInstability
{
SCOURGE_DISGUISE_FAILING_MESSAGE_1 = 28552, // Scourge Disguise Failing! Find a safe place!
SCOURGE_DISGUISE_FAILING_MESSAGE_2 = 28758, // Scourge Disguise Failing! Run for cover!
SCOURGE_DISGUISE_FAILING_MESSAGE_3 = 28759, // Scourge Disguise Failing! Hide quickly!
};
std::vector<uint32> const scourgeDisguiseTextIDs = { SCOURGE_DISGUISE_FAILING_MESSAGE_1, SCOURGE_DISGUISE_FAILING_MESSAGE_2, SCOURGE_DISGUISE_FAILING_MESSAGE_3 };
class spell_scourge_disguise_instability : public AuraScript
{
PrepareAuraScript(spell_scourge_disguise_instability);
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_SCOURGE_DISGUISE_EXPIRING });
}
void HandleApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
SetDuration(urand(3 * MINUTE * IN_MILLISECONDS, 5 * MINUTE * IN_MILLISECONDS));
}
void HandleRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
if (Unit* caster = GetCaster())
{
if (Player* player = caster->ToPlayer())
{
if (player->HasAnyAuras(SPELL_SCOURGE_DISGUISE, SPELL_SCOURGE_DISGUISE_INSTANT_CAST))
{
uint32 textId = Acore::Containers::SelectRandomContainerElement(scourgeDisguiseTextIDs);
player->Unit::Whisper(textId, player, true);
player->CastSpell(player, SPELL_SCOURGE_DISGUISE_EXPIRING, true);
}
}
}
}
void Register() override
{
OnEffectApply += AuraEffectApplyFn(spell_scourge_disguise_instability::HandleApply, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
OnEffectRemove += AuraEffectRemoveFn(spell_scourge_disguise_instability::HandleRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
}
};
void AddSC_zuldrak()
{
// Ours
@@ -880,4 +928,6 @@ void AddSC_zuldrak()
new npc_crusade_recruit();
new go_scourge_enclosure();
new npc_storm_cloud();
RegisterSpellScript(spell_scourge_disguise_instability);
}

View File

@@ -899,6 +899,74 @@ class spell_warr_retaliation : public AuraScript
}
};
// 29707 - Heroic Strike (Rank 10)
// 30324 - Heroic Strike (Rank 11)
// 47449 - Heroic Strike (Rank 12)
// 47450 - Heroic Strike (Rank 13)
enum DazeSpells
{
ICON_GENERIC_DAZE = 15,
SPELL_GENERIC_AFTERMATH = 18118,
};
class spell_warr_heroic_strike : public SpellScript
{
PrepareSpellScript(spell_warr_heroic_strike);
void HandleOnHit()
{
Unit* target = GetHitUnit();
if (!target)
return;
std::list<AuraEffect*> AuraEffectList = target->GetAuraEffectsByType(SPELL_AURA_MOD_DECREASE_SPEED);
bool bonusDamage = false;
for (AuraEffect* eff : AuraEffectList)
{
const SpellInfo* spellInfo = eff->GetSpellInfo();
if (!spellInfo)
continue;
// Warrior Spells: Piercing Howl or Dazed (29703)
if (spellInfo->SpellFamilyName == SPELLFAMILY_WARRIOR && (spellInfo->SpellFamilyFlags[1] & (0x20 | 0x200000)))
{
bonusDamage = true;
break;
}
// Generic Daze: icon 15 with mechanic daze or snare
if ((spellInfo->SpellIconID == ICON_GENERIC_DAZE)
&& ((spellInfo->Mechanic == MECHANIC_DAZE || spellInfo->HasEffectMechanic(MECHANIC_DAZE))
|| (spellInfo->Mechanic == MECHANIC_SNARE || spellInfo->HasEffectMechanic(MECHANIC_SNARE))
)
)
{
bonusDamage = true;
break;
}
if ((spellInfo->Id == SPELL_GENERIC_AFTERMATH)
|| (spellInfo->SpellFamilyName == SPELLFAMILY_MAGE && (spellInfo->SpellFamilyFlags[1] & 0x40)) // Blast Wave
|| (spellInfo->SpellFamilyName == SPELLFAMILY_PALADIN && (spellInfo->SpellFamilyFlags[2] & 0x4000)) // Avenger's Shield
)
{
bonusDamage = true;
break;
}
}
if (bonusDamage)
{
int32 damage = GetHitDamage();
AddPct(damage, 35); // "Causes ${0.35*$m1} additional damage against Dazed targets."
SetHitDamage(damage);
}
}
void Register() override
{
OnHit += SpellHitFn(spell_warr_heroic_strike::HandleOnHit);
}
};
void AddSC_warrior_spell_scripts()
{
RegisterSpellScript(spell_warr_mocking_blow);
@@ -925,4 +993,5 @@ void AddSC_warrior_spell_scripts()
RegisterSpellScript(spell_warr_vigilance);
RegisterSpellScript(spell_warr_vigilance_trigger);
RegisterSpellScript(spell_warr_t3_prot_8p_bonus);
RegisterSpellScript(spell_warr_heroic_strike);
}