Revert "Fallback handled. Yes, I tested this."
This reverts commit 986d510a88.
This commit is contained in:
@@ -1,293 +1,271 @@
|
||||
local function distance( x1, y1, x2, y2 )
|
||||
return math.pow( math.pow(x1-x2, 2) + math.pow(y1-y2, 2), 0.5 );
|
||||
end
|
||||
|
||||
local children = {
|
||||
};
|
||||
|
||||
local Letters = {
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
|
||||
"O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "?", "!",
|
||||
"BACK", "ENTER"
|
||||
};
|
||||
|
||||
local LetterIndexes = { };
|
||||
for i, l in ipairs(Letters) do
|
||||
LetterIndexes[l] = i;
|
||||
end
|
||||
|
||||
local MapNameToLetter = {};
|
||||
local MapLetterToName = {};
|
||||
local letter = LoadFont("Common Normal")..{
|
||||
InitCommand=function(self)
|
||||
self:zoom(0.8);
|
||||
self:shadowlength(0);
|
||||
end;
|
||||
PulseCommand = function(self)
|
||||
self:finishtweening();
|
||||
local z = self:GetZoomX();
|
||||
self:accelerate(0.15);
|
||||
self:zoom(1.2);
|
||||
self:decelerate(0.15);
|
||||
self:zoom(z);
|
||||
end;
|
||||
};
|
||||
|
||||
for l in ivalues(Letters) do
|
||||
local Name = "letter " .. l;
|
||||
MapNameToLetter[Name] = l;
|
||||
MapLetterToName[l] = Name;
|
||||
children[#children+1] = letter..{
|
||||
Name = Name;
|
||||
Text = l;
|
||||
};
|
||||
if l == "BACK" or l == "ENTER" then
|
||||
children[#children].File = THEME:GetPathF("Common", "Normal");
|
||||
end;
|
||||
end;
|
||||
children[#children+1] = LoadActor( THEME:GetPathS("Common", "value") ) .. { Name = "Type"; SupportPan = true; }
|
||||
children[#children+1] = LoadActor( THEME:GetPathS("Common", "cancel") ) .. { Name = "Back"; SupportPan = true; }
|
||||
children[#children+1] = LoadActor( THEME:GetPathS("Common", "start") ).. { Name = "Enter"; SupportPan = true; }
|
||||
children[#children+1] = LoadActor( THEME:GetPathS("MusicWheel", "change") ) .. { Name = "Move"; SupportPan = true; }
|
||||
|
||||
local CursorFiles = {
|
||||
[PLAYER_1] = "Cursor P1",
|
||||
[PLAYER_2] = "Cursor P2"
|
||||
};
|
||||
|
||||
for pn in ivalues(PlayerNumber) do
|
||||
children[#children+1] = LoadActor( THEME:GetPathG("_frame", "1D"),
|
||||
{ 4/10, 2/10, 4/10 },
|
||||
LoadActor(CursorFiles[pn])
|
||||
)..{
|
||||
Name = CursorFiles[pn];
|
||||
BeginCommand = function(self)
|
||||
self:visible(SCREENMAN:GetTopScreen():GetEnteringName(pn));
|
||||
end;
|
||||
OnCommand = function(self)
|
||||
self:zoom(0);
|
||||
self:rotationz(-360 * 2);
|
||||
self:sleep(0.55);
|
||||
self:decelerate(0.5);
|
||||
self:zoom(1);
|
||||
self:rotationz(0);
|
||||
end;
|
||||
OffCommand = function(self)
|
||||
self:sleep(0.3);
|
||||
self:queuecommand("TweenOff");
|
||||
end;
|
||||
PlayerFinishedMessageCommand = function(self,param)
|
||||
if param.PlayerNumber ~= pn then return end
|
||||
self:playcommand("TweenOff" );
|
||||
end;
|
||||
TweenOffCommand = function(self,param)
|
||||
self:accelerate(0.25);
|
||||
self:zoomx(0);
|
||||
end;
|
||||
};
|
||||
end
|
||||
|
||||
local c;
|
||||
local Keys = {};
|
||||
local Selection = {};
|
||||
local PlayerX = {};
|
||||
local PlayerY = {};
|
||||
return Def.ActorFrame {
|
||||
children = children;
|
||||
BeginCommand = function(self)
|
||||
c = self:GetChildren();
|
||||
c.Cursors = {};
|
||||
c.Cursors[PLAYER_1] = c["Cursor P1"];
|
||||
c.Cursors[PLAYER_2] = c["Cursor P2"];
|
||||
|
||||
for l in ivalues(Letters) do
|
||||
local Name = MapLetterToName[l];
|
||||
assert(Name);
|
||||
Keys[l] = {
|
||||
Text = c[Name];
|
||||
Width = c[Name]:GetWidth();
|
||||
Height = c[Name]:GetHeight();
|
||||
};
|
||||
assert(c[Name], Name);
|
||||
end;
|
||||
|
||||
-- Position letters.
|
||||
local RowWidths = {};
|
||||
local fX = 0;
|
||||
local fXPadding = 4;
|
||||
local fY = 0;
|
||||
local iRow = 1;
|
||||
for l in ivalues(Letters) do
|
||||
if l == "O" or l == "0" or l == "BACK" then
|
||||
fX = 0;
|
||||
fY = fY + 30;
|
||||
iRow = iRow + 1;
|
||||
end;
|
||||
Keys[l].Row = iRow;
|
||||
Keys[l].Text:x(fX + Keys[l].Width/2);
|
||||
Keys[l].Text:y(fY + Keys[l].Height/2);
|
||||
fX = fX + Keys[l].Width;
|
||||
RowWidths[iRow] = fX;
|
||||
fX = fX + fXPadding;
|
||||
end;
|
||||
for l in ivalues(Letters) do
|
||||
local iRow = Keys[l].Row;
|
||||
Keys[l].Text:addx(-RowWidths[iRow]/2);
|
||||
end;
|
||||
|
||||
Keys["BACK"].Text:x(-140);
|
||||
Keys["ENTER"].Text:x(140);
|
||||
|
||||
for pn in ivalues(PlayerNumber) do
|
||||
local sName = SCREENMAN:GetTopScreen():GetSelection(pn);
|
||||
local DefaultKey = "A"
|
||||
if #sName > 0 then
|
||||
DefaultKey = "ENTER"
|
||||
end
|
||||
|
||||
self:playcommand("SelectKey", { Key = DefaultKey, PlayerNumber = pn });
|
||||
c.Cursors[pn]:finishtweening();
|
||||
end
|
||||
end;
|
||||
|
||||
CodeMessageCommand = function(self, param)
|
||||
local pn = param.PlayerNumber;
|
||||
if not SCREENMAN:GetTopScreen():GetAnyStillEntering() and param.Name == "Enter" then
|
||||
if SCREENMAN:GetTopScreen():Finish(pn) then
|
||||
c.Enter:play();
|
||||
end
|
||||
return;
|
||||
end
|
||||
if SCREENMAN:GetTopScreen():GetFinalized( pn ) then
|
||||
return;
|
||||
end
|
||||
|
||||
if param.Name == "Left" or param.Name == "Right" then
|
||||
local iDir = param.Name == "Left" and -1 or 1;
|
||||
|
||||
local idx = LetterIndexes[Selection[pn]];
|
||||
idx = idx + iDir;
|
||||
idx = math.mod(idx+#Letters-1, #Letters)+1;
|
||||
self:playcommand("SelectKey", { Key = Letters[idx], PlayerNumber = pn });
|
||||
c.Move:playforplayer(pn);
|
||||
return;
|
||||
end
|
||||
|
||||
if param.Name == "JumpToEnter" then
|
||||
self:playcommand("SelectKey", { Key = "ENTER", PlayerNumber = pn });
|
||||
c.Move:playforplayer(pn);
|
||||
return;
|
||||
end
|
||||
if param.Name == "Backspace" then
|
||||
if SCREENMAN:GetTopScreen():Backspace(pn) then
|
||||
c.Back:playforplayer(pn);
|
||||
end
|
||||
return;
|
||||
end
|
||||
|
||||
if param.Name == "NextRow" or param.Name == "PrevRow" then
|
||||
local iDir = param.Name == "PrevRow" and -1 or 1;
|
||||
local idx = LetterIndexes[Selection[pn]];
|
||||
local iRow = Keys[Selection[pn]].Row;
|
||||
iRow = iRow + iDir;
|
||||
iRow = math.mod(iRow+4-1, 4)+1;
|
||||
|
||||
local NearestLetter
|
||||
local NearestLetterDistance
|
||||
for l in ivalues(Letters) do
|
||||
if iRow == Keys[l].Row then
|
||||
local Text = Keys[l].Text;
|
||||
|
||||
local fDist = distance( PlayerX[pn], PlayerY[pn],
|
||||
Text:GetX(), Text:GetY() );
|
||||
if not NearestLetterDistance or fDist < NearestLetterDistance then
|
||||
NearestLetterDistance = fDist;
|
||||
NearestLetter = l;
|
||||
end
|
||||
end
|
||||
end
|
||||
assert( NearestLetter );
|
||||
self:playcommand("SelectKey", { Key = NearestLetter, PlayerNumber = pn, NoStore = true });
|
||||
|
||||
c.Move:playforplayer(pn);
|
||||
|
||||
return;
|
||||
end
|
||||
|
||||
if param.Name == "Enter" then
|
||||
if Selection[pn] == "BACK" then
|
||||
if SCREENMAN:GetTopScreen():Backspace(pn) then
|
||||
Keys[Selection[pn]].Text:playcommand( "Pulse" );
|
||||
c.Back:playforplayer(pn);
|
||||
end
|
||||
elseif Selection[pn] == "ENTER" then
|
||||
if SCREENMAN:GetTopScreen():Finish(pn) then
|
||||
Keys[Selection[pn]].Text:playcommand( "Pulse" );
|
||||
c.Enter:playforplayer(pn);
|
||||
end
|
||||
else
|
||||
local key = Selection[pn]; -- EnterKey may change this
|
||||
if SCREENMAN:GetTopScreen():EnterKey(pn, key) then
|
||||
Keys[key].Text:playcommand( "Pulse" );
|
||||
c.Type:playforplayer(pn);
|
||||
end
|
||||
end
|
||||
end
|
||||
end;
|
||||
|
||||
SelectKeyMessageCommand = function(self, param)
|
||||
local key = param.Key;
|
||||
local pn = param.PlayerNumber;
|
||||
if not Keys[key] then return end
|
||||
|
||||
Selection[pn] = key;
|
||||
c.Cursors[pn]:stoptweening();
|
||||
c.Cursors[pn]:playcommand( "SetSize", {
|
||||
Width=Keys[key].Width+6; tween=function(self)
|
||||
self:stoptweening();
|
||||
self:linear(0.10);
|
||||
end;
|
||||
} );
|
||||
c.Cursors[pn]:x( Keys[key].Text:GetX() );
|
||||
c.Cursors[pn]:y( Keys[key].Text:GetY() );
|
||||
if not param.NoStore then
|
||||
PlayerX[pn] = Keys[key].Text:GetX();
|
||||
PlayerY[pn] = Keys[key].Text:GetY();
|
||||
end
|
||||
end;
|
||||
MenuTimerExpiredMessageCommand = function(self, param)
|
||||
for pn in ivalues(PlayerNumber) do
|
||||
SCREENMAN:GetTopScreen():Finish(pn);
|
||||
end
|
||||
c.Enter:play();
|
||||
end;
|
||||
|
||||
OnCommand = function(self, param)
|
||||
for key in ivalues(Letters) do
|
||||
local fDist = distance( Keys[key].Text:GetX(), Keys[key].Text:GetY(), 0, 100 );
|
||||
local f = function(self)
|
||||
self:diffusealpha(0);
|
||||
self:zoom(0);
|
||||
self:sleep(0.25 + (fDist / 400));
|
||||
self:decelerate(0.5);
|
||||
self:diffusealpha(1);
|
||||
self:zoom(0.8);
|
||||
end;
|
||||
f(Keys[key].Text);
|
||||
end
|
||||
end;
|
||||
|
||||
OffCommand = function(self, param)
|
||||
for key in ivalues(Letters) do
|
||||
local fDist = distance( Keys[key].Text:GetX(), Keys[key].Text:GetY(), 0, 100 );
|
||||
local f = function(self)
|
||||
self:sleep(0.0 + (fDist / 400));
|
||||
self:decelerate(0.5);
|
||||
self:diffusealpha(0);
|
||||
self:zoom(0);
|
||||
end;
|
||||
f(Keys[key].Text);
|
||||
end
|
||||
end;
|
||||
};
|
||||
local function distance( x1, y1, x2, y2 )
|
||||
return math.pow( math.pow(x1-x2, 2) + math.pow(y1-y2, 2), 0.5 );
|
||||
end
|
||||
|
||||
local children = {
|
||||
};
|
||||
|
||||
local Letters = {
|
||||
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
|
||||
"O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "?", "!",
|
||||
"BACK", "ENTER"
|
||||
};
|
||||
|
||||
local LetterIndexes = { };
|
||||
for i, l in ipairs(Letters) do
|
||||
LetterIndexes[l] = i;
|
||||
end
|
||||
|
||||
local MapNameToLetter = {};
|
||||
local MapLetterToName = {};
|
||||
local letter = LoadFont("Common Normal")..{
|
||||
InitCommand=cmd(zoom,0.8;shadowlength,0);
|
||||
PulseCommand = function(self)
|
||||
self:finishtweening();
|
||||
local z = self:GetZoomX();
|
||||
(cmd(accelerate,0.15;zoom,1.2;decelerate,0.15;zoom,z))(self);
|
||||
end;
|
||||
};
|
||||
|
||||
for l in ivalues(Letters) do
|
||||
local Name = "letter " .. l;
|
||||
MapNameToLetter[Name] = l;
|
||||
MapLetterToName[l] = Name;
|
||||
children[#children+1] = letter..{
|
||||
Name = Name;
|
||||
Text = l;
|
||||
};
|
||||
if l == "BACK" or l == "ENTER" then
|
||||
children[#children].File = THEME:GetPathF("Common", "Normal");
|
||||
end;
|
||||
end;
|
||||
children[#children+1] = LoadActor( THEME:GetPathS("Common", "value") ) .. { Name = "Type"; SupportPan = true; }
|
||||
children[#children+1] = LoadActor( THEME:GetPathS("Common", "cancel") ) .. { Name = "Back"; SupportPan = true; }
|
||||
children[#children+1] = LoadActor( THEME:GetPathS("Common", "start") ).. { Name = "Enter"; SupportPan = true; }
|
||||
children[#children+1] = LoadActor( THEME:GetPathS("MusicWheel", "change") ) .. { Name = "Move"; SupportPan = true; }
|
||||
|
||||
local CursorFiles = {
|
||||
[PLAYER_1] = "Cursor P1",
|
||||
[PLAYER_2] = "Cursor P2"
|
||||
};
|
||||
|
||||
for pn in ivalues(PlayerNumber) do
|
||||
children[#children+1] = LoadActor( THEME:GetPathG("_frame", "1D"),
|
||||
{ 4/10, 2/10, 4/10 },
|
||||
LoadActor(CursorFiles[pn])
|
||||
)..{
|
||||
Name = CursorFiles[pn];
|
||||
BeginCommand = cmd(visible,SCREENMAN:GetTopScreen():GetEnteringName(pn));
|
||||
OnCommand = cmd(
|
||||
zoom,0;
|
||||
rotationz,-360*2;
|
||||
sleep,0.55;
|
||||
decelerate,0.5;
|
||||
zoom,1;
|
||||
rotationz,0;
|
||||
);
|
||||
OffCommand = cmd(sleep,0.3;queuecommand,"TweenOff");
|
||||
PlayerFinishedMessageCommand = function(self,param)
|
||||
if param.PlayerNumber ~= pn then return end
|
||||
self:playcommand("TweenOff" );
|
||||
end;
|
||||
TweenOffCommand = function(self,param)
|
||||
(cmd(accelerate,0.25;zoomx,0;))(self);
|
||||
end;
|
||||
};
|
||||
end
|
||||
|
||||
local c;
|
||||
local Keys = {};
|
||||
local Selection = {};
|
||||
local PlayerX = {};
|
||||
local PlayerY = {};
|
||||
return Def.ActorFrame {
|
||||
children = children;
|
||||
BeginCommand = function(self)
|
||||
c = self:GetChildren();
|
||||
c.Cursors = {};
|
||||
c.Cursors[PLAYER_1] = c["Cursor P1"];
|
||||
c.Cursors[PLAYER_2] = c["Cursor P2"];
|
||||
|
||||
for l in ivalues(Letters) do
|
||||
local Name = MapLetterToName[l];
|
||||
assert(Name);
|
||||
Keys[l] = {
|
||||
Text = c[Name];
|
||||
Width = c[Name]:GetWidth();
|
||||
Height = c[Name]:GetHeight();
|
||||
};
|
||||
assert(c[Name], Name);
|
||||
end;
|
||||
|
||||
-- Position letters.
|
||||
local RowWidths = {};
|
||||
local fX = 0;
|
||||
local fXPadding = 4;
|
||||
local fY = 0;
|
||||
local iRow = 1;
|
||||
for l in ivalues(Letters) do
|
||||
if l == "O" or l == "0" or l == "BACK" then
|
||||
fX = 0;
|
||||
fY = fY + 30;
|
||||
iRow = iRow + 1;
|
||||
end;
|
||||
Keys[l].Row = iRow;
|
||||
Keys[l].Text:x(fX + Keys[l].Width/2);
|
||||
Keys[l].Text:y(fY + Keys[l].Height/2);
|
||||
fX = fX + Keys[l].Width;
|
||||
RowWidths[iRow] = fX;
|
||||
fX = fX + fXPadding;
|
||||
end;
|
||||
for l in ivalues(Letters) do
|
||||
local iRow = Keys[l].Row;
|
||||
Keys[l].Text:addx(-RowWidths[iRow]/2);
|
||||
end;
|
||||
|
||||
Keys["BACK"].Text:x(-140);
|
||||
Keys["ENTER"].Text:x(140);
|
||||
|
||||
for pn in ivalues(PlayerNumber) do
|
||||
local sName = SCREENMAN:GetTopScreen():GetSelection(pn);
|
||||
local DefaultKey = "A"
|
||||
if #sName > 0 then
|
||||
DefaultKey = "ENTER"
|
||||
end
|
||||
|
||||
self:playcommand("SelectKey", { Key = DefaultKey, PlayerNumber = pn });
|
||||
c.Cursors[pn]:finishtweening();
|
||||
end
|
||||
end;
|
||||
|
||||
CodeMessageCommand = function(self, param)
|
||||
local pn = param.PlayerNumber;
|
||||
if not SCREENMAN:GetTopScreen():GetAnyStillEntering() and param.Name == "Enter" then
|
||||
if SCREENMAN:GetTopScreen():Finish(pn) then
|
||||
c.Enter:play();
|
||||
end
|
||||
return;
|
||||
end
|
||||
if SCREENMAN:GetTopScreen():GetFinalized( pn ) then
|
||||
return;
|
||||
end
|
||||
|
||||
if param.Name == "Left" or param.Name == "Right" then
|
||||
local iDir = param.Name == "Left" and -1 or 1;
|
||||
|
||||
local idx = LetterIndexes[Selection[pn]];
|
||||
idx = idx + iDir;
|
||||
idx = math.mod(idx+#Letters-1, #Letters)+1;
|
||||
self:playcommand("SelectKey", { Key = Letters[idx], PlayerNumber = pn });
|
||||
c.Move:playforplayer(pn);
|
||||
return;
|
||||
end
|
||||
|
||||
if param.Name == "JumpToEnter" then
|
||||
self:playcommand("SelectKey", { Key = "ENTER", PlayerNumber = pn });
|
||||
c.Move:playforplayer(pn);
|
||||
return;
|
||||
end
|
||||
if param.Name == "Backspace" then
|
||||
if SCREENMAN:GetTopScreen():Backspace(pn) then
|
||||
c.Back:playforplayer(pn);
|
||||
end
|
||||
return;
|
||||
end
|
||||
|
||||
if param.Name == "NextRow" or param.Name == "PrevRow" then
|
||||
local iDir = param.Name == "PrevRow" and -1 or 1;
|
||||
local idx = LetterIndexes[Selection[pn]];
|
||||
local iRow = Keys[Selection[pn]].Row;
|
||||
iRow = iRow + iDir;
|
||||
iRow = math.mod(iRow+4-1, 4)+1;
|
||||
|
||||
local NearestLetter
|
||||
local NearestLetterDistance
|
||||
for l in ivalues(Letters) do
|
||||
if iRow == Keys[l].Row then
|
||||
local Text = Keys[l].Text;
|
||||
|
||||
local fDist = distance( PlayerX[pn], PlayerY[pn],
|
||||
Text:GetX(), Text:GetY() );
|
||||
if not NearestLetterDistance or fDist < NearestLetterDistance then
|
||||
NearestLetterDistance = fDist;
|
||||
NearestLetter = l;
|
||||
end
|
||||
end
|
||||
end
|
||||
assert( NearestLetter );
|
||||
self:playcommand("SelectKey", { Key = NearestLetter, PlayerNumber = pn, NoStore = true });
|
||||
|
||||
c.Move:playforplayer(pn);
|
||||
|
||||
return;
|
||||
end
|
||||
|
||||
if param.Name == "Enter" then
|
||||
if Selection[pn] == "BACK" then
|
||||
if SCREENMAN:GetTopScreen():Backspace(pn) then
|
||||
Keys[Selection[pn]].Text:playcommand( "Pulse" );
|
||||
c.Back:playforplayer(pn);
|
||||
end
|
||||
elseif Selection[pn] == "ENTER" then
|
||||
if SCREENMAN:GetTopScreen():Finish(pn) then
|
||||
Keys[Selection[pn]].Text:playcommand( "Pulse" );
|
||||
c.Enter:playforplayer(pn);
|
||||
end
|
||||
else
|
||||
local key = Selection[pn]; -- EnterKey may change this
|
||||
if SCREENMAN:GetTopScreen():EnterKey(pn, key) then
|
||||
Keys[key].Text:playcommand( "Pulse" );
|
||||
c.Type:playforplayer(pn);
|
||||
end
|
||||
end
|
||||
end
|
||||
end;
|
||||
|
||||
SelectKeyMessageCommand = function(self, param)
|
||||
local key = param.Key;
|
||||
local pn = param.PlayerNumber;
|
||||
if not Keys[key] then return end
|
||||
|
||||
Selection[pn] = key;
|
||||
c.Cursors[pn]:stoptweening();
|
||||
c.Cursors[pn]:playcommand( "SetSize", { Width=Keys[key].Width+6; tween=cmd(stoptweening;linear,0.10); } );
|
||||
c.Cursors[pn]:x( Keys[key].Text:GetX() );
|
||||
c.Cursors[pn]:y( Keys[key].Text:GetY() );
|
||||
if not param.NoStore then
|
||||
PlayerX[pn] = Keys[key].Text:GetX();
|
||||
PlayerY[pn] = Keys[key].Text:GetY();
|
||||
end
|
||||
end;
|
||||
MenuTimerExpiredMessageCommand = function(self, param)
|
||||
for pn in ivalues(PlayerNumber) do
|
||||
SCREENMAN:GetTopScreen():Finish(pn);
|
||||
end
|
||||
c.Enter:play();
|
||||
end;
|
||||
|
||||
OnCommand = function(self, param)
|
||||
for key in ivalues(Letters) do
|
||||
local fDist = distance( Keys[key].Text:GetX(), Keys[key].Text:GetY(), 0, 100 );
|
||||
local f = cmd(
|
||||
diffusealpha,0;zoom,0;
|
||||
sleep,0.25 + (fDist / 400);
|
||||
decelerate,0.5;
|
||||
diffusealpha,1;zoom,0.8);
|
||||
f(Keys[key].Text);
|
||||
end
|
||||
end;
|
||||
|
||||
OffCommand = function(self, param)
|
||||
for key in ivalues(Letters) do
|
||||
local fDist = distance( Keys[key].Text:GetX(), Keys[key].Text:GetY(), 0, 100 );
|
||||
local f = cmd(
|
||||
sleep,0.0 + (fDist / 400);
|
||||
decelerate,0.5;
|
||||
diffusealpha,0;zoom,0);
|
||||
f(Keys[key].Text);
|
||||
end
|
||||
end;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user