diff --git a/Content.Client/Chat/ChatManager.cs b/Content.Client/Chat/ChatManager.cs
index 61dbdc28f1..8553c6ed58 100644
--- a/Content.Client/Chat/ChatManager.cs
+++ b/Content.Client/Chat/ChatManager.cs
@@ -247,13 +247,17 @@ namespace Content.Client.Chat
case OOCAlias:
{
var conInput = text.Substring(1);
+ if (string.IsNullOrWhiteSpace(conInput))
+ return;
_console.ProcessCommand($"ooc \"{CommandParsing.Escape(conInput)}\"");
break;
}
case AdminChatAlias:
{
var conInput = text.Substring(1);
- if(_groupController.CanCommand("asay")){
+ if (string.IsNullOrWhiteSpace(conInput))
+ return;
+ if (_groupController.CanCommand("asay")){
_console.ProcessCommand($"asay \"{CommandParsing.Escape(conInput)}\"");
}
else
@@ -265,6 +269,8 @@ namespace Content.Client.Chat
case MeAlias:
{
var conInput = text.Substring(1);
+ if (string.IsNullOrWhiteSpace(conInput))
+ return;
_console.ProcessCommand($"me \"{CommandParsing.Escape(conInput)}\"");
break;
}
diff --git a/Content.Client/Chat/SpeechBubble.cs b/Content.Client/Chat/SpeechBubble.cs
index 715715d9a1..bffe42c721 100644
--- a/Content.Client/Chat/SpeechBubble.cs
+++ b/Content.Client/Chat/SpeechBubble.cs
@@ -106,13 +106,13 @@ namespace Content.Client.Chat
}
// Lerp to our new vertical offset if it's been modified.
- if (FloatMath.CloseTo(_verticalOffsetAchieved - VerticalOffset, 0, 0.1))
+ if (MathHelper.CloseTo(_verticalOffsetAchieved - VerticalOffset, 0, 0.1))
{
_verticalOffsetAchieved = VerticalOffset;
}
else
{
- _verticalOffsetAchieved = FloatMath.Lerp(_verticalOffsetAchieved, VerticalOffset, 10 * args.DeltaSeconds);
+ _verticalOffsetAchieved = MathHelper.Lerp(_verticalOffsetAchieved, VerticalOffset, 10 * args.DeltaSeconds);
}
var worldPos = _senderEntity.Transform.WorldPosition;
@@ -122,7 +122,7 @@ namespace Content.Client.Chat
var screenPos = lowerCenter - (Width / 2, ContentHeight + _verticalOffsetAchieved);
LayoutContainer.SetPosition(this, screenPos);
- var height = FloatMath.Clamp(lowerCenter.Y - screenPos.Y, 0, ContentHeight);
+ var height = MathHelper.Clamp(lowerCenter.Y - screenPos.Y, 0, ContentHeight);
LayoutContainer.SetSize(this, (Size.X, height));
}
diff --git a/Content.Client/GameObjects/Components/Body/BodyManagerComponent.cs b/Content.Client/GameObjects/Components/Body/BodyManagerComponent.cs
index 2948c9191e..61049ec7a0 100644
--- a/Content.Client/GameObjects/Components/Body/BodyManagerComponent.cs
+++ b/Content.Client/GameObjects/Components/Body/BodyManagerComponent.cs
@@ -33,7 +33,7 @@ namespace Content.Client.GameObjects.Components.Body
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null)
{
- if (!Owner.TryGetComponent(out ISpriteComponent sprite))
+ if (!Owner.TryGetComponent(out ISpriteComponent? sprite))
{
return;
}
@@ -50,7 +50,7 @@ namespace Content.Client.GameObjects.Components.Body
if (!partRemoved.Dropped.HasValue ||
!_entityManager.TryGetEntity(partRemoved.Dropped.Value, out var entity) ||
- !entity.TryGetComponent(out ISpriteComponent droppedSprite))
+ !entity.TryGetComponent(out ISpriteComponent? droppedSprite))
{
break;
}
diff --git a/Content.Client/GameObjects/Components/ClickableComponent.cs b/Content.Client/GameObjects/Components/ClickableComponent.cs
index 57d9420167..a92af22451 100644
--- a/Content.Client/GameObjects/Components/ClickableComponent.cs
+++ b/Content.Client/GameObjects/Components/ClickableComponent.cs
@@ -37,7 +37,7 @@ namespace Content.Client.GameObjects.Components
/// True if the click worked, false otherwise.
public bool CheckClick(Vector2 worldPos, out int drawDepth, out uint renderOrder)
{
- if (!Owner.TryGetComponent(out ISpriteComponent sprite) || !sprite.Visible)
+ if (!Owner.TryGetComponent(out ISpriteComponent? sprite) || !sprite.Visible)
{
drawDepth = default;
renderOrder = default;
diff --git a/Content.Client/GameObjects/Components/Disposal/DisposalRouterBoundUserInterface.cs b/Content.Client/GameObjects/Components/Disposal/DisposalRouterBoundUserInterface.cs
new file mode 100644
index 0000000000..1bb74c1bee
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Disposal/DisposalRouterBoundUserInterface.cs
@@ -0,0 +1,67 @@
+#nullable enable
+using JetBrains.Annotations;
+using Robust.Client.GameObjects.Components.UserInterface;
+using Robust.Shared.GameObjects.Components.UserInterface;
+using Robust.Shared.Localization;
+using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalRouterComponent;
+
+namespace Content.Client.GameObjects.Components.Disposal
+{
+ ///
+ /// Initializes a and updates it when new server messages are received.
+ ///
+ [UsedImplicitly]
+ public class DisposalRouterBoundUserInterface : BoundUserInterface
+ {
+ private DisposalRouterWindow? _window;
+
+ public DisposalRouterBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _window = new DisposalRouterWindow();
+
+ _window.OpenCentered();
+ _window.OnClose += Close;
+
+ _window.Confirm.OnPressed += _ => ButtonPressed(UiAction.Ok, _window.TagInput.Text);
+ _window.TagInput.OnTextEntered += args => ButtonPressed(UiAction.Ok, args.Text);
+
+ }
+
+ private void ButtonPressed(UiAction action, string tag)
+ {
+ SendMessage(new UiActionMessage(action, tag));
+ _window?.Close();
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+
+ if (!(state is DisposalRouterUserInterfaceState cast))
+ {
+ return;
+ }
+
+ _window?.UpdateState(cast);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (disposing)
+ {
+ _window?.Dispose();
+ }
+ }
+
+
+ }
+
+}
diff --git a/Content.Client/GameObjects/Components/Disposal/DisposalRouterWindow.cs b/Content.Client/GameObjects/Components/Disposal/DisposalRouterWindow.cs
new file mode 100644
index 0000000000..98e345f124
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Disposal/DisposalRouterWindow.cs
@@ -0,0 +1,51 @@
+using Content.Shared.GameObjects.Components.Disposal;
+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.Disposal.SharedDisposalRouterComponent;
+
+namespace Content.Client.GameObjects.Components.Disposal
+{
+ ///
+ /// Client-side UI used to control a
+ ///
+ public class DisposalRouterWindow : SS14Window
+ {
+ public readonly LineEdit TagInput;
+ public readonly Button Confirm;
+
+ protected override Vector2? CustomSize => (400, 80);
+
+ public DisposalRouterWindow()
+ {
+ Title = Loc.GetString("Disposal Router");
+
+ Contents.AddChild(new VBoxContainer
+ {
+ Children =
+ {
+ new Label {Text = Loc.GetString("Tags:")},
+ new Control {CustomMinimumSize = (0, 10)},
+ new HBoxContainer
+ {
+ Children =
+ {
+ (TagInput = new LineEdit {SizeFlagsHorizontal = SizeFlags.Expand, CustomMinimumSize = (320, 0),
+ ToolTip = Loc.GetString("A comma separated list of tags"), IsValid = tags => TagRegex.IsMatch(tags)}),
+ new Control {CustomMinimumSize = (10, 0)},
+ (Confirm = new Button {Text = Loc.GetString("Confirm")})
+ }
+ }
+ }
+ });
+ }
+
+
+ public void UpdateState(DisposalRouterUserInterfaceState state)
+ {
+ TagInput.Text = state.Tags;
+ }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Disposal/DisposalTaggerBoundUserInterface.cs b/Content.Client/GameObjects/Components/Disposal/DisposalTaggerBoundUserInterface.cs
new file mode 100644
index 0000000000..76d8a4fd48
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Disposal/DisposalTaggerBoundUserInterface.cs
@@ -0,0 +1,67 @@
+#nullable enable
+using JetBrains.Annotations;
+using Robust.Client.GameObjects.Components.UserInterface;
+using Robust.Shared.GameObjects.Components.UserInterface;
+using Robust.Shared.Localization;
+using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalTaggerComponent;
+
+namespace Content.Client.GameObjects.Components.Disposal
+{
+ ///
+ /// Initializes a and updates it when new server messages are received.
+ ///
+ [UsedImplicitly]
+ public class DisposalTaggerBoundUserInterface : BoundUserInterface
+ {
+ private DisposalTaggerWindow? _window;
+
+ public DisposalTaggerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _window = new DisposalTaggerWindow();
+
+ _window.OpenCentered();
+ _window.OnClose += Close;
+
+ _window.Confirm.OnPressed += _ => ButtonPressed(UiAction.Ok, _window.TagInput.Text);
+ _window.TagInput.OnTextEntered += args => ButtonPressed(UiAction.Ok, args.Text);
+
+ }
+
+ private void ButtonPressed(UiAction action, string tag)
+ {
+ SendMessage(new UiActionMessage(action, tag));
+ _window?.Close();
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+
+ if (!(state is DisposalTaggerUserInterfaceState cast))
+ {
+ return;
+ }
+
+ _window?.UpdateState(cast);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (disposing)
+ {
+ _window?.Dispose();
+ }
+ }
+
+
+ }
+
+}
diff --git a/Content.Client/GameObjects/Components/Disposal/DisposalTaggerWindow.cs b/Content.Client/GameObjects/Components/Disposal/DisposalTaggerWindow.cs
new file mode 100644
index 0000000000..54dec5b807
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Disposal/DisposalTaggerWindow.cs
@@ -0,0 +1,51 @@
+using Content.Shared.GameObjects.Components.Disposal;
+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.Disposal.SharedDisposalTaggerComponent;
+
+namespace Content.Client.GameObjects.Components.Disposal
+{
+ ///
+ /// Client-side UI used to control a
+ ///
+ public class DisposalTaggerWindow : SS14Window
+ {
+ public readonly LineEdit TagInput;
+ public readonly Button Confirm;
+
+ protected override Vector2? CustomSize => (400, 80);
+
+ public DisposalTaggerWindow()
+ {
+ Title = Loc.GetString("Disposal Tagger");
+
+ Contents.AddChild(new VBoxContainer
+ {
+ Children =
+ {
+ new Label {Text = Loc.GetString("Tag:")},
+ new Control {CustomMinimumSize = (0, 10)},
+ new HBoxContainer
+ {
+ Children =
+ {
+ (TagInput = new LineEdit {SizeFlagsHorizontal = SizeFlags.Expand, CustomMinimumSize = (320, 0),
+ IsValid = tag => TagRegex.IsMatch(tag)}),
+ new Control {CustomMinimumSize = (10, 0)},
+ (Confirm = new Button {Text = Loc.GetString("Confirm")})
+ }
+ }
+ }
+ });
+ }
+
+
+ public void UpdateState(DisposalTaggerUserInterfaceState state)
+ {
+ TagInput.Text = state.Tag;
+ }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Disposal/DisposalUnitWindow.cs b/Content.Client/GameObjects/Components/Disposal/DisposalUnitWindow.cs
index 62e6bd1a24..98bf7af37c 100644
--- a/Content.Client/GameObjects/Components/Disposal/DisposalUnitWindow.cs
+++ b/Content.Client/GameObjects/Components/Disposal/DisposalUnitWindow.cs
@@ -113,12 +113,12 @@ namespace Content.Client.GameObjects.Components.Disposal
if (normalized <= leftSideSize)
{
normalized /= leftSideSize; // Adjust range to 0.0 to 1.0
- finalHue = FloatMath.Lerp(leftHue, middleHue, normalized);
+ finalHue = MathHelper.Lerp(leftHue, middleHue, normalized);
}
else
{
normalized = (normalized - leftSideSize) / rightSideSize; // Adjust range to 0.0 to 1.0.
- finalHue = FloatMath.Lerp(middleHue, rightHue, normalized);
+ finalHue = MathHelper.Lerp(middleHue, rightHue, normalized);
}
// Check if null first to avoid repeatedly creating this.
diff --git a/Content.Client/GameObjects/Components/HandheldLightComponent.cs b/Content.Client/GameObjects/Components/HandheldLightComponent.cs
index d6f4a75aaa..2b2d43b1a7 100644
--- a/Content.Client/GameObjects/Components/HandheldLightComponent.cs
+++ b/Content.Client/GameObjects/Components/HandheldLightComponent.cs
@@ -78,7 +78,7 @@ namespace Content.Client.GameObjects.Components
int level;
- if (FloatMath.CloseTo(charge, 0))
+ if (MathHelper.CloseTo(charge, 0))
{
level = 0;
}
diff --git a/Content.Client/GameObjects/Components/Items/HandsComponent.cs b/Content.Client/GameObjects/Components/Items/HandsComponent.cs
index daa03d74e1..30e32afad3 100644
--- a/Content.Client/GameObjects/Components/Items/HandsComponent.cs
+++ b/Content.Client/GameObjects/Components/Items/HandsComponent.cs
@@ -148,7 +148,7 @@ namespace Content.Client.GameObjects.Components.Items
return;
}
- if (!entity.TryGetComponent(out ItemComponent item)) return;
+ if (!entity.TryGetComponent(out ItemComponent? item)) return;
var maybeInHands = item.GetInHandStateInfo(hand.Location);
diff --git a/Content.Client/GameObjects/Components/MagicMirrorBoundUserInterface.cs b/Content.Client/GameObjects/Components/MagicMirrorBoundUserInterface.cs
index aea3434e61..440b9f1d2d 100644
--- a/Content.Client/GameObjects/Components/MagicMirrorBoundUserInterface.cs
+++ b/Content.Client/GameObjects/Components/MagicMirrorBoundUserInterface.cs
@@ -243,7 +243,7 @@ namespace Content.Client.GameObjects.Components
if (int.TryParse(ev.Text, out var result))
{
- result = FloatMath.Clamp(result, 0, byte.MaxValue);
+ result = MathHelper.Clamp(result, 0, byte.MaxValue);
_ignoreEvents = true;
_colorValue = (byte) result;
diff --git a/Content.Client/GameObjects/Components/MedicalScanner/MedicalScannerBoundUserInterface.cs b/Content.Client/GameObjects/Components/MedicalScanner/MedicalScannerBoundUserInterface.cs
index 4b2fa2a1fc..2280406c5b 100644
--- a/Content.Client/GameObjects/Components/MedicalScanner/MedicalScannerBoundUserInterface.cs
+++ b/Content.Client/GameObjects/Components/MedicalScanner/MedicalScannerBoundUserInterface.cs
@@ -22,6 +22,7 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
Title = Owner.Owner.Name,
};
_window.OnClose += Close;
+ _window.ScanButton.OnPressed += _ => SendMessage(new UiButtonPressedMessage(UiButton.ScanDNA));
_window.OpenCentered();
}
diff --git a/Content.Client/GameObjects/Components/MedicalScanner/MedicalScannerWindow.cs b/Content.Client/GameObjects/Components/MedicalScanner/MedicalScannerWindow.cs
index 61adc113f2..4668854fbf 100644
--- a/Content.Client/GameObjects/Components/MedicalScanner/MedicalScannerWindow.cs
+++ b/Content.Client/GameObjects/Components/MedicalScanner/MedicalScannerWindow.cs
@@ -12,18 +12,38 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
{
public class MedicalScannerWindow : SS14Window
{
+ public readonly Button ScanButton;
+ private readonly Label _diagnostics;
protected override Vector2? CustomSize => (485, 90);
+ public MedicalScannerWindow()
+ {
+ Contents.AddChild(new VBoxContainer
+ {
+ Children =
+ {
+ (ScanButton = new Button
+ {
+ Text = "Scan and Save DNA"
+ }),
+ (_diagnostics = new Label
+ {
+ Text = ""
+ })
+ }
+ });
+ }
+
public void Populate(MedicalScannerBoundUserInterfaceState state)
{
- Contents.RemoveAllChildren();
var text = new StringBuilder();
if (!state.Entity.HasValue ||
!state.HasDamage() ||
!IoCManager.Resolve().TryGetEntity(state.Entity.Value, out var entity))
{
- text.Append(Loc.GetString("No patient data."));
+ _diagnostics.Text = Loc.GetString("No patient data.");
+ ScanButton.Disabled = true;
}
else
{
@@ -45,9 +65,10 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
text.Append("\n");
}
- }
- Contents.AddChild(new Label() {Text = text.ToString()});
+ _diagnostics.Text = text.ToString();
+ ScanButton.Disabled = state.IsScanned;
+ }
}
}
}
diff --git a/Content.Client/GameObjects/Components/Mobs/CameraRecoilComponent.cs b/Content.Client/GameObjects/Components/Mobs/CameraRecoilComponent.cs
index 68288ff66a..cfdbe607a3 100644
--- a/Content.Client/GameObjects/Components/Mobs/CameraRecoilComponent.cs
+++ b/Content.Client/GameObjects/Components/Mobs/CameraRecoilComponent.cs
@@ -23,7 +23,7 @@ namespace Content.Client.GameObjects.Components.Mobs
private const float RestoreRateRamp = 0.1f;
// The maximum magnitude of the kick applied to the camera at any point.
- private const float KickMagnitudeMax = 5f;
+ private const float KickMagnitudeMax = 2f;
private Vector2 _currentKick;
private float _lastKickTime;
@@ -87,7 +87,7 @@ namespace Content.Client.GameObjects.Components.Mobs
// Continually restore camera to 0.
var normalized = _currentKick.Normalized;
_lastKickTime += frameTime;
- var restoreRate = FloatMath.Lerp(RestoreRateMin, RestoreRateMax, Math.Min(1, _lastKickTime/RestoreRateRamp));
+ var restoreRate = MathHelper.Lerp(RestoreRateMin, RestoreRateMax, Math.Min(1, _lastKickTime/RestoreRateRamp));
var restore = normalized * restoreRate * frameTime;
var (x, y) = _currentKick - restore;
if (Math.Sign(x) != Math.Sign(_currentKick.X))
diff --git a/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs b/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs
index 926e6a5c46..8c9344d5fd 100644
--- a/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs
+++ b/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs
@@ -152,7 +152,7 @@ namespace Content.Client.GameObjects.Components.Mobs
var progress = (_gameTiming.CurTime - start).TotalSeconds / length;
var ratio = (progress <= 1 ? (1 - progress) : (_gameTiming.CurTime - end).TotalSeconds * -5);
- cooldownGraphic.Progress = FloatMath.Clamp((float)ratio, -1, 1);
+ cooldownGraphic.Progress = MathHelper.Clamp((float)ratio, -1, 1);
cooldownGraphic.Visible = ratio > -1f;
}
}
diff --git a/Content.Client/GameObjects/Components/Mobs/StunnableComponent.cs b/Content.Client/GameObjects/Components/Mobs/StunnableComponent.cs
index 4c02d343cb..9d6ce08f8f 100644
--- a/Content.Client/GameObjects/Components/Mobs/StunnableComponent.cs
+++ b/Content.Client/GameObjects/Components/Mobs/StunnableComponent.cs
@@ -34,7 +34,7 @@ namespace Content.Client.GameObjects.Components.Mobs
WalkModifierOverride = state.WalkModifierOverride;
RunModifierOverride = state.RunModifierOverride;
- if (Owner.TryGetComponent(out MovementSpeedModifierComponent movement))
+ if (Owner.TryGetComponent(out MovementSpeedModifierComponent? movement))
{
movement.RefreshMovementSpeedModifiers();
}
diff --git a/Content.Client/GameObjects/Components/Movement/ClimbableComponent.cs b/Content.Client/GameObjects/Components/Movement/ClimbableComponent.cs
new file mode 100644
index 0000000000..d637853960
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Movement/ClimbableComponent.cs
@@ -0,0 +1,12 @@
+using Robust.Shared.GameObjects;
+using Content.Shared.GameObjects.Components.Movement;
+
+namespace Content.Client.GameObjects.Components.Movement
+{
+ [RegisterComponent]
+ [ComponentReference(typeof(IClimbable))]
+ public class ClimbableComponent : SharedClimbableComponent
+ {
+
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Movement/ClimbingComponent.cs b/Content.Client/GameObjects/Components/Movement/ClimbingComponent.cs
new file mode 100644
index 0000000000..07f8c7c5b6
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Movement/ClimbingComponent.cs
@@ -0,0 +1,34 @@
+using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Components;
+using Content.Shared.GameObjects.Components.Movement;
+using Content.Client.Interfaces.GameObjects.Components.Interaction;
+using Content.Shared.Physics;
+
+namespace Content.Client.GameObjects.Components.Movement
+{
+ [RegisterComponent]
+ public class ClimbingComponent : SharedClimbingComponent, IClientDraggable
+ {
+ public override void HandleComponentState(ComponentState curState, ComponentState nextState)
+ {
+ if (!(curState is ClimbModeComponentState climbModeState) || Body == null)
+ {
+ return;
+ }
+
+ IsClimbing = climbModeState.Climbing;
+ }
+
+ public override bool IsClimbing { get; set; }
+
+ bool IClientDraggable.ClientCanDropOn(CanDropEventArgs eventArgs)
+ {
+ return eventArgs.Target.HasComponent();
+ }
+
+ bool IClientDraggable.ClientCanDrag(CanDragEventArgs eventArgs)
+ {
+ return true;
+ }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Nutrition/HungerComponent.cs b/Content.Client/GameObjects/Components/Nutrition/HungerComponent.cs
index 8323fb5e7a..6db62b1bb3 100644
--- a/Content.Client/GameObjects/Components/Nutrition/HungerComponent.cs
+++ b/Content.Client/GameObjects/Components/Nutrition/HungerComponent.cs
@@ -20,7 +20,7 @@ namespace Content.Client.GameObjects.Components.Nutrition
_currentHungerThreshold = hunger.CurrentThreshold;
- if (Owner.TryGetComponent(out MovementSpeedModifierComponent movement))
+ if (Owner.TryGetComponent(out MovementSpeedModifierComponent? movement))
{
movement.RefreshMovementSpeedModifiers();
}
diff --git a/Content.Client/GameObjects/Components/Nutrition/ThirstComponent.cs b/Content.Client/GameObjects/Components/Nutrition/ThirstComponent.cs
index b77d59a34a..a211afe239 100644
--- a/Content.Client/GameObjects/Components/Nutrition/ThirstComponent.cs
+++ b/Content.Client/GameObjects/Components/Nutrition/ThirstComponent.cs
@@ -20,7 +20,7 @@ namespace Content.Client.GameObjects.Components.Nutrition
_currentThirstThreshold = thirst.CurrentThreshold;
- if (Owner.TryGetComponent(out MovementSpeedModifierComponent movement))
+ if (Owner.TryGetComponent(out MovementSpeedModifierComponent? movement))
{
movement.RefreshMovementSpeedModifiers();
}
diff --git a/Content.Client/GameObjects/Components/PDA/PDAVisualizer.cs b/Content.Client/GameObjects/Components/PDA/PDAVisualizer.cs
index 019bd03ae1..5ea8bbbbc1 100644
--- a/Content.Client/GameObjects/Components/PDA/PDAVisualizer.cs
+++ b/Content.Client/GameObjects/Components/PDA/PDAVisualizer.cs
@@ -1,4 +1,4 @@
-using Content.Shared.GameObjects.Components.PDA;
+using Content.Shared.GameObjects.Components.PDA;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
@@ -10,7 +10,7 @@ namespace Content.Client.GameObjects.Components.PDA
private enum PDAVisualLayers
{
Base,
- Unlit
+ Flashlight
}
@@ -22,13 +22,13 @@ namespace Content.Client.GameObjects.Components.PDA
return;
}
var sprite = component.Owner.GetComponent();
- sprite.LayerSetVisible(PDAVisualLayers.Unlit, false);
- if(!component.TryGetData(PDAVisuals.ScreenLit, out var isScreenLit))
+ sprite.LayerSetVisible(PDAVisualLayers.Flashlight, false);
+ if(!component.TryGetData(PDAVisuals.FlashlightLit, out var isScreenLit))
{
return;
}
- sprite.LayerSetState(PDAVisualLayers.Unlit, "unlit_pda_screen");
- sprite.LayerSetVisible(PDAVisualLayers.Unlit, isScreenLit);
+ sprite.LayerSetState(PDAVisualLayers.Flashlight, "light_overlay");
+ sprite.LayerSetVisible(PDAVisualLayers.Flashlight, isScreenLit);
}
diff --git a/Content.Client/GameObjects/Components/Power/ApcBoundUserInterface.cs b/Content.Client/GameObjects/Components/Power/ApcBoundUserInterface.cs
index 616997820e..108b85fe55 100644
--- a/Content.Client/GameObjects/Components/Power/ApcBoundUserInterface.cs
+++ b/Content.Client/GameObjects/Components/Power/ApcBoundUserInterface.cs
@@ -86,12 +86,12 @@ namespace Content.Client.GameObjects.Components.Power
if (normalizedCharge <= leftSideSize)
{
normalizedCharge /= leftSideSize; // Adjust range to 0.0 to 1.0
- finalHue = FloatMath.Lerp(leftHue, middleHue, normalizedCharge);
+ finalHue = MathHelper.Lerp(leftHue, middleHue, normalizedCharge);
}
else
{
normalizedCharge = (normalizedCharge - leftSideSize) / rightSideSize; // Adjust range to 0.0 to 1.0.
- finalHue = FloatMath.Lerp(middleHue, rightHue, normalizedCharge);
+ finalHue = MathHelper.Lerp(middleHue, rightHue, normalizedCharge);
}
// Check if null first to avoid repeatedly creating this.
diff --git a/Content.Client/GameObjects/Components/Weapons/FlashableComponent.cs b/Content.Client/GameObjects/Components/Weapons/FlashableComponent.cs
index 18047a8f85..5e8573034a 100644
--- a/Content.Client/GameObjects/Components/Weapons/FlashableComponent.cs
+++ b/Content.Client/GameObjects/Components/Weapons/FlashableComponent.cs
@@ -141,7 +141,7 @@ namespace Content.Client.GameObjects.Components.Weapons
const float xOffset = 0.0f;
// Overkill but easy to adjust if you want to mess around with the design
- var result = (float) FloatMath.Clamp(slope * (float) Math.Pow(ratio - xOffset, exponent) + yOffset, 0.0, 1.0);
+ var result = (float) MathHelper.Clamp(slope * (float) Math.Pow(ratio - xOffset, exponent) + yOffset, 0.0, 1.0);
DebugTools.Assert(!float.IsNaN(result));
return result;
}
diff --git a/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterGui.cs b/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterGui.cs
index b46b359ff4..a6724c0f6a 100644
--- a/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterGui.cs
+++ b/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterGui.cs
@@ -143,7 +143,7 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter
{
base.FrameUpdate(args);
- if (AttachedEntity?.IsValid() != true || !AttachedEntity.TryGetComponent(out DoAfterComponent doAfterComponent))
+ if (AttachedEntity?.IsValid() != true || !AttachedEntity.TryGetComponent(out DoAfterComponent? doAfterComponent))
{
return;
}
diff --git a/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs b/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs
index 8aed094c7f..cc0337a941 100644
--- a/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs
+++ b/Content.Client/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs
@@ -67,7 +67,7 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter
Gui ??= new DoAfterGui();
Gui.AttachedEntity = entity;
- if (entity.TryGetComponent(out DoAfterComponent doAfterComponent))
+ if (entity.TryGetComponent(out DoAfterComponent? doAfterComponent))
{
foreach (var (_, doAfter) in doAfterComponent.DoAfters)
{
@@ -87,7 +87,7 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter
return;
}
- if (!_player.TryGetComponent(out DoAfterComponent doAfterComponent))
+ if (!_player.TryGetComponent(out DoAfterComponent? doAfterComponent))
{
return;
}
diff --git a/Content.Client/GameObjects/EntitySystems/MoverSystem.cs b/Content.Client/GameObjects/EntitySystems/MoverSystem.cs
index cdabb87d7c..db3ad9fc33 100644
--- a/Content.Client/GameObjects/EntitySystems/MoverSystem.cs
+++ b/Content.Client/GameObjects/EntitySystems/MoverSystem.cs
@@ -25,7 +25,7 @@ namespace Content.Client.GameObjects.EntitySystems
{
var playerEnt = _playerManager.LocalPlayer?.ControlledEntity;
- if (playerEnt == null || !playerEnt.TryGetComponent(out IMoverComponent mover))
+ if (playerEnt == null || !playerEnt.TryGetComponent(out IMoverComponent? mover))
{
return;
}
diff --git a/Content.Client/GameObjects/EntitySystems/VerbSystem.cs b/Content.Client/GameObjects/EntitySystems/VerbSystem.cs
index f388263d65..486d491d1c 100644
--- a/Content.Client/GameObjects/EntitySystems/VerbSystem.cs
+++ b/Content.Client/GameObjects/EntitySystems/VerbSystem.cs
@@ -207,11 +207,10 @@ namespace Content.Client.GameObjects.EntitySystems
//Get verbs, component dependent.
foreach (var (component, verb) in VerbUtility.GetVerbs(entity))
{
- if (verb.RequireInteractionRange && !VerbUtility.InVerbUseRange(user, entity))
- continue;
-
- if (verb.BlockedByContainers && !user.IsInSameOrNoContainer(entity))
+ if (!VerbUtility.VerbAccessChecks(user, entity, verb))
+ {
continue;
+ }
var verbData = verb.GetData(user, component);
@@ -232,11 +231,10 @@ namespace Content.Client.GameObjects.EntitySystems
//Get global verbs. Visible for all entities regardless of their components.
foreach (var globalVerb in VerbUtility.GetGlobalVerbs(Assembly.GetExecutingAssembly()))
{
- if (globalVerb.RequireInteractionRange && !VerbUtility.InVerbUseRange(user, entity))
- continue;
-
- if (globalVerb.BlockedByContainers && !user.IsInSameOrNoContainer(entity))
+ if (!VerbUtility.VerbAccessChecks(user, entity, globalVerb))
+ {
continue;
+ }
var verbData = globalVerb.GetData(user, entity);
diff --git a/Content.Client/GameTicking/ClientGameTicker.cs b/Content.Client/GameTicking/ClientGameTicker.cs
index 3b58d1c17b..3eeda60421 100644
--- a/Content.Client/GameTicking/ClientGameTicker.cs
+++ b/Content.Client/GameTicking/ClientGameTicker.cs
@@ -26,14 +26,16 @@ namespace Content.Client.GameTicking
[ViewVariables] public bool AreWeReady { get; private set; }
[ViewVariables] public bool IsGameStarted { get; private set; }
+ [ViewVariables] public bool DisallowedLateJoin { get; private set; }
[ViewVariables] public string ServerInfoBlob { get; private set; }
[ViewVariables] public DateTime StartTime { get; private set; }
[ViewVariables] public bool Paused { get; private set; }
- [ViewVariables] public Dictionary Ready { get; private set; }
+ [ViewVariables] public Dictionary Status { get; private set; }
public event Action InfoBlobUpdated;
public event Action LobbyStatusUpdated;
public event Action LobbyReadyUpdated;
+ public event Action LobbyLateJoinStatusUpdated;
public void Initialize()
{
@@ -50,11 +52,17 @@ namespace Content.Client.GameTicking
{
IoCManager.Resolve().RequestWindowAttention();
});
+ _netManager.RegisterNetMessage(nameof(MsgTickerLateJoinStatus), LateJoinStatus);
- Ready = new Dictionary();
+ Status = new Dictionary();
_initialized = true;
}
+ private void LateJoinStatus(MsgTickerLateJoinStatus message)
+ {
+ DisallowedLateJoin = message.Disallowed;
+ LobbyLateJoinStatusUpdated?.Invoke();
+ }
private void JoinLobby(MsgTickerJoinLobby message)
@@ -69,7 +77,7 @@ namespace Content.Client.GameTicking
AreWeReady = message.YouAreReady;
Paused = message.Paused;
if (IsGameStarted)
- Ready.Clear();
+ Status.Clear();
LobbyStatusUpdated?.Invoke();
}
@@ -95,9 +103,9 @@ namespace Content.Client.GameTicking
private void LobbyReady(MsgTickerLobbyReady message)
{
// Merge the Dictionaries
- foreach (var p in message.PlayerReady)
+ foreach (var p in message.PlayerStatus)
{
- Ready[p.Key] = p.Value;
+ Status[p.Key] = p.Value;
}
LobbyReadyUpdated?.Invoke();
}
@@ -105,7 +113,7 @@ namespace Content.Client.GameTicking
private void RoundEnd(MsgRoundEndMessage message)
{
//This is not ideal at all, but I don't see an immediately better fit anywhere else.
- var roundEnd = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundDuration, message.AllPlayersEndInfo);
+ var roundEnd = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundEndText, message.RoundDuration, message.AllPlayersEndInfo);
}
}
diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs
index d4588e5c84..6e67d79995 100644
--- a/Content.Client/IgnoredComponents.cs
+++ b/Content.Client/IgnoredComponents.cs
@@ -142,6 +142,8 @@
"Listening",
"Radio",
"DisposalHolder",
+ "DisposalTagger",
+ "DisposalRouter",
"DisposalTransit",
"DisposalEntry",
"DisposalJunction",
diff --git a/Content.Client/Interfaces/IClientGameTicker.cs b/Content.Client/Interfaces/IClientGameTicker.cs
index 0bedce3995..45e9a05faf 100644
--- a/Content.Client/Interfaces/IClientGameTicker.cs
+++ b/Content.Client/Interfaces/IClientGameTicker.cs
@@ -1,6 +1,7 @@
using Robust.Shared.Network;
using System;
using System.Collections.Generic;
+using static Content.Shared.SharedGameTicker;
namespace Content.Client.Interfaces
{
@@ -9,13 +10,15 @@ namespace Content.Client.Interfaces
bool IsGameStarted { get; }
string ServerInfoBlob { get; }
bool AreWeReady { get; }
+ bool DisallowedLateJoin { get; }
DateTime StartTime { get; }
bool Paused { get; }
- Dictionary Ready { get; }
+ Dictionary Status { get; }
void Initialize();
event Action InfoBlobUpdated;
event Action LobbyStatusUpdated;
event Action LobbyReadyUpdated;
+ event Action LobbyLateJoinStatusUpdated;
}
}
diff --git a/Content.Client/State/LobbyState.cs b/Content.Client/State/LobbyState.cs
index 20b4256393..d3f756a0c8 100644
--- a/Content.Client/State/LobbyState.cs
+++ b/Content.Client/State/LobbyState.cs
@@ -18,6 +18,7 @@ using Robust.Shared.Localization;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
+using static Content.Shared.SharedGameTicker;
namespace Content.Client.State
{
@@ -101,13 +102,18 @@ namespace Content.Client.State
_clientGameTicker.InfoBlobUpdated += UpdateLobbyUi;
_clientGameTicker.LobbyStatusUpdated += LobbyStatusUpdated;
_clientGameTicker.LobbyReadyUpdated += LobbyReadyUpdated;
+ _clientGameTicker.LobbyLateJoinStatusUpdated += LobbyLateJoinStatusUpdated;
}
public override void Shutdown()
{
_playerManager.PlayerListUpdated -= PlayerManagerOnPlayerListUpdated;
_clientGameTicker.InfoBlobUpdated -= UpdateLobbyUi;
- _clientGameTicker.LobbyStatusUpdated -= UpdateLobbyUi;
+ _clientGameTicker.LobbyStatusUpdated -= LobbyStatusUpdated;
+ _clientGameTicker.LobbyReadyUpdated -= LobbyReadyUpdated;
+ _clientGameTicker.LobbyLateJoinStatusUpdated -= LobbyLateJoinStatusUpdated;
+
+ _clientGameTicker.Status.Clear();
_lobby.Dispose();
_characterSetup.Dispose();
@@ -153,11 +159,14 @@ namespace Content.Client.State
private void PlayerManagerOnPlayerListUpdated(object sender, EventArgs e)
{
// Remove disconnected sessions from the Ready Dict
- foreach (var p in _clientGameTicker.Ready)
+ foreach (var p in _clientGameTicker.Status)
{
if (!_playerManager.SessionsDict.TryGetValue(p.Key, out _))
{
- _clientGameTicker.Ready.Remove(p.Key);
+ // This is a shitty fix. Observers can rejoin because they are already in the game.
+ // So we don't delete them, but keep them if they decide to rejoin
+ if (p.Value != PlayerStatus.Observer)
+ _clientGameTicker.Status.Remove(p.Key);
}
}
UpdatePlayerList();
@@ -170,6 +179,11 @@ namespace Content.Client.State
UpdateLobbyUi();
}
+ private void LobbyLateJoinStatusUpdated()
+ {
+ _lobby.ReadyButton.Disabled = _clientGameTicker.DisallowedLateJoin;
+ }
+
private void UpdateLobbyUi()
{
if (_lobby == null)
@@ -188,6 +202,7 @@ namespace Content.Client.State
_lobby.StartTime.Text = "";
_lobby.ReadyButton.Text = Loc.GetString("Ready Up");
_lobby.ReadyButton.ToggleMode = true;
+ _lobby.ReadyButton.Disabled = false;
_lobby.ReadyButton.Pressed = _clientGameTicker.AreWeReady;
}
@@ -207,12 +222,19 @@ namespace Content.Client.State
// Don't show ready state if we're ingame
if (!_clientGameTicker.IsGameStarted)
{
- var ready = false;
+ var status = PlayerStatus.NotReady;
if (session.SessionId == _playerManager.LocalPlayer.SessionId)
- ready = _clientGameTicker.AreWeReady;
+ status = _clientGameTicker.AreWeReady ? PlayerStatus.Ready : PlayerStatus.NotReady;
else
- _clientGameTicker.Ready.TryGetValue(session.SessionId, out ready);
- readyState = ready ? Loc.GetString("Ready") : Loc.GetString("Not Ready");
+ _clientGameTicker.Status.TryGetValue(session.SessionId, out status);
+
+ readyState = status switch
+ {
+ PlayerStatus.NotReady => Loc.GetString("Not Ready"),
+ PlayerStatus.Ready => Loc.GetString("Ready"),
+ PlayerStatus.Observer => Loc.GetString("Observer"),
+ _ => "",
+ };
}
_lobby.PlayerReadyList.AddItem(readyState, null, false);
}
diff --git a/Content.Client/UserInterface/CooldownGraphic.cs b/Content.Client/UserInterface/CooldownGraphic.cs
index 777ec9e1d9..99fa4fe18b 100644
--- a/Content.Client/UserInterface/CooldownGraphic.cs
+++ b/Content.Client/UserInterface/CooldownGraphic.cs
@@ -30,6 +30,7 @@ namespace Content.Client.UserInterface
protected override void Draw(DrawingHandleScreen handle)
{
+ Span x = stackalloc float[10];
Color color;
var lerp = 1f - MathF.Abs(Progress); // for future bikeshedding purposes
@@ -41,7 +42,7 @@ namespace Content.Client.UserInterface
}
else
{
- var alpha = FloatMath.Clamp(0.5f * lerp, 0f, 0.5f);
+ var alpha = MathHelper.Clamp(0.5f * lerp, 0f, 0.5f);
color = new Color(1f, 1f, 1f, alpha);
}
diff --git a/Content.Client/UserInterface/ItemSlotManager.cs b/Content.Client/UserInterface/ItemSlotManager.cs
index d7d4c1e78d..2ac6588f73 100644
--- a/Content.Client/UserInterface/ItemSlotManager.cs
+++ b/Content.Client/UserInterface/ItemSlotManager.cs
@@ -107,7 +107,7 @@ namespace Content.Client.UserInterface
var progress = (_gameTiming.CurTime - start).TotalSeconds / length;
var ratio = (progress <= 1 ? (1 - progress) : (_gameTiming.CurTime - end).TotalSeconds * -5);
- cooldownDisplay.Progress = FloatMath.Clamp((float)ratio, -1, 1);
+ cooldownDisplay.Progress = MathHelper.Clamp((float)ratio, -1, 1);
if (ratio > -1f)
{
diff --git a/Content.Client/UserInterface/LobbyCharacterPreviewPanel.cs b/Content.Client/UserInterface/LobbyCharacterPreviewPanel.cs
index b4dcdca7fb..1a4e40ac88 100644
--- a/Content.Client/UserInterface/LobbyCharacterPreviewPanel.cs
+++ b/Content.Client/UserInterface/LobbyCharacterPreviewPanel.cs
@@ -82,6 +82,8 @@ namespace Content.Client.UserInterface
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
+ _preferencesManager.OnServerDataLoaded -= UpdateUI;
+
if (!disposing) return;
_previewDummy.Delete();
_previewDummy = null;
diff --git a/Content.Client/UserInterface/RoundEndSummaryWindow.cs b/Content.Client/UserInterface/RoundEndSummaryWindow.cs
index 5f2d736d9c..8d40c0b8ba 100644
--- a/Content.Client/UserInterface/RoundEndSummaryWindow.cs
+++ b/Content.Client/UserInterface/RoundEndSummaryWindow.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using Content.Client.Utility;
+using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Localization;
@@ -17,7 +18,7 @@ namespace Content.Client.UserInterface
private TabContainer RoundEndWindowTabs { get; }
protected override Vector2? CustomSize => (520, 580);
- public RoundEndSummaryWindow(string gm, TimeSpan roundTimeSpan, List info )
+ public RoundEndSummaryWindow(string gm, string roundEnd, TimeSpan roundTimeSpan, List info)
{
Title = Loc.GetString("Round End Summary");
@@ -49,6 +50,14 @@ namespace Content.Client.UserInterface
gamemodeLabel.SetMarkup(Loc.GetString("Round of [color=white]{0}[/color] has ended.", gm));
RoundEndSummaryTab.AddChild(gamemodeLabel);
+ //Round end text
+ if (!string.IsNullOrEmpty(roundEnd))
+ {
+ var roundendLabel = new RichTextLabel();
+ roundendLabel.SetMarkup(Loc.GetString(roundEnd));
+ RoundEndSummaryTab.AddChild(roundendLabel);
+ }
+
//Duration
var roundTimeLabel = new RichTextLabel();
roundTimeLabel.SetMarkup(Loc.GetString("It lasted for [color=yellow]{0} hours, {1} minutes, and {2} seconds.",
@@ -65,30 +74,40 @@ namespace Content.Client.UserInterface
//Create labels for each player info.
foreach (var plyinfo in manifestSortedList)
{
-
var playerInfoText = new RichTextLabel()
{
- SizeFlagsVertical = SizeFlags.Fill
+ SizeFlagsVertical = SizeFlags.Fill,
};
//TODO: On Hover display a popup detailing more play info.
//For example: their antag goals and if they completed them sucessfully.
var icNameColor = plyinfo.Antag ? "red" : "white";
playerInfoText.SetMarkup(
- Loc.GetString($"[color=gray]{plyinfo.PlayerOOCName}[/color] was [color={icNameColor}]{plyinfo.PlayerICName}[/color] playing role of [color=orange]{plyinfo.Role}[/color]."));
+ Loc.GetString("[color=gray]{0}[/color] was [color={1}]{2}[/color] playing role of [color=orange]{3}[/color].",
+ plyinfo.PlayerOOCName, icNameColor, plyinfo.PlayerICName, Loc.GetString(plyinfo.Role)));
innerScrollContainer.AddChild(playerInfoText);
}
scrollContainer.AddChild(innerScrollContainer);
//Attach the entire ScrollContainer that holds all the playerinfo.
PlayerManifestoTab.AddChild(scrollContainer);
+ // TODO: 1240 Overlap, remove once it's fixed. Temp Hack to make the lines not overlap
+ PlayerManifestoTab.OnVisibilityChanged += PlayerManifestoTab_OnVisibilityChanged;
//Finally, display the window.
OpenCentered();
MoveToFront();
-
}
+ private void PlayerManifestoTab_OnVisibilityChanged(Control obj)
+ {
+ if (obj.Visible)
+ {
+ // For some reason the lines get not properly drawn with the right height
+ // so we just force a update
+ ForceRunLayoutUpdate();
+ }
+ }
}
}
diff --git a/Content.IntegrationTests/ContentIntegrationTest.cs b/Content.IntegrationTests/ContentIntegrationTest.cs
index 1970ba9ee7..023cf5fe0f 100644
--- a/Content.IntegrationTests/ContentIntegrationTest.cs
+++ b/Content.IntegrationTests/ContentIntegrationTest.cs
@@ -4,6 +4,7 @@ using Content.Client;
using Content.Client.Interfaces.Parallax;
using Content.Server;
using Content.Server.Interfaces.GameTicking;
+using NUnit.Framework;
using Robust.Shared.ContentPack;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC;
@@ -12,6 +13,7 @@ using EntryPoint = Content.Client.EntryPoint;
namespace Content.IntegrationTests
{
+ [Parallelizable(ParallelScope.All)]
public abstract class ContentIntegrationTest : RobustIntegrationTest
{
protected sealed override ClientIntegrationInstance StartClient(ClientIntegrationOptions options = null)
diff --git a/Content.IntegrationTests/DummyGameTicker.cs b/Content.IntegrationTests/DummyGameTicker.cs
index f135339c75..afe44ed8ff 100644
--- a/Content.IntegrationTests/DummyGameTicker.cs
+++ b/Content.IntegrationTests/DummyGameTicker.cs
@@ -41,7 +41,7 @@ namespace Content.IntegrationTests
{
}
- public void EndRound()
+ public void EndRound(string roundEnd)
{
}
diff --git a/Content.IntegrationTests/Tests/DeleteInventoryTest.cs b/Content.IntegrationTests/Tests/DeleteInventoryTest.cs
new file mode 100644
index 0000000000..4c3782b7fe
--- /dev/null
+++ b/Content.IntegrationTests/Tests/DeleteInventoryTest.cs
@@ -0,0 +1,51 @@
+using System.Threading.Tasks;
+using Content.Server.GameObjects.Components.GUI;
+using Content.Server.GameObjects.Components.Items.Clothing;
+using NUnit.Framework;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.Interfaces.Map;
+using Robust.Shared.IoC;
+using Robust.Shared.Map;
+using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines;
+
+namespace Content.IntegrationTests.Tests
+{
+ [TestFixture]
+ public class DeleteInventoryTest : ContentIntegrationTest
+ {
+ // Test that when deleting an entity with an InventoryComponent,
+ // any equipped items also get deleted.
+ [Test]
+ public async Task Test()
+ {
+ var server = StartServerDummyTicker();
+
+ server.Assert(() =>
+ {
+ // Spawn everything.
+ var mapMan = IoCManager.Resolve();
+
+ mapMan.CreateNewMapEntity(MapId.Nullspace);
+
+ var entMgr = IoCManager.Resolve();
+ var container = entMgr.SpawnEntity(null, MapCoordinates.Nullspace);
+ var inv = container.AddComponent();
+
+ var child = entMgr.SpawnEntity(null, MapCoordinates.Nullspace);
+ var item = child.AddComponent();
+ item.SlotFlags = SlotFlags.HEAD;
+
+ // Equip item.
+ Assert.That(inv.Equip(Slots.HEAD, item, false), Is.True);
+
+ // Delete parent.
+ container.Delete();
+
+ // Assert that child item was also deleted.
+ Assert.That(item.Deleted, Is.True);
+ });
+
+ await server.WaitIdleAsync();
+ }
+ }
+}
diff --git a/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs b/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs
index 4f6b309bf7..3eb3821634 100644
--- a/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs
+++ b/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs
@@ -81,8 +81,8 @@ namespace Content.IntegrationTests.Tests.Disposal
var disposalTrunk = entityManager.SpawnEntity("DisposalTrunk", disposalUnit.Transform.MapPosition);
// Test for components existing
- Assert.True(disposalUnit.TryGetComponent(out unit));
- Assert.True(disposalTrunk.TryGetComponent(out entry));
+ Assert.True(disposalUnit.TryGetComponent(out unit!));
+ Assert.True(disposalTrunk.TryGetComponent(out entry!));
// Can't insert, unanchored and unpowered
var disposalUnitAnchorable = disposalUnit.GetComponent();
@@ -92,8 +92,8 @@ namespace Content.IntegrationTests.Tests.Disposal
// Anchor the disposal unit
await disposalUnitAnchorable.TryAnchor(human, null, true);
- Assert.True(disposalUnit.TryGetComponent(out AnchorableComponent anchorableUnit));
- Assert.True(await anchorableUnit.TryAnchor(human, wrench));
+ Assert.True(disposalUnit.TryGetComponent(out AnchorableComponent? anchorableUnit));
+ Assert.True(await anchorableUnit!.TryAnchor(human, wrench));
Assert.True(unit.Anchored);
// No power
@@ -118,8 +118,8 @@ namespace Content.IntegrationTests.Tests.Disposal
Flush(unit, false, entry, human, wrench);
// Remove power need
- Assert.True(disposalUnit.TryGetComponent(out PowerReceiverComponent power));
- power.NeedsPower = false;
+ Assert.True(disposalUnit.TryGetComponent(out PowerReceiverComponent? power));
+ power!.NeedsPower = false;
Assert.True(unit.Powered);
// Flush with a mob and an item
diff --git a/Content.IntegrationTests/Tests/GameObjects/Components/Movement/ClimbUnitTest.cs b/Content.IntegrationTests/Tests/GameObjects/Components/Movement/ClimbUnitTest.cs
new file mode 100644
index 0000000000..e55218ef09
--- /dev/null
+++ b/Content.IntegrationTests/Tests/GameObjects/Components/Movement/ClimbUnitTest.cs
@@ -0,0 +1,64 @@
+#nullable enable
+
+using System.Threading.Tasks;
+using NUnit.Framework;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.Interfaces.Map;
+using Robust.Shared.IoC;
+using Robust.Shared.Map;
+using Content.Server.GameObjects.Components.Movement;
+using Content.Shared.Physics;
+using Robust.Shared.GameObjects.Components;
+
+namespace Content.IntegrationTests.Tests.GameObjects.Components.Movement
+{
+ [TestFixture]
+ [TestOf(typeof(ClimbableComponent))]
+ [TestOf(typeof(ClimbingComponent))]
+ public class ClimbUnitTest : ContentIntegrationTest
+ {
+ [Test]
+ public async Task Test()
+ {
+ var server = StartServerDummyTicker();
+
+ IEntity human;
+ IEntity table;
+ IEntity carpet;
+ ClimbableComponent climbable;
+ ClimbingComponent climbing;
+
+ server.Assert(() =>
+ {
+ var mapManager = IoCManager.Resolve();
+ mapManager.CreateNewMapEntity(MapId.Nullspace);
+
+ var entityManager = IoCManager.Resolve();
+
+ // Spawn the entities
+ human = entityManager.SpawnEntity("HumanMob_Content", MapCoordinates.Nullspace);
+ table = entityManager.SpawnEntity("Table", MapCoordinates.Nullspace);
+
+ // Test for climb components existing
+ // Players and tables should have these in their prototypes.
+ Assert.True(human.TryGetComponent(out climbing!), "Human has no climbing");
+ Assert.True(table.TryGetComponent(out climbable!), "Table has no climbable");
+
+ // Now let's make the player enter a climbing transitioning state.
+ climbing.IsClimbing = true;
+ climbing.TryMoveTo(human.Transform.WorldPosition, table.Transform.WorldPosition);
+ var body = human.GetComponent();
+
+ Assert.True(body.HasController(), "Player has no ClimbController");
+
+ // Force the player out of climb state. It should immediately remove the ClimbController.
+ climbing.IsClimbing = false;
+
+ Assert.True(!body.HasController(), "Player wrongly has a ClimbController");
+
+ });
+
+ await server.WaitIdleAsync();
+ }
+ }
+}
diff --git a/Content.IntegrationTests/Tests/PowerTest.cs b/Content.IntegrationTests/Tests/PowerTest.cs
new file mode 100644
index 0000000000..b53fa69734
--- /dev/null
+++ b/Content.IntegrationTests/Tests/PowerTest.cs
@@ -0,0 +1,150 @@
+using Content.Server.GameObjects.Components.Power;
+using Content.Server.GameObjects.Components.Power.ApcNetComponents;
+using Content.Server.GameObjects.Components.Power.PowerNetComponents;
+using NUnit.Framework;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.Interfaces.Map;
+using Robust.Shared.IoC;
+using Robust.Shared.Map;
+using Robust.Shared.Maths;
+using System.Threading.Tasks;
+
+namespace Content.IntegrationTests.Tests
+{
+ [TestFixture]
+ public class PowerTest : ContentIntegrationTest
+ {
+ [Test]
+ public async Task PowerNetTest()
+ {
+ var server = StartServerDummyTicker();
+
+ PowerSupplierComponent supplier = null;
+ PowerConsumerComponent consumer1 = null;
+ PowerConsumerComponent consumer2 = null;
+
+ server.Assert(() =>
+ {
+ var mapMan = IoCManager.Resolve();
+ var entityMan = IoCManager.Resolve();
+ mapMan.CreateMap(new MapId(1));
+ var grid = mapMan.CreateGrid(new MapId(1));
+
+ var generatorEnt = entityMan.SpawnEntity("DebugGenerator", new GridCoordinates(new Vector2(0, 0), grid.Index));
+ var consumerEnt1 = entityMan.SpawnEntity("DebugConsumer", new GridCoordinates(new Vector2(0, 1), grid.Index));
+ var consumerEnt2 = entityMan.SpawnEntity("DebugConsumer", new GridCoordinates(new Vector2(0, 2), grid.Index));
+
+ Assert.That(generatorEnt.TryGetComponent(out supplier));
+ Assert.That(consumerEnt1.TryGetComponent(out consumer1));
+ Assert.That(consumerEnt2.TryGetComponent(out consumer2));
+
+ var supplyRate = 1000; //arbitrary amount of power supply
+
+ supplier.SupplyRate = supplyRate;
+ consumer1.DrawRate = supplyRate / 2; //arbitrary draw less than supply
+ consumer2.DrawRate = supplyRate * 2; //arbitrary draw greater than supply
+
+ consumer1.Priority = Priority.First; //power goes to this consumer first
+ consumer2.Priority = Priority.Last; //any excess power should go to low priority consumer
+ });
+
+ server.RunTicks(1); //let run a tick for PowerNet to process power
+
+ server.Assert(() =>
+ {
+ Assert.That(consumer1.DrawRate, Is.EqualTo(consumer1.ReceivedPower)); //first should be fully powered
+ Assert.That(consumer2.ReceivedPower, Is.EqualTo(supplier.SupplyRate - consumer1.ReceivedPower)); //second should get remaining power
+ });
+
+ await server.WaitIdleAsync();
+ }
+
+ [Test]
+ public async Task ApcChargingTest()
+ {
+ var server = StartServerDummyTicker();
+
+ BatteryComponent apcBattery = null;
+ PowerSupplierComponent substationSupplier = null;
+
+ server.Assert(() =>
+ {
+ var mapMan = IoCManager.Resolve();
+ var entityMan = IoCManager.Resolve();
+ mapMan.CreateMap(new MapId(1));
+ var grid = mapMan.CreateGrid(new MapId(1));
+
+ var generatorEnt = entityMan.SpawnEntity("DebugGenerator", new GridCoordinates(new Vector2(0, 0), grid.Index));
+ var substationEnt = entityMan.SpawnEntity("DebugSubstation", new GridCoordinates(new Vector2(0, 1), grid.Index));
+ var apcEnt = entityMan.SpawnEntity("DebugApc", new GridCoordinates(new Vector2(0, 2), grid.Index));
+
+ Assert.That(generatorEnt.TryGetComponent(out var generatorSupplier));
+
+ Assert.That(substationEnt.TryGetComponent(out substationSupplier));
+ Assert.That(substationEnt.TryGetComponent(out var substationStorage));
+ Assert.That(substationEnt.TryGetComponent(out var substationDischarger));
+
+ Assert.That(apcEnt.TryGetComponent(out apcBattery));
+ Assert.That(apcEnt.TryGetComponent(out var apcStorage));
+
+ generatorSupplier.SupplyRate = 1000; //arbitrary nonzero amount of power
+ substationStorage.ActiveDrawRate = 1000; //arbitrary nonzero power draw
+ substationDischarger.ActiveSupplyRate = 500; //arbitirary nonzero power supply less than substation storage draw
+ apcStorage.ActiveDrawRate = 500; //arbitrary nonzero power draw
+ apcBattery.MaxCharge = 100; //abbitrary nonzero amount of charge
+ apcBattery.CurrentCharge = 0; //no charge
+ });
+
+ server.RunTicks(5); //let run a few ticks for PowerNets to reevaluate and start charging apc
+
+ server.Assert(() =>
+ {
+ Assert.That(substationSupplier.SupplyRate, Is.Not.EqualTo(0)); //substation should be providing power
+ Assert.That(apcBattery.CurrentCharge, Is.Not.EqualTo(0)); //apc battery should have gained charge
+ });
+
+ await server.WaitIdleAsync();
+ }
+
+ [Test]
+ public async Task ApcNetTest()
+ {
+ var server = StartServerDummyTicker();
+
+ PowerReceiverComponent receiver = null;
+
+ server.Assert(() =>
+ {
+ var mapMan = IoCManager.Resolve();
+ var entityMan = IoCManager.Resolve();
+ mapMan.CreateMap(new MapId(1));
+ var grid = mapMan.CreateGrid(new MapId(1));
+
+ var apcEnt = entityMan.SpawnEntity("DebugApc", new GridCoordinates(new Vector2(0, 0), grid.Index));
+ var apcExtensionEnt = entityMan.SpawnEntity("ApcExtensionCable", new GridCoordinates(new Vector2(0, 1), grid.Index));
+ var powerReceiverEnt = entityMan.SpawnEntity("DebugPowerReceiver", new GridCoordinates(new Vector2(0, 2), grid.Index));
+
+ Assert.That(apcEnt.TryGetComponent(out var apc));
+ Assert.That(apcExtensionEnt.TryGetComponent(out var provider));
+ Assert.That(powerReceiverEnt.TryGetComponent(out receiver));
+
+ provider.PowerTransferRange = 5; //arbitrary range to reach receiver
+ receiver.PowerReceptionRange = 5; //arbitrary range to reach provider
+
+ apc.Battery.MaxCharge = 10000; //arbitrary nonzero amount of charge
+ apc.Battery.CurrentCharge = apc.Battery.MaxCharge; //fill battery
+
+ receiver.Load = 1; //arbitrary small amount of power
+ });
+
+ server.RunTicks(1); //let run a tick for ApcNet to process power
+
+ server.Assert(() =>
+ {
+ Assert.That(receiver.Powered);
+ });
+
+ await server.WaitIdleAsync();
+ }
+ }
+}
diff --git a/Content.IntegrationTests/Tests/SaveLoadMapTest.cs b/Content.IntegrationTests/Tests/SaveLoadMapTest.cs
index 05c3a6f243..e6c685285c 100644
--- a/Content.IntegrationTests/Tests/SaveLoadMapTest.cs
+++ b/Content.IntegrationTests/Tests/SaveLoadMapTest.cs
@@ -3,8 +3,11 @@ using NUnit.Framework;
using Robust.Server.Interfaces.Maps;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
+using Robust.Shared.Interfaces.Resources;
+using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
+using Robust.Shared.Utility;
namespace Content.IntegrationTests.Tests
{
@@ -14,7 +17,7 @@ namespace Content.IntegrationTests.Tests
[Test]
public async Task SaveLoadMultiGridMap()
{
- const string mapPath = @"Maps/Test/TestMap.yml";
+ const string mapPath = @"/Maps/Test/TestMap.yml";
var server = StartServer();
await server.WaitIdleAsync();
@@ -24,6 +27,10 @@ namespace Content.IntegrationTests.Tests
server.Post(() =>
{
+ var dir = new ResourcePath(mapPath).Directory;
+ IoCManager.Resolve()
+ .UserData.CreateDir(dir);
+
var mapId = mapManager.CreateMap(new MapId(5));
{
diff --git a/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs b/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs
index d462beb279..cb4a8d3ea4 100644
--- a/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs
+++ b/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs
@@ -38,14 +38,14 @@ namespace Content.IntegrationTests.Tests
string one;
string two;
- var rp1 = new ResourcePath("save load save 1.yml");
+ var rp1 = new ResourcePath("/save load save 1.yml");
using (var stream = userData.Open(rp1, FileMode.Open))
using (var reader = new StreamReader(stream))
{
one = reader.ReadToEnd();
}
- var rp2 = new ResourcePath("save load save 2.yml");
+ var rp2 = new ResourcePath("/save load save 2.yml");
using (var stream = userData.Open(rp2, FileMode.Open))
using (var reader = new StreamReader(stream))
{
@@ -96,7 +96,7 @@ namespace Content.IntegrationTests.Tests
server.Post(() =>
{
- mapLoader.SaveBlueprint(grid.Index, "load save ticks save 2.yml");
+ mapLoader.SaveBlueprint(grid.Index, "/load save ticks save 2.yml");
});
await server.WaitIdleAsync();
@@ -105,13 +105,13 @@ namespace Content.IntegrationTests.Tests
string one;
string two;
- using (var stream = userData.Open(new ResourcePath("load save ticks save 1.yml"), FileMode.Open))
+ using (var stream = userData.Open(new ResourcePath("/load save ticks save 1.yml"), FileMode.Open))
using (var reader = new StreamReader(stream))
{
one = reader.ReadToEnd();
}
- using (var stream = userData.Open(new ResourcePath("load save ticks save 2.yml"), FileMode.Open))
+ using (var stream = userData.Open(new ResourcePath("/load save ticks save 2.yml"), FileMode.Open))
using (var reader = new StreamReader(stream))
{
two = reader.ReadToEnd();
diff --git a/Content.Server.Database/Configuration.cs b/Content.Server.Database/Configuration.cs
index 1541b42ed8..6db9a594d3 100644
--- a/Content.Server.Database/Configuration.cs
+++ b/Content.Server.Database/Configuration.cs
@@ -1,4 +1,5 @@
-using Microsoft.EntityFrameworkCore;
+using Microsoft.Data.Sqlite;
+using Microsoft.EntityFrameworkCore;
using Npgsql;
namespace Content.Server.Database
@@ -50,9 +51,10 @@ namespace Content.Server.Database
public class SqliteConfiguration : IDatabaseConfiguration
{
- private readonly string _databaseFilePath;
+ private readonly string? _databaseFilePath;
- public SqliteConfiguration(string databaseFilePath)
+ /// If null, an in-memory database is used.
+ public SqliteConfiguration(string? databaseFilePath)
{
_databaseFilePath = databaseFilePath;
}
@@ -62,7 +64,20 @@ namespace Content.Server.Database
get
{
var optionsBuilder = new DbContextOptionsBuilder();
- optionsBuilder.UseSqlite($"Data Source={_databaseFilePath}");
+ SqliteConnection connection;
+ if (_databaseFilePath != null)
+ {
+ connection = new SqliteConnection($"Data Source={_databaseFilePath}");
+ }
+ else
+ {
+ connection = new SqliteConnection("Data Source=:memory:");
+ // When using an in-memory DB we have to open it manually
+ // so EFCore doesn't open, close and wipe it.
+ connection.Open();
+ }
+
+ optionsBuilder.UseSqlite(connection);
return optionsBuilder.Options;
}
}
diff --git a/Content.Server/AI/Utility/Actions/UtilityAction.cs b/Content.Server/AI/Utility/Actions/UtilityAction.cs
index f8be97abaa..b79fa0955a 100644
--- a/Content.Server/AI/Utility/Actions/UtilityAction.cs
+++ b/Content.Server/AI/Utility/Actions/UtilityAction.cs
@@ -112,19 +112,14 @@ namespace Content.Server.AI.Utility.Actions
UpdateBlackboard(context);
var considerations = GetConsiderations(context);
DebugTools.Assert(considerations.Count > 0);
- // I used the IAUS video although I did have some confusion on how to structure it overall
- // as some of the slides seemed contradictory
- // Ideally we should early-out each action as cheaply as possible if it's not valid
-
- // We also need some way to tell if the action isn't going to
- // have a better score than the current action (if applicable) and early-out that way as well.
-
- // 23:00 Building a better centaur
+ // Overall structure is based on Building a better centaur
+ // Ideally we should early-out each action as cheaply as possible if it's not valid, thus
+ // the finalScore can only go down over time.
+
var finalScore = 1.0f;
var minThreshold = min / Bonus;
context.GetState().SetValue(considerations.Count);
- // See 10:09 for this and the adjustments
foreach (var consideration in considerations)
{
diff --git a/Content.Server/AI/Utility/Considerations/Consideration.cs b/Content.Server/AI/Utility/Considerations/Consideration.cs
index 20afc4dec1..e7212bf1dd 100644
--- a/Content.Server/AI/Utility/Considerations/Consideration.cs
+++ b/Content.Server/AI/Utility/Considerations/Consideration.cs
@@ -13,18 +13,27 @@ namespace Content.Server.AI.Utility.Considerations
private float GetAdjustedScore(Blackboard context)
{
var score = GetScore(context);
+ /*
+ * Now using the geometric mean
+ * for n scores you take the n-th root of the scores multiplied
+ * e.g. a, b, c scores you take Math.Pow(a * b * c, 1/3)
+ * To get the ACTUAL geometric mean at any one stage you'd need to divide by the running consideration count
+ * however, the downside to this is it will fluctuate up and down over time.
+ * For our purposes if we go below the minimum threshold we want to cut it off, thus we take a
+ * "running geometric mean" which can only ever go down (and by the final value will equal the actual geometric mean).
+ */
+
+ // Previously we used a makeupvalue method although the geometric mean is less punishing for more considerations
var considerationsCount = context.GetState().GetValue();
- var modificationFactor = 1.0f - 1.0f / considerationsCount;
- var makeUpValue = (1.0f - score) * modificationFactor;
- var adjustedScore = score + makeUpValue * score;
- return FloatMath.Clamp(adjustedScore, 0.0f, 1.0f);
+ var adjustedScore = MathF.Pow(score, 1 / (float) considerationsCount);
+ return MathHelper.Clamp(adjustedScore, 0.0f, 1.0f);
}
[Pure]
private static float BoolCurve(float x)
{
// ReSharper disable once CompareOfFloatsByEqualityOperator
- return x == 1.0f ? 1.0f : 0.0f;
+ return x > 0.0f ? 1.0f : 0.0f;
}
public Func BoolCurve(Blackboard context)
@@ -42,7 +51,7 @@ namespace Content.Server.AI.Utility.Considerations
private static float InverseBoolCurve(float x)
{
// ReSharper disable once CompareOfFloatsByEqualityOperator
- return x == 1.0f ? 0.0f : 1.0f;
+ return x == 0.0f ? 1.0f : 0.0f;
}
public Func InverseBoolCurve(Blackboard context)
@@ -59,7 +68,7 @@ namespace Content.Server.AI.Utility.Considerations
[Pure]
private static float LogisticCurve(float x, float slope, float exponent, float yOffset, float xOffset)
{
- return FloatMath.Clamp(
+ return MathHelper.Clamp(
exponent * (1 / (1 + (float) Math.Pow(Math.Log(1000) * slope, -1 * x + xOffset))) + yOffset, 0.0f, 1.0f);
}
@@ -77,7 +86,7 @@ namespace Content.Server.AI.Utility.Considerations
[Pure]
private static float QuadraticCurve(float x, float slope, float exponent, float yOffset, float xOffset)
{
- return FloatMath.Clamp(slope * (float) Math.Pow(x - xOffset, exponent) + yOffset, 0.0f, 1.0f);
+ return MathHelper.Clamp(slope * (float) Math.Pow(x - xOffset, exponent) + yOffset, 0.0f, 1.0f);
}
public Func QuadraticCurve(Blackboard context, float slope, float exponent, float yOffset, float xOffset)
diff --git a/Content.Server/AI/WorldState/States/Inventory/InventoryState.cs b/Content.Server/AI/WorldState/States/Inventory/InventoryState.cs
index 3f570d58bd..8f9701a9ee 100644
--- a/Content.Server/AI/WorldState/States/Inventory/InventoryState.cs
+++ b/Content.Server/AI/WorldState/States/Inventory/InventoryState.cs
@@ -16,6 +16,9 @@ namespace Content.Server.AI.WorldState.States.Inventory
{
foreach (var item in handsComponent.GetAllHeldItems())
{
+ if (item.Owner.Deleted)
+ continue;
+
yield return item.Owner;
}
}
diff --git a/Content.Server/Administration/ReadyAll.cs b/Content.Server/Administration/ReadyAll.cs
new file mode 100644
index 0000000000..05d0b48fc8
--- /dev/null
+++ b/Content.Server/Administration/ReadyAll.cs
@@ -0,0 +1,40 @@
+#nullable enable
+using Content.Server.GameTicking;
+using Content.Server.Interfaces.GameTicking;
+using Robust.Server.Interfaces.Console;
+using Robust.Server.Interfaces.Player;
+using Robust.Shared.IoC;
+
+namespace Content.Server.Administration
+{
+ public class ReadyAll : IClientCommand
+ {
+ public string Command => "readyall";
+ public string Description => "Readies up all players in the lobby.";
+ public string Help => $"{Command} | ̣{Command} ";
+ public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
+ {
+ var ready = true;
+
+ if (args.Length > 0)
+ {
+ ready = bool.Parse(args[0]);
+ }
+
+ var gameTicker = IoCManager.Resolve();
+ var playerManager = IoCManager.Resolve();
+
+
+ if (gameTicker.RunLevel != GameRunLevel.PreRoundLobby)
+ {
+ shell.SendText(player, "This command can only be ran while in the lobby!");
+ return;
+ }
+
+ foreach (var p in playerManager.GetAllPlayers())
+ {
+ gameTicker.ToggleReady(p, ready);
+ }
+ }
+ }
+}
diff --git a/Content.Server/Atmos/HighPressureMovementController.cs b/Content.Server/Atmos/HighPressureMovementController.cs
index 2e27f513ad..bd9f6b3e38 100644
--- a/Content.Server/Atmos/HighPressureMovementController.cs
+++ b/Content.Server/Atmos/HighPressureMovementController.cs
@@ -53,14 +53,14 @@ namespace Content.Server.Atmos
{
if (throwTarget != GridCoordinates.InvalidGrid)
{
- var moveForce = maxForce * FloatMath.Clamp(moveProb, 0, 100) / 150f;
+ var moveForce = maxForce * MathHelper.Clamp(moveProb, 0, 100) / 150f;
var pos = ((throwTarget.Position - transform.GridPosition.Position).Normalized + direction.ToVec()).Normalized;
LinearVelocity = pos * moveForce;
}
else
{
- var moveForce = MathF.Min(maxForce * FloatMath.Clamp(moveProb, 0, 100) / 2500f, 20f);
+ var moveForce = MathF.Min(maxForce * MathHelper.Clamp(moveProb, 0, 100) / 2500f, 20f);
LinearVelocity = direction.ToVec() * moveForce;
}
diff --git a/Content.Server/Atmos/TileAtmosphere.cs b/Content.Server/Atmos/TileAtmosphere.cs
index 10ff57c948..b69feedf4f 100644
--- a/Content.Server/Atmos/TileAtmosphere.cs
+++ b/Content.Server/Atmos/TileAtmosphere.cs
@@ -172,7 +172,7 @@ namespace Content.Server.Atmos
{
if(_soundCooldown == 0)
EntitySystem.Get().PlayAtCoords("/Audio/Effects/space_wind.ogg",
- GridIndices.ToGridCoordinates(_mapManager, GridIndex), AudioHelpers.WithVariation(0.125f).WithVolume(FloatMath.Clamp(PressureDifference / 10, 10, 100)));
+ GridIndices.ToGridCoordinates(_mapManager, GridIndex), AudioHelpers.WithVariation(0.125f).WithVolume(MathHelper.Clamp(PressureDifference / 10, 10, 100)));
}
@@ -1043,7 +1043,7 @@ namespace Content.Server.Atmos
private void HandleDecompressionFloorRip(float sum)
{
- var chance = FloatMath.Clamp(sum / 500, 0.005f, 0.5f);
+ var chance = MathHelper.Clamp(sum / 500, 0.005f, 0.5f);
if (sum > 20 && _robustRandom.Prob(chance))
_gridAtmosphereComponent.PryTile(GridIndices);
}
diff --git a/Content.Server/Body/BodyCommands.cs b/Content.Server/Body/BodyCommands.cs
index 89d6355203..9809a5845f 100644
--- a/Content.Server/Body/BodyCommands.cs
+++ b/Content.Server/Body/BodyCommands.cs
@@ -32,7 +32,7 @@ namespace Content.Server.Body
return;
}
- if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent body))
+ if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent? body))
{
var random = IoCManager.Resolve();
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
@@ -72,7 +72,7 @@ namespace Content.Server.Body
return;
}
- if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent body))
+ if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent? body))
{
var random = IoCManager.Resolve();
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
@@ -119,7 +119,7 @@ namespace Content.Server.Body
return;
}
- if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent body))
+ if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent? body))
{
var random = IoCManager.Resolve();
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
diff --git a/Content.Server/Body/Mechanisms/Behaviors/HeartBehavior.cs b/Content.Server/Body/Mechanisms/Behaviors/HeartBehavior.cs
index 51366583bd..2229357bfc 100644
--- a/Content.Server/Body/Mechanisms/Behaviors/HeartBehavior.cs
+++ b/Content.Server/Body/Mechanisms/Behaviors/HeartBehavior.cs
@@ -19,7 +19,7 @@ namespace Content.Server.Body.Mechanisms.Behaviors
base.PreMetabolism(frameTime);
if (Mechanism.Body == null ||
- !Mechanism.Body.Owner.TryGetComponent(out BloodstreamComponent bloodstream))
+ !Mechanism.Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
{
return;
}
diff --git a/Content.Server/Body/Mechanisms/Behaviors/LungBehavior.cs b/Content.Server/Body/Mechanisms/Behaviors/LungBehavior.cs
index 675cc37e76..bc2591c387 100644
--- a/Content.Server/Body/Mechanisms/Behaviors/LungBehavior.cs
+++ b/Content.Server/Body/Mechanisms/Behaviors/LungBehavior.cs
@@ -16,7 +16,7 @@ namespace Content.Server.Body.Mechanisms.Behaviors
base.PreMetabolism(frameTime);
if (Mechanism.Body == null ||
- !Mechanism.Body.Owner.TryGetComponent(out LungComponent lung))
+ !Mechanism.Body.Owner.TryGetComponent(out LungComponent? lung))
{
return;
}
diff --git a/Content.Server/Body/Mechanisms/Behaviors/StomachBehavior.cs b/Content.Server/Body/Mechanisms/Behaviors/StomachBehavior.cs
index ae1e17b49b..ce6a2bcf43 100644
--- a/Content.Server/Body/Mechanisms/Behaviors/StomachBehavior.cs
+++ b/Content.Server/Body/Mechanisms/Behaviors/StomachBehavior.cs
@@ -18,7 +18,7 @@ namespace Content.Server.Body.Mechanisms.Behaviors
base.PreMetabolism(frameTime);
if (Mechanism.Body == null ||
- !Mechanism.Body.Owner.TryGetComponent(out StomachComponent stomach))
+ !Mechanism.Body.Owner.TryGetComponent(out StomachComponent? stomach))
{
return;
}
diff --git a/Content.Server/Chat/ChatCommands.cs b/Content.Server/Chat/ChatCommands.cs
index 777bba1fe9..c5f194d429 100644
--- a/Content.Server/Chat/ChatCommands.cs
+++ b/Content.Server/Chat/ChatCommands.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Linq;
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage;
@@ -32,9 +32,11 @@ namespace Content.Server.Chat
if (args.Length < 1)
return;
- var chat = IoCManager.Resolve();
+ var message = string.Join(" ", args).Trim();
+ if (string.IsNullOrEmpty(message))
+ return;
- var message = string.Join(" ", args);
+ var chat = IoCManager.Resolve();
if (player.AttachedEntity.HasComponent())
chat.SendDeadChat(player, message);
@@ -61,9 +63,11 @@ namespace Content.Server.Chat
if (args.Length < 1)
return;
- var chat = IoCManager.Resolve();
+ var action = string.Join(" ", args).Trim();
+ if (string.IsNullOrEmpty(action))
+ return;
- var action = string.Join(" ", args);
+ var chat = IoCManager.Resolve();
var mindComponent = player.ContentData().Mind;
chat.EntityMe(mindComponent.OwnedEntity, action);
@@ -78,8 +82,15 @@ namespace Content.Server.Chat
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
{
+ if (args.Length < 1)
+ return;
+
+ var message = string.Join(" ", args).Trim();
+ if (string.IsNullOrEmpty(message))
+ return;
+
var chat = IoCManager.Resolve();
- chat.SendOOC(player, string.Join(" ", args));
+ chat.SendOOC(player, message);
}
}
@@ -91,8 +102,15 @@ namespace Content.Server.Chat
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
{
+ if (args.Length < 1)
+ return;
+
+ var message = string.Join(" ", args).Trim();
+ if (string.IsNullOrEmpty(message))
+ return;
+
var chat = IoCManager.Resolve();
- chat.SendAdminChat(player, string.Join(" ", args));
+ chat.SendAdminChat(player, message);
}
}
diff --git a/Content.Server/GameObjects/Components/Access/AccessReaderComponent.cs b/Content.Server/GameObjects/Components/Access/AccessReaderComponent.cs
index 4ebfcd20bd..c861074d34 100644
--- a/Content.Server/GameObjects/Components/Access/AccessReaderComponent.cs
+++ b/Content.Server/GameObjects/Components/Access/AccessReaderComponent.cs
@@ -71,16 +71,16 @@ namespace Content.Server.GameObjects.Components.Access
public static ICollection FindAccessTags(IEntity entity)
{
- if (entity.TryGetComponent(out IAccess accessComponent))
+ if (entity.TryGetComponent(out IAccess? accessComponent))
{
return accessComponent.Tags;
}
- if (entity.TryGetComponent(out IHandsComponent handsComponent))
+ if (entity.TryGetComponent(out IHandsComponent? handsComponent))
{
var activeHandEntity = handsComponent.GetActiveHand?.Owner;
if (activeHandEntity != null &&
- activeHandEntity.TryGetComponent(out IAccess handAccessComponent))
+ activeHandEntity.TryGetComponent(out IAccess? handAccessComponent))
{
return handAccessComponent.Tags;
}
@@ -90,11 +90,11 @@ namespace Content.Server.GameObjects.Components.Access
return Array.Empty();
}
- if (entity.TryGetComponent(out InventoryComponent inventoryComponent))
+ if (entity.TryGetComponent(out InventoryComponent? inventoryComponent))
{
if (inventoryComponent.HasSlot(EquipmentSlotDefines.Slots.IDCARD) &&
inventoryComponent.TryGetSlotItem(EquipmentSlotDefines.Slots.IDCARD, out ItemComponent item) &&
- item.Owner.TryGetComponent(out IAccess idAccessComponent)
+ item.Owner.TryGetComponent(out IAccess? idAccessComponent)
)
{
return idAccessComponent.Tags;
diff --git a/Content.Server/GameObjects/Components/AnchorableComponent.cs b/Content.Server/GameObjects/Components/AnchorableComponent.cs
index 9837ac04cb..224ce59886 100644
--- a/Content.Server/GameObjects/Components/AnchorableComponent.cs
+++ b/Content.Server/GameObjects/Components/AnchorableComponent.cs
@@ -34,7 +34,7 @@ namespace Content.Server.GameObjects.Components
if (!force)
{
if (utilizing == null ||
- !utilizing.TryGetComponent(out ToolComponent tool) ||
+ !utilizing.TryGetComponent(out ToolComponent? tool) ||
!(await tool.UseTool(user, Owner, 0.5f, ToolQuality.Anchoring)))
{
return false;
@@ -93,7 +93,7 @@ namespace Content.Server.GameObjects.Components
/// true if toggled, false otherwise
private async Task TryToggleAnchor(IEntity user, IEntity? utilizing = null, bool force = false)
{
- if (!Owner.TryGetComponent(out ICollidableComponent collidable))
+ if (!Owner.TryGetComponent(out ICollidableComponent? collidable))
{
return false;
}
diff --git a/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs b/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs
index 066bc0940a..2a6a2be135 100644
--- a/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs
+++ b/Content.Server/GameObjects/Components/Atmos/GasAnalyzerComponent.cs
@@ -116,7 +116,7 @@ namespace Content.Server.GameObjects.Components.Atmos
{
_pressureDanger = GasAnalyzerDanger.Nominal;
}
-
+
Dirty();
_timeSinceSync = 0f;
}
@@ -131,11 +131,11 @@ namespace Content.Server.GameObjects.Components.Atmos
if (session.AttachedEntity == null)
return;
- if (!session.AttachedEntity.TryGetComponent(out IHandsComponent handsComponent))
+ if (!session.AttachedEntity.TryGetComponent(out IHandsComponent? handsComponent))
return;
var activeHandEntity = handsComponent?.GetActiveHand?.Owner;
- if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent gasAnalyzer))
+ if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent? gasAnalyzer))
{
return;
}
@@ -147,7 +147,7 @@ namespace Content.Server.GameObjects.Components.Atmos
// Check if position is out of range => don't update
if (!_position.Value.InRange(_mapManager, pos, SharedInteractionSystem.InteractionRange))
return;
-
+
pos = _position.Value;
}
@@ -195,7 +195,7 @@ namespace Content.Server.GameObjects.Components.Atmos
return;
}
- if (!player.TryGetComponent(out IHandsComponent handsComponent))
+ if (!player.TryGetComponent(out IHandsComponent? handsComponent))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, player,
Loc.GetString("You have no hands."));
@@ -203,7 +203,7 @@ namespace Content.Server.GameObjects.Components.Atmos
}
var activeHandEntity = handsComponent.GetActiveHand?.Owner;
- if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent gasAnalyzer))
+ if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent? gasAnalyzer))
{
_notifyManager.PopupMessage(serverMsg.Session.AttachedEntity,
serverMsg.Session.AttachedEntity,
@@ -225,7 +225,7 @@ namespace Content.Server.GameObjects.Components.Atmos
return;
}
- if (eventArgs.User.TryGetComponent(out IActorComponent actor))
+ if (eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
OpenInterface(actor.playerSession, eventArgs.ClickLocation);
//TODO: show other sprite when ui open?
@@ -236,7 +236,7 @@ namespace Content.Server.GameObjects.Components.Atmos
void IDropped.Dropped(DroppedEventArgs eventArgs)
{
- if (eventArgs.User.TryGetComponent(out IActorComponent actor))
+ if (eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
CloseInterface(actor.playerSession);
//TODO: if other sprite is shown, change again
@@ -245,7 +245,7 @@ namespace Content.Server.GameObjects.Components.Atmos
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
{
- if (eventArgs.User.TryGetComponent(out IActorComponent actor))
+ if (eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
OpenInterface(actor.playerSession);
//TODO: show other sprite when ui open?
diff --git a/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs b/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs
index ac2f427299..5c14e4da0c 100644
--- a/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs
+++ b/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs
@@ -272,7 +272,7 @@ namespace Content.Server.GameObjects.Components.Body
private void CalculateSpeed()
{
- if (!Owner.TryGetComponent(out MovementSpeedModifierComponent playerMover))
+ if (!Owner.TryGetComponent(out MovementSpeedModifierComponent? playerMover))
{
return;
}
diff --git a/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs b/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs
index 877a0c2727..fcb1144b47 100644
--- a/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs
+++ b/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs
@@ -112,7 +112,7 @@ namespace Content.Server.GameObjects.Components.Buckle
///
private void BuckleStatus()
{
- if (Owner.TryGetComponent(out ServerStatusEffectsComponent status))
+ if (Owner.TryGetComponent(out ServerStatusEffectsComponent? status))
{
status.ChangeStatusEffectIcon(StatusEffect.Buckled,
Buckled
@@ -291,7 +291,7 @@ namespace Content.Server.GameObjects.Components.Buckle
return false;
}
- if (Owner.TryGetComponent(out AppearanceComponent appearance))
+ if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(BuckleVisuals.Buckled, true);
}
@@ -359,12 +359,12 @@ namespace Content.Server.GameObjects.Components.Buckle
Owner.Transform.WorldRotation = oldBuckledTo.Owner.Transform.WorldRotation;
}
- if (Owner.TryGetComponent(out AppearanceComponent appearance))
+ if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(BuckleVisuals.Buckled, false);
}
- if (Owner.TryGetComponent(out StunnableComponent stunnable) && stunnable.KnockedDown)
+ if (Owner.TryGetComponent(out StunnableComponent? stunnable) && stunnable.KnockedDown)
{
StandingStateHelper.Down(Owner);
}
@@ -373,14 +373,14 @@ namespace Content.Server.GameObjects.Components.Buckle
StandingStateHelper.Standing(Owner);
}
- if (Owner.TryGetComponent(out MobStateManagerComponent stateManager))
+ if (Owner.TryGetComponent(out MobStateManagerComponent? stateManager))
{
stateManager.CurrentMobState.EnterState(Owner);
}
BuckleStatus();
- if (oldBuckledTo.Owner.TryGetComponent(out StrapComponent strap))
+ if (oldBuckledTo.Owner.TryGetComponent(out StrapComponent? strap))
{
strap.Remove(this);
_entitySystem.GetEntitySystem()
@@ -535,7 +535,7 @@ namespace Content.Server.GameObjects.Components.Buckle
_entityManager.EventBus.UnsubscribeEvents(this);
if (BuckledTo != null &&
- BuckledTo.Owner.TryGetComponent(out StrapComponent strap))
+ BuckledTo.Owner.TryGetComponent(out StrapComponent? strap))
{
strap.Remove(this);
}
@@ -552,7 +552,7 @@ namespace Content.Server.GameObjects.Components.Buckle
if (BuckledTo != null &&
Owner.Transform.WorldRotation.GetCardinalDir() == Direction.North &&
- BuckledTo.Owner.TryGetComponent(out SpriteComponent strapSprite))
+ BuckledTo.Owner.TryGetComponent(out SpriteComponent? strapSprite))
{
drawDepth = strapSprite.DrawDepth - 1;
}
diff --git a/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs b/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs
index 9e263f5795..ca6445e5e6 100644
--- a/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs
+++ b/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs
@@ -159,7 +159,7 @@ namespace Content.Server.GameObjects.Components.Cargo
void IActivate.Activate(ActivateEventArgs eventArgs)
{
- if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
+ if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
diff --git a/Content.Server/GameObjects/Components/Conveyor/ConveyorComponent.cs b/Content.Server/GameObjects/Components/Conveyor/ConveyorComponent.cs
index ab636c4fe4..689e3f8bea 100644
--- a/Content.Server/GameObjects/Components/Conveyor/ConveyorComponent.cs
+++ b/Content.Server/GameObjects/Components/Conveyor/ConveyorComponent.cs
@@ -60,7 +60,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
{
_state = value;
- if (!Owner.TryGetComponent(out AppearanceComponent appearance))
+ if (!Owner.TryGetComponent(out AppearanceComponent? appearance))
{
return;
}
@@ -93,7 +93,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
return false;
}
- if (Owner.TryGetComponent(out PowerReceiverComponent receiver) &&
+ if (Owner.TryGetComponent(out PowerReceiverComponent? receiver) &&
!receiver.Powered)
{
return false;
@@ -114,7 +114,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
return false;
}
- if (!entity.TryGetComponent(out ICollidableComponent collidable) ||
+ if (!entity.TryGetComponent(out ICollidableComponent? collidable) ||
collidable.Anchored)
{
return false;
@@ -155,7 +155,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
continue;
}
- if (entity.TryGetComponent(out ICollidableComponent collidable))
+ if (entity.TryGetComponent(out ICollidableComponent? collidable))
{
var controller = collidable.EnsureController();
controller.Move(direction, _speed * frameTime);
@@ -225,7 +225,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
continue;
}
- if (!@switch.TryGetComponent(out ConveyorSwitchComponent component))
+ if (!@switch.TryGetComponent(out ConveyorSwitchComponent? component))
{
continue;
}
@@ -247,13 +247,13 @@ namespace Content.Server.GameObjects.Components.Conveyor
async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
- if (eventArgs.Using.TryGetComponent(out ConveyorSwitchComponent conveyorSwitch))
+ if (eventArgs.Using.TryGetComponent(out ConveyorSwitchComponent? conveyorSwitch))
{
conveyorSwitch.Connect(this, eventArgs.User);
return true;
}
- if (eventArgs.Using.TryGetComponent(out ToolComponent tool))
+ if (eventArgs.Using.TryGetComponent(out ToolComponent? tool))
{
return await ToolUsed(eventArgs.User, tool);
}
diff --git a/Content.Server/GameObjects/Components/Conveyor/ConveyorSwitchComponent.cs b/Content.Server/GameObjects/Components/Conveyor/ConveyorSwitchComponent.cs
index 3c06575fd2..ae5e2c3aa1 100644
--- a/Content.Server/GameObjects/Components/Conveyor/ConveyorSwitchComponent.cs
+++ b/Content.Server/GameObjects/Components/Conveyor/ConveyorSwitchComponent.cs
@@ -34,7 +34,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
{
_state = value;
- if (Owner.TryGetComponent(out AppearanceComponent appearance))
+ if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(ConveyorVisuals.State, value);
}
@@ -145,7 +145,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
continue;
}
- if (!conveyor.TryGetComponent(out ConveyorComponent component))
+ if (!conveyor.TryGetComponent(out ConveyorComponent? component))
{
continue;
}
@@ -172,7 +172,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
continue;
}
- if (!@switch.TryGetComponent(out ConveyorSwitchComponent component))
+ if (!@switch.TryGetComponent(out ConveyorSwitchComponent? component))
{
continue;
}
@@ -196,13 +196,13 @@ namespace Content.Server.GameObjects.Components.Conveyor
async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
- if (eventArgs.Using.TryGetComponent(out ConveyorComponent conveyor))
+ if (eventArgs.Using.TryGetComponent(out ConveyorComponent? conveyor))
{
Connect(conveyor, eventArgs.User);
return true;
}
- if (eventArgs.Using.TryGetComponent(out ConveyorSwitchComponent otherSwitch))
+ if (eventArgs.Using.TryGetComponent(out ConveyorSwitchComponent? otherSwitch))
{
SyncWith(otherSwitch, eventArgs.User);
return true;
diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalCommands.cs b/Content.Server/GameObjects/Components/Disposal/DisposalCommands.cs
index 5401182b5b..238b31c737 100644
--- a/Content.Server/GameObjects/Components/Disposal/DisposalCommands.cs
+++ b/Content.Server/GameObjects/Components/Disposal/DisposalCommands.cs
@@ -41,7 +41,7 @@ namespace Content.Server.GameObjects.Components.Disposal
return;
}
- if (!entity.TryGetComponent(out IDisposalTubeComponent tube))
+ if (!entity.TryGetComponent(out IDisposalTubeComponent? tube))
{
shell.SendText(player, Loc.GetString("Entity with uid {0} doesn't have a {1} component", id, nameof(IDisposalTubeComponent)));
return;
diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalHolderComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalHolderComponent.cs
index ee39a57d76..838cf933d2 100644
--- a/Content.Server/GameObjects/Components/Disposal/DisposalHolderComponent.cs
+++ b/Content.Server/GameObjects/Components/Disposal/DisposalHolderComponent.cs
@@ -1,10 +1,12 @@
#nullable enable
+using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Shared.GameObjects.Components.Body;
using Robust.Server.GameObjects.Components.Container;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
@@ -41,6 +43,12 @@ namespace Content.Server.GameObjects.Components.Disposal
[ViewVariables]
public IDisposalTubeComponent? NextTube { get; set; }
+ ///
+ /// A list of tags attached to the content, used for sorting
+ ///
+ [ViewVariables]
+ public HashSet Tags { get; set; } = new HashSet();
+
private bool CanInsert(IEntity entity)
{
if (!_contents.CanInsert(entity))
@@ -48,6 +56,12 @@ namespace Content.Server.GameObjects.Components.Disposal
return false;
}
+ if (!entity.TryGetComponent(out ICollidableComponent? collidable) ||
+ !collidable.CanCollide)
+ {
+ return false;
+ }
+
return entity.HasComponent() ||
entity.HasComponent();
}
@@ -59,6 +73,11 @@ namespace Content.Server.GameObjects.Components.Disposal
return false;
}
+ if (entity.TryGetComponent(out ICollidableComponent? collidable))
+ {
+ collidable.CanCollide = false;
+ }
+
return true;
}
@@ -86,6 +105,11 @@ namespace Content.Server.GameObjects.Components.Disposal
foreach (var entity in _contents.ContainedEntities.ToArray())
{
+ if (entity.TryGetComponent(out ICollidableComponent? collidable))
+ {
+ collidable.CanCollide = true;
+ }
+
_contents.ForceRemove(entity);
if (entity.Transform.Parent == Owner.Transform)
diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalRouterComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalRouterComponent.cs
new file mode 100644
index 0000000000..1d3662a85c
--- /dev/null
+++ b/Content.Server/GameObjects/Components/Disposal/DisposalRouterComponent.cs
@@ -0,0 +1,179 @@
+using Content.Server.Interfaces;
+using Content.Server.Interfaces.GameObjects.Components.Items;
+using Content.Shared.GameObjects.EntitySystems;
+using Content.Shared.Interfaces.GameObjects.Components;
+using Robust.Server.GameObjects.Components.UserInterface;
+using Robust.Server.GameObjects.EntitySystems;
+using Robust.Server.Interfaces.GameObjects;
+using Robust.Shared.Audio;
+using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Components;
+using Robust.Shared.GameObjects.Systems;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Localization;
+using Robust.Shared.Maths;
+using Robust.Shared.ViewVariables;
+using System;
+using System.Collections.Generic;
+using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalRouterComponent;
+
+namespace Content.Server.GameObjects.Components.Disposal
+{
+ [RegisterComponent]
+ [ComponentReference(typeof(IActivate))]
+ [ComponentReference(typeof(IDisposalTubeComponent))]
+ public class DisposalRouterComponent : DisposalJunctionComponent, IActivate
+ {
+#pragma warning disable 649
+ [Dependency] private readonly IServerNotifyManager _notifyManager;
+#pragma warning restore 649
+ public override string Name => "DisposalRouter";
+
+ [ViewVariables]
+ private BoundUserInterface _userInterface;
+
+ [ViewVariables]
+ private HashSet _tags;
+
+ [ViewVariables]
+ public bool Anchored =>
+ !Owner.TryGetComponent(out CollidableComponent collidable) ||
+ collidable.Anchored;
+
+ public override Direction NextDirection(DisposalHolderComponent holder)
+ {
+ var directions = ConnectableDirections();
+
+ if (holder.Tags.Overlaps(_tags))
+ {
+ return directions[1];
+ }
+
+ return Owner.Transform.LocalRotation.GetDir();
+ }
+
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ _userInterface = Owner.GetComponent()
+ .GetBoundUserInterface(DisposalRouterUiKey.Key);
+ _userInterface.OnReceiveMessage += OnUiReceiveMessage;
+
+ _tags = new HashSet();
+
+ UpdateUserInterface();
+ }
+
+ ///
+ /// Handles ui messages from the client. For things such as button presses
+ /// which interact with the world and require server action.
+ ///
+ /// A user interface message from the client.
+ private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
+ {
+ var msg = (UiActionMessage) obj.Message;
+
+ if (!PlayerCanUseDisposalTagger(obj.Session.AttachedEntity))
+ return;
+
+ //Check for correct message and ignore maleformed strings
+ if (msg.Action == UiAction.Ok && TagRegex.IsMatch(msg.Tags))
+ {
+ _tags.Clear();
+ foreach (var tag in msg.Tags.Split(',', StringSplitOptions.RemoveEmptyEntries))
+ {
+ _tags.Add(tag.Trim());
+ ClickSound();
+ }
+ }
+ }
+
+ ///
+ /// Checks whether the player entity is able to use the configuration interface of the pipe tagger.
+ ///
+ /// The player entity.
+ /// Returns true if the entity can use the configuration interface, and false if it cannot.
+ private bool PlayerCanUseDisposalTagger(IEntity playerEntity)
+ {
+ //Need player entity to check if they are still able to use the configuration interface
+ if (playerEntity == null)
+ return false;
+ if (!Anchored)
+ return false;
+ //Check if player can interact in their current state
+ if (!ActionBlockerSystem.CanInteract(playerEntity) || !ActionBlockerSystem.CanUse(playerEntity))
+ return false;
+
+ return true;
+ }
+
+ ///
+ /// Gets component data to be used to update the user interface client-side.
+ ///
+ /// Returns a
+ private DisposalRouterUserInterfaceState GetUserInterfaceState()
+ {
+ if(_tags == null || _tags.Count <= 0)
+ {
+ return new DisposalRouterUserInterfaceState("");
+ }
+
+ var taglist = new System.Text.StringBuilder();
+
+ foreach (var tag in _tags)
+ {
+ taglist.Append(tag);
+ taglist.Append(", ");
+ }
+
+ taglist.Remove(taglist.Length - 2, 2);
+
+ return new DisposalRouterUserInterfaceState(taglist.ToString());
+ }
+
+ private void UpdateUserInterface()
+ {
+ var state = GetUserInterfaceState();
+ _userInterface.SetState(state);
+ }
+
+ private void ClickSound()
+ {
+ EntitySystem.Get().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
+ }
+
+ ///
+ /// Called when you click the owner entity with an empty hand. Opens the UI client-side if possible.
+ ///
+ /// Data relevant to the event such as the actor which triggered it.
+ void IActivate.Activate(ActivateEventArgs args)
+ {
+ if (!args.User.TryGetComponent(out IActorComponent actor))
+ {
+ return;
+ }
+
+ if (!args.User.TryGetComponent(out IHandsComponent hands))
+ {
+ _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
+ Loc.GetString("You have no hands."));
+ return;
+ }
+
+ var activeHandEntity = hands.GetActiveHand?.Owner;
+ if (activeHandEntity == null)
+ {
+ UpdateUserInterface();
+ _userInterface.Open(actor.playerSession);
+ }
+ }
+
+ public override void OnRemove()
+ {
+ _userInterface.CloseAll();
+ base.OnRemove();
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalTaggerComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalTaggerComponent.cs
new file mode 100644
index 0000000000..3c1ab7bf81
--- /dev/null
+++ b/Content.Server/GameObjects/Components/Disposal/DisposalTaggerComponent.cs
@@ -0,0 +1,150 @@
+using Content.Server.Interfaces;
+using Content.Server.Interfaces.GameObjects.Components.Items;
+using Content.Shared.GameObjects.EntitySystems;
+using Content.Shared.Interfaces.GameObjects.Components;
+using Robust.Server.GameObjects.Components.UserInterface;
+using Robust.Server.GameObjects.EntitySystems;
+using Robust.Server.Interfaces.GameObjects;
+using Robust.Shared.Audio;
+using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Components;
+using Robust.Shared.GameObjects.Systems;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Localization;
+using Robust.Shared.Maths;
+using Robust.Shared.ViewVariables;
+using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalTaggerComponent;
+
+namespace Content.Server.GameObjects.Components.Disposal
+{
+ [RegisterComponent]
+ [ComponentReference(typeof(IActivate))]
+ [ComponentReference(typeof(IDisposalTubeComponent))]
+ public class DisposalTaggerComponent : DisposalTransitComponent, IActivate
+ {
+#pragma warning disable 649
+ [Dependency] private readonly IServerNotifyManager _notifyManager;
+#pragma warning restore 649
+ public override string Name => "DisposalTagger";
+
+ [ViewVariables]
+ private BoundUserInterface _userInterface;
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ private string _tag = "";
+
+ [ViewVariables]
+ public bool Anchored =>
+ !Owner.TryGetComponent(out CollidableComponent collidable) ||
+ collidable.Anchored;
+
+ public override Direction NextDirection(DisposalHolderComponent holder)
+ {
+ holder.Tags.Add(_tag);
+ return base.NextDirection(holder);
+ }
+
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ _userInterface = Owner.GetComponent()
+ .GetBoundUserInterface(DisposalTaggerUiKey.Key);
+ _userInterface.OnReceiveMessage += OnUiReceiveMessage;
+
+ UpdateUserInterface();
+ }
+
+ ///
+ /// Handles ui messages from the client. For things such as button presses
+ /// which interact with the world and require server action.
+ ///
+ /// A user interface message from the client.
+ private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
+ {
+ var msg = (UiActionMessage) obj.Message;
+
+ if (!PlayerCanUseDisposalTagger(obj.Session.AttachedEntity))
+ return;
+
+ //Check for correct message and ignore maleformed strings
+ if (msg.Action == UiAction.Ok && TagRegex.IsMatch(msg.Tag))
+ {
+ _tag = msg.Tag;
+ ClickSound();
+ }
+ }
+
+ ///
+ /// Checks whether the player entity is able to use the configuration interface of the pipe tagger.
+ ///
+ /// The player entity.
+ /// Returns true if the entity can use the configuration interface, and false if it cannot.
+ private bool PlayerCanUseDisposalTagger(IEntity playerEntity)
+ {
+ //Need player entity to check if they are still able to use the configuration interface
+ if (playerEntity == null)
+ return false;
+ if (!Anchored)
+ return false;
+ //Check if player can interact in their current state
+ if (!ActionBlockerSystem.CanInteract(playerEntity) || !ActionBlockerSystem.CanUse(playerEntity))
+ return false;
+
+ return true;
+ }
+
+ ///
+ /// Gets component data to be used to update the user interface client-side.
+ ///
+ /// Returns a
+ private DisposalTaggerUserInterfaceState GetUserInterfaceState()
+ {
+ return new DisposalTaggerUserInterfaceState(_tag);
+ }
+
+ private void UpdateUserInterface()
+ {
+ var state = GetUserInterfaceState();
+ _userInterface.SetState(state);
+ }
+
+ private void ClickSound()
+ {
+ EntitySystem.Get().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
+ }
+
+ ///
+ /// Called when you click the owner entity with an empty hand. Opens the UI client-side if possible.
+ ///
+ /// Data relevant to the event such as the actor which triggered it.
+ void IActivate.Activate(ActivateEventArgs args)
+ {
+ if (!args.User.TryGetComponent(out IActorComponent actor))
+ {
+ return;
+ }
+
+ if (!args.User.TryGetComponent(out IHandsComponent hands))
+ {
+ _notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
+ Loc.GetString("You have no hands."));
+ return;
+ }
+
+ var activeHandEntity = hands.GetActiveHand?.Owner;
+ if (activeHandEntity == null)
+ {
+ UpdateUserInterface();
+ _userInterface.Open(actor.playerSession);
+ }
+ }
+
+ public override void OnRemove()
+ {
+ base.OnRemove();
+ _userInterface.CloseAll();
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs
index 37b2f2275e..94dccce0a7 100644
--- a/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs
+++ b/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs
@@ -44,7 +44,7 @@ namespace Content.Server.GameObjects.Components.Disposal
[ViewVariables]
private bool Anchored =>
- !Owner.TryGetComponent(out CollidableComponent collidable) ||
+ !Owner.TryGetComponent(out CollidableComponent? collidable) ||
collidable.Anchored;
///
@@ -71,7 +71,7 @@ namespace Content.Server.GameObjects.Components.Disposal
var snapGrid = Owner.GetComponent();
var tube = snapGrid
.GetInDir(nextDirection)
- .Select(x => x.TryGetComponent(out IDisposalTubeComponent c) ? c : null)
+ .Select(x => x.TryGetComponent(out IDisposalTubeComponent? c) ? c : null)
.FirstOrDefault(x => x != null && x != this);
if (tube == null)
@@ -153,7 +153,7 @@ namespace Content.Server.GameObjects.Components.Disposal
foreach (var entity in Contents.ContainedEntities.ToArray())
{
- if (!entity.TryGetComponent(out DisposalHolderComponent holder))
+ if (!entity.TryGetComponent(out DisposalHolderComponent? holder))
{
continue;
}
@@ -171,7 +171,7 @@ namespace Content.Server.GameObjects.Components.Disposal
private void UpdateVisualState()
{
- if (!Owner.TryGetComponent(out AppearanceComponent appearance))
+ if (!Owner.TryGetComponent(out AppearanceComponent? appearance))
{
return;
}
@@ -187,7 +187,7 @@ namespace Content.Server.GameObjects.Components.Disposal
private void AnchoredChanged()
{
- if (!Owner.TryGetComponent(out CollidableComponent collidable))
+ if (!Owner.TryGetComponent(out CollidableComponent? collidable))
{
return;
}
diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs
index 399ba6ffbd..2e420ef91e 100644
--- a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs
+++ b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs
@@ -86,12 +86,12 @@ namespace Content.Server.GameObjects.Components.Disposal
[ViewVariables]
public bool Powered =>
- !Owner.TryGetComponent(out PowerReceiverComponent receiver) ||
+ !Owner.TryGetComponent(out PowerReceiverComponent? receiver) ||
receiver.Powered;
[ViewVariables]
public bool Anchored =>
- !Owner.TryGetComponent(out CollidableComponent collidable) ||
+ !Owner.TryGetComponent(out CollidableComponent? collidable) ||
collidable.Anchored;
[ViewVariables]
@@ -122,6 +122,12 @@ namespace Content.Server.GameObjects.Components.Disposal
return false;
}
+ if (!entity.TryGetComponent(out ICollidableComponent? collidable) ||
+ !collidable.CanCollide)
+ {
+ return false;
+ }
+
if (!entity.HasComponent() &&
!entity.HasComponent())
{
@@ -153,7 +159,7 @@ namespace Content.Server.GameObjects.Components.Disposal
{
TryQueueEngage();
- if (entity.TryGetComponent(out IActorComponent actor))
+ if (entity.TryGetComponent(out IActorComponent? actor))
{
_userInterface.Close(actor.playerSession);
}
@@ -175,7 +181,7 @@ namespace Content.Server.GameObjects.Components.Disposal
private bool TryDrop(IEntity user, IEntity entity)
{
- if (!user.TryGetComponent(out HandsComponent hands))
+ if (!user.TryGetComponent(out HandsComponent? hands))
{
return false;
}
@@ -267,7 +273,7 @@ namespace Content.Server.GameObjects.Components.Disposal
private void TogglePower()
{
- if (!Owner.TryGetComponent(out PowerReceiverComponent receiver))
+ if (!Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
return;
}
@@ -346,7 +352,7 @@ namespace Content.Server.GameObjects.Components.Disposal
private void UpdateVisualState(bool flush)
{
- if (!Owner.TryGetComponent(out AppearanceComponent appearance))
+ if (!Owner.TryGetComponent(out AppearanceComponent? appearance))
{
return;
}
@@ -482,7 +488,7 @@ namespace Content.Server.GameObjects.Components.Disposal
var collidable = Owner.EnsureComponent();
collidable.AnchoredChanged += UpdateVisualState;
- if (Owner.TryGetComponent(out PowerReceiverComponent receiver))
+ if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
receiver.OnPowerStateChanged += PowerStateChanged;
}
@@ -492,12 +498,12 @@ namespace Content.Server.GameObjects.Components.Disposal
public override void OnRemove()
{
- if (Owner.TryGetComponent(out ICollidableComponent collidable))
+ if (Owner.TryGetComponent(out ICollidableComponent? collidable))
{
collidable.AnchoredChanged -= UpdateVisualState;
}
- if (Owner.TryGetComponent(out PowerReceiverComponent receiver))
+ if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
receiver.OnPowerStateChanged -= PowerStateChanged;
}
@@ -524,7 +530,7 @@ namespace Content.Server.GameObjects.Components.Disposal
switch (message)
{
case RelayMovementEntityMessage msg:
- if (!msg.Entity.TryGetComponent(out HandsComponent hands) ||
+ if (!msg.Entity.TryGetComponent(out HandsComponent? hands) ||
hands.Count == 0 ||
_gameTiming.CurTime < _lastExitAttempt + ExitAttemptDelay)
{
@@ -553,7 +559,7 @@ namespace Content.Server.GameObjects.Components.Disposal
return false;
}
- if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
+ if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
return false;
}
diff --git a/Content.Server/GameObjects/Components/DoAfterComponent.cs b/Content.Server/GameObjects/Components/DoAfterComponent.cs
index d77fc080d0..2013028ae6 100644
--- a/Content.Server/GameObjects/Components/DoAfterComponent.cs
+++ b/Content.Server/GameObjects/Components/DoAfterComponent.cs
@@ -60,7 +60,7 @@ namespace Content.Server.GameObjects.Components
{
connectedClient = null;
- if (!Owner.TryGetComponent(out IActorComponent actorComponent))
+ if (!Owner.TryGetComponent(out IActorComponent? actorComponent))
{
return false;
}
diff --git a/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs b/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs
index 6f8f94d217..398e9230c9 100644
--- a/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs
+++ b/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs
@@ -418,7 +418,7 @@ namespace Content.Server.GameObjects.Components.Doors
return true;
}
- if (!await tool.UseTool(eventArgs.User, Owner, 3f, ToolQuality.Prying, AirlockCheck)) return false;
+ if (!await tool.UseTool(eventArgs.User, Owner, 0.2f, ToolQuality.Prying, AirlockCheck)) return false;
if (State == DoorState.Closed)
Open();
diff --git a/Content.Server/GameObjects/Components/Fluids/SpillHelper.cs b/Content.Server/GameObjects/Components/Fluids/SpillHelper.cs
index 5b85924b8d..e616a7f5f0 100644
--- a/Content.Server/GameObjects/Components/Fluids/SpillHelper.cs
+++ b/Content.Server/GameObjects/Components/Fluids/SpillHelper.cs
@@ -63,7 +63,7 @@ namespace Content.Server.GameObjects.Components.Fluids
foreach (var spillEntity in entityManager.GetEntitiesAt(spillTileMapGrid.ParentMapId, spillGridCoords.Position))
{
- if (!spillEntity.TryGetComponent(out PuddleComponent puddleComponent))
+ if (!spillEntity.TryGetComponent(out PuddleComponent? puddleComponent))
{
continue;
}
diff --git a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs
index 572e504f53..69fe0be734 100644
--- a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs
+++ b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs
@@ -711,7 +711,7 @@ namespace Content.Server.GameObjects.Components.GUI
Dirty();
- if (!message.Entity.TryGetComponent(out ICollidableComponent collidable))
+ if (!message.Entity.TryGetComponent(out ICollidableComponent? collidable))
{
return;
}
@@ -724,13 +724,13 @@ namespace Content.Server.GameObjects.Components.GUI
private void AddPullingStatuses(IEntity pulled)
{
- if (pulled.TryGetComponent(out ServerStatusEffectsComponent pulledStatus))
+ if (pulled.TryGetComponent(out ServerStatusEffectsComponent? pulledStatus))
{
pulledStatus.ChangeStatusEffectIcon(StatusEffect.Pulled,
"/Textures/Interface/StatusEffects/Pull/pulled.png");
}
- if (Owner.TryGetComponent(out ServerStatusEffectsComponent ownerStatus))
+ if (Owner.TryGetComponent(out ServerStatusEffectsComponent? ownerStatus))
{
ownerStatus.ChangeStatusEffectIcon(StatusEffect.Pulling,
"/Textures/Interface/StatusEffects/Pull/pulling.png");
@@ -739,12 +739,12 @@ namespace Content.Server.GameObjects.Components.GUI
private void RemovePullingStatuses(IEntity pulled)
{
- if (pulled.TryGetComponent(out ServerStatusEffectsComponent pulledStatus))
+ if (pulled.TryGetComponent(out ServerStatusEffectsComponent? pulledStatus))
{
pulledStatus.RemoveStatusEffect(StatusEffect.Pulled);
}
- if (Owner.TryGetComponent(out ServerStatusEffectsComponent ownerStatus))
+ if (Owner.TryGetComponent(out ServerStatusEffectsComponent? ownerStatus))
{
ownerStatus.RemoveStatusEffect(StatusEffect.Pulling);
}
diff --git a/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs b/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs
index 57ab715eb5..a0b86f0625 100644
--- a/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs
+++ b/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs
@@ -10,6 +10,7 @@ using Content.Server.Interfaces.GameObjects;
using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Server.GameObjects.Components.Container;
+using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
@@ -115,8 +116,14 @@ namespace Content.Server.GameObjects.Components.GUI
public override void OnRemove()
{
var slots = _slotContainers.Keys.ToList();
+
foreach (var slot in slots)
{
+ if (TryGetSlotItem(slot, out ItemComponent item))
+ {
+ item.Owner.Delete();
+ }
+
RemoveSlot(slot);
}
@@ -267,17 +274,17 @@ namespace Content.Server.GameObjects.Components.GUI
}
var inventorySlot = _slotContainers[slot];
- var item = inventorySlot.ContainedEntity.GetComponent();
- if (!inventorySlot.Remove(inventorySlot.ContainedEntity))
+ var entity = inventorySlot.ContainedEntity;
+ var item = entity.GetComponent();
+ if (!inventorySlot.Remove(entity))
{
return false;
}
// TODO: The item should be dropped to the container our owner is in, if any.
- var itemTransform = item.Owner.GetComponent();
- itemTransform.GridPosition = Owner.GetComponent().GridPosition;
+ ContainerHelpers.AttachParentToContainerOrGrid(entity.Transform);
- _entitySystemManager.GetEntitySystem().UnequippedInteraction(Owner, item.Owner, slot);
+ _entitySystemManager.GetEntitySystem().UnequippedInteraction(Owner, entity, slot);
OnItemChanged?.Invoke();
@@ -286,6 +293,29 @@ namespace Content.Server.GameObjects.Components.GUI
return true;
}
+ public void ForceUnequip(Slots slot)
+ {
+ var inventorySlot = _slotContainers[slot];
+ var entity = inventorySlot.ContainedEntity;
+ if (entity == null)
+ {
+ return;
+ }
+
+ var item = entity.GetComponent();
+ inventorySlot.ForceRemove(entity);
+
+ var itemTransform = entity.Transform;
+
+ ContainerHelpers.AttachParentToContainerOrGrid(itemTransform);
+
+ _entitySystemManager.GetEntitySystem().UnequippedInteraction(Owner, item.Owner, slot);
+
+ OnItemChanged?.Invoke();
+
+ Dirty();
+ }
+
///
/// Checks whether an item can be dropped from the specified slot.
///
@@ -340,13 +370,11 @@ namespace Content.Server.GameObjects.Components.GUI
throw new InvalidOperationException($"Slow '{slot}' does not exist.");
}
- if (GetSlotItem(slot) != null && !Unequip(slot))
- {
- // TODO: Handle this potential failiure better.
- throw new InvalidOperationException(
- "Unable to remove slot as the contained clothing could not be dropped");
- }
+ ForceUnequip(slot);
+ var container = _slotContainers[slot];
+
+ container.Shutdown();
_slotContainers.Remove(slot);
OnItemChanged?.Invoke();
diff --git a/Content.Server/GameObjects/Components/Interactable/ToolCommands.cs b/Content.Server/GameObjects/Components/Interactable/ToolCommands.cs
index 3008f11d44..9dd07ff951 100644
--- a/Content.Server/GameObjects/Components/Interactable/ToolCommands.cs
+++ b/Content.Server/GameObjects/Components/Interactable/ToolCommands.cs
@@ -106,7 +106,7 @@ namespace Content.Server.GameObjects.Components.Interactable
foreach (var entity in entities)
{
- if (entity.TryGetComponent(out AnchorableComponent anchorable))
+ if (entity.TryGetComponent(out AnchorableComponent? anchorable))
{
anchorable.TryAnchor(player.AttachedEntity, force: true);
}
@@ -151,7 +151,7 @@ namespace Content.Server.GameObjects.Components.Interactable
foreach (var entity in entities)
{
- if (entity.TryGetComponent(out AnchorableComponent anchorable))
+ if (entity.TryGetComponent(out AnchorableComponent? anchorable))
{
anchorable.TryUnAnchor(player.AttachedEntity, force: true);
}
diff --git a/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs
index 96aef95564..8d6f4650ae 100644
--- a/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs
+++ b/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs
@@ -101,13 +101,13 @@ namespace Content.Server.GameObjects.Components.Items.Storage
{
EnsureInitialCalculated();
- if (entity.TryGetComponent(out ServerStorageComponent storage) &&
+ if (entity.TryGetComponent(out ServerStorageComponent? storage) &&
storage._storageCapacityMax >= _storageCapacityMax)
{
return false;
}
- if (entity.TryGetComponent(out StorableComponent store) &&
+ if (entity.TryGetComponent(out StorableComponent? store) &&
store.ObjectSize > _storageCapacityMax - _storageUsed)
{
return false;
@@ -164,7 +164,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
Logger.DebugS(LoggerName, $"Storage (UID {Owner.Uid}) had entity (UID {message.Entity.Uid}) removed from it.");
- if (!message.Entity.TryGetComponent(out StorableComponent storable))
+ if (!message.Entity.TryGetComponent(out StorableComponent? storable))
{
Logger.WarningS(LoggerName, $"Removed entity {message.Entity.Uid} without a StorableComponent from storage {Owner.Uid} at {Owner.Transform.MapPosition}");
@@ -186,7 +186,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
{
EnsureInitialCalculated();
- if (!player.TryGetComponent(out IHandsComponent hands) ||
+ if (!player.TryGetComponent(out IHandsComponent? hands) ||
hands.GetActiveHand == null)
{
return false;
@@ -317,7 +317,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
private void UpdateDoorState()
{
- if (Owner.TryGetComponent(out AppearanceComponent appearance))
+ if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(StorageVisuals.Open, SubscribedSessions.Count != 0);
}
@@ -382,7 +382,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
var item = entity.GetComponent();
if (item == null ||
- !player.TryGetComponent(out HandsComponent hands))
+ !player.TryGetComponent(out HandsComponent? hands))
{
break;
}
@@ -496,7 +496,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
foreach (var entity in storedEntities)
{
- var exActs = entity.GetAllComponents();
+ var exActs = entity.GetAllComponents().ToArray();
foreach (var exAct in exActs)
{
exAct.OnExplosion(eventArgs);
@@ -506,7 +506,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
bool IDragDrop.CanDragDrop(DragDropEventArgs eventArgs)
{
- return eventArgs.Target.TryGetComponent(out PlaceableSurfaceComponent placeable) &&
+ return eventArgs.Target.TryGetComponent(out PlaceableSurfaceComponent? placeable) &&
placeable.IsPlaceable;
}
diff --git a/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs b/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs
index 4e36ffed71..d50513e374 100644
--- a/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs
+++ b/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
+using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.GameObjects.Components.Medical;
using Content.Shared.GameObjects.EntitySystems;
@@ -38,9 +39,14 @@ namespace Content.Server.GameObjects.Components.Medical
_appearance = Owner.GetComponent();
_userInterface = Owner.GetComponent()
.GetBoundUserInterface(MedicalScannerUiKey.Key);
+ _userInterface.OnReceiveMessage += OnUiReceiveMessage;
_bodyContainer = ContainerManagerComponent.Ensure($"{Name}-bodyContainer", Owner);
_powerReceiver = Owner.GetComponent();
+ //TODO: write this so that it checks for a change in power events and acts accordingly.
+ var newState = GetUserInterfaceState();
+ _userInterface.SetState(newState);
+
UpdateUserInterface();
}
@@ -48,7 +54,8 @@ namespace Content.Server.GameObjects.Components.Medical
new MedicalScannerBoundUserInterfaceState(
null,
new Dictionary(),
- new Dictionary());
+ new Dictionary(),
+ false);
private MedicalScannerBoundUserInterfaceState GetUserInterfaceState()
{
@@ -68,7 +75,7 @@ namespace Content.Server.GameObjects.Components.Medical
var classes = new Dictionary(damageable.DamageClasses);
var types = new Dictionary(damageable.DamageTypes);
- return new MedicalScannerBoundUserInterfaceState(body.Uid, classes, types);
+ return new MedicalScannerBoundUserInterfaceState(body.Uid, classes, types, CloningSystem.HasUid(body.Uid));
}
private void UpdateUserInterface()
@@ -92,12 +99,18 @@ namespace Content.Server.GameObjects.Components.Medical
default: throw new ArgumentException(nameof(damageState));
}
}
+
private MedicalScannerStatus GetStatus()
{
- var body = _bodyContainer.ContainedEntity;
- return body == null
- ? MedicalScannerStatus.Open
- : GetStatusFromDamageState(body.GetComponent().CurrentDamageState);
+ if (Powered)
+ {
+ var body = _bodyContainer.ContainedEntity;
+ return body == null
+ ? MedicalScannerStatus.Open
+ : GetStatusFromDamageState(body.GetComponent().CurrentDamageState);
+ }
+
+ return MedicalScannerStatus.Off;
}
private void UpdateAppearance()
@@ -178,14 +191,28 @@ namespace Content.Server.GameObjects.Components.Medical
public void Update(float frameTime)
{
- if (_bodyContainer.ContainedEntity == null)
- {
- // There's no need to update if there's no one inside
- return;
- }
-
UpdateUserInterface();
UpdateAppearance();
}
+
+ private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
+ {
+ if (!(obj.Message is UiButtonPressedMessage message))
+ {
+ return;
+ }
+
+ switch (message.Button)
+ {
+ case UiButton.ScanDNA:
+ if (_bodyContainer.ContainedEntity != null)
+ {
+ CloningSystem.AddToScannedUids(_bodyContainer.ContainedEntity.Uid);
+ }
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
}
}
diff --git a/Content.Server/GameObjects/Components/Metabolism/MetabolismComponent.cs b/Content.Server/GameObjects/Components/Metabolism/MetabolismComponent.cs
index 913d571dd6..10e9e2c592 100644
--- a/Content.Server/GameObjects/Components/Metabolism/MetabolismComponent.cs
+++ b/Content.Server/GameObjects/Components/Metabolism/MetabolismComponent.cs
@@ -213,7 +213,7 @@ namespace Content.Server.GameObjects.Components.Metabolism
if (Suffocating &&
Owner.TryGetComponent(out IDamageableComponent damageable))
{
- damageable.ChangeDamage(DamageClass.Airloss, _suffocationDamage, false);
+ // damageable.ChangeDamage(DamageClass.Airloss, _suffocationDamage, false);
}
}
diff --git a/Content.Server/GameObjects/Components/Mobs/MindComponent.cs b/Content.Server/GameObjects/Components/Mobs/MindComponent.cs
index fd2fed7f5f..b5f9eba7e1 100644
--- a/Content.Server/GameObjects/Components/Mobs/MindComponent.cs
+++ b/Content.Server/GameObjects/Components/Mobs/MindComponent.cs
@@ -79,7 +79,7 @@ namespace Content.Server.GameObjects.Components.Mobs
var visiting = Mind?.VisitingEntity;
if (visiting != null)
{
- if (visiting.TryGetComponent(out GhostComponent ghost))
+ if (visiting.TryGetComponent(out GhostComponent? ghost))
{
ghost.CanReturnToBody = false;
}
diff --git a/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs b/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs
new file mode 100644
index 0000000000..a6bb5ed2fb
--- /dev/null
+++ b/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs
@@ -0,0 +1,235 @@
+
+using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Components;
+using Robust.Shared.GameObjects.Systems;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Localization;
+using Robust.Shared.Serialization;
+using Robust.Shared.ViewVariables;
+using Robust.Server.Interfaces.Player;
+using Content.Server.Interfaces;
+using Content.Shared.GameObjects.EntitySystems;
+using Content.Shared.Interfaces.GameObjects.Components;
+using Content.Shared.GameObjects.Components.Movement;
+using Content.Shared.Interfaces;
+using Content.Server.GameObjects.Components.Body;
+using Content.Server.GameObjects.EntitySystems.DoAfter;
+using Robust.Shared.Maths;
+using System;
+
+namespace Content.Server.GameObjects.Components.Movement
+{
+ [RegisterComponent]
+ [ComponentReference(typeof(IClimbable))]
+ public class ClimbableComponent : SharedClimbableComponent, IDragDropOn
+ {
+#pragma warning disable 649
+ [Dependency] private readonly IServerNotifyManager _notifyManager = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+#pragma warning restore 649
+
+ ///
+ /// The range from which this entity can be climbed.
+ ///
+ [ViewVariables]
+ private float _range;
+
+ ///
+ /// The time it takes to climb onto the entity.
+ ///
+ [ViewVariables]
+ private float _climbDelay;
+
+ private ICollidableComponent _collidableComponent;
+ private DoAfterSystem _doAfterSystem;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _collidableComponent = Owner.GetComponent();
+ _doAfterSystem = EntitySystem.Get();
+ }
+
+ public override void ExposeData(ObjectSerializer serializer)
+ {
+ base.ExposeData(serializer);
+
+ serializer.DataField(ref _range, "range", SharedInteractionSystem.InteractionRange / 1.4f);
+ serializer.DataField(ref _climbDelay, "delay", 0.8f);
+ }
+
+ bool IDragDropOn.CanDragDropOn(DragDropEventArgs eventArgs)
+ {
+ if (!ActionBlockerSystem.CanInteract(eventArgs.User))
+ {
+ _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't do that!"));
+
+ return false;
+ }
+
+ if (eventArgs.User == eventArgs.Dropped) // user is dragging themselves onto a climbable
+ {
+ if (!eventArgs.User.HasComponent())
+ {
+ _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You are incapable of climbing!"));
+
+ return false;
+ }
+
+ var bodyManager = eventArgs.User.GetComponent();
+
+ if (bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Leg).Count == 0 ||
+ bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Foot).Count == 0)
+ {
+ _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You are unable to climb!"));
+
+ return false;
+ }
+
+ var userPosition = eventArgs.User.Transform.MapPosition;
+ var climbablePosition = eventArgs.Target.Transform.MapPosition;
+ var interaction = EntitySystem.Get();
+ bool Ignored(IEntity entity) => (entity == eventArgs.Target || entity == eventArgs.User);
+
+ if (!interaction.InRangeUnobstructed(userPosition, climbablePosition, _range, predicate: Ignored))
+ {
+ _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't reach there!"));
+
+ return false;
+ }
+ }
+ else // user is dragging some other entity onto a climbable
+ {
+ if (eventArgs.Target == null || !eventArgs.Dropped.HasComponent())
+ {
+ _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't do that!"));
+
+ return false;
+ }
+
+ var userPosition = eventArgs.User.Transform.MapPosition;
+ var otherUserPosition = eventArgs.Dropped.Transform.MapPosition;
+ var climbablePosition = eventArgs.Target.Transform.MapPosition;
+ var interaction = EntitySystem.Get();
+ bool Ignored(IEntity entity) => (entity == eventArgs.Target || entity == eventArgs.User || entity == eventArgs.Dropped);
+
+ if (!interaction.InRangeUnobstructed(userPosition, climbablePosition, _range, predicate: Ignored) ||
+ !interaction.InRangeUnobstructed(userPosition, otherUserPosition, _range, predicate: Ignored))
+ {
+ _notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't reach there!"));
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool IDragDropOn.DragDropOn(DragDropEventArgs eventArgs)
+ {
+ if (eventArgs.User == eventArgs.Dropped)
+ {
+ TryClimb(eventArgs.User);
+ }
+ else
+ {
+ TryMoveEntity(eventArgs.User, eventArgs.Dropped);
+ }
+
+ return true;
+ }
+
+ private async void TryMoveEntity(IEntity user, IEntity entityToMove)
+ {
+ var doAfterEventArgs = new DoAfterEventArgs(user, _climbDelay, default, entityToMove)
+ {
+ BreakOnTargetMove = true,
+ BreakOnUserMove = true,
+ BreakOnDamage = true,
+ BreakOnStun = true
+ };
+
+ var result = await _doAfterSystem.DoAfter(doAfterEventArgs);
+
+ if (result != DoAfterStatus.Cancelled && entityToMove.TryGetComponent(out ICollidableComponent body) && body.PhysicsShapes.Count >= 1)
+ {
+ var direction = (Owner.Transform.WorldPosition - entityToMove.Transform.WorldPosition).Normalized;
+ var endPoint = Owner.Transform.WorldPosition;
+
+ var climbMode = entityToMove.GetComponent();
+ climbMode.IsClimbing = true;
+
+ if (MathF.Abs(direction.X) < 0.6f) // user climbed mostly vertically so lets make it a clean straight line
+ {
+ endPoint = new Vector2(entityToMove.Transform.WorldPosition.X, endPoint.Y);
+ }
+ else if (MathF.Abs(direction.Y) < 0.6f) // user climbed mostly horizontally so lets make it a clean straight line
+ {
+ endPoint = new Vector2(endPoint.X, entityToMove.Transform.WorldPosition.Y);
+ }
+
+ climbMode.TryMoveTo(entityToMove.Transform.WorldPosition, endPoint);
+ // we may potentially need additional logic since we're forcing a player onto a climbable
+ // there's also the cases where the user might collide with the person they are forcing onto the climbable that i haven't accounted for
+
+ PopupMessageOtherClientsInRange(user, Loc.GetString("{0:theName} forces {1:theName} onto {2:theName}!", user, entityToMove, Owner), 15);
+ _notifyManager.PopupMessage(user, user, Loc.GetString("You force {0:theName} onto {1:theName}!", entityToMove, Owner));
+ }
+ }
+
+ private async void TryClimb(IEntity user)
+ {
+ var doAfterEventArgs = new DoAfterEventArgs(user, _climbDelay, default, Owner)
+ {
+ BreakOnTargetMove = true,
+ BreakOnUserMove = true,
+ BreakOnDamage = true,
+ BreakOnStun = true
+ };
+
+ var result = await _doAfterSystem.DoAfter(doAfterEventArgs);
+
+ if (result != DoAfterStatus.Cancelled && user.TryGetComponent(out ICollidableComponent body) && body.PhysicsShapes.Count >= 1)
+ {
+ var direction = (Owner.Transform.WorldPosition - user.Transform.WorldPosition).Normalized;
+ var endPoint = Owner.Transform.WorldPosition;
+
+ var climbMode = user.GetComponent();
+ climbMode.IsClimbing = true;
+
+ if (MathF.Abs(direction.X) < 0.6f) // user climbed mostly vertically so lets make it a clean straight line
+ {
+ endPoint = new Vector2(user.Transform.WorldPosition.X, endPoint.Y);
+ }
+ else if (MathF.Abs(direction.Y) < 0.6f) // user climbed mostly horizontally so lets make it a clean straight line
+ {
+ endPoint = new Vector2(endPoint.X, user.Transform.WorldPosition.Y);
+ }
+
+ climbMode.TryMoveTo(user.Transform.WorldPosition, endPoint);
+
+ PopupMessageOtherClientsInRange(user, Loc.GetString("{0:theName} jumps onto {1:theName}!", user, Owner), 15);
+ _notifyManager.PopupMessage(user, user, Loc.GetString("You jump onto {0:theName}!", Owner));
+ }
+ }
+
+ private void PopupMessageOtherClientsInRange(IEntity source, string message, int maxReceiveDistance)
+ {
+ var viewers = _playerManager.GetPlayersInRange(source.Transform.GridPosition, maxReceiveDistance);
+
+ foreach (var viewer in viewers)
+ {
+ var viewerEntity = viewer.AttachedEntity;
+
+ if (viewerEntity == null || source == viewerEntity)
+ {
+ continue;
+ }
+
+ source.PopupMessage(viewer.AttachedEntity, message);
+ }
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/Components/Movement/ClimbingComponent.cs b/Content.Server/GameObjects/Components/Movement/ClimbingComponent.cs
new file mode 100644
index 0000000000..591cd4c761
--- /dev/null
+++ b/Content.Server/GameObjects/Components/Movement/ClimbingComponent.cs
@@ -0,0 +1,76 @@
+using Robust.Shared.GameObjects;
+using Robust.Shared.Maths;
+using Content.Shared.Physics;
+using Content.Shared.GameObjects.Components.Movement;
+using Content.Shared.GameObjects.EntitySystems;
+
+namespace Content.Server.GameObjects.Components.Movement
+{
+ [RegisterComponent]
+ public class ClimbingComponent : SharedClimbingComponent, IActionBlocker
+ {
+ private bool _isClimbing = false;
+ private ClimbController _climbController = default;
+
+ public override bool IsClimbing
+ {
+ get
+ {
+ return _isClimbing;
+ }
+ set
+ {
+ if (!value && Body != null)
+ {
+ Body.TryRemoveController();
+ }
+
+ _isClimbing = value;
+ Dirty();
+ }
+ }
+
+ ///
+ /// Make the owner climb from one point to another
+ ///
+ public void TryMoveTo(Vector2 from, Vector2 to)
+ {
+ if (Body != null)
+ {
+ _climbController = Body.EnsureController();
+ _climbController.TryMoveTo(from, to);
+ }
+ }
+
+ public void Update(float frameTime)
+ {
+ if (Body != null && IsClimbing)
+ {
+ if (_climbController != null && (_climbController.IsBlocked || !_climbController.IsActive))
+ {
+ if (Body.TryRemoveController())
+ {
+ _climbController = null;
+ }
+ }
+
+ if (IsClimbing)
+ {
+ Body.WakeBody();
+ }
+
+ if (!IsOnClimbableThisFrame && IsClimbing && _climbController == null)
+ {
+ IsClimbing = false;
+ }
+
+ IsOnClimbableThisFrame = false;
+ }
+ }
+
+ public override ComponentState GetComponentState()
+ {
+ return new ClimbModeComponentState(_isClimbing);
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs b/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs
index 85bb04b84b..1b39598c2f 100644
--- a/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs
+++ b/Content.Server/GameObjects/Components/Movement/ShuttleControllerComponent.cs
@@ -71,7 +71,7 @@ namespace Content.Server.GameObjects.Components.Movement
_entityManager.TryGetEntity(grid.GridEntityId, out var gridEntity))
{
//TODO: Switch to shuttle component
- if (!gridEntity.TryGetComponent(out ICollidableComponent collidable))
+ if (!gridEntity.TryGetComponent(out ICollidableComponent? collidable))
{
collidable = gridEntity.AddComponent();
collidable.Mass = 1;
@@ -137,9 +137,9 @@ namespace Content.Server.GameObjects.Components.Movement
private void SetController(IEntity entity)
{
if (_controller != null ||
- !entity.TryGetComponent(out MindComponent mind) ||
+ !entity.TryGetComponent(out MindComponent? mind) ||
mind.Mind == null ||
- !Owner.TryGetComponent(out ServerStatusEffectsComponent status))
+ !Owner.TryGetComponent(out ServerStatusEffectsComponent? status))
{
return;
}
@@ -179,17 +179,17 @@ namespace Content.Server.GameObjects.Components.Movement
/// The entity to update
private void UpdateRemovedEntity(IEntity entity)
{
- if (Owner.TryGetComponent(out ServerStatusEffectsComponent status))
+ if (Owner.TryGetComponent(out ServerStatusEffectsComponent? status))
{
status.RemoveStatusEffect(StatusEffect.Piloting);
}
- if (entity.TryGetComponent(out MindComponent mind))
+ if (entity.TryGetComponent(out MindComponent? mind))
{
mind.Mind?.UnVisit();
}
- if (entity.TryGetComponent(out BuckleComponent buckle))
+ if (entity.TryGetComponent(out BuckleComponent? buckle))
{
buckle.TryUnbuckle(entity, true);
}
diff --git a/Content.Server/GameObjects/Components/PDA/PDAComponent.cs b/Content.Server/GameObjects/Components/PDA/PDAComponent.cs
index 53f3543d62..1aa9f2f776 100644
--- a/Content.Server/GameObjects/Components/PDA/PDAComponent.cs
+++ b/Content.Server/GameObjects/Components/PDA/PDAComponent.cs
@@ -141,7 +141,7 @@ namespace Content.Server.GameObjects.Components.PDA
private void UpdatePDAAppearance()
{
- _appearance?.SetData(PDAVisuals.ScreenLit, _lightOn);
+ _appearance?.SetData(PDAVisuals.FlashlightLit, _lightOn);
}
public async Task InteractUsing(InteractUsingEventArgs eventArgs)
@@ -164,7 +164,7 @@ namespace Content.Server.GameObjects.Components.PDA
void IActivate.Activate(ActivateEventArgs eventArgs)
{
- if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
+ if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
@@ -175,7 +175,7 @@ namespace Content.Server.GameObjects.Components.PDA
public bool UseEntity(UseEntityEventArgs eventArgs)
{
- if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
+ if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
return false;
}
diff --git a/Content.Server/GameObjects/Components/Pointing/PointingArrowComponent.cs b/Content.Server/GameObjects/Components/Pointing/PointingArrowComponent.cs
index 0c95a3d69f..caff655173 100644
--- a/Content.Server/GameObjects/Components/Pointing/PointingArrowComponent.cs
+++ b/Content.Server/GameObjects/Components/Pointing/PointingArrowComponent.cs
@@ -64,7 +64,7 @@ namespace Content.Server.GameObjects.Components.Pointing
{
base.Startup();
- if (Owner.TryGetComponent(out SpriteComponent sprite))
+ if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
sprite.DrawDepth = (int) DrawDepth.Overlays;
}
diff --git a/Content.Server/GameObjects/Components/Pointing/RoguePointingArrowComponent.cs b/Content.Server/GameObjects/Components/Pointing/RoguePointingArrowComponent.cs
index 35706da030..f20f1305d5 100644
--- a/Content.Server/GameObjects/Components/Pointing/RoguePointingArrowComponent.cs
+++ b/Content.Server/GameObjects/Components/Pointing/RoguePointingArrowComponent.cs
@@ -57,7 +57,7 @@ namespace Content.Server.GameObjects.Components.Pointing
private void UpdateAppearance()
{
if (_chasing == null ||
- !Owner.TryGetComponent(out AppearanceComponent appearance))
+ !Owner.TryGetComponent(out AppearanceComponent? appearance))
{
return;
}
@@ -69,7 +69,7 @@ namespace Content.Server.GameObjects.Components.Pointing
{
base.Startup();
- if (Owner.TryGetComponent(out SpriteComponent sprite))
+ if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
sprite.DrawDepth = (int) DrawDepth.Overlays;
}
diff --git a/Content.Server/GameObjects/Components/Power/BatteryComponent.cs b/Content.Server/GameObjects/Components/Power/BatteryComponent.cs
index 77ce70f0d4..de481e77ec 100644
--- a/Content.Server/GameObjects/Components/Power/BatteryComponent.cs
+++ b/Content.Server/GameObjects/Components/Power/BatteryComponent.cs
@@ -100,7 +100,7 @@ namespace Content.Server.GameObjects.Components.Power
private void SetCurrentCharge(float newChargeAmount)
{
- _currentCharge = FloatMath.Clamp(newChargeAmount, 0, MaxCharge);
+ _currentCharge = MathHelper.Clamp(newChargeAmount, 0, MaxCharge);
UpdateStorageState();
OnChargeChanged();
}
diff --git a/Content.Server/GameObjects/Components/Rotatable/FlippableComponent.cs b/Content.Server/GameObjects/Components/Rotatable/FlippableComponent.cs
index c986fe04dd..ad524e517c 100644
--- a/Content.Server/GameObjects/Components/Rotatable/FlippableComponent.cs
+++ b/Content.Server/GameObjects/Components/Rotatable/FlippableComponent.cs
@@ -24,7 +24,7 @@ namespace Content.Server.GameObjects.Components.Rotatable
private void TryFlip(IEntity user)
{
- if (Owner.TryGetComponent(out ICollidableComponent collidable) &&
+ if (Owner.TryGetComponent(out ICollidableComponent? collidable) &&
collidable.Anchored)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("It's stuck."));
diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs
index 7c53360eb1..aca55f1921 100644
--- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs
+++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs
@@ -178,7 +178,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
{
var currentTime = _gameTiming.CurTime;
var timeSinceLastFire = (currentTime - _lastFire).TotalSeconds;
- var newTheta = FloatMath.Clamp(_currentAngle.Theta + _angleIncrease - _angleDecay * timeSinceLastFire, _minAngle.Theta, _maxAngle.Theta);
+ var newTheta = MathHelper.Clamp(_currentAngle.Theta + _angleIncrease - _angleDecay * timeSinceLastFire, _minAngle.Theta, _maxAngle.Theta);
_currentAngle = new Angle(newTheta);
var random = (_robustRandom.NextDouble() - 0.5) * 2;
diff --git a/Content.Server/GameObjects/Components/WiresComponent.cs b/Content.Server/GameObjects/Components/WiresComponent.cs
index d8abb14bc7..6867052599 100644
--- a/Content.Server/GameObjects/Components/WiresComponent.cs
+++ b/Content.Server/GameObjects/Components/WiresComponent.cs
@@ -373,7 +373,7 @@ namespace Content.Server.GameObjects.Components
return;
}
- if (!player.TryGetComponent(out IHandsComponent handsComponent))
+ if (!player.TryGetComponent(out IHandsComponent? handsComponent))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, player,
Loc.GetString("You have no hands."));
diff --git a/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs b/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs
index a9a7899300..2d955dbe82 100644
--- a/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs
+++ b/Content.Server/GameObjects/EntitySystems/AI/Steering/AiSteeringSystem.cs
@@ -647,7 +647,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI.Steering
var additionalVector = (centerGrid.Position - entityGridCoords.Position);
var distance = additionalVector.Length;
// If we're too far no point, if we're close then cap it at the normalized vector
- distance = FloatMath.Clamp(2.5f - distance, 0.0f, 1.0f);
+ distance = MathHelper.Clamp(2.5f - distance, 0.0f, 1.0f);
additionalVector = new Angle(90 * distance).RotateVec(additionalVector);
avoidanceVector += additionalVector;
// if we do need to avoid that means we'll have to lookahead for the next tile
diff --git a/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs b/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs
index 456bb14293..d70331f71e 100644
--- a/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs
+++ b/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs
@@ -30,19 +30,19 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
[Robust.Shared.IoC.Dependency] private readonly IPlayerManager _playerManager = default!;
[Robust.Shared.IoC.Dependency] private readonly IMapManager _mapManager = default!;
[Robust.Shared.IoC.Dependency] private readonly IConfigurationManager _configManager = default!;
-
+
///
/// The tiles that have had their atmos data updated since last tick
///
private Dictionary> _invalidTiles = new Dictionary>();
-
- private Dictionary _knownPlayerChunks =
+
+ private Dictionary _knownPlayerChunks =
new Dictionary();
-
+
///
/// Gas data stored in chunks to make PVS / bubbling easier.
///
- private Dictionary> _overlay =
+ private Dictionary> _overlay =
new Dictionary>();
///
@@ -52,7 +52,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
// Because the gas overlay updates aren't run every tick we need to avoid the pop-in that might occur with
// the regular PVS range.
private const float RangeOffset = 6.0f;
-
+
///
/// Overlay update ticks per second.
///
@@ -164,8 +164,8 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
var moles = tile.Air.Gases[i];
if (moles < gas.GasMolesVisible) continue;
-
- var data = new GasData(i, (byte) (FloatMath.Clamp01(moles / gas.GasMolesVisibleMax) * 255));
+
+ var data = new GasData(i, (byte) (MathHelper.Clamp01(moles / gas.GasMolesVisibleMax) * 255));
tileData.Add(data);
}
@@ -175,7 +175,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
{
return false;
}
-
+
return true;
}
@@ -187,10 +187,10 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
private List GetChunksInRange(IEntity entity)
{
var inRange = new List();
-
+
// This is the max in any direction that we can get a chunk (e.g. max 2 chunks away of data).
var (maxXDiff, maxYDiff) = ((int) (_updateRange / ChunkSize) + 1, (int) (_updateRange / ChunkSize) + 1);
-
+
var worldBounds = Box2.CenteredAround(entity.Transform.WorldPosition,
new Vector2(_updateRange, _updateRange));
@@ -202,7 +202,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
}
var entityTile = grid.GetTileRef(entity.Transform.GridPosition).GridIndices;
-
+
for (var x = -maxXDiff; x <= maxXDiff; x++)
{
for (var y = -maxYDiff; y <= maxYDiff; y++)
@@ -210,7 +210,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
var chunkIndices = GetGasChunkIndices(new MapIndices(entityTile.X + x * ChunkSize, entityTile.Y + y * ChunkSize));
if (!chunks.TryGetValue(chunkIndices, out var chunk)) continue;
-
+
// Now we'll check if it's in range and relevant for us
// (e.g. if we're on the very edge of a chunk we may need more chunks).
@@ -219,7 +219,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
yDiff > 0 && yDiff > _updateRange ||
xDiff < 0 && Math.Abs(xDiff + ChunkSize) > _updateRange ||
yDiff < 0 && Math.Abs(yDiff + ChunkSize) > _updateRange) continue;
-
+
inRange.Add(chunk);
}
}
@@ -237,25 +237,25 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
{
return;
}
-
+
_updateRange = _configManager.GetCVar("net.maxupdaterange") + RangeOffset;
-
+
// TODO: So in the worst case scenario we still have to send a LOT of tile data per tick if there's a fire.
// If we go with say 15 tile radius then we have up to 900 tiles to update per tick.
// In a saltern fire the worst you'll normally see is around 650 at the moment.
// Need a way to fake this more because sending almost 2,000 tile updates per second to even 50 players is... yikes
// I mean that's as big as it gets so larger maps will have the same but still, that's a lot of data.
-
+
// Some ways to do this are potentially: splitting fire and gas update data so they don't update at the same time
// (gives the illusion of more updates happening), e.g. if gas updates are 3 times a second and fires are 1.6 times a second or something.
// Could also look at updating tiles close to us more frequently (e.g. within 1 chunk every tick).
// Stuff just out of our viewport we need so when we move it doesn't pop in but it doesn't mean we need to update it every tick.
-
+
AccumulatedFrameTime -= _updateCooldown;
var gridAtmosComponents = new Dictionary();
var updatedTiles = new Dictionary>();
-
+
// So up to this point we've been caching the updated tiles for multiple ticks.
// Now we'll go through and check whether the update actually matters for the overlay or not,
// and if not then we won't bother sending the data.
@@ -263,7 +263,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
{
var gridEntityId = _mapManager.GetGrid(gridId).GridEntityId;
- if (!EntityManager.GetEntity(gridEntityId).TryGetComponent(out GridAtmosphereComponent gam))
+ if (!EntityManager.GetEntity(gridEntityId).TryGetComponent(out GridAtmosphereComponent? gam))
{
continue;
}
@@ -286,7 +286,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
tiles = new HashSet();
updatedTiles[chunk] = tiles;
}
-
+
updatedTiles[chunk].Add(invalid);
chunk.Update(data, invalid);
}
@@ -306,13 +306,13 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
foreach (var (session, overlay) in _knownPlayerChunks)
{
if (session.AttachedEntity == null) continue;
-
+
// Get chunks in range and update if we've moved around or the chunks have new overlay data
var chunksInRange = GetChunksInRange(session.AttachedEntity);
var knownChunks = overlay.GetKnownChunks();
var chunksToRemove = new List();
var chunksToAdd = new List();
-
+
foreach (var chunk in chunksInRange)
{
if (!knownChunks.Contains(chunk))
@@ -328,7 +328,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
chunksToRemove.Add(chunk);
}
}
-
+
foreach (var chunk in chunksToAdd)
{
var message = overlay.AddChunk(currentTick, chunk);
@@ -342,7 +342,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
{
overlay.RemoveChunk(chunk);
}
-
+
var clientInvalids = new Dictionary>();
// Check for any dirty chunks in range and bundle the data to send to the client.
@@ -355,7 +355,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
existingData = new List<(MapIndices, GasOverlayData)>();
clientInvalids[chunk.GridIndices] = existingData;
}
-
+
chunk.GetData(existingData, invalids);
}
@@ -370,10 +370,10 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
}
private sealed class PlayerGasOverlay
{
- private readonly Dictionary> _data =
+ private readonly Dictionary> _data =
new Dictionary>();
-
- private readonly Dictionary _lastSent =
+
+ private readonly Dictionary _lastSent =
new Dictionary();
public GasOverlayMessage UpdateClient(GridId grid, List<(MapIndices, GasOverlayData)> data)
@@ -386,11 +386,11 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
_data.Clear();
_lastSent.Clear();
}
-
+
public List GetKnownChunks()
{
var known = new List();
-
+
foreach (var (_, chunks) in _data)
{
foreach (var (_, chunk) in chunks)
@@ -414,7 +414,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
{
return null;
}
-
+
_lastSent[chunk] = currentTick;
var message = ChunkToMessage(chunk);
@@ -444,7 +444,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
{
// Chunk data should already be up to date.
// Only send relevant tiles to client.
-
+
var tileData = new List<(MapIndices, GasOverlayData)>();
for (var x = 0; x < ChunkSize; x++)
@@ -467,7 +467,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
{
return null;
}
-
+
return new GasOverlayMessage(chunk.GridIndices, tileData);
}
}
diff --git a/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs b/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs
index 327104a148..a5ffa6e9ce 100644
--- a/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs
+++ b/Content.Server/GameObjects/EntitySystems/AtmosphereSystem.cs
@@ -34,7 +34,7 @@ namespace Content.Server.GameObjects.EntitySystems
if (!EntityManager.TryGetEntity(grid.GridEntityId, out var gridEnt)) return null;
- return gridEnt.TryGetComponent(out IGridAtmosphereComponent atmos) ? atmos : null;
+ return gridEnt.TryGetComponent(out IGridAtmosphereComponent? atmos) ? atmos : null;
}
public override void Update(float frameTime)
diff --git a/Content.Server/GameObjects/EntitySystems/ClimbSystem.cs b/Content.Server/GameObjects/EntitySystems/ClimbSystem.cs
new file mode 100644
index 0000000000..f7d2e37372
--- /dev/null
+++ b/Content.Server/GameObjects/EntitySystems/ClimbSystem.cs
@@ -0,0 +1,18 @@
+using Content.Server.GameObjects.Components.Movement;
+using JetBrains.Annotations;
+using Robust.Shared.GameObjects.Systems;
+
+namespace Content.Server.GameObjects.EntitySystems
+{
+ [UsedImplicitly]
+ internal sealed class ClimbSystem : EntitySystem
+ {
+ public override void Update(float frameTime)
+ {
+ foreach (var comp in ComponentManager.EntityQuery())
+ {
+ comp.Update(frameTime);
+ }
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/EntitySystems/CloningSystem.cs b/Content.Server/GameObjects/EntitySystems/CloningSystem.cs
new file mode 100644
index 0000000000..469c0f6157
--- /dev/null
+++ b/Content.Server/GameObjects/EntitySystems/CloningSystem.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Systems;
+
+namespace Content.Server.GameObjects.EntitySystems
+{
+ internal sealed class CloningSystem : EntitySystem
+ {
+ public static List scannedUids = new List();
+
+ public static void AddToScannedUids(EntityUid uid)
+ {
+ if (!scannedUids.Contains(uid))
+ {
+ scannedUids.Add(uid);
+ }
+ }
+
+ public static bool HasUid(EntityUid uid)
+ {
+ return scannedUids.Contains(uid);
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/EntitySystems/DoAfter/DoAfter.cs b/Content.Server/GameObjects/EntitySystems/DoAfter/DoAfter.cs
index 3e00f334fd..2f45195624 100644
--- a/Content.Server/GameObjects/EntitySystems/DoAfter/DoAfter.cs
+++ b/Content.Server/GameObjects/EntitySystems/DoAfter/DoAfter.cs
@@ -54,7 +54,7 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter
// For this we need to stay on the same hand slot and need the same item in that hand slot
// (or if there is no item there we need to keep it free).
- if (eventArgs.NeedHand && eventArgs.User.TryGetComponent(out HandsComponent handsComponent))
+ if (eventArgs.NeedHand && eventArgs.User.TryGetComponent(out HandsComponent? handsComponent))
{
_activeHand = handsComponent.ActiveHand;
_activeItem = handsComponent.GetActiveHand;
@@ -126,7 +126,7 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter
}
if (EventArgs.BreakOnStun &&
- EventArgs.User.TryGetComponent(out StunnableComponent stunnableComponent) &&
+ EventArgs.User.TryGetComponent(out StunnableComponent? stunnableComponent) &&
stunnableComponent.Stunned)
{
return true;
@@ -134,7 +134,7 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter
if (EventArgs.NeedHand)
{
- if (!EventArgs.User.TryGetComponent(out HandsComponent handsComponent))
+ if (!EventArgs.User.TryGetComponent(out HandsComponent? handsComponent))
{
// If we had a hand but no longer have it that's still a paddlin'
if (_activeHand != null)
diff --git a/Content.Server/GameObjects/EntitySystems/MedicalScannerSystem.cs b/Content.Server/GameObjects/EntitySystems/MedicalScannerSystem.cs
index 3180063247..01d1cd20ac 100644
--- a/Content.Server/GameObjects/EntitySystems/MedicalScannerSystem.cs
+++ b/Content.Server/GameObjects/EntitySystems/MedicalScannerSystem.cs
@@ -7,6 +7,7 @@ namespace Content.Server.GameObjects.EntitySystems
[UsedImplicitly]
internal sealed class MedicalScannerSystem : EntitySystem
{
+
public override void Update(float frameTime)
{
foreach (var comp in ComponentManager.EntityQuery())
diff --git a/Content.Server/GameObjects/EntitySystems/MoverSystem.cs b/Content.Server/GameObjects/EntitySystems/MoverSystem.cs
index 84250d4e91..41ded38a03 100644
--- a/Content.Server/GameObjects/EntitySystems/MoverSystem.cs
+++ b/Content.Server/GameObjects/EntitySystems/MoverSystem.cs
@@ -83,7 +83,7 @@ namespace Content.Server.GameObjects.EntitySystems
ev.Entity.RemoveComponent();
}
- if (ev.Entity.TryGetComponent(out ICollidableComponent physics) &&
+ if (ev.Entity.TryGetComponent(out ICollidableComponent? physics) &&
physics.TryGetController(out MoverController controller))
{
controller.StopMoving();
diff --git a/Content.Server/GameObjects/EntitySystems/PointingSystem.cs b/Content.Server/GameObjects/EntitySystems/PointingSystem.cs
index d27857954f..301d55db95 100644
--- a/Content.Server/GameObjects/EntitySystems/PointingSystem.cs
+++ b/Content.Server/GameObjects/EntitySystems/PointingSystem.cs
@@ -2,10 +2,13 @@
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Pointing;
+using Content.Server.Players;
+using Content.Server.Utility;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Input;
using Content.Shared.Interfaces;
using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore.Diagnostics;
using Robust.Server.GameObjects.Components;
using Robust.Server.Interfaces.Player;
using Robust.Server.Player;
@@ -40,6 +43,8 @@ namespace Content.Server.GameObjects.EntitySystems
///
private readonly Dictionary _pointers = new Dictionary();
+ private const float PointingRange = 15f;
+
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.NewStatus != SessionStatus.Disconnected)
@@ -79,7 +84,7 @@ namespace Content.Server.GameObjects.EntitySystems
public bool TryPoint(ICommonSession? session, GridCoordinates coords, EntityUid uid)
{
- var player = session?.AttachedEntity;
+ var player = (session as IPlayerSession)?.ContentData().Mind.CurrentEntity;
if (player == null)
{
return false;
@@ -112,16 +117,27 @@ namespace Content.Server.GameObjects.EntitySystems
}
}
- var viewers = _playerManager.GetPlayersInRange(player.Transform.GridPosition, 15);
-
var arrow = EntityManager.SpawnEntity("pointingarrow", coords);
- if (player.TryGetComponent(out VisibilityComponent playerVisibility))
+ var layer = (int)VisibilityFlags.Normal;
+ if (player.TryGetComponent(out VisibilityComponent? playerVisibility))
{
var arrowVisibility = arrow.EnsureComponent();
- arrowVisibility.Layer = playerVisibility.Layer;
+ layer = arrowVisibility.Layer = playerVisibility.Layer;
}
+ // Get players that are in range and whose visibility layer matches the arrow's.
+ var viewers = _playerManager.GetPlayersBy((playerSession) =>
+ {
+ if ((playerSession.VisibilityMask & layer) == 0)
+ return false;
+
+ var ent = playerSession.ContentData().Mind.CurrentEntity;
+
+ return ent != null
+ && ent.Transform.MapPosition.InRange(player.Transform.MapPosition, PointingRange);
+ });
+
string selfMessage;
string viewerMessage;
string? viewerPointedAtMessage = null;
diff --git a/Content.Server/GameObjects/EntitySystems/StationEvents/StationEventSystem.cs b/Content.Server/GameObjects/EntitySystems/StationEvents/StationEventSystem.cs
index 48786d52dc..c34a502b21 100644
--- a/Content.Server/GameObjects/EntitySystems/StationEvents/StationEventSystem.cs
+++ b/Content.Server/GameObjects/EntitySystems/StationEvents/StationEventSystem.cs
@@ -1,4 +1,8 @@
-using Content.Server.StationEvents;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Content.Server.StationEvents;
+using Content.Server.Interfaces.GameTicking;
using JetBrains.Annotations;
using Robust.Server.Console;
using Robust.Server.Interfaces.Player;
@@ -9,25 +13,23 @@ using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
-using System;
-using System.Collections.Generic;
-using System.Text;
using static Content.Shared.StationEvents.SharedStationEvent;
namespace Content.Server.GameObjects.EntitySystems.StationEvents
{
[UsedImplicitly]
+ // Somewhat based off of TG's implementation of events
public sealed class StationEventSystem : EntitySystem
{
#pragma warning disable 649
[Dependency] private readonly IServerNetManager _netManager;
[Dependency] private readonly IPlayerManager _playerManager;
+ [Dependency] private readonly IGameTicker _gameTicker;
#pragma warning restore 649
- // Somewhat based off of TG's implementation of events
public StationEvent CurrentEvent { get; private set; }
-
public IReadOnlyCollection StationEvents => _stationEvents;
+
private List _stationEvents = new List();
private const float MinimumTimeUntilFirstEvent = 600;
@@ -194,7 +196,18 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents
{
return;
}
-
+
+ // Stop events from happening in lobby and force active event to end if the round ends
+ if (_gameTicker.RunLevel != GameTicking.GameRunLevel.InRound)
+ {
+ if (CurrentEvent != null)
+ {
+ Enabled = false;
+ }
+
+ return;
+ }
+
// Keep running the current event
if (CurrentEvent != null)
{
diff --git a/Content.Server/GameObjects/EntitySystems/VerbSystem.cs b/Content.Server/GameObjects/EntitySystems/VerbSystem.cs
index 761681b1ac..63775b3e44 100644
--- a/Content.Server/GameObjects/EntitySystems/VerbSystem.cs
+++ b/Content.Server/GameObjects/EntitySystems/VerbSystem.cs
@@ -51,12 +51,7 @@ namespace Content.Server.GameObjects.EntitySystems
continue;
}
- if (verb.RequireInteractionRange && !VerbUtility.InVerbUseRange(userEntity, entity))
- {
- break;
- }
-
- if (verb.BlockedByContainers && !userEntity.IsInSameOrNoContainer(entity))
+ if (!VerbUtility.VerbAccessChecks(userEntity, entity, verb))
{
break;
}
@@ -72,13 +67,7 @@ namespace Content.Server.GameObjects.EntitySystems
continue;
}
- if (globalVerb.RequireInteractionRange &&
- !VerbUtility.InVerbUseRange(userEntity, entity))
- {
- break;
- }
-
- if (globalVerb.BlockedByContainers && !userEntity.IsInSameOrNoContainer(entity))
+ if (!VerbUtility.VerbAccessChecks(userEntity, entity, globalVerb))
{
break;
}
@@ -110,19 +99,9 @@ namespace Content.Server.GameObjects.EntitySystems
//Get verbs, component dependent.
foreach (var (component, verb) in VerbUtility.GetVerbs(entity))
{
- if (verb.RequireInteractionRange && !VerbUtility.InVerbUseRange(userEntity, entity))
- continue;
-
- if (verb.BlockedByContainers)
+ if (!VerbUtility.VerbAccessChecks(userEntity, entity, verb))
{
- if (!userEntity.IsInSameOrNoContainer(entity))
- {
- if (!ContainerHelpers.TryGetContainer(entity, out var container) ||
- container.Owner != userEntity)
- {
- continue;
- }
- }
+ continue;
}
var verbData = verb.GetData(userEntity, component);
@@ -136,11 +115,10 @@ namespace Content.Server.GameObjects.EntitySystems
//Get global verbs. Visible for all entities regardless of their components.
foreach (var globalVerb in VerbUtility.GetGlobalVerbs(Assembly.GetExecutingAssembly()))
{
- if (globalVerb.RequireInteractionRange && !VerbUtility.InVerbUseRange(userEntity, entity))
- continue;
-
- if (globalVerb.BlockedByContainers && !userEntity.IsInSameOrNoContainer(entity))
+ if (!VerbUtility.VerbAccessChecks(userEntity, entity, globalVerb))
+ {
continue;
+ }
var verbData = globalVerb.GetData(userEntity, entity);
if (verbData.IsInvisible)
diff --git a/Content.Server/GameObjects/VisibilityFlags.cs b/Content.Server/GameObjects/VisibilityFlags.cs
index 4acb9c988a..bcd103ddfc 100644
--- a/Content.Server/GameObjects/VisibilityFlags.cs
+++ b/Content.Server/GameObjects/VisibilityFlags.cs
@@ -5,6 +5,7 @@ namespace Content.Server.GameObjects
[Flags]
public enum VisibilityFlags
{
+ Normal = 1,
Ghost = 2,
}
}
diff --git a/Content.Server/GameTicking/GamePreset.cs b/Content.Server/GameTicking/GamePreset.cs
index 0e7ce86795..ddde553722 100644
--- a/Content.Server/GameTicking/GamePreset.cs
+++ b/Content.Server/GameTicking/GamePreset.cs
@@ -12,6 +12,7 @@ namespace Content.Server.GameTicking
public abstract bool Start(IReadOnlyList readyPlayers, bool force = false);
public virtual string ModeTitle => "Sandbox";
public virtual string Description => "Secret!";
+ public virtual bool DisallowLateJoin => false;
public Dictionary readyProfiles;
}
}
diff --git a/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs b/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs
index fc09cc14a3..f79abde6b2 100644
--- a/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs
+++ b/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs
@@ -13,6 +13,7 @@ using System.Linq;
using Content.Server.GameObjects.Components.Suspicion;
using Content.Shared.Roles;
using Robust.Shared.GameObjects;
+using Robust.Shared.Interfaces.Configuration;
using Robust.Shared.Log;
using Robust.Shared.Maths;
@@ -24,17 +25,32 @@ namespace Content.Server.GameTicking.GamePresets
[Dependency] private readonly IChatManager _chatManager;
[Dependency] private readonly IGameTicker _gameTicker;
[Dependency] private readonly IRobustRandom _random;
+ [Dependency] private readonly IConfigurationManager _cfg;
[Dependency] private IPrototypeManager _prototypeManager;
#pragma warning restore 649
- public int MinPlayers { get; set; } = 5;
- public int MinTraitors { get; set; } = 2;
- public int PlayersPerTraitor { get; set; } = 5;
+ public int MinPlayers { get; set; }
+ public int MinTraitors { get; set; }
+ public int PlayersPerTraitor { get; set; }
+
+ public override bool DisallowLateJoin => true;
+
private static string TraitorID = "SuspicionTraitor";
private static string InnocentID = "SuspicionInnocent";
+ public static void RegisterCVars(IConfigurationManager cfg)
+ {
+ cfg.RegisterCVar("game.suspicion_min_players", 5);
+ cfg.RegisterCVar("game.suspicion_min_traitors", 2);
+ cfg.RegisterCVar("game.suspicion_players_per_traitor", 5);
+ }
+
public override bool Start(IReadOnlyList readyPlayers, bool force = false)
{
+ MinPlayers = _cfg.GetCVar("game.suspicion_min_players");
+ MinTraitors = _cfg.GetCVar("game.suspicion_min_traitors");
+ PlayersPerTraitor = _cfg.GetCVar("game.suspicion_players_per_traitor");
+
if (!force && readyPlayers.Count < MinPlayers)
{
_chatManager.DispatchServerAnnouncement($"Not enough players readied up for the game! There were {readyPlayers.Count} players readied up out of {MinPlayers} needed.");
@@ -65,14 +81,21 @@ namespace Content.Server.GameTicking.GamePresets
player.AttachedEntity?.EnsureComponent();
}
- var numTraitors = FloatMath.Clamp(readyPlayers.Count % PlayersPerTraitor,
+ var numTraitors = MathHelper.Clamp(readyPlayers.Count / PlayersPerTraitor,
MinTraitors, readyPlayers.Count);
+ var traitors = new List();
+
for (var i = 0; i < numTraitors; i++)
{
IPlayerSession traitor;
if(prefList.Count == 0)
{
+ if (list.Count == 0)
+ {
+ Logger.InfoS("preset", "Insufficient ready players to fill up with traitors, stopping the selection.");
+ break;
+ }
traitor = _random.PickAndTake(list);
Logger.InfoS("preset", "Insufficient preferred traitors, picking at random.");
}
@@ -84,7 +107,9 @@ namespace Content.Server.GameTicking.GamePresets
}
var mind = traitor.Data.ContentData().Mind;
var antagPrototype = _prototypeManager.Index(TraitorID);
- mind.AddRole(new SuspicionTraitorRole(mind, antagPrototype));
+ var traitorRole = new SuspicionTraitorRole(mind, antagPrototype);
+ mind.AddRole(traitorRole);
+ traitors.Add(traitorRole);
}
foreach (var player in list)
@@ -94,6 +119,11 @@ namespace Content.Server.GameTicking.GamePresets
mind.AddRole(new SuspicionInnocentRole(mind, antagPrototype));
}
+ foreach (var traitor in traitors)
+ {
+ traitor.GreetSuspicion(traitors, _chatManager);
+ }
+
_gameTicker.AddGameRule();
return true;
}
diff --git a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs
index 553df745a2..026c11516a 100644
--- a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs
+++ b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs
@@ -1,5 +1,6 @@
using System;
using System.Threading;
+using Content.Server.GameObjects.Components.Suspicion;
using Content.Server.Interfaces.Chat;
using Content.Server.Interfaces.GameTicking;
using Content.Server.Mobs.Roles;
@@ -50,7 +51,8 @@ namespace Content.Server.GameTicking.GameRules
foreach (var playerSession in _playerManager.GetAllPlayers())
{
if (playerSession.AttachedEntity == null
- || !playerSession.AttachedEntity.TryGetComponent(out IDamageableComponent damageable))
+ || !playerSession.AttachedEntity.TryGetComponent(out IDamageableComponent damageable)
+ || !playerSession.AttachedEntity.TryGetComponent(out SuspicionRoleComponent suspicionRole))
{
continue;
}
@@ -69,24 +71,46 @@ namespace Content.Server.GameTicking.GameRules
if ((innocentsAlive + traitorsAlive) == 0)
{
_chatManager.DispatchServerAnnouncement("Everybody is dead, it's a stalemate!");
- EndRound();
+ EndRound(Victory.Stalemate);
}
else if (traitorsAlive == 0)
{
_chatManager.DispatchServerAnnouncement("The traitors are dead! The innocents win.");
- EndRound();
+ EndRound(Victory.Innocents);
}
else if (innocentsAlive == 0)
{
_chatManager.DispatchServerAnnouncement("The innocents are dead! The traitors win.");
- EndRound();
+ EndRound(Victory.Traitors);
}
}
- private void EndRound()
+ private enum Victory
{
- _gameTicker.EndRound();
+ Stalemate,
+ Innocents,
+ Traitors
+ }
+
+ private void EndRound(Victory victory)
+ {
+ string text;
+
+ switch (victory)
+ {
+ case Victory.Innocents:
+ text = "The innocents have won!";
+ break;
+ case Victory.Traitors:
+ text = "The traitors have won!";
+ break;
+ default:
+ text = "Nobody wins!";
+ break;
+ }
+
+ _gameTicker.EndRound(text);
_chatManager.DispatchServerAnnouncement($"Restarting in 10 seconds.");
_checkTimerCancel.Cancel();
Timer.Spawn(TimeSpan.FromSeconds(10), () => _gameTicker.RestartRound());
diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs
index 476a7c55f8..785ec372a0 100644
--- a/Content.Server/GameTicking/GameTicker.cs
+++ b/Content.Server/GameTicking/GameTicker.cs
@@ -80,9 +80,8 @@ namespace Content.Server.GameTicking
[ViewVariables] private readonly List _gameRules = new List();
[ViewVariables] private readonly List _manifest = new List();
- // Value is whether they're ready.
[ViewVariables]
- private readonly Dictionary _playersInLobby = new Dictionary();
+ private readonly Dictionary _playersInLobby = new Dictionary();
[ViewVariables] private bool _initialized;
@@ -94,6 +93,8 @@ namespace Content.Server.GameTicking
[ViewVariables] private GameRunLevel _runLevel;
[ViewVariables(VVAccess.ReadWrite)] private GridCoordinates _spawnPoint;
+ [ViewVariables] private bool DisallowLateJoin { get; set; } = false;
+
[ViewVariables] private bool LobbyEnabled => _configurationManager.GetCVar("game.lobbyenabled");
[ViewVariables] private bool _updateOnRoundEnd;
@@ -132,6 +133,8 @@ namespace Content.Server.GameTicking
_configurationManager.RegisterCVar("game.defaultpreset", "Suspicion", CVar.ARCHIVE);
_configurationManager.RegisterCVar("game.fallbackpreset", "Sandbox", CVar.ARCHIVE);
+ PresetSuspicion.RegisterCVars(_configurationManager);
+
_playerManager.PlayerStatusChanged += _handlePlayerStatusChanged;
_netManager.RegisterNetMessage(nameof(MsgTickerJoinLobby));
@@ -142,6 +145,7 @@ namespace Content.Server.GameTicking
_netManager.RegisterNetMessage(nameof(MsgTickerLobbyReady));
_netManager.RegisterNetMessage(nameof(MsgRoundEndMessage));
_netManager.RegisterNetMessage(nameof(MsgRequestWindowAttention));
+ _netManager.RegisterNetMessage(nameof(MsgTickerLateJoinStatus));
SetStartPreset(_configurationManager.GetCVar("game.defaultpreset"));
@@ -234,7 +238,7 @@ namespace Content.Server.GameTicking
List readyPlayers;
if (LobbyEnabled)
{
- readyPlayers = _playersInLobby.Where(p => p.Value).Select(p => p.Key).ToList();
+ readyPlayers = _playersInLobby.Where(p => p.Value == PlayerStatus.Ready).Select(p => p.Key).ToList();
}
else
{
@@ -285,6 +289,8 @@ namespace Content.Server.GameTicking
// Time to start the preset.
var preset = MakeGamePreset(profiles);
+ DisallowLateJoin |= preset.DisallowLateJoin;
+
if (!preset.Start(assignedJobs.Keys.ToList(), force))
{
SetStartPreset(_configurationManager.GetCVar("game.fallbackpreset"));
@@ -295,11 +301,21 @@ namespace Content.Server.GameTicking
{
throw new ApplicationException("Fallback preset failed to start!");
}
+
+ DisallowLateJoin = false;
+ DisallowLateJoin |= newPreset.DisallowLateJoin;
}
_roundStartTimeSpan = IoCManager.Resolve().RealTime;
_sendStatusToAll();
ReqWindowAttentionAll();
+ UpdateLateJoinStatus();
+ }
+
+ private void UpdateLateJoinStatus()
+ {
+ var msg = new MsgTickerLateJoinStatus(null) {Disallowed = DisallowLateJoin};
+ _netManager.ServerSendToAll(msg);
}
private void SendServerMessage(string message)
@@ -314,7 +330,7 @@ namespace Content.Server.GameTicking
(HumanoidCharacterProfile) (await _prefsManager.GetPreferencesAsync(p.SessionId.Username))
.SelectedCharacter;
- public void EndRound()
+ public void EndRound(string roundEndText = "")
{
DebugTools.Assert(RunLevel == GameRunLevel.InRound);
Logger.InfoS("ticker", "Ending round!");
@@ -324,6 +340,7 @@ namespace Content.Server.GameTicking
//Tell every client the round has ended.
var roundEndMessage = _netManager.CreateNetMessage();
roundEndMessage.GamemodeTitle = MakeGamePreset(null).ModeTitle;
+ roundEndMessage.RoundEndText = roundEndText;
//Get the timespan of the round.
roundEndMessage.RoundDuration = IoCManager.Resolve().RealTime.Subtract(_roundStartTimeSpan);
@@ -368,6 +385,8 @@ namespace Content.Server.GameTicking
if (!_playersInLobby.ContainsKey(player)) return;
_spawnObserver(player);
+ _playersInLobby[player] = PlayerStatus.Observer;
+ _netManager.ServerSendToAll(GetStatusSingle(player, PlayerStatus.Observer));
}
public void MakeJoinGame(IPlayerSession player, string jobId = null)
@@ -381,9 +400,10 @@ namespace Content.Server.GameTicking
{
if (!_playersInLobby.ContainsKey(player)) return;
- _playersInLobby[player] = ready;
+ var status = ready ? PlayerStatus.Ready : PlayerStatus.NotReady;
+ _playersInLobby[player] = ready ? PlayerStatus.Ready : PlayerStatus.NotReady;
_netManager.ServerSendMessage(_getStatusMsg(player), player.ConnectedClient);
- _netManager.ServerSendToAll(GetReadySingle(player, ready));
+ _netManager.ServerSendToAll(GetStatusSingle(player, status));
}
public T AddGameRule() where T : GameRule, new()
@@ -400,12 +420,12 @@ namespace Content.Server.GameTicking
public bool HasGameRule(Type t)
{
- if (t == null || !t.IsAssignableFrom(typeof(GameRule)))
+ if (t == null || !typeof(GameRule).IsAssignableFrom(t))
return false;
foreach (var rule in _gameRules)
{
- if (rule.GetType().Equals(t))
+ if (rule.GetType().IsAssignableFrom(t))
return true;
}
@@ -637,7 +657,7 @@ namespace Content.Server.GameTicking
_playerJoinLobby(player);
}
-
+
EntitySystem.Get().ResettingCleanup();
EntitySystem.Get().ResettingCleanup();
EntitySystem.Get().ResettingCleanup();
@@ -646,6 +666,7 @@ namespace Content.Server.GameTicking
_spawnedPositions.Clear();
_manifest.Clear();
+ DisallowLateJoin = false;
}
private void _preRoundSetup()
@@ -776,6 +797,12 @@ namespace Content.Server.GameTicking
string jobId = null,
bool lateJoin = true)
{
+ if (lateJoin && DisallowLateJoin)
+ {
+ MakeObserve(session);
+ return;
+ }
+
_playerJoinGame(session);
var data = session.ContentData();
@@ -868,13 +895,13 @@ namespace Content.Server.GameTicking
private void _playerJoinLobby(IPlayerSession session)
{
- _playersInLobby.Add(session, false);
+ _playersInLobby.Add(session, PlayerStatus.NotReady);
_prefsManager.OnClientConnected(session);
_netManager.ServerSendMessage(_netManager.CreateNetMessage(), session.ConnectedClient);
_netManager.ServerSendMessage(_getStatusMsg(session), session.ConnectedClient);
_netManager.ServerSendMessage(GetInfoMsg(), session.ConnectedClient);
- _netManager.ServerSendMessage(GetReadyStatus(), session.ConnectedClient);
+ _netManager.ServerSendMessage(GetPlayerStatus(), session.ConnectedClient);
}
private void _playerJoinGame(IPlayerSession session)
@@ -886,33 +913,35 @@ namespace Content.Server.GameTicking
_netManager.ServerSendMessage(_netManager.CreateNetMessage(), session.ConnectedClient);
}
- private MsgTickerLobbyReady GetReadyStatus()
+ private MsgTickerLobbyReady GetPlayerStatus()
{
var msg = _netManager.CreateNetMessage();
- msg.PlayerReady = new Dictionary();
+ msg.PlayerStatus = new Dictionary();
foreach (var player in _playersInLobby.Keys)
{
- _playersInLobby.TryGetValue(player, out var ready);
- msg.PlayerReady.Add(player.SessionId, ready);
+ _playersInLobby.TryGetValue(player, out var status);
+ msg.PlayerStatus.Add(player.SessionId, status);
}
return msg;
}
- private MsgTickerLobbyReady GetReadySingle(IPlayerSession player, bool ready)
+ private MsgTickerLobbyReady GetStatusSingle(IPlayerSession player, PlayerStatus status)
{
var msg = _netManager.CreateNetMessage();
- msg.PlayerReady = new Dictionary();
- msg.PlayerReady.Add(player.SessionId, ready);
+ msg.PlayerStatus = new Dictionary
+ {
+ { player.SessionId, status }
+ };
return msg;
}
private MsgTickerLobbyStatus _getStatusMsg(IPlayerSession session)
{
- _playersInLobby.TryGetValue(session, out var ready);
+ _playersInLobby.TryGetValue(session, out var status);
var msg = _netManager.CreateNetMessage();
msg.IsRoundStarted = RunLevel != GameRunLevel.PreRoundLobby;
msg.StartTime = _roundStartTimeUtc;
- msg.YouAreReady = ready;
+ msg.YouAreReady = status == PlayerStatus.Ready;
msg.Paused = Paused;
return msg;
}
diff --git a/Content.Server/Interfaces/GameTicking/IGameTicker.cs b/Content.Server/Interfaces/GameTicking/IGameTicker.cs
index 6ec11e8421..a4e045e2ee 100644
--- a/Content.Server/Interfaces/GameTicking/IGameTicker.cs
+++ b/Content.Server/Interfaces/GameTicking/IGameTicker.cs
@@ -22,7 +22,7 @@ namespace Content.Server.Interfaces.GameTicking
void RestartRound();
void StartRound(bool force = false);
- void EndRound();
+ void EndRound(string roundEndText = "");
void Respawn(IPlayerSession targetPlayer);
void MakeObserve(IPlayerSession player);
diff --git a/Content.Server/Mobs/Roles/SuspicionInnocentRole.cs b/Content.Server/Mobs/Roles/SuspicionInnocentRole.cs
index e8ab7a96be..570e307c79 100644
--- a/Content.Server/Mobs/Roles/SuspicionInnocentRole.cs
+++ b/Content.Server/Mobs/Roles/SuspicionInnocentRole.cs
@@ -24,7 +24,7 @@ namespace Content.Server.Mobs.Roles
base.Greet();
var chat = IoCManager.Resolve();
- chat.DispatchServerMessage(Mind.Session, $"You're a {Name}!");
+ chat.DispatchServerMessage(Mind.Session, $"You're an {Name}!");
chat.DispatchServerMessage(Mind.Session, $"Objective: {Objective}");
}
}
diff --git a/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs b/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs
index 753f5424e1..8a834564fd 100644
--- a/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs
+++ b/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs
@@ -1,6 +1,8 @@
+using System.Collections.Generic;
+using System.Linq;
using Content.Server.Interfaces.Chat;
using Content.Shared.Roles;
-using Robust.Shared.IoC;
+using Robust.Shared.Localization;
namespace Content.Server.Mobs.Roles
{
@@ -19,13 +21,25 @@ namespace Content.Server.Mobs.Roles
public string Objective => Prototype.Objective;
public override bool Antagonist { get; }
- public override void Greet()
+ public void GreetSuspicion(List traitors, IChatManager chatMgr)
{
- base.Greet();
+ chatMgr.DispatchServerMessage(Mind.Session, Loc.GetString("You're a {0}!", Name));
+ chatMgr.DispatchServerMessage(Mind.Session, Loc.GetString("Objective: {0}", Objective));
- var chat = IoCManager.Resolve();
- chat.DispatchServerMessage(Mind.Session, $"You're a {Name}!");
- chat.DispatchServerMessage(Mind.Session, $"Objective: {Objective}");
+ if (traitors.Count == 1)
+ {
+ // Only traitor.
+ chatMgr.DispatchServerMessage(Mind.Session, Loc.GetString("You're on your own. Good luck!"));
+ return;
+ }
+
+ var text = string.Join(", ", traitors.Where(p => p != this).Select(p => p.Mind.CharacterName));
+
+ var pluralText = Loc.GetPluralString("Your partner in crime is: {0}",
+ "Your partners in crime are: {0}",
+ traitors.Count-1, text);
+
+ chatMgr.DispatchServerMessage(Mind.Session, pluralText);
}
}
}
diff --git a/Content.Server/Preferences/PreferencesDatabase.cs b/Content.Server/Preferences/PreferencesDatabase.cs
index 8f13a1324e..e4a0b4176f 100644
--- a/Content.Server/Preferences/PreferencesDatabase.cs
+++ b/Content.Server/Preferences/PreferencesDatabase.cs
@@ -60,7 +60,7 @@ namespace Content.Server.Preferences
await _prefsSemaphore.WaitAsync();
try
{
- index = FloatMath.Clamp(index, 0, _maxCharacterSlots - 1);
+ index = MathHelper.Clamp(index, 0, _maxCharacterSlots - 1);
await _prefsDb.SaveSelectedCharacterIndex(username, index);
}
finally
diff --git a/Content.Server/Preferences/ServerPreferencesManager.cs b/Content.Server/Preferences/ServerPreferencesManager.cs
index 9b10a7e725..ac9e944f56 100644
--- a/Content.Server/Preferences/ServerPreferencesManager.cs
+++ b/Content.Server/Preferences/ServerPreferencesManager.cs
@@ -50,11 +50,11 @@ namespace Content.Server.Preferences
{
case "sqlite":
var configPreferencesDbPath = _configuration.GetCVar("database.prefs_sqlite_dbpath");
- var finalPreferencesDbPath =
+ var inMemory = _resourceManager.UserData.RootDir == null;
+ var finalPreferencesDbPath = inMemory ?
+ null :
Path.Combine(_resourceManager.UserData.RootDir, configPreferencesDbPath);
- dbConfig = new SqliteConfiguration(
- finalPreferencesDbPath
- );
+ dbConfig = new SqliteConfiguration(finalPreferencesDbPath);
break;
case "postgres":
dbConfig = new PostgresConfiguration(
diff --git a/Content.Server/StationEvents/StationEventCommand.cs b/Content.Server/StationEvents/StationEventCommand.cs
index 6712c8fa46..85f335b2ff 100644
--- a/Content.Server/StationEvents/StationEventCommand.cs
+++ b/Content.Server/StationEvents/StationEventCommand.cs
@@ -1,5 +1,4 @@
#nullable enable
-using Content.Server.GameObjects.EntitySystems;
using Content.Server.GameObjects.EntitySystems.StationEvents;
using JetBrains.Annotations;
using Robust.Server.Interfaces.Console;
diff --git a/Content.Shared/GameObjects/Components/Disposal/SharedDisposalRouterComponent.cs b/Content.Shared/GameObjects/Components/Disposal/SharedDisposalRouterComponent.cs
new file mode 100644
index 0000000000..f580fcbfbd
--- /dev/null
+++ b/Content.Shared/GameObjects/Components/Disposal/SharedDisposalRouterComponent.cs
@@ -0,0 +1,55 @@
+using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Components.UserInterface;
+using Robust.Shared.Serialization;
+using System;
+using System.Text.RegularExpressions;
+
+namespace Content.Shared.GameObjects.Components.Disposal
+{
+ public class SharedDisposalRouterComponent : Component
+ {
+ public override string Name => "DisposalRouter";
+
+ public static readonly Regex TagRegex = new Regex("^[a-zA-Z0-9, ]*$", RegexOptions.Compiled);
+
+ [Serializable, NetSerializable]
+ public class DisposalRouterUserInterfaceState : BoundUserInterfaceState
+ {
+ public readonly string Tags;
+
+ public DisposalRouterUserInterfaceState(string tags)
+ {
+ Tags = tags;
+ }
+ }
+
+ [Serializable, NetSerializable]
+ public class UiActionMessage : BoundUserInterfaceMessage
+ {
+ public readonly UiAction Action;
+ public readonly string Tags = "";
+
+ public UiActionMessage(UiAction action, string tags)
+ {
+ Action = action;
+
+ if (Action == UiAction.Ok)
+ {
+ Tags = tags.Substring(0, Math.Min(tags.Length, 150));
+ }
+ }
+ }
+
+ [Serializable, NetSerializable]
+ public enum UiAction
+ {
+ Ok
+ }
+
+ [Serializable, NetSerializable]
+ public enum DisposalRouterUiKey
+ {
+ Key
+ }
+ }
+}
diff --git a/Content.Shared/GameObjects/Components/Disposal/SharedDisposalTaggerComponent.cs b/Content.Shared/GameObjects/Components/Disposal/SharedDisposalTaggerComponent.cs
new file mode 100644
index 0000000000..f20d6248d4
--- /dev/null
+++ b/Content.Shared/GameObjects/Components/Disposal/SharedDisposalTaggerComponent.cs
@@ -0,0 +1,56 @@
+#nullable enable
+using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Components.UserInterface;
+using Robust.Shared.Serialization;
+using System;
+using System.Text.RegularExpressions;
+
+namespace Content.Shared.GameObjects.Components.Disposal
+{
+ public class SharedDisposalTaggerComponent : Component
+ {
+ public override string Name => "DisposalTagger";
+
+ public static readonly Regex TagRegex = new Regex("^[a-zA-Z0-9 ]*$", RegexOptions.Compiled);
+
+ [Serializable, NetSerializable]
+ public class DisposalTaggerUserInterfaceState : BoundUserInterfaceState
+ {
+ public readonly string Tag;
+
+ public DisposalTaggerUserInterfaceState(string tag)
+ {
+ Tag = tag;
+ }
+ }
+
+ [Serializable, NetSerializable]
+ public class UiActionMessage : BoundUserInterfaceMessage
+ {
+ public readonly UiAction Action;
+ public readonly string Tag = "";
+
+ public UiActionMessage(UiAction action, string tag)
+ {
+ Action = action;
+
+ if (Action == UiAction.Ok)
+ {
+ Tag = tag.Substring(0, Math.Min(tag.Length, 30));
+ }
+ }
+ }
+
+ [Serializable, NetSerializable]
+ public enum UiAction
+ {
+ Ok
+ }
+
+ [Serializable, NetSerializable]
+ public enum DisposalTaggerUiKey
+ {
+ Key
+ }
+ }
+}
diff --git a/Content.Shared/GameObjects/Components/Medical/SharedMedicalScannerComponent.cs b/Content.Shared/GameObjects/Components/Medical/SharedMedicalScannerComponent.cs
index 8573a50c63..10262d1fd2 100644
--- a/Content.Shared/GameObjects/Components/Medical/SharedMedicalScannerComponent.cs
+++ b/Content.Shared/GameObjects/Components/Medical/SharedMedicalScannerComponent.cs
@@ -17,15 +17,18 @@ namespace Content.Shared.GameObjects.Components.Medical
public readonly EntityUid? Entity;
public readonly Dictionary DamageClasses;
public readonly Dictionary DamageTypes;
+ public readonly bool IsScanned;
public MedicalScannerBoundUserInterfaceState(
EntityUid? entity,
Dictionary damageClasses,
- Dictionary damageTypes)
+ Dictionary damageTypes,
+ bool isScanned)
{
Entity = entity;
DamageClasses = damageClasses;
DamageTypes = damageTypes;
+ IsScanned = isScanned;
}
public bool HasDamage()
@@ -56,5 +59,24 @@ namespace Content.Shared.GameObjects.Components.Medical
Green,
Yellow,
}
+
+ [Serializable, NetSerializable]
+ public enum UiButton
+ {
+ ScanDNA,
+ }
+
+ [Serializable, NetSerializable]
+ public class UiButtonPressedMessage : BoundUserInterfaceMessage
+ {
+ public readonly UiButton Button;
+
+ public UiButtonPressedMessage(UiButton button)
+ {
+ Button = button;
+ }
+ }
+
+
}
}
diff --git a/Content.Shared/GameObjects/Components/Movement/SharedClimbableComponent.cs b/Content.Shared/GameObjects/Components/Movement/SharedClimbableComponent.cs
new file mode 100644
index 0000000000..951d051ac1
--- /dev/null
+++ b/Content.Shared/GameObjects/Components/Movement/SharedClimbableComponent.cs
@@ -0,0 +1,11 @@
+using Robust.Shared.GameObjects;
+
+namespace Content.Shared.GameObjects.Components.Movement
+{
+ public interface IClimbable { };
+
+ public class SharedClimbableComponent : Component, IClimbable
+ {
+ public sealed override string Name => "Climbable";
+ }
+}
diff --git a/Content.Shared/GameObjects/Components/Movement/SharedClimbingComponent.cs b/Content.Shared/GameObjects/Components/Movement/SharedClimbingComponent.cs
new file mode 100644
index 0000000000..3fdb287576
--- /dev/null
+++ b/Content.Shared/GameObjects/Components/Movement/SharedClimbingComponent.cs
@@ -0,0 +1,66 @@
+using Content.Shared.GameObjects.EntitySystems;
+using Content.Shared.Physics;
+using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Components;
+using Robust.Shared.Physics;
+using Robust.Shared.Serialization;
+using System;
+
+namespace Content.Shared.GameObjects.Components.Movement
+{
+ public abstract class SharedClimbingComponent : Component, IActionBlocker, ICollideSpecial
+ {
+ public sealed override string Name => "Climbing";
+ public sealed override uint? NetID => ContentNetIDs.CLIMBING;
+
+ protected ICollidableComponent Body;
+ protected bool IsOnClimbableThisFrame = false;
+
+ protected bool OwnerIsTransitioning
+ {
+ get
+ {
+ if (Body.TryGetController(out var controller))
+ {
+ return controller.IsActive;
+ }
+
+ return false;
+ }
+ }
+
+ public abstract bool IsClimbing { get; set; }
+
+ bool IActionBlocker.CanMove() => !OwnerIsTransitioning;
+ bool IActionBlocker.CanChangeDirection() => !OwnerIsTransitioning;
+
+ bool ICollideSpecial.PreventCollide(IPhysBody collided)
+ {
+ if (((CollisionGroup)collided.CollisionLayer).HasFlag(CollisionGroup.VaultImpassable) && collided.Entity.HasComponent())
+ {
+ IsOnClimbableThisFrame = true;
+ return IsClimbing;
+ }
+
+ return false;
+ }
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ Owner.TryGetComponent(out Body);
+ }
+
+ [Serializable, NetSerializable]
+ protected sealed class ClimbModeComponentState : ComponentState
+ {
+ public ClimbModeComponentState(bool climbing) : base(ContentNetIDs.CLIMBING)
+ {
+ Climbing = climbing;
+ }
+
+ public bool Climbing { get; }
+ }
+ }
+}
diff --git a/Content.Shared/GameObjects/Components/Movement/SharedPlayerInputMoverComponent.cs b/Content.Shared/GameObjects/Components/Movement/SharedPlayerInputMoverComponent.cs
index 6d47299c4d..a3b3794d3e 100644
--- a/Content.Shared/GameObjects/Components/Movement/SharedPlayerInputMoverComponent.cs
+++ b/Content.Shared/GameObjects/Components/Movement/SharedPlayerInputMoverComponent.cs
@@ -56,7 +56,7 @@ namespace Content.Shared.GameObjects.Components.Movement
{
get
{
- if (Owner.TryGetComponent(out MovementSpeedModifierComponent component))
+ if (Owner.TryGetComponent(out MovementSpeedModifierComponent? component))
{
return component.CurrentWalkSpeed;
}
@@ -69,7 +69,7 @@ namespace Content.Shared.GameObjects.Components.Movement
{
get
{
- if (Owner.TryGetComponent(out MovementSpeedModifierComponent component))
+ if (Owner.TryGetComponent(out MovementSpeedModifierComponent? component))
{
return component.CurrentSprintSpeed;
}
diff --git a/Content.Shared/GameObjects/Components/PDA/SharedPDAComponent.cs b/Content.Shared/GameObjects/Components/PDA/SharedPDAComponent.cs
index 8e24cbcd95..cb2fdcd60e 100644
--- a/Content.Shared/GameObjects/Components/PDA/SharedPDAComponent.cs
+++ b/Content.Shared/GameObjects/Components/PDA/SharedPDAComponent.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Serialization;
@@ -108,7 +108,7 @@ namespace Content.Shared.GameObjects.Components.PDA
[NetSerializable, Serializable]
public enum PDAVisuals
{
- ScreenLit,
+ FlashlightLit,
}
[NetSerializable, Serializable]
diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs
index 337a543627..7d7bcfdc4f 100644
--- a/Content.Shared/GameObjects/ContentNetIDs.cs
+++ b/Content.Shared/GameObjects/ContentNetIDs.cs
@@ -54,7 +54,6 @@
public const uint STUNNABLE = 1048;
public const uint HUNGER = 1049;
public const uint THIRST = 1050;
-
public const uint FLASHABLE = 1051;
public const uint BUCKLE = 1052;
public const uint PROJECTILE = 1053;
@@ -65,6 +64,7 @@
public const uint DO_AFTER = 1058;
public const uint RADIATION_PULSE = 1059;
public const uint BODY_MANAGER = 1060;
+ public const uint CLIMBING = 1061;
// Net IDs for integration tests.
public const uint PREDICTION_TEST = 10001;
diff --git a/Content.Shared/GameObjects/EntitySystems/ActionBlockerSystem.cs b/Content.Shared/GameObjects/EntitySystems/ActionBlockerSystem.cs
index c06963d53f..e7a32bf4fe 100644
--- a/Content.Shared/GameObjects/EntitySystems/ActionBlockerSystem.cs
+++ b/Content.Shared/GameObjects/EntitySystems/ActionBlockerSystem.cs
@@ -10,21 +10,13 @@ namespace Content.Shared.GameObjects.EntitySystems
public interface IActionBlocker
{
bool CanMove() => true;
-
bool CanInteract() => true;
-
bool CanUse() => true;
-
bool CanThrow() => true;
-
bool CanSpeak() => true;
-
bool CanDrop() => true;
-
bool CanPickup() => true;
-
bool CanEmote() => true;
-
bool CanAttack() => true;
bool CanEquip() => true;
diff --git a/Content.Shared/GameObjects/EntitySystems/SharedMoverSystem.cs b/Content.Shared/GameObjects/EntitySystems/SharedMoverSystem.cs
index 49f77ad735..57c66cb3f5 100644
--- a/Content.Shared/GameObjects/EntitySystems/SharedMoverSystem.cs
+++ b/Content.Shared/GameObjects/EntitySystems/SharedMoverSystem.cs
@@ -179,7 +179,7 @@ namespace Content.Shared.GameObjects.EntitySystems
}
private static bool TryGetAttachedComponent(ICommonSession? session, [MaybeNullWhen(false)] out T component)
- where T : IComponent
+ where T : class, IComponent
{
component = default;
@@ -188,7 +188,7 @@ namespace Content.Shared.GameObjects.EntitySystems
if (ent == null || !ent.IsValid())
return false;
- if (!ent.TryGetComponent(out T comp))
+ if (!ent.TryGetComponent(out T? comp))
return false;
component = comp;
diff --git a/Content.Shared/GameObjects/Verbs/GlobalVerb.cs b/Content.Shared/GameObjects/Verbs/GlobalVerb.cs
index e14830fab3..c4ff6bfe0c 100644
--- a/Content.Shared/GameObjects/Verbs/GlobalVerb.cs
+++ b/Content.Shared/GameObjects/Verbs/GlobalVerb.cs
@@ -12,20 +12,8 @@ namespace Content.Shared.GameObjects.Verbs
/// To add a global verb to all entities,
/// define it and mark it with
///
- public abstract class GlobalVerb
+ public abstract class GlobalVerb : VerbBase
{
- ///
- /// If true, this verb requires the user to be within
- /// meters from the entity on which this verb resides.
- ///
- public virtual bool RequireInteractionRange => true;
-
- ///
- /// If true, this verb requires both the user and the entity on which
- /// this verb resides to be in the same container or no container.
- ///
- public virtual bool BlockedByContainers => true;
-
///
/// Gets the visible verb data for the user.
///
diff --git a/Content.Shared/GameObjects/Verbs/Verb.cs b/Content.Shared/GameObjects/Verbs/Verb.cs
index 723f90f1f3..89b1ac59c6 100644
--- a/Content.Shared/GameObjects/Verbs/Verb.cs
+++ b/Content.Shared/GameObjects/Verbs/Verb.cs
@@ -12,21 +12,8 @@ namespace Content.Shared.GameObjects.Verbs
/// and mark it with
///
[UsedImplicitly]
- public abstract class Verb
+ public abstract class Verb : VerbBase
{
- ///
- /// If true, this verb requires the user to be inside within
- /// meters from the entity on which this verb resides.
- ///
- public virtual bool RequireInteractionRange => true;
-
- ///
- /// If true, this verb requires both the user and the entity on which
- /// this verb resides to be in the same container or no container.
- /// OR the user can be the entity's container
- ///
- public virtual bool BlockedByContainers => true;
-
///
/// Gets the visible verb data for the user.
///
diff --git a/Content.Shared/GameObjects/Verbs/VerbBase.cs b/Content.Shared/GameObjects/Verbs/VerbBase.cs
new file mode 100644
index 0000000000..a1b6c21cd3
--- /dev/null
+++ b/Content.Shared/GameObjects/Verbs/VerbBase.cs
@@ -0,0 +1,18 @@
+namespace Content.Shared.GameObjects.Verbs
+{
+ public abstract class VerbBase
+ {
+ ///
+ /// If true, this verb requires the user to be inside within
+ /// meters from the entity on which this verb resides.
+ ///
+ public virtual bool RequireInteractionRange => true;
+
+ ///
+ /// If true, this verb requires both the user and the entity on which
+ /// this verb resides to be in the same container or no container.
+ /// OR the user can be the entity's container
+ ///
+ public virtual bool BlockedByContainers => true;
+ }
+}
diff --git a/Content.Shared/GameObjects/Verbs/VerbUtility.cs b/Content.Shared/GameObjects/Verbs/VerbUtility.cs
index 275a00330f..a876f7c11b 100644
--- a/Content.Shared/GameObjects/Verbs/VerbUtility.cs
+++ b/Content.Shared/GameObjects/Verbs/VerbUtility.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Reflection;
+using Robust.Shared.Containers;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Utility;
@@ -50,6 +51,21 @@ namespace Content.Shared.GameObjects.Verbs
}
}
+ public static bool VerbAccessChecks(IEntity user, IEntity target, VerbBase verb)
+ {
+ if (verb.RequireInteractionRange && !InVerbUseRange(user, target))
+ {
+ return false;
+ }
+
+ if (verb.BlockedByContainers && !VerbContainerCheck(user, target))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
public static bool InVerbUseRange(IEntity user, IEntity target)
{
var distanceSquared = (user.Transform.WorldPosition - target.Transform.WorldPosition)
@@ -60,5 +76,19 @@ namespace Content.Shared.GameObjects.Verbs
}
return true;
}
+
+ public static bool VerbContainerCheck(IEntity user, IEntity target)
+ {
+ if (!user.IsInSameOrNoContainer(target))
+ {
+ if (!ContainerHelpers.TryGetContainer(target, out var container) ||
+ container.Owner != user)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
}
diff --git a/Content.Shared/Physics/ClimbController.cs b/Content.Shared/Physics/ClimbController.cs
new file mode 100644
index 0000000000..a61be77317
--- /dev/null
+++ b/Content.Shared/Physics/ClimbController.cs
@@ -0,0 +1,89 @@
+#nullable enable
+using Robust.Shared.Maths;
+using Robust.Shared.Physics;
+
+namespace Content.Shared.Physics
+{
+ ///
+ /// Movement controller used by the climb system. Lerps the player from A to B.
+ /// Also does checks to make sure the player isn't blocked.
+ ///
+ public class ClimbController : VirtualController
+ {
+ private Vector2? _movingTo = null;
+ private Vector2 _lastKnownPosition = default;
+ private int _numTicksBlocked = 0;
+
+ ///
+ /// If 5 ticks have passed and our position has not changed then something is blocking us.
+ ///
+ public bool IsBlocked => _numTicksBlocked > 5 || _isMovingWrongDirection;
+
+ ///
+ /// If the controller is currently moving the player somewhere, it is considered active.
+ ///
+ public bool IsActive => _movingTo.HasValue;
+
+ private float _initialDist = default;
+ private bool _isMovingWrongDirection = false;
+
+ public void TryMoveTo(Vector2 from, Vector2 to)
+ {
+ if (ControlledComponent == null)
+ {
+ return;
+ }
+
+ _initialDist = (from - to).Length;
+ _numTicksBlocked = 0;
+ _lastKnownPosition = from;
+ _movingTo = to;
+ _isMovingWrongDirection = false;
+ }
+
+ public override void UpdateAfterProcessing()
+ {
+ base.UpdateAfterProcessing();
+
+ if (ControlledComponent == null || _movingTo == null)
+ {
+ return;
+ }
+
+ ControlledComponent.WakeBody();
+
+ if ((ControlledComponent.Owner.Transform.WorldPosition - _lastKnownPosition).Length <= 0.05f)
+ {
+ _numTicksBlocked++;
+ }
+ else
+ {
+ _numTicksBlocked = 0;
+ }
+
+ _lastKnownPosition = ControlledComponent.Owner.Transform.WorldPosition;
+
+ if ((ControlledComponent.Owner.Transform.WorldPosition - _movingTo.Value).Length <= 0.1f)
+ {
+ _movingTo = null;
+ }
+
+ if (_movingTo.HasValue)
+ {
+ var dist = (_lastKnownPosition - _movingTo.Value).Length;
+
+ if (dist > _initialDist)
+ {
+ _isMovingWrongDirection = true;
+ }
+
+ var diff = _movingTo.Value - ControlledComponent.Owner.Transform.WorldPosition;
+ LinearVelocity = diff.Normalized * 5;
+ }
+ else
+ {
+ LinearVelocity = Vector2.Zero;
+ }
+ }
+ }
+}
diff --git a/Content.Shared/SharedGameTicker.cs b/Content.Shared/SharedGameTicker.cs
index d7100f93e4..077f4c71b0 100644
--- a/Content.Shared/SharedGameTicker.cs
+++ b/Content.Shared/SharedGameTicker.cs
@@ -54,6 +54,31 @@ namespace Content.Shared
}
}
+ protected class MsgTickerLateJoinStatus : NetMessage
+ {
+ #region REQUIRED
+
+ public const MsgGroups GROUP = MsgGroups.Command;
+ public const string NAME = nameof(MsgTickerLateJoinStatus);
+
+ public bool Disallowed { get; set; }
+
+ public MsgTickerLateJoinStatus(INetChannel channel) : base(NAME, GROUP) { }
+
+ #endregion
+
+ public override void ReadFromBuffer(NetIncomingMessage buffer)
+ {
+ Disallowed = buffer.ReadBoolean();
+ }
+
+ public override void WriteToBuffer(NetOutgoingMessage buffer)
+ {
+ buffer.Write(Disallowed);
+ }
+ }
+
+
protected class MsgTickerLobbyStatus : NetMessage
{
#region REQUIRED
@@ -166,13 +191,13 @@ namespace Content.Shared
#endregion
///
- /// The Players Ready (SessionID:ready)
+ /// The Status of the Player in the lobby (ready, observer, ...)
///
- public Dictionary PlayerReady { get; set; }
+ public Dictionary PlayerStatus { get; set; }
public override void ReadFromBuffer(NetIncomingMessage buffer)
{
- PlayerReady = new Dictionary();
+ PlayerStatus = new Dictionary();
var length = buffer.ReadInt32();
for (int i = 0; i < length; i++)
{
@@ -183,16 +208,16 @@ namespace Content.Shared
{
serializer.DeserializeDirect(stream, out sessionID);
}
- var ready = buffer.ReadBoolean();
- PlayerReady.Add(sessionID, ready);
+ var status = (PlayerStatus)buffer.ReadByte();
+ PlayerStatus.Add(sessionID, status);
}
}
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
var serializer = IoCManager.Resolve();
- buffer.Write(PlayerReady.Count);
- foreach (var p in PlayerReady)
+ buffer.Write(PlayerStatus.Count);
+ foreach (var p in PlayerStatus)
{
using (var stream = new MemoryStream())
{
@@ -201,7 +226,7 @@ namespace Content.Shared
stream.TryGetBuffer(out var segment);
buffer.Write(segment);
}
- buffer.Write(p.Value);
+ buffer.Write((byte)p.Value);
}
}
}
@@ -213,7 +238,6 @@ namespace Content.Shared
public string PlayerICName;
public string Role;
public bool Antag;
-
}
protected class MsgRoundEndMessage : NetMessage
@@ -228,6 +252,7 @@ namespace Content.Shared
#endregion
public string GamemodeTitle;
+ public string RoundEndText;
public TimeSpan RoundDuration;
@@ -238,6 +263,7 @@ namespace Content.Shared
public override void ReadFromBuffer(NetIncomingMessage buffer)
{
GamemodeTitle = buffer.ReadString();
+ RoundEndText = buffer.ReadString();
var hours = buffer.ReadInt32();
var mins = buffer.ReadInt32();
@@ -264,6 +290,7 @@ namespace Content.Shared
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
buffer.Write(GamemodeTitle);
+ buffer.Write(RoundEndText);
buffer.Write(RoundDuration.Hours);
buffer.Write(RoundDuration.Minutes);
buffer.Write(RoundDuration.Seconds);
@@ -280,6 +307,13 @@ namespace Content.Shared
}
}
+
+ public enum PlayerStatus : byte
+ {
+ NotReady = 0,
+ Ready,
+ Observer,
+ }
}
}
diff --git a/Resources/Groups/groups.yml b/Resources/Groups/groups.yml
index ea45d9a588..e18adbfd95 100644
--- a/Resources/Groups/groups.yml
+++ b/Resources/Groups/groups.yml
@@ -96,6 +96,7 @@
- tilewalls
- events
- destroymechanism
+ - readyall
CanViewVar: true
CanAdminPlace: true
@@ -186,6 +187,7 @@
- tilewalls
- events
- destroymechanism
+ - readyall
CanViewVar: true
CanAdminPlace: true
CanScript: true
diff --git a/Resources/Prototypes/Entities/Constructible/Ground/table.yml b/Resources/Prototypes/Entities/Constructible/Ground/table.yml
index 568a1f958d..1dd92f308e 100644
--- a/Resources/Prototypes/Entities/Constructible/Ground/table.yml
+++ b/Resources/Prototypes/Entities/Constructible/Ground/table.yml
@@ -22,6 +22,7 @@
- type: IconSmooth
key: generic
base: solid_
+ - type: Climbable
- type: Destructible
maxHP: 50
spawnOnDestroy: SteelSheet1
diff --git a/Resources/Prototypes/Entities/Constructible/disposal.yml b/Resources/Prototypes/Entities/Constructible/disposal.yml
index 5187ce2a88..1d85dd33a6 100644
--- a/Resources/Prototypes/Entities/Constructible/disposal.yml
+++ b/Resources/Prototypes/Entities/Constructible/disposal.yml
@@ -44,6 +44,31 @@
state_anchored: pipe-s
state_broken: pipe-b
+- type: entity
+ id: DisposalTagger
+ parent: DisposalPipeBase
+ name: disposal pipe tagger
+ description: A pipe that tags entities for routing
+ components:
+ - type: Sprite
+ drawdepth: BelowFloor
+ sprite: Constructible/Power/disposal.rsi
+ state: conpipe-tagger
+ - type: Icon
+ sprite: Constructible/Power/disposal.rsi
+ state: conpipe-tagger
+ - type: DisposalTagger
+ - type: Appearance
+ visuals:
+ - type: DisposalVisualizer
+ state_free: conpipe-tagger
+ state_anchored: pipe-tagger
+ state_broken: pipe-b
+ - type: UserInterface
+ interfaces:
+ - key: enum.DisposalTaggerUiKey.Key
+ type: DisposalTaggerBoundUserInterface
+
- type: entity
id: DisposalTrunk
parent: DisposalPipeBase
@@ -131,6 +156,63 @@
- key: enum.DisposalUnitUiKey.Key
type: DisposalUnitBoundUserInterface
+- type: entity
+ id: DisposalRouter
+ parent: DisposalPipeBase
+ name: disposal router
+ description: A three-way router. Entities with matching tags get routed to the side
+ components:
+ - type: Sprite
+ drawdepth: BelowFloor
+ sprite: Constructible/Power/disposal.rsi
+ state: conpipe-j1s
+ - type: Icon
+ sprite: Constructible/Power/disposal.rsi
+ state: conpipe-j1s
+ - type: DisposalRouter
+ degrees:
+ - 0
+ - -90
+ - 180
+ - type: Appearance
+ visuals:
+ - type: DisposalVisualizer
+ state_free: conpipe-j1s
+ state_anchored: pipe-j1s
+ state_broken: pipe-b
+ - type: Flippable
+ entity: DisposalRouterFlipped
+ - type: UserInterface
+ interfaces:
+ - key: enum.DisposalRouterUiKey.Key
+ type: DisposalRouterBoundUserInterface
+
+- type: entity
+ id: DisposalRouterFlipped
+ parent: DisposalRouter
+ name: flipped router junction
+ components:
+ - type: Sprite
+ drawdepth: BelowFloor
+ sprite: Constructible/Power/disposal.rsi
+ state: conpipe-j2s
+ - type: Icon
+ sprite: Constructible/Power/disposal.rsi
+ state: conpipe-j2s
+ - type: DisposalRouter
+ degrees:
+ - 0
+ - 90
+ - 180
+ - type: Appearance
+ visuals:
+ - type: DisposalVisualizer
+ state_free: conpipe-j2s
+ state_anchored: pipe-j2s
+ state_broken: pipe-b
+ - type: Flippable
+ entity: DisposalRouter
+
- type: entity
id: DisposalJunction
parent: DisposalPipeBase
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml
index f048fefc25..ada753f860 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml
@@ -50,7 +50,7 @@
range: 1.5
arcwidth: 0
arc: claw
- damage: 90
+ damage: 10
- type: Appearance
visuals:
- type: DamageStateVisualizer
diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml
index 637f722349..e104ad5ccb 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/human.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml
@@ -141,6 +141,7 @@
- type: RotationVisualizer
- type: BuckleVisualizer
- type: CombatMode
+ - type: Climbing
- type: Teleportable
- type: CharacterInfo
- type: FootstepSound
diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml
index b2395af536..672db4b488 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml
@@ -21,7 +21,6 @@
type: PDABoundUserInterface
- type: LoopingSound
-
- type: entity
name: Assistant PDA
parent: BasePDA
@@ -39,9 +38,9 @@
layers:
- state: pda
map: ["enum.PDAVisualLayers.Base"]
- - state: unlit_pda_screen
+ - state: light_overlay
shader: unshaded
- map: ["enum.PDAVisualLayers.Unlit"]
+ map: ["enum.PDAVisualLayers.Flashlight"]
- type: entity
name: Chef PDA
@@ -53,16 +52,16 @@
idCard: ChefIDCard
- type: Icon
sprite: Objects/Devices/pda.rsi
- state: pda-chef
+ state: pda-cook
- type: Sprite
sprite: Objects/Devices/pda.rsi
netsync: false
layers:
- - state: pda-chef
+ - state: pda-cook
map: ["enum.PDAVisualLayers.Base"]
- - state: unlit_pda_screen
+ - state: light_overlay
shader: unshaded
- map: ["enum.PDAVisualLayers.Unlit"]
+ map: ["enum.PDAVisualLayers.Flashlight"]
- type: entity
name: Clown PDA
@@ -81,9 +80,9 @@
layers:
- state: pda-clown
map: ["enum.PDAVisualLayers.Base"]
- - state: unlit_pda_screen
+ - state: light_overlay
shader: unshaded
- map: ["enum.PDAVisualLayers.Unlit"]
+ map: ["enum.PDAVisualLayers.Flashlight"]
- type: Slippery
paralyzeTime: 4
@@ -104,9 +103,9 @@
layers:
- state: pda-cargo
map: ["enum.PDAVisualLayers.Base"]
- - state: unlit_pda_screen
+ - state: light_overlay
shader: unshaded
- map: ["enum.PDAVisualLayers.Unlit"]
+ map: ["enum.PDAVisualLayers.Flashlight"]
- type: entity
name: Bartender PDA
@@ -118,16 +117,16 @@
idCard: BartenderIDCard
- type: Icon
sprite: Objects/Devices/pda.rsi
- state: pda-bar
+ state: pda-bartender
- type: Sprite
sprite: Objects/Devices/pda.rsi
netsync: false
layers:
- - state: pda-bar
+ - state: pda-bartender
map: ["enum.PDAVisualLayers.Base"]
- - state: unlit_pda_screen
+ - state: light_overlay
shader: unshaded
- map: ["enum.PDAVisualLayers.Unlit"]
+ map: ["enum.PDAVisualLayers.Flashlight"]
- type: entity
@@ -140,16 +139,16 @@
idCard: JanitorIDCard
- type: Icon
sprite: Objects/Devices/pda.rsi
- state: pda-j
+ state: pda-janitor
- type: Sprite
sprite: Objects/Devices/pda.rsi
netsync: false
layers:
- - state: pda-j
+ - state: pda-janitor
map: ["enum.PDAVisualLayers.Base"]
- - state: unlit_pda_screen
+ - state: light_overlay
shader: unshaded
- map: ["enum.PDAVisualLayers.Unlit"]
+ map: ["enum.PDAVisualLayers.Flashlight"]
- type: entity
name: Captain PDA
@@ -161,16 +160,16 @@
idCard: CaptainIDCard
- type: Icon
sprite: Objects/Devices/pda.rsi
- state: pda-c
+ state: pda-captain
- type: Sprite
sprite: Objects/Devices/pda.rsi
netsync: false
layers:
- - state: pda-c
+ - state: pda-captain
map: ["enum.PDAVisualLayers.Base"]
- - state: unlit_pda_screen
+ - state: light_overlay
shader: unshaded
- map: ["enum.PDAVisualLayers.Unlit"]
+ map: ["enum.PDAVisualLayers.Flashlight"]
- type: entity
name: HoP PDA
@@ -188,9 +187,9 @@
layers:
- state: pda-hop
map: ["enum.PDAVisualLayers.Base"]
- - state: unlit_pda_screen
+ - state: light_overlay
shader: unshaded
- map: ["enum.PDAVisualLayers.Unlit"]
+ map: ["enum.PDAVisualLayers.Flashlight"]
- type: entity
name: CE PDA
@@ -208,9 +207,9 @@
layers:
- state: pda-ce
map: ["enum.PDAVisualLayers.Base"]
- - state: unlit_pda_screen
+ - state: light_overlay
shader: unshaded
- map: ["enum.PDAVisualLayers.Unlit"]
+ map: ["enum.PDAVisualLayers.Flashlight"]
- type: entity
@@ -222,16 +221,16 @@
idCard: EngineeringIDCard
- type: Icon
sprite: Objects/Devices/pda.rsi
- state: pda-e
+ state: pda-engineer
- type: Sprite
sprite: Objects/Devices/pda.rsi
netsync: false
layers:
- - state: pda-e
+ - state: pda-engineer
map: ["enum.PDAVisualLayers.Base"]
- - state: unlit_pda_screen
+ - state: light_overlay
shader: unshaded
- map: ["enum.PDAVisualLayers.Unlit"]
+ map: ["enum.PDAVisualLayers.Flashlight"]
- type: entity
name: CMO PDA
@@ -249,10 +248,9 @@
layers:
- state: pda-cmo
map: ["enum.PDAVisualLayers.Base"]
- - state: unlit_pda_screen
+ - state: light_overlay
shader: unshaded
- map: ["enum.PDAVisualLayers.Unlit"]
-
+ map: ["enum.PDAVisualLayers.Flashlight"]
- type: entity
name: Medical PDA
@@ -263,16 +261,16 @@
idCard: MedicalIDCard
- type: Icon
sprite: Objects/Devices/pda.rsi
- state: pda-m
+ state: pda-medical
- type: Sprite
sprite: Objects/Devices/pda.rsi
netsync: false
layers:
- - state: pda-m
+ - state: pda-medical
map: ["enum.PDAVisualLayers.Base"]
- - state: unlit_pda_screen
+ - state: light_overlay
shader: unshaded
- map: ["enum.PDAVisualLayers.Unlit"]
+ map: ["enum.PDAVisualLayers.Flashlight"]
- type: entity
name: RnD PDA
@@ -290,9 +288,9 @@
layers:
- state: pda-rd
map: ["enum.PDAVisualLayers.Base"]
- - state: unlit_pda_screen
+ - state: light_overlay
shader: unshaded
- map: ["enum.PDAVisualLayers.Unlit"]
+ map: ["enum.PDAVisualLayers.Flashlight"]
- type: entity
name: Science PDA
@@ -310,9 +308,9 @@
layers:
- state: pda-rd
map: ["enum.PDAVisualLayers.Base"]
- - state: unlit_pda_screen
+ - state: light_overlay
shader: unshaded
- map: ["enum.PDAVisualLayers.Unlit"]
+ map: ["enum.PDAVisualLayers.Flashlight"]
- type: entity
name: HoS PDA
@@ -330,9 +328,9 @@
layers:
- state: pda-hos
map: ["enum.PDAVisualLayers.Base"]
- - state: unlit_pda_screen
+ - state: light_overlay
shader: unshaded
- map: ["enum.PDAVisualLayers.Unlit"]
+ map: ["enum.PDAVisualLayers.Flashlight"]
- type: entity
name: Security PDA
@@ -343,13 +341,13 @@
idCard: SecurityIDCard
- type: Icon
sprite: Objects/Devices/pda.rsi
- state: pda-s
+ state: pda-security
- type: Sprite
sprite: Objects/Devices/pda.rsi
netsync: false
layers:
- - state: pda-s
+ - state: pda-security
map: ["enum.PDAVisualLayers.Base"]
- - state: unlit_pda_screen
+ - state: light_overlay
shader: unshaded
- map: ["enum.PDAVisualLayers.Unlit"]
+ map: ["enum.PDAVisualLayers.Flashlight"]
diff --git a/Resources/Prototypes/SoundCollections/toy_squeak.yml b/Resources/Prototypes/SoundCollections/toy_squeak.yml
index fa626f486c..d4675cc4ec 100644
--- a/Resources/Prototypes/SoundCollections/toy_squeak.yml
+++ b/Resources/Prototypes/SoundCollections/toy_squeak.yml
@@ -1,6 +1,6 @@
- type: soundCollection
id: ToySqueak
files:
- - /Audio/Items/toys/toysqueak1.ogg
- - /Audio/Items/toys/toysqueak2.ogg
- - /Audio/Items/toys/toysqueak3.ogg
+ - /Audio/Items/Toys/toysqueak1.ogg
+ - /Audio/Items/Toys/toysqueak2.ogg
+ - /Audio/Items/Toys/toysqueak3.ogg
diff --git a/Resources/Textures/Constructible/Power/disposal.rsi/conpipe-tagger.png b/Resources/Textures/Constructible/Power/disposal.rsi/conpipe-tagger.png
new file mode 100644
index 0000000000..e1c4637244
Binary files /dev/null and b/Resources/Textures/Constructible/Power/disposal.rsi/conpipe-tagger.png differ
diff --git a/Resources/Textures/Constructible/Power/disposal.rsi/meta.json b/Resources/Textures/Constructible/Power/disposal.rsi/meta.json
index 96aadcfa66..f11b589795 100644
--- a/Resources/Textures/Constructible/Power/disposal.rsi/meta.json
+++ b/Resources/Textures/Constructible/Power/disposal.rsi/meta.json
@@ -1 +1,779 @@
-{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "https://github.com/discordia-space/CEV-Eris/blob/bbe32606902c90f5290b57d905a3f31b84dc6d7d/icons/obj/pipes/disposal.dmi and modified by DrSmugleaf", "states": [{"name": "condisposal", "directions": 1, "delays": [[1.0]]}, {"name": "conpipe-c", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-j1", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-j1s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-j2", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-j2s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-t", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "conpipe-y", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "disposal", "directions": 1, "delays": [[1.0]]}, {"name": "disposal-charging", "directions": 1, "delays": [[1.0]]}, {"name": "disposal-flush", "directions": 1, "delays": [[0.1, 0.1, 0.1, 0.1, 0.1, 0.5, 0.1, 0.1, 0.1]]}, {"name": "dispover-charge", "directions": 1, "delays": [[0.4, 0.4]]}, {"name": "dispover-full", "directions": 1, "delays": [[0.2, 0.2]]}, {"name": "dispover-handle", "directions": 1, "delays": [[1.0]]}, {"name": "dispover-ready", "directions": 1, "delays": [[1.0]]}, {"name": "intake", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "intake-closing", "directions": 4, "delays": [[0.5, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.5, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.5, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.5, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]]}, {"name": "outlet", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "outlet-open", "directions": 4, "delays": [[0.5, 0.5, 0.5, 0.5, 0.5, 0.1, 1.5, 0.1], [0.5, 0.5, 0.5, 0.5, 0.5, 0.1, 1.5, 0.1], [0.5, 0.5, 0.5, 0.5, 0.5, 0.1, 1.5, 0.1], [0.5, 0.5, 0.5, 0.5, 0.5, 0.1, 1.5, 0.1]]}, {"name": "pipe-b", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-bf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-c", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-cf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-d", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j1", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j1f", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j1s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j1sf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j2", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j2f", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j2s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-j2sf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-s", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-sf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-t", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-tagger", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-tagger-partial", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-tf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-u", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-y", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pipe-yf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}]}
\ No newline at end of file
+{
+ "version": 1,
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "license": "CC-BY-SA-3.0",
+ "copyright": "https://github.com/discordia-space/CEV-Eris/blob/bbe32606902c90f5290b57d905a3f31b84dc6d7d/icons/obj/pipes/disposal.dmi and modified by DrSmugleaf",
+ "states": [
+ {
+ "name": "condisposal",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "conpipe-c",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "conpipe-j1",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "conpipe-j1s",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "conpipe-j2",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "conpipe-j2s",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "conpipe-s",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "conpipe-t",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "conpipe-tagger",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "conpipe-y",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "disposal",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "disposal-charging",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "disposal-flush",
+ "directions": 1,
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.5,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "dispover-charge",
+ "directions": 1,
+ "delays": [
+ [
+ 0.4,
+ 0.4
+ ]
+ ]
+ },
+ {
+ "name": "dispover-full",
+ "directions": 1,
+ "delays": [
+ [
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "dispover-handle",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "dispover-ready",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "intake",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "intake-closing",
+ "directions": 4,
+ "delays": [
+ [
+ 0.5,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.5,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.5,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ],
+ [
+ 0.5,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "outlet",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "outlet-open",
+ "directions": 4,
+ "delays": [
+ [
+ 0.5,
+ 0.5,
+ 0.5,
+ 0.5,
+ 0.5,
+ 0.1,
+ 1.5,
+ 0.1
+ ],
+ [
+ 0.5,
+ 0.5,
+ 0.5,
+ 0.5,
+ 0.5,
+ 0.1,
+ 1.5,
+ 0.1
+ ],
+ [
+ 0.5,
+ 0.5,
+ 0.5,
+ 0.5,
+ 0.5,
+ 0.1,
+ 1.5,
+ 0.1
+ ],
+ [
+ 0.5,
+ 0.5,
+ 0.5,
+ 0.5,
+ 0.5,
+ 0.1,
+ 1.5,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "pipe-b",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-bf",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-c",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-cf",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-d",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-j1",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-j1f",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-j1s",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-j1sf",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-j2",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-j2f",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-j2s",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-j2sf",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-s",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-sf",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-t",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-tagger",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-tagger-partial",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-tf",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-u",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-y",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "pipe-yf",
+ "directions": 4,
+ "delays": [
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ],
+ [
+ 1.0
+ ]
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Resources/Textures/Constructible/Power/disposal.rsi/pipe-tagger.png b/Resources/Textures/Constructible/Power/disposal.rsi/pipe-tagger.png
index a8463fd0c1..ce48830a50 100644
Binary files a/Resources/Textures/Constructible/Power/disposal.rsi/pipe-tagger.png and b/Resources/Textures/Constructible/Power/disposal.rsi/pipe-tagger.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/id_overlay.png b/Resources/Textures/Objects/Devices/pda.rsi/id_overlay.png
new file mode 100644
index 0000000000..3f5d310e70
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/id_overlay.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/insert_overlay.png b/Resources/Textures/Objects/Devices/pda.rsi/insert_overlay.png
new file mode 100644
index 0000000000..61ba781c1f
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/insert_overlay.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/light_overlay.png b/Resources/Textures/Objects/Devices/pda.rsi/light_overlay.png
new file mode 100644
index 0000000000..286a6c3255
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/light_overlay.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/meta.json b/Resources/Textures/Objects/Devices/pda.rsi/meta.json
index d3bb5b7f05..2ea705a466 100644
--- a/Resources/Textures/Objects/Devices/pda.rsi/meta.json
+++ b/Resources/Textures/Objects/Devices/pda.rsi/meta.json
@@ -1 +1,367 @@
-{"version":1,"size":{"x":32,"y":32},"states":[{"name":"pda","directions":1,"delays":[[1]]},{"name":"unlit_pda_screen","directions":1,"delays":[[1]]},{"name":"pda-atmo","directions":1,"delays":[[1]]},{"name":"pda-bar","directions":1,"delays":[[1]]},{"name":"pda-c","directions":1,"delays":[[1]]},{"name":"pda-cargo","directions":1,"delays":[[1]]},{"name":"pda-ce","directions":1,"delays":[[1]]},{"name":"pda-chef","directions":1,"delays":[[1]]},{"name":"pda-chem","directions":1,"delays":[[1]]},{"name":"pda-clown","directions":1,"delays":[[1]]},{"name":"pda-cmo","directions":1,"delays":[[1]]},{"name":"pda-det","directions":1,"delays":[[1]]},{"name":"pda-e","directions":1,"delays":[[1]]},{"name":"pda-h","directions":1,"delays":[[1]]},{"name":"pda-holy","directions":1,"delays":[[1]]},{"name":"pda-hop","directions":1,"delays":[[1]]},{"name":"pda-hos","directions":1,"delays":[[1]]},{"name":"pda-hydro","directions":1,"delays":[[1]]},{"name":"pda-j","directions":1,"delays":[[1]]},{"name":"pda-lawyer","directions":1,"delays":[[1]]},{"name":"pda-lawyer-old","directions":1,"delays":[[1]]},{"name":"pda-libb","directions":1,"delays":[[1]]},{"name":"pda-libc","directions":1,"delays":[[0.1,0.1,0.1,0.1]]},{"name":"pda-m","directions":1,"delays":[[1]]},{"name":"pda-mime","directions":1,"delays":[[1]]},{"name":"pda-miner","directions":1,"delays":[[1]]},{"name":"pda-q","directions":1,"delays":[[1]]},{"name":"pda-r","directions":1,"delays":[[0.8,0.8]]},{"name":"pda-rd","directions":1,"delays":[[1]]},{"name":"pda-robot","directions":1,"delays":[[1]]},{"name":"pda-s","directions":1,"delays":[[1]]},{"name":"pda-syn","directions":1,"delays":[[1]]},{"name":"pda-tox","directions":1,"delays":[[1]]},{"name":"pda-transp","directions":1,"delays":[[1]]},{"name":"pda-v","directions":1,"delays":[[1]]},{"name":"pda-warden","directions":1,"delays":[[1]]},{"name":"pda_pen","directions":1,"delays":[[1]]},{"name":"pdabox","directions":1,"delays":[[1]]}]}
+{
+ "version": 1,
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "license": "CC-BY-SA 3.0",
+ "copyright": "https://github.com/tgstation/tgstation/commit/59f2a4e10e5ba36033c9734ddebfbbdc6157472d",
+ "states": [
+ {
+ "name": "id_overlay",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "insert_overlay",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "light_overlay",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pai_off_overlay",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pai_overlay",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-atmos",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-bartender",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-captain",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-cargo",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-ce",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-chaplain",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-chemistry",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-clear",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-clown",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-cmo",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-cook",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-detective",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-engineer",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-genetics",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-hop",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-hos",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-hydro",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-janitor",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-lawyer",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-library",
+ "directions": 1,
+ "delays": [
+ [
+ 0.3,
+ 0.3,
+ 0.3,
+ 0.3
+ ]
+ ]
+ },
+ {
+ "name": "pda-medical",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-mime",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-miner",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-qm",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-r",
+ "directions": 1,
+ "delays": [
+ [
+ 0.8,
+ 0.8
+ ]
+ ]
+ },
+ {
+ "name": "pda-r-library",
+ "directions": 1,
+ "delays": [
+ [
+ 0.8,
+ 0.8
+ ]
+ ]
+ },
+ {
+ "name": "pda-rd",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-roboticist",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-science",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-security",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-syndi",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-virology",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ },
+ {
+ "name": "pda-warden",
+ "directions": 1,
+ "delays": [
+ [
+ 1
+ ]
+ ]
+ }
+ ]
+}
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pai_off_overlay.png b/Resources/Textures/Objects/Devices/pda.rsi/pai_off_overlay.png
new file mode 100644
index 0000000000..7d557d092e
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pai_off_overlay.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pai_overlay.png b/Resources/Textures/Objects/Devices/pda.rsi/pai_overlay.png
new file mode 100644
index 0000000000..92d93851cf
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pai_overlay.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-atmo.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-atmo.png
deleted file mode 100644
index b3977d4b42..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-atmo.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-atmos.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-atmos.png
new file mode 100644
index 0000000000..b1eb54fe6b
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-atmos.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-bar.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-bar.png
deleted file mode 100644
index 8b6942ceef..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-bar.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-bartender.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-bartender.png
new file mode 100644
index 0000000000..c9348a7cf4
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-bartender.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-c.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-c.png
deleted file mode 100644
index 67dd678967..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-c.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-captain.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-captain.png
new file mode 100644
index 0000000000..c9880c57a6
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-captain.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-cargo.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-cargo.png
index f43bcb2481..8477ee86da 100644
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-cargo.png and b/Resources/Textures/Objects/Devices/pda.rsi/pda-cargo.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-ce.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-ce.png
index 9ed242f17d..d1516cb24a 100644
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-ce.png and b/Resources/Textures/Objects/Devices/pda.rsi/pda-ce.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-chaplain.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-chaplain.png
new file mode 100644
index 0000000000..a1695844b2
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-chaplain.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-chef.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-chef.png
deleted file mode 100644
index 00e98af427..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-chef.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-chem.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-chem.png
deleted file mode 100644
index 1bafc8f3d0..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-chem.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-chemistry.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-chemistry.png
new file mode 100644
index 0000000000..e8a4c7cec5
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-chemistry.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-clear.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-clear.png
new file mode 100644
index 0000000000..02de198e99
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-clear.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-clown.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-clown.png
index 7f31664a00..fd0b30f0b3 100644
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-clown.png and b/Resources/Textures/Objects/Devices/pda.rsi/pda-clown.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-cmo.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-cmo.png
index d22662c5b3..5d91356252 100644
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-cmo.png and b/Resources/Textures/Objects/Devices/pda.rsi/pda-cmo.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-cook.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-cook.png
new file mode 100644
index 0000000000..ab7feeab4c
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-cook.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-det.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-det.png
deleted file mode 100644
index e93ec473b4..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-det.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-detective.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-detective.png
new file mode 100644
index 0000000000..1e75195ec9
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-detective.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-e.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-e.png
deleted file mode 100644
index d2caa6827e..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-e.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-engineer.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-engineer.png
new file mode 100644
index 0000000000..42e81c0052
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-engineer.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-genetics.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-genetics.png
new file mode 100644
index 0000000000..bce7b0f55f
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-genetics.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-h.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-h.png
deleted file mode 100644
index 39b498c96d..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-h.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-holy.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-holy.png
deleted file mode 100644
index bb433abdfe..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-holy.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-hop.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-hop.png
index 040704ca53..05dca8a449 100644
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-hop.png and b/Resources/Textures/Objects/Devices/pda.rsi/pda-hop.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-hos.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-hos.png
index 1346ac1d2c..3a6c5e6078 100644
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-hos.png and b/Resources/Textures/Objects/Devices/pda.rsi/pda-hos.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-hydro.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-hydro.png
index 1cca5cea17..386ac64239 100644
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-hydro.png and b/Resources/Textures/Objects/Devices/pda.rsi/pda-hydro.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-j.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-j.png
deleted file mode 100644
index 5091e9fa04..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-j.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-janitor.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-janitor.png
new file mode 100644
index 0000000000..85405e4df5
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-janitor.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-lawyer-old.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-lawyer-old.png
deleted file mode 100644
index 4fe3013540..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-lawyer-old.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-lawyer.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-lawyer.png
index 6f9496648d..d18a4d2911 100644
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-lawyer.png and b/Resources/Textures/Objects/Devices/pda.rsi/pda-lawyer.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-libb.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-libb.png
deleted file mode 100644
index 579c665884..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-libb.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-libc.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-libc.png
deleted file mode 100644
index 054b0a39f4..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-libc.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-library.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-library.png
new file mode 100644
index 0000000000..6e8ed2bdf2
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-library.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-m.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-m.png
deleted file mode 100644
index 3adb51ab57..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-m.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-medical.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-medical.png
new file mode 100644
index 0000000000..53ca842166
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-medical.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-mime.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-mime.png
index 3a97e0650c..3aaa225257 100644
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-mime.png and b/Resources/Textures/Objects/Devices/pda.rsi/pda-mime.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-miner.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-miner.png
index 42734a457d..a5c7fb5b5e 100644
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-miner.png and b/Resources/Textures/Objects/Devices/pda.rsi/pda-miner.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-q.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-q.png
deleted file mode 100644
index d90ee0c144..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-q.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-qm.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-qm.png
new file mode 100644
index 0000000000..4b8ac99881
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-qm.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-r-library.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-r-library.png
new file mode 100644
index 0000000000..4d36793ed9
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-r-library.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-r.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-r.png
index 8382078b26..b6b37bfc7b 100644
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-r.png and b/Resources/Textures/Objects/Devices/pda.rsi/pda-r.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-rd.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-rd.png
index 99669734b1..1d84af29b7 100644
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-rd.png and b/Resources/Textures/Objects/Devices/pda.rsi/pda-rd.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-robot.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-robot.png
deleted file mode 100644
index 1923f8ee03..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-robot.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-roboticist.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-roboticist.png
new file mode 100644
index 0000000000..c8df94faf0
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-roboticist.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-s.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-s.png
deleted file mode 100644
index 005e75870e..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-s.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-science.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-science.png
new file mode 100644
index 0000000000..c7b103cf24
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-science.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-security.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-security.png
new file mode 100644
index 0000000000..0af0eafcce
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-security.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-syn.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-syn.png
deleted file mode 100644
index cf21c285e7..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-syn.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-syndi.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-syndi.png
new file mode 100644
index 0000000000..51fa1bd294
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-syndi.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-tox.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-tox.png
deleted file mode 100644
index 1425d9e5ae..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-tox.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-transp.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-transp.png
deleted file mode 100644
index e140da7f04..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-transp.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-v.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-v.png
deleted file mode 100644
index b5c169dde3..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-v.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-virology.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-virology.png
new file mode 100644
index 0000000000..e259406b58
Binary files /dev/null and b/Resources/Textures/Objects/Devices/pda.rsi/pda-virology.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda-warden.png b/Resources/Textures/Objects/Devices/pda.rsi/pda-warden.png
index a6ec69de0f..0eff588698 100644
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda-warden.png and b/Resources/Textures/Objects/Devices/pda.rsi/pda-warden.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda.png b/Resources/Textures/Objects/Devices/pda.rsi/pda.png
index 0f0abfbe1e..8473ddaab1 100644
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda.png and b/Resources/Textures/Objects/Devices/pda.rsi/pda.png differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pda_pen.png b/Resources/Textures/Objects/Devices/pda.rsi/pda_pen.png
deleted file mode 100644
index 324a706220..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pda_pen.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/pdabox.png b/Resources/Textures/Objects/Devices/pda.rsi/pdabox.png
deleted file mode 100644
index 4f5012c896..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/pdabox.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Devices/pda.rsi/unlit_pda_screen.png b/Resources/Textures/Objects/Devices/pda.rsi/unlit_pda_screen.png
deleted file mode 100644
index 93e998e3c7..0000000000
Binary files a/Resources/Textures/Objects/Devices/pda.rsi/unlit_pda_screen.png and /dev/null differ
diff --git a/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/meta.json b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/meta.json
new file mode 100644
index 0000000000..36d0f22970
--- /dev/null
+++ b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/meta.json
@@ -0,0 +1,88 @@
+{
+ "version": 1,
+ "license": "CC BY-SA 3.0",
+ "copyright": "Taken from https://github.com/tgstation/tgstation at commit 9bebd81ae0b0a7f952b59886a765c681205de31f",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "pod_0",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "scanner",
+ "directions": 1,
+ "delays": [
+ [
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "scanner_maintenance",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "scanner_occupied",
+ "directions": 1,
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "scanner_open",
+ "directions": 1,
+ "delays": [
+ [
+ 0.2,
+ 0.2
+ ]
+ ]
+ },
+ {
+ "name": "scanner_open_maintenance",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "scanner_open_unpowered",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ },
+ {
+ "name": "scanner_unpowered",
+ "directions": 1,
+ "delays": [
+ [
+ 1.0
+ ]
+ ]
+ }
+ ]
+}
diff --git a/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/pod_0.png b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/pod_0.png
new file mode 100644
index 0000000000..13d289fd6c
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/pod_0.png differ
diff --git a/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner.png b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner.png
new file mode 100644
index 0000000000..01b9c37f9e
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner.png differ
diff --git a/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_maintenance.png b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_maintenance.png
new file mode 100644
index 0000000000..b659cb7f35
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_maintenance.png differ
diff --git a/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_occupied.png b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_occupied.png
new file mode 100644
index 0000000000..427daed697
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_occupied.png differ
diff --git a/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_open.png b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_open.png
new file mode 100644
index 0000000000..4af3b01611
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_open.png differ
diff --git a/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_open_maintenance.png b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_open_maintenance.png
new file mode 100644
index 0000000000..513eb24d71
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_open_maintenance.png differ
diff --git a/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_open_unpowered.png b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_open_unpowered.png
new file mode 100644
index 0000000000..206e0c02ac
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_open_unpowered.png differ
diff --git a/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_unpowered.png b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_unpowered.png
new file mode 100644
index 0000000000..4d0705aac0
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/Cloning.rsi/scanner_unpowered.png differ
diff --git a/RobustToolbox b/RobustToolbox
index 1513389fc1..f0824212da 160000
--- a/RobustToolbox
+++ b/RobustToolbox
@@ -1 +1 @@
-Subproject commit 1513389fc195442a0f08b0af488a25affb33d410
+Subproject commit f0824212da5d913a51c8e40dee9a6622b9d5d1b7