abandon C_Timer
This commit is contained in:
+1
-5
@@ -28,12 +28,8 @@ WeakAuras.prettyPrint = function(msg)
|
|||||||
print(WeakAuras.printPrefix .. msg)
|
print(WeakAuras.printPrefix .. msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
WeakAuras.versionMismatchPrint = function()
|
|
||||||
WeakAuras.prettyPrint("You need to restart your game client to complete the WeakAuras update!")
|
|
||||||
end
|
|
||||||
|
|
||||||
if versionString ~= versionStringFromToc and versionStringFromToc ~= "Dev" then
|
if versionString ~= versionStringFromToc and versionStringFromToc ~= "Dev" then
|
||||||
C_Timer.After(1, WeakAuras.versionMismatchPrint)
|
WeakAuras.prettyPrint("You need to restart your game client to complete the WeakAuras update!")
|
||||||
end
|
end
|
||||||
|
|
||||||
WeakAuras.PowerAurasPath = "Interface\\Addons\\WeakAuras\\PowerAurasMedia\\Auras\\"
|
WeakAuras.PowerAurasPath = "Interface\\Addons\\WeakAuras\\PowerAurasMedia\\Auras\\"
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
-- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient
|
-- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient
|
||||||
-- data structure that allows easy dispatching and fast rescheduling. Timers can be registered
|
-- data structure that allows easy dispatching and fast rescheduling. Timers can be registered
|
||||||
-- or canceled at any time, even from within a running timer, without conflict or large overhead.\\
|
-- or canceled at any time, even from within a running timer, without conflict or large overhead.\\
|
||||||
-- AceTimer is currently limited to firing timers at a frequency of 0.01s as this is what the WoW timer API
|
-- AceTimer is currently limited to firing timers at a frequency of 0.01s.
|
||||||
-- restricts us to.
|
|
||||||
--
|
--
|
||||||
-- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you
|
-- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you
|
||||||
-- need to cancel the timer you just registered.
|
-- need to cancel the timer you just registered.
|
||||||
@@ -15,23 +14,69 @@
|
|||||||
-- make into AceTimer.
|
-- make into AceTimer.
|
||||||
-- @class file
|
-- @class file
|
||||||
-- @name AceTimer-3.0
|
-- @name AceTimer-3.0
|
||||||
-- @release $Id: AceTimer-3.0.lua 1202 2019-05-15 23:11:22Z nevcairiel $
|
-- @release $Id$
|
||||||
|
|
||||||
local MAJOR, MINOR = "AceTimer-3.0", 17 -- Bump minor on changes
|
local MAJOR, MINOR = "AceTimer-3.0", 117 -- Bump minor on changes
|
||||||
local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||||
|
|
||||||
if not AceTimer then return end -- No upgrade needed
|
if not AceTimer then return end -- No upgrade needed
|
||||||
|
AceTimer.frame = AceTimer.frame or CreateFrame("Frame", "AceTimer30Frame")
|
||||||
AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list
|
AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list
|
||||||
local activeTimers = AceTimer.activeTimers -- Upvalue our private data
|
local activeTimers = AceTimer.activeTimers -- Upvalue our private data
|
||||||
|
|
||||||
-- Lua APIs
|
-- Lua APIs
|
||||||
|
local assert, loadstring, rawset, tconcat = assert, loadstring, rawset, table.concat
|
||||||
local type, unpack, next, error, select = type, unpack, next, error, select
|
local type, unpack, next, error, select = type, unpack, next, error, select
|
||||||
-- WoW APIs
|
-- WoW APIs
|
||||||
local GetTime, C_TimerAfter = GetTime, C_Timer.After
|
local GetTime = GetTime
|
||||||
|
|
||||||
|
--[[
|
||||||
|
xpcall safecall implementation
|
||||||
|
]]
|
||||||
|
local xpcall = xpcall
|
||||||
|
|
||||||
|
local function errorhandler(err)
|
||||||
|
return geterrorhandler()(err)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function CreateDispatcher(argCount)
|
||||||
|
local code = [[
|
||||||
|
local xpcall, eh = ...
|
||||||
|
local method, ARGS
|
||||||
|
local function call() return method(ARGS) end
|
||||||
|
|
||||||
|
local function dispatch(func, ...)
|
||||||
|
method = func
|
||||||
|
if not method then return end
|
||||||
|
ARGS = ...
|
||||||
|
return xpcall(call, eh)
|
||||||
|
end
|
||||||
|
|
||||||
|
return dispatch
|
||||||
|
]]
|
||||||
|
|
||||||
|
local ARGS = {}
|
||||||
|
for i = 1, argCount do ARGS[i] = "arg"..i end
|
||||||
|
code = code:gsub("ARGS", tconcat(ARGS, ", "))
|
||||||
|
return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler)
|
||||||
|
end
|
||||||
|
|
||||||
|
local Dispatchers = setmetatable({}, {__index=function(self, argCount)
|
||||||
|
local dispatcher = CreateDispatcher(argCount)
|
||||||
|
rawset(self, argCount, dispatcher)
|
||||||
|
return dispatcher
|
||||||
|
end})
|
||||||
|
Dispatchers[0] = function(func)
|
||||||
|
return xpcall(func, errorhandler)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function safecall(func, ...)
|
||||||
|
return Dispatchers[select("#", ...)](func, ...)
|
||||||
|
end
|
||||||
|
|
||||||
local function new(self, loop, func, delay, ...)
|
local function new(self, loop, func, delay, ...)
|
||||||
if delay < 0.01 then
|
if delay < 0.01 then
|
||||||
delay = 0.01 -- Restrict to the lowest time that the C_Timer API allows us
|
delay = 0.01 -- Restrict to the lowest time
|
||||||
end
|
end
|
||||||
|
|
||||||
local timer = {
|
local timer = {
|
||||||
@@ -40,39 +85,13 @@ local function new(self, loop, func, delay, ...)
|
|||||||
looping = loop,
|
looping = loop,
|
||||||
argsCount = select("#", ...),
|
argsCount = select("#", ...),
|
||||||
delay = delay,
|
delay = delay,
|
||||||
|
timeleft = delay,
|
||||||
ends = GetTime() + delay,
|
ends = GetTime() + delay,
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
|
|
||||||
activeTimers[timer] = timer
|
activeTimers[timer] = timer
|
||||||
|
|
||||||
-- Create new timer closure to wrap the "timer" object
|
|
||||||
timer.callback = function()
|
|
||||||
if not timer.cancelled then
|
|
||||||
if type(timer.func) == "string" then
|
|
||||||
-- We manually set the unpack count to prevent issues with an arg set that contains nil and ends with nil
|
|
||||||
-- e.g. local t = {1, 2, nil, 3, nil} print(#t) will result in 2, instead of 5. This fixes said issue.
|
|
||||||
timer.object[timer.func](timer.object, unpack(timer, 1, timer.argsCount))
|
|
||||||
else
|
|
||||||
timer.func(unpack(timer, 1, timer.argsCount))
|
|
||||||
end
|
|
||||||
|
|
||||||
if timer.looping and not timer.cancelled then
|
|
||||||
-- Compensate delay to get a perfect average delay, even if individual times don't match up perfectly
|
|
||||||
-- due to fps differences
|
|
||||||
local time = GetTime()
|
|
||||||
local delay = timer.delay - (time - timer.ends)
|
|
||||||
-- Ensure the delay doesn't go below the threshold
|
|
||||||
if delay < 0.01 then delay = 0.01 end
|
|
||||||
C_TimerAfter(delay, timer.callback)
|
|
||||||
timer.ends = time + delay
|
|
||||||
else
|
|
||||||
activeTimers[timer.handle or timer] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
C_TimerAfter(delay, timer.callback)
|
|
||||||
return timer
|
return timer
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -211,7 +230,6 @@ if oldminor and oldminor < 10 then
|
|||||||
elseif oldminor and oldminor < 17 then
|
elseif oldminor and oldminor < 17 then
|
||||||
-- Upgrade from old animation based timers to C_Timer.After timers.
|
-- Upgrade from old animation based timers to C_Timer.After timers.
|
||||||
AceTimer.inactiveTimers = nil
|
AceTimer.inactiveTimers = nil
|
||||||
AceTimer.frame = nil
|
|
||||||
local oldTimers = AceTimer.activeTimers
|
local oldTimers = AceTimer.activeTimers
|
||||||
-- Clear old timer table and update upvalue
|
-- Clear old timer table and update upvalue
|
||||||
AceTimer.activeTimers = {}
|
AceTimer.activeTimers = {}
|
||||||
@@ -276,3 +294,41 @@ end
|
|||||||
for addon in next, AceTimer.embeds do
|
for addon in next, AceTimer.embeds do
|
||||||
AceTimer:Embed(addon)
|
AceTimer:Embed(addon)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
AceTimer.frame:SetScript("OnUpdate", function(self, elapsed)
|
||||||
|
-- local total = 0
|
||||||
|
|
||||||
|
for _, timer in next, activeTimers do
|
||||||
|
-- print(timer.timeleft, timer.object.name)
|
||||||
|
if not timer.cancelled then
|
||||||
|
if timer.timeleft > elapsed then
|
||||||
|
timer.timeleft = timer.timeleft - elapsed
|
||||||
|
else
|
||||||
|
if type(timer.func) == "string" then
|
||||||
|
-- We manually set the unpack count to prevent issues with an arg set that contains nil and ends with nil
|
||||||
|
-- e.g. local t = {1, 2, nil, 3, nil} print(#t) will result in 2, instead of 5. This fixes said issue.
|
||||||
|
safecall(timer.object[timer.func], timer.object, unpack(timer, 1, timer.argsCount))
|
||||||
|
else
|
||||||
|
safecall(timer.func, unpack(timer, 1, timer.argsCount))
|
||||||
|
end
|
||||||
|
|
||||||
|
if timer.looping and not timer.cancelled then
|
||||||
|
-- Compensate delay to get a perfect average delay, even if individual times don't match up perfectly
|
||||||
|
-- due to fps differences
|
||||||
|
local time = GetTime()
|
||||||
|
local delay = timer.delay - (time - timer.ends)
|
||||||
|
-- Ensure the delay doesn't go below the threshold
|
||||||
|
if delay < 0.01 then delay = 0.01 end
|
||||||
|
timer.ends = time + delay
|
||||||
|
timer.timeleft = timer.delay
|
||||||
|
else
|
||||||
|
activeTimers[timer.handle or timer] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if total == 0 then
|
||||||
|
-- self:Hide()
|
||||||
|
-- end
|
||||||
|
end)
|
||||||
@@ -7,7 +7,7 @@ if not lib then return end
|
|||||||
lib.callbacks = lib.callbacks or LibStub("CallbackHandler-1.0"):New(lib)
|
lib.callbacks = lib.callbacks or LibStub("CallbackHandler-1.0"):New(lib)
|
||||||
local callbacks = lib.callbacks
|
local callbacks = lib.callbacks
|
||||||
|
|
||||||
local GetPlayerInfoByGUID, UnitExists, IsAddOnLoaded, C_Timer, UnitIsUnit, SecureButton_GetUnit = GetPlayerInfoByGUID, UnitExists, IsAddOnLoaded, C_Timer, UnitIsUnit, SecureButton_GetUnit
|
local GetPlayerInfoByGUID, UnitExists, IsAddOnLoaded, UnitIsUnit, SecureButton_GetUnit = GetPlayerInfoByGUID, UnitExists, IsAddOnLoaded, UnitIsUnit, SecureButton_GetUnit
|
||||||
local tinsert, CopyTable, wipe = tinsert, CopyTable, wipe
|
local tinsert, CopyTable, wipe = tinsert, CopyTable, wipe
|
||||||
|
|
||||||
local maxDepth = 50
|
local maxDepth = 50
|
||||||
@@ -118,14 +118,23 @@ local function doScanForUnitFrames()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local waitFrame = CreateFrame("Frame")
|
||||||
|
local function waitFrame_OnUpdate(self, elapsed)
|
||||||
|
self.delay = (self.delay or 1) - elapsed
|
||||||
|
if self.delay < elapsed then
|
||||||
|
doScanForUnitFrames()
|
||||||
|
self:SetScript("OnUpdate", nil)
|
||||||
|
self.delay = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function ScanForUnitFrames(noDelay)
|
local function ScanForUnitFrames(noDelay)
|
||||||
if noDelay then
|
if noDelay then
|
||||||
doScanForUnitFrames()
|
doScanForUnitFrames()
|
||||||
elseif not wait then
|
elseif not wait then
|
||||||
wait = true
|
wait = true
|
||||||
C_Timer.After(1, function()
|
waitFrame:SetScript("OnUpdate", waitFrame_OnUpdate)
|
||||||
doScanForUnitFrames()
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -30,32 +30,6 @@ function CreateFromMixins(...)
|
|||||||
return Mixin({}, ...)
|
return Mixin({}, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Lerp(startValue, endValue, amount)
|
|
||||||
return (1 - amount) * startValue + amount * endValue
|
|
||||||
end
|
|
||||||
|
|
||||||
function Clamp(value, min, max)
|
|
||||||
if value > max then
|
|
||||||
return max
|
|
||||||
elseif value < min then
|
|
||||||
return min
|
|
||||||
end
|
|
||||||
return value
|
|
||||||
end
|
|
||||||
|
|
||||||
function Saturate(value)
|
|
||||||
return Clamp(value, 0.0, 1.0)
|
|
||||||
end
|
|
||||||
|
|
||||||
local TARGET_FRAME_PER_SEC = 60.0;
|
|
||||||
function DeltaLerp(startValue, endValue, amount, timeSec)
|
|
||||||
return Lerp(startValue, endValue, Saturate(amount * timeSec * TARGET_FRAME_PER_SEC));
|
|
||||||
end
|
|
||||||
|
|
||||||
function FrameDeltaLerp(startValue, endValue, amount, tickTime)
|
|
||||||
return DeltaLerp(startValue, endValue, amount, tickTime)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Round(value)
|
function Round(value)
|
||||||
if value < 0 then
|
if value < 0 then
|
||||||
return ceil(value - .5);
|
return ceil(value - .5);
|
||||||
@@ -71,69 +45,6 @@ function tIndexOf(tbl, item)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local g_updatingBars = {};
|
|
||||||
|
|
||||||
local function IsCloseEnough(bar, newValue, targetValue)
|
|
||||||
local min, max = bar:GetMinMaxValues();
|
|
||||||
local range = max - min
|
|
||||||
if range > 0.0 then
|
|
||||||
return math.abs((newValue - targetValue) / range) < .00001
|
|
||||||
end
|
|
||||||
|
|
||||||
return true;
|
|
||||||
end
|
|
||||||
|
|
||||||
do
|
|
||||||
local f = CreateFrame("Frame")
|
|
||||||
f:Show()
|
|
||||||
f:SetScript("OnUpdate", function(_, elapsed)
|
|
||||||
for bar, targetValue in pairs(g_updatingBars) do
|
|
||||||
local effectiveTargetValue = Clamp(targetValue, bar:GetMinMaxValues())
|
|
||||||
local newValue = FrameDeltaLerp(bar:GetValue(), effectiveTargetValue, .25, elapsed)
|
|
||||||
|
|
||||||
if IsCloseEnough(bar, newValue, effectiveTargetValue) then
|
|
||||||
g_updatingBars[bar] = nil
|
|
||||||
bar:SetValue(effectiveTargetValue)
|
|
||||||
else
|
|
||||||
bar:SetValue(newValue)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
SmoothStatusBarMixin = {}
|
|
||||||
|
|
||||||
function SmoothStatusBarMixin:ResetSmoothedValue(value)
|
|
||||||
local targetValue = g_updatingBars[self]
|
|
||||||
if targetValue then
|
|
||||||
g_updatingBars[self] = nil
|
|
||||||
self:SetValue(value or targetValue)
|
|
||||||
elseif value then
|
|
||||||
self:SetValue(value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function SmoothStatusBarMixin:SetSmoothedValue(value)
|
|
||||||
g_updatingBars[self] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
function SmoothStatusBarMixin:SetMinMaxSmoothedValue(min, max)
|
|
||||||
self:SetMinMaxValues(min, max)
|
|
||||||
|
|
||||||
local targetValue = g_updatingBars[self]
|
|
||||||
if targetValue then
|
|
||||||
local ratio = 1
|
|
||||||
if max ~= 0 and self.lastSmoothedMax and self.lastSmoothedMax ~= 0 then
|
|
||||||
ratio = max / self.lastSmoothedMax
|
|
||||||
end
|
|
||||||
|
|
||||||
g_updatingBars[self] = targetValue * ratio
|
|
||||||
end
|
|
||||||
|
|
||||||
self.lastSmoothedMin = min
|
|
||||||
self.lastSmoothedMax = max
|
|
||||||
end
|
|
||||||
|
|
||||||
local oldGetInstanceDifficulty = GetInstanceDifficulty
|
local oldGetInstanceDifficulty = GetInstanceDifficulty
|
||||||
function GetInstanceDifficulty()
|
function GetInstanceDifficulty()
|
||||||
local diff = oldGetInstanceDifficulty()
|
local diff = oldGetInstanceDifficulty()
|
||||||
@@ -162,102 +73,6 @@ function GetNumGroupMembers()
|
|||||||
return GetNumRaidMembers()
|
return GetNumRaidMembers()
|
||||||
end
|
end
|
||||||
|
|
||||||
if not C_Timer or C_Timer._version ~= 2 then
|
|
||||||
local setmetatable = setmetatable
|
|
||||||
local type = type
|
|
||||||
local tinsert = table.insert
|
|
||||||
local tremove = table.remove
|
|
||||||
|
|
||||||
C_Timer = C_Timer or {}
|
|
||||||
C_Timer._version = 2
|
|
||||||
|
|
||||||
local TickerPrototype = {}
|
|
||||||
local TickerMetatable = {
|
|
||||||
__index = TickerPrototype,
|
|
||||||
__metatable = true
|
|
||||||
}
|
|
||||||
|
|
||||||
local waitTable = {}
|
|
||||||
local waitFrame = TimerFrame or CreateFrame("Frame", "TimerFrame", UIParent)
|
|
||||||
waitFrame:SetScript("OnUpdate", function(self, elapsed)
|
|
||||||
local total = #waitTable
|
|
||||||
local i = 1
|
|
||||||
|
|
||||||
while i <= total do
|
|
||||||
local ticker = waitTable[i]
|
|
||||||
|
|
||||||
if ticker._cancelled then
|
|
||||||
tremove(waitTable, i)
|
|
||||||
total = total - 1
|
|
||||||
elseif ticker._delay > elapsed then
|
|
||||||
ticker._delay = ticker._delay - elapsed
|
|
||||||
i = i + 1
|
|
||||||
else
|
|
||||||
ticker._callback(ticker)
|
|
||||||
|
|
||||||
if ticker._remainingIterations == -1 then
|
|
||||||
ticker._delay = ticker._duration
|
|
||||||
i = i + 1
|
|
||||||
elseif ticker._remainingIterations > 1 then
|
|
||||||
ticker._remainingIterations = ticker._remainingIterations - 1
|
|
||||||
ticker._delay = ticker._duration
|
|
||||||
i = i + 1
|
|
||||||
elseif ticker._remainingIterations == 1 then
|
|
||||||
tremove(waitTable, i)
|
|
||||||
total = total - 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if #waitTable == 0 then
|
|
||||||
self:Hide()
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
local function AddDelayedCall(ticker, oldTicker)
|
|
||||||
if oldTicker and type(oldTicker) == "table" then
|
|
||||||
ticker = oldTicker
|
|
||||||
end
|
|
||||||
|
|
||||||
tinsert(waitTable, ticker)
|
|
||||||
waitFrame:Show()
|
|
||||||
end
|
|
||||||
|
|
||||||
_G.AddDelayedCall = AddDelayedCall
|
|
||||||
|
|
||||||
local function CreateTicker(duration, callback, iterations)
|
|
||||||
local ticker = setmetatable({}, TickerMetatable)
|
|
||||||
ticker._remainingIterations = iterations or -1
|
|
||||||
ticker._duration = duration
|
|
||||||
ticker._delay = duration
|
|
||||||
ticker._callback = callback
|
|
||||||
|
|
||||||
AddDelayedCall(ticker)
|
|
||||||
|
|
||||||
return ticker
|
|
||||||
end
|
|
||||||
|
|
||||||
function C_Timer.After(duration, callback)
|
|
||||||
AddDelayedCall({
|
|
||||||
_remainingIterations = 1,
|
|
||||||
_delay = duration,
|
|
||||||
_callback = callback
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
function C_Timer.NewTimer(duration, callback)
|
|
||||||
return CreateTicker(duration, callback, 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
function C_Timer.NewTicker(duration, callback, iterations)
|
|
||||||
return CreateTicker(duration, callback, iterations)
|
|
||||||
end
|
|
||||||
|
|
||||||
function TickerPrototype:Cancel()
|
|
||||||
self._cancelled = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
RAID_CLASS_COLORS.HUNTER.colorStr = "ffabd473"
|
RAID_CLASS_COLORS.HUNTER.colorStr = "ffabd473"
|
||||||
RAID_CLASS_COLORS.WARLOCK.colorStr = "ff8788ee"
|
RAID_CLASS_COLORS.WARLOCK.colorStr = "ff8788ee"
|
||||||
RAID_CLASS_COLORS.PRIEST.colorStr = "ffffffff"
|
RAID_CLASS_COLORS.PRIEST.colorStr = "ffffffff"
|
||||||
|
|||||||
@@ -2785,9 +2785,9 @@ end
|
|||||||
-- Thus Reload the options after a very small delay.
|
-- Thus Reload the options after a very small delay.
|
||||||
function WeakAuras.ScheduleReloadOptions(data)
|
function WeakAuras.ScheduleReloadOptions(data)
|
||||||
if (type(data.id) ~= "table") then
|
if (type(data.id) ~= "table") then
|
||||||
C_Timer.After(0.1, function()
|
WeakAuras.timer:ScheduleTimer(function()
|
||||||
WeakAuras.ReloadOptions(data.id)
|
WeakAuras.ReloadOptions(data.id)
|
||||||
end );
|
end, 0.1);
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user