mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
warrior slam specific condditionals and update readme
This commit is contained in:
@@ -552,6 +552,188 @@ function CleveRoids.ValidateSwingTimer(operator, amount)
|
||||
return false
|
||||
end
|
||||
|
||||
-- Constants for Slam window calculations
|
||||
local GCD_DURATION = 1.5 -- Global cooldown in seconds
|
||||
local DEFAULT_SLAM_CAST = 1.5 -- Default Slam cast time in vanilla WoW
|
||||
|
||||
-- Cache for Slam cast time from tooltip
|
||||
local cachedSlamCastTime = nil
|
||||
local slamCastTimeLastUpdate = 0
|
||||
local SLAM_CACHE_DURATION = 2 -- Re-scan tooltip every 2 seconds
|
||||
|
||||
-- Hidden tooltip for scanning spell info
|
||||
local SlamScanTooltip = nil
|
||||
|
||||
-- Create hidden tooltip for scanning (once)
|
||||
local function GetSlamScanTooltip()
|
||||
if not SlamScanTooltip then
|
||||
SlamScanTooltip = CreateFrame("GameTooltip", "CleveRoidsSlamScanTooltip", nil, "GameTooltipTemplate")
|
||||
SlamScanTooltip:SetOwner(WorldFrame, "ANCHOR_NONE")
|
||||
end
|
||||
return SlamScanTooltip
|
||||
end
|
||||
|
||||
-- Parse cast time from tooltip text (e.g., "1.5 sec cast" or "1.59 sec cast")
|
||||
local function ParseCastTimeFromText(text)
|
||||
if not text then return nil end
|
||||
-- Match patterns like "1.5 sec cast", "1.59 sec cast", "2 sec cast"
|
||||
local castTime = string.match(text, "(%d+%.?%d*) sec cast")
|
||||
if castTime then
|
||||
return tonumber(castTime)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Get Slam's spellbook slot
|
||||
local function GetSlamSpellSlot()
|
||||
-- Search through spellbook for Slam
|
||||
local i = 1
|
||||
while true do
|
||||
local spellName, spellRank = GetSpellName(i, BOOKTYPE_SPELL)
|
||||
if not spellName then break end
|
||||
if spellName == "Slam" then
|
||||
return i, BOOKTYPE_SPELL
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
-- Expose for debug command
|
||||
CleveRoids.GetSlamSpellSlot = GetSlamSpellSlot
|
||||
|
||||
-- Scan Slam tooltip for cast time
|
||||
local function ScanSlamCastTime()
|
||||
local slot, bookType = GetSlamSpellSlot()
|
||||
if not slot then return nil end
|
||||
|
||||
local tooltip = GetSlamScanTooltip()
|
||||
tooltip:ClearLines()
|
||||
tooltip:SetSpell(slot, bookType)
|
||||
|
||||
-- Scan tooltip lines for cast time
|
||||
for i = 1, tooltip:NumLines() do
|
||||
local leftText = getglobal("CleveRoidsSlamScanTooltipTextLeft" .. i)
|
||||
if leftText then
|
||||
local text = leftText:GetText()
|
||||
local castTime = ParseCastTimeFromText(text)
|
||||
if castTime then
|
||||
return castTime
|
||||
end
|
||||
end
|
||||
local rightText = getglobal("CleveRoidsSlamScanTooltipTextRight" .. i)
|
||||
if rightText then
|
||||
local text = rightText:GetText()
|
||||
local castTime = ParseCastTimeFromText(text)
|
||||
if castTime then
|
||||
return castTime
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Get Slam's cast time in seconds (with caching)
|
||||
-- Reads from spellbook tooltip to get accurate cast time with haste/talents
|
||||
function CleveRoids.GetSlamCastTime()
|
||||
local now = GetTime()
|
||||
|
||||
-- Use cached value if still valid
|
||||
if cachedSlamCastTime and (now - slamCastTimeLastUpdate) < SLAM_CACHE_DURATION then
|
||||
return cachedSlamCastTime
|
||||
end
|
||||
|
||||
-- Try to scan tooltip for cast time
|
||||
local castTime = ScanSlamCastTime()
|
||||
if castTime and castTime > 0 then
|
||||
cachedSlamCastTime = castTime
|
||||
slamCastTimeLastUpdate = now
|
||||
return castTime
|
||||
end
|
||||
|
||||
-- Fall back to default
|
||||
return DEFAULT_SLAM_CAST
|
||||
end
|
||||
|
||||
-- Force refresh of cached Slam cast time (call when buffs change)
|
||||
function CleveRoids.RefreshSlamCastTime()
|
||||
cachedSlamCastTime = nil
|
||||
slamCastTimeLastUpdate = 0
|
||||
end
|
||||
|
||||
-- Calculate maximum elapsed swing timer % to cast Slam without clipping auto-attack
|
||||
-- Formula: MaxSlamPercent = (SwingTimer - SlamCastTime) / SwingTimer * 100
|
||||
function CleveRoids.GetSlamWindowPercent()
|
||||
local attackSpeed = UnitAttackSpeed("player")
|
||||
if not attackSpeed or attackSpeed <= 0 then return 0 end
|
||||
|
||||
local slamCastTime = CleveRoids.GetSlamCastTime()
|
||||
local maxSlamStart = attackSpeed - slamCastTime
|
||||
|
||||
if maxSlamStart <= 0 then return 0 end -- Slam cast time exceeds swing timer
|
||||
|
||||
return (maxSlamStart / attackSpeed) * 100
|
||||
end
|
||||
|
||||
-- Calculate maximum elapsed swing timer % to cast instant without clipping NEXT Slam
|
||||
-- Scenario: No Slam this swing, cast instant, then Slam next swing without clipping
|
||||
-- Formula: MaxInstantPercent = (2 * SwingTimer - SlamCastTime - GCD) / SwingTimer * 100
|
||||
function CleveRoids.GetInstantWindowPercent()
|
||||
local attackSpeed = UnitAttackSpeed("player")
|
||||
if not attackSpeed or attackSpeed <= 0 then return 0 end
|
||||
|
||||
local slamCastTime = CleveRoids.GetSlamCastTime()
|
||||
local maxInstantStart = (2 * attackSpeed) - slamCastTime - GCD_DURATION
|
||||
|
||||
if maxInstantStart <= 0 then return 0 end -- Window is impossible with current timings
|
||||
|
||||
return (maxInstantStart / attackSpeed) * 100
|
||||
end
|
||||
|
||||
-- Validate if current swing timer is within the Slam window (no clip)
|
||||
-- Returns true if casting Slam NOW will NOT clip the auto-attack
|
||||
function CleveRoids.ValidateNoSlamClip()
|
||||
-- Check if SP_SwingTimer is loaded
|
||||
if st_timer == nil then
|
||||
if not CleveRoids._slamClipErrorShown then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[SuperCleveRoidMacros]|r The [noslamclip] conditional requires the SP_SwingTimer addon. Get it at: https://github.com/jrc13245/SP_SwingTimer", 1, 0.5, 0.5)
|
||||
CleveRoids._slamClipErrorShown = true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local attackSpeed = UnitAttackSpeed("player")
|
||||
if not attackSpeed or attackSpeed <= 0 then return false end
|
||||
|
||||
local timeElapsed = attackSpeed - st_timer
|
||||
local percentElapsed = (timeElapsed / attackSpeed) * 100
|
||||
local maxPercent = CleveRoids.GetSlamWindowPercent()
|
||||
|
||||
return percentElapsed <= maxPercent
|
||||
end
|
||||
|
||||
-- Validate if current swing timer is within the instant window for next Slam
|
||||
-- Returns true if casting an instant NOW will NOT cause the NEXT Slam to clip
|
||||
function CleveRoids.ValidateNoNextSlamClip()
|
||||
-- Check if SP_SwingTimer is loaded
|
||||
if st_timer == nil then
|
||||
if not CleveRoids._slamClipErrorShown then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[SuperCleveRoidMacros]|r The [nonextslamclip] conditional requires the SP_SwingTimer addon. Get it at: https://github.com/jrc13245/SP_SwingTimer", 1, 0.5, 0.5)
|
||||
CleveRoids._slamClipErrorShown = true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local attackSpeed = UnitAttackSpeed("player")
|
||||
if not attackSpeed or attackSpeed <= 0 then return false end
|
||||
|
||||
local timeElapsed = attackSpeed - st_timer
|
||||
local percentElapsed = (timeElapsed / attackSpeed) * 100
|
||||
local maxPercent = CleveRoids.GetInstantWindowPercent()
|
||||
|
||||
return percentElapsed <= maxPercent
|
||||
end
|
||||
|
||||
function CleveRoids.ValidateLevel(unit, operator, amount)
|
||||
if not unit or not operator or not amount then return false end
|
||||
local level = UnitLevel(unit)
|
||||
@@ -3509,6 +3691,36 @@ CleveRoids.Keywords = {
|
||||
end, conditionals, "notte")
|
||||
end,
|
||||
|
||||
-- Slam clip window conditionals for Warrior Slam rotation optimization
|
||||
-- Based on math: MaxSlamPercent = (SwingTimer - SlamCastTime) / SwingTimer * 100
|
||||
-- Requires SP_SwingTimer addon and Nampower for cast time lookup
|
||||
|
||||
-- [noslamclip] - True if casting Slam NOW will NOT clip the auto-attack
|
||||
-- Usage: /cast [noslamclip] Slam
|
||||
noslamclip = function(conditionals)
|
||||
return CleveRoids.ValidateNoSlamClip()
|
||||
end,
|
||||
|
||||
-- [slamclip] - True if casting Slam NOW WILL clip the auto-attack (negated)
|
||||
-- Usage: /cast [slamclip] Heroic Strike -- Use HS instead when past slam window
|
||||
slamclip = function(conditionals)
|
||||
return not CleveRoids.ValidateNoSlamClip()
|
||||
end,
|
||||
|
||||
-- [nonextslamclip] - True if casting an instant NOW will NOT cause NEXT Slam to clip
|
||||
-- Scenario: Skip Slam this swing, cast instant, then Slam next swing without clipping
|
||||
-- Formula: MaxInstantPercent = (2 * SwingTimer - SlamCastTime - GCD) / SwingTimer * 100
|
||||
-- Usage: /cast [nonextslamclip] Bloodthirst
|
||||
nonextslamclip = function(conditionals)
|
||||
return CleveRoids.ValidateNoNextSlamClip()
|
||||
end,
|
||||
|
||||
-- [nextslamclip] - True if casting an instant NOW WILL cause NEXT Slam to clip (negated)
|
||||
-- Usage: Use this when you want to know you're past the instant window
|
||||
nextslamclip = function(conditionals)
|
||||
return not CleveRoids.ValidateNoNextSlamClip()
|
||||
end,
|
||||
|
||||
-- Checks if the target uses a specific power type (mana, rage, energy)
|
||||
powertype = function(conditionals)
|
||||
local unit = conditionals.target or "target"
|
||||
|
||||
@@ -4237,6 +4237,8 @@ SlashCmdList["CLEVEROID"] = function(msg)
|
||||
DEFAULT_CHAT_FRAME:AddMessage('/cleveroid channeldebug - Toggle [channeltime] conditional debug output')
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00Action Slot Debug:|r")
|
||||
DEFAULT_CHAT_FRAME:AddMessage('/cleveroid slotdebug <slot> - Debug action slot state (tooltip/range/mana)')
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffffaa00Slam Rotation (Warrior):|r")
|
||||
DEFAULT_CHAT_FRAME:AddMessage('/cleveroid slamdebug - Show Slam cast time and clip window calculations')
|
||||
return
|
||||
end
|
||||
|
||||
@@ -5113,6 +5115,94 @@ SlashCmdList["CLEVEROID"] = function(msg)
|
||||
return
|
||||
end
|
||||
|
||||
-- slamdebug (show Slam cast time and window calculations for Warrior rotation)
|
||||
if cmd == "slamdebug" or cmd == "slam" or cmd == "slamtime" then
|
||||
CleveRoids.Print("|cff88ff88=== Slam Clip Window Debug ===|r")
|
||||
|
||||
-- Get swing timer info
|
||||
local attackSpeed = UnitAttackSpeed("player")
|
||||
if not attackSpeed or attackSpeed <= 0 then
|
||||
CleveRoids.Print("|cffff0000No attack speed available!|r")
|
||||
CleveRoids.Print("You need to be in combat or have a weapon equipped.")
|
||||
return
|
||||
end
|
||||
|
||||
-- Check SP_SwingTimer
|
||||
if st_timer == nil then
|
||||
CleveRoids.Print("|cffff0000SP_SwingTimer not detected!|r")
|
||||
CleveRoids.Print("The [noslamclip] conditionals require SP_SwingTimer addon.")
|
||||
CleveRoids.Print("Get it at: https://github.com/jrc13245/SP_SwingTimer")
|
||||
else
|
||||
local timeElapsed = attackSpeed - st_timer
|
||||
local percentElapsed = (timeElapsed / attackSpeed) * 100
|
||||
CleveRoids.Print("|cffffaa00SP_SwingTimer:|r st_timer=" .. string.format("%.3f", st_timer) .. "s")
|
||||
CleveRoids.Print("Swing elapsed: " .. string.format("%.2f", percentElapsed) .. "%")
|
||||
end
|
||||
|
||||
CleveRoids.Print(" ")
|
||||
CleveRoids.Print("|cffffaa00Swing Timer:|r " .. string.format("%.3f", attackSpeed) .. "s")
|
||||
|
||||
-- Get Slam cast time from tooltip scanning
|
||||
local slamCastTime = CleveRoids.GetSlamCastTime()
|
||||
local isDefault = (slamCastTime == 1.5)
|
||||
|
||||
CleveRoids.Print("|cffffaa00Slam Cast Time:|r " .. string.format("%.3f", slamCastTime) .. "s" .. (isDefault and " (default)" or " (from tooltip)"))
|
||||
|
||||
-- Show tooltip scanning diagnostic info
|
||||
if CleveRoids.GetSlamSpellSlot then
|
||||
local slot = CleveRoids.GetSlamSpellSlot()
|
||||
if slot then
|
||||
CleveRoids.Print(" Slam found in spellbook slot: " .. slot)
|
||||
else
|
||||
CleveRoids.Print(" |cffff0000Slam not found in spellbook!|r Are you a Warrior?")
|
||||
end
|
||||
end
|
||||
|
||||
if isDefault then
|
||||
CleveRoids.Print(" Note: Could not read from tooltip, using default 1.5s")
|
||||
end
|
||||
|
||||
-- Calculate windows
|
||||
CleveRoids.Print(" ")
|
||||
local slamWindowTime = attackSpeed - slamCastTime
|
||||
local slamWindow = 0
|
||||
if slamWindowTime > 0 then
|
||||
slamWindow = (slamWindowTime / attackSpeed) * 100
|
||||
end
|
||||
|
||||
local instantWindowTime = (2 * attackSpeed) - slamCastTime - 1.5
|
||||
local instantWindow = 0
|
||||
if instantWindowTime > 0 then
|
||||
instantWindow = (instantWindowTime / attackSpeed) * 100
|
||||
end
|
||||
|
||||
CleveRoids.Print("|cff00ff00=== Window Calculations ===|r")
|
||||
CleveRoids.Print("|cffffaa00Slam Window:|r " .. string.format("%.2f", slamWindow) .. "%")
|
||||
CleveRoids.Print(" Formula: (SwingTimer - SlamCast) / SwingTimer")
|
||||
CleveRoids.Print(" (" .. string.format("%.3f", attackSpeed) .. " - " .. string.format("%.3f", slamCastTime) .. ") / " .. string.format("%.3f", attackSpeed) .. " = " .. string.format("%.2f", slamWindow) .. "%")
|
||||
CleveRoids.Print(" Max time to cast Slam: " .. string.format("%.3f", slamWindowTime) .. "s into swing")
|
||||
|
||||
CleveRoids.Print(" ")
|
||||
CleveRoids.Print("|cffffaa00Instant Window:|r " .. string.format("%.2f", instantWindow) .. "%")
|
||||
CleveRoids.Print(" Formula: (2×SwingTimer - SlamCast - GCD) / SwingTimer")
|
||||
CleveRoids.Print(" (2×" .. string.format("%.3f", attackSpeed) .. " - " .. string.format("%.3f", slamCastTime) .. " - 1.5) / " .. string.format("%.3f", attackSpeed) .. " = " .. string.format("%.2f", instantWindow) .. "%")
|
||||
CleveRoids.Print(" Max time to cast instant: " .. string.format("%.3f", instantWindowTime) .. "s into swing")
|
||||
|
||||
-- Show current status
|
||||
if st_timer ~= nil then
|
||||
CleveRoids.Print(" ")
|
||||
CleveRoids.Print("|cff00ff00=== Current Status ===|r")
|
||||
local timeElapsed = attackSpeed - st_timer
|
||||
local percentElapsed = (timeElapsed / attackSpeed) * 100
|
||||
local inSlamWindow = percentElapsed <= slamWindow
|
||||
local inInstantWindow = percentElapsed <= instantWindow
|
||||
CleveRoids.Print("[noslamclip]: " .. (inSlamWindow and "|cff00ff00TRUE|r (safe to Slam)" or "|cffff0000FALSE|r (would clip)"))
|
||||
CleveRoids.Print("[nonextslamclip]: " .. (inInstantWindow and "|cff00ff00TRUE|r (safe to instant)" or "|cffff0000FALSE|r (would clip next Slam)"))
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- Unknown command fallback
|
||||
CleveRoids.Print("Usage:")
|
||||
DEFAULT_CHAT_FRAME:AddMessage("/cleveroid - Show current settings")
|
||||
|
||||
@@ -55,9 +55,6 @@ The addon will not function without all three DLL mods installed.
|
||||
/cleveroid talenttabs -- Show talent tab IDs
|
||||
/cleveroid listtab <tab> -- List talents in a tab
|
||||
/cleveroid talents -- Show current talent ranks
|
||||
/cleveroid testtalent <spellID> -- Test talent modifier
|
||||
/cleveroid diagnosetalent <spellID> -- Diagnose talent issues
|
||||
/cleveroid testequip <spellID> -- Test equipment modifier
|
||||
```
|
||||
|
||||
### Combo Point Tracking
|
||||
@@ -133,21 +130,23 @@ Both formats work identically:
|
||||
|
||||
### Multi-Value Conditionals
|
||||
|
||||
#### OR Operator (`/`)
|
||||
#### Conditionals are split by / for OR, & for AND...
|
||||
```lua
|
||||
[zone:Stormwind_City/Ironforge] -- In Stormwind OR Ironforge
|
||||
[nozone:Stormwind_City/Ironforge] -- NOT in Stormwind OR NOT in Ironforge
|
||||
[nomypower:>10/<20] -- Power NOT >10 OR NOT <20
|
||||
[zone:Stormwind_City/Ironforge] -- In at least one of the zones
|
||||
[zone:Stormwind_City&Ironforge] -- In both zones (impossible, but simple to understand)
|
||||
```
|
||||
#### Negated Conditionals are reversed...
|
||||
```lua
|
||||
[nozone:Stormwind_City/Ironforge] -- NOT in Stormwind AND NOT in Ironforge (neither)
|
||||
[nozone:Stormwind_City&Ironforge] -- NOT in Stormwind OR NOT in Ironforge (either)
|
||||
```
|
||||
#### More Examples
|
||||
```lua
|
||||
[nomypower:>10/<20] -- Power NOT >10 AND NOT <20
|
||||
[nomybuff:Mark_of_the_Wild/Thorns] -- Missing both buffs
|
||||
[nomybuff:"Seal of Wisdom"&"Seal of Light"] -- Missing at least one of the buffs
|
||||
```
|
||||
|
||||
#### AND Operator (`&`)
|
||||
```lua
|
||||
[zone:Stormwind_City&Elwynn_Forest] -- In both zones (subzone & zone)
|
||||
[nozone:Stormwind_City&Ironforge] -- NOT in Stormwind AND NOT in Ironforge
|
||||
[nomybuff:Mark_of_the_Wild&Thorns] -- Missing both buffs
|
||||
```
|
||||
|
||||
**Important:** `/` always means OR, even for negated conditionals. Use `&` for AND logic.
|
||||
|
||||
### Negation
|
||||
Prefix with `no` to negate (marked as "Noable" in tables):
|
||||
@@ -192,7 +191,7 @@ Use slot numbers (1-19):
|
||||
|
||||
| Prefix | Example | Description |
|
||||
|:------:|---------|-------------|
|
||||
| `!` | `!Attack`<br/>`!Cat Form` | Only use if not active<br/>Shorthand for `[nomybuff]` |
|
||||
| `!` | `!Attack`<br/>`!Cat Form` | Only use if not active<br/>Shorthand for `[nomybuff]` connected with an AND |
|
||||
| `?` | `?[equipped:Swords] Ability` | Hides icon/tooltip<br/>Must be first character |
|
||||
| `~` | `~Slow Fall` | Toggle buff on/off |
|
||||
|
||||
@@ -695,6 +694,63 @@ For 3.5s weapon:
|
||||
|
||||
---
|
||||
|
||||
# Slam Clip Conditionals (Warrior Only)
|
||||
|
||||
Specialized conditionals for Warrior Slam rotation to prevent auto-attack clipping.
|
||||
|
||||
**Note:** These conditionals are designed specifically for Warrior Slam rotation optimization. They are not intended for general-purpose use with other classes or abilities.
|
||||
|
||||
**Requirements:**
|
||||
- [SP_SwingTimer](https://github.com/jrc13245/SP_SwingTimer) addon
|
||||
- Warrior class (reads Slam cast time from spellbook tooltip)
|
||||
|
||||
**How It Works:**
|
||||
Slam pauses the swing timer if it would complete during the cast, causing auto-attack clipping. These conditionals calculate optimal windows based on your current swing timer and Slam cast time (accounting for haste and talents).
|
||||
|
||||
**The Math:**
|
||||
- **Slam Window**: `(SwingTimer - SlamCastTime) / SwingTimer × 100`
|
||||
- **Instant Window**: `(2 × SwingTimer - SlamCastTime - GCD) / SwingTimer × 100`
|
||||
|
||||
**Conditionals:**
|
||||
```lua
|
||||
[noslamclip] -- True if Slam NOW won't clip auto-attack
|
||||
[slamclip] -- True if Slam NOW WILL clip (negated)
|
||||
[nonextslamclip] -- True if instant NOW won't cause NEXT Slam to clip
|
||||
[nextslamclip] -- True if instant NOW WILL cause NEXT Slam to clip (negated)
|
||||
```
|
||||
|
||||
**Debug Command:**
|
||||
```lua
|
||||
/cleveroid slamdebug -- Show cast time, windows, and current status
|
||||
```
|
||||
|
||||
**Example Values** (2.7s swing timer, 1.6s Slam cast):
|
||||
- Slam window: ~40% (can cast Slam up to 40% into swing)
|
||||
- Instant window: ~85% (can cast BT/WW up to 85% into swing)
|
||||
|
||||
**Examples:**
|
||||
```lua
|
||||
-- Cast Slam only when safe
|
||||
#showtooltip Slam
|
||||
/cast [noslamclip] Slam
|
||||
|
||||
-- Use Heroic Strike when past Slam window
|
||||
#showtooltip Heroic Strike
|
||||
/cast [slamclip] Heroic Strike
|
||||
|
||||
-- Cast Bloodthirst only when next Slam won't clip
|
||||
#showtooltip Bloodthirst
|
||||
/cast [nonextslamclip] Bloodthirst
|
||||
|
||||
-- Full Slam rotation macro
|
||||
#showtooltip
|
||||
/cast [noslamclip] Slam
|
||||
/cast [nonextslamclip] Bloodthirst
|
||||
/cast [slamclip] Heroic Strike
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Threat Integration
|
||||
|
||||
Requires [TWThreat](https://github.com/MarcelineVQ/TWThreat).
|
||||
|
||||
Reference in New Issue
Block a user