Add /tfb test mode for previewing target buffs and debuffs
- /tfb test [buffs debuffs] and an options-panel button fill the target frame with fake auras (default 32 buffs, 16 debuffs) to check layout, stacks, borders, timers and sweeps. Targets the player if nothing is targeted. - Fake auras restart when they expire so timers keep running. - Works on the native path too (icons, stacks, borders only). - Bump to 0.9.4; tests now 21 scenarios. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019W3rwutyoLNHxXPycYWSMc
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.9.4
|
||||||
|
|
||||||
|
- Add `/tfb test [buffs debuffs]` and an options-panel button: fake auras on the
|
||||||
|
current target for checking layout, timers, stacks, borders and sweeps.
|
||||||
|
- Tests: 21 scenarios.
|
||||||
|
|
||||||
## 0.9.3
|
## 0.9.3
|
||||||
|
|
||||||
- Warn once in chat when ShaguTweaks Debuff Timer is drawing on target debuffs.
|
- Warn once in chat when ShaguTweaks Debuff Timer is drawing on target debuffs.
|
||||||
|
|||||||
@@ -38,10 +38,17 @@ aura order and lists your own auras first.
|
|||||||
| `/tfb sweeps [on\|off]` | Reverse cooldown sweeps | off |
|
| `/tfb sweeps [on\|off]` | Reverse cooldown sweeps | off |
|
||||||
| `/tfb size 6-20` | Countdown font size | 9 |
|
| `/tfb size 6-20` | Countdown font size | 9 |
|
||||||
| `/tfb warning 0-30` | Seconds left at which text turns red | 5 |
|
| `/tfb warning 0-30` | Seconds left at which text turns red | 5 |
|
||||||
|
| `/tfb test [buffs debuffs]` | Fake auras on your target for checking layout (default 32 and 16; 0-32 each). Run again or `/tfb test off` to stop. | off |
|
||||||
| `/tfb reset` | Restore defaults | |
|
| `/tfb reset` | Restore defaults | |
|
||||||
|
|
||||||
Settings are saved and kept even while ClassicAPI is absent.
|
Settings are saved and kept even while ClassicAPI is absent.
|
||||||
|
|
||||||
|
**Test mode** fills the target frame with fake buffs and debuffs so you can check
|
||||||
|
layout, borders, stack counts and, with ClassicAPI, timers, sweeps and the
|
||||||
|
red warning. It targets you if you have no target, restarts expiring fake auras
|
||||||
|
so timers keep running, and is not saved; `/reload` turns it off. Debuffs past 16
|
||||||
|
spill into the buff row, as real ones do. The options panel has a matching button.
|
||||||
|
|
||||||
## Compatibility with ShaguTweaks
|
## Compatibility with ShaguTweaks
|
||||||
|
|
||||||
Checked against the upstream [ShaguTweaks](https://github.com/shagu/ShaguTweaks) source.
|
Checked against the upstream [ShaguTweaks](https://github.com/shagu/ShaguTweaks) source.
|
||||||
|
|||||||
+18
-1
@@ -72,6 +72,14 @@ local function CreateOptions()
|
|||||||
end);
|
end);
|
||||||
sliders[i] = slider;
|
sliders[i] = slider;
|
||||||
end
|
end
|
||||||
|
local testButton = CreateFrame("Button", "TargetFrameBuffTestButton", panel, "UIPanelButtonTemplate");
|
||||||
|
testButton:SetWidth(180);
|
||||||
|
testButton:SetHeight(22);
|
||||||
|
testButton:SetPoint("TOPLEFT", panel, "TOPLEFT", 22, -330);
|
||||||
|
testButton:SetText("Toggle test auras");
|
||||||
|
testButton:SetScript("OnClick", function()
|
||||||
|
TargetFrameBuff_SetTest(not TargetFrameBuff_IsTest());
|
||||||
|
end);
|
||||||
local saved = {};
|
local saved = {};
|
||||||
local function Sync()
|
local function Sync()
|
||||||
for i = 1, 3 do checks[i]:SetChecked(TargetFrameBuffDB[checks[i].key]); end
|
for i = 1, 3 do checks[i]:SetChecked(TargetFrameBuffDB[checks[i].key]); end
|
||||||
@@ -115,6 +123,15 @@ SlashCmdList["TARGETFRAMEBUFF"] = function(message)
|
|||||||
Print("Choose a number from "..min.." to "..max.."."); return;
|
Print("Choose a number from "..min.." to "..max.."."); return;
|
||||||
end
|
end
|
||||||
TargetFrameBuffDB[command == "size" and "timerSize" or "warning"] = math.floor(number + 0.5);
|
TargetFrameBuffDB[command == "size" and "timerSize" or "warning"] = math.floor(number + 0.5);
|
||||||
|
elseif (command == "test") then
|
||||||
|
local _, _, buffs, debuffs = string.find(value, "^(%d+)%s*(%d*)$");
|
||||||
|
local on;
|
||||||
|
if (value == "off") then on = false;
|
||||||
|
elseif (value == "" ) then on = not TargetFrameBuff_IsTest();
|
||||||
|
elseif (value == "on" or buffs) then on = true;
|
||||||
|
else Print("Use /tfb test [on|off] or /tfb test <buffs> <debuffs>."); return; end
|
||||||
|
local _, nb, nd = TargetFrameBuff_SetTest(on, tonumber(buffs), tonumber(debuffs));
|
||||||
|
Print("test auras: "..(on and ("on ("..nb.." buffs, "..nd.." debuffs). Needs a target; /tfb test turns it off.") or "off"));
|
||||||
elseif (command == "reset") then
|
elseif (command == "reset") then
|
||||||
TargetFrameBuffDB = nil;
|
TargetFrameBuffDB = nil;
|
||||||
Validate();
|
Validate();
|
||||||
@@ -122,7 +139,7 @@ SlashCmdList["TARGETFRAMEBUFF"] = function(message)
|
|||||||
CreateOptions();
|
CreateOptions();
|
||||||
InterfaceOptionsFrame_OpenToCategory(panel);
|
InterfaceOptionsFrame_OpenToCategory(panel);
|
||||||
else
|
else
|
||||||
Print("/tfb timers|highlight|sweeps [on|off], /tfb size 6-20, /tfb warning 0-30, /tfb reset");
|
Print("/tfb timers|highlight|sweeps [on|off], /tfb size 6-20, /tfb warning 0-30, /tfb test [buffs debuffs], /tfb reset");
|
||||||
return;
|
return;
|
||||||
end
|
end
|
||||||
RefreshAuras();
|
RefreshAuras();
|
||||||
|
|||||||
+91
-6
@@ -26,6 +26,42 @@ local lHelpfulSlots = {};
|
|||||||
local lHarmfulSlots = {};
|
local lHarmfulSlots = {};
|
||||||
local lRemovableColor = {r = 1, g = 0.82, b = 0};
|
local lRemovableColor = {r = 1, g = 0.82, b = 0};
|
||||||
|
|
||||||
|
-- Test mode (/tfb test): fake auras on whatever you are targeting, for checking
|
||||||
|
-- layout, timers, stacks and borders. Lasts until turned off or /reload.
|
||||||
|
local lTestMode = false;
|
||||||
|
local lTestBuffs = 32;
|
||||||
|
local lTestDebuffs = 16;
|
||||||
|
local lTestStart = 0;
|
||||||
|
local lTestElapsed = 0;
|
||||||
|
local TEST_ICONS = {
|
||||||
|
"Interface\\Icons\\Spell_Holy_WordFortitude", "Interface\\Icons\\Spell_Nature_Regeneration",
|
||||||
|
"Interface\\Icons\\Spell_Shadow_ShadowWordPain", "Interface\\Icons\\Spell_Fire_FlameBolt",
|
||||||
|
"Interface\\Icons\\Spell_Frost_FrostArmor02", "Interface\\Icons\\Spell_Nature_Slow",
|
||||||
|
"Interface\\Icons\\Ability_Rogue_Rupture", "Interface\\Icons\\Spell_Holy_PowerWordShield"
|
||||||
|
};
|
||||||
|
local TEST_TYPES = {"Magic", "Curse", "Disease", "Poison"};
|
||||||
|
-- Mixed lengths so seconds, minutes, hours and the red warning all appear.
|
||||||
|
local TEST_DURATIONS = {10, 18, 45, 90, 300, 900, 1800, 3600, 7200};
|
||||||
|
|
||||||
|
-- Lua 5.0 has no % operator.
|
||||||
|
local function TargetFrameBuff_Mod(a, b)
|
||||||
|
return a - b * math.floor(a / b);
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Same values as UnitAura: name, icon, count, dispelType, duration, expiration,
|
||||||
|
-- source, removable. Each aura restarts when its time runs out.
|
||||||
|
local function TargetFrameBuff_TestAura(pass, index)
|
||||||
|
local duration = TEST_DURATIONS[TargetFrameBuff_Mod(index * 2, table.getn(TEST_DURATIONS)) + 1];
|
||||||
|
local expiration = lTestStart + duration * (math.floor((GetTime() - lTestStart) / duration) + 1);
|
||||||
|
local count = 1;
|
||||||
|
if (TargetFrameBuff_Mod(index, 4) == 0) then count = TargetFrameBuff_Mod(index, 7) + 2; end
|
||||||
|
local dispelType = nil;
|
||||||
|
if (pass == 2) then dispelType = TEST_TYPES[TargetFrameBuff_Mod(index, 5) + 1]; end
|
||||||
|
local icon = TEST_ICONS[TargetFrameBuff_Mod(index, table.getn(TEST_ICONS)) + 1];
|
||||||
|
return "Test aura "..index, icon, count, dispelType, duration, expiration, "player",
|
||||||
|
(pass == 1 and TargetFrameBuff_Mod(index, 5) == 0);
|
||||||
|
end
|
||||||
|
|
||||||
local function TargetFrameBuff_Setting(key)
|
local function TargetFrameBuff_Setting(key)
|
||||||
if (TargetFrameBuffDB and TargetFrameBuffDB[key] ~= nil) then
|
if (TargetFrameBuffDB and TargetFrameBuffDB[key] ~= nil) then
|
||||||
return TargetFrameBuffDB[key];
|
return TargetFrameBuffDB[key];
|
||||||
@@ -49,7 +85,11 @@ local function TargetFrameBuff_AuraOnEnter()
|
|||||||
TargetFrameBuff_UpdateClassicAPI();
|
TargetFrameBuff_UpdateClassicAPI();
|
||||||
if (not this.auraIndex) then GameTooltip:Hide(); return; end
|
if (not this.auraIndex) then GameTooltip:Hide(); return; end
|
||||||
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT", 15, -25);
|
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT", 15, -25);
|
||||||
GameTooltip:SetUnitAura("target", this.auraIndex, this.auraFilter);
|
if (lTestMode) then
|
||||||
|
GameTooltip:SetText("Test "..(this.auraFilter == "HARMFUL" and "debuff " or "buff ")..this.auraIndex);
|
||||||
|
else
|
||||||
|
GameTooltip:SetUnitAura("target", this.auraIndex, this.auraFilter);
|
||||||
|
end
|
||||||
GameTooltip:Show();
|
GameTooltip:Show();
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -205,7 +245,12 @@ end
|
|||||||
function TargetFrameBuff_UpdateNative()
|
function TargetFrameBuff_UpdateNative()
|
||||||
local button, buff, i;
|
local button, buff, i;
|
||||||
for i=1, TARGETFRAMEBUFF_MAX_TARGET_BUFFS do
|
for i=1, TARGETFRAMEBUFF_MAX_TARGET_BUFFS do
|
||||||
buff = UnitBuff("target", i);
|
if (lTestMode) then
|
||||||
|
buff = nil;
|
||||||
|
if (i <= lTestBuffs) then buff = TEST_ICONS[TargetFrameBuff_Mod(i, table.getn(TEST_ICONS)) + 1]; end
|
||||||
|
else
|
||||||
|
buff = UnitBuff("target", i);
|
||||||
|
end
|
||||||
button = lBuffButtons[i];
|
button = lBuffButtons[i];
|
||||||
if (buff) then
|
if (buff) then
|
||||||
getglobal("TargetFrameBuff"..i.."Icon"):SetTexture(buff);
|
getglobal("TargetFrameBuff"..i.."Icon"):SetTexture(buff);
|
||||||
@@ -218,7 +263,16 @@ function TargetFrameBuff_UpdateNative()
|
|||||||
|
|
||||||
local debuff, debuffApplications, debuffType;
|
local debuff, debuffApplications, debuffType;
|
||||||
for i=1, TARGETFRAMEBUFF_MAX_TARGET_DEBUFFS do
|
for i=1, TARGETFRAMEBUFF_MAX_TARGET_DEBUFFS do
|
||||||
debuff, debuffApplications, debuffType = UnitDebuff("target", i);
|
if (lTestMode) then
|
||||||
|
debuff, debuffApplications, debuffType = nil, nil, nil;
|
||||||
|
if (i <= lTestDebuffs) then
|
||||||
|
debuff = TEST_ICONS[TargetFrameBuff_Mod(i, table.getn(TEST_ICONS)) + 1];
|
||||||
|
debuffApplications = TargetFrameBuff_Mod(i, 4) == 0 and 3 or 1;
|
||||||
|
debuffType = TEST_TYPES[TargetFrameBuff_Mod(i, 5) + 1];
|
||||||
|
end
|
||||||
|
else
|
||||||
|
debuff, debuffApplications, debuffType = UnitDebuff("target", i);
|
||||||
|
end
|
||||||
button = lDebuffButtons[i];
|
button = lDebuffButtons[i];
|
||||||
if (debuff) then
|
if (debuff) then
|
||||||
TargetFrameBuff_ColorBorder(i, debuffType);
|
TargetFrameBuff_ColorBorder(i, debuffType);
|
||||||
@@ -320,7 +374,7 @@ local function TargetFrameBuff_ShowAura(button, filter, index, icon, count, disp
|
|||||||
button.timerText = nil;
|
button.timerText = nil;
|
||||||
button.timer:Hide();
|
button.timer:Hide();
|
||||||
end
|
end
|
||||||
if (GameTooltip:IsOwned(button)) then
|
if (GameTooltip:IsOwned(button) and not lTestMode) then
|
||||||
GameTooltip:SetUnitAura("target", index, filter);
|
GameTooltip:SetUnitAura("target", index, filter);
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -342,13 +396,17 @@ function TargetFrameBuff_UpdateClassicAPI()
|
|||||||
else
|
else
|
||||||
filter, slots = "HARMFUL", lHarmfulSlots;
|
filter, slots = "HARMFUL", lHarmfulSlots;
|
||||||
end
|
end
|
||||||
if (batched) then
|
if (lTestMode) then
|
||||||
|
n = (pass == 1) and lTestBuffs or lTestDebuffs;
|
||||||
|
elseif (batched) then
|
||||||
token, n = C_UnitAuras.GetAuraSlots("target", filter, nil, nil, slots);
|
token, n = C_UnitAuras.GetAuraSlots("target", filter, nil, nil, slots);
|
||||||
else
|
else
|
||||||
n = TARGETFRAMEBUFF_MAX_TARGET_BUFFS + TARGETFRAMEBUFF_MAX_TARGET_DEBUFFS;
|
n = TARGETFRAMEBUFF_MAX_TARGET_BUFFS + TARGETFRAMEBUFF_MAX_TARGET_DEBUFFS;
|
||||||
end
|
end
|
||||||
for index = 1, n do
|
for index = 1, n do
|
||||||
if (batched) then
|
if (lTestMode) then
|
||||||
|
name, icon, count, dispelType, duration, expiration, source, removable = TargetFrameBuff_TestAura(pass, index);
|
||||||
|
elseif (batched) then
|
||||||
name, icon, count, dispelType, duration, expiration, source, removable = C_UnitAuras.UnitAuraBySlot("target", slots[index]);
|
name, icon, count, dispelType, duration, expiration, source, removable = C_UnitAuras.UnitAuraBySlot("target", slots[index]);
|
||||||
else
|
else
|
||||||
name, icon, count, dispelType, duration, expiration, source, removable = C_UnitAuras.UnitAura("target", index, filter);
|
name, icon, count, dispelType, duration, expiration, source, removable = C_UnitAuras.UnitAura("target", index, filter);
|
||||||
@@ -455,6 +513,13 @@ end
|
|||||||
-- Driven by TargetFrameBuffFrame, which is parented to TargetFrame, so this
|
-- Driven by TargetFrameBuffFrame, which is parented to TargetFrame, so this
|
||||||
-- only runs while you have a target.
|
-- only runs while you have a target.
|
||||||
function TargetFrameBuff_OnUpdate(elapsed)
|
function TargetFrameBuff_OnUpdate(elapsed)
|
||||||
|
if (lTestMode) then
|
||||||
|
lTestElapsed = lTestElapsed + (elapsed or 0);
|
||||||
|
if (lTestElapsed >= 1) then
|
||||||
|
lTestElapsed = 0;
|
||||||
|
TargetFrameBuff_Update();
|
||||||
|
end
|
||||||
|
end
|
||||||
if (not lBuffButtons[1] or not lBuffButtons[1].timer or not TargetFrameBuff_HasClassicAPI()) then
|
if (not lBuffButtons[1] or not lBuffButtons[1].timer or not TargetFrameBuff_HasClassicAPI()) then
|
||||||
return;
|
return;
|
||||||
end
|
end
|
||||||
@@ -500,3 +565,23 @@ function TargetFrameBuff_Restore()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Turn test mode on or off. buffs (0-32) and debuffs (0-32) are optional; debuffs
|
||||||
|
-- past 16 spill into the buff row, as real ones do with ClassicAPI.
|
||||||
|
function TargetFrameBuff_SetTest(enabled, buffs, debuffs)
|
||||||
|
lTestMode = enabled and true or false;
|
||||||
|
if (buffs) then lTestBuffs = math.max(0, math.min(TARGETFRAMEBUFF_MAX_TARGET_BUFFS, math.floor(buffs))); end
|
||||||
|
if (debuffs) then lTestDebuffs = math.max(0, math.min(TARGETFRAMEBUFF_MAX_TARGET_BUFFS, math.floor(debuffs))); end
|
||||||
|
lTestStart = GetTime();
|
||||||
|
lTestElapsed = 0;
|
||||||
|
if (lTestMode and UnitExists and TargetUnit and not UnitExists("target")) then
|
||||||
|
TargetUnit("player");
|
||||||
|
end
|
||||||
|
TargetFrameBuff_Update();
|
||||||
|
return lTestMode, lTestBuffs, lTestDebuffs;
|
||||||
|
end
|
||||||
|
|
||||||
|
function TargetFrameBuff_IsTest()
|
||||||
|
return lTestMode;
|
||||||
|
end
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
## Interface: 1700
|
## Interface: 1700
|
||||||
## Title: TargetFrameBuff v0.9.3
|
## Title: TargetFrameBuff v0.9.4
|
||||||
## Notes: Shows up to 32 Buffs & 16 Debuffs on a Target, with countdowns when ClassicAPI is installed
|
## Notes: Shows up to 32 Buffs & 16 Debuffs on a Target, with countdowns when ClassicAPI is installed
|
||||||
## Author: lua@lumpn.de (modified)
|
## Author: lua@lumpn.de (modified)
|
||||||
## SavedVariables: TargetFrameBuffDB
|
## SavedVariables: TargetFrameBuffDB
|
||||||
|
|||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
# TargetFrameBuff v0.9.3 validation
|
# TargetFrameBuff v0.9.4 validation
|
||||||
|
|
||||||
19 simulated Lua 5.1 regression scenarios passed, plus XML parsing. These cover
|
21 simulated Lua 5.1 regression scenarios passed, plus XML parsing. These cover
|
||||||
old/native clients, aura reordering, overflow, settings, sweep lifecycle, delta
|
old/native clients, aura reordering, overflow, settings, sweep lifecycle, delta
|
||||||
refreshes and full updates, the Debuff Timer overlap warning, target/pet/spellbook changes, reused delta payloads,
|
refreshes and full updates, the Debuff Timer overlap warning, test mode, target/pet/spellbook changes, reused delta payloads,
|
||||||
no polling in delta mode, and native-number/flash suppression.
|
no polling in delta mode, and native-number/flash suppression.
|
||||||
|
|
||||||
ClassicAPI 1.17.10+ is detected through GetAuraDataByAuraInstanceID, introduced with
|
ClassicAPI 1.17.10+ is detected through GetAuraDataByAuraInstanceID, introduced with
|
||||||
|
|||||||
@@ -226,6 +226,33 @@ TargetFrameDebuff1.cd={readable={}}
|
|||||||
Tick(); Tick()
|
Tick(); Tick()
|
||||||
for _,m in ipairs(messages) do assert(not string.find(m,"Debuff Timer",1,true)) end
|
for _,m in ipairs(messages) do assert(not string.find(m,"Debuff Timer",1,true)) end
|
||||||
''')
|
''')
|
||||||
|
run("test mode fills all 32 buffs and 16 debuffs, then restores real auras", 'InstallAPI(true); auras.HELPFUL={Aura("Real")}', '''
|
||||||
|
Tick(); assert(TargetFrameBuff1Icon.texture=="Real" and not TargetFrameBuff2:IsShown())
|
||||||
|
SlashCmdList.TARGETFRAMEBUFF("test")
|
||||||
|
assert(TargetFrameBuff_IsTest())
|
||||||
|
for i=1,32 do assert(_G["TargetFrameBuff"..i]:IsShown() and _G["TargetFrameBuff"..i].timer.text) end
|
||||||
|
for i=1,16 do assert(_G["TargetFrameDebuff"..i]:IsShown() and _G["TargetFrameDebuff"..i].timer.text) end
|
||||||
|
assert(TargetFrameBuff5.auraBorder:IsShown() and not TargetFrameBuff6.auraBorder:IsShown())
|
||||||
|
assert(TargetFrameBuff4.auraCount.text==6)
|
||||||
|
this=TargetFrameBuff3; this.scripts.OnEnter()
|
||||||
|
assert(GameTooltip.tooltip==nil)
|
||||||
|
now=now+500; Tick(); TargetFrameBuff_OnUpdate(1)
|
||||||
|
for i=1,32 do assert(_G["TargetFrameBuff"..i]:IsShown()) end
|
||||||
|
SlashCmdList.TARGETFRAMEBUFF("test")
|
||||||
|
assert(not TargetFrameBuff_IsTest())
|
||||||
|
assert(TargetFrameBuff1Icon.texture=="Real" and not TargetFrameBuff2:IsShown())
|
||||||
|
''')
|
||||||
|
run("test mode custom counts, clamping, native path and bad input", 'nativeBuffs={}; nativeDebuffs={}', '''
|
||||||
|
SlashCmdList.TARGETFRAMEBUFF("test 3 5")
|
||||||
|
assert(TargetFrameBuff3:IsShown() and not TargetFrameBuff4:IsShown())
|
||||||
|
assert(TargetFrameDebuff5:IsShown() and not TargetFrameDebuff6:IsShown())
|
||||||
|
SlashCmdList.TARGETFRAMEBUFF("test 40 99")
|
||||||
|
assert(TargetFrameBuff32:IsShown() and TargetFrameDebuff16:IsShown())
|
||||||
|
SlashCmdList.TARGETFRAMEBUFF("test banana")
|
||||||
|
assert(TargetFrameBuff_IsTest())
|
||||||
|
SlashCmdList.TARGETFRAMEBUFF("test off")
|
||||||
|
assert(not TargetFrameBuff_IsTest() and not TargetFrameBuff1:IsShown() and not TargetFrameDebuff1:IsShown())
|
||||||
|
''')
|
||||||
ET.parse(addon/"TargetFrameBuff.xml")
|
ET.parse(addon/"TargetFrameBuff.xml")
|
||||||
print("PASS XML parsing")
|
print("PASS XML parsing")
|
||||||
print(f"{passed} Lua regression scenarios passed; XML parsed.")
|
print(f"{passed} Lua regression scenarios passed; XML parsed.")
|
||||||
|
|||||||
Reference in New Issue
Block a user