Merge branch 'master' of https://github.com/space-wizards/space-station-14
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Content.Client.GameObjects.Components
|
||||
{
|
||||
public sealed class ComputerVisualizer2D : AppearanceVisualizer
|
||||
{
|
||||
private string KeyboardState = "generic_key";
|
||||
private string ScreenState = "generic";
|
||||
|
||||
public override void LoadData(YamlMappingNode node)
|
||||
{
|
||||
base.LoadData(node);
|
||||
|
||||
if (node.TryGetNode("key", out var scalar))
|
||||
{
|
||||
KeyboardState = scalar.AsString();
|
||||
}
|
||||
|
||||
if (node.TryGetNode("screen", out scalar))
|
||||
{
|
||||
ScreenState = scalar.AsString();
|
||||
}
|
||||
}
|
||||
|
||||
public override void InitializeEntity(IEntity entity)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
|
||||
var sprite = entity.GetComponent<ISpriteComponent>();
|
||||
sprite.LayerSetState(Layers.Screen, ScreenState);
|
||||
sprite.LayerSetState(Layers.Keyboard, $"{KeyboardState}_off");
|
||||
sprite.LayerSetState(Layers.KeyboardOn, KeyboardState);
|
||||
}
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||
|
||||
if (!component.TryGetData(ComputerVisuals.Powered, out bool powered))
|
||||
{
|
||||
powered = true;
|
||||
}
|
||||
|
||||
component.TryGetData(ComputerVisuals.Broken, out bool broken);
|
||||
|
||||
if (broken)
|
||||
{
|
||||
sprite.LayerSetState(Layers.Body, "broken");
|
||||
sprite.LayerSetState(Layers.Screen, "computer_broken");
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.LayerSetState(Layers.Body, "computer");
|
||||
sprite.LayerSetState(Layers.Screen, ScreenState);
|
||||
}
|
||||
|
||||
sprite.LayerSetVisible(Layers.Screen, powered);
|
||||
sprite.LayerSetVisible(Layers.KeyboardOn, powered);
|
||||
}
|
||||
|
||||
public enum Layers
|
||||
{
|
||||
Body,
|
||||
Screen,
|
||||
Keyboard,
|
||||
KeyboardOn
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ namespace Content.Client.GameObjects
|
||||
base.Initialize();
|
||||
|
||||
_window = new HumanInventoryWindow(_loc, _resourceCache);
|
||||
|
||||
_window.OnClose += () => GameHud.InventoryButtonDown = false;
|
||||
foreach (var (slot, button) in _window.Buttons)
|
||||
{
|
||||
button.OnPressed = AddToInventory;
|
||||
@@ -185,7 +185,7 @@ namespace Content.Client.GameObjects
|
||||
|
||||
// Right column
|
||||
AddButton(Slots.EARS, "ears", (2 * (size + sep), 0));
|
||||
AddButton(Slots.IDCARD, "mask", (2 * (size + sep), size + sep));
|
||||
AddButton(Slots.IDCARD, "id", (2 * (size + sep), size + sep));
|
||||
AddButton(Slots.GLOVES, "gloves", (2 * (size + sep), 2 * (size + sep)));
|
||||
|
||||
// Far right column.
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
using System;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Shared.GameObjects.Components.Power;
|
||||
using NJsonSchema.Validation;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using Robust.Client.GameObjects.Components.UserInterface;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.Interfaces.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Power
|
||||
{
|
||||
@@ -72,34 +67,34 @@ namespace Content.Client.GameObjects.Components.Power
|
||||
|
||||
_chargeBar.Value = castState.Charge;
|
||||
UpdateChargeBarColor(castState.Charge);
|
||||
float ChargePercentage = (castState.Charge / _chargeBar.MaxValue) * 100.0f;
|
||||
_window.ChargePercentage.Text = " " + ChargePercentage.ToString("0.00") + "%";
|
||||
var chargePercentage = (castState.Charge / _chargeBar.MaxValue) * 100.0f;
|
||||
_window.ChargePercentage.Text = " " + chargePercentage.ToString("0.00") + "%";
|
||||
}
|
||||
|
||||
private void UpdateChargeBarColor(float charge)
|
||||
{
|
||||
float normalizedCharge = charge / _chargeBar.MaxValue;
|
||||
var normalizedCharge = charge / _chargeBar.MaxValue;
|
||||
|
||||
float leftHue = 0.0f;// Red
|
||||
float middleHue = 0.066f;// Orange
|
||||
float rightHue = 0.33f;// Green
|
||||
float saturation = 1.0f;// Uniform saturation
|
||||
float value = 0.8f;// Uniform value / brightness
|
||||
float alpha = 1.0f;// Uniform alpha
|
||||
const float leftHue = 0.0f; // Red
|
||||
const float middleHue = 0.066f; // Orange
|
||||
const float rightHue = 0.33f; // Green
|
||||
const float saturation = 1.0f; // Uniform saturation
|
||||
const float value = 0.8f; // Uniform value / brightness
|
||||
const float alpha = 1.0f; // Uniform alpha
|
||||
|
||||
// These should add up to 1.0 or your transition won't be smooth
|
||||
float leftSideSize = 0.5f;// Fraction of _chargeBar lerped from leftHue to middleHue
|
||||
float rightSideSize = 0.5f;// Fraction of _chargeBar lerped from middleHue to rightHue
|
||||
const float leftSideSize = 0.5f; // Fraction of _chargeBar lerped from leftHue to middleHue
|
||||
const float rightSideSize = 0.5f; // Fraction of _chargeBar lerped from middleHue to rightHue
|
||||
|
||||
float finalHue;
|
||||
if (normalizedCharge <= leftSideSize)
|
||||
{
|
||||
normalizedCharge /= leftSideSize;// Adjust range to 0.0 to 1.0
|
||||
normalizedCharge /= leftSideSize; // Adjust range to 0.0 to 1.0
|
||||
finalHue = FloatMath.Lerp(leftHue, middleHue, normalizedCharge);
|
||||
}
|
||||
else
|
||||
{
|
||||
normalizedCharge = (normalizedCharge - leftSideSize) / rightSideSize;// Adjust range to 0.0 to 1.0.
|
||||
normalizedCharge = (normalizedCharge - leftSideSize) / rightSideSize; // Adjust range to 0.0 to 1.0.
|
||||
finalHue = FloatMath.Lerp(middleHue, rightHue, normalizedCharge);
|
||||
}
|
||||
|
||||
@@ -109,7 +104,7 @@ namespace Content.Client.GameObjects.Components.Power
|
||||
_chargeBar.ForegroundStyleBoxOverride = new StyleBoxFlat();
|
||||
}
|
||||
|
||||
var foregroundStyleBoxOverride = (StyleBoxFlat)_chargeBar.ForegroundStyleBoxOverride;
|
||||
var foregroundStyleBoxOverride = (StyleBoxFlat) _chargeBar.ForegroundStyleBoxOverride;
|
||||
foregroundStyleBoxOverride.BackgroundColor =
|
||||
Color.FromHsv(new Vector4(finalHue, saturation, value, alpha));
|
||||
}
|
||||
@@ -134,29 +129,29 @@ namespace Content.Client.GameObjects.Components.Power
|
||||
public ApcWindow()
|
||||
{
|
||||
Title = "APC";
|
||||
var rows = new VBoxContainer("Rows");
|
||||
var rows = new VBoxContainer();
|
||||
|
||||
var statusHeader = new Label("StatusHeader") { Text = "Power Status: " };
|
||||
var statusHeader = new Label {Text = "Power Status: "};
|
||||
rows.AddChild(statusHeader);
|
||||
|
||||
var breaker = new HBoxContainer("Breaker");
|
||||
var breakerLabel = new Label("Label") { Text = "Main Breaker: " };
|
||||
BreakerButton = new CheckButton {Name = "Breaker", Text = "Toggle"};
|
||||
var breaker = new HBoxContainer();
|
||||
var breakerLabel = new Label {Text = "Main Breaker: "};
|
||||
BreakerButton = new CheckButton {Text = "Toggle"};
|
||||
breaker.AddChild(breakerLabel);
|
||||
breaker.AddChild(BreakerButton);
|
||||
rows.AddChild(breaker);
|
||||
|
||||
var externalStatus = new HBoxContainer("ExternalStatus");
|
||||
var externalStatusLabel = new Label("Label") { Text = "External Power: " };
|
||||
ExternalPowerStateLabel = new Label("Status") { Text = "Good" };
|
||||
var externalStatus = new HBoxContainer();
|
||||
var externalStatusLabel = new Label {Text = "External Power: "};
|
||||
ExternalPowerStateLabel = new Label {Text = "Good"};
|
||||
ExternalPowerStateLabel.SetOnlyStyleClass(NanoStyle.StyleClassPowerStateGood);
|
||||
externalStatus.AddChild(externalStatusLabel);
|
||||
externalStatus.AddChild(ExternalPowerStateLabel);
|
||||
rows.AddChild(externalStatus);
|
||||
|
||||
var charge = new HBoxContainer("Charge");
|
||||
var chargeLabel = new Label("Label") { Text = "Charge:" };
|
||||
ChargeBar = new ProgressBar("Charge")
|
||||
var charge = new HBoxContainer();
|
||||
var chargeLabel = new Label {Text = "Charge:"};
|
||||
ChargeBar = new ProgressBar
|
||||
{
|
||||
SizeFlagsHorizontal = Control.SizeFlags.FillExpand,
|
||||
MinValue = 0.0f,
|
||||
@@ -164,7 +159,7 @@ namespace Content.Client.GameObjects.Components.Power
|
||||
Page = 0.0f,
|
||||
Value = 0.5f
|
||||
};
|
||||
ChargePercentage = new Label("ChargePercentage");
|
||||
ChargePercentage = new Label();
|
||||
charge.AddChild(chargeLabel);
|
||||
charge.AddChild(ChargeBar);
|
||||
charge.AddChild(ChargePercentage);
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Content.Client.GameObjects.Components.Storage
|
||||
base.OnAdd();
|
||||
|
||||
Window = new StorageWindow()
|
||||
{ StorageEntity = this};
|
||||
{StorageEntity = this};
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
@@ -44,7 +44,8 @@ namespace Content.Client.GameObjects.Components.Storage
|
||||
base.ExposeData(serializer);
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
|
||||
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null,
|
||||
IComponent component = null)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
@@ -108,32 +109,27 @@ namespace Content.Client.GameObjects.Components.Storage
|
||||
|
||||
public StorageWindow()
|
||||
{
|
||||
Size = new Vector2(180.0f, 320.0f);
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Size = (180, 320);
|
||||
|
||||
Title = "Storage Item";
|
||||
RectClipContent = true;
|
||||
|
||||
VSplitContainer = new VBoxContainer("VSplitContainer");
|
||||
Information = new Label("Information")
|
||||
VSplitContainer = new VBoxContainer();
|
||||
Information = new Label
|
||||
{
|
||||
Text = "Items: 0 Volume: 0/0 Stuff",
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter
|
||||
};
|
||||
VSplitContainer.AddChild(Information);
|
||||
|
||||
var listScrollContainer = new ScrollContainer("ListScrollContainer")
|
||||
var listScrollContainer = new ScrollContainer
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
HScrollEnabled = true,
|
||||
VScrollEnabled = true
|
||||
};
|
||||
EntityList = new VBoxContainer("EntityList")
|
||||
EntityList = new VBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||
};
|
||||
@@ -182,7 +178,8 @@ namespace Content.Client.GameObjects.Components.Storage
|
||||
//Sets information about entire storage container current capacity
|
||||
if (StorageEntity.StorageCapacityMax != 0)
|
||||
{
|
||||
Information.Text = String.Format("Items: {0}, Stored: {1}/{2}", storagelist.Count, StorageEntity.StorageSizeUsed, StorageEntity.StorageCapacityMax);
|
||||
Information.Text = String.Format("Items: {0}, Stored: {1}/{2}", storagelist.Count,
|
||||
StorageEntity.StorageSizeUsed, StorageEntity.StorageCapacityMax);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -196,7 +193,7 @@ namespace Content.Client.GameObjects.Components.Storage
|
||||
/// <param name="args"></param>
|
||||
private void OnItemButtonToggled(BaseButton.ButtonToggledEventArgs args)
|
||||
{
|
||||
var control = (EntityButton)args.Button.Parent;
|
||||
var control = (EntityButton) args.Button.Parent;
|
||||
args.Button.Pressed = false;
|
||||
StorageEntity.Interact(control.EntityuID);
|
||||
}
|
||||
@@ -208,17 +205,15 @@ namespace Content.Client.GameObjects.Components.Storage
|
||||
private class EntityButton : PanelContainer
|
||||
{
|
||||
public EntityUid EntityuID { get; set; }
|
||||
public Button ActualButton { get; private set; }
|
||||
public SpriteView EntitySpriteView { get; private set; }
|
||||
public Control EntityControl { get; private set; }
|
||||
public Label EntityName { get; private set; }
|
||||
public Label EntitySize { get; private set; }
|
||||
public Button ActualButton { get; }
|
||||
public SpriteView EntitySpriteView { get; }
|
||||
public Control EntityControl { get; }
|
||||
public Label EntityName { get; }
|
||||
public Label EntitySize { get; }
|
||||
|
||||
protected override void Initialize()
|
||||
public EntityButton()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
ActualButton = new Button("Button")
|
||||
ActualButton = new Button
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||
@@ -227,12 +222,12 @@ namespace Content.Client.GameObjects.Components.Storage
|
||||
};
|
||||
AddChild(ActualButton);
|
||||
|
||||
var hBoxContainer = new HBoxContainer("HBoxContainer") {MouseFilter = MouseFilterMode.Ignore};
|
||||
EntitySpriteView = new SpriteView("SpriteView")
|
||||
var hBoxContainer = new HBoxContainer {MouseFilter = MouseFilterMode.Ignore};
|
||||
EntitySpriteView = new SpriteView
|
||||
{
|
||||
CustomMinimumSize = new Vector2(32.0f, 32.0f), MouseFilter = MouseFilterMode.Ignore
|
||||
};
|
||||
EntityName = new Label("Name")
|
||||
EntityName = new Label
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
Text = "Backpack",
|
||||
@@ -241,11 +236,11 @@ namespace Content.Client.GameObjects.Components.Storage
|
||||
hBoxContainer.AddChild(EntitySpriteView);
|
||||
hBoxContainer.AddChild(EntityName);
|
||||
|
||||
EntityControl = new Control("Control")
|
||||
EntityControl = new Control
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand, MouseFilter = MouseFilterMode.Ignore
|
||||
};
|
||||
EntitySize = new Label("Size")
|
||||
EntitySize = new Label
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
Text = "Size 6",
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using Content.Client.VendingMachines;
|
||||
using Content.Shared.GameObjects.Components.VendingMachines;
|
||||
using Robust.Client.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.VendingMachines
|
||||
{
|
||||
class VendingMachineBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private VendingMachineMenu _menu;
|
||||
|
||||
public SharedVendingMachineComponent VendingMachine { get; private set; }
|
||||
|
||||
public VendingMachineBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
{
|
||||
SendMessage(new SharedVendingMachineComponent.InventorySyncRequestMessage());
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
if(!Owner.Owner.TryGetComponent(out SharedVendingMachineComponent vendingMachine))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VendingMachine = vendingMachine;
|
||||
|
||||
_menu = new VendingMachineMenu() { Owner = this, Title = Owner.Owner.Name };
|
||||
_menu.Populate(VendingMachine.Inventory);
|
||||
|
||||
_menu.OnClose += Close;
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
public void Eject(string ID)
|
||||
{
|
||||
SendMessage(new SharedVendingMachineComponent.VendingMachineEjectMessage(ID));
|
||||
}
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
switch(message)
|
||||
{
|
||||
case SharedVendingMachineComponent.VendingMachineInventoryMessage msg:
|
||||
_menu.Populate(msg.Inventory);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if(!disposing) { return; }
|
||||
_menu?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using Content.Client.GameObjects.Components.Doors;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.GameObjects.Components.Animations;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using static Content.Shared.GameObjects.Components.VendingMachines.SharedVendingMachineComponent;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.VendingMachines
|
||||
{
|
||||
public class VendingMachineVisualizer2D : AppearanceVisualizer
|
||||
{
|
||||
// TODO: The length of these animations is supposed to be dictated
|
||||
// by the vending machine's pack prototype's `AnimationDuration`
|
||||
// but we have no good way of passing that data from the server
|
||||
// to the client at the moment. Rework Visualizers?
|
||||
private const string DeniedAnimationKey = "deny";
|
||||
private const string EjectAnimationKey = "eject";
|
||||
|
||||
private Animation _deniedAnimation;
|
||||
private Animation _ejectAnimation;
|
||||
|
||||
public override void LoadData(YamlMappingNode node)
|
||||
{
|
||||
base.LoadData(node);
|
||||
_deniedAnimation = new Animation {Length = TimeSpan.FromSeconds(1.2f)};
|
||||
{
|
||||
var flick = new AnimationTrackSpriteFlick();
|
||||
_deniedAnimation.AnimationTracks.Add(flick);
|
||||
flick.LayerKey = VendingMachineVisualLayers.Base;
|
||||
flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("deny", 0f));
|
||||
}
|
||||
|
||||
_ejectAnimation = new Animation {Length = TimeSpan.FromSeconds(1.2f)};
|
||||
{
|
||||
var flick = new AnimationTrackSpriteFlick();
|
||||
_ejectAnimation.AnimationTracks.Add(flick);
|
||||
flick.LayerKey = VendingMachineVisualLayers.Base;
|
||||
flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("eject", 0f));
|
||||
}
|
||||
}
|
||||
|
||||
public override void InitializeEntity(IEntity entity)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
|
||||
if (!entity.HasComponent<AnimationPlayerComponent>())
|
||||
{
|
||||
entity.AddComponent<AnimationPlayerComponent>();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||
var animPlayer = component.Owner.GetComponent<AnimationPlayerComponent>();
|
||||
if (!component.TryGetData(VendingMachineVisuals.VisualState, out VendingMachineVisualState state))
|
||||
{
|
||||
state = VendingMachineVisualState.Normal;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case VendingMachineVisualState.Normal:
|
||||
sprite.LayerSetState(VendingMachineVisualLayers.Base, "normal");
|
||||
break;
|
||||
case VendingMachineVisualState.Off:
|
||||
sprite.LayerSetState(VendingMachineVisualLayers.Base, "off");
|
||||
break;
|
||||
case VendingMachineVisualState.Broken:
|
||||
sprite.LayerSetState(VendingMachineVisualLayers.Base, "broken");
|
||||
break;
|
||||
case VendingMachineVisualState.Deny:
|
||||
if (!animPlayer.HasRunningAnimation(DeniedAnimationKey))
|
||||
{
|
||||
animPlayer.Play(_deniedAnimation, DeniedAnimationKey);
|
||||
}
|
||||
|
||||
break;
|
||||
case VendingMachineVisualState.Eject:
|
||||
if (!animPlayer.HasRunningAnimation(EjectAnimationKey))
|
||||
{
|
||||
animPlayer.Play(_ejectAnimation, EjectAnimationKey);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public enum VendingMachineVisualLayers
|
||||
{
|
||||
Base,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user