mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-01-25 22:56:24 +00:00
Merge branch 'azerothcore:master' into Playerbot
This commit is contained in:
@@ -55,6 +55,12 @@ struct boss_moam : public BossAI
|
||||
{
|
||||
boss_moam(Creature* creature) : BossAI(creature, DATA_MOAM) {}
|
||||
|
||||
void InitializeAI() override
|
||||
{
|
||||
me->m_CombatDistance = 50.0f;
|
||||
Reset();
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
_Reset();
|
||||
|
||||
@@ -63,6 +63,13 @@ struct boss_sartura : public BossAI
|
||||
{
|
||||
boss_sartura(Creature* creature) : BossAI(creature, DATA_SARTURA) {}
|
||||
|
||||
void InitializeAI() override
|
||||
{
|
||||
me->m_CombatDistance = 60.f;
|
||||
me->m_SightDistance = 60.f;
|
||||
Reset();
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
_Reset();
|
||||
|
||||
@@ -15,368 +15,283 @@
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* ScriptData
|
||||
SDName: Boss_Twinemperors
|
||||
SD%Complete: 95
|
||||
SDComment:
|
||||
SDCategory: Temple of Ahn'Qiraj
|
||||
EndScriptData */
|
||||
|
||||
#include "Item.h"
|
||||
#include "Player.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "Spell.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "SpellScript.h"
|
||||
#include "TaskScheduler.h"
|
||||
#include "temple_of_ahnqiraj.h"
|
||||
|
||||
enum Spells
|
||||
{
|
||||
// Both
|
||||
SPELL_TWIN_EMPATHY = 1177,
|
||||
SPELL_TWIN_TELEPORT_1 = 800,
|
||||
SPELL_TWIN_TELEPORT_VISUAL = 26638,
|
||||
SPELL_HEAL_BROTHER = 7393,
|
||||
SPELL_TWIN_TELEPORT = 800, // CTRA watches for this spell to start its teleport timer
|
||||
SPELL_TWIN_TELEPORT_VISUAL = 26638, // visual
|
||||
SPELL_EXPLODEBUG = 804,
|
||||
SPELL_MUTATE_BUG = 802,
|
||||
SPELL_BERSERK = 26662,
|
||||
// Vek'lor
|
||||
SPELL_SHADOW_BOLT = 26006,
|
||||
SPELL_BLIZZARD = 26607,
|
||||
SPELL_FRENZY = 27897,
|
||||
SPELL_ARCANE_BURST = 568,
|
||||
SPELL_EXPLODE_BUG = 804,
|
||||
SPELL_TWIN_TELEPORT_0 = 799,
|
||||
// Vek'nilash
|
||||
SPELL_UPPERCUT = 26007,
|
||||
SPELL_UNBALANCING_STRIKE = 26613,
|
||||
SPELL_SHADOWBOLT = 26006,
|
||||
SPELL_BLIZZARD = 26607,
|
||||
SPELL_ARCANEBURST = 568,
|
||||
SPELL_BERSERK = 27680,
|
||||
SPELL_MUTATE_BUG = 802,
|
||||
// Bugs
|
||||
SPELL_VIRULENT_POISON_PROC = 22413
|
||||
};
|
||||
|
||||
enum Sound
|
||||
enum Actions
|
||||
{
|
||||
SOUND_VL_AGGRO = 8657, //8657 - Aggro - To Late
|
||||
SOUND_VL_KILL = 8658, //8658 - Kill - You will not
|
||||
SOUND_VL_DEATH = 8659, //8659 - Death
|
||||
SOUND_VN_DEATH = 8660, //8660 - Death - Feel
|
||||
SOUND_VN_AGGRO = 8661, //8661 - Aggro - Let none
|
||||
SOUND_VN_KILL = 8662, //8661 - Kill - your fate
|
||||
ACTION_START_INTRO = 0,
|
||||
ACTION_CANCEL_INTRO = 1,
|
||||
ACTION_AFTER_TELEPORT = 2
|
||||
};
|
||||
|
||||
enum Say
|
||||
{
|
||||
SAY_INTRO_0 = 0,
|
||||
SAY_INTRO_1 = 1,
|
||||
SAY_INTRO_2 = 2,
|
||||
SAY_KILL = 3,
|
||||
SAY_DEATH = 4,
|
||||
EMOTE_ENRAGE = 5,
|
||||
|
||||
EMOTE_MASTERS_EYE_AT = 0,
|
||||
};
|
||||
|
||||
enum Sounds
|
||||
{
|
||||
SOUND_VK_AGGRO = 8657,
|
||||
SOUND_VN_AGGRO = 8661
|
||||
};
|
||||
|
||||
enum Misc
|
||||
{
|
||||
PULL_RANGE = 50,
|
||||
ABUSE_BUG_RANGE = 20,
|
||||
VEKLOR_DIST = 20, // VL will not come to melee when attacking
|
||||
TELEPORTTIME = 30000
|
||||
GROUP_INTRO = 0,
|
||||
|
||||
NPC_QIRAJI_SCARAB = 15316,
|
||||
NPC_QIRAJI_SCORPION = 15317,
|
||||
|
||||
FACTION_HOSTILE = 16
|
||||
};
|
||||
|
||||
constexpr float veklorOrientationIntro = 2.241519f;
|
||||
constexpr float veknilashOrientationIntro = 1.144451f;
|
||||
|
||||
struct boss_twinemperorsAI : public BossAI
|
||||
{
|
||||
boss_twinemperorsAI(Creature* creature): BossAI(creature, DATA_TWIN_EMPERORS) { }
|
||||
|
||||
uint32 Heal_Timer;
|
||||
uint32 Teleport_Timer;
|
||||
bool AfterTeleport;
|
||||
uint32 AfterTeleportTimer;
|
||||
bool DontYellWhenDead;
|
||||
uint32 Abuse_Bug_Timer, BugsTimer;
|
||||
bool tspellcast;
|
||||
uint32 EnrageTimer;
|
||||
|
||||
virtual bool IAmVeklor() = 0;
|
||||
void Reset() override = 0;
|
||||
virtual void CastSpellOnBug(Creature* target) = 0;
|
||||
|
||||
void TwinReset()
|
||||
boss_twinemperorsAI(Creature* creature): BossAI(creature, DATA_TWIN_EMPERORS), _introDone(false)
|
||||
{
|
||||
Heal_Timer = 0; // first heal immediately when they get close together
|
||||
Teleport_Timer = TELEPORTTIME;
|
||||
AfterTeleport = false;
|
||||
tspellcast = false;
|
||||
AfterTeleportTimer = 0;
|
||||
Abuse_Bug_Timer = urand(10000, 17000);
|
||||
BugsTimer = 2000;
|
||||
me->ClearUnitState(UNIT_STATE_STUNNED);
|
||||
DontYellWhenDead = false;
|
||||
EnrageTimer = 15 * 60000;
|
||||
me->SetStandState(UNIT_STAND_STATE_KNEEL);
|
||||
|
||||
instance->HandleGameObject(instance->GetGuidData(AQ40_DOOR_1), true);
|
||||
}
|
||||
|
||||
Creature* GetOtherBoss()
|
||||
{
|
||||
return ObjectAccessor::GetCreature(*me, instance->GetGuidData(IAmVeklor() ? DATA_VEKNILASH : DATA_VEKLOR));
|
||||
}
|
||||
|
||||
void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override
|
||||
{
|
||||
Unit* pOtherBoss = GetOtherBoss();
|
||||
if (pOtherBoss)
|
||||
{
|
||||
float dPercent = ((float)damage) / ((float)me->GetMaxHealth());
|
||||
int odmg = (int)(dPercent * ((float)pOtherBoss->GetMaxHealth()));
|
||||
int ohealth = pOtherBoss->GetHealth() - odmg;
|
||||
pOtherBoss->SetHealth(ohealth > 0 ? ohealth : 0);
|
||||
if (ohealth <= 0)
|
||||
_scheduler.SetValidator([this]
|
||||
{
|
||||
pOtherBoss->setDeathState(JUST_DIED);
|
||||
pOtherBoss->SetDynamicFlag(UNIT_DYNFLAG_LOOTABLE);
|
||||
return !me->HasUnitState(UNIT_STATE_CASTING);
|
||||
});
|
||||
}
|
||||
|
||||
Creature* GetTwin()
|
||||
{
|
||||
return instance->GetCreature(IAmVeklor() ? DATA_VEKNILASH : DATA_VEKLOR);
|
||||
}
|
||||
|
||||
void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType, SpellSchoolMask) override
|
||||
{
|
||||
if (attacker)
|
||||
{
|
||||
if (attacker->GetEntry() == NPC_VEKLOR || attacker->GetEntry() == NPC_VEKNILASH)
|
||||
{
|
||||
me->LowerPlayerDamageReq(damage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Creature* twin = GetTwin())
|
||||
{
|
||||
float dmgPct = damage / (float)me->GetMaxHealth();
|
||||
int32 actualDmg = dmgPct * twin->GetMaxHealth();
|
||||
twin->CastCustomSpell(twin, SPELL_TWIN_EMPATHY, &actualDmg, nullptr, nullptr, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
void KilledUnit(Unit* victim) override
|
||||
{
|
||||
Creature* pOtherBoss = GetOtherBoss();
|
||||
if (pOtherBoss)
|
||||
{
|
||||
pOtherBoss->SetHealth(0);
|
||||
pOtherBoss->setDeathState(JUST_DIED);
|
||||
pOtherBoss->SetDynamicFlag(UNIT_DYNFLAG_LOOTABLE);
|
||||
CAST_AI(boss_twinemperorsAI, pOtherBoss->AI())->DontYellWhenDead = true;
|
||||
}
|
||||
if (!DontYellWhenDead) // I hope AI is not threaded
|
||||
DoPlaySoundToSet(me, IAmVeklor() ? SOUND_VL_DEATH : SOUND_VN_DEATH);
|
||||
|
||||
instance->HandleGameObject(instance->GetGuidData(AQ40_DOOR_1), true);
|
||||
instance->HandleGameObject(instance->GetGuidData(AQ40_DOOR_2), true);
|
||||
if (victim && victim->GetTypeId() == TYPEID_PLAYER)
|
||||
Talk(SAY_KILL);
|
||||
}
|
||||
|
||||
void KilledUnit(Unit* /*victim*/) override
|
||||
void EnterEvadeMode(EvadeReason why) override
|
||||
{
|
||||
DoPlaySoundToSet(me, IAmVeklor() ? SOUND_VL_KILL : SOUND_VN_KILL);
|
||||
BossAI::EnterEvadeMode(why);
|
||||
|
||||
if (Creature* twin = GetTwin())
|
||||
if (!twin->IsInEvadeMode())
|
||||
twin->AI()->EnterEvadeMode(why);
|
||||
|
||||
_scheduler.CancelAll();
|
||||
}
|
||||
|
||||
void JustDied(Unit* killer) override
|
||||
{
|
||||
if (Creature* twin = GetTwin())
|
||||
if (twin->IsAlive())
|
||||
Unit::Kill(me, twin);
|
||||
|
||||
Talk(SAY_DEATH);
|
||||
|
||||
BossAI::JustDied(killer);
|
||||
}
|
||||
|
||||
void DoAction(int32 action) override
|
||||
{
|
||||
if (action == ACTION_CANCEL_INTRO)
|
||||
{
|
||||
_introDone = true;
|
||||
_scheduler.CancelGroup(GROUP_INTRO);
|
||||
return;
|
||||
}
|
||||
|
||||
if (action == ACTION_AFTER_TELEPORT)
|
||||
{
|
||||
DoResetThreat();
|
||||
me->SetReactState(REACT_PASSIVE);
|
||||
DoCastSelf(SPELL_TWIN_TELEPORT_VISUAL, true);
|
||||
_scheduler.DelayAll(2300ms);
|
||||
_scheduler.Schedule(2s, [this](TaskContext /*context*/)
|
||||
{
|
||||
me->SetReactState(REACT_AGGRESSIVE);
|
||||
me->SetControlled(false, UNIT_STATE_ROOT);
|
||||
if (Unit* victim = me->SelectNearestTarget())
|
||||
{
|
||||
me->AddThreat(victim, 200000.f);
|
||||
AttackStart(victim);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (action != ACTION_START_INTRO)
|
||||
return;
|
||||
|
||||
_scheduler.Schedule(5s, [this](TaskContext /*context*/)
|
||||
{
|
||||
me->SetStandState(UNIT_STAND_STATE_STAND);
|
||||
me->LoadEquipment(1, true);
|
||||
});
|
||||
|
||||
if (IAmVeklor())
|
||||
{
|
||||
_scheduler
|
||||
.Schedule(12s, GROUP_INTRO, [this](TaskContext /*context*/)
|
||||
{
|
||||
Talk(SAY_INTRO_0);
|
||||
})
|
||||
.Schedule(20s, GROUP_INTRO, [this](TaskContext /*context*/)
|
||||
{
|
||||
Talk(SAY_INTRO_1);
|
||||
})
|
||||
.Schedule(28s, GROUP_INTRO, [this](TaskContext /*context*/)
|
||||
{
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_ROAR);
|
||||
})
|
||||
.Schedule(30s, GROUP_INTRO, [this](TaskContext /*context*/)
|
||||
{
|
||||
me->SetFacingTo(veklorOrientationIntro);
|
||||
Talk(SAY_INTRO_2);
|
||||
})
|
||||
.Schedule(33s, GROUP_INTRO, [this](TaskContext /*context*/)
|
||||
{
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_POINT);
|
||||
_introDone = true;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_scheduler
|
||||
.Schedule(17s, GROUP_INTRO, [this](TaskContext /*context*/)
|
||||
{
|
||||
Talk(SAY_INTRO_0);
|
||||
})
|
||||
.Schedule(23s, GROUP_INTRO, [this](TaskContext /*context*/)
|
||||
{
|
||||
Talk(SAY_INTRO_1);
|
||||
})
|
||||
.Schedule(28s, GROUP_INTRO, [this](TaskContext /*context*/)
|
||||
{
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_ROAR);
|
||||
})
|
||||
.Schedule(32s, GROUP_INTRO, [this](TaskContext /*context*/)
|
||||
{
|
||||
me->SetFacingTo(veknilashOrientationIntro);
|
||||
Talk(SAY_INTRO_2);
|
||||
})
|
||||
.Schedule(33s, GROUP_INTRO, [this](TaskContext /*context*/)
|
||||
{
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_POINT);
|
||||
_introDone = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void EnterCombat(Unit* who) override
|
||||
{
|
||||
DoZoneInCombat();
|
||||
Creature* pOtherBoss = GetOtherBoss();
|
||||
if (pOtherBoss)
|
||||
BossAI::EnterCombat(who);
|
||||
|
||||
if (!_introDone)
|
||||
{
|
||||
/// @todo we should activate the other boss location so he can start attackning even if nobody
|
||||
// is near I dont know how to do that
|
||||
ScriptedAI* otherAI = CAST_AI(ScriptedAI, pOtherBoss->AI());
|
||||
if (!pOtherBoss->IsInCombat())
|
||||
DoAction(ACTION_CANCEL_INTRO);
|
||||
if (Creature* twin = GetTwin())
|
||||
twin->AI()->DoAction(ACTION_CANCEL_INTRO);
|
||||
}
|
||||
|
||||
if (Creature* twin = GetTwin())
|
||||
if (!twin->IsInCombat())
|
||||
twin->AI()->AttackStart(who);
|
||||
|
||||
_scheduler
|
||||
.Schedule(15min, [this](TaskContext /*context*/)
|
||||
{
|
||||
DoPlaySoundToSet(me, IAmVeklor() ? SOUND_VL_AGGRO : SOUND_VN_AGGRO);
|
||||
otherAI->AttackStart(who);
|
||||
otherAI->DoZoneInCombat();
|
||||
}
|
||||
}
|
||||
|
||||
instance->HandleGameObject(instance->GetGuidData(AQ40_DOOR_1), false);
|
||||
}
|
||||
|
||||
void SpellHit(Unit* caster, SpellInfo const* entry) override
|
||||
{
|
||||
if (caster == me)
|
||||
return;
|
||||
|
||||
Creature* pOtherBoss = GetOtherBoss();
|
||||
if (entry->Id != SPELL_HEAL_BROTHER || !pOtherBoss)
|
||||
return;
|
||||
|
||||
// add health so we keep same percentage for both brothers
|
||||
uint32 mytotal = me->GetMaxHealth(), histotal = pOtherBoss->GetMaxHealth();
|
||||
float mult = ((float)mytotal) / ((float)histotal);
|
||||
if (mult < 1)
|
||||
mult = 1.0f / mult;
|
||||
#define HEAL_BROTHER_AMOUNT 30000.0f
|
||||
uint32 largerAmount = (uint32)((HEAL_BROTHER_AMOUNT * mult) - HEAL_BROTHER_AMOUNT);
|
||||
|
||||
if (mytotal > histotal)
|
||||
{
|
||||
uint32 h = me->GetHealth() + largerAmount;
|
||||
me->SetHealth(std::min(mytotal, h));
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32 h = pOtherBoss->GetHealth() + largerAmount;
|
||||
pOtherBoss->SetHealth(std::min(histotal, h));
|
||||
}
|
||||
}
|
||||
|
||||
void TryHealBrother(uint32 diff)
|
||||
{
|
||||
if (IAmVeklor()) // this spell heals caster and the other brother so let VN cast it
|
||||
return;
|
||||
|
||||
if (Heal_Timer <= diff)
|
||||
{
|
||||
Unit* pOtherBoss = GetOtherBoss();
|
||||
if (pOtherBoss && pOtherBoss->IsWithinDist(me, 60))
|
||||
{
|
||||
DoCast(pOtherBoss, SPELL_HEAL_BROTHER);
|
||||
Heal_Timer = 1000;
|
||||
}
|
||||
}
|
||||
else Heal_Timer -= diff;
|
||||
}
|
||||
|
||||
void TeleportToMyBrother()
|
||||
{
|
||||
Teleport_Timer = TELEPORTTIME;
|
||||
|
||||
if (IAmVeklor())
|
||||
return; // mechanics handled by veknilash so they teleport exactly at the same time and to correct coordinates
|
||||
|
||||
Creature* pOtherBoss = GetOtherBoss();
|
||||
if (pOtherBoss)
|
||||
{
|
||||
//me->Yell("Teleporting ...", LANG_UNIVERSAL);
|
||||
Position thisPos;
|
||||
thisPos.Relocate(me);
|
||||
Position otherPos;
|
||||
otherPos.Relocate(pOtherBoss);
|
||||
pOtherBoss->SetPosition(thisPos);
|
||||
me->SetPosition(otherPos);
|
||||
|
||||
SetAfterTeleport();
|
||||
CAST_AI(boss_twinemperorsAI, pOtherBoss->AI())->SetAfterTeleport();
|
||||
}
|
||||
}
|
||||
|
||||
void SetAfterTeleport()
|
||||
{
|
||||
me->InterruptNonMeleeSpells(false);
|
||||
DoStopAttack();
|
||||
DoResetThreat();
|
||||
DoCast(me, SPELL_TWIN_TELEPORT_VISUAL);
|
||||
me->AddUnitState(UNIT_STATE_STUNNED);
|
||||
AfterTeleport = true;
|
||||
AfterTeleportTimer = 2000;
|
||||
tspellcast = false;
|
||||
}
|
||||
|
||||
bool TryActivateAfterTTelep(uint32 diff)
|
||||
{
|
||||
if (AfterTeleport)
|
||||
{
|
||||
if (!tspellcast)
|
||||
{
|
||||
me->ClearUnitState(UNIT_STATE_STUNNED);
|
||||
DoCast(me, SPELL_TWIN_TELEPORT);
|
||||
me->AddUnitState(UNIT_STATE_STUNNED);
|
||||
}
|
||||
|
||||
tspellcast = true;
|
||||
|
||||
if (AfterTeleportTimer <= diff)
|
||||
{
|
||||
AfterTeleport = false;
|
||||
me->ClearUnitState(UNIT_STATE_STUNNED);
|
||||
if (Unit* nearu = me->SelectNearestTarget(100))
|
||||
if (IAmVeklor())
|
||||
{
|
||||
//DoYell(nearu->GetName(), LANG_UNIVERSAL);
|
||||
AttackStart(nearu);
|
||||
me->AddThreat(nearu, 10000);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AfterTeleportTimer -= diff;
|
||||
// update important timers which would otherwise get skipped
|
||||
if (EnrageTimer > diff)
|
||||
EnrageTimer -= diff;
|
||||
else
|
||||
EnrageTimer = 0;
|
||||
if (Teleport_Timer > diff)
|
||||
Teleport_Timer -= diff;
|
||||
else
|
||||
Teleport_Timer = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void MoveInLineOfSight(Unit* who) override
|
||||
{
|
||||
if (!who || me->GetVictim())
|
||||
return;
|
||||
|
||||
if (me->CanCreatureAttack(who))
|
||||
{
|
||||
if (me->IsWithinDistInMap(who, PULL_RANGE, true, false) && me->GetDistanceZ(who) <= /*CREATURE_Z_ATTACK_RANGE*/7 /*there are stairs*/)
|
||||
{
|
||||
//if (who->HasStealthAura())
|
||||
// who->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH);
|
||||
AttackStart(who);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Creature* RespawnNearbyBugsAndGetOne()
|
||||
{
|
||||
std::list<Creature*> lUnitList;
|
||||
me->GetCreatureListWithEntryInGrid(lUnitList, 15316, 150.0f);
|
||||
me->GetCreatureListWithEntryInGrid(lUnitList, 15317, 150.0f);
|
||||
|
||||
if (lUnitList.empty())
|
||||
return nullptr;
|
||||
|
||||
Creature* nearb = nullptr;
|
||||
|
||||
for (std::list<Creature*>::const_iterator iter = lUnitList.begin(); iter != lUnitList.end(); ++iter)
|
||||
{
|
||||
Creature* c = *iter;
|
||||
if (c)
|
||||
{
|
||||
if (c->isDead())
|
||||
{
|
||||
c->Respawn();
|
||||
c->SetFaction(FACTION_CREATURE);
|
||||
c->RemoveAllAuras();
|
||||
}
|
||||
if (c->IsWithinDistInMap(me, ABUSE_BUG_RANGE))
|
||||
{
|
||||
if (!nearb || (rand() % 4) == 0)
|
||||
nearb = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nearb;
|
||||
}
|
||||
|
||||
void HandleBugs(uint32 diff)
|
||||
{
|
||||
if (BugsTimer < diff || Abuse_Bug_Timer <= diff)
|
||||
{
|
||||
Creature* c = RespawnNearbyBugsAndGetOne();
|
||||
if (Abuse_Bug_Timer <= diff)
|
||||
{
|
||||
if (c)
|
||||
{
|
||||
CastSpellOnBug(c);
|
||||
Abuse_Bug_Timer = urand(10000, 17000);
|
||||
DoCastSelf(SPELL_FRENZY, true);
|
||||
Talk(EMOTE_ENRAGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
Abuse_Bug_Timer = 1000;
|
||||
}
|
||||
}
|
||||
else
|
||||
DoCastSelf(SPELL_BERSERK, true);
|
||||
})
|
||||
.Schedule(3600ms, [this](TaskContext context) // according to sniffs it should be casted by both emperors.
|
||||
{
|
||||
Abuse_Bug_Timer -= diff;
|
||||
}
|
||||
BugsTimer = 2000;
|
||||
}
|
||||
else
|
||||
{
|
||||
BugsTimer -= diff;
|
||||
Abuse_Bug_Timer -= diff;
|
||||
}
|
||||
if (Creature* twin = GetTwin())
|
||||
{
|
||||
if (me->IsWithinDist(twin, 60.f))
|
||||
DoCast(twin, SPELL_HEAL_BROTHER, true);
|
||||
}
|
||||
|
||||
context.Repeat();
|
||||
});
|
||||
}
|
||||
|
||||
void CheckEnrage(uint32 diff)
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
if (EnrageTimer <= diff)
|
||||
{
|
||||
if (!me->IsNonMeleeSpellCast(true))
|
||||
if (!UpdateVictim() && _introDone)
|
||||
return;
|
||||
|
||||
_scheduler.Update(diff, [this]
|
||||
{
|
||||
DoCast(me, SPELL_BERSERK);
|
||||
EnrageTimer = 60 * 60000;
|
||||
}
|
||||
else EnrageTimer = 0;
|
||||
}
|
||||
else EnrageTimer -= diff;
|
||||
if (!IAmVeklor())
|
||||
DoMeleeAttackIfReady();
|
||||
});
|
||||
}
|
||||
|
||||
virtual bool IAmVeklor() = 0;
|
||||
|
||||
protected:
|
||||
TaskScheduler _scheduler;
|
||||
bool _introDone;
|
||||
};
|
||||
|
||||
struct boss_veknilash : public boss_twinemperorsAI
|
||||
@@ -385,192 +300,172 @@ struct boss_veknilash : public boss_twinemperorsAI
|
||||
|
||||
bool IAmVeklor() override { return false; }
|
||||
|
||||
uint32 UpperCut_Timer;
|
||||
uint32 UnbalancingStrike_Timer;
|
||||
uint32 Scarabs_Timer;
|
||||
int Rand;
|
||||
int RandX;
|
||||
int RandY;
|
||||
|
||||
Creature* Summoned;
|
||||
|
||||
void Reset() override
|
||||
void EnterCombat(Unit* who) override
|
||||
{
|
||||
TwinReset();
|
||||
UpperCut_Timer = urand(14000, 29000);
|
||||
UnbalancingStrike_Timer = urand(8000, 18000);
|
||||
Scarabs_Timer = urand(7000, 14000);
|
||||
boss_twinemperorsAI::EnterCombat(who);
|
||||
|
||||
//Added. Can be removed if its included in DB.
|
||||
me->ApplySpellImmune(0, IMMUNITY_DAMAGE, SPELL_SCHOOL_MASK_MAGIC, true);
|
||||
}
|
||||
DoPlaySoundToSet(me, SOUND_VN_AGGRO);
|
||||
|
||||
void CastSpellOnBug(Creature* target) override
|
||||
{
|
||||
target->SetFaction(FACTION_MONSTER);
|
||||
target->AI()->AttackStart(me->GetThreatMgr().getHostileTarget());
|
||||
target->AddAura(SPELL_MUTATE_BUG, target);
|
||||
target->SetFullHealth();
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
//Return since we have no target
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
if (!TryActivateAfterTTelep(diff))
|
||||
return;
|
||||
|
||||
//UnbalancingStrike_Timer
|
||||
if (UnbalancingStrike_Timer <= diff)
|
||||
{
|
||||
DoCastVictim(SPELL_UNBALANCING_STRIKE);
|
||||
UnbalancingStrike_Timer = 8000 + rand() % 12000;
|
||||
}
|
||||
else UnbalancingStrike_Timer -= diff;
|
||||
|
||||
if (UpperCut_Timer <= diff)
|
||||
{
|
||||
Unit* randomMelee = SelectTarget(SelectTargetMethod::Random, 0, NOMINAL_MELEE_RANGE, true);
|
||||
if (randomMelee)
|
||||
DoCast(randomMelee, SPELL_UPPERCUT);
|
||||
UpperCut_Timer = 15000 + rand() % 15000;
|
||||
}
|
||||
else UpperCut_Timer -= diff;
|
||||
|
||||
HandleBugs(diff);
|
||||
|
||||
//Heal brother when 60yrds close
|
||||
TryHealBrother(diff);
|
||||
|
||||
//Teleporting to brother
|
||||
if (Teleport_Timer <= diff)
|
||||
{
|
||||
TeleportToMyBrother();
|
||||
}
|
||||
else Teleport_Timer -= diff;
|
||||
|
||||
CheckEnrage(diff);
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
_scheduler
|
||||
.Schedule(14s, [this](TaskContext context)
|
||||
{
|
||||
DoCastRandomTarget(SPELL_UPPERCUT, 0, me->GetMeleeReach(), true);
|
||||
context.Repeat(4s, 15s);
|
||||
})
|
||||
.Schedule(12s, [this](TaskContext context)
|
||||
{
|
||||
DoCastVictim(SPELL_UNBALANCING_STRIKE);
|
||||
context.Repeat(8s, 20s);
|
||||
})
|
||||
.Schedule(16s, [this](TaskContext context)
|
||||
{
|
||||
DoCastAOE(SPELL_MUTATE_BUG);
|
||||
context.Repeat(10s, 20s);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
struct boss_veklor : public boss_twinemperorsAI
|
||||
{
|
||||
boss_veklor(Creature* creature) : boss_twinemperorsAI(creature) { }
|
||||
boss_veklor(Creature* creature) : boss_twinemperorsAI(creature)
|
||||
{
|
||||
me->SetFloatValue(UNIT_FIELD_COMBATREACH, 45.f);
|
||||
}
|
||||
|
||||
bool IAmVeklor() override { return true; }
|
||||
|
||||
uint32 ShadowBolt_Timer;
|
||||
uint32 Blizzard_Timer;
|
||||
uint32 ArcaneBurst_Timer;
|
||||
uint32 Scorpions_Timer;
|
||||
int Rand;
|
||||
int RandX;
|
||||
|
||||
Creature* Summoned;
|
||||
|
||||
void Reset() override
|
||||
void EnterCombat(Unit* who) override
|
||||
{
|
||||
TwinReset();
|
||||
ShadowBolt_Timer = 0;
|
||||
Blizzard_Timer = urand(15000, 20000);
|
||||
ArcaneBurst_Timer = 1000;
|
||||
Scorpions_Timer = urand(7000, 14000);
|
||||
boss_twinemperorsAI::EnterCombat(who);
|
||||
|
||||
//Added. Can be removed if its included in DB.
|
||||
me->ApplySpellImmune(0, IMMUNITY_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, true);
|
||||
DoPlaySoundToSet(me, SOUND_VK_AGGRO);
|
||||
|
||||
_scheduler
|
||||
.Schedule(4s, [this](TaskContext context)
|
||||
{
|
||||
DoCastVictim(SPELL_SHADOW_BOLT);
|
||||
context.Repeat(2500ms);
|
||||
})
|
||||
.Schedule(10s, 15s, [this](TaskContext context)
|
||||
{
|
||||
DoCastRandomTarget(SPELL_BLIZZARD, 0, 45.f);
|
||||
context.Repeat(5s, 12s);
|
||||
})
|
||||
.Schedule(1s, [this](TaskContext context)
|
||||
{
|
||||
if (me->SelectNearestPlayer(NOMINAL_MELEE_RANGE))
|
||||
DoCastAOE(SPELL_ARCANE_BURST);
|
||||
context.Repeat(7s, 12s);
|
||||
})
|
||||
.Schedule(30s, 40s, [this](TaskContext context)
|
||||
{
|
||||
DoCastSelf(SPELL_TWIN_TELEPORT_0);
|
||||
context.Repeat();
|
||||
})
|
||||
.Schedule(5s, [this](TaskContext context)
|
||||
{
|
||||
DoCastAOE(SPELL_EXPLODE_BUG);
|
||||
context.Repeat(4500ms, 10s);
|
||||
});
|
||||
}
|
||||
|
||||
void CastSpellOnBug(Creature* target) override
|
||||
void SpellHit(Unit* /*caster*/, SpellInfo const* spellInfo) override
|
||||
{
|
||||
target->SetFaction(FACTION_MONSTER);
|
||||
target->AddAura(SPELL_EXPLODEBUG, target);
|
||||
target->SetFullHealth();
|
||||
if (spellInfo->Id == SPELL_TWIN_TELEPORT_0)
|
||||
{
|
||||
if (Creature* veknilash = GetTwin())
|
||||
{
|
||||
DoCastSelf(SPELL_TWIN_TELEPORT_1, true);
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
|
||||
Position veklorPos = me->GetPosition();
|
||||
Position veknilashPos = veknilash->GetPosition();
|
||||
me->NearTeleportTo(veknilashPos);
|
||||
|
||||
veknilash->CastSpell(veknilash, SPELL_TWIN_TELEPORT_1, true);
|
||||
veknilash->SetControlled(true, UNIT_STATE_ROOT);
|
||||
veknilash->NearTeleportTo(veklorPos);
|
||||
|
||||
veknilash->AI()->DoAction(ACTION_AFTER_TELEPORT);
|
||||
DoAction(ACTION_AFTER_TELEPORT);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class at_twin_emperors : public OnlyOnceAreaTriggerScript
|
||||
{
|
||||
public:
|
||||
at_twin_emperors() : OnlyOnceAreaTriggerScript("at_twin_emperors") { }
|
||||
|
||||
bool _OnTrigger(Player* player, const AreaTrigger* /*at*/) override
|
||||
{
|
||||
if (InstanceScript* instance = player->GetInstanceScript())
|
||||
{
|
||||
if (instance->GetBossState(DATA_TWIN_EMPERORS) != DONE)
|
||||
{
|
||||
if (Creature* mastersEye = instance->GetCreature(DATA_MASTERS_EYE))
|
||||
{
|
||||
mastersEye->AI()->Talk(EMOTE_MASTERS_EYE_AT, player);
|
||||
mastersEye->DespawnOrUnsummon(11000);
|
||||
mastersEye->m_Events.AddEventAtOffset([mastersEye, player]()
|
||||
{
|
||||
mastersEye->SetFacingToObject(player);
|
||||
}, 3s);
|
||||
}
|
||||
|
||||
if (Creature* veklor = instance->GetCreature(DATA_VEKLOR))
|
||||
veklor->AI()->DoAction(ACTION_START_INTRO);
|
||||
|
||||
if (Creature* veknilash = instance->GetCreature(DATA_VEKNILASH))
|
||||
veknilash->AI()->DoAction(ACTION_START_INTRO);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class spell_mutate_explode_bug : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_mutate_explode_bug);
|
||||
|
||||
void FilterTargets(std::list<WorldObject*>& targets)
|
||||
{
|
||||
targets.remove_if([&](WorldObject const* target) -> bool
|
||||
{
|
||||
if (target->GetEntry() != NPC_QIRAJI_SCARAB && target->GetEntry() != NPC_QIRAJI_SCORPION)
|
||||
return true;
|
||||
if (Creature const* creature = target->ToCreature())
|
||||
if (creature->HasAura(SPELL_EXPLODE_BUG) || creature->HasAura(SPELL_MUTATE_BUG))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
Acore::Containers::RandomResize(targets, 1);
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
void HandleOnHit()
|
||||
{
|
||||
//Return since we have no target
|
||||
if (!UpdateVictim())
|
||||
if (!GetHitUnit())
|
||||
return;
|
||||
|
||||
// reset arcane burst after teleport - we need to do this because
|
||||
// when VL jumps to VN's location there will be a warrior who will get only 2s to run away
|
||||
// which is almost impossible
|
||||
if (AfterTeleport)
|
||||
ArcaneBurst_Timer = 5000;
|
||||
if (!TryActivateAfterTTelep(diff))
|
||||
Creature* target = GetHitUnit()->ToCreature();
|
||||
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
//ShadowBolt_Timer
|
||||
if (ShadowBolt_Timer <= diff)
|
||||
{
|
||||
if (!me->IsWithinDist(me->GetVictim(), 45.0f))
|
||||
me->GetMotionMaster()->MoveChase(me->GetVictim(), VEKLOR_DIST, 0);
|
||||
else
|
||||
DoCastVictim(SPELL_SHADOWBOLT);
|
||||
ShadowBolt_Timer = 2000;
|
||||
}
|
||||
else ShadowBolt_Timer -= diff;
|
||||
|
||||
//Blizzard_Timer
|
||||
if (Blizzard_Timer <= diff)
|
||||
{
|
||||
Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 45, true);
|
||||
if (target)
|
||||
{
|
||||
DoCast(target, SPELL_BLIZZARD);
|
||||
}
|
||||
Blizzard_Timer = 15000 + rand() % 15000;
|
||||
}
|
||||
else Blizzard_Timer -= diff;
|
||||
|
||||
if (ArcaneBurst_Timer <= diff)
|
||||
{
|
||||
Unit* mvic;
|
||||
if ((mvic = SelectTarget(SelectTargetMethod::MaxDistance, 0, NOMINAL_MELEE_RANGE, true)) != nullptr)
|
||||
{
|
||||
DoCast(mvic, SPELL_ARCANEBURST);
|
||||
ArcaneBurst_Timer = 5000;
|
||||
}
|
||||
}
|
||||
else ArcaneBurst_Timer -= diff;
|
||||
|
||||
HandleBugs(diff);
|
||||
|
||||
//Heal brother when 60yrds close
|
||||
TryHealBrother(diff);
|
||||
|
||||
//Teleporting to brother
|
||||
if (Teleport_Timer <= diff)
|
||||
{
|
||||
TeleportToMyBrother();
|
||||
}
|
||||
else Teleport_Timer -= diff;
|
||||
|
||||
CheckEnrage(diff);
|
||||
|
||||
//VL doesn't melee
|
||||
//DoMeleeAttackIfReady();
|
||||
if (m_scriptSpellId == SPELL_MUTATE_BUG)
|
||||
target->CastSpell(target, SPELL_VIRULENT_POISON_PROC, true);
|
||||
target->SetFaction(FACTION_HOSTILE);
|
||||
target->SetReactState(REACT_AGGRESSIVE);
|
||||
target->SetInCombatWithZone();
|
||||
}
|
||||
|
||||
void AttackStart(Unit* who) override
|
||||
void Register() override
|
||||
{
|
||||
if (!who)
|
||||
return;
|
||||
|
||||
if (who->isTargetableForAttack())
|
||||
{
|
||||
// VL doesn't melee
|
||||
if (me->Attack(who, false))
|
||||
{
|
||||
me->GetMotionMaster()->MoveChase(who, VEKLOR_DIST, 0);
|
||||
me->AddThreat(who, 0.0f);
|
||||
}
|
||||
}
|
||||
OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_mutate_explode_bug::FilterTargets, EFFECT_ALL, TARGET_UNIT_SRC_AREA_ENTRY);
|
||||
OnHit += SpellHitFn(spell_mutate_explode_bug::HandleOnHit);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -578,4 +473,6 @@ void AddSC_boss_twinemperors()
|
||||
{
|
||||
RegisterTempleOfAhnQirajCreatureAI(boss_veknilash);
|
||||
RegisterTempleOfAhnQirajCreatureAI(boss_veklor);
|
||||
new at_twin_emperors();
|
||||
RegisterSpellScript(spell_mutate_explode_bug);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,10 @@ ObjectData const creatureData[] =
|
||||
{
|
||||
{ NPC_SARTURA, DATA_SARTURA },
|
||||
{ NPC_EYE_OF_CTHUN, DATA_EYE_OF_CTHUN },
|
||||
{ NPC_OURO_SPAWNER, DATA_OURO_SPAWNER }
|
||||
{ NPC_OURO_SPAWNER, DATA_OURO_SPAWNER },
|
||||
{ NPC_MASTERS_EYE, DATA_MASTERS_EYE },
|
||||
{ NPC_VEKLOR, DATA_VEKLOR },
|
||||
{ NPC_VEKNILASH, DATA_VEKNILASH }
|
||||
};
|
||||
|
||||
class instance_temple_of_ahnqiraj : public InstanceMapScript
|
||||
@@ -46,15 +49,10 @@ public:
|
||||
SetBossNumber(MAX_BOSS_NUMBER);
|
||||
}
|
||||
|
||||
//If Vem is dead...
|
||||
bool IsBossDied[3];
|
||||
|
||||
ObjectGuid SkeramGUID;
|
||||
ObjectGuid VemGUID;
|
||||
ObjectGuid KriGUID;
|
||||
ObjectGuid YaujGUID;
|
||||
ObjectGuid VeklorGUID;
|
||||
ObjectGuid VeknilashGUID;
|
||||
ObjectGuid ViscidusGUID;
|
||||
ObjectGuid CThunGUID;
|
||||
GuidVector CThunGraspGUIDs;
|
||||
@@ -65,9 +63,6 @@ public:
|
||||
|
||||
void Initialize() override
|
||||
{
|
||||
IsBossDied[0] = false;
|
||||
IsBossDied[1] = false;
|
||||
IsBossDied[2] = false;
|
||||
BugTrioDeathCount = 0;
|
||||
CthunPhase = 0;
|
||||
}
|
||||
@@ -92,16 +87,6 @@ public:
|
||||
case NPC_YAUJ:
|
||||
YaujGUID = creature->GetGUID();
|
||||
break;
|
||||
case NPC_VEKLOR:
|
||||
VeklorGUID = creature->GetGUID();
|
||||
if (!creature->IsAlive())
|
||||
{
|
||||
HandleGameObject(doorGUIDs[1], true);
|
||||
}
|
||||
break;
|
||||
case NPC_VEKNILASH:
|
||||
VeknilashGUID = creature->GetGUID();
|
||||
break;
|
||||
case NPC_VISCIDUS:
|
||||
ViscidusGUID = creature->GetGUID();
|
||||
break;
|
||||
@@ -109,6 +94,10 @@ public:
|
||||
if (GetBossState(DATA_OURO) != DONE)
|
||||
creature->Respawn();
|
||||
break;
|
||||
case NPC_MASTERS_EYE:
|
||||
if (GetBossState(DATA_TWIN_EMPERORS) != DONE)
|
||||
creature->Respawn(true);
|
||||
break;
|
||||
case NPC_CTHUN:
|
||||
CThunGUID = creature->GetGUID();
|
||||
if (!creature->IsAlive())
|
||||
@@ -138,7 +127,7 @@ public:
|
||||
break;
|
||||
case AQ40_DOOR_2:
|
||||
doorGUIDs[1] = go->GetGUID();
|
||||
if (Creature* veklor = instance->GetCreature(VeklorGUID))
|
||||
if (Creature* veklor = GetCreature(DATA_VEKLOR))
|
||||
{
|
||||
if (!veklor->IsAlive())
|
||||
{
|
||||
@@ -177,16 +166,6 @@ public:
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case DATA_VEKLORISDEAD:
|
||||
if (IsBossDied[1])
|
||||
return 1;
|
||||
break;
|
||||
|
||||
case DATA_VEKNILASHISDEAD:
|
||||
if (IsBossDied[2])
|
||||
return 1;
|
||||
break;
|
||||
|
||||
case DATA_BUG_TRIO_DEATH:
|
||||
return BugTrioDeathCount;
|
||||
|
||||
@@ -208,10 +187,6 @@ public:
|
||||
return KriGUID;
|
||||
case DATA_YAUJ:
|
||||
return YaujGUID;
|
||||
case DATA_VEKLOR:
|
||||
return VeklorGUID;
|
||||
case DATA_VEKNILASH:
|
||||
return VeknilashGUID;
|
||||
case DATA_VISCIDUS:
|
||||
return ViscidusGUID;
|
||||
case AQ40_DOOR_1:
|
||||
@@ -234,12 +209,6 @@ public:
|
||||
else
|
||||
BugTrioDeathCount = 0;
|
||||
break;
|
||||
case DATA_VEKLOR_DEATH:
|
||||
IsBossDied[1] = true;
|
||||
break;
|
||||
case DATA_VEKNILASH_DEATH:
|
||||
IsBossDied[2] = true;
|
||||
break;
|
||||
case DATA_CTHUN_PHASE:
|
||||
CthunPhase = data;
|
||||
if (data == PHASE_CTHUN_DONE)
|
||||
@@ -253,6 +222,8 @@ public:
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,17 +42,15 @@ enum DataTypes
|
||||
DATA_BUG_TRIO_DEATH = 13,
|
||||
DATA_OURO_SPAWNER = 14,
|
||||
DATA_VEKLOR = 15,
|
||||
DATA_VEKLORISDEAD = 16,
|
||||
DATA_VEKLOR_DEATH = 17,
|
||||
DATA_VEKNILASH = 18,
|
||||
DATA_VEKNILASHISDEAD = 19,
|
||||
DATA_VEKNILASH_DEATH = 20,
|
||||
DATA_CTHUN_PHASE = 21,
|
||||
DATA_EYE_OF_CTHUN = 22
|
||||
DATA_VEKNILASH = 16,
|
||||
DATA_CTHUN_PHASE = 17,
|
||||
DATA_EYE_OF_CTHUN = 18,
|
||||
DATA_MASTERS_EYE = 19
|
||||
};
|
||||
|
||||
enum Creatures
|
||||
{
|
||||
NPC_MASTERS_EYE = 15963,
|
||||
NPC_CTHUN = 15727,
|
||||
NPC_EYE_OF_CTHUN = 15589,
|
||||
NPC_CTHUN_PORTAL = 15896,
|
||||
|
||||
@@ -3676,6 +3676,32 @@ class spell_item_mirrens_drinking_hat : public SpellScript
|
||||
}
|
||||
};
|
||||
|
||||
class spell_item_snowman : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_item_snowman);
|
||||
|
||||
SpellCastResult CheckCast()
|
||||
{
|
||||
if (Player* caster = GetCaster()->ToPlayer())
|
||||
{
|
||||
if (Battleground* bg = caster->GetBattleground())
|
||||
{
|
||||
if (bg->GetStatus() == STATUS_WAIT_JOIN)
|
||||
{
|
||||
return SPELL_FAILED_NOT_READY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SPELL_CAST_OK;
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnCheckCast += SpellCheckCastFn(spell_item_snowman::CheckCast);
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_item_spell_scripts()
|
||||
{
|
||||
RegisterSpellScript(spell_item_massive_seaforium_charge);
|
||||
@@ -3789,4 +3815,5 @@ void AddSC_item_spell_scripts()
|
||||
RegisterSpellScript(spell_item_recall);
|
||||
RegisterSpellScript(spell_item_wraith_scythe_drain_life);
|
||||
RegisterSpellScript(spell_item_mirrens_drinking_hat);
|
||||
RegisterSpellScript(spell_item_snowman);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user