Core/CombatLog:
- Send more proper hitinfo values in SMSG_ATTACKERSTATEUPDATE (still lot of research todo) - Some renaming and commenting - Minor cleanup in Player::UpdateZone
This commit is contained in:
@@ -1890,7 +1890,7 @@ bool ChatHandler::HandleDamageCommand(const char * args)
|
|||||||
{
|
{
|
||||||
m_session->GetPlayer()->DealDamage(target, damage, NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false);
|
m_session->GetPlayer()->DealDamage(target, damage, NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false);
|
||||||
if (target != m_session->GetPlayer())
|
if (target != m_session->GetPlayer())
|
||||||
m_session->GetPlayer()->SendAttackStateUpdate (HITINFO_NORMALSWING2, target, 1, SPELL_SCHOOL_MASK_NORMAL, damage, 0, 0, VICTIMSTATE_HIT, 0);
|
m_session->GetPlayer()->SendAttackStateUpdate (HITINFO_AFFECTS_VICTIM, target, 1, SPELL_SCHOOL_MASK_NORMAL, damage, 0, 0, VICTIMSTATE_HIT, 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1920,7 +1920,7 @@ bool ChatHandler::HandleDamageCommand(const char * args)
|
|||||||
|
|
||||||
m_session->GetPlayer()->DealDamageMods(target, damage, &absorb);
|
m_session->GetPlayer()->DealDamageMods(target, damage, &absorb);
|
||||||
m_session->GetPlayer()->DealDamage(target, damage, NULL, DIRECT_DAMAGE, schoolmask, NULL, false);
|
m_session->GetPlayer()->DealDamage(target, damage, NULL, DIRECT_DAMAGE, schoolmask, NULL, false);
|
||||||
m_session->GetPlayer()->SendAttackStateUpdate (HITINFO_NORMALSWING2, target, 1, schoolmask, damage, absorb, resist, VICTIMSTATE_HIT, 0);
|
m_session->GetPlayer()->SendAttackStateUpdate (HITINFO_AFFECTS_VICTIM, target, 1, schoolmask, damage, absorb, resist, VICTIMSTATE_HIT, 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7424,17 +7424,15 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// group update
|
// group update
|
||||||
if (GetGroup())
|
if (Group* group = GetGroup())
|
||||||
{
|
{
|
||||||
SetGroupUpdateFlag(GROUP_UPDATE_FULL);
|
SetGroupUpdateFlag(GROUP_UPDATE_FULL);
|
||||||
Group* grp = GetGroup();
|
if (GetSession() && group->isLFGGroup() && sLFGMgr->IsTeleported(GetGUID()))
|
||||||
if (GetSession() && grp->isLFGGroup() && sLFGMgr->IsTeleported(GetGUID()))
|
|
||||||
{
|
{
|
||||||
for (GroupReference* itr = grp->GetFirstMember(); itr != NULL; itr = itr->next())
|
for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next())
|
||||||
{
|
{
|
||||||
Player* tempplr = itr->getSource();
|
if (Player* member = itr->getSource())
|
||||||
if (tempplr)
|
GetSession()->SendNameQueryOpcode(member->GetGUID());
|
||||||
GetSession()->SendNameQueryOpcode(tempplr->GetGUID());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7451,9 +7449,8 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea)
|
|||||||
|
|
||||||
if (sWorld->getBoolConfig(CONFIG_WEATHER))
|
if (sWorld->getBoolConfig(CONFIG_WEATHER))
|
||||||
{
|
{
|
||||||
Weather* wth = WeatherMgr::FindWeather(zone->ID);
|
if (Weather* weather = WeatherMgr::FindWeather(zone->ID))
|
||||||
if (wth)
|
weather->SendWeatherUpdateToPlayer(this);
|
||||||
wth->SendWeatherUpdateToPlayer(this);
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!WeatherMgr::AddWeather(zone->ID))
|
if (!WeatherMgr::AddWeather(zone->ID))
|
||||||
@@ -7485,7 +7482,7 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zone->flags & AREA_FLAG_CAPITAL) // in capital city
|
if (zone->flags & AREA_FLAG_CAPITAL) // Is in a capital city
|
||||||
{
|
{
|
||||||
if (!pvpInfo.inHostileArea || zone->IsSanctuary())
|
if (!pvpInfo.inHostileArea || zone->IsSanctuary())
|
||||||
{
|
{
|
||||||
@@ -7495,19 +7492,21 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea)
|
|||||||
}
|
}
|
||||||
pvpInfo.inNoPvPArea = true;
|
pvpInfo.inNoPvPArea = true;
|
||||||
}
|
}
|
||||||
else // anywhere else
|
else
|
||||||
{
|
{
|
||||||
if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING)) // but resting (walk from city or maybe in tavern or leave tavern recently)
|
if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING))
|
||||||
{
|
{
|
||||||
if (GetRestType() == REST_TYPE_IN_TAVERN) // has been in tavern. Is still in?
|
if (GetRestType() == REST_TYPE_IN_TAVERN) // Still inside a tavern or has recently left
|
||||||
{
|
{
|
||||||
if (GetMapId() != GetInnPosMapId() || sqrt((GetPositionX()-GetInnPosX())*(GetPositionX()-GetInnPosX())+(GetPositionY()-GetInnPosY())*(GetPositionY()-GetInnPosY())+(GetPositionZ()-GetInnPosZ())*(GetPositionZ()-GetInnPosZ()))>40)
|
// Remove rest state if we have recently left a tavern.
|
||||||
|
// Why is 40 yd hardcoded?
|
||||||
|
if (GetMapId() != GetInnPosMapId() || GetExactDist(GetInnPosX(), GetInnPosY(), GetInnPosZ()) > 40.0f)
|
||||||
{
|
{
|
||||||
RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING);
|
RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING);
|
||||||
SetRestType(REST_TYPE_NO);
|
SetRestType(REST_TYPE_NO);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else // not in tavern (leave city then)
|
else // Recently left a capital city
|
||||||
{
|
{
|
||||||
RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING);
|
RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING);
|
||||||
SetRestType(REST_TYPE_NO);
|
SetRestType(REST_TYPE_NO);
|
||||||
|
|||||||
@@ -110,23 +110,27 @@ m_damageType(DIRECT_DAMAGE), m_attackType(dmgInfo.attackType)
|
|||||||
m_resist = 0;
|
m_resist = 0;
|
||||||
m_block = 0;
|
m_block = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DamageInfo::ModifyDamage(int32 amount)
|
void DamageInfo::ModifyDamage(int32 amount)
|
||||||
{
|
{
|
||||||
amount = std::min(amount, int32(GetDamage()));
|
amount = std::min(amount, int32(GetDamage()));
|
||||||
m_damage += amount;
|
m_damage += amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DamageInfo::AbsorbDamage(uint32 amount)
|
void DamageInfo::AbsorbDamage(uint32 amount)
|
||||||
{
|
{
|
||||||
amount = std::min(amount, GetDamage());
|
amount = std::min(amount, GetDamage());
|
||||||
m_absorb += amount;
|
m_absorb += amount;
|
||||||
m_damage -= amount;
|
m_damage -= amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DamageInfo::ResistDamage(uint32 amount)
|
void DamageInfo::ResistDamage(uint32 amount)
|
||||||
{
|
{
|
||||||
amount = std::min(amount, GetDamage());
|
amount = std::min(amount, GetDamage());
|
||||||
m_resist += amount;
|
m_resist += amount;
|
||||||
m_damage -= amount;
|
m_damage -= amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DamageInfo::BlockDamage(uint32 amount)
|
void DamageInfo::BlockDamage(uint32 amount)
|
||||||
{
|
{
|
||||||
amount = std::min(amount, GetDamage());
|
amount = std::min(amount, GetDamage());
|
||||||
@@ -1096,6 +1100,7 @@ void Unit::CalculateMeleeDamage(Unit* victim, uint32 damage, CalcDamageInfo* dam
|
|||||||
|
|
||||||
if (!victim)
|
if (!victim)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!isAlive() || !victim->isAlive())
|
if (!isAlive() || !victim->isAlive())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -1105,12 +1110,11 @@ void Unit::CalculateMeleeDamage(Unit* victim, uint32 damage, CalcDamageInfo* dam
|
|||||||
case BASE_ATTACK:
|
case BASE_ATTACK:
|
||||||
damageInfo->procAttacker = PROC_FLAG_DONE_MELEE_AUTO_ATTACK | PROC_FLAG_DONE_MAINHAND_ATTACK;
|
damageInfo->procAttacker = PROC_FLAG_DONE_MELEE_AUTO_ATTACK | PROC_FLAG_DONE_MAINHAND_ATTACK;
|
||||||
damageInfo->procVictim = PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK;
|
damageInfo->procVictim = PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK;
|
||||||
damageInfo->HitInfo = HITINFO_NORMALSWING2;
|
|
||||||
break;
|
break;
|
||||||
case OFF_ATTACK:
|
case OFF_ATTACK:
|
||||||
damageInfo->procAttacker = PROC_FLAG_DONE_MELEE_AUTO_ATTACK | PROC_FLAG_DONE_OFFHAND_ATTACK;
|
damageInfo->procAttacker = PROC_FLAG_DONE_MELEE_AUTO_ATTACK | PROC_FLAG_DONE_OFFHAND_ATTACK;
|
||||||
damageInfo->procVictim = PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK;
|
damageInfo->procVictim = PROC_FLAG_TAKEN_MELEE_AUTO_ATTACK;
|
||||||
damageInfo->HitInfo = HITINFO_LEFTSWING;
|
damageInfo->HitInfo = HITINFO_OFFHAND;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
@@ -1244,6 +1248,10 @@ void Unit::CalculateMeleeDamage(Unit* victim, uint32 damage, CalcDamageInfo* dam
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always apply HITINFO_AFFECTS_VICTIM in case its not a miss
|
||||||
|
if (!(damageInfo->HitInfo & HITINFO_MISS))
|
||||||
|
damageInfo->HitInfo |= HITINFO_AFFECTS_VICTIM;
|
||||||
|
|
||||||
int32 resilienceReduction = damageInfo->damage;
|
int32 resilienceReduction = damageInfo->damage;
|
||||||
if (attackType != RANGED_ATTACK)
|
if (attackType != RANGED_ATTACK)
|
||||||
ApplyResilience(victim, NULL, &resilienceReduction, (damageInfo->hitOutCome == MELEE_HIT_CRIT), CR_CRIT_TAKEN_MELEE);
|
ApplyResilience(victim, NULL, &resilienceReduction, (damageInfo->hitOutCome == MELEE_HIT_CRIT), CR_CRIT_TAKEN_MELEE);
|
||||||
@@ -1259,14 +1267,17 @@ void Unit::CalculateMeleeDamage(Unit* victim, uint32 damage, CalcDamageInfo* dam
|
|||||||
damageInfo->procVictim |= PROC_FLAG_TAKEN_DAMAGE;
|
damageInfo->procVictim |= PROC_FLAG_TAKEN_DAMAGE;
|
||||||
// Calculate absorb & resists
|
// Calculate absorb & resists
|
||||||
CalcAbsorbResist(damageInfo->target, SpellSchoolMask(damageInfo->damageSchoolMask), DIRECT_DAMAGE, damageInfo->damage, &damageInfo->absorb, &damageInfo->resist);
|
CalcAbsorbResist(damageInfo->target, SpellSchoolMask(damageInfo->damageSchoolMask), DIRECT_DAMAGE, damageInfo->damage, &damageInfo->absorb, &damageInfo->resist);
|
||||||
damageInfo->damage -= damageInfo->absorb + damageInfo->resist;
|
|
||||||
if (damageInfo->absorb)
|
if (damageInfo->absorb)
|
||||||
{
|
{
|
||||||
damageInfo->HitInfo |= HITINFO_ABSORB;
|
damageInfo->HitInfo |= (damageInfo->damage - damageInfo->absorb == 0 ? HITINFO_FULL_ABSORB : HITINFO_PARTIAL_ABSORB);
|
||||||
damageInfo->procEx |= PROC_EX_ABSORB;
|
damageInfo->procEx |= PROC_EX_ABSORB;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (damageInfo->resist)
|
if (damageInfo->resist)
|
||||||
damageInfo->HitInfo |= HITINFO_RESIST;
|
damageInfo->HitInfo |= (damageInfo->damage - damageInfo->resist == 0 ? HITINFO_FULL_RESIST : HITINFO_PARTIAL_RESIST);
|
||||||
|
|
||||||
|
damageInfo->damage -= damageInfo->absorb + damageInfo->resist;
|
||||||
}
|
}
|
||||||
else // Impossible get negative result but....
|
else // Impossible get negative result but....
|
||||||
damageInfo->damage = 0;
|
damageInfo->damage = 0;
|
||||||
@@ -2157,8 +2168,8 @@ void Unit::SendMeleeAttackStop(Unit* victim)
|
|||||||
{
|
{
|
||||||
WorldPacket data(SMSG_ATTACKSTOP, (8+8+4));
|
WorldPacket data(SMSG_ATTACKSTOP, (8+8+4));
|
||||||
data.append(GetPackGUID());
|
data.append(GetPackGUID());
|
||||||
data.append(victim ? victim->GetPackGUID() : 0); // can be 0x00...
|
data.append(victim ? victim->GetPackGUID() : 0);
|
||||||
data << uint32(0); // can be 0x1
|
data << uint32(0); //! Can also take the value 0x01, which seems related to updating rotation
|
||||||
SendMessageToSet(&data, true);
|
SendMessageToSet(&data, true);
|
||||||
sLog->outStaticDebug("WORLD: Sent SMSG_ATTACKSTOP");
|
sLog->outStaticDebug("WORLD: Sent SMSG_ATTACKSTOP");
|
||||||
|
|
||||||
@@ -2988,20 +2999,20 @@ bool Unit::IsNonMeleeSpellCasted(bool withDelayed, bool skipChanneled, bool skip
|
|||||||
(withDelayed || m_currentSpells[CURRENT_GENERIC_SPELL]->getState() != SPELL_STATE_DELAYED))
|
(withDelayed || m_currentSpells[CURRENT_GENERIC_SPELL]->getState() != SPELL_STATE_DELAYED))
|
||||||
{
|
{
|
||||||
if (!isAutoshoot || !(m_currentSpells[CURRENT_GENERIC_SPELL]->m_spellInfo->AttributesEx2 & SPELL_ATTR2_NOT_RESET_AUTO_ACTIONS))
|
if (!isAutoshoot || !(m_currentSpells[CURRENT_GENERIC_SPELL]->m_spellInfo->AttributesEx2 & SPELL_ATTR2_NOT_RESET_AUTO_ACTIONS))
|
||||||
return(true);
|
return true;
|
||||||
}
|
}
|
||||||
// channeled spells may be delayed, but they are still considered casted
|
// channeled spells may be delayed, but they are still considered casted
|
||||||
else if (!skipChanneled && m_currentSpells[CURRENT_CHANNELED_SPELL] &&
|
else if (!skipChanneled && m_currentSpells[CURRENT_CHANNELED_SPELL] &&
|
||||||
(m_currentSpells[CURRENT_CHANNELED_SPELL]->getState() != SPELL_STATE_FINISHED))
|
(m_currentSpells[CURRENT_CHANNELED_SPELL]->getState() != SPELL_STATE_FINISHED))
|
||||||
{
|
{
|
||||||
if (!isAutoshoot || !(m_currentSpells[CURRENT_CHANNELED_SPELL]->m_spellInfo->AttributesEx2 & SPELL_ATTR2_NOT_RESET_AUTO_ACTIONS))
|
if (!isAutoshoot || !(m_currentSpells[CURRENT_CHANNELED_SPELL]->m_spellInfo->AttributesEx2 & SPELL_ATTR2_NOT_RESET_AUTO_ACTIONS))
|
||||||
return(true);
|
return true;
|
||||||
}
|
}
|
||||||
// autorepeat spells may be finished or delayed, but they are still considered casted
|
// autorepeat spells may be finished or delayed, but they are still considered casted
|
||||||
else if (!skipAutorepeat && m_currentSpells[CURRENT_AUTOREPEAT_SPELL])
|
else if (!skipAutorepeat && m_currentSpells[CURRENT_AUTOREPEAT_SPELL])
|
||||||
return(true);
|
return true;
|
||||||
|
|
||||||
return(false);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::InterruptNonMeleeSpells(bool withDelayed, uint32 spell_id, bool withInstant)
|
void Unit::InterruptNonMeleeSpells(bool withDelayed, uint32 spell_id, bool withInstant)
|
||||||
@@ -4846,28 +4857,29 @@ void Unit::SendAttackStateUpdate(CalcDamageInfo* damageInfo)
|
|||||||
data << uint32(damageInfo->damage); // Sub Damage
|
data << uint32(damageInfo->damage); // Sub Damage
|
||||||
}
|
}
|
||||||
|
|
||||||
if (damageInfo->HitInfo & (HITINFO_ABSORB | HITINFO_ABSORB2))
|
if (damageInfo->HitInfo & (HITINFO_FULL_ABSORB | HITINFO_PARTIAL_ABSORB))
|
||||||
{
|
{
|
||||||
for (uint32 i = 0; i < count; ++i)
|
for (uint32 i = 0; i < count; ++i)
|
||||||
data << uint32(damageInfo->absorb); // Absorb
|
data << uint32(damageInfo->absorb); // Absorb
|
||||||
}
|
}
|
||||||
|
|
||||||
if (damageInfo->HitInfo & (HITINFO_RESIST | HITINFO_RESIST2))
|
if (damageInfo->HitInfo & (HITINFO_FULL_RESIST | HITINFO_PARTIAL_RESIST))
|
||||||
{
|
{
|
||||||
for (uint32 i = 0; i < count; ++i)
|
for (uint32 i = 0; i < count; ++i)
|
||||||
data << uint32(damageInfo->resist); // Resist
|
data << uint32(damageInfo->resist); // Resist
|
||||||
}
|
}
|
||||||
|
|
||||||
data << uint8(damageInfo->TargetState);
|
data << uint8(damageInfo->TargetState);
|
||||||
data << uint32(0);
|
data << uint32(0); // Unknown attackerstate
|
||||||
data << uint32(0);
|
data << uint32(0); // Melee spellid
|
||||||
|
|
||||||
if (damageInfo->HitInfo & HITINFO_BLOCK)
|
if (damageInfo->HitInfo & HITINFO_BLOCK)
|
||||||
data << uint32(damageInfo->blocked_amount);
|
data << uint32(damageInfo->blocked_amount);
|
||||||
|
|
||||||
if (damageInfo->HitInfo & HITINFO_UNK3)
|
if (damageInfo->HitInfo & HITINFO_RAGE_GAIN)
|
||||||
data << uint32(0);
|
data << uint32(0);
|
||||||
|
|
||||||
|
//! Probably used for debugging purposes, as it is not known to appear on retail servers
|
||||||
if (damageInfo->HitInfo & HITINFO_UNK1)
|
if (damageInfo->HitInfo & HITINFO_UNK1)
|
||||||
{
|
{
|
||||||
data << uint32(0);
|
data << uint32(0);
|
||||||
|
|||||||
@@ -286,29 +286,29 @@ enum HitInfo
|
|||||||
{
|
{
|
||||||
HITINFO_NORMALSWING = 0x00000000,
|
HITINFO_NORMALSWING = 0x00000000,
|
||||||
HITINFO_UNK1 = 0x00000001, // req correct packet structure
|
HITINFO_UNK1 = 0x00000001, // req correct packet structure
|
||||||
HITINFO_NORMALSWING2 = 0x00000002,
|
HITINFO_AFFECTS_VICTIM = 0x00000002,
|
||||||
HITINFO_LEFTSWING = 0x00000004,
|
HITINFO_OFFHAND = 0x00000004,
|
||||||
HITINFO_UNK2 = 0x00000008,
|
HITINFO_UNK2 = 0x00000008,
|
||||||
HITINFO_MISS = 0x00000010,
|
HITINFO_MISS = 0x00000010,
|
||||||
HITINFO_ABSORB = 0x00000020, // absorbed damage
|
HITINFO_FULL_ABSORB = 0x00000020,
|
||||||
HITINFO_ABSORB2 = 0x00000040, // absorbed damage
|
HITINFO_PARTIAL_ABSORB = 0x00000040,
|
||||||
HITINFO_RESIST = 0x00000080, // resisted atleast some damage
|
HITINFO_FULL_RESIST = 0x00000080,
|
||||||
HITINFO_RESIST2 = 0x00000100, // resisted atleast some damage
|
HITINFO_PARTIAL_RESIST = 0x00000100,
|
||||||
HITINFO_CRITICALHIT = 0x00000200, // critical hit
|
HITINFO_CRITICALHIT = 0x00000200, // critical hit
|
||||||
// 0x00000400
|
// 0x00000400
|
||||||
// 0x00000800
|
// 0x00000800
|
||||||
// 0x00001000
|
// 0x00001000
|
||||||
HITINFO_BLOCK = 0x00002000, // blocked damage
|
HITINFO_BLOCK = 0x00002000, // blocked damage
|
||||||
// 0x00004000
|
// 0x00004000 // Hides worldtext for 0 damage
|
||||||
// 0x00008000
|
// 0x00008000 // Related to blood visual
|
||||||
HITINFO_GLANCING = 0x00010000,
|
HITINFO_GLANCING = 0x00010000,
|
||||||
HITINFO_CRUSHING = 0x00020000,
|
HITINFO_CRUSHING = 0x00020000,
|
||||||
HITINFO_NOACTION = 0x00040000, // guessed
|
HITINFO_NO_ANIMATION = 0x00040000,
|
||||||
// 0x00080000
|
// 0x00080000
|
||||||
// 0x00100000
|
// 0x00100000
|
||||||
HITINFO_SWINGNOHITSOUND = 0x00200000, // guessed
|
HITINFO_SWINGNOHITSOUND = 0x00200000, // unused?
|
||||||
// 0x00400000
|
// 0x00400000
|
||||||
HITINFO_UNK3 = 0x00800000
|
HITINFO_RAGE_GAIN = 0x00800000
|
||||||
};
|
};
|
||||||
|
|
||||||
//i would like to remove this: (it is defined in item.h
|
//i would like to remove this: (it is defined in item.h
|
||||||
|
|||||||
Reference in New Issue
Block a user