diff --git a/Content.Client/Arcade/SpaceVillainArcadeMenu.cs b/Content.Client/Arcade/SpaceVillainArcadeMenu.cs
index a6463019f1..529d4b6fa7 100644
--- a/Content.Client/Arcade/SpaceVillainArcadeMenu.cs
+++ b/Content.Client/Arcade/SpaceVillainArcadeMenu.cs
@@ -1,7 +1,9 @@
-using Content.Client.GameObjects.Components.Arcade;
+using System.Linq;
+using Content.Client.GameObjects.Components.Arcade;
using Content.Shared.GameObjects.Components.Arcade;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
+using Robust.Shared.Localization;
using Vector2 = Robust.Shared.Maths.Vector2;
namespace Content.Client.Arcade
@@ -16,17 +18,17 @@ namespace Content.Client.Arcade
private Label _enemyInfoLabel;
private Label _playerActionLabel;
private Label _enemyActionLabel;
+
+ private Button[] _gameButtons = new Button[3]; //used to disable/enable all game buttons
public SpaceVillainArcadeMenu(SpaceVillainArcadeBoundUserInterface owner)
{
- Title = "Space Villain";
+ Title = Loc.GetString("Space Villain");
Owner = owner;
- GridContainer grid = new GridContainer();
- grid.Columns = 1;
+ var grid = new GridContainer {Columns = 1};
- GridContainer infoGrid = new GridContainer();
- infoGrid.Columns = 3;
- infoGrid.AddChild(new Label{ Text = "Player", Align = Label.AlignMode.Center });
+ var infoGrid = new GridContainer {Columns = 3};
+ infoGrid.AddChild(new Label{ Text = Loc.GetString("Player"), Align = Label.AlignMode.Center });
infoGrid.AddChild(new Label{ Text = "|", Align = Label.AlignMode.Center });
_enemyNameLabel = new Label{ Align = Label.AlignMode.Center};
infoGrid.AddChild(_enemyNameLabel);
@@ -36,38 +38,35 @@ namespace Content.Client.Arcade
infoGrid.AddChild(new Label{ Text = "|", Align = Label.AlignMode.Center });
_enemyInfoLabel = new Label {Align = Label.AlignMode.Center};
infoGrid.AddChild(_enemyInfoLabel);
- CenterContainer centerContainer = new CenterContainer();
+ var centerContainer = new CenterContainer();
centerContainer.AddChild(infoGrid);
grid.AddChild(centerContainer);
- _playerActionLabel = new Label();
- _playerActionLabel.Align = Label.AlignMode.Center;
+ _playerActionLabel = new Label {Align = Label.AlignMode.Center};
grid.AddChild(_playerActionLabel);
- _enemyActionLabel = new Label();
- _enemyActionLabel.Align = Label.AlignMode.Center;
+ _enemyActionLabel = new Label {Align = Label.AlignMode.Center};
grid.AddChild(_enemyActionLabel);
- GridContainer buttonGrid = new GridContainer();
- buttonGrid.Columns = 3;
- Button attack = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Attack);
- attack.Text = "ATTACK";
- buttonGrid.AddChild(attack);
+ var buttonGrid = new GridContainer {Columns = 3};
+ _gameButtons[0] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Attack)
+ {Text = Loc.GetString("ATTACK")};
+ buttonGrid.AddChild(_gameButtons[0]);
- Button heal = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Heal);
- heal.Text = "HEAL";
- buttonGrid.AddChild(heal);
+ _gameButtons[1] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Heal)
+ {Text = Loc.GetString("HEAL")};
+ buttonGrid.AddChild(_gameButtons[1]);
- Button recharge = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Recharge);
- recharge.Text = "RECHARGE";
- buttonGrid.AddChild(recharge);
+ _gameButtons[2] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Recharge)
+ {Text = Loc.GetString("RECHARGE")};
+ buttonGrid.AddChild(_gameButtons[2]);
centerContainer = new CenterContainer();
centerContainer.AddChild(buttonGrid);
grid.AddChild(centerContainer);
- Button newGame = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.NewGame);
- newGame.Text = "New Game";
+ var newGame = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.NewGame)
+ {Text = Loc.GetString("New Game")};
grid.AddChild(newGame);
centerContainer = new CenterContainer();
@@ -79,6 +78,11 @@ namespace Content.Client.Arcade
{
Title = message.GameTitle;
_enemyNameLabel.Text = message.EnemyName;
+
+ foreach (var gameButton in _gameButtons)
+ {
+ gameButton.Disabled = message.ButtonsDisabled;
+ }
}
public void UpdateInfo(SharedSpaceVillainArcadeComponent.SpaceVillainArcadeDataUpdateMessage message)
diff --git a/Content.Client/ClientNotifyManager.cs b/Content.Client/ClientNotifyManager.cs
index ccb4ae3a03..aabeadf003 100644
--- a/Content.Client/ClientNotifyManager.cs
+++ b/Content.Client/ClientNotifyManager.cs
@@ -1,3 +1,4 @@
+#nullable enable
using System;
using System.Collections.Generic;
using Content.Client.Interfaces;
@@ -54,27 +55,23 @@ namespace Content.Client
private void DoNotifyEntity(MsgDoNotifyEntity message)
{
- if (!_entityManager.TryGetEntity(message.Entity, out var entity))
+ if (_playerManager.LocalPlayer?.ControlledEntity == null ||
+ !_entityManager.TryGetEntity(message.Entity, out var entity))
{
return;
}
- PopupMessage(_eyeManager.CoordinatesToScreen(entity.Transform.Coordinates), message.Message);
+ PopupMessage(entity, _playerManager.LocalPlayer.ControlledEntity, message.Message);
}
public override void PopupMessage(IEntity source, IEntity viewer, string message)
{
- if (viewer != _playerManager.LocalPlayer?.ControlledEntity)
- {
- return;
- }
-
- PopupMessage(_eyeManager.CoordinatesToScreen(source.Transform.Coordinates), message);
+ PopupMessage(_eyeManager.CoordinatesToScreen(source.Transform.Coordinates), message, source);
}
public override void PopupMessage(EntityCoordinates coordinates, IEntity viewer, string message)
{
- if (viewer != _playerManager.LocalPlayer.ControlledEntity)
+ if (viewer != _playerManager.LocalPlayer?.ControlledEntity)
{
return;
}
@@ -84,7 +81,7 @@ namespace Content.Client
public override void PopupMessageCursor(IEntity viewer, string message)
{
- if (viewer != _playerManager.LocalPlayer.ControlledEntity)
+ if (viewer != _playerManager.LocalPlayer?.ControlledEntity)
{
return;
}
@@ -94,13 +91,21 @@ namespace Content.Client
public void PopupMessage(ScreenCoordinates coordinates, string message)
{
- var label = new PopupLabel
+ PopupMessage(coordinates, message, null);
+ }
+
+ public void PopupMessage(ScreenCoordinates coordinates, string message, IEntity? entity)
+ {
+ var label = new PopupLabel(_eyeManager)
{
+ Entity = entity,
Text = message,
StyleClasses = { StyleNano.StyleClassPopupMessage },
};
+
_userInterfaceManager.PopupRoot.AddChild(label);
var minimumSize = label.CombinedMinimumSize;
+
LayoutContainer.SetPosition(label, label.InitialPos = coordinates.Position - minimumSize / 2);
_aliveLabels.Add(label);
}
@@ -125,20 +130,30 @@ namespace Content.Client
private class PopupLabel : Label
{
+ private readonly IEyeManager _eyeManager;
+
public float TimeLeft { get; private set; }
public Vector2 InitialPos { get; set; }
+ public IEntity? Entity { get; set; }
- public PopupLabel()
+ public PopupLabel(IEyeManager eyeManager)
{
+ _eyeManager = eyeManager;
ShadowOffsetXOverride = 1;
ShadowOffsetYOverride = 1;
FontColorShadowOverride = Color.Black;
}
- protected override void Update(FrameEventArgs eventArgs)
+ protected override void FrameUpdate(FrameEventArgs eventArgs)
{
TimeLeft += eventArgs.DeltaSeconds;
- LayoutContainer.SetPosition(this, InitialPos - (0, 20 * (TimeLeft * TimeLeft + TimeLeft)));
+
+ var position = Entity == null
+ ? InitialPos
+ : _eyeManager.CoordinatesToScreen(Entity.Transform.Coordinates).Position - CombinedMinimumSize / 2;
+
+ LayoutContainer.SetPosition(this, position - (0, 20 * (TimeLeft * TimeLeft + TimeLeft)));
+
if (TimeLeft > 0.5f)
{
Modulate = Color.White.WithAlpha(1f - 0.2f * (float)Math.Pow(TimeLeft - 0.5f, 3f));
diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj
index 5c56fa52fc..5a9c5641d6 100644
--- a/Content.Client/Content.Client.csproj
+++ b/Content.Client/Content.Client.csproj
@@ -8,6 +8,7 @@
false
..\bin\Content.Client\
Exe
+ CS8604;CS8765
diff --git a/Content.Client/GameObjects/Components/Body/BodyComponent.cs b/Content.Client/GameObjects/Components/Body/BodyComponent.cs
index fb36486055..524513b372 100644
--- a/Content.Client/GameObjects/Components/Body/BodyComponent.cs
+++ b/Content.Client/GameObjects/Components/Body/BodyComponent.cs
@@ -1,4 +1,4 @@
-#nullable enable
+#nullable enable
using Content.Client.GameObjects.Components.Disposal;
using Content.Client.GameObjects.Components.MedicalScanner;
using Content.Shared.GameObjects.Components.Body;
@@ -14,7 +14,8 @@ namespace Content.Client.GameObjects.Components.Body
public bool CanDrop(CanDropEventArgs eventArgs)
{
if (eventArgs.Target.HasComponent() ||
- eventArgs.Target.HasComponent())
+ eventArgs.Target.HasComponent() ||
+ eventArgs.Target.HasComponent())
{
return true;
}
diff --git a/Content.Client/GameObjects/Components/Configuration/ConfigurationBoundUserInterface.cs b/Content.Client/GameObjects/Components/Configuration/ConfigurationBoundUserInterface.cs
new file mode 100644
index 0000000000..d173f061ed
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Configuration/ConfigurationBoundUserInterface.cs
@@ -0,0 +1,55 @@
+using Robust.Client.GameObjects.Components.UserInterface;
+using Robust.Shared.GameObjects.Components.UserInterface;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+using static Content.Shared.GameObjects.Components.SharedConfigurationComponent;
+
+namespace Content.Client.GameObjects.Components.Wires
+{
+ public class ConfigurationBoundUserInterface : BoundUserInterface
+ {
+ public Regex Validation { get; internal set; }
+
+ public ConfigurationBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
+ {
+ }
+
+ private ConfigurationMenu _menu;
+
+ protected override void Open()
+ {
+ base.Open();
+ _menu = new ConfigurationMenu(this);
+
+ _menu.OnClose += Close;
+ _menu.OpenCentered();
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+ _menu.Populate(state as ConfigurationBoundUserInterfaceState);
+ }
+
+ protected override void ReceiveMessage(BoundUserInterfaceMessage message)
+ {
+ base.ReceiveMessage(message);
+ if (message is ValidationUpdateMessage msg)
+ {
+ Validation = new Regex(msg.ValidationString, RegexOptions.Compiled);
+ }
+ }
+
+ public void SendConfiguration(Dictionary config)
+ {
+ SendMessage(new ConfigurationUpdatedMessage(config));
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ _menu.Close();
+ }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Configuration/ConfigurationMenu.cs b/Content.Client/GameObjects/Components/Configuration/ConfigurationMenu.cs
new file mode 100644
index 0000000000..d236ae0202
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Configuration/ConfigurationMenu.cs
@@ -0,0 +1,176 @@
+using System.Collections.Generic;
+using Namotion.Reflection;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Shared.Localization;
+using Robust.Shared.Maths;
+using static Content.Shared.GameObjects.Components.SharedConfigurationComponent;
+using static Robust.Client.UserInterface.Controls.BaseButton;
+
+namespace Content.Client.GameObjects.Components.Wires
+{
+ public class ConfigurationMenu : SS14Window
+ {
+ public ConfigurationBoundUserInterface Owner { get; }
+
+ private readonly VBoxContainer _baseContainer;
+ private readonly VBoxContainer _column;
+ private readonly HBoxContainer _row;
+
+ private readonly List<(string name, LineEdit input)> _inputs;
+
+ protected override Vector2? CustomSize => (300, 250);
+
+ public ConfigurationMenu(ConfigurationBoundUserInterface owner)
+ {
+ Owner = owner;
+
+ _inputs = new List<(string name, LineEdit input)>();
+
+ Title = Loc.GetString("Device Configuration");
+
+ var margin = new MarginContainer
+ {
+ MarginBottomOverride = 8,
+ MarginLeftOverride = 8,
+ MarginRightOverride = 8,
+ MarginTopOverride = 8
+ };
+
+ _baseContainer = new VBoxContainer
+ {
+ SizeFlagsVertical = SizeFlags.FillExpand,
+ SizeFlagsHorizontal = SizeFlags.FillExpand
+ };
+
+ _column = new VBoxContainer
+ {
+ SeparationOverride = 16,
+ SizeFlagsVertical = SizeFlags.Fill
+ };
+
+ _row = new HBoxContainer
+ {
+ SeparationOverride = 16,
+ SizeFlagsHorizontal = SizeFlags.FillExpand
+ };
+
+ var buttonRow = new HBoxContainer
+ {
+ SizeFlagsHorizontal = SizeFlags.FillExpand
+ };
+
+ var spacer1 = new HBoxContainer
+ {
+ SizeFlagsHorizontal = SizeFlags.Expand
+ };
+
+ var spacer2 = new HBoxContainer()
+ {
+ SizeFlagsHorizontal = SizeFlags.Expand
+ };
+
+ var confirmButton = new Button
+ {
+ Text = Loc.GetString("Confirm"),
+ SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
+ SizeFlagsVertical = SizeFlags.ShrinkCenter
+ };
+
+ confirmButton.OnButtonUp += OnConfirm;
+ buttonRow.AddChild(spacer1);
+ buttonRow.AddChild(confirmButton);
+ buttonRow.AddChild(spacer2);
+
+ var outerColumn = new ScrollContainer
+ {
+ SizeFlagsVertical = SizeFlags.FillExpand,
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ ModulateSelfOverride = Color.FromHex("#202025")
+ };
+
+ margin.AddChild(_column);
+ outerColumn.AddChild(margin);
+ _baseContainer.AddChild(outerColumn);
+ _baseContainer.AddChild(buttonRow);
+ Contents.AddChild(_baseContainer);
+ }
+
+ public void Populate(ConfigurationBoundUserInterfaceState state)
+ {
+ _column.Children.Clear();
+ _inputs.Clear();
+
+ foreach (var field in state.Config)
+ {
+ var margin = new MarginContainer
+ {
+ MarginRightOverride = 8
+ };
+
+ var label = new Label
+ {
+ Name = field.Key,
+ Text = field.Key + ":",
+ SizeFlagsVertical = SizeFlags.ShrinkCenter,
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ SizeFlagsStretchRatio = .2f,
+ CustomMinimumSize = new Vector2(60, 0)
+ };
+
+ var input = new LineEdit
+ {
+ Name = field.Key + "-input",
+ Text = field.Value,
+ IsValid = Validate,
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ SizeFlagsStretchRatio = .8f
+ };
+
+ _inputs.Add((field.Key, input));
+
+ var row = new HBoxContainer();
+ CopyProperties(_row, row);
+
+ margin.AddChild(label);
+ row.AddChild(margin);
+ row.AddChild(input);
+ _column.AddChild(row);
+ }
+ }
+
+ private void OnConfirm(ButtonEventArgs args)
+ {
+ var config = GenerateDictionary(_inputs, "Text");
+
+ Owner.SendConfiguration(config);
+ Close();
+ }
+
+ private bool Validate(string value)
+ {
+ return Owner.Validation == null || Owner.Validation.IsMatch(value);
+ }
+
+ private Dictionary GenerateDictionary(List<(string name, TInput input)> inputs, string propertyName) where TInput : Control
+ {
+ var dictionary = new Dictionary();
+ foreach (var input in inputs)
+ {
+ var value = input.input.TryGetPropertyValue(propertyName);
+ dictionary.Add(input.name, value);
+ }
+
+ return dictionary;
+ }
+
+ private static void CopyProperties(T from, T to) where T : Control
+ {
+ foreach (var property in from.AllAttachedProperties)
+ {
+ to.SetValue(property.Key, property.Value);
+ }
+ }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Disposal/DisposalMailingUnitBoundUserInterface.cs b/Content.Client/GameObjects/Components/Disposal/DisposalMailingUnitBoundUserInterface.cs
new file mode 100644
index 0000000000..164ba9f6f5
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Disposal/DisposalMailingUnitBoundUserInterface.cs
@@ -0,0 +1,73 @@
+#nullable enable
+using JetBrains.Annotations;
+using Robust.Client.GameObjects.Components.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.GameObjects.Components.UserInterface;
+using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalMailingUnitComponent;
+
+namespace Content.Client.GameObjects.Components.Disposal
+{
+ ///
+ /// Initializes a and updates it when new server messages are received.
+ ///
+ [UsedImplicitly]
+ public class DisposalMailingUnitBoundUserInterface : BoundUserInterface
+ {
+ private DisposalMailingUnitWindow? _window;
+
+ public DisposalMailingUnitBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
+ {
+ }
+
+ private void ButtonPressed(UiButton button)
+ {
+ SendMessage(new UiButtonPressedMessage(button));
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _window = new DisposalMailingUnitWindow();
+
+ _window.OpenCentered();
+ _window.OnClose += Close;
+
+ _window.Eject.OnPressed += _ => ButtonPressed(UiButton.Eject);
+ _window.Engage.OnPressed += _ => ButtonPressed(UiButton.Engage);
+ _window.Power.OnPressed += _ => ButtonPressed(UiButton.Power);
+ _window.TargetListContainer.OnItemSelected += TargetSelected;
+
+ }
+
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+
+ if (!(state is DisposalMailingUnitBoundUserInterfaceState cast))
+ {
+ return;
+ }
+
+ _window?.UpdateState(cast);
+ }
+
+ private void TargetSelected(ItemList.ItemListSelectedEventArgs item)
+ {
+ SendMessage(new UiTargetUpdateMessage(_window?.TargetList[item.ItemIndex]));
+ //(ノ°Д°)ノ︵ ┻━┻
+ if (_window != null) _window.Engage.Disabled = false;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (disposing)
+ {
+ _window?.Dispose();
+ }
+ }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Disposal/DisposalMailingUnitComponent.cs b/Content.Client/GameObjects/Components/Disposal/DisposalMailingUnitComponent.cs
new file mode 100644
index 0000000000..fd661f6afa
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Disposal/DisposalMailingUnitComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.GameObjects.Components.Disposal;
+using Robust.Shared.GameObjects;
+
+namespace Content.Client.GameObjects.Components.Disposal
+{
+ [RegisterComponent]
+ [ComponentReference(typeof(SharedDisposalMailingUnitComponent))]
+ public class DisposalMailingUnitComponent : SharedDisposalMailingUnitComponent
+ {
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Disposal/DisposalMailingUnitWindow.cs b/Content.Client/GameObjects/Components/Disposal/DisposalMailingUnitWindow.cs
new file mode 100644
index 0000000000..1849a33722
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Disposal/DisposalMailingUnitWindow.cs
@@ -0,0 +1,285 @@
+using Content.Shared.GameObjects.Components.Disposal;
+using Robust.Client.Graphics.Drawing;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Shared.Localization;
+using Robust.Shared.Maths;
+using System.Collections.Generic;
+using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalMailingUnitComponent;
+
+namespace Content.Client.GameObjects.Components.Disposal
+{
+ ///
+ /// Client-side UI used to control a
+ ///
+ public class DisposalMailingUnitWindow : SS14Window
+ {
+ private readonly Label _unitState;
+ private readonly ProgressBar _pressureBar;
+ private readonly Label _pressurePercentage;
+ public readonly Button Engage;
+ public readonly Button Eject;
+ public readonly Button Power;
+
+ public readonly ItemList TargetListContainer;
+ public List TargetList;
+ private readonly Label _tagLabel;
+
+ protected override Vector2? CustomSize => (460, 220);
+
+ public DisposalMailingUnitWindow()
+ {
+ TargetList = new List();
+ Contents.AddChild(new HBoxContainer
+ {
+ Children =
+ {
+ new MarginContainer
+ {
+ MarginLeftOverride = 8,
+ MarginRightOverride = 8,
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ Children =
+ {
+ new VBoxContainer
+ {
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ Children =
+ {
+ new HBoxContainer
+ {
+ Children =
+ {
+ new Label {Text = Loc.GetString("State: ")},
+ new Control {CustomMinimumSize = (4, 0)},
+ (_unitState = new Label {Text = Loc.GetString("Ready")})
+ }
+ },
+ new Control {CustomMinimumSize = (0, 10)},
+ new HBoxContainer
+ {
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ Children =
+ {
+ new Label {Text = Loc.GetString("Pressure:")},
+ new Control {CustomMinimumSize = (4, 0)},
+ (_pressureBar = new ProgressBar
+ {
+ CustomMinimumSize = (100, 20),
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ MinValue = 0,
+ MaxValue = 1,
+ Page = 0,
+ Value = 0.5f,
+ Children =
+ {
+ (_pressurePercentage = new Label())
+ }
+ })
+ }
+ },
+ new Control {CustomMinimumSize = (0, 10)},
+ new HBoxContainer
+ {
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ Children =
+ {
+ new Label {Text = Loc.GetString("Handle:")},
+ new Control {
+ CustomMinimumSize = (4, 0),
+ SizeFlagsHorizontal = SizeFlags.FillExpand
+ },
+ (Engage = new Button
+ {
+ CustomMinimumSize = (16, 0),
+ Text = Loc.GetString("Engage"),
+ ToggleMode = true,
+ Disabled = true
+ })
+ }
+ },
+ new Control {CustomMinimumSize = (0, 10)},
+ new HBoxContainer
+ {
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ Children =
+ {
+ new Label {Text = Loc.GetString("Eject:")},
+ new Control {
+ CustomMinimumSize = (4, 0),
+ SizeFlagsHorizontal = SizeFlags.FillExpand
+ },
+ (Eject = new Button {
+ CustomMinimumSize = (16, 0),
+ Text = Loc.GetString("Eject Contents"),
+ //SizeFlagsHorizontal = SizeFlags.ShrinkEnd
+ })
+ }
+ },
+ new Control {CustomMinimumSize = (0, 10)},
+ new HBoxContainer
+ {
+ Children =
+ {
+ (Power = new CheckButton {Text = Loc.GetString("Power")}),
+ }
+ }
+ }
+ },
+ }
+ },
+ new MarginContainer
+ {
+ MarginLeftOverride = 12,
+ MarginRightOverride = 8,
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ Children =
+ {
+ new VBoxContainer
+ {
+ SizeFlagsHorizontal = SizeFlags.Fill,
+ Children =
+ {
+ new HBoxContainer
+ {
+ Children =
+ {
+ new Label
+ {
+ Text = Loc.GetString("Select a destination:")
+ }
+ }
+ },
+ new Control { CustomMinimumSize = new Vector2(0, 8) },
+ new HBoxContainer
+ {
+ SizeFlagsVertical = SizeFlags.FillExpand,
+ Children =
+ {
+ (TargetListContainer = new ItemList
+ {
+ SelectMode = ItemList.ItemListSelectMode.Single,
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ SizeFlagsVertical = SizeFlags.FillExpand
+ })
+ }
+ },
+ new PanelContainer
+ {
+ PanelOverride = new StyleBoxFlat
+ {
+ BackgroundColor = Color.FromHex("#ACBDBA")
+ },
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ CustomMinimumSize = new Vector2(0, 1),
+ },
+ new HBoxContainer
+ {
+ Children =
+ {
+ new VBoxContainer
+ {
+ Children =
+ {
+ new MarginContainer
+ {
+ MarginLeftOverride = 4,
+ Children =
+ {
+ new HBoxContainer
+ {
+ SizeFlagsHorizontal = SizeFlags.FillExpand,
+ Children =
+ {
+ new Label
+ {
+ Text = Loc.GetString("This unit:")
+ },
+ new Control
+ {
+ CustomMinimumSize = new Vector2(4, 0)
+ },
+ (_tagLabel = new Label
+ {
+ Text = "-",
+ SizeFlagsVertical = SizeFlags.ShrinkEnd
+ })
+ }
+ }
+ }
+ },
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ });
+ }
+
+ private void UpdatePressureBar(float pressure)
+ {
+ _pressureBar.Value = pressure;
+
+ var normalized = pressure / _pressureBar.MaxValue;
+
+ 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
+ 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 (normalized <= leftSideSize)
+ {
+ normalized /= leftSideSize; // Adjust range to 0.0 to 1.0
+ finalHue = MathHelper.Lerp(leftHue, middleHue, normalized);
+ }
+ else
+ {
+ normalized = (normalized - leftSideSize) / rightSideSize; // Adjust range to 0.0 to 1.0.
+ finalHue = MathHelper.Lerp(middleHue, rightHue, normalized);
+ }
+
+ // Check if null first to avoid repeatedly creating this.
+ _pressureBar.ForegroundStyleBoxOverride ??= new StyleBoxFlat();
+
+ var foregroundStyleBoxOverride = (StyleBoxFlat) _pressureBar.ForegroundStyleBoxOverride;
+ foregroundStyleBoxOverride.BackgroundColor =
+ Color.FromHsv(new Vector4(finalHue, saturation, value, alpha));
+
+ var percentage = pressure / _pressureBar.MaxValue * 100;
+ _pressurePercentage.Text = $" {percentage:0}%";
+ }
+
+ public void UpdateState(DisposalMailingUnitBoundUserInterfaceState state)
+ {
+ Title = state.UnitName;
+ _unitState.Text = state.UnitState;
+ UpdatePressureBar(state.Pressure);
+ Power.Pressed = state.Powered;
+ Engage.Pressed = state.Engaged;
+ PopulateTargetList(state.Tags);
+ _tagLabel.Text = state.Tag;
+ TargetList = state.Tags;
+ }
+
+ private void PopulateTargetList(List tags)
+ {
+ TargetListContainer.Clear();
+ foreach (var target in tags)
+ {
+ TargetListContainer.AddItem(target);
+ }
+ }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs b/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs
index 5612a71f67..d74f8d30e7 100644
--- a/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs
+++ b/Content.Client/GameObjects/Components/HUD/Inventory/ClientInventoryComponent.cs
@@ -82,7 +82,10 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory
foreach (var (slot, entityUid) in cast.Entities)
{
- var entity = Owner.EntityManager.GetEntity(entityUid);
+ if (!Owner.EntityManager.TryGetEntity(entityUid, out var entity))
+ {
+ continue;
+ }
if (!_slots.ContainsKey(slot) || _slots[slot] != entity)
{
_slots[slot] = entity;
diff --git a/Content.Client/GameObjects/Components/HandheldLightComponent.cs b/Content.Client/GameObjects/Components/HandheldLightComponent.cs
index fb5be31391..1c09818ae0 100644
--- a/Content.Client/GameObjects/Components/HandheldLightComponent.cs
+++ b/Content.Client/GameObjects/Components/HandheldLightComponent.cs
@@ -1,5 +1,6 @@
using System;
using Content.Shared.GameObjects.Components;
+using Content.Shared.Utility;
using Robust.Client.Graphics.Drawing;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -88,23 +89,27 @@ namespace Content.Client.GameObjects.Components
}
else
{
- level = 1 + (int) MathF.Round(charge * 6);
+ level = ContentHelpers.RoundToNearestLevels(charge, 1.0, 6) + 1;
}
- if (level == 1)
+ if (level == 0)
+ {
+ _sections[0].PanelOverride = _styleBoxUnlit;
+ }
+ else if (level == 1)
{
// Flash the last light.
_sections[0].PanelOverride = _timer > TimerCycle / 2 ? _styleBoxLit : _styleBoxUnlit;
}
else
{
- _sections[0].PanelOverride = level > 2 ? _styleBoxLit : _styleBoxUnlit;
+ _sections[0].PanelOverride = _styleBoxLit;
}
- _sections[1].PanelOverride = level > 3 ? _styleBoxLit : _styleBoxUnlit;
- _sections[2].PanelOverride = level > 4 ? _styleBoxLit : _styleBoxUnlit;
- _sections[3].PanelOverride = level > 5 ? _styleBoxLit : _styleBoxUnlit;
- _sections[4].PanelOverride = level > 6 ? _styleBoxLit : _styleBoxUnlit;
+ _sections[1].PanelOverride = level >= 3 ? _styleBoxLit : _styleBoxUnlit;
+ _sections[2].PanelOverride = level >= 4 ? _styleBoxLit : _styleBoxUnlit;
+ _sections[3].PanelOverride = level >= 5 ? _styleBoxLit : _styleBoxUnlit;
+ _sections[4].PanelOverride = level >= 6 ? _styleBoxLit : _styleBoxUnlit;
}
}
}
diff --git a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs
index ab86148401..d30bcf6da9 100644
--- a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs
+++ b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs
@@ -7,6 +7,7 @@ using Content.Shared.Physics;
using Robust.Client.Audio.Midi;
using Robust.Shared.Audio.Midi;
using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Components.Timers;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
@@ -41,6 +42,10 @@ namespace Content.Client.GameObjects.Components.Instruments
private uint _sequenceStartTick;
+ private bool _allowPercussion;
+
+ private bool _allowProgramChange;
+
///
/// A queue of MidiEvents to be sent to the server.
///
@@ -67,7 +72,7 @@ namespace Content.Client.GameObjects.Components.Instruments
/// Changes the instrument the midi renderer will play.
///
[ViewVariables(VVAccess.ReadWrite)]
- public byte InstrumentProgram
+ public override byte InstrumentProgram
{
get => _instrumentProgram;
set
@@ -84,7 +89,7 @@ namespace Content.Client.GameObjects.Components.Instruments
/// Changes the instrument bank the midi renderer will use.
///
[ViewVariables(VVAccess.ReadWrite)]
- public byte InstrumentBank
+ public override byte InstrumentBank
{
get => _instrumentBank;
set
@@ -97,6 +102,34 @@ namespace Content.Client.GameObjects.Components.Instruments
}
}
+ [ViewVariables(VVAccess.ReadWrite)]
+ public override bool AllowPercussion
+ {
+ get => _allowPercussion;
+ set
+ {
+ _allowPercussion = value;
+ if (_renderer != null)
+ {
+ _renderer.DisablePercussionChannel = !_allowPercussion;
+ }
+ }
+ }
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ public override bool AllowProgramChange
+ {
+ get => _allowProgramChange;
+ set
+ {
+ _allowProgramChange = value;
+ if (_renderer != null)
+ {
+ _renderer.DisableProgramChangeEvent = !_allowProgramChange;
+ }
+ }
+ }
+
///
/// Whether this instrument is handheld or not.
///
@@ -127,7 +160,7 @@ namespace Content.Client.GameObjects.Components.Instruments
IoCManager.InjectDependencies(this);
}
- protected void SetupRenderer(bool fromStateChange = false)
+ protected virtual void SetupRenderer(bool fromStateChange = false)
{
if (IsRendererAlive) return;
@@ -141,6 +174,8 @@ namespace Content.Client.GameObjects.Components.Instruments
_renderer.MidiBank = _instrumentBank;
_renderer.MidiProgram = _instrumentProgram;
_renderer.TrackingEntity = Owner;
+ _renderer.DisablePercussionChannel = !_allowPercussion;
+ _renderer.DisableProgramChangeEvent = !_allowProgramChange;
_renderer.OnMidiPlayerFinished += () =>
{
OnMidiPlaybackEnded?.Invoke();
@@ -173,7 +208,7 @@ namespace Content.Client.GameObjects.Components.Instruments
var renderer = _renderer;
// We dispose of the synth two seconds from now to allow the last notes to stop from playing.
- Timer.Spawn(2000, () => { renderer?.Dispose(); });
+ Owner.SpawnTimer(2000, () => { renderer?.Dispose(); });
_renderer = null;
_midiEventBuffer.Clear();
@@ -195,6 +230,8 @@ namespace Content.Client.GameObjects.Components.Instruments
serializer.DataField(this, x => Handheld, "handheld", false);
serializer.DataField(ref _instrumentProgram, "program", (byte) 1);
serializer.DataField(ref _instrumentBank, "bank", (byte) 0);
+ serializer.DataField(ref _allowPercussion, "allowPercussion", false);
+ serializer.DataField(ref _allowProgramChange, "allowProgramChange", false);
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null)
@@ -279,6 +316,11 @@ namespace Content.Client.GameObjects.Components.Instruments
{
EndRenderer(true);
}
+
+ AllowPercussion = state.AllowPercussion;
+ AllowProgramChange = state.AllowProgramChange;
+ InstrumentBank = state.InstrumentBank;
+ InstrumentProgram = state.InstrumentProgram;
}
///
diff --git a/Content.Client/GameObjects/Components/Items/HandsComponent.cs b/Content.Client/GameObjects/Components/Items/HandsComponent.cs
index 2750547ad3..c967a7e4d7 100644
--- a/Content.Client/GameObjects/Components/Items/HandsComponent.cs
+++ b/Content.Client/GameObjects/Components/Items/HandsComponent.cs
@@ -21,7 +21,6 @@ namespace Content.Client.GameObjects.Components.Items
private HandsGui? _gui;
- ///
private readonly List _hands = new List();
[ViewVariables] public IReadOnlyList Hands => _hands;
@@ -90,7 +89,7 @@ namespace Content.Client.GameObjects.Components.Items
{
if (!TryHand(sharedHand.Name, out var hand))
{
- hand = new Hand(sharedHand, Owner.EntityManager);
+ hand = new Hand(this, sharedHand, Owner.EntityManager);
AddHand(hand);
}
else
@@ -102,6 +101,8 @@ namespace Content.Client.GameObjects.Components.Items
: null;
}
+ hand.Enabled = sharedHand.Enabled;
+
UpdateHandSprites(hand);
}
@@ -197,10 +198,35 @@ namespace Content.Client.GameObjects.Components.Items
_gameHud.HandsContainer.AddChild(_gui);
_gui.UpdateHandIcons();
break;
-
case PlayerDetachedMsg _:
_gui?.Parent?.RemoveChild(_gui);
break;
+ case HandEnabledMsg msg:
+ {
+ var hand = GetHand(msg.Name);
+
+ if (hand?.Button == null)
+ {
+ break;
+ }
+
+ hand.Button.Blocked.Visible = false;
+
+ break;
+ }
+ case HandDisabledMsg msg:
+ {
+ var hand = GetHand(msg.Name);
+
+ if (hand?.Button == null)
+ {
+ break;
+ }
+
+ hand.Button.Blocked.Visible = true;
+
+ break;
+ }
}
}
@@ -235,9 +261,11 @@ namespace Content.Client.GameObjects.Components.Items
public class Hand
{
- // TODO: Separate into server hand and client hand
- public Hand(SharedHand hand, IEntityManager manager, HandButton? button = null)
+ private bool _enabled = true;
+
+ public Hand(HandsComponent parent, SharedHand hand, IEntityManager manager, HandButton? button = null)
{
+ Parent = parent;
Index = hand.Index;
Name = hand.Name;
Location = hand.Location;
@@ -252,10 +280,33 @@ namespace Content.Client.GameObjects.Components.Items
Entity = entity;
}
+ private HandsComponent Parent { get; }
public int Index { get; }
public string Name { get; }
public HandLocation Location { get; set; }
public IEntity? Entity { get; set; }
public HandButton? Button { get; set; }
+
+ public bool Enabled
+ {
+ get => _enabled;
+ set
+ {
+ if (_enabled == value)
+ {
+ return;
+ }
+
+ _enabled = value;
+ Parent.Dirty();
+
+ var message = value
+ ? (ComponentMessage) new HandEnabledMsg(Name)
+ : new HandDisabledMsg(Name);
+
+ Parent.HandleMessage(message, Parent);
+ Parent.Owner.SendMessage(Parent, message);
+ }
+ }
}
}
diff --git a/Content.Client/GameObjects/Components/Items/ItemComponent.cs b/Content.Client/GameObjects/Components/Items/ItemComponent.cs
index 5da0dfaec5..4b1b2b46be 100644
--- a/Content.Client/GameObjects/Components/Items/ItemComponent.cs
+++ b/Content.Client/GameObjects/Components/Items/ItemComponent.cs
@@ -21,6 +21,8 @@ namespace Content.Client.GameObjects.Components.Items
[ComponentReference(typeof(IItemComponent))]
public class ItemComponent : Component, IItemComponent, IDraggable
{
+ [Dependency] private IResourceCache _resourceCache = default!;
+
public override string Name => "Item";
public override uint? NetID => ContentNetIDs.ITEM;
@@ -72,8 +74,7 @@ namespace Content.Client.GameObjects.Components.Items
protected RSI GetRSI()
{
- var resourceCache = IoCManager.Resolve();
- return resourceCache.GetResource(SharedSpriteComponent.TextureRoot / RsiPath).RSI;
+ return _resourceCache.GetResource(SharedSpriteComponent.TextureRoot / RsiPath).RSI;
}
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
diff --git a/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs b/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs
index 8503644d77..bd292db23f 100644
--- a/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs
+++ b/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs
@@ -35,6 +35,8 @@ namespace Content.Client.GameObjects.Components.Mobs
[ViewVariables]
private Dictionary _cooldown = new Dictionary();
+ public override IReadOnlyDictionary Statuses => _status;
+
///
/// Allows calculating if we need to act due to this component being controlled by the current mob
///
diff --git a/Content.Client/GameObjects/Components/Morgue/BodyBagVisualizer.cs b/Content.Client/GameObjects/Components/Morgue/BodyBagVisualizer.cs
new file mode 100644
index 0000000000..a8aa86b777
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Morgue/BodyBagVisualizer.cs
@@ -0,0 +1,30 @@
+#nullable enable
+using Content.Shared.GameObjects.Components.Morgue;
+using Robust.Client.GameObjects;
+using Robust.Client.Interfaces.GameObjects.Components;
+
+namespace Content.Client.GameObjects.Components.Morgue
+{
+ public sealed class BodyBagVisualizer : AppearanceVisualizer
+ {
+ public override void OnChangeData(AppearanceComponent component)
+ {
+ base.OnChangeData(component);
+
+ if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite))
+ {
+ return;
+ }
+
+ if (component.TryGetData(BodyBagVisuals.Label, out bool labelVal))
+ {
+ sprite.LayerSetVisible(BodyBagVisualLayers.Label, labelVal);
+ }
+ }
+ }
+
+ public enum BodyBagVisualLayers
+ {
+ Label,
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Morgue/CrematoriumVisualizer.cs b/Content.Client/GameObjects/Components/Morgue/CrematoriumVisualizer.cs
new file mode 100644
index 0000000000..b7b5b9d8c8
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Morgue/CrematoriumVisualizer.cs
@@ -0,0 +1,75 @@
+#nullable enable
+using Content.Shared.GameObjects.Components.Morgue;
+using Robust.Client.GameObjects;
+using Robust.Client.Interfaces.GameObjects.Components;
+using Robust.Shared.Utility;
+using YamlDotNet.RepresentationModel;
+
+namespace Content.Client.GameObjects.Components.Storage
+{
+ public sealed class CrematoriumVisualizer : AppearanceVisualizer
+ {
+ private string _stateOpen = "";
+ private string _stateClosed = "";
+
+ private string _lightContents = "";
+ private string _lightBurning = "";
+
+ public override void LoadData(YamlMappingNode node)
+ {
+ base.LoadData(node);
+
+ if (node.TryGetNode("state_open", out var child))
+ {
+ _stateOpen = child.AsString();
+ }
+ if (node.TryGetNode("state_closed", out child))
+ {
+ _stateClosed = child.AsString();
+ }
+
+ if (node.TryGetNode("light_contents", out child))
+ {
+ _lightContents = child.AsString();
+ }
+ if (node.TryGetNode("light_burning", out child))
+ {
+ _lightBurning = child.AsString();
+ }
+ }
+
+ public override void OnChangeData(AppearanceComponent component)
+ {
+ base.OnChangeData(component);
+
+ if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite)) return;
+
+ sprite.LayerSetState(
+ CrematoriumVisualLayers.Base,
+ component.GetData(MorgueVisuals.Open)
+ ? _stateOpen
+ : _stateClosed
+ );
+
+ var lightState = "";
+ if (component.TryGetData(MorgueVisuals.HasContents, out bool hasContents) && hasContents) lightState = _lightContents;
+ if (component.TryGetData(CrematoriumVisuals.Burning, out bool isBurning) && isBurning) lightState = _lightBurning;
+
+ if (!string.IsNullOrEmpty(lightState))
+ {
+ sprite.LayerSetState(CrematoriumVisualLayers.Light, lightState);
+ sprite.LayerSetVisible(CrematoriumVisualLayers.Light, true);
+ }
+ else
+ {
+ sprite.LayerSetVisible(CrematoriumVisualLayers.Light, false);
+ }
+ }
+ }
+
+ public enum CrematoriumVisualLayers
+ {
+ Base,
+ Light,
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Morgue/MorgueVisualizer.cs b/Content.Client/GameObjects/Components/Morgue/MorgueVisualizer.cs
new file mode 100644
index 0000000000..9f0a10305e
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Morgue/MorgueVisualizer.cs
@@ -0,0 +1,81 @@
+#nullable enable
+using Content.Shared.GameObjects.Components.Morgue;
+using Robust.Client.GameObjects;
+using Robust.Client.Interfaces.GameObjects.Components;
+using Robust.Shared.Utility;
+using YamlDotNet.RepresentationModel;
+
+namespace Content.Client.GameObjects.Components.Storage
+{
+ public sealed class MorgueVisualizer : AppearanceVisualizer
+ {
+ private string _stateOpen = "";
+ private string _stateClosed = "";
+
+ private string _lightContents = "";
+ private string _lightMob = "";
+ private string _lightSoul = "";
+
+ public override void LoadData(YamlMappingNode node)
+ {
+ base.LoadData(node);
+
+ if (node.TryGetNode("state_open", out var child))
+ {
+ _stateOpen = child.AsString();
+ }
+ if (node.TryGetNode("state_closed", out child))
+ {
+ _stateClosed = child.AsString();
+ }
+
+ if (node.TryGetNode("light_contents", out child))
+ {
+ _lightContents = child.AsString();
+ }
+ if (node.TryGetNode("light_mob", out child))
+ {
+ _lightMob = child.AsString();
+ }
+ if (node.TryGetNode("light_soul", out child))
+ {
+ _lightSoul = child.AsString();
+ }
+ }
+
+ public override void OnChangeData(AppearanceComponent component)
+ {
+ base.OnChangeData(component);
+
+ if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite)) return;
+
+ sprite.LayerSetState(
+ MorgueVisualLayers.Base,
+ component.GetData(MorgueVisuals.Open)
+ ? _stateOpen
+ : _stateClosed
+ );
+
+ var lightState = "";
+ if (component.TryGetData(MorgueVisuals.HasContents, out bool hasContents) && hasContents) lightState = _lightContents;
+ if (component.TryGetData(MorgueVisuals.HasMob, out bool hasMob) && hasMob) lightState = _lightMob;
+ if (component.TryGetData(MorgueVisuals.HasSoul, out bool hasSoul) && hasSoul) lightState = _lightSoul;
+
+ if (!string.IsNullOrEmpty(lightState))
+ {
+ sprite.LayerSetState(MorgueVisualLayers.Light, lightState);
+ sprite.LayerSetVisible(MorgueVisualLayers.Light, true);
+ }
+ else
+ {
+ sprite.LayerSetVisible(MorgueVisualLayers.Light, false);
+ }
+ }
+ }
+
+ public enum MorgueVisualLayers
+ {
+ Base,
+ Light,
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Nutrition/DrinkFoodVisualizer.cs b/Content.Client/GameObjects/Components/Nutrition/DrinkFoodVisualizer.cs
index caef972db0..214c8d9cef 100644
--- a/Content.Client/GameObjects/Components/Nutrition/DrinkFoodVisualizer.cs
+++ b/Content.Client/GameObjects/Components/Nutrition/DrinkFoodVisualizer.cs
@@ -22,7 +22,11 @@ namespace Content.Client.GameObjects.Components.Nutrition
public override void OnChangeData(AppearanceComponent component)
{
- var sprite = component.Owner.GetComponent();
+ if(!component.Owner.TryGetComponent(out var sprite))
+ {
+ return;
+ };
+
if (!component.TryGetData(SharedFoodComponent.FoodVisuals.MaxUses, out var maxUses))
{
return;
diff --git a/Content.Client/GameObjects/Components/ParticleAcceleratorPartVisualizer.cs b/Content.Client/GameObjects/Components/ParticleAcceleratorPartVisualizer.cs
new file mode 100644
index 0000000000..2e992a516f
--- /dev/null
+++ b/Content.Client/GameObjects/Components/ParticleAcceleratorPartVisualizer.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Content.Shared.GameObjects.Components;
+using Content.Shared.GameObjects.Components.Singularity;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Client.Interfaces.GameObjects.Components;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.Interfaces.Reflection;
+using Robust.Shared.Interfaces.Serialization;
+using Robust.Shared.IoC;
+using Robust.Shared.Log;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Utility;
+using YamlDotNet.RepresentationModel;
+
+namespace Content.Client.GameObjects.Components
+{
+ public class ParticleAcceleratorPartVisualizer : AppearanceVisualizer
+ {
+ private Dictionary _states = new Dictionary();
+
+ public override void LoadData(YamlMappingNode node)
+ {
+ base.LoadData(node);
+
+ var serializer = YamlObjectSerializer.NewReader(node);
+ if (!serializer.TryReadDataField("baseState", out var baseState))
+ {
+ throw new PrototypeLoadException("No baseState property specified for ParticleAcceleratorPartVisualizer");
+ }
+
+ _states.Add(ParticleAcceleratorVisualState.Powered, baseState+"p");
+ _states.Add(ParticleAcceleratorVisualState.Level0, baseState+"p0");
+ _states.Add(ParticleAcceleratorVisualState.Level1, baseState+"p1");
+ _states.Add(ParticleAcceleratorVisualState.Level2, baseState+"p2");
+ _states.Add(ParticleAcceleratorVisualState.Level3, baseState+"p3");
+ }
+
+ public override void InitializeEntity(IEntity entity)
+ {
+ base.InitializeEntity(entity);
+ if (!entity.TryGetComponent(out var sprite))
+ {
+ throw new EntityCreationException("No sprite component found in entity that has ParticleAcceleratorPartVisualizer");
+ }
+
+ if (!sprite.AllLayers.Any())
+ {
+ throw new EntityCreationException("No Layer set for entity that has ParticleAcceleratorPartVisualizer");
+ }
+ }
+
+ public override void OnChangeData(AppearanceComponent component)
+ {
+ if (component.Owner.Deleted)
+ return;
+
+ if (!component.Owner.TryGetComponent(out var sprite)) return;
+ if (!component.TryGetData(ParticleAcceleratorVisuals.VisualState, out ParticleAcceleratorVisualState state))
+ {
+ state = ParticleAcceleratorVisualState.Unpowered;
+ }
+
+ if (state != ParticleAcceleratorVisualState.Unpowered)
+ {
+ sprite.LayerSetVisible(ParticleAcceleratorVisualLayers.Unlit, true);
+ sprite.LayerSetState(ParticleAcceleratorVisualLayers.Unlit, _states[state]);
+ }
+ else
+ {
+ sprite.LayerSetVisible(ParticleAcceleratorVisualLayers.Unlit, false);
+ }
+ }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Power/PowerCellVisualizer.cs b/Content.Client/GameObjects/Components/Power/PowerCellVisualizer.cs
index 71b4d81ff7..408c09d7d6 100644
--- a/Content.Client/GameObjects/Components/Power/PowerCellVisualizer.cs
+++ b/Content.Client/GameObjects/Components/Power/PowerCellVisualizer.cs
@@ -36,7 +36,8 @@ namespace Content.Client.GameObjects.Components.Power
var sprite = component.Owner.GetComponent();
if (component.TryGetData(PowerCellVisuals.ChargeLevel, out float fraction))
{
- sprite.LayerSetState(Layers.Charge, $"{_prefix}_{ContentHelpers.RoundToLevels(fraction, 1, 5) * 25}");
+ int level = ContentHelpers.RoundToNearestLevels(fraction, 1, 4) * 25;
+ sprite.LayerSetState(Layers.Charge, $"{_prefix}_{level}");
}
}
diff --git a/Content.Client/GameObjects/Components/RadiationCollectorVisualizer.cs b/Content.Client/GameObjects/Components/RadiationCollectorVisualizer.cs
new file mode 100644
index 0000000000..07f4738f47
--- /dev/null
+++ b/Content.Client/GameObjects/Components/RadiationCollectorVisualizer.cs
@@ -0,0 +1,104 @@
+using System;
+using Content.Client.GameObjects.Components.Doors;
+using Content.Client.GameObjects.Components.Wires;
+using Content.Shared.GameObjects.Components.Doors;
+using Content.Shared.GameObjects.Components.Singularity;
+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;
+
+namespace Content.Client.GameObjects.Components
+{
+ public class RadiationCollectorVisualizer : AppearanceVisualizer
+ {
+ private const string AnimationKey = "radiationcollector_animation";
+
+ private Animation ActivateAnimation;
+ private Animation DeactiveAnimation;
+
+ public override void LoadData(YamlMappingNode node)
+ {
+ ActivateAnimation = new Animation {Length = TimeSpan.FromSeconds(0.8f)};
+ {
+ var flick = new AnimationTrackSpriteFlick();
+ ActivateAnimation.AnimationTracks.Add(flick);
+ flick.LayerKey = RadiationCollectorVisualLayers.Main;
+ flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("ca_active", 0f));
+
+ /*var sound = new AnimationTrackPlaySound();
+ CloseAnimation.AnimationTracks.Add(sound);
+ sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(closeSound, 0));*/
+ }
+
+ DeactiveAnimation = new Animation {Length = TimeSpan.FromSeconds(0.8f)};
+ {
+ var flick = new AnimationTrackSpriteFlick();
+ DeactiveAnimation.AnimationTracks.Add(flick);
+ flick.LayerKey = RadiationCollectorVisualLayers.Main;
+ flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("ca_deactive", 0f));
+
+ /*var sound = new AnimationTrackPlaySound();
+ CloseAnimation.AnimationTracks.Add(sound);
+ sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(closeSound, 0));*/
+ }
+ }
+
+ public override void InitializeEntity(IEntity entity)
+ {
+ if (!entity.HasComponent())
+ {
+ entity.AddComponent();
+ }
+ }
+
+
+ public override void OnChangeData(AppearanceComponent component)
+ {
+ if (component.Owner.Deleted)
+ return;
+
+ if (!component.Owner.TryGetComponent(out var sprite)) return;
+ if (!component.Owner.TryGetComponent(out var animPlayer)) return;
+ if (!component.TryGetData(RadiationCollectorVisuals.VisualState, out RadiationCollectorVisualState state))
+ {
+ state = RadiationCollectorVisualState.Deactive;
+ }
+
+ switch (state)
+ {
+ case RadiationCollectorVisualState.Active:
+ sprite.LayerSetState(RadiationCollectorVisualLayers.Main, "ca_on");
+ break;
+ case RadiationCollectorVisualState.Activating:
+ if (!animPlayer.HasRunningAnimation(AnimationKey))
+ {
+ animPlayer.Play(ActivateAnimation, AnimationKey);
+ animPlayer.AnimationCompleted += _ =>
+ component.SetData(RadiationCollectorVisuals.VisualState,
+ RadiationCollectorVisualState.Active);
+ }
+ break;
+ case RadiationCollectorVisualState.Deactivating:
+ if (!animPlayer.HasRunningAnimation(AnimationKey))
+ {
+ animPlayer.Play(DeactiveAnimation, AnimationKey);
+ animPlayer.AnimationCompleted += _ =>
+ component.SetData(RadiationCollectorVisuals.VisualState,
+ RadiationCollectorVisualState.Deactive);
+ }
+ break;
+ case RadiationCollectorVisualState.Deactive:
+ sprite.LayerSetState(RadiationCollectorVisualLayers.Main, "ca_off");
+ break;
+ }
+ }
+
+ }
+ public enum RadiationCollectorVisualLayers
+ {
+ Main
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Singularity/ContainmentFieldComponent.cs b/Content.Client/GameObjects/Components/Singularity/ContainmentFieldComponent.cs
new file mode 100644
index 0000000000..692f0b4b89
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Singularity/ContainmentFieldComponent.cs
@@ -0,0 +1,28 @@
+using Robust.Client.GameObjects;
+using Robust.Client.Interfaces.GameObjects.Components;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Log;
+
+namespace Content.Client.GameObjects.Components.Singularity
+{
+ public class ContainmentFieldComponent : Component
+ {
+ public override string Name => "Containment Field";
+
+ private SpriteComponent _spriteComponent;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ if (!Owner.TryGetComponent(out _spriteComponent))
+ {
+ Logger.Error("Containmentfieldcomponent created without spritecomponent");
+ }
+ else
+ {
+ _spriteComponent.Directional = false;
+ }
+ }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Singularity/EmitterVisualizer.cs b/Content.Client/GameObjects/Components/Singularity/EmitterVisualizer.cs
new file mode 100644
index 0000000000..44b4d356cc
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Singularity/EmitterVisualizer.cs
@@ -0,0 +1,49 @@
+using System;
+using Content.Shared.GameObjects.Components.Singularity;
+using Robust.Client.GameObjects;
+using Robust.Client.Interfaces.GameObjects.Components;
+
+namespace Content.Client.GameObjects.Components.Singularity
+{
+ public class EmitterVisualizer : AppearanceVisualizer
+ {
+ private const string OverlayBeam = "emitter-beam";
+ private const string OverlayUnderPowered = "emitter-underpowered";
+
+ public override void OnChangeData(AppearanceComponent component)
+ {
+ base.OnChangeData(component);
+
+ if (!component.Owner.TryGetComponent(out ISpriteComponent sprite))
+ {
+ return;
+ }
+
+ if (!component.TryGetData(EmitterVisuals.Locked, out bool locked))
+ locked = false;
+
+
+ if (!component.TryGetData(EmitterVisuals.VisualState, out EmitterVisualState state))
+ state = EmitterVisualState.Off;
+
+ switch (state)
+ {
+ case EmitterVisualState.On:
+ sprite.LayerSetVisible(1, true);
+ sprite.LayerSetState(1, OverlayBeam);
+ break;
+ case EmitterVisualState.Underpowered:
+ sprite.LayerSetVisible(1, true);
+ sprite.LayerSetState(1, OverlayUnderPowered);
+ break;
+ case EmitterVisualState.Off:
+ sprite.LayerSetVisible(1, false);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ sprite.LayerSetVisible(2, locked);
+ }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Sound/LoopingSoundComponent.cs b/Content.Client/GameObjects/Components/Sound/LoopingSoundComponent.cs
index c9caf04338..b2dec0fcbc 100644
--- a/Content.Client/GameObjects/Components/Sound/LoopingSoundComponent.cs
+++ b/Content.Client/GameObjects/Components/Sound/LoopingSoundComponent.cs
@@ -1,8 +1,11 @@
+using System;
using System.Collections.Generic;
+using System.Linq;
using Content.Shared.GameObjects.Components.Sound;
using Content.Shared.Physics;
using Robust.Client.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Components.Timers;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.Interfaces.Random;
@@ -51,11 +54,19 @@ namespace Content.Client.GameObjects.Components.Sound
{
if (!schedule.Play) return;
- Timer.Spawn((int) schedule.Delay + (_random.Next((int) schedule.RandomDelay)),() =>
+ Owner.SpawnTimer((int) schedule.Delay + (_random.Next((int) schedule.RandomDelay)),() =>
{
if (!schedule.Play) return; // We make sure this hasn't changed.
if (_audioSystem == null) _audioSystem = EntitySystem.Get();
- _audioStreams.Add(schedule,_audioSystem.Play(schedule.Filename, Owner, schedule.AudioParams));
+
+ if (!_audioStreams.ContainsKey(schedule))
+ {
+ _audioStreams.Add(schedule,_audioSystem.Play(schedule.Filename, Owner, schedule.AudioParams));
+ }
+ else
+ {
+ _audioStreams[schedule] = _audioSystem.Play(schedule.Filename, Owner, schedule.AudioParams);
+ }
if (schedule.Times == 0) return;
diff --git a/Content.Client/GameObjects/Components/Storage/StorageVisualizer.cs b/Content.Client/GameObjects/Components/Storage/StorageVisualizer.cs
index 926d4e5c7a..e47c035bba 100644
--- a/Content.Client/GameObjects/Components/Storage/StorageVisualizer.cs
+++ b/Content.Client/GameObjects/Components/Storage/StorageVisualizer.cs
@@ -1,4 +1,4 @@
-using Content.Shared.GameObjects.Components.Storage;
+using Content.Shared.GameObjects.Components.Storage;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
@@ -74,9 +74,12 @@ namespace Content.Client.GameObjects.Components.Storage
}
}
- if (component.TryGetData(StorageVisuals.Welded, out bool weldedVal))
+ if (component.TryGetData(StorageVisuals.CanWeld, out bool canWeld) && canWeld)
{
- sprite.LayerSetVisible(StorageVisualLayers.Welded, weldedVal);
+ if (component.TryGetData(StorageVisuals.Welded, out bool weldedVal))
+ {
+ sprite.LayerSetVisible(StorageVisualLayers.Welded, weldedVal);
+ }
}
}
}
diff --git a/Content.Client/GameObjects/Components/Weapons/FlashableComponent.cs b/Content.Client/GameObjects/Components/Weapons/FlashableComponent.cs
index 5e8573034a..f990eb2b27 100644
--- a/Content.Client/GameObjects/Components/Weapons/FlashableComponent.cs
+++ b/Content.Client/GameObjects/Components/Weapons/FlashableComponent.cs
@@ -7,6 +7,7 @@ using Robust.Client.Interfaces.Graphics;
using Robust.Client.Interfaces.Graphics.Overlays;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Components.Timers;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -83,7 +84,7 @@ namespace Content.Client.GameObjects.Components.Weapons
}
_cancelToken = new CancellationTokenSource();
- Timer.Spawn((int) duration * 1000, DisableOverlay, _cancelToken.Token);
+ Owner.SpawnTimer((int) duration * 1000, DisableOverlay, _cancelToken.Token);
}
private void DisableOverlay()
diff --git a/Content.Client/GameObjects/Components/WindowComponent.cs b/Content.Client/GameObjects/Components/WindowComponent.cs
index 8587c2bcac..3a06cba79e 100644
--- a/Content.Client/GameObjects/Components/WindowComponent.cs
+++ b/Content.Client/GameObjects/Components/WindowComponent.cs
@@ -1,4 +1,5 @@
-using Content.Client.GameObjects.EntitySystems;
+using System.Diagnostics.CodeAnalysis;
+using Content.Client.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
@@ -34,14 +35,33 @@ namespace Content.Client.GameObjects.Components
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new WindowSmoothDirtyEvent(Owner));
var state0 = $"{_stateBase}0";
+ const string cracksRSIPath = "/Textures/Constructible/Structures/Windows/cracks.rsi";
_sprite.LayerMapSet(CornerLayers.SE, _sprite.AddLayerState(state0));
_sprite.LayerSetDirOffset(CornerLayers.SE, SpriteComponent.DirectionOffset.None);
+ _sprite.LayerMapSet(WindowDamageLayers.DamageSE, _sprite.AddLayerState("0_1", cracksRSIPath));
+ _sprite.LayerSetShader(WindowDamageLayers.DamageSE, "unshaded");
+ _sprite.LayerSetVisible(WindowDamageLayers.DamageSE, false);
+
_sprite.LayerMapSet(CornerLayers.NE, _sprite.AddLayerState(state0));
_sprite.LayerSetDirOffset(CornerLayers.NE, SpriteComponent.DirectionOffset.CounterClockwise);
+ _sprite.LayerMapSet(WindowDamageLayers.DamageNE, _sprite.AddLayerState("0_1", cracksRSIPath));
+ _sprite.LayerSetDirOffset(WindowDamageLayers.DamageNE, SpriteComponent.DirectionOffset.CounterClockwise);
+ _sprite.LayerSetShader(WindowDamageLayers.DamageNE, "unshaded");
+ _sprite.LayerSetVisible(WindowDamageLayers.DamageNE, false);
+
_sprite.LayerMapSet(CornerLayers.NW, _sprite.AddLayerState(state0));
_sprite.LayerSetDirOffset(CornerLayers.NW, SpriteComponent.DirectionOffset.Flip);
+ _sprite.LayerMapSet(WindowDamageLayers.DamageNW, _sprite.AddLayerState("0_1", cracksRSIPath));
+ _sprite.LayerSetDirOffset(WindowDamageLayers.DamageNW, SpriteComponent.DirectionOffset.Flip);
+ _sprite.LayerSetShader(WindowDamageLayers.DamageNW, "unshaded");
+ _sprite.LayerSetVisible(WindowDamageLayers.DamageNW, false);
+
_sprite.LayerMapSet(CornerLayers.SW, _sprite.AddLayerState(state0));
_sprite.LayerSetDirOffset(CornerLayers.SW, SpriteComponent.DirectionOffset.Clockwise);
+ _sprite.LayerMapSet(WindowDamageLayers.DamageSW, _sprite.AddLayerState("0_1", cracksRSIPath));
+ _sprite.LayerSetDirOffset(WindowDamageLayers.DamageSW, SpriteComponent.DirectionOffset.Clockwise);
+ _sprite.LayerSetShader(WindowDamageLayers.DamageSW, "unshaded");
+ _sprite.LayerSetVisible(WindowDamageLayers.DamageSW, false);
}
///
@@ -91,4 +111,13 @@ namespace Content.Client.GameObjects.Components
serializer.DataField(ref _stateBase, "base", null);
}
}
+
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public enum WindowDamageLayers
+ {
+ DamageSE,
+ DamageNE,
+ DamageNW,
+ DamageSW
+ }
}
diff --git a/Content.Client/GameObjects/Components/WindowVisualizer.cs b/Content.Client/GameObjects/Components/WindowVisualizer.cs
new file mode 100644
index 0000000000..e66ded87d2
--- /dev/null
+++ b/Content.Client/GameObjects/Components/WindowVisualizer.cs
@@ -0,0 +1,60 @@
+using System;
+using Content.Shared.GameObjects.Components;
+using Content.Shared.Utility;
+using JetBrains.Annotations;
+using Robust.Client.GameObjects;
+using Robust.Client.Interfaces.GameObjects.Components;
+using Robust.Shared.GameObjects.Components.Transform;
+using Robust.Shared.Interfaces.GameObjects;
+
+namespace Content.Client.GameObjects.Components
+{
+ [UsedImplicitly]
+ public sealed class WindowVisualizer : AppearanceVisualizer
+ {
+ public override void OnChangeData(AppearanceComponent component)
+ {
+ base.OnChangeData(component);
+
+ var sprite = component.Owner.GetComponent();
+ var snapGrid = component.Owner.GetComponent();
+ var lowWall = FindLowWall(snapGrid);
+ if (lowWall == null) return;
+
+ if (component.TryGetData(WindowVisuals.Damage, out float fraction))
+ {
+ var level = Math.Min(ContentHelpers.RoundToLevels(fraction, 1, 7), 5);
+ if (level == 0)
+ {
+ foreach (WindowDamageLayers val in Enum.GetValues(typeof(WindowDamageLayers)))
+ {
+ sprite.LayerSetVisible(val, false);
+ }
+ return;
+ }
+ foreach (WindowDamageLayers val in Enum.GetValues(typeof(WindowDamageLayers)))
+ {
+ sprite.LayerSetVisible(val, true);
+ }
+
+ sprite.LayerSetState(WindowDamageLayers.DamageNE, $"{(int) lowWall.LastCornerNE}_{level}");
+ sprite.LayerSetState(WindowDamageLayers.DamageSE, $"{(int) lowWall.LastCornerSE}_{level}");
+ sprite.LayerSetState(WindowDamageLayers.DamageSW, $"{(int) lowWall.LastCornerSW}_{level}");
+ sprite.LayerSetState(WindowDamageLayers.DamageNW, $"{(int) lowWall.LastCornerNW}_{level}");
+
+ }
+ }
+
+ private static LowWallComponent FindLowWall(SnapGridComponent snapGrid)
+ {
+ foreach (var entity in snapGrid.GetLocal())
+ {
+ if (entity.TryGetComponent(out LowWallComponent lowWall))
+ {
+ return lowWall;
+ }
+ }
+ return null;
+ }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Wires/WiresMenu.cs b/Content.Client/GameObjects/Components/Wires/WiresMenu.cs
index a3fcda4861..7469f31a8d 100644
--- a/Content.Client/GameObjects/Components/Wires/WiresMenu.cs
+++ b/Content.Client/GameObjects/Components/Wires/WiresMenu.cs
@@ -21,6 +21,8 @@ namespace Content.Client.GameObjects.Components.Wires
{
public class WiresMenu : BaseWindow
{
+ [Dependency] private IResourceCache _resourceCache = default!;
+
public WiresBoundUserInterface Owner { get; }
private readonly Control _wiresHBox;
@@ -34,7 +36,7 @@ namespace Content.Client.GameObjects.Components.Wires
public WiresMenu(WiresBoundUserInterface owner)
{
- var resourceCache = IoCManager.Resolve();
+ IoCManager.InjectDependencies(this);
Owner = owner;
var rootContainer = new LayoutContainer {Name = "WireRoot"};
@@ -42,7 +44,7 @@ namespace Content.Client.GameObjects.Components.Wires
MouseFilter = MouseFilterMode.Stop;
- var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
+ var panelTex = _resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
var back = new StyleBoxTexture
{
Texture = panelTex,
@@ -135,8 +137,8 @@ namespace Content.Client.GameObjects.Components.Wires
LayoutContainer.SetAnchorPreset(topContainerWrap, LayoutContainer.LayoutPreset.Wide);
- var font = resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13);
- var fontSmall = resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 10);
+ var font = _resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13);
+ var fontSmall = _resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 10);
Button helpButton;
var topRow = new MarginContainer
@@ -255,7 +257,7 @@ namespace Content.Client.GameObjects.Components.Wires
var mirror = random.Next(2) == 0;
var flip = random.Next(2) == 0;
var type = random.Next(2);
- var control = new WireControl(wire.Color, wire.Letter, wire.IsCut, flip, mirror, type)
+ var control = new WireControl(wire.Color, wire.Letter, wire.IsCut, flip, mirror, type, _resourceCache)
{
SizeFlagsVertical = SizeFlags.ShrinkEnd
};
@@ -278,7 +280,7 @@ namespace Content.Client.GameObjects.Components.Wires
{
if (status.Value is StatusLightData statusLightData)
{
- _statusContainer.AddChild(new StatusLight(statusLightData));
+ _statusContainer.AddChild(new StatusLight(statusLightData, _resourceCache));
}
else
{
@@ -305,18 +307,20 @@ namespace Content.Client.GameObjects.Components.Wires
private sealed class WireControl : Control
{
+ private IResourceCache _resourceCache;
+
private const string TextureContact = "/Textures/Interface/WireHacking/contact.svg.96dpi.png";
public event Action WireClicked;
public event Action ContactsClicked;
- public WireControl(WireColor color, WireLetter letter, bool isCut, bool flip, bool mirror, int type)
+ public WireControl(WireColor color, WireLetter letter, bool isCut, bool flip, bool mirror, int type, IResourceCache resourceCache)
{
+ _resourceCache = resourceCache;
+
SizeFlagsHorizontal = SizeFlags.ShrinkCenter;
MouseFilter = MouseFilterMode.Stop;
- var resourceCache = IoCManager.Resolve();
-
var layout = new LayoutContainer();
AddChild(layout);
@@ -326,7 +330,7 @@ namespace Content.Client.GameObjects.Components.Wires
SizeFlagsVertical = SizeFlags.ShrinkEnd,
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
Align = Label.AlignMode.Center,
- FontOverride = resourceCache.GetFont("/Fonts/NotoSansDisplay/NotoSansDisplay-Bold.ttf", 12),
+ FontOverride = _resourceCache.GetFont("/Fonts/NotoSansDisplay/NotoSansDisplay-Bold.ttf", 12),
FontColorOverride = Color.Gray,
ToolTip = letter.Name(),
MouseFilter = MouseFilterMode.Stop
@@ -337,7 +341,7 @@ namespace Content.Client.GameObjects.Components.Wires
LayoutContainer.SetGrowVertical(greek, LayoutContainer.GrowDirection.Begin);
LayoutContainer.SetGrowHorizontal(greek, LayoutContainer.GrowDirection.Both);
- var contactTexture = resourceCache.GetTexture(TextureContact);
+ var contactTexture = _resourceCache.GetTexture(TextureContact);
var contact1 = new TextureRect
{
Texture = contactTexture,
@@ -356,7 +360,7 @@ namespace Content.Client.GameObjects.Components.Wires
layout.AddChild(contact2);
LayoutContainer.SetPosition(contact2, (0, 60));
- var wire = new WireRender(color, isCut, flip, mirror, type);
+ var wire = new WireRender(color, isCut, flip, mirror, type, _resourceCache);
layout.AddChild(wire);
LayoutContainer.SetPosition(wire, (2, 16));
@@ -420,8 +424,11 @@ namespace Content.Client.GameObjects.Components.Wires
"/Textures/Interface/WireHacking/wire_2_copper.svg.96dpi.png"
};
- public WireRender(WireColor color, bool isCut, bool flip, bool mirror, int type)
+ private IResourceCache _resourceCache;
+
+ public WireRender(WireColor color, bool isCut, bool flip, bool mirror, int type, IResourceCache resourceCache)
{
+ _resourceCache = resourceCache;
_color = color;
_isCut = isCut;
_flip = flip;
@@ -436,10 +443,8 @@ namespace Content.Client.GameObjects.Components.Wires
protected override void Draw(DrawingHandleScreen handle)
{
- var resC = IoCManager.Resolve();
-
var colorValue = _color.ColorValue();
- var tex = resC.GetTexture(_isCut ? TextureCut[_type] : TextureNormal[_type]);
+ var tex = _resourceCache.GetTexture(_isCut ? TextureCut[_type] : TextureNormal[_type]);
var l = 0f;
var r = tex.Width + l;
@@ -465,7 +470,7 @@ namespace Content.Client.GameObjects.Components.Wires
if (_isCut)
{
var copper = Color.Orange;
- var copperTex = resC.GetTexture(TextureCopper[_type]);
+ var copperTex = _resourceCache.GetTexture(TextureCopper[_type]);
handle.DrawTextureRect(copperTex, rect, copper);
}
@@ -516,9 +521,8 @@ namespace Content.Client.GameObjects.Components.Wires
}
};
- public StatusLight(StatusLightData data)
+ public StatusLight(StatusLightData data, IResourceCache resourceCache)
{
- var resC = IoCManager.Resolve();
var hsv = Color.ToHsv(data.Color);
hsv.Z /= 2;
var dimColor = Color.FromHsv(hsv);
@@ -530,7 +534,7 @@ namespace Content.Client.GameObjects.Components.Wires
{
new TextureRect
{
- Texture = resC.GetTexture(
+ Texture = resourceCache.GetTexture(
"/Textures/Interface/WireHacking/light_off_base.svg.96dpi.png"),
Stretch = TextureRect.StretchMode.KeepCentered,
ModulateSelfOverride = dimColor
@@ -540,7 +544,7 @@ namespace Content.Client.GameObjects.Components.Wires
ModulateSelfOverride = data.Color.WithAlpha(0.4f),
Stretch = TextureRect.StretchMode.KeepCentered,
Texture =
- resC.GetTexture("/Textures/Interface/WireHacking/light_on_base.svg.96dpi.png"),
+ resourceCache.GetTexture("/Textures/Interface/WireHacking/light_on_base.svg.96dpi.png"),
})
}
};
@@ -577,7 +581,7 @@ namespace Content.Client.GameObjects.Components.Wires
};
}
- var font = IoCManager.Resolve().GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 12);
+ var font = resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 12);
var hBox = new HBoxContainer {SeparationOverride = 4};
hBox.AddChild(new Label
diff --git a/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs b/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs
index 42583cb010..7dfb8225d0 100644
--- a/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs
+++ b/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs
@@ -1,4 +1,4 @@
-#nullable enable
+#nullable enable
using System.Collections.Generic;
using System.Linq;
using Content.Client.GameObjects.Components;
@@ -124,9 +124,7 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter
if (doAfter.BreakOnTargetMove)
{
- var targetEntity = _entityManager.GetEntity(doAfter.TargetUid);
-
- if (targetEntity.Transform.Coordinates != doAfter.TargetGrid)
+ if (_entityManager.TryGetEntity(doAfter.TargetUid, out var targetEntity) && targetEntity.Transform.Coordinates != doAfter.TargetGrid)
{
comp.Cancel(id, currentTime);
continue;
diff --git a/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs b/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs
index d2280535e7..806e122204 100644
--- a/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs
+++ b/Content.Client/GameObjects/EntitySystems/MeleeWeaponSystem.cs
@@ -6,6 +6,7 @@ using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Components.Timers;
using Robust.Shared.GameObjects.EntitySystemMessages;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.Timing;
@@ -95,7 +96,7 @@ namespace Content.Client.GameObjects.EntitySystems
var newColor = Color.Red * originalColor;
sprite.Color = newColor;
- Timer.Spawn(100, () =>
+ hitEntity.SpawnTimer(100, () =>
{
// Only reset back to the original color if something else didn't change the color in the mean time.
if (sprite.Color == newColor)
diff --git a/Content.Client/GameObjects/EntitySystems/StandingStateSystem.cs b/Content.Client/GameObjects/EntitySystems/StandingStateSystem.cs
index 212d386c01..b5659fe7b1 100644
--- a/Content.Client/GameObjects/EntitySystems/StandingStateSystem.cs
+++ b/Content.Client/GameObjects/EntitySystems/StandingStateSystem.cs
@@ -24,12 +24,6 @@ namespace Content.Client.GameObjects.EntitySystems
appearance.SetData(RotationVisuals.RotationState, newState);
}
- if (playSound)
- {
- var file = AudioHelpers.GetRandomFileFromSoundCollection("bodyfall");
- Get().Play(file, entity, AudioHelpers.WithVariation(0.25f));
- }
-
return true;
}
diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs
index ffaa51467d..c906233b6c 100644
--- a/Content.Client/IgnoredComponents.cs
+++ b/Content.Client/IgnoredComponents.cs
@@ -33,7 +33,6 @@
"Door",
"PoweredLight",
"Smes",
- "Powercell",
"LightBulb",
"Healing",
"Catwalk",
@@ -42,6 +41,7 @@
"HitscanWeaponCapacitor",
"PowerCell",
"PowerCellCharger",
+ "PowerCellSlot",
"WeaponCapacitorCharger",
"AiController",
"Computer",
@@ -182,6 +182,7 @@
"GasCanisterPort",
"Lung",
"Cleanable",
+ "Configuration",
"Brain",
"PlantHolder",
"SeedExtractor",
@@ -190,7 +191,27 @@
"Hoe",
"Seed",
"BotanySharp",
- "PlantSampleTaker"
+ "PlantSampleTaker",
+ "Internals",
+ "GasTank",
+ "BreathMask",
+ "RadiationCollector",
+ "ContainmentFieldGenerator",
+ "ContainmentField",
+ "Emitter",
+ "Singularity",
+ "SingularityGenerator",
+ "EmitterBoltComponent",
+ "ParticleProjectile",
+ "ParticleAcceleratorControlBox",
+ "ParticleAcceleratorEmitter",
+ "ParticleAcceleratorEndCap",
+ "ParticleAcceleratorFuelChamber",
+ "ParticleAcceleratorPowerBox",
+ "BodyBagEntityStorage",
+ "MorgueEntityStorage",
+ "MorgueTray",
+ "CrematoriumEntityStorage",
};
}
}
diff --git a/Content.Client/Instruments/InstrumentMenu.cs b/Content.Client/Instruments/InstrumentMenu.cs
index 76ea0a8c28..c980ffbba7 100644
--- a/Content.Client/Instruments/InstrumentMenu.cs
+++ b/Content.Client/Instruments/InstrumentMenu.cs
@@ -1,6 +1,7 @@
using Content.Client.GameObjects.Components.Instruments;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
+using Content.Shared.GameObjects.EntitySystems;
using Robust.Client.Audio.Midi;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.UserInterface;
@@ -176,34 +177,20 @@ namespace Content.Client.Instruments
var filters = new FileDialogFilters(new FileDialogFilters.Group("mid", "midi"));
var filename = await _fileDialogManager.OpenFile(filters);
- var instrumentEnt = _owner.Instrument.Owner;
- var instrument = _owner.Instrument;
-
- ContainerHelpers.TryGetContainerMan(_owner.Instrument.Owner, out var conMan);
-
- var localPlayer = IoCManager.Resolve().LocalPlayer;
-
// The following checks are only in place to prevent players from playing MIDI songs locally.
// There are equivalents for these checks on the server.
if (string.IsNullOrEmpty(filename)) return;
- // If we don't have a player or controlled entity, we return.
- if(localPlayer?.ControlledEntity == null) return;
-
- // If the instrument is handheld and we're not holding it, we return.
- if((instrument.Handheld && (conMan == null
- || conMan.Owner != localPlayer.ControlledEntity))) return;
-
- // We check that we're in range unobstructed just in case.
- if (!localPlayer.InRangeUnobstructed(instrumentEnt)) return;
-
if (!_midiManager.IsMidiFile(filename))
{
Logger.Warning($"Not a midi file! Chosen file: {filename}");
return;
}
+ if (!PlayCheck())
+ return;
+
MidiStopButtonOnPressed(null);
await Timer.Delay(100);
if (!_owner.Instrument.OpenMidi(filename)) return;
@@ -216,6 +203,9 @@ namespace Content.Client.Instruments
{
if (obj.Pressed)
{
+ if (!PlayCheck())
+ return;
+
MidiStopButtonOnPressed(null);
_owner.Instrument.OpenInput();
}
@@ -223,6 +213,26 @@ namespace Content.Client.Instruments
_owner.Instrument.CloseInput();
}
+ private bool PlayCheck()
+ {
+ var instrumentEnt = _owner.Instrument.Owner;
+ var instrument = _owner.Instrument;
+
+ ContainerHelpers.TryGetContainerMan(_owner.Instrument.Owner, out var conMan);
+
+ var localPlayer = IoCManager.Resolve().LocalPlayer;
+
+ // If we don't have a player or controlled entity, we return.
+ if(localPlayer?.ControlledEntity == null) return false;
+
+ // If the instrument is handheld and we're not holding it, we return.
+ if((instrument.Handheld && (conMan == null
+ || conMan.Owner != localPlayer.ControlledEntity))) return false;
+
+ // We check that we're in range unobstructed just in case.
+ return localPlayer.InRangeUnobstructed(instrumentEnt, predicate:(e) => e == instrumentEnt || e == localPlayer.ControlledEntity);
+ }
+
private void MidiStopButtonOnPressed(BaseButton.ButtonEventArgs obj)
{
MidiPlaybackSetButtonsDisabled(true);
diff --git a/Content.Client/UserInterface/AdminMenu/AdminMenuWindow.cs b/Content.Client/UserInterface/AdminMenu/AdminMenuWindow.cs
index 0025f0df6b..f45ac25c88 100644
--- a/Content.Client/UserInterface/AdminMenu/AdminMenuWindow.cs
+++ b/Content.Client/UserInterface/AdminMenu/AdminMenuWindow.cs
@@ -37,7 +37,7 @@ namespace Content.Client.UserInterface.AdminMenu
{
new KickCommandButton(),
new DirectCommandButton("Admin Ghost", "aghost"),
- //TODO: teleport
+ new TeleportCommandButton(),
};
private readonly List _adminbusButtons = new List
{
@@ -534,6 +534,30 @@ namespace Content.Client.UserInterface.AdminMenu
}
}
+ private class TeleportCommandButton : UICommandButton
+ {
+ public override string Name => "Teleport";
+ public override string RequiredCommand => "tpto";
+
+ private readonly CommandUIDropDown _playerDropDown = new CommandUIDropDown
+ {
+ Name = "Player",
+ GetData = () => IoCManager.Resolve().Sessions.ToList