rework script system and fix windrunner spire
This commit is contained in:
@@ -123,9 +123,13 @@ void SummonList::DoActionImpl(int32 action, StorageType& summons, uint16 max)
|
||||
}
|
||||
}
|
||||
|
||||
ScriptedAI::ScriptedAI(Creature* creature, uint32 scriptId) noexcept : CreatureAI(creature, scriptId), _isCombatMovementAllowed(true)
|
||||
ScriptedAI::ScriptedAI(Creature* creature, uint32 scriptId) noexcept : CreatureAI(creature, scriptId), summons(creature), _isCombatMovementAllowed(true)
|
||||
{
|
||||
_difficulty = me->GetMap()->GetDifficultyID();
|
||||
scheduler.SetValidator([this]
|
||||
{
|
||||
return !me->HasUnitState(UNIT_STATE_CASTING);
|
||||
});
|
||||
}
|
||||
|
||||
void ScriptedAI::AttackStartNoMove(Unit* who)
|
||||
@@ -145,10 +149,64 @@ void ScriptedAI::AttackStart(Unit* who)
|
||||
AttackStartNoMove(who);
|
||||
}
|
||||
|
||||
void ScriptedAI::UpdateAI(uint32 /*diff*/)
|
||||
|
||||
void ScriptedAI::UpdateAI(uint32 diff)
|
||||
{
|
||||
// Check if we have a current target
|
||||
UpdateVictim();
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = events.ExecuteEvent())
|
||||
{
|
||||
ExecuteEvent(eventId);
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptedAI::JustSummoned(Creature* summon)
|
||||
{
|
||||
summons.Summon(summon);
|
||||
if (me->IsEngaged())
|
||||
DoZoneInCombat(summon);
|
||||
}
|
||||
|
||||
void ScriptedAI::SummonedCreatureDespawn(Creature* summon)
|
||||
{
|
||||
summons.Despawn(summon);
|
||||
}
|
||||
|
||||
void ScriptedAI::DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/)
|
||||
{
|
||||
damageEvents.DamageTaken(me->GetHealth(), me->GetMaxHealth(), damage, events);
|
||||
}
|
||||
|
||||
void ScriptedAI::Reset()
|
||||
{
|
||||
if (!me->IsAlive())
|
||||
return;
|
||||
|
||||
events.Reset();
|
||||
damageEvents.Reset();
|
||||
summons.DespawnAll();
|
||||
scheduler.CancelAll();
|
||||
}
|
||||
|
||||
void ScriptedAI::JustEngagedWith(Unit* /*who*/)
|
||||
{
|
||||
ScheduleTasks();
|
||||
}
|
||||
|
||||
void ScriptedAI::JustDied(Unit* /*killer*/)
|
||||
{
|
||||
events.Reset();
|
||||
damageEvents.Reset();
|
||||
summons.DespawnAll();
|
||||
scheduler.CancelAll();
|
||||
}
|
||||
|
||||
void ScriptedAI::DoStartMovement(Unit* victim, float distance, float angle)
|
||||
@@ -542,7 +600,7 @@ void ScriptedAI::DoAddEvent(Milliseconds timer, BasicEvent* event, WorldObject*
|
||||
}
|
||||
|
||||
// BossAI - for instanced bosses
|
||||
BossAI::BossAI(Creature* creature, uint32 bossId) noexcept : ScriptedAI(creature), instance(creature->GetInstanceScript()), summons(creature), _bossId(bossId)
|
||||
BossAI::BossAI(Creature* creature, uint32 bossId) noexcept : ScriptedAI(creature), instance(creature->GetInstanceScript()), _bossId(bossId)
|
||||
{
|
||||
if (instance)
|
||||
SetBoundary(instance->GetBossBoundary(bossId));
|
||||
@@ -559,21 +617,16 @@ void BossAI::_Reset()
|
||||
if (!me->IsAlive())
|
||||
return;
|
||||
|
||||
ScriptedAI::Reset();
|
||||
me->ResetLootMode();
|
||||
events.Reset();
|
||||
damageEvents.Reset();
|
||||
summons.DespawnAll();
|
||||
scheduler.CancelAll();
|
||||
me->RemoveAllAreaTriggers();
|
||||
if (instance && instance->GetBossState(_bossId) != DONE)
|
||||
instance->SetBossState(_bossId, NOT_STARTED);
|
||||
}
|
||||
|
||||
void BossAI::_JustDied()
|
||||
{
|
||||
events.Reset();
|
||||
damageEvents.Reset();
|
||||
summons.DespawnAll();
|
||||
scheduler.CancelAll();
|
||||
ScriptedAI::JustDied(nullptr);
|
||||
if (instance)
|
||||
instance->SetBossState(_bossId, DONE);
|
||||
}
|
||||
@@ -597,7 +650,7 @@ void BossAI::_JustEngagedWith(Unit* who)
|
||||
}
|
||||
|
||||
me->setActive(true);
|
||||
ScheduleTasks();
|
||||
ScriptedAI::JustEngagedWith(who);
|
||||
}
|
||||
|
||||
void BossAI::TeleportCheaters()
|
||||
@@ -613,36 +666,6 @@ void BossAI::TeleportCheaters()
|
||||
}
|
||||
}
|
||||
|
||||
void BossAI::JustSummoned(Creature* summon)
|
||||
{
|
||||
summons.Summon(summon);
|
||||
if (me->IsEngaged())
|
||||
DoZoneInCombat(summon);
|
||||
}
|
||||
|
||||
void BossAI::SummonedCreatureDespawn(Creature* summon)
|
||||
{
|
||||
summons.Despawn(summon);
|
||||
}
|
||||
|
||||
void BossAI::UpdateAI(uint32 diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = events.ExecuteEvent())
|
||||
{
|
||||
ExecuteEvent(eventId);
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool BossAI::CanAIAttack(Unit const* target) const
|
||||
{
|
||||
return IsInBoundary(target);
|
||||
@@ -673,7 +696,7 @@ void BossAI::_DespawnAtEvade(Seconds delayToRespawn /*= 30s*/, Creature* who /*=
|
||||
}
|
||||
|
||||
// WorldBossAI - for non-instanced bosses
|
||||
WorldBossAI::WorldBossAI(Creature* creature) noexcept : ScriptedAI(creature), summons(creature) {}
|
||||
WorldBossAI::WorldBossAI(Creature* creature) noexcept : ScriptedAI(creature) {}
|
||||
|
||||
WorldBossAI::~WorldBossAI() = default;
|
||||
|
||||
@@ -682,14 +705,12 @@ void WorldBossAI::_Reset()
|
||||
if (!me->IsAlive())
|
||||
return;
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
ScriptedAI::Reset();
|
||||
}
|
||||
|
||||
void WorldBossAI::_JustDied()
|
||||
{
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
ScriptedAI::JustDied(nullptr);
|
||||
}
|
||||
|
||||
void WorldBossAI::_JustEngagedWith()
|
||||
@@ -697,38 +718,19 @@ void WorldBossAI::_JustEngagedWith()
|
||||
Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true);
|
||||
if (target)
|
||||
AttackStart(target);
|
||||
|
||||
ScriptedAI::JustEngagedWith(target);
|
||||
}
|
||||
|
||||
void WorldBossAI::JustSummoned(Creature* summon)
|
||||
{
|
||||
summons.Summon(summon);
|
||||
ScriptedAI::JustSummoned(summon);
|
||||
|
||||
Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true);
|
||||
if (target)
|
||||
summon->AI()->AttackStart(target);
|
||||
}
|
||||
|
||||
void WorldBossAI::SummonedCreatureDespawn(Creature* summon)
|
||||
{
|
||||
summons.Despawn(summon);
|
||||
}
|
||||
|
||||
void WorldBossAI::UpdateAI(uint32 diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
while (uint32 eventId = events.ExecuteEvent())
|
||||
{
|
||||
ExecuteEvent(eventId);
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Ashamane >
|
||||
void ScriptedAI::ApplyDefaultBossImmuneMask(uint32 except)
|
||||
@@ -740,10 +742,4 @@ void ScriptedAI::ApplyDefaultBossImmuneMask(uint32 except)
|
||||
if (IMMUNE_TO_MOVEMENT_IMPAIRMENT_AND_LOSS_CONTROL_MASK & (1ULL << (i - 1)))
|
||||
me->ApplySpellImmune(placeholderSpellId, IMMUNITY_MECHANIC, i, true);
|
||||
}
|
||||
|
||||
void BossAI::DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo*/)
|
||||
{
|
||||
damageEvents.DamageTaken(me->GetHealth(), me->GetMaxHealth(), damage, events);
|
||||
}
|
||||
|
||||
// < Ashamane
|
||||
|
||||
@@ -258,6 +258,20 @@ public:
|
||||
// Ashamane >
|
||||
public:
|
||||
void ApplyDefaultBossImmuneMask(uint32 except = 0);
|
||||
void JustSummoned(Creature* summon) override;
|
||||
void SummonedCreatureDespawn(Creature* summon) override;
|
||||
void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType damageType, SpellInfo const* spellInfo = nullptr) override;
|
||||
void Reset() override;
|
||||
void JustEngagedWith(Unit* who) override;
|
||||
void JustDied(Unit* killer) override;
|
||||
|
||||
// Hook used to execute events scheduled into EventMap without the need
|
||||
// to override UpdateAI
|
||||
// note: You must re-schedule the event within this method if the event
|
||||
// is supposed to run more than once
|
||||
virtual void ExecuteEvent(uint32 /*eventId*/) {}
|
||||
|
||||
virtual void ScheduleTasks() {}
|
||||
// < Ashamane
|
||||
|
||||
template <class T>
|
||||
@@ -312,6 +326,12 @@ public:
|
||||
return heroic25;
|
||||
}
|
||||
|
||||
protected:
|
||||
EventMap events;
|
||||
DamageEventMap damageEvents;
|
||||
SummonList summons;
|
||||
TaskScheduler scheduler;
|
||||
|
||||
private:
|
||||
Difficulty _difficulty;
|
||||
bool _isCombatMovementAllowed;
|
||||
@@ -325,19 +345,6 @@ public:
|
||||
|
||||
InstanceScript* const instance;
|
||||
|
||||
void JustSummoned(Creature* summon) override;
|
||||
void SummonedCreatureDespawn(Creature* summon) override;
|
||||
|
||||
virtual void UpdateAI(uint32 diff) override;
|
||||
|
||||
// Hook used to execute events scheduled into EventMap without the need
|
||||
// to override UpdateAI
|
||||
// note: You must re-schedule the event within this method if the event
|
||||
// is supposed to run more than once
|
||||
virtual void ExecuteEvent(uint32 /*eventId*/) {}
|
||||
|
||||
virtual void ScheduleTasks() {}
|
||||
|
||||
void Reset() override { _Reset(); }
|
||||
void JustEngagedWith(Unit* who) override { _JustEngagedWith(who); }
|
||||
void JustDied(Unit* /*killer*/) override { _JustDied(); }
|
||||
@@ -347,9 +354,6 @@ public:
|
||||
|
||||
uint32 GetBossId() const { return _bossId; }
|
||||
|
||||
//WowCommunity
|
||||
void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType damageType, SpellInfo const* spellInfo = nullptr) override;
|
||||
//WowCommunity
|
||||
protected:
|
||||
void _Reset();
|
||||
void _JustEngagedWith(Unit* who);
|
||||
@@ -359,13 +363,6 @@ protected:
|
||||
|
||||
void TeleportCheaters();
|
||||
|
||||
EventMap events;
|
||||
//WowCommunity
|
||||
DamageEventMap damageEvents;
|
||||
//WowCommunity
|
||||
SummonList summons;
|
||||
TaskScheduler scheduler;
|
||||
|
||||
private:
|
||||
uint32 const _bossId;
|
||||
};
|
||||
@@ -377,15 +374,6 @@ public:
|
||||
virtual ~WorldBossAI();
|
||||
|
||||
void JustSummoned(Creature* summon) override;
|
||||
void SummonedCreatureDespawn(Creature* summon) override;
|
||||
|
||||
virtual void UpdateAI(uint32 diff) override;
|
||||
|
||||
// Hook used to execute events scheduled into EventMap without the need
|
||||
// to override UpdateAI
|
||||
// note: You must re-schedule the event within this method if the event
|
||||
// is supposed to run more than once
|
||||
virtual void ExecuteEvent(uint32 /*eventId*/) {}
|
||||
|
||||
void Reset() override { _Reset(); }
|
||||
void JustEngagedWith(Unit* /*who*/) override { _JustEngagedWith(); }
|
||||
@@ -395,9 +383,6 @@ protected:
|
||||
void _Reset();
|
||||
void _JustEngagedWith();
|
||||
void _JustDied();
|
||||
|
||||
EventMap events;
|
||||
SummonList summons;
|
||||
};
|
||||
|
||||
// SD2 grid searchers.
|
||||
|
||||
@@ -245,6 +245,7 @@ WorldObject::~WorldObject()
|
||||
void WorldObject::Update(uint32 diff)
|
||||
{
|
||||
m_Events.Update(diff);
|
||||
_scheduler.Update(diff);
|
||||
|
||||
_heartbeatTimer -= Milliseconds(diff);
|
||||
while (_heartbeatTimer <= 0ms)
|
||||
@@ -334,6 +335,8 @@ void WorldObject::SetFarVisible(bool on)
|
||||
|
||||
void WorldObject::CleanupsBeforeDelete(bool /*finalCleanup*/)
|
||||
{
|
||||
_scheduler.CancelAll();
|
||||
|
||||
if (IsInWorld())
|
||||
RemoveFromWorld();
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ struct npc_wrs_spectral_axethrower : public ScriptedAI
|
||||
{
|
||||
npc_wrs_spectral_axethrower(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
/* void ScheduleTasks() override
|
||||
void ScheduleTasks() override
|
||||
{
|
||||
DoZoneInCombat();
|
||||
events.ScheduleEvent(EVENT_AXE_THROW, 2s);
|
||||
@@ -255,7 +255,7 @@ struct npc_wrs_spectral_axethrower : public ScriptedAI
|
||||
DoCastVictim(SPELL_AXE_THROW);
|
||||
events.ScheduleEvent(EVENT_AXE_THROW, 6s);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
};
|
||||
|
||||
// 234061 - Phantasmal Mystic
|
||||
@@ -263,7 +263,7 @@ struct npc_wrs_phantasmal_mystic : public ScriptedAI
|
||||
{
|
||||
npc_wrs_phantasmal_mystic(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
/* void ScheduleTasks() override
|
||||
void ScheduleTasks() override
|
||||
{
|
||||
DoZoneInCombat();
|
||||
events.ScheduleEvent(EVENT_CHAIN_LIGHTNING, 2s);
|
||||
@@ -289,7 +289,7 @@ struct npc_wrs_phantasmal_mystic : public ScriptedAI
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
};
|
||||
|
||||
// 232446 - Haunting Grunt
|
||||
@@ -297,7 +297,7 @@ struct npc_wrs_haunting_grunt : public ScriptedAI
|
||||
{
|
||||
npc_wrs_haunting_grunt(Creature* creature) : ScriptedAI(creature) { }
|
||||
|
||||
/*void ScheduleTasks() override
|
||||
void ScheduleTasks() override
|
||||
{
|
||||
DoZoneInCombat();
|
||||
events.ScheduleEvent(EVENT_INTERCEPTING_CHARGE, 1s);
|
||||
@@ -314,7 +314,7 @@ struct npc_wrs_haunting_grunt : public ScriptedAI
|
||||
|
||||
events.ScheduleEvent(EVENT_INTERCEPTING_CHARGE, 10s);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
};
|
||||
|
||||
// CreateProperties 35182 | Template 34184
|
||||
|
||||
@@ -198,13 +198,13 @@ struct instance_windrunner_spire : public InstanceScript
|
||||
lirath->SetHover(false);
|
||||
lirath->SetWalk(true);
|
||||
lirath->GetMotionMaster()->MovePoint(0, 5213.23f, -3124.39f, 302.135f, false);
|
||||
//lirath->GetScheduler().Schedule(1s, [lirath](TaskContext /*context*/)
|
||||
//{
|
||||
// lirath->SetWalk(false);
|
||||
// lirath->GetMotionMaster()->MoveIdle();
|
||||
// lirath->SetFacingTo(1.487266f);
|
||||
// lirath->HandleEmoteCommand(EMOTE_STATE_KNEEL);
|
||||
//});
|
||||
lirath->GetScheduler().Schedule(1s, [lirath](TaskContext /*context*/)
|
||||
{
|
||||
lirath->SetWalk(false);
|
||||
lirath->GetMotionMaster()->MoveIdle();
|
||||
lirath->SetFacingTo(1.487266f);
|
||||
lirath->HandleEmoteCommand(EMOTE_STATE_KNEEL);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ struct npc_wrs_spellguard_magus : public ScriptedAI
|
||||
{
|
||||
using ScriptedAI::ScriptedAI;
|
||||
|
||||
/* void ScheduleTasks() override
|
||||
void ScheduleTasks() override
|
||||
{
|
||||
events.ScheduleEvent(EVENT_ARCANE_SALVO, 5s, 10s);
|
||||
damageEvents.ScheduleEvent(EVENT_SPELLGUARD_PROTECTION_AT, 50);
|
||||
@@ -213,7 +213,7 @@ struct npc_wrs_spellguard_magus : public ScriptedAI
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
};
|
||||
|
||||
// 39539
|
||||
@@ -239,7 +239,7 @@ struct npc_wrs_restless_stewart : public ScriptedAI
|
||||
{
|
||||
using ScriptedAI::ScriptedAI;
|
||||
|
||||
/* void ScheduleTasks() override
|
||||
void ScheduleTasks() override
|
||||
{
|
||||
events.ScheduleEvent(EVENT_SPIRIT_BOLT, 2s, 5s);
|
||||
events.ScheduleEvent(EVENT_SOUL_TOURMENT, 5s, 10s);
|
||||
@@ -260,7 +260,7 @@ struct npc_wrs_restless_stewart : public ScriptedAI
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
};
|
||||
|
||||
// 232056
|
||||
@@ -268,7 +268,7 @@ struct npc_wrs_territorial_dragonhawk : public ScriptedAI
|
||||
{
|
||||
using ScriptedAI::ScriptedAI;
|
||||
|
||||
/* void ScheduleTasks() override
|
||||
void ScheduleTasks() override
|
||||
{
|
||||
events.ScheduleEvent(EVENT_FIRE_SPIT, 5s, 10s);
|
||||
events.ScheduleEvent(EVENT_BOLSTERING_FLAME, 10s, 20s);
|
||||
@@ -289,7 +289,7 @@ struct npc_wrs_territorial_dragonhawk : public ScriptedAI
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
};
|
||||
|
||||
// 234673
|
||||
@@ -319,7 +319,7 @@ struct npc_wrs_creeping_spindleweb : public ScriptedAI
|
||||
DoCastSelf(SPELL_ACEDIC_DEMISE_AURA);
|
||||
}
|
||||
|
||||
/* void ScheduleTasks() override
|
||||
void ScheduleTasks() override
|
||||
{
|
||||
events.ScheduleEvent(EVENT_POISON_SPRAY, 5s, 10s);
|
||||
}
|
||||
@@ -333,7 +333,7 @@ struct npc_wrs_creeping_spindleweb : public ScriptedAI
|
||||
events.Repeat(20s, 25s);
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
void JustDied(Unit* /*killer*/) override
|
||||
{
|
||||
@@ -346,7 +346,7 @@ struct npc_wrs_apex_lynx : public ScriptedAI
|
||||
{
|
||||
using ScriptedAI::ScriptedAI;
|
||||
|
||||
/*void ScheduleTasks() override
|
||||
void ScheduleTasks() override
|
||||
{
|
||||
events.ScheduleEvent(EVENT_FEROCIOUS_POUNCE, 5s, 10s);
|
||||
}
|
||||
@@ -364,7 +364,7 @@ struct npc_wrs_apex_lynx : public ScriptedAI
|
||||
events.Repeat(20s, 25s);
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
void SpellHitTarget(WorldObject* /*target*/, SpellInfo const* spellInfo) override
|
||||
{
|
||||
@@ -384,7 +384,7 @@ struct npc_wrs_bloated_lasher : public ScriptedAI
|
||||
{
|
||||
using ScriptedAI::ScriptedAI;
|
||||
|
||||
/* void ScheduleTasks() override
|
||||
void ScheduleTasks() override
|
||||
{
|
||||
events.ScheduleEvent(EVENT_FUNGAL_BOLT, 0s, 1s);
|
||||
events.ScheduleEvent(EVENT_SPORE_DISPERSAL, 3s);
|
||||
@@ -403,7 +403,7 @@ struct npc_wrs_bloated_lasher : public ScriptedAI
|
||||
events.Repeat(20s, 30s);
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
};
|
||||
|
||||
// 238099
|
||||
|
||||
@@ -24,7 +24,7 @@ void AddSC_boss_commander_kroluk();
|
||||
void AddSC_boss_derelict_duo();
|
||||
void AddSC_boss_emberdawn();
|
||||
void AddSC_boss_restless_heart();
|
||||
//void AddSC_instance_windrunner_spire();
|
||||
void AddSC_instance_windrunner_spire();
|
||||
void AddSC_windrunner_spire();
|
||||
void AddSC_instance_maisara_caverns();
|
||||
void AddSC_boss_murojin();
|
||||
@@ -37,7 +37,7 @@ void AddMidnightScripts()
|
||||
AddSC_boss_derelict_duo();
|
||||
AddSC_boss_emberdawn();
|
||||
AddSC_boss_restless_heart();
|
||||
// AddSC_instance_windrunner_spire();
|
||||
AddSC_instance_windrunner_spire();
|
||||
AddSC_windrunner_spire();
|
||||
AddSC_instance_maisara_caverns();
|
||||
AddSC_boss_murojin();
|
||||
|
||||
Reference in New Issue
Block a user