mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7bbb8df010 | |||
| 96dbe9aca8 | |||
| 1de455f96e | |||
| d93d97a469 | |||
| 2a562e0550 | |||
| daaab2fd11 | |||
| 43f1ba378a | |||
| 828acc0c4b | |||
| f296425c94 | |||
| 4dd5326172 | |||
| c5ddc6c313 |
+6
-19
@@ -3,16 +3,11 @@
|
||||
|
||||
ClassicAPI is a client mod (sibling to Nampower/SuperWoW) that backports the
|
||||
modern C_* API into the 1.12.1 Lua environment. It is a HARD REQUIREMENT of
|
||||
this addon (ClassicAPI v1.15.0+, which added frame:RegisterUnitEvent), so the
|
||||
wrappers below call the API directly — no fallbacks. The load-time requirement
|
||||
check (Core.lua) uses IsAvailable() to warn when the DLL is missing and
|
||||
HasMinimumVersion() when it's too old; users who don't want ClassicAPI should
|
||||
run the upstream addon.
|
||||
|
||||
The minimum is not advisory: Utility.lua calls frame:RegisterUnitEvent at file
|
||||
scope, so an older ClassicAPI aborts that chunk and leaves most of the addon
|
||||
undefined. Raise the Core.lua minimum in step with any new API adopted at file
|
||||
scope.
|
||||
this addon (ClassicAPI v1.12.1+, which added the positional
|
||||
C_UnitAuras.UnitAura), so the wrappers below call the API directly — no
|
||||
fallbacks. The load-time requirement check (Core.lua) uses IsAvailable() to
|
||||
warn when the DLL is missing and HasMinimumVersion() when it's too old; users
|
||||
who don't want ClassicAPI should run the upstream addon.
|
||||
|
||||
Detection: the global CLASSIC_API_VERSION is defined once the client has
|
||||
booted, encoded as X*10000 + Y*100 + Z for a vX.Y.Z tag (untagged dev builds
|
||||
@@ -42,14 +37,6 @@ function API.GetVersionNumber()
|
||||
return CLASSIC_API_VERSION or 0
|
||||
end
|
||||
|
||||
-- Returns the loaded version as major, minor, patch (0, 0, 0 if absent).
|
||||
function API.GetVersion()
|
||||
local v = CLASSIC_API_VERSION or 0
|
||||
local major = math.floor(v / 10000)
|
||||
local minor = math.floor(v / 100) - major * 100
|
||||
return major, minor, v - math.floor(v / 100) * 100
|
||||
end
|
||||
|
||||
-- True if the ClassicAPI client mod is loaded at all.
|
||||
function API.IsAvailable()
|
||||
return CLASSIC_API_VERSION ~= nil
|
||||
@@ -69,7 +56,7 @@ end
|
||||
|
||||
-- Scan one aura range of `unit` (filter = "HELPFUL" or "HARMFUL") for an aura
|
||||
-- matching the dispel type. Uses the positional C_UnitAuras.UnitAura (added in
|
||||
-- ClassicAPI v1.12.1, below this addon's minimum) -- no table allocated per slot, with
|
||||
-- ClassicAPI v1.12.1, this addon's minimum) -- no table allocated per slot, with
|
||||
-- dispelName as the 4th return. The filtered index self-terminates at the end of
|
||||
-- the range (nil name); 48 is a backstop over vanilla's 32 helpful / 16 harmful slots.
|
||||
local function scanDispel(unit, filter, dispelType, wantAny)
|
||||
|
||||
@@ -745,7 +745,7 @@ function Extension.OnLoad()
|
||||
Extension.RegisterEvent("SPELLCAST_FAILED", "OnSpellcastFailed")
|
||||
Extension.RegisterEvent("SPELLCAST_INTERRUPTED", "OnSpellcastInterrupted")
|
||||
Extension.RegisterEvent("PLAYER_TARGET_CHANGED", "OnTargetChanged")
|
||||
Extension.RegisterUnitEvent("UNIT_AURA", "OnUnitAura", "target", "player")
|
||||
Extension.RegisterEvent("UNIT_AURA", "OnUnitAura")
|
||||
Extension.RegisterEvent("PLAYER_COMBO_POINTS", "OnComboPointsChanged")
|
||||
|
||||
-- PERFORMANCE OPTIMIZATION: Removed OnUpdate polling for combo points
|
||||
@@ -765,10 +765,10 @@ function Extension.OnTargetChanged()
|
||||
CleveRoids.UpdateComboPoints()
|
||||
end
|
||||
|
||||
-- Registered as a unit event for target and player, so arg1 is always one of
|
||||
-- those two -- no token check needed.
|
||||
function Extension.OnUnitAura()
|
||||
CleveRoids.UpdateComboPoints()
|
||||
if arg1 == "target" or arg1 == "player" then
|
||||
CleveRoids.UpdateComboPoints()
|
||||
end
|
||||
end
|
||||
|
||||
function Extension.OnComboPointsChanged()
|
||||
|
||||
@@ -198,12 +198,10 @@ requirementCheckFrame:SetScript("OnEvent", function()
|
||||
local hasNampower30 = hasNampower and CleveRoids.NampowerAPI
|
||||
and CleveRoids.NampowerAPI.HasMinimumVersion(3, 0, 0)
|
||||
local hasClassicAPI = CleveRoids.ClassicAPI and CleveRoids.ClassicAPI.IsAvailable()
|
||||
-- v1.15.0 added frame:RegisterUnitEvent, which Utility.lua calls at file scope.
|
||||
-- On an older build that call raises, aborting the rest of the chunk -- so this
|
||||
-- is not a degraded-features warning, it's "the addon did not finish loading".
|
||||
local hasClassicAPI1150 = hasClassicAPI and CleveRoids.ClassicAPI.HasMinimumVersion(1, 15, 0)
|
||||
-- v1.12.1 added the positional C_UnitAuras.UnitAura the dispel conditionals use.
|
||||
local hasClassicAPI1121 = hasClassicAPI and CleveRoids.ClassicAPI.HasMinimumVersion(1, 12, 1)
|
||||
|
||||
if not hasNampower30 or not hasUnitXP or not hasClassicAPI or not hasClassicAPI1150 then
|
||||
if not hasNampower30 or not hasUnitXP or not hasClassicAPI or not hasClassicAPI1121 then
|
||||
-- Show warnings (don't disable — tearing down a partially-initialized addon causes hangs)
|
||||
if not hasNampower then
|
||||
CleveRoids.Print("|cFFFF9900WARNING:|r |cFF00FFFFAvitasia's Nampower v3.0.0+|r is required:")
|
||||
@@ -223,11 +221,10 @@ requirementCheckFrame:SetScript("OnEvent", function()
|
||||
CleveRoids.Print("|cFFFF9900WARNING:|r |cFF00FFFFClassicAPI|r is required:")
|
||||
CleveRoids.Print("https://github.com/brues-code/ClassicAPI")
|
||||
CleveRoids.Print("Dispel-type and movement conditionals will be unavailable without it.")
|
||||
elseif not hasClassicAPI1150 then
|
||||
local major, minor, patch = CleveRoids.ClassicAPI.GetVersion()
|
||||
CleveRoids.Print(format("|cFFFF9900WARNING:|r |cFF00FFFFClassicAPI v1.15.0+|r is required (you have v%d.%d.%d):", major, minor, patch))
|
||||
elseif not hasClassicAPI1121 then
|
||||
CleveRoids.Print("|cFFFF9900WARNING:|r |cFF00FFFFClassicAPI v1.12.1+|r is required:")
|
||||
CleveRoids.Print("https://github.com/brues-code/ClassicAPI")
|
||||
CleveRoids.Print("The addon cannot finish loading on this version -- update ClassicAPI.")
|
||||
CleveRoids.Print("Dispel-type conditionals will be unavailable with this older version.")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -594,10 +591,6 @@ frame:SetScript("OnEvent", function()
|
||||
if type(CleveRoidMacros.macrocheck) ~= "number" then
|
||||
CleveRoidMacros.macrocheck = 1 -- enabled by default
|
||||
end
|
||||
|
||||
-- The saved realtime setting is only readable now; the unit streams were
|
||||
-- registered at load assuming event-driven mode.
|
||||
CleveRoids.ApplyUnitStreamEvents()
|
||||
end)
|
||||
|
||||
-- Queues a full update of all action bars.
|
||||
@@ -1142,7 +1135,7 @@ local publishedDisplay = {}
|
||||
-- nothing matched" and shows the question mark. Skipping the call instead would hand
|
||||
-- the macro back to ClassicAPI's own #showtooltip parser.
|
||||
function CleveRoids.PublishDisplay(actions)
|
||||
if not CleveRoids.ClassicAPIMacroDisplay then return end
|
||||
if not CleveRoids.useClassicAPIDisplay then return end
|
||||
local macroID = actions and actions.macroID
|
||||
if not macroID then return end -- SuperMacro macros have no Blizzard index
|
||||
|
||||
@@ -1157,7 +1150,7 @@ end
|
||||
-- ClassicAPI re-evaluates nothing for us. Covers macros that aren't on a bar too,
|
||||
-- which is what keeps the macro window grid's icons correct.
|
||||
function CleveRoids.PublishAllDisplays()
|
||||
if not CleveRoids.ClassicAPIMacroDisplay then return end
|
||||
if not CleveRoids.useClassicAPIDisplay then return end
|
||||
-- Forget what we published so every macro republishes once. Callers reach here
|
||||
-- after login and after a re-parse, where a cached value could otherwise
|
||||
-- suppress the publish a freshly rebuilt macro still needs.
|
||||
@@ -1172,16 +1165,13 @@ end
|
||||
|
||||
-- Hand every macro back to ClassicAPI's own parser and stop claiming ownership.
|
||||
function CleveRoids.ReleaseDisplays()
|
||||
if not CleveRoids.ClassicAPIMacroDisplay then return end
|
||||
-- Sweep the whole index space rather than the macros we know we published:
|
||||
-- PublishAllDisplays clears that record on every re-parse, so a macro claimed
|
||||
-- before one and gone after it would keep our last value forever. Releasing a
|
||||
-- slot we never claimed costs nothing, and this runs once.
|
||||
for i = 1, CleveRoids.MAX_MACRO_SLOTS do
|
||||
if not CleveRoids.useClassicAPIDisplay then return end
|
||||
for i = 1, 36 do
|
||||
C_Macro.SetMacroDisplay(i, nil)
|
||||
end
|
||||
publishedDisplay = {}
|
||||
CleveRoids.ClassicAPIMacroDisplay = false
|
||||
CleveRoids.useClassicAPIDisplay = false
|
||||
end
|
||||
|
||||
-- PERFORMANCE: Static buffer references for hot path
|
||||
@@ -1227,10 +1217,17 @@ function CleveRoids.TestForAllActiveActions()
|
||||
local slots = actionsToSlots[actions]
|
||||
local stateChanged = CleveRoids.TestForActiveAction(actions)
|
||||
if stateChanged then
|
||||
-- Publishing repaints every slot holding this macro through the client's
|
||||
-- own notifier, so there is no per-slot fan-out to do here. It no-ops once
|
||||
-- ReleaseDisplays has handed the macros back.
|
||||
CleveRoids.PublishDisplay(actions)
|
||||
if CleveRoids.useClassicAPIDisplay then
|
||||
-- Publishing repaints every slot holding this macro through the
|
||||
-- client's own notifier, so the per-slot fan-out below is redundant.
|
||||
CleveRoids.PublishDisplay(actions)
|
||||
else
|
||||
-- Send event to ALL slots that use this macro
|
||||
local count = slots._count
|
||||
for j = 1, count do
|
||||
CleveRoids.SendEventForAction(slots[j], "ACTIONBAR_SLOT_CHANGED", slots[j])
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Clear for reuse (reset count and clear buffer reference)
|
||||
for j = 1, slots._count do
|
||||
@@ -4467,36 +4464,18 @@ if type(C_LossOfControl) == "table" then
|
||||
CleveRoids.Frame:RegisterEvent("LOSS_OF_CONTROL_ADDED")
|
||||
CleveRoids.Frame:RegisterEvent("LOSS_OF_CONTROL_UPDATE")
|
||||
end
|
||||
-- The unit state streams that drive icon refresh: GUID events when Nampower
|
||||
-- provides them (v2.39+, one event per unit change rather than one per token),
|
||||
-- else the stock per-token events. These cannot become RegisterUnitEvent calls
|
||||
-- -- the handlers ignore the unit and refresh every macro, because a conditional
|
||||
-- may name any unit ([@party3,hp:<50], @focus, @mouseover), so narrowing the
|
||||
-- token set would leave those icons stale.
|
||||
local unitStreamEvents
|
||||
-- Use GUID events when available (v2.39+), fall back to standard per-token events
|
||||
if CleveRoids.NampowerAPI.features.hasUnitGuidEvents then
|
||||
unitStreamEvents = { "UNIT_AURA_GUID", "UNIT_HEALTH_GUID", "UNIT_MANA_GUID", "UNIT_RAGE_GUID", "UNIT_ENERGY_GUID" }
|
||||
CleveRoids.Frame:RegisterEvent("UNIT_AURA_GUID")
|
||||
CleveRoids.Frame:RegisterEvent("UNIT_HEALTH_GUID")
|
||||
CleveRoids.Frame:RegisterEvent("UNIT_MANA_GUID")
|
||||
CleveRoids.Frame:RegisterEvent("UNIT_RAGE_GUID")
|
||||
CleveRoids.Frame:RegisterEvent("UNIT_ENERGY_GUID")
|
||||
else
|
||||
unitStreamEvents = { "UNIT_AURA", "UNIT_HEALTH", "UNIT_POWER" }
|
||||
CleveRoids.Frame:RegisterEvent("UNIT_AURA")
|
||||
CleveRoids.Frame:RegisterEvent("UNIT_HEALTH")
|
||||
CleveRoids.Frame:RegisterEvent("UNIT_POWER")
|
||||
end
|
||||
|
||||
-- They do fire continuously for every unit in range, and in realtime mode their
|
||||
-- handlers do nothing at all: the OnUpdate refreshes on every throttled tick and
|
||||
-- QueueActionUpdate no-ops. Rather than pay a Lua dispatch per event to return
|
||||
-- early, drop the registrations entirely while realtime is on. Re-applied at
|
||||
-- VARIABLES_LOADED (when the saved value is first known) and whenever
|
||||
-- `/cleveroid realtime` flips it.
|
||||
function CleveRoids.ApplyUnitStreamEvents()
|
||||
local eventDriven = not CleveRoidMacros or CleveRoidMacros.realtime == 0
|
||||
for i = 1, table.getn(unitStreamEvents) do
|
||||
if eventDriven then
|
||||
CleveRoids.Frame:RegisterEvent(unitStreamEvents[i])
|
||||
else
|
||||
CleveRoids.Frame:UnregisterEvent(unitStreamEvents[i])
|
||||
end
|
||||
end
|
||||
end
|
||||
CleveRoids.ApplyUnitStreamEvents()
|
||||
if CleveRoids.hasSuperwow then
|
||||
CleveRoids.Frame:RegisterEvent("UNIT_CASTEVENT")
|
||||
end
|
||||
@@ -5829,8 +5808,6 @@ SlashCmdList["CLEVEROID"] = function(msg)
|
||||
local num = tonumber(val)
|
||||
if num == 0 or num == 1 then
|
||||
CleveRoidMacros.realtime = num
|
||||
-- The unit streams are only worth receiving in event-driven mode.
|
||||
CleveRoids.ApplyUnitStreamEvents()
|
||||
CleveRoids.Print("realtime set to " .. num)
|
||||
else
|
||||
CleveRoids.Print("Usage: /cleveroid realtime 0 or 1 - Force realtime updates rather than event based updates (Default: 0. 1 = on, increases CPU load.)")
|
||||
|
||||
@@ -1180,7 +1180,7 @@ local function ReportAllMacroErrors()
|
||||
|
||||
-- Collect body (syntax) errors per macro. Macro names are no longer
|
||||
-- restricted (slot/index-based identification), so no name checks here.
|
||||
for i = 1, CleveRoids.MAX_MACRO_SLOTS do
|
||||
for i = 1, 36 do
|
||||
local nameOk, name = pcall(GetMacroInfo, i)
|
||||
if nameOk and name and name ~= "" then
|
||||
local errors = {}
|
||||
|
||||
@@ -66,10 +66,6 @@ function CleveRoids.RegisterExtension(name)
|
||||
CleveRoids.RegisterEvent(name, eventName, callbackName)
|
||||
end
|
||||
|
||||
extension.RegisterUnitEvent = function(eventName, callbackName, ...)
|
||||
CleveRoids.RegisterUnitEvent(name, eventName, callbackName, unpack(arg))
|
||||
end
|
||||
|
||||
extension.Hook = function(functionName, callbackName, dontCallOriginal)
|
||||
CleveRoids.RegisterHook(name, functionName, callbackName, dontCallOriginal)
|
||||
end
|
||||
@@ -150,20 +146,6 @@ function CleveRoids.RegisterEvent(extensionName, eventName, callbackName)
|
||||
extension.internal.frame:RegisterEvent(eventName)
|
||||
end
|
||||
|
||||
-- Registers a callback for a UNIT_* event, filtered to the given unit tokens.
|
||||
-- The callback then only runs for those units: the client drops every other
|
||||
-- unit's copy, instead of all of them reaching Lua to be compared away. Use
|
||||
-- this over RegisterEvent whenever the handler starts by testing arg1.
|
||||
-- extensionName: The name of the extension trying to register the callback
|
||||
-- eventName: The UNIT_* event to register
|
||||
-- callbackName: The name of the callback that gets called when the event fires
|
||||
-- ...: the unit tokens to accept (e.g. "player", "target")
|
||||
function CleveRoids.RegisterUnitEvent(extensionName, eventName, callbackName, ...)
|
||||
local extension = CleveRoids.Extensions[extensionName]
|
||||
extension.internal.eventHandlers[eventName] = callbackName
|
||||
extension.internal.frame:RegisterUnitEvent(eventName, unpack(arg))
|
||||
end
|
||||
|
||||
-- Hooks the given function by it's name
|
||||
-- extensionName: The name of the extension trying to register the callback
|
||||
-- functionName: The name of the function that'll be hooked
|
||||
|
||||
@@ -19,15 +19,6 @@ CleveRoids.mouseOverResolvers = {}
|
||||
CleveRoids.mouseoverUnit = CleveRoids.mouseoverUnit or nil
|
||||
CleveRoids.mouseOverUnit = nil
|
||||
|
||||
-- Every macro slot the client can hold: 18 account-wide (1-18) followed by 18
|
||||
-- character-specific (19-36). This is the index space GetMacroInfo and
|
||||
-- C_Macro.SetMacroDisplay address, and it is fixed -- GetNumMacros() returns how
|
||||
-- many of each tab are *used*, which cannot be summed into a range, because the
|
||||
-- character block starts at 19 no matter how few account macros exist. Blizzard's
|
||||
-- own MAX_MACROS is no help either: it lives in the load-on-demand Blizzard_MacroUI
|
||||
-- and is nil until the player opens the macro window.
|
||||
CleveRoids.MAX_MACRO_SLOTS = 36
|
||||
|
||||
-- Environment flags
|
||||
CleveRoids.hasSuperwow = SetAutoloot and true or false
|
||||
CleveRoids.hasTurtle = (type(_G.TURTLE_WOW_VERSION) ~= "nil")
|
||||
@@ -38,17 +29,14 @@ CleveRoids.supported = CleveRoids.hasTurtle
|
||||
-- tooltip, cooldown sweep, range and usable state all come from the client -- including
|
||||
-- the drag cursor and the macro window grid, which Lua cannot reach.
|
||||
--
|
||||
-- ClassicAPI stands down from macro display entirely when it sees this addon loaded;
|
||||
-- ClassicAPIMacroDisplay is what tells it we drive it instead, and ReleaseDisplays
|
||||
-- clears it to hand every macro back. A fork that leaves the flag unset keeps the old
|
||||
-- behavior -- both must never drive the same buttons. It doubles as the internal
|
||||
-- "may we call C_Macro.SetMacroDisplay" guard, so the two can never disagree.
|
||||
--
|
||||
-- Feature-detect rather than version-check: SetMacroDisplay ships in ClassicAPI
|
||||
-- v1.15.0, this addon's minimum, so a nil here means the client mod is missing
|
||||
-- outright -- the case Core.lua's requirement check warns about but keeps running.
|
||||
CleveRoids.ClassicAPIMacroDisplay =
|
||||
-- Feature-detect rather than version-check: the API is unreleased, so
|
||||
-- CLASSIC_API_VERSION reports the dev sentinel. ClassicAPI stands down from macro
|
||||
-- display entirely when it sees this addon loaded; ClassicAPIMacroDisplay is what
|
||||
-- tells it we drive it instead. A fork that leaves the flag unset keeps the old
|
||||
-- behavior -- both must never drive the same buttons.
|
||||
CleveRoids.useClassicAPIDisplay =
|
||||
(type(C_Macro) == "table" and C_Macro.SetMacroDisplay ~= nil) and true or false
|
||||
CleveRoids.ClassicAPIMacroDisplay = CleveRoids.useClassicAPIDisplay
|
||||
|
||||
CleveRoids.ParsedMsg = {}
|
||||
CleveRoids.ExpandedGroups = {}
|
||||
|
||||
+11
-1
@@ -1096,7 +1096,17 @@ function CleveRoids.ValidateAllMacros()
|
||||
local results = {}
|
||||
local totalErrors = 0
|
||||
|
||||
for i = 1, CleveRoids.MAX_MACRO_SLOTS do
|
||||
-- Account-wide macros are indexed from 1 up to GetNumMacros().
|
||||
-- Character-specific macros occupy the slots immediately following the account-wide ones.
|
||||
-- In Classic clients, the macro UI has 18 General (Account) slots and 18 Character-Specific slots.
|
||||
local numAccountMacros = GetNumMacros()
|
||||
|
||||
-- The WoW API GetMacroInfo(index) supports indexing up to 36 (1-18 for General, 19-36 for Character)
|
||||
-- in Classic clients, even though the total is GetNumMacros() + GetNumCharacterMacros() in Retail.
|
||||
-- To ensure we check all 36 possible slots:
|
||||
local totalSlots = 36
|
||||
|
||||
for i = 1, totalSlots do
|
||||
local nameSuccess, name = pcall(GetMacroInfo, i)
|
||||
|
||||
-- Check if GetMacroInfo returned a name (i.e., the slot is used)
|
||||
|
||||
@@ -10,7 +10,7 @@ Enhanced macro addon for World of Warcraft 1.12.1 (Vanilla/Turtle WoW) with dyna
|
||||
|-----|:--------:|---------|
|
||||
| [Nampower](https://github.com/brues-code/nampower/releases) (v3.0.0+) | ✅ | Spell queueing, DBC data, auto-attack events |
|
||||
| [UnitXP_SP3](https://codeberg.org/konaka/UnitXP_SP3/releases) | ✅ | Distance checks, `[multiscan]` enemy scanning |
|
||||
| [ClassicAPI](https://github.com/brues-code/ClassicAPI/releases) (v1.15.0+) | ✅ | Modern `C_*` API: dispel-type conditionals (`[magic]`, `[curse]`, …), `[moving]` speed, unit-filtered events |
|
||||
| [ClassicAPI](https://github.com/brues-code/ClassicAPI/releases) | ✅ | Modern `C_*` API: dispel-type conditionals (`[magic]`, `[curse]`, …), `[moving]` speed |
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
+2
-2
@@ -4115,7 +4115,7 @@ end)
|
||||
|
||||
local ev = CreateFrame("Frame", "CleveRoidsLibDebuffFrame", UIParent)
|
||||
ev:RegisterEvent("PLAYER_TARGET_CHANGED")
|
||||
ev:RegisterUnitEvent("UNIT_AURA", "target")
|
||||
ev:RegisterEvent("UNIT_AURA")
|
||||
ev:RegisterEvent("ADDON_LOADED") -- For pfUI integration initialization
|
||||
ev:RegisterEvent("ZONE_CHANGED_NEW_AREA") -- Clear known enemy GUIDs on zone change
|
||||
|
||||
@@ -4214,7 +4214,7 @@ ev:SetScript("OnEvent", function()
|
||||
end
|
||||
SeedUnit("target")
|
||||
|
||||
elseif event == "UNIT_AURA" then
|
||||
elseif event == "UNIT_AURA" and arg1 == "target" then
|
||||
SeedUnit("target")
|
||||
|
||||
elseif event == "UNIT_CASTEVENT" then
|
||||
|
||||
Reference in New Issue
Block a user