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