Add temp banning to servers
This commit is contained in:
@@ -36,7 +36,7 @@ namespace OpenRA.GameRules
|
|||||||
public bool VerboseNatDiscovery = false; // print very detailed logs for debugging
|
public bool VerboseNatDiscovery = false; // print very detailed logs for debugging
|
||||||
public bool AllowCheats = false;
|
public bool AllowCheats = false;
|
||||||
public string Map = null;
|
public string Map = null;
|
||||||
public string[] Ban = null;
|
public string[] Ban = { };
|
||||||
public int TimeOut = 0;
|
public int TimeOut = 0;
|
||||||
public bool Dedicated = false;
|
public bool Dedicated = false;
|
||||||
public bool DedicatedLoop = true;
|
public bool DedicatedLoop = true;
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ namespace OpenRA.Network
|
|||||||
{
|
{
|
||||||
public string ServerName;
|
public string ServerName;
|
||||||
public string Map;
|
public string Map;
|
||||||
public string[] Ban;
|
|
||||||
public string[] Mods = { "ra" }; // mod names
|
public string[] Mods = { "ra" }; // mod names
|
||||||
public int OrderLatency = 3; // net tick frames (x 120 = ms)
|
public int OrderLatency = 3; // net tick frames (x 120 = ms)
|
||||||
public int RandomSeed = 0;
|
public int RandomSeed = 0;
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ namespace OpenRA.Server
|
|||||||
protected set { pState = value; }
|
protected set { pState = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<string> TempBans = new List<string>();
|
||||||
|
|
||||||
public void Shutdown()
|
public void Shutdown()
|
||||||
{
|
{
|
||||||
State = ServerState.ShuttingDown;
|
State = ServerState.ShuttingDown;
|
||||||
@@ -103,7 +105,6 @@ namespace OpenRA.Server
|
|||||||
lobbyInfo.GlobalSettings.RandomSeed = randomSeed;
|
lobbyInfo.GlobalSettings.RandomSeed = randomSeed;
|
||||||
lobbyInfo.GlobalSettings.Map = settings.Map;
|
lobbyInfo.GlobalSettings.Map = settings.Map;
|
||||||
lobbyInfo.GlobalSettings.ServerName = settings.Name;
|
lobbyInfo.GlobalSettings.ServerName = settings.Name;
|
||||||
lobbyInfo.GlobalSettings.Ban = settings.Ban;
|
|
||||||
lobbyInfo.GlobalSettings.Dedicated = settings.Dedicated;
|
lobbyInfo.GlobalSettings.Dedicated = settings.Dedicated;
|
||||||
|
|
||||||
foreach (var t in ServerTraits.WithInterface<INotifyServerStart>())
|
foreach (var t in ServerTraits.WithInterface<INotifyServerStart>())
|
||||||
@@ -270,18 +271,13 @@ namespace OpenRA.Server
|
|||||||
client.IpAddress = ((IPEndPoint)newConn.socket.RemoteEndPoint).Address.ToString();
|
client.IpAddress = ((IPEndPoint)newConn.socket.RemoteEndPoint).Address.ToString();
|
||||||
|
|
||||||
// Check if IP is banned
|
// Check if IP is banned
|
||||||
if (lobbyInfo.GlobalSettings.Ban != null)
|
var bans = Settings.Ban.Union(TempBans);
|
||||||
|
if (bans.Contains(client.IpAddress))
|
||||||
{
|
{
|
||||||
|
Log.Write("server", "Rejected connection from {0}; Banned.", newConn.socket.RemoteEndPoint);
|
||||||
if (lobbyInfo.GlobalSettings.Ban.Contains(client.IpAddress))
|
SendOrderTo(newConn, "ServerError", "You are {0} from the server.".F(Settings.Ban.Contains(client.IpAddress) ? "banned" : "temporarily banned"));
|
||||||
{
|
DropClient(newConn);
|
||||||
Console.WriteLine("Rejected connection from "+client.Name+"("+newConn.socket.RemoteEndPoint+"); Banned.");
|
return;
|
||||||
Log.Write("server", "Rejected connection from {0}; Banned.",
|
|
||||||
newConn.socket.RemoteEndPoint);
|
|
||||||
SendOrderTo(newConn, "ServerError", "You are banned from the server!");
|
|
||||||
DropClient(newConn);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Promote connection to a valid client
|
// Promote connection to a valid client
|
||||||
@@ -502,7 +498,7 @@ namespace OpenRA.Server
|
|||||||
{
|
{
|
||||||
conns.Remove(toDrop);
|
conns.Remove(toDrop);
|
||||||
|
|
||||||
var dropClient = lobbyInfo.Clients.Where(c1 => c1.Index == toDrop.PlayerIndex).FirstOrDefault();
|
var dropClient = lobbyInfo.Clients.FirstOrDefault(c1 => c1.Index == toDrop.PlayerIndex);
|
||||||
if (dropClient == null)
|
if (dropClient == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -531,7 +527,10 @@ namespace OpenRA.Server
|
|||||||
|
|
||||||
DispatchOrders(toDrop, toDrop.MostRecentFrame, new byte[] {0xbf});
|
DispatchOrders(toDrop, toDrop.MostRecentFrame, new byte[] {0xbf});
|
||||||
|
|
||||||
if (conns.Count != 0 || lobbyInfo.GlobalSettings.Dedicated)
|
if (!conns.Any())
|
||||||
|
TempBans.Clear();
|
||||||
|
|
||||||
|
if (conns.Any() || lobbyInfo.GlobalSettings.Dedicated)
|
||||||
SyncLobbyInfo();
|
SyncLobbyInfo();
|
||||||
|
|
||||||
if (!lobbyInfo.GlobalSettings.Dedicated && dropClient.IsAdmin)
|
if (!lobbyInfo.GlobalSettings.Dedicated && dropClient.IsAdmin)
|
||||||
|
|||||||
@@ -380,6 +380,7 @@
|
|||||||
<Compile Include="Widgets\BuildPaletteWidget.cs" />
|
<Compile Include="Widgets\BuildPaletteWidget.cs" />
|
||||||
<Compile Include="Widgets\LogicTickerWidget.cs" />
|
<Compile Include="Widgets\LogicTickerWidget.cs" />
|
||||||
<Compile Include="Widgets\Logic\IngameMenuLogic.cs" />
|
<Compile Include="Widgets\Logic\IngameMenuLogic.cs" />
|
||||||
|
<Compile Include="Widgets\Logic\KickClientLogic.cs" />
|
||||||
<Compile Include="Widgets\Logic\ModBrowserLogic.cs" />
|
<Compile Include="Widgets\Logic\ModBrowserLogic.cs" />
|
||||||
<Compile Include="Widgets\Logic\ColorPickerLogic.cs" />
|
<Compile Include="Widgets\Logic\ColorPickerLogic.cs" />
|
||||||
<Compile Include="Widgets\Logic\ConnectionLogic.cs" />
|
<Compile Include="Widgets\Logic\ConnectionLogic.cs" />
|
||||||
|
|||||||
@@ -413,25 +413,44 @@ namespace OpenRA.Mods.RA.Server
|
|||||||
{ "kick",
|
{ "kick",
|
||||||
s =>
|
s =>
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!client.IsAdmin)
|
if (!client.IsAdmin)
|
||||||
{
|
{
|
||||||
server.SendOrderTo(conn, "Message", "Only the host can kick players");
|
server.SendOrderTo(conn, "Message", "Only the host can kick players");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int clientID;
|
var split = s.Split(' ');
|
||||||
int.TryParse(s, out clientID);
|
if (split.Length < 2)
|
||||||
|
{
|
||||||
|
server.SendOrderTo(conn, "Message", "Malformed kick command");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
var connToKick = server.conns.SingleOrDefault( c => server.GetClient(c) != null && server.GetClient(c).Index == clientID);
|
int kickClientID;
|
||||||
if (connToKick == null)
|
int.TryParse(split[0], out kickClientID);
|
||||||
|
|
||||||
|
var kickConn = server.conns.SingleOrDefault(c => server.GetClient(c) != null && server.GetClient(c).Index == kickClientID);
|
||||||
|
if (kickConn == null)
|
||||||
{
|
{
|
||||||
server.SendOrderTo(conn, "Message", "Noone in that slot.");
|
server.SendOrderTo(conn, "Message", "Noone in that slot.");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
server.SendOrderTo(connToKick, "ServerError", "You have been kicked from the server");
|
var kickConnIP = server.GetClient(kickConn).IpAddress;
|
||||||
server.DropClient(connToKick);
|
|
||||||
|
Log.Write("server", "Kicking client {0} as requested", kickClientID);
|
||||||
|
server.SendOrderTo(kickConn, "ServerError", "You have been kicked from the server");
|
||||||
|
server.DropClient(kickConn);
|
||||||
|
|
||||||
|
bool tempBan;
|
||||||
|
bool.TryParse(split[1], out tempBan);
|
||||||
|
|
||||||
|
if (tempBan)
|
||||||
|
{
|
||||||
|
Log.Write("server", "Temporarily banning client {0} ({1}) as requested", kickClientID, kickConnIP);
|
||||||
|
server.TempBans.Add(kickConnIP);
|
||||||
|
}
|
||||||
|
|
||||||
server.SyncLobbyInfo();
|
server.SyncLobbyInfo();
|
||||||
return true;
|
return true;
|
||||||
}},
|
}},
|
||||||
|
|||||||
37
OpenRA.Mods.RA/Widgets/Logic/KickClientLogic.cs
Normal file
37
OpenRA.Mods.RA/Widgets/Logic/KickClientLogic.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright 2007-2013 The OpenRA Developers (see AUTHORS)
|
||||||
|
* This file is part of OpenRA, which is free software. It is made
|
||||||
|
* available to you under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation. For more information,
|
||||||
|
* see COPYING.
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using OpenRA.Widgets;
|
||||||
|
|
||||||
|
namespace OpenRA.Mods.RA.Widgets.Logic
|
||||||
|
{
|
||||||
|
class KickClientLogic
|
||||||
|
{
|
||||||
|
[ObjectCreator.UseCtor]
|
||||||
|
public KickClientLogic(Widget widget, string clientName, Action<bool> okPressed)
|
||||||
|
{
|
||||||
|
widget.Get<LabelWidget>("TITLE").GetText = () => "Kick {0}?".F(clientName);
|
||||||
|
|
||||||
|
var tempBan = false;
|
||||||
|
var preventRejoiningCheckbox = widget.Get<CheckboxWidget>("PREVENT_REJOINING_CHECKBOX");
|
||||||
|
preventRejoiningCheckbox.IsChecked = () => tempBan;
|
||||||
|
preventRejoiningCheckbox.OnClick = () => tempBan ^= true;
|
||||||
|
|
||||||
|
widget.Get<ButtonWidget>("OK_BUTTON").OnClick = () =>
|
||||||
|
{
|
||||||
|
widget.Parent.RemoveChild(widget);
|
||||||
|
okPressed(tempBan);
|
||||||
|
};
|
||||||
|
|
||||||
|
widget.Get<ButtonWidget>("CANCEL_BUTTON").OnClick = () => widget.Parent.RemoveChild(widget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,8 +20,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
{
|
{
|
||||||
public class LobbyLogic
|
public class LobbyLogic
|
||||||
{
|
{
|
||||||
|
Widget lobby;
|
||||||
Widget EditablePlayerTemplate, NonEditablePlayerTemplate, EmptySlotTemplate,
|
Widget EditablePlayerTemplate, NonEditablePlayerTemplate, EmptySlotTemplate,
|
||||||
EditableSpectatorTemplate, NonEditableSpectatorTemplate, NewSpectatorTemplate;
|
EditableSpectatorTemplate, NonEditableSpectatorTemplate, NewSpectatorTemplate;
|
||||||
ScrollPanelWidget chatPanel;
|
ScrollPanelWidget chatPanel;
|
||||||
Widget chatTemplate;
|
Widget chatTemplate;
|
||||||
|
|
||||||
@@ -85,7 +86,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
internal LobbyLogic(Widget widget, World world, OrderManager orderManager,
|
internal LobbyLogic(Widget widget, World world, OrderManager orderManager,
|
||||||
Action onExit, Action onStart, bool addBots)
|
Action onExit, Action onStart, bool addBots)
|
||||||
{
|
{
|
||||||
var lobby = widget;
|
lobby = widget;
|
||||||
this.orderManager = orderManager;
|
this.orderManager = orderManager;
|
||||||
this.OnGameStart = () => { CloseWindow(); onStart(); };
|
this.OnGameStart = () => { CloseWindow(); onStart(); };
|
||||||
this.onExit = onExit;
|
this.onExit = onExit;
|
||||||
@@ -425,7 +426,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
|
|
||||||
LobbyUtils.SetupClientWidget(template, slot, client, orderManager, client.Bot == null);
|
LobbyUtils.SetupClientWidget(template, slot, client, orderManager, client.Bot == null);
|
||||||
LobbyUtils.SetupNameWidget(template, slot, client);
|
LobbyUtils.SetupNameWidget(template, slot, client);
|
||||||
LobbyUtils.SetupKickWidget(template, slot, client, orderManager);
|
LobbyUtils.SetupKickWidget(template, slot, client, orderManager, lobby);
|
||||||
LobbyUtils.SetupColorWidget(template, slot, client);
|
LobbyUtils.SetupColorWidget(template, slot, client);
|
||||||
LobbyUtils.SetupFactionWidget(template, slot, client, CountryNames);
|
LobbyUtils.SetupFactionWidget(template, slot, client, CountryNames);
|
||||||
LobbyUtils.SetupTeamWidget(template, slot, client);
|
LobbyUtils.SetupTeamWidget(template, slot, client);
|
||||||
@@ -467,7 +468,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
template = NonEditableSpectatorTemplate.Clone();
|
template = NonEditableSpectatorTemplate.Clone();
|
||||||
|
|
||||||
LobbyUtils.SetupNameWidget(template, null, client);
|
LobbyUtils.SetupNameWidget(template, null, client);
|
||||||
LobbyUtils.SetupKickWidget(template, null, client, orderManager);
|
LobbyUtils.SetupKickWidget(template, null, client, orderManager, lobby);
|
||||||
}
|
}
|
||||||
|
|
||||||
LobbyUtils.SetupClientWidget(template, null, c, orderManager, true);
|
LobbyUtils.SetupClientWidget(template, null, c, orderManager, true);
|
||||||
|
|||||||
@@ -262,12 +262,17 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
slot.IsVisible = () => false;
|
slot.IsVisible = () => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetupKickWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager)
|
public static void SetupKickWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, Widget lobby)
|
||||||
{
|
{
|
||||||
var button = parent.Get<ButtonWidget>("KICK");
|
var button = parent.Get<ButtonWidget>("KICK");
|
||||||
button.IsVisible = () => Game.IsHost && c.Index != orderManager.LocalClient.Index;
|
button.IsVisible = () => Game.IsHost && c.Index != orderManager.LocalClient.Index;
|
||||||
button.IsDisabled = () => orderManager.LocalClient.IsReady;
|
button.IsDisabled = () => orderManager.LocalClient.IsReady;
|
||||||
button.OnClick = () => orderManager.IssueOrder(Order.Command("kick " + c.Index));
|
Action<bool> okPressed = tempBan => orderManager.IssueOrder(Order.Command("kick {0} {1}".F(c.Index, tempBan)));
|
||||||
|
button.OnClick = () => Game.LoadWidget(null, "KICK_CLIENT_DIALOG", lobby, new WidgetArgs
|
||||||
|
{
|
||||||
|
{ "clientName", c.Name },
|
||||||
|
{ "okPressed", okPressed }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetupEditableColorWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, ColorPreviewManagerWidget colorPreview)
|
public static void SetupEditableColorWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, ColorPreviewManagerWidget colorPreview)
|
||||||
|
|||||||
@@ -132,3 +132,55 @@ Container@CONFIRM_PROMPT:
|
|||||||
Width:140
|
Width:140
|
||||||
Height:35
|
Height:35
|
||||||
Text:Confirm
|
Text:Confirm
|
||||||
|
|
||||||
|
Background@KICK_CLIENT_DIALOG:
|
||||||
|
X:15
|
||||||
|
Y:30
|
||||||
|
Width:503
|
||||||
|
Height:219
|
||||||
|
Logic:KickClientLogic
|
||||||
|
Background:panel-black
|
||||||
|
Children:
|
||||||
|
Label@TITLE:
|
||||||
|
X:0
|
||||||
|
Y:40
|
||||||
|
Width:PARENT_RIGHT
|
||||||
|
Height:25
|
||||||
|
Font:Bold
|
||||||
|
Align:Center
|
||||||
|
Label@TEXTA:
|
||||||
|
X:0
|
||||||
|
Y:67
|
||||||
|
Width:PARENT_RIGHT
|
||||||
|
Height:25
|
||||||
|
Font:Regular
|
||||||
|
Align:Center
|
||||||
|
Text:You may also apply a temporary ban, preventing
|
||||||
|
Label@TEXTB:
|
||||||
|
X:0
|
||||||
|
Y:85
|
||||||
|
Width:PARENT_RIGHT
|
||||||
|
Height:25
|
||||||
|
Font:Regular
|
||||||
|
Align:Center
|
||||||
|
Text:them from joining for the remainder of this game.
|
||||||
|
Checkbox@PREVENT_REJOINING_CHECKBOX:
|
||||||
|
X:(PARENT_RIGHT - WIDTH)/2
|
||||||
|
Y:120
|
||||||
|
Width:150
|
||||||
|
Height:20
|
||||||
|
Text:Temporarily Ban
|
||||||
|
Button@OK_BUTTON:
|
||||||
|
X:(PARENT_RIGHT - WIDTH)/2 + 75
|
||||||
|
Y:155
|
||||||
|
Width:120
|
||||||
|
Height:25
|
||||||
|
Text:Kick
|
||||||
|
Font:Bold
|
||||||
|
Button@CANCEL_BUTTON:
|
||||||
|
X:(PARENT_RIGHT - WIDTH)/2 - 75
|
||||||
|
Y:155
|
||||||
|
Width:120
|
||||||
|
Height:25
|
||||||
|
Text:Cancel
|
||||||
|
Font:Bold
|
||||||
@@ -486,3 +486,55 @@ Background@SERVER_LOBBY:
|
|||||||
Text:Start Game
|
Text:Start Game
|
||||||
Font:Bold
|
Font:Bold
|
||||||
TooltipContainer@TOOLTIP_CONTAINER:
|
TooltipContainer@TOOLTIP_CONTAINER:
|
||||||
|
|
||||||
|
Background@KICK_CLIENT_DIALOG:
|
||||||
|
X:20
|
||||||
|
Y:67
|
||||||
|
Width:504
|
||||||
|
Height:235
|
||||||
|
Logic:KickClientLogic
|
||||||
|
Background:dialog3
|
||||||
|
Children:
|
||||||
|
Label@TITLE:
|
||||||
|
X:0
|
||||||
|
Y:40
|
||||||
|
Width:PARENT_RIGHT
|
||||||
|
Height:25
|
||||||
|
Font:Bold
|
||||||
|
Align:Center
|
||||||
|
Label@TEXTA:
|
||||||
|
X:0
|
||||||
|
Y:67
|
||||||
|
Width:PARENT_RIGHT
|
||||||
|
Height:25
|
||||||
|
Font:Regular
|
||||||
|
Align:Center
|
||||||
|
Text:You may also apply a temporary ban, preventing
|
||||||
|
Label@TEXTB:
|
||||||
|
X:0
|
||||||
|
Y:85
|
||||||
|
Width:PARENT_RIGHT
|
||||||
|
Height:25
|
||||||
|
Font:Regular
|
||||||
|
Align:Center
|
||||||
|
Text:them from joining for the remainder of this game.
|
||||||
|
Checkbox@PREVENT_REJOINING_CHECKBOX:
|
||||||
|
X:(PARENT_RIGHT - WIDTH)/2
|
||||||
|
Y:120
|
||||||
|
Width:150
|
||||||
|
Height:20
|
||||||
|
Text:Temporarily Ban
|
||||||
|
Button@OK_BUTTON:
|
||||||
|
X:(PARENT_RIGHT - WIDTH)/2 + 75
|
||||||
|
Y:155
|
||||||
|
Width:120
|
||||||
|
Height:25
|
||||||
|
Text:Kick
|
||||||
|
Font:Bold
|
||||||
|
Button@CANCEL_BUTTON:
|
||||||
|
X:(PARENT_RIGHT - WIDTH)/2 - 75
|
||||||
|
Y:155
|
||||||
|
Width:120
|
||||||
|
Height:25
|
||||||
|
Text:Cancel
|
||||||
|
Font:Bold
|
||||||
@@ -492,3 +492,55 @@ Background@SERVER_LOBBY:
|
|||||||
Text:Start Game
|
Text:Start Game
|
||||||
Font:Bold
|
Font:Bold
|
||||||
TooltipContainer@TOOLTIP_CONTAINER:
|
TooltipContainer@TOOLTIP_CONTAINER:
|
||||||
|
|
||||||
|
Background@KICK_CLIENT_DIALOG:
|
||||||
|
X:20
|
||||||
|
Y:67
|
||||||
|
Width:504
|
||||||
|
Height:235
|
||||||
|
Logic:KickClientLogic
|
||||||
|
Background:dialog3
|
||||||
|
Children:
|
||||||
|
Label@TITLE:
|
||||||
|
X:0
|
||||||
|
Y:40
|
||||||
|
Width:PARENT_RIGHT
|
||||||
|
Height:25
|
||||||
|
Font:Bold
|
||||||
|
Align:Center
|
||||||
|
Label@TEXTA:
|
||||||
|
X:0
|
||||||
|
Y:67
|
||||||
|
Width:PARENT_RIGHT
|
||||||
|
Height:25
|
||||||
|
Font:Regular
|
||||||
|
Align:Center
|
||||||
|
Text:You may also apply a temporary ban, preventing
|
||||||
|
Label@TEXTB:
|
||||||
|
X:0
|
||||||
|
Y:85
|
||||||
|
Width:PARENT_RIGHT
|
||||||
|
Height:25
|
||||||
|
Font:Regular
|
||||||
|
Align:Center
|
||||||
|
Text:them from joining for the remainder of this game.
|
||||||
|
Checkbox@PREVENT_REJOINING_CHECKBOX:
|
||||||
|
X:(PARENT_RIGHT - WIDTH)/2
|
||||||
|
Y:120
|
||||||
|
Width:150
|
||||||
|
Height:20
|
||||||
|
Text:Temporarily Ban
|
||||||
|
Button@OK_BUTTON:
|
||||||
|
X:(PARENT_RIGHT - WIDTH)/2 + 75
|
||||||
|
Y:155
|
||||||
|
Width:120
|
||||||
|
Height:25
|
||||||
|
Text:Kick
|
||||||
|
Font:Bold
|
||||||
|
Button@CANCEL_BUTTON:
|
||||||
|
X:(PARENT_RIGHT - WIDTH)/2 - 75
|
||||||
|
Y:155
|
||||||
|
Width:120
|
||||||
|
Height:25
|
||||||
|
Text:Cancel
|
||||||
|
Font:Bold
|
||||||
Reference in New Issue
Block a user