diff --git a/Content.Client/Atmos/GasTileOverlay.cs b/Content.Client/Atmos/GasTileOverlay.cs index 8f852d0ed1..16a55249e4 100644 --- a/Content.Client/Atmos/GasTileOverlay.cs +++ b/Content.Client/Atmos/GasTileOverlay.cs @@ -40,7 +40,12 @@ namespace Content.Client.Atmos foreach (var mapGrid in _mapManager.FindGridsIntersecting(mapId, worldBounds)) { - foreach (var tile in mapGrid.GetTilesIntersecting(worldBounds)) + if (!_gasTileOverlaySystem.HasData(mapGrid.Index)) + continue; + + var gridBounds = new Box2(mapGrid.WorldToLocal(worldBounds.BottomLeft), mapGrid.WorldToLocal(worldBounds.TopRight)); + + foreach (var tile in mapGrid.GetTilesIntersecting(gridBounds)) { foreach (var (texture, color) in _gasTileOverlaySystem.GetOverlays(mapGrid.Index, tile.GridIndices)) { diff --git a/Content.Client/Chat/ChatBox.cs b/Content.Client/Chat/ChatBox.cs index eb37c86703..dbe24f1d76 100644 --- a/Content.Client/Chat/ChatBox.cs +++ b/Content.Client/Chat/ChatBox.cs @@ -35,6 +35,8 @@ namespace Content.Client.Chat public bool ReleaseFocusOnEnter { get; set; } = true; + public bool ClearOnEnter { get; set; } = true; + public ChatBox() { /*MarginLeft = -475.0f; @@ -166,12 +168,18 @@ namespace Content.Client.Chat private void Input_OnTextEntered(LineEdit.LineEditEventArgs args) { + // We set it there to true so it's set to false by TextSubmitted.Invoke if necessary + ClearOnEnter = true; + if (!string.IsNullOrWhiteSpace(args.Text)) { TextSubmitted?.Invoke(this, args.Text); } - Input.Clear(); + if (ClearOnEnter) + { + Input.Clear(); + } if (ReleaseFocusOnEnter) { diff --git a/Content.Client/Chat/ChatManager.cs b/Content.Client/Chat/ChatManager.cs index 1cf96367a6..8553c6ed58 100644 --- a/Content.Client/Chat/ChatManager.cs +++ b/Content.Client/Chat/ChatManager.cs @@ -1,17 +1,21 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Content.Client.Interfaces.Chat; using Content.Shared.Chat; using Robust.Client.Console; using Robust.Client.Interfaces.Graphics.ClientEye; using Robust.Client.Interfaces.UserInterface; +using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Network; using Robust.Shared.IoC; +using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.Maths; +using Robust.Shared.Network; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -45,6 +49,11 @@ namespace Content.Client.Chat /// private const int SpeechBubbleCap = 4; + /// + /// The max amount of characters an entity can send in one message + /// + private int _maxMessageLength = 1000; + private const char ConCmdSlash = '/'; private const char OOCAlias = '['; private const char MeAlias = '@'; @@ -89,11 +98,15 @@ namespace Content.Client.Chat public void Initialize() { _netManager.RegisterNetMessage(MsgChatMessage.NAME, _onChatMessage); + _netManager.RegisterNetMessage(ChatMaxMsgLengthMessage.NAME, _onMaxLengthReceived); _speechBubbleRoot = new LayoutContainer(); LayoutContainer.SetAnchorPreset(_speechBubbleRoot, LayoutContainer.LayoutPreset.Wide); _userInterfaceManager.StateRoot.AddChild(_speechBubbleRoot); _speechBubbleRoot.SetPositionFirst(); + + // When connexion is achieved, request the max chat message length + _netManager.Connected += new EventHandler(RequestMaxLength); } public void FrameUpdate(FrameEventArgs delta) @@ -213,6 +226,15 @@ namespace Content.Client.Chat if (string.IsNullOrWhiteSpace(text)) return; + // Check if message is longer than the character limit + if (text.Length > _maxMessageLength) + { + string locWarning = Loc.GetString("Your message exceeds {0} character limit", _maxMessageLength); + _currentChatBox?.AddLine(locWarning, ChatChannel.Server, Color.Orange); + _currentChatBox.ClearOnEnter = false; // The text shouldn't be cleared if it hasn't been sent + return; + } + switch (text[0]) { case ConCmdSlash: @@ -225,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 @@ -243,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; } @@ -323,8 +351,6 @@ namespace Content.Client.Chat private void _onChatMessage(MsgChatMessage msg) { - Logger.Debug($"{msg.Channel}: {msg.Message}"); - // Log all incoming chat to repopulate when filter is un-toggled var storedMessage = new StoredChatMessage(msg); filteredHistory.Add(storedMessage); @@ -347,6 +373,17 @@ namespace Content.Client.Chat } } + private void _onMaxLengthReceived(ChatMaxMsgLengthMessage msg) + { + _maxMessageLength = msg.MaxMessageLength; + } + + private void RequestMaxLength(object sender, NetChannelArgs args) + { + ChatMaxMsgLengthMessage msg = _netManager.CreateNetMessage(); + _netManager.ClientSendMessage(msg); + } + private void AddSpeechBubble(MsgChatMessage msg, SpeechBubble.SpeechType speechType) { if (!_entityManager.TryGetEntity(msg.SenderEntity, out var entity)) diff --git a/Content.Client/ClientContentIoC.cs b/Content.Client/ClientContentIoC.cs index 9fe86a4b73..8639ea4c24 100644 --- a/Content.Client/ClientContentIoC.cs +++ b/Content.Client/ClientContentIoC.cs @@ -5,6 +5,7 @@ using Content.Client.Interfaces.Chat; using Content.Client.Interfaces.Parallax; using Content.Client.Parallax; using Content.Client.Sandbox; +using Content.Client.StationEvents; using Content.Client.UserInterface; using Content.Client.UserInterface.Stylesheets; using Content.Client.Utility; @@ -31,6 +32,7 @@ namespace Content.Client IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } } diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs index 9a3065d77c..ef26c72a49 100644 --- a/Content.Client/EntryPoint.cs +++ b/Content.Client/EntryPoint.cs @@ -7,6 +7,7 @@ using Content.Client.Interfaces.Parallax; using Content.Client.Parallax; using Content.Client.Sandbox; using Content.Client.State; +using Content.Client.StationEvents; using Content.Client.UserInterface; using Content.Client.UserInterface.Stylesheets; using Content.Shared.GameObjects.Components; @@ -150,6 +151,7 @@ namespace Content.Client IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); + IoCManager.Resolve().Initialize(); _baseClient.RunLevelChanged += (sender, args) => { diff --git a/Content.Client/GameObjects/Components/Atmos/CanSeeGasesComponent.cs b/Content.Client/GameObjects/Components/Atmos/CanSeeGasesComponent.cs deleted file mode 100644 index d95748f72b..0000000000 --- a/Content.Client/GameObjects/Components/Atmos/CanSeeGasesComponent.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Content.Client.Atmos; -using Robust.Client.GameObjects; -using Robust.Client.Interfaces.Graphics.Overlays; -using Robust.Shared.GameObjects; -using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.IoC; - -namespace Content.Client.GameObjects.Components.Atmos -{ - [RegisterComponent] - public class CanSeeGasesComponent : Component - { - [Dependency] private readonly IOverlayManager _overlayManager = default!; - - public override string Name => "CanSeeGases"; - - public override void HandleMessage(ComponentMessage message, IComponent component) - { - base.HandleMessage(message, component); - - switch (message) - { - case PlayerAttachedMsg _: - if(!_overlayManager.HasOverlay(nameof(GasTileOverlay))) - _overlayManager.AddOverlay(new GasTileOverlay()); - break; - - case PlayerDetachedMsg _: - if(!_overlayManager.HasOverlay(nameof(GasTileOverlay))) - _overlayManager.RemoveOverlay(nameof(GasTileOverlay)); - break; - } - } - } -} diff --git a/Content.Client/GameObjects/Components/Atmos/ExtinguisherVisualizer.cs b/Content.Client/GameObjects/Components/Atmos/ExtinguisherVisualizer.cs new file mode 100644 index 0000000000..6a33b51e00 --- /dev/null +++ b/Content.Client/GameObjects/Components/Atmos/ExtinguisherVisualizer.cs @@ -0,0 +1,67 @@ +using System; +using JetBrains.Annotations; +using Robust.Client.Animations; +using Robust.Client.GameObjects; +using Robust.Client.GameObjects.Components.Animations; +using Robust.Client.Interfaces.GameObjects.Components; +using Robust.Shared.Animations; +using Robust.Shared.Maths; +using Content.Shared.GameObjects.Components; + +namespace Content.Client.GameObjects.Components.Atmos +{ + [UsedImplicitly] + public class ExtinguisherVisualizer : AppearanceVisualizer + { + + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + + if (component.Deleted) + { + return; + } + + if (component.TryGetData(ExtinguisherVisuals.Rotation, out var degrees)) + { + SetRotation(component, Angle.FromDegrees(degrees)); + } + } + + private void SetRotation(AppearanceComponent component, Angle rotation) + { + var sprite = component.Owner.GetComponent(); + + if (!sprite.Owner.TryGetComponent(out AnimationPlayerComponent animation)) + { + sprite.Rotation = rotation; + return; + } + + if (animation.HasRunningAnimation("rotate")) + { + animation.Stop("rotate"); + } + + animation.Play(new Animation + { + Length = TimeSpan.FromSeconds(0.125), + AnimationTracks = + { + new AnimationTrackComponentProperty + { + ComponentType = typeof(ISpriteComponent), + Property = nameof(ISpriteComponent.Rotation), + InterpolationMode = AnimationInterpolationMode.Linear, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(sprite.Rotation, 0), + new AnimationTrackProperty.KeyFrame(rotation, 0.125f) + } + } + } + }, "rotate"); + } + } +} diff --git a/Content.Client/GameObjects/Components/Body/BodyManagerComponent.cs b/Content.Client/GameObjects/Components/Body/BodyManagerComponent.cs new file mode 100644 index 0000000000..61049ec7a0 --- /dev/null +++ b/Content.Client/GameObjects/Components/Body/BodyManagerComponent.cs @@ -0,0 +1,71 @@ +#nullable enable +using Content.Client.GameObjects.Components.Disposal; +using Content.Client.Interfaces.GameObjects.Components.Interaction; +using Content.Shared.GameObjects.Components.Body; +using Content.Shared.GameObjects.Components.Damage; +using Robust.Client.Interfaces.GameObjects.Components; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.IoC; +using Robust.Shared.Players; + +namespace Content.Client.GameObjects.Components.Body +{ + [RegisterComponent] + [ComponentReference(typeof(IDamageableComponent))] + [ComponentReference(typeof(IBodyManagerComponent))] + public class BodyManagerComponent : SharedBodyManagerComponent, IClientDraggable + { +#pragma warning disable 649 + [Dependency] private readonly IEntityManager _entityManager = default!; +#pragma warning restore 649 + + public bool ClientCanDropOn(CanDropEventArgs eventArgs) + { + return eventArgs.Target.HasComponent(); + } + + public bool ClientCanDrag(CanDragEventArgs eventArgs) + { + return true; + } + + public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null) + { + if (!Owner.TryGetComponent(out ISpriteComponent? sprite)) + { + return; + } + + switch (message) + { + case BodyPartAddedMessage partAdded: + sprite.LayerSetVisible(partAdded.RSIMap, true); + sprite.LayerSetRSI(partAdded.RSIMap, partAdded.RSIPath); + sprite.LayerSetState(partAdded.RSIMap, partAdded.RSIState); + break; + case BodyPartRemovedMessage partRemoved: + sprite.LayerSetVisible(partRemoved.RSIMap, false); + + if (!partRemoved.Dropped.HasValue || + !_entityManager.TryGetEntity(partRemoved.Dropped.Value, out var entity) || + !entity.TryGetComponent(out ISpriteComponent? droppedSprite)) + { + break; + } + + var color = sprite[partRemoved.RSIMap].Color; + + droppedSprite.LayerSetColor(0, color); + break; + case MechanismSpriteAddedMessage mechanismAdded: + sprite.LayerSetVisible(mechanismAdded.RSIMap, true); + break; + case MechanismSpriteRemovedMessage mechanismRemoved: + sprite.LayerSetVisible(mechanismRemoved.RSIMap, false); + break; + } + } + } +} diff --git a/Content.Client/Health/BodySystem/BodyScanner/BodyScannerBoundUserInterface.cs b/Content.Client/GameObjects/Components/Body/Scanner/BodyScannerBoundUserInterface.cs similarity index 76% rename from Content.Client/Health/BodySystem/BodyScanner/BodyScannerBoundUserInterface.cs rename to Content.Client/GameObjects/Components/Body/Scanner/BodyScannerBoundUserInterface.cs index 5bf9bec18b..5b2f5b7abc 100644 --- a/Content.Client/Health/BodySystem/BodyScanner/BodyScannerBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/Body/Scanner/BodyScannerBoundUserInterface.cs @@ -1,11 +1,13 @@ using System.Collections.Generic; -using Content.Shared.Health.BodySystem.BodyScanner; +using Content.Shared.Body.Scanner; +using JetBrains.Annotations; using Robust.Client.GameObjects.Components.UserInterface; using Robust.Shared.GameObjects.Components.UserInterface; using Robust.Shared.ViewVariables; -namespace Content.Client.Health.BodySystem.BodyScanner +namespace Content.Client.GameObjects.Components.Body.Scanner { + [UsedImplicitly] public class BodyScannerBoundUserInterface : BoundUserInterface { [ViewVariables] @@ -17,9 +19,7 @@ namespace Content.Client.Health.BodySystem.BodyScanner [ViewVariables] private Dictionary _parts; - public BodyScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) - { - } + public BodyScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { } protected override void Open() { @@ -34,7 +34,9 @@ namespace Content.Client.Health.BodySystem.BodyScanner base.UpdateState(state); if (!(state is BodyScannerInterfaceState scannerState)) + { return; + } _template = scannerState.Template; _parts = scannerState.Parts; @@ -45,7 +47,13 @@ namespace Content.Client.Health.BodySystem.BodyScanner protected override void Dispose(bool disposing) { base.Dispose(disposing); - } + if (disposing) + { + _display.Dispose(); + _template = null; + _parts.Clear(); + } + } } } diff --git a/Content.Client/GameObjects/Components/Body/Scanner/BodyScannerDisplay.cs b/Content.Client/GameObjects/Components/Body/Scanner/BodyScannerDisplay.cs new file mode 100644 index 0000000000..c5dfe18f44 --- /dev/null +++ b/Content.Client/GameObjects/Components/Body/Scanner/BodyScannerDisplay.cs @@ -0,0 +1,164 @@ +using System.Collections.Generic; +using System.Globalization; +using Content.Shared.Body.Scanner; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Maths; +using static Robust.Client.UserInterface.Controls.ItemList; + +namespace Content.Client.GameObjects.Components.Body.Scanner +{ + public sealed class BodyScannerDisplay : SS14Window + { + private BodyScannerTemplateData _template; + + private Dictionary _parts; + + private List _slots; + + private BodyScannerBodyPartData _currentBodyPart; + + public BodyScannerDisplay(BodyScannerBoundUserInterface owner) + { + IoCManager.InjectDependencies(this); + Owner = owner; + Title = Loc.GetString("Body Scanner"); + + var hSplit = new HBoxContainer + { + Children = + { + // Left half + new ScrollContainer + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + Children = + { + (BodyPartList = new ItemList()) + } + }, + // Right half + new VBoxContainer + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + Children = + { + // Top half of the right half + new VBoxContainer + { + SizeFlagsVertical = SizeFlags.FillExpand, + Children = + { + (BodyPartLabel = new Label()), + new HBoxContainer + { + Children = + { + new Label + { + Text = "Health: " + }, + (BodyPartHealth = new Label()) + } + }, + new ScrollContainer + { + SizeFlagsVertical = SizeFlags.FillExpand, + Children = + { + (MechanismList = new ItemList()) + } + } + } + }, + // Bottom half of the right half + (MechanismInfoLabel = new RichTextLabel + { + SizeFlagsVertical = SizeFlags.FillExpand + }) + } + } + } + }; + + Contents.AddChild(hSplit); + + BodyPartList.OnItemSelected += BodyPartOnItemSelected; + MechanismList.OnItemSelected += MechanismOnItemSelected; + } + + public BodyScannerBoundUserInterface Owner { get; } + + protected override Vector2? CustomSize => (800, 600); + + private ItemList BodyPartList { get; } + + private Label BodyPartLabel { get; } + + private Label BodyPartHealth { get; } + + private ItemList MechanismList { get; } + + private RichTextLabel MechanismInfoLabel { get; } + + public void UpdateDisplay(BodyScannerTemplateData template, Dictionary parts) + { + _template = template; + _parts = parts; + _slots = new List(); + BodyPartList.Clear(); + + foreach (var slotName in _parts.Keys) + { + // We have to do this since ItemLists only return the index of what item is + // selected and dictionaries don't allow you to explicitly grab things by index. + // So we put the contents of the dictionary into a list so + // that we can grab the list by index. I don't know either. + _slots.Add(slotName); + + BodyPartList.AddItem(Loc.GetString(slotName)); + } + } + + public void BodyPartOnItemSelected(ItemListSelectedEventArgs args) + { + if (_parts.TryGetValue(_slots[args.ItemIndex], out _currentBodyPart)) { + UpdateBodyPartBox(_currentBodyPart, _slots[args.ItemIndex]); + } + } + + private void UpdateBodyPartBox(BodyScannerBodyPartData part, string slotName) + { + BodyPartLabel.Text = $"{Loc.GetString(slotName)}: {Loc.GetString(part.Name)}"; + BodyPartHealth.Text = $"{part.CurrentDurability}/{part.MaxDurability}"; + + MechanismList.Clear(); + foreach (var mechanism in part.Mechanisms) { + MechanismList.AddItem(mechanism.Name); + } + } + + public void MechanismOnItemSelected(ItemListSelectedEventArgs args) + { + UpdateMechanismBox(_currentBodyPart.Mechanisms[args.ItemIndex]); + } + + private void UpdateMechanismBox(BodyScannerMechanismData mechanism) + { + // TODO: Improve UI + if (mechanism == null) + { + MechanismInfoLabel.SetMessage(""); + return; + } + + var message = + Loc.GetString( + $"{mechanism.Name}\nHealth: {mechanism.CurrentDurability}/{mechanism.MaxDurability}\n{mechanism.Description}"); + + MechanismInfoLabel.SetMessage(message); + } + } +} diff --git a/Content.Client/Health/BodySystem/Surgery/GenericSurgeryBoundUserInterface.cs b/Content.Client/GameObjects/Components/Body/Surgery/GenericSurgeryBoundUserInterface.cs similarity index 72% rename from Content.Client/Health/BodySystem/Surgery/GenericSurgeryBoundUserInterface.cs rename to Content.Client/GameObjects/Components/Body/Surgery/GenericSurgeryBoundUserInterface.cs index 04c0246e83..e44f368221 100644 --- a/Content.Client/Health/BodySystem/Surgery/GenericSurgeryBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/Body/Surgery/GenericSurgeryBoundUserInterface.cs @@ -1,29 +1,30 @@ -using Content.Shared.Health.BodySystem.Surgery; +#nullable enable +using Content.Shared.Body.Surgery; +using JetBrains.Annotations; using Robust.Client.GameObjects.Components.UserInterface; using Robust.Shared.GameObjects.Components.UserInterface; -namespace Content.Client.Health.BodySystem.Surgery +namespace Content.Client.GameObjects.Components.Body.Surgery { - - //TODO : Make window close if target or surgery tool gets too far away from user. + // TODO : Make window close if target or surgery tool gets too far away from user. /// - /// Generic client-side UI list popup that allows users to choose from an option of limbs or organs to operate on. + /// Generic client-side UI list popup that allows users to choose from an option + /// of limbs or organs to operate on. /// + [UsedImplicitly] public class GenericSurgeryBoundUserInterface : BoundUserInterface { + private GenericSurgeryWindow? _window; - private GenericSurgeryWindow _window; - - public GenericSurgeryBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) - { - - } + public GenericSurgeryBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { } protected override void Open() { _window = new GenericSurgeryWindow(); + _window.OpenCentered(); + _window.OnClose += Close; } protected override void ReceiveMessage(BoundUserInterfaceMessage message) @@ -44,40 +45,42 @@ namespace Content.Client.Health.BodySystem.Surgery private void HandleBodyPartRequest(RequestBodyPartSurgeryUIMessage msg) { - _window.BuildDisplay(msg.Targets, BodyPartSelectedCallback); + _window?.BuildDisplay(msg.Targets, BodyPartSelectedCallback); } + private void HandleMechanismRequest(RequestMechanismSurgeryUIMessage msg) { - _window.BuildDisplay(msg.Targets, MechanismSelectedCallback); + _window?.BuildDisplay(msg.Targets, MechanismSelectedCallback); } + private void HandleBodyPartSlotRequest(RequestBodyPartSlotSurgeryUIMessage msg) { - _window.BuildDisplay(msg.Targets, BodyPartSlotSelectedCallback); + _window?.BuildDisplay(msg.Targets, BodyPartSlotSelectedCallback); } - - private void BodyPartSelectedCallback(int selectedOptionData) { SendMessage(new ReceiveBodyPartSurgeryUIMessage(selectedOptionData)); } + private void MechanismSelectedCallback(int selectedOptionData) { SendMessage(new ReceiveMechanismSurgeryUIMessage(selectedOptionData)); } + private void BodyPartSlotSelectedCallback(int selectedOptionData) { SendMessage(new ReceiveBodyPartSlotSurgeryUIMessage(selectedOptionData)); } - - protected override void Dispose(bool disposing) { base.Dispose(disposing); - if (!disposing) - return; - _window.Dispose(); + + if (disposing) + { + _window?.Dispose(); + } } } } diff --git a/Content.Client/Health/BodySystem/Surgery/GenericSurgeryWindow.cs b/Content.Client/GameObjects/Components/Body/Surgery/GenericSurgeryWindow.cs similarity index 57% rename from Content.Client/Health/BodySystem/Surgery/GenericSurgeryWindow.cs rename to Content.Client/GameObjects/Components/Body/Surgery/GenericSurgeryWindow.cs index baae1b145c..d432c3342a 100644 --- a/Content.Client/Health/BodySystem/Surgery/GenericSurgeryWindow.cs +++ b/Content.Client/GameObjects/Components/Body/Surgery/GenericSurgeryWindow.cs @@ -1,58 +1,62 @@ using System; using System.Collections.Generic; -using System.Globalization; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Shared.Localization; using Robust.Shared.Maths; -namespace Content.Client.Health.BodySystem.Surgery +namespace Content.Client.GameObjects.Components.Body.Surgery { public class GenericSurgeryWindow : SS14Window { - public delegate void CloseCallback(); public delegate void OptionSelectedCallback(int selectedOptionData); - private Control _vSplitContainer; - private VBoxContainer _optionsBox; + private readonly VBoxContainer _optionsBox; private OptionSelectedCallback _optionSelectedCallback; - protected override Vector2? CustomSize => (300, 400); public GenericSurgeryWindow() { Title = Loc.GetString("Select surgery target..."); RectClipContent = true; - _vSplitContainer = new VBoxContainer(); - var listScrollContainer = new ScrollContainer - { - SizeFlagsVertical = SizeFlags.FillExpand, - SizeFlagsHorizontal = SizeFlags.FillExpand, - HScrollEnabled = true, - VScrollEnabled = true - }; - _optionsBox = new VBoxContainer - { - SizeFlagsHorizontal = SizeFlags.FillExpand - }; - listScrollContainer.AddChild(_optionsBox); - _vSplitContainer.AddChild(listScrollContainer); - Contents.AddChild(_vSplitContainer); + var vSplitContainer = new VBoxContainer + { + Children = + { + new ScrollContainer + { + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsHorizontal = SizeFlags.FillExpand, + HScrollEnabled = true, + VScrollEnabled = true, + Children = + { + (_optionsBox = new VBoxContainer + { + SizeFlagsHorizontal = SizeFlags.FillExpand + }) + } + } + } + }; + + Contents.AddChild(vSplitContainer); } public void BuildDisplay(Dictionary data, OptionSelectedCallback callback) { _optionsBox.DisposeAllChildren(); _optionSelectedCallback = callback; + foreach (var (displayText, callbackData) in data) { var button = new SurgeryButton(callbackData); - button.SetOnToggleBehavior(OnButtonPressed); - button.SetDisplayText(CultureInfo.CurrentCulture.TextInfo.ToTitleCase(displayText)); + button.SetOnToggleBehavior(OnButtonPressed); + button.SetDisplayText(Loc.GetString(displayText)); _optionsBox.AddChild(button); } @@ -60,17 +64,23 @@ namespace Content.Client.Health.BodySystem.Surgery private void OnButtonPressed(BaseButton.ButtonEventArgs args) { - var pressedButton = (SurgeryButton)args.Button.Parent; - _optionSelectedCallback(pressedButton.CallbackData); + if (args.Button.Parent is SurgeryButton surgery) + { + _optionSelectedCallback(surgery.CallbackData); + } } } class SurgeryButton : PanelContainer { public Button Button { get; } + private SpriteView SpriteView { get; } + private Control EntityControl { get; } + private Label DisplayText { get; } + public int CallbackData { get; } public SurgeryButton(int callbackData) @@ -84,25 +94,28 @@ namespace Content.Client.Health.BodySystem.Surgery ToggleMode = true, MouseFilter = MouseFilterMode.Stop }; + AddChild(Button); - var hBoxContainer = new HBoxContainer(); - SpriteView = new SpriteView + + AddChild(new HBoxContainer { - CustomMinimumSize = new Vector2(32.0f, 32.0f) - }; - DisplayText = new Label - { - SizeFlagsVertical = SizeFlags.ShrinkCenter, - Text = "N/A", - }; - hBoxContainer.AddChild(SpriteView); - hBoxContainer.AddChild(DisplayText); - EntityControl = new Control - { - SizeFlagsHorizontal = SizeFlags.FillExpand - }; - hBoxContainer.AddChild(EntityControl); - AddChild(hBoxContainer); + Children = + { + (SpriteView = new SpriteView + { + CustomMinimumSize = new Vector2(32.0f, 32.0f) + }), + (DisplayText = new Label + { + SizeFlagsVertical = SizeFlags.ShrinkCenter, + Text = "N/A", + }), + (new Control + { + SizeFlagsHorizontal = SizeFlags.FillExpand + }) + } + }); } public void SetDisplayText(string text) 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/DamageableComponent.cs b/Content.Client/GameObjects/Components/DamageableComponent.cs deleted file mode 100644 index 2e42501894..0000000000 --- a/Content.Client/GameObjects/Components/DamageableComponent.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using Content.Shared.GameObjects.Components.Damage; -using Robust.Shared.GameObjects; - -namespace Content.Client.GameObjects.Components -{ - /// - /// Fuck I really hate doing this - /// TODO: make sure the client only gets damageable component on the clientside entity for its player mob - /// - [RegisterComponent] - public class DamageableComponent : SharedDamageableComponent - { - /// - public override string Name => "Damageable"; - - public Dictionary CurrentDamage = new Dictionary(); - - public override void HandleComponentState(ComponentState curState, ComponentState nextState) - { - base.HandleComponentState(curState, nextState); - - if(curState is DamageComponentState damagestate) - { - CurrentDamage = damagestate.CurrentDamage; - } - } - } -} 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/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/MedicalScanner/MedicalScannerBoundUserInterface.cs b/Content.Client/GameObjects/Components/MedicalScanner/MedicalScannerBoundUserInterface.cs index 135a55f992..2280406c5b 100644 --- a/Content.Client/GameObjects/Components/MedicalScanner/MedicalScannerBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/MedicalScanner/MedicalScannerBoundUserInterface.cs @@ -1,9 +1,11 @@ +using JetBrains.Annotations; using Robust.Client.GameObjects.Components.UserInterface; using Robust.Shared.GameObjects.Components.UserInterface; using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent; namespace Content.Client.GameObjects.Components.MedicalScanner { + [UsedImplicitly] public class MedicalScannerBoundUserInterface : BoundUserInterface { public MedicalScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) @@ -20,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 4f6eefb5d9..4668854fbf 100644 --- a/Content.Client/GameObjects/Components/MedicalScanner/MedicalScannerWindow.cs +++ b/Content.Client/GameObjects/Components/MedicalScanner/MedicalScannerWindow.cs @@ -1,6 +1,10 @@ using System.Text; +using Content.Shared.Damage; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; using Robust.Shared.Maths; using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent; @@ -8,28 +12,63 @@ 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.MaxHealth == 0) - { - text.Append("No patient data."); - } else - { - text.Append($"Patient's health: {state.CurrentHealth}/{state.MaxHealth}\n"); - if (state.DamageDictionary != null) - { - foreach (var (dmgType, amount) in state.DamageDictionary) - { - text.Append($"\n{dmgType}: {amount}"); - } - } + if (!state.Entity.HasValue || + !state.HasDamage() || + !IoCManager.Resolve().TryGetEntity(state.Entity.Value, out var entity)) + { + _diagnostics.Text = Loc.GetString("No patient data."); + ScanButton.Disabled = true; + } + else + { + text.Append($"{entity.Name}{Loc.GetString("'s health:")}\n"); + + foreach (var (@class, classAmount) in state.DamageClasses) + { + text.Append($"\n{Loc.GetString("{0}: {1}", @class, classAmount)}"); + + foreach (var type in @class.ToTypes()) + { + if (!state.DamageTypes.TryGetValue(type, out var typeAmount)) + { + continue; + } + + text.Append($"\n- {Loc.GetString("{0}: {1}", type, typeAmount)}"); + } + + text.Append("\n"); + } + + _diagnostics.Text = text.ToString(); + ScanButton.Disabled = state.IsScanned; } - Contents.AddChild(new Label(){Text = text.ToString()}); } } } diff --git a/Content.Client/GameObjects/Components/Mobs/CameraRecoilComponent.cs b/Content.Client/GameObjects/Components/Mobs/CameraRecoilComponent.cs index 7cda73f26b..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; diff --git a/Content.Client/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs b/Content.Client/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs index 44b4b7e4ce..0ddf3f175a 100644 --- a/Content.Client/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs +++ b/Content.Client/GameObjects/Components/Mobs/HumanoidAppearanceComponent.cs @@ -44,8 +44,8 @@ namespace Content.Client.GameObjects.Components.Mobs sprite.LayerSetColor(HumanoidVisualLayers.Hair, Appearance.HairColor); sprite.LayerSetColor(HumanoidVisualLayers.FacialHair, Appearance.FacialHairColor); - sprite.LayerSetState(HumanoidVisualLayers.Chest, Sex == Sex.Male ? "human_chest_m" : "human_chest_f"); - sprite.LayerSetState(HumanoidVisualLayers.Head, Sex == Sex.Male ? "human_head_m" : "human_head_f"); + sprite.LayerSetState(HumanoidVisualLayers.Chest, Sex == Sex.Male ? "torso_m" : "torso_f"); + sprite.LayerSetState(HumanoidVisualLayers.Head, Sex == Sex.Male ? "head_m" : "head_f"); sprite.LayerSetVisible(HumanoidVisualLayers.StencilMask, Sex == Sex.Female); diff --git a/Content.Client/GameObjects/Components/Mobs/SpeciesComponent.cs b/Content.Client/GameObjects/Components/Mobs/SpeciesComponent.cs deleted file mode 100644 index 6500134159..0000000000 --- a/Content.Client/GameObjects/Components/Mobs/SpeciesComponent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Content.Client.GameObjects.Components.Disposal; -using Content.Client.Interfaces.GameObjects.Components.Interaction; -using Content.Shared.GameObjects.Components.Mobs; -using Robust.Shared.GameObjects; - -namespace Content.Client.GameObjects.Components.Mobs -{ - [RegisterComponent] - [ComponentReference(typeof(SharedSpeciesComponent))] - public class SpeciesComponent : SharedSpeciesComponent, IClientDraggable - { - bool IClientDraggable.ClientCanDropOn(CanDropEventArgs eventArgs) - { - return eventArgs.Target.HasComponent(); - } - - bool IClientDraggable.ClientCanDrag(CanDragEventArgs eventArgs) - { - return true; - } - } -} 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/Mobs/SpeciesVisualizer.cs b/Content.Client/GameObjects/Components/Rotation/RotationVisualizer.cs similarity index 81% rename from Content.Client/GameObjects/Components/Mobs/SpeciesVisualizer.cs rename to Content.Client/GameObjects/Components/Rotation/RotationVisualizer.cs index cc2e60b92e..d78cf08a33 100644 --- a/Content.Client/GameObjects/Components/Mobs/SpeciesVisualizer.cs +++ b/Content.Client/GameObjects/Components/Rotation/RotationVisualizer.cs @@ -1,5 +1,6 @@ using System; -using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Rotation; +using JetBrains.Annotations; using Robust.Client.Animations; using Robust.Client.GameObjects; using Robust.Client.GameObjects.Components.Animations; @@ -7,22 +8,23 @@ using Robust.Client.Interfaces.GameObjects.Components; using Robust.Shared.Animations; using Robust.Shared.Maths; -namespace Content.Client.GameObjects.Components.Mobs +namespace Content.Client.GameObjects.Components.Rotation { - public class SpeciesVisualizer : AppearanceVisualizer + [UsedImplicitly] + public class RotationVisualizer : AppearanceVisualizer { public override void OnChangeData(AppearanceComponent component) { base.OnChangeData(component); - if (component.TryGetData(SharedSpeciesComponent.MobVisuals.RotationState, out var state)) + if (component.TryGetData(RotationVisuals.RotationState, out var state)) { switch (state) { - case SharedSpeciesComponent.MobState.Standing: + case RotationState.Vertical: SetRotation(component, 0); break; - case SharedSpeciesComponent.MobState.Down: + case RotationState.Horizontal: SetRotation(component, Angle.FromDegrees(90)); break; } @@ -40,7 +42,9 @@ namespace Content.Client.GameObjects.Components.Mobs } if (animation.HasRunningAnimation("rotate")) + { animation.Stop("rotate"); + } animation.Play(new Animation { 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/GasTileOverlaySystem.cs b/Content.Client/GameObjects/EntitySystems/GasTileOverlaySystem.cs index a3eeadd0d3..253563f527 100644 --- a/Content.Client/GameObjects/EntitySystems/GasTileOverlaySystem.cs +++ b/Content.Client/GameObjects/EntitySystems/GasTileOverlaySystem.cs @@ -1,13 +1,19 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; +using Content.Client.Atmos; +using Content.Client.GameObjects.Components.Atmos; using Content.Shared.Atmos; -using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.GameObjects.EntitySystems.Atmos; using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Client.GameObjects.EntitySystems; using Robust.Client.Graphics; +using Robust.Client.Interfaces.Graphics.Overlays; using Robust.Client.Interfaces.ResourceManagement; using Robust.Client.ResourceManagement; using Robust.Client.Utility; -using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; @@ -16,8 +22,9 @@ using Robust.Shared.Utility; namespace Content.Client.GameObjects.EntitySystems { [UsedImplicitly] - public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem + internal sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem { + [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IResourceCache _resourceCache = default!; private readonly Dictionary _fireCache = new Dictionary(); @@ -36,19 +43,20 @@ namespace Content.Client.GameObjects.EntitySystems private readonly float[][] _fireFrameDelays = new float[FireStates][]; private readonly int[] _fireFrameCounter = new int[FireStates]; private readonly Texture[][] _fireFrames = new Texture[FireStates][]; - - private Dictionary> _overlay = new Dictionary>(); + + private Dictionary> _tileData = + new Dictionary>(); public override void Initialize() { base.Initialize(); - - SubscribeNetworkEvent(new EntityEventHandler(OnTileOverlayMessage)); + SubscribeNetworkEvent(HandleGasOverlayMessage); + _mapManager.OnGridRemoved += OnGridRemoved; for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) { - var gas = Atmospherics.GetGas(i); - switch (gas.GasOverlay) + var overlay = Atmospherics.GetOverlay(i); + switch (overlay) { case SpriteSpecifier.Rsi animated: var rsi = _resourceCache.GetResource(animated.RsiPath).RSI; @@ -82,13 +90,77 @@ namespace Content.Client.GameObjects.EntitySystems _fireFrameDelays[i] = state.GetDelays(); _fireFrameCounter[i] = 0; } + + var overlayManager = IoCManager.Resolve(); + if(!overlayManager.HasOverlay(nameof(GasTileOverlay))) + overlayManager.AddOverlay(new GasTileOverlay()); + } + + private void HandleGasOverlayMessage(GasOverlayMessage message) + { + foreach (var (indices, data) in message.OverlayData) + { + var chunk = GetOrCreateChunk(message.GridId, indices); + chunk.Update(data, indices); + } + } + + // Slightly different to the server-side system version + private GasOverlayChunk GetOrCreateChunk(GridId gridId, MapIndices indices) + { + if (!_tileData.TryGetValue(gridId, out var chunks)) + { + chunks = new Dictionary(); + _tileData[gridId] = chunks; + } + + var chunkIndices = GetGasChunkIndices(indices); + + if (!chunks.TryGetValue(chunkIndices, out var chunk)) + { + chunk = new GasOverlayChunk(gridId, chunkIndices); + chunks[chunkIndices] = chunk; + } + + return chunk; + } + + public override void Shutdown() + { + base.Shutdown(); + _mapManager.OnGridRemoved -= OnGridRemoved; + var overlayManager = IoCManager.Resolve(); + if(!overlayManager.HasOverlay(nameof(GasTileOverlay))) + overlayManager.RemoveOverlay(nameof(GasTileOverlay)); + } + + private void OnGridRemoved(GridId gridId) + { + if (_tileData.ContainsKey(gridId)) + { + _tileData.Remove(gridId); + } + } + + public bool HasData(GridId gridId) + { + return _tileData.ContainsKey(gridId); } public (Texture, Color color)[] GetOverlays(GridId gridIndex, MapIndices indices) { - if (!_overlay.TryGetValue(gridIndex, out var tiles) || !tiles.TryGetValue(indices, out var overlays)) + if (!_tileData.TryGetValue(gridIndex, out var chunks)) return Array.Empty<(Texture, Color)>(); + + var chunkIndex = GetGasChunkIndices(indices); + if (!chunks.TryGetValue(chunkIndex, out var chunk)) + return Array.Empty<(Texture, Color)>(); + + var overlays = chunk.GetData(indices); + if (overlays.Gas == null) + return Array.Empty<(Texture, Color)>(); + var fire = overlays.FireState != 0; var length = overlays.Gas.Length + (fire ? 1 : 0); @@ -112,23 +184,6 @@ namespace Content.Client.GameObjects.EntitySystems return list; } - private void OnTileOverlayMessage(GasTileOverlayMessage ev) - { - if(ev.ClearAllOtherOverlays) - _overlay.Clear(); - - foreach (var data in ev.OverlayData) - { - if (!_overlay.TryGetValue(data.GridIndex, out var gridOverlays)) - { - gridOverlays = new Dictionary(); - _overlay.Add(data.GridIndex, gridOverlays); - } - - gridOverlays[data.GridIndices] = data.Data; - } - } - public override void FrameUpdate(float frameTime) { base.FrameUpdate(frameTime); 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/GameTicking/ClientGameTicker.cs b/Content.Client/GameTicking/ClientGameTicker.cs index a80f606215..1ae8bcd8a7 100644 --- a/Content.Client/GameTicking/ClientGameTicker.cs +++ b/Content.Client/GameTicking/ClientGameTicker.cs @@ -1,11 +1,15 @@ using System; +using System.Collections.Generic; using Content.Client.Interfaces; using Content.Client.State; using Content.Client.UserInterface; using Content.Shared; +using Content.Shared.Network.NetMessages; +using Robust.Client.Interfaces.Graphics; using Robust.Client.Interfaces.State; using Robust.Shared.Interfaces.Network; using Robust.Shared.IoC; +using Robust.Shared.Network; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; @@ -22,12 +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; } public event Action InfoBlobUpdated; public event Action LobbyStatusUpdated; + public event Action LobbyReadyUpdated; + public event Action LobbyLateJoinStatusUpdated; public void Initialize() { @@ -38,11 +46,23 @@ namespace Content.Client.GameTicking _netManager.RegisterNetMessage(nameof(MsgTickerLobbyStatus), LobbyStatus); _netManager.RegisterNetMessage(nameof(MsgTickerLobbyInfo), LobbyInfo); _netManager.RegisterNetMessage(nameof(MsgTickerLobbyCountdown), LobbyCountdown); + _netManager.RegisterNetMessage(nameof(MsgTickerLobbyReady), LobbyReady); _netManager.RegisterNetMessage(nameof(MsgRoundEndMessage), RoundEnd); + _netManager.RegisterNetMessage(nameof(MsgRequestWindowAttention), msg => + { + IoCManager.Resolve().RequestWindowAttention(); + }); + _netManager.RegisterNetMessage(nameof(MsgTickerLateJoinStatus), LateJoinStatus); + Ready = new Dictionary(); _initialized = true; } + private void LateJoinStatus(MsgTickerLateJoinStatus message) + { + DisallowedLateJoin = message.Disallowed; + LobbyLateJoinStatusUpdated?.Invoke(); + } private void JoinLobby(MsgTickerJoinLobby message) @@ -56,6 +76,8 @@ namespace Content.Client.GameTicking IsGameStarted = message.IsRoundStarted; AreWeReady = message.YouAreReady; Paused = message.Paused; + if (IsGameStarted) + Ready.Clear(); LobbyStatusUpdated?.Invoke(); } @@ -78,11 +100,20 @@ namespace Content.Client.GameTicking Paused = message.Paused; } + private void LobbyReady(MsgTickerLobbyReady message) + { + // Merge the Dictionaries + foreach (var p in message.PlayerReady) + { + Ready[p.Key] = p.Value; + } + LobbyReadyUpdated?.Invoke(); + } + 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/Health/BodySystem/BodyScanner/BodyScannerDisplay.cs b/Content.Client/Health/BodySystem/BodyScanner/BodyScannerDisplay.cs deleted file mode 100644 index d997c73216..0000000000 --- a/Content.Client/Health/BodySystem/BodyScanner/BodyScannerDisplay.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System.Collections.Generic; -using System.Globalization; -using Content.Shared.Health.BodySystem.BodyScanner; -using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.CustomControls; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Maths; -using static Robust.Client.UserInterface.Controls.ItemList; - -namespace Content.Client.Health.BodySystem.BodyScanner -{ - public sealed class BodyScannerDisplay : SS14Window - { - #pragma warning disable 649 - [Dependency] private readonly ILocalizationManager _loc; - #pragma warning restore 649 - - public BodyScannerBoundUserInterface Owner { get; private set; } - protected override Vector2? CustomSize => (800, 600); - private ItemList BodyPartList { get; } - private Label BodyPartLabel { get; } - private Label BodyPartHealth { get; } - private ItemList MechanismList { get; } - private RichTextLabel MechanismInfoLabel { get; } - - - private BodyScannerTemplateData _template; - private Dictionary _parts; - private List _slots; - private BodyScannerBodyPartData _currentBodyPart; - - - public BodyScannerDisplay(BodyScannerBoundUserInterface owner) - { - IoCManager.InjectDependencies(this); - Owner = owner; - Title = _loc.GetString("Body Scanner"); - - var hSplit = new HBoxContainer(); - Contents.AddChild(hSplit); - - //Left half - var scrollBox = new ScrollContainer - { - SizeFlagsHorizontal = SizeFlags.FillExpand, - }; - hSplit.AddChild(scrollBox); - BodyPartList = new ItemList { }; - scrollBox.AddChild(BodyPartList); - BodyPartList.OnItemSelected += BodyPartOnItemSelected; - - //Right half - var vSplit = new VBoxContainer - { - SizeFlagsHorizontal = SizeFlags.FillExpand, - }; - hSplit.AddChild(vSplit); - - //Top half of the right half - var limbBox = new VBoxContainer - { - SizeFlagsVertical = SizeFlags.FillExpand - }; - vSplit.AddChild(limbBox); - BodyPartLabel = new Label(); - limbBox.AddChild(BodyPartLabel); - var limbHealthHBox = new HBoxContainer(); - limbBox.AddChild(limbHealthHBox); - var healthLabel = new Label - { - Text = "Health: " - }; - limbHealthHBox.AddChild(healthLabel); - BodyPartHealth = new Label(); - limbHealthHBox.AddChild(BodyPartHealth); - var limbScroll = new ScrollContainer - { - SizeFlagsVertical = SizeFlags.FillExpand - }; - limbBox.AddChild(limbScroll); - MechanismList = new ItemList(); - limbScroll.AddChild(MechanismList); - MechanismList.OnItemSelected += MechanismOnItemSelected; - - //Bottom half of the right half - MechanismInfoLabel = new RichTextLabel - { - SizeFlagsVertical = SizeFlags.FillExpand - }; - vSplit.AddChild(MechanismInfoLabel); - } - - - public void UpdateDisplay(BodyScannerTemplateData template, Dictionary parts) - { - _template = template; - _parts = parts; - _slots = new List(); - BodyPartList.Clear(); - foreach (var (key, value) in _parts) - { - _slots.Add(key); //We have to do this since ItemLists only return the index of what item is selected and dictionaries don't allow you to explicitly grab things by index. - //So we put the contents of the dictionary into a list so that we can grab the list by index. I don't know either. - BodyPartList.AddItem(CultureInfo.CurrentCulture.TextInfo.ToTitleCase(key)); - } - } - - - public void BodyPartOnItemSelected(ItemListSelectedEventArgs args) - { - if(_parts.TryGetValue(_slots[args.ItemIndex], out _currentBodyPart)) { - UpdateBodyPartBox(_currentBodyPart, _slots[args.ItemIndex]); - } - } - private void UpdateBodyPartBox(BodyScannerBodyPartData part, string slotName) - { - BodyPartLabel.Text = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(slotName) + ": " + CultureInfo.CurrentCulture.TextInfo.ToTitleCase(part.Name); - BodyPartHealth.Text = part.CurrentDurability + "/" + part.MaxDurability; - - MechanismList.Clear(); - foreach (var mechanism in part.Mechanisms) { - MechanismList.AddItem(mechanism.Name); - } - } - - - public void MechanismOnItemSelected(ItemListSelectedEventArgs args) - { - UpdateMechanismBox(_currentBodyPart.Mechanisms[args.ItemIndex]); - } - private void UpdateMechanismBox(BodyScannerMechanismData mechanism) - { - //TODO: Make UI look less shit and clean up whatever the fuck this is lmao - if (mechanism != null) - { - string message = ""; - message += mechanism.Name; - message += "\nHealth: "; - message += mechanism.CurrentDurability; - message += "/"; - message += mechanism.MaxDurability; - message += "\n"; - message += mechanism.Description; - MechanismInfoLabel.SetMessage(message); - } - else - { - MechanismInfoLabel.SetMessage(""); - } - } - - - } -} diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 5169e4a189..6e67d79995 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -96,7 +96,6 @@ "BarSign", "DroppedBodyPart", "DroppedMechanism", - "BodyManager", "SolarPanel", "BodyScanner", "Stunbaton", @@ -143,6 +142,8 @@ "Listening", "Radio", "DisposalHolder", + "DisposalTagger", + "DisposalRouter", "DisposalTransit", "DisposalEntry", "DisposalJunction", @@ -157,6 +158,10 @@ "Vapor", "DamageOnHighSpeedImpact", "Barotrauma", + "GasSprayer", + "GasVapor", + "MobStateManager", + "Metabolism", }; } } diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index 1250627a4f..89ddd64268 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -19,6 +19,7 @@ namespace Content.Client.Input common.AddFunction(ContentKeyFunctions.OpenTutorial); common.AddFunction(ContentKeyFunctions.TakeScreenshot); common.AddFunction(ContentKeyFunctions.TakeScreenshotNoUI); + common.AddFunction(ContentKeyFunctions.Point); var human = contexts.GetContext("human"); human.AddFunction(ContentKeyFunctions.SwapHands); @@ -37,9 +38,6 @@ namespace Content.Client.Input human.AddFunction(ContentKeyFunctions.MouseMiddle); human.AddFunction(ContentKeyFunctions.ToggleCombatMode); human.AddFunction(ContentKeyFunctions.WideAttack); - human.AddFunction(ContentKeyFunctions.Point); - human.AddFunction(ContentKeyFunctions.TryPullObject); - human.AddFunction(ContentKeyFunctions.MovePulledObject); var ghost = contexts.New("ghost", "common"); ghost.AddFunction(EngineKeyFunctions.MoveUp); diff --git a/Content.Client/Interfaces/IClientGameTicker.cs b/Content.Client/Interfaces/IClientGameTicker.cs index 8d47a0e1dc..e1e584e2f3 100644 --- a/Content.Client/Interfaces/IClientGameTicker.cs +++ b/Content.Client/Interfaces/IClientGameTicker.cs @@ -1,4 +1,6 @@ +using Robust.Shared.Network; using System; +using System.Collections.Generic; namespace Content.Client.Interfaces { @@ -7,11 +9,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; } void Initialize(); event Action InfoBlobUpdated; event Action LobbyStatusUpdated; + event Action LobbyReadyUpdated; + event Action LobbyLateJoinStatusUpdated; } } diff --git a/Content.Client/State/GameScreenBase.cs b/Content.Client/State/GameScreenBase.cs index 1372dacce5..64e62d2b16 100644 --- a/Content.Client/State/GameScreenBase.cs +++ b/Content.Client/State/GameScreenBase.cs @@ -226,7 +226,10 @@ namespace Content.Client.State // client side command handlers will always be sent the local player session. var session = PlayerManager.LocalPlayer.Session; - inputSys.HandleInputCommand(session, func, message); + if (inputSys.HandleInputCommand(session, func, message)) + { + args.Handle(); + } } } } diff --git a/Content.Client/State/LobbyState.cs b/Content.Client/State/LobbyState.cs index 23f1d28d83..2580dadb64 100644 --- a/Content.Client/State/LobbyState.cs +++ b/Content.Client/State/LobbyState.cs @@ -99,14 +99,20 @@ namespace Content.Client.State _playerManager.PlayerListUpdated += PlayerManagerOnPlayerListUpdated; _clientGameTicker.InfoBlobUpdated += UpdateLobbyUi; - _clientGameTicker.LobbyStatusUpdated += 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.Ready.Clear(); _lobby.Dispose(); _characterSetup.Dispose(); @@ -149,7 +155,30 @@ namespace Content.Client.State _lobby.StartTime.Text = Loc.GetString("Round Starts In: {0}", text); } - private void PlayerManagerOnPlayerListUpdated(object sender, EventArgs e) => UpdatePlayerList(); + private void PlayerManagerOnPlayerListUpdated(object sender, EventArgs e) + { + // Remove disconnected sessions from the Ready Dict + foreach (var p in _clientGameTicker.Ready) + { + if (!_playerManager.SessionsDict.TryGetValue(p.Key, out _)) + { + _clientGameTicker.Ready.Remove(p.Key); + } + } + UpdatePlayerList(); + } + private void LobbyReadyUpdated() => UpdatePlayerList(); + + private void LobbyStatusUpdated() + { + UpdatePlayerList(); + UpdateLobbyUi(); + } + + private void LobbyLateJoinStatusUpdated() + { + _lobby.ReadyButton.Disabled = _clientGameTicker.DisallowedLateJoin; + } private void UpdateLobbyUi() { @@ -169,6 +198,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; } @@ -178,10 +208,24 @@ namespace Content.Client.State private void UpdatePlayerList() { _lobby.OnlinePlayerItemList.Clear(); + _lobby.PlayerReadyList.Clear(); foreach (var session in _playerManager.Sessions.OrderBy(s => s.Name)) { _lobby.OnlinePlayerItemList.AddItem(session.Name); + + var readyState = ""; + // Don't show ready state if we're ingame + if (!_clientGameTicker.IsGameStarted) + { + var ready = false; + if (session.SessionId == _playerManager.LocalPlayer.SessionId) + ready = _clientGameTicker.AreWeReady; + else + _clientGameTicker.Ready.TryGetValue(session.SessionId, out ready); + readyState = ready ? Loc.GetString("Ready") : Loc.GetString("Not Ready"); + } + _lobby.PlayerReadyList.AddItem(readyState, null, false); } } @@ -193,6 +237,7 @@ namespace Content.Client.State } _console.ProcessCommand($"toggleready {newReady}"); + UpdatePlayerList(); } } } diff --git a/Content.Client/StationEvents/IStationEventManager.cs b/Content.Client/StationEvents/IStationEventManager.cs new file mode 100644 index 0000000000..828b20e80d --- /dev/null +++ b/Content.Client/StationEvents/IStationEventManager.cs @@ -0,0 +1,13 @@ +#nullable enable +using System; +using System.Collections.Generic; + +namespace Content.Client.StationEvents +{ + public interface IStationEventManager + { + public List? StationEvents { get; } + public void Initialize(); + public event Action OnStationEventsReceived; + } +} diff --git a/Content.Client/StationEvents/StationEventManager.cs b/Content.Client/StationEvents/StationEventManager.cs new file mode 100644 index 0000000000..eb1ce55416 --- /dev/null +++ b/Content.Client/StationEvents/StationEventManager.cs @@ -0,0 +1,42 @@ +#nullable enable +using Content.Shared.StationEvents; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.IoC; +using System; +using System.Collections.Generic; + +namespace Content.Client.StationEvents +{ + class StationEventManager : SharedStationEvent, IStationEventManager + { + private List? _events; + public List? StationEvents + { + get + { + if (_events == null) + RequestEvents(); + return _events; + } + } + public event Action? OnStationEventsReceived; + + public void Initialize() + { + var netManager = IoCManager.Resolve(); + netManager.RegisterNetMessage(nameof(MsgGetStationEvents), EventHandler); + netManager.Disconnect += (sender, msg) => _events = null; + } + + private void EventHandler(MsgGetStationEvents msg) + { + _events = msg.Events; + OnStationEventsReceived?.Invoke(); + } + public void RequestEvents() + { + var netManager = IoCManager.Resolve(); + netManager.ClientSendMessage(netManager.CreateNetMessage()); + } + } +} diff --git a/Content.Client/UserInterface/CooldownGraphic.cs b/Content.Client/UserInterface/CooldownGraphic.cs index a092cf425b..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 diff --git a/Content.Client/UserInterface/LobbyGui.cs b/Content.Client/UserInterface/LobbyGui.cs index 2b077446f1..2f8caf3574 100644 --- a/Content.Client/UserInterface/LobbyGui.cs +++ b/Content.Client/UserInterface/LobbyGui.cs @@ -1,4 +1,4 @@ -using Content.Client.Chat; +using Content.Client.Chat; using Content.Client.Interfaces; using Content.Client.UserInterface.Stylesheets; using Content.Client.Utility; @@ -22,6 +22,7 @@ namespace Content.Client.UserInterface public Button LeaveButton { get; } public ChatBox Chat { get; } public ItemList OnlinePlayerItemList { get; } + public ItemList PlayerReadyList { get; } public ServerInfo ServerInfo { get; } public LobbyCharacterPreviewPanel CharacterPreview { get; } @@ -219,7 +220,25 @@ namespace Content.Client.UserInterface MarginBottomOverride = 3, Children = { - (OnlinePlayerItemList = new ItemList()) + new HBoxContainer + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + CustomMinimumSize = (50,50), + Children = + { + (OnlinePlayerItemList = new ItemList + { + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsHorizontal = SizeFlags.FillExpand, + }), + (PlayerReadyList = new ItemList + { + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 0.2f + }), + } + } } }, new NanoHeading 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/Atmos/AtmosHelpersTest.cs b/Content.IntegrationTests/Tests/Atmos/AtmosHelpersTest.cs new file mode 100644 index 0000000000..844198ce66 --- /dev/null +++ b/Content.IntegrationTests/Tests/Atmos/AtmosHelpersTest.cs @@ -0,0 +1,160 @@ +using System.Threading.Tasks; +using Content.Server.Atmos; +using NUnit.Framework; +using Robust.Shared.Map; + +namespace Content.IntegrationTests.Tests.Atmos +{ + [TestFixture] + [TestOf(typeof(AtmosHelpersTest))] + public class AtmosHelpersTest : ContentIntegrationTest + { + [Test] + public async Task GetTileAtmosphereGridCoordinatesNullTest() + { + var server = StartServerDummyTicker(); + + server.Assert(() => + { + Assert.DoesNotThrow(() => + { + var atmosphere = default(GridCoordinates).GetTileAtmosphere(); + + Assert.Null(atmosphere); + }); + }); + + await server.WaitIdleAsync(); + } + + [Test] + public async Task GetTileAirGridCoordinatesNullTest() + { + var server = StartServerDummyTicker(); + + server.Assert(() => + { + Assert.DoesNotThrow(() => + { + var air = default(GridCoordinates).GetTileAir(); + + Assert.Null(air); + }); + }); + + await server.WaitIdleAsync(); + } + + [Test] + public async Task TryGetTileAtmosphereGridCoordinatesNullTest() + { + var server = StartServerDummyTicker(); + + server.Assert(() => + { + Assert.DoesNotThrow(() => + { + var hasAtmosphere = default(GridCoordinates).TryGetTileAtmosphere(out var atmosphere); + + Assert.False(hasAtmosphere); + Assert.Null(atmosphere); + }); + }); + + await server.WaitIdleAsync(); + } + + [Test] + public async Task TryGetTileTileAirGridCoordinatesNullTest() + { + var server = StartServerDummyTicker(); + + server.Assert(() => + { + Assert.DoesNotThrow(() => + { + var hasAir = default(GridCoordinates).TryGetTileAir(out var air); + + Assert.False(hasAir); + Assert.Null(air); + }); + }); + + await server.WaitIdleAsync(); + } + + [Test] + public async Task GetTileAtmosphereMapIndicesNullTest() + { + var server = StartServerDummyTicker(); + + server.Assert(() => + { + Assert.DoesNotThrow(() => + { + var atmosphere = default(MapIndices).GetTileAtmosphere(default); + + Assert.Null(atmosphere); + }); + }); + + await server.WaitIdleAsync(); + } + + [Test] + public async Task GetTileAirMapIndicesNullTest() + { + var server = StartServerDummyTicker(); + + server.Assert(() => + { + Assert.DoesNotThrow(() => + { + var air = default(MapIndices).GetTileAir(default); + + Assert.Null(air); + }); + }); + + await server.WaitIdleAsync(); + } + + [Test] + public async Task TryGetTileAtmosphereMapIndicesNullTest() + { + var server = StartServerDummyTicker(); + + server.Assert(() => + { + Assert.DoesNotThrow(() => + { + var hasAtmosphere = default(MapIndices).TryGetTileAtmosphere(default, out var atmosphere); + + Assert.False(hasAtmosphere); + Assert.Null(atmosphere); + }); + }); + + await server.WaitIdleAsync(); + } + + [Test] + public async Task TryGetTileAirMapIndicesNullTest() + { + var server = StartServerDummyTicker(); + + server.Assert(() => + { + Assert.DoesNotThrow(() => + { + var hasAir = default(MapIndices).TryGetTileAir(default, out var air); + + Assert.False(hasAir); + Assert.Null(air); + }); + }); + + await server.WaitIdleAsync(); + } + } +} diff --git a/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs b/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs index 15b2167631..3eb3821634 100644 --- a/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs +++ b/Content.IntegrationTests/Tests/Disposal/DisposalUnitTest.cs @@ -66,7 +66,7 @@ namespace Content.IntegrationTests.Tests.Disposal DisposalUnitComponent unit; DisposalEntryComponent entry; - server.Assert(() => + server.Assert(async () => { var mapManager = IoCManager.Resolve(); @@ -81,19 +81,19 @@ 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(); - disposalUnitAnchorable.TryUnAnchor(human, null, true); + await disposalUnitAnchorable.TryUnAnchor(human, null, true); Assert.False(unit.Anchored); UnitInsertContains(unit, false, human, wrench, disposalUnit, disposalTrunk); // Anchor the disposal unit - disposalUnitAnchorable.TryAnchor(human, null, true); - Assert.True(disposalUnit.TryGetComponent(out AnchorableComponent anchorableUnit)); - Assert.True(anchorableUnit.TryAnchor(human, wrench)); + await disposalUnitAnchorable.TryAnchor(human, null, true); + 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/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs index 4df604e4ad..639a2f78b6 100644 --- a/Content.IntegrationTests/Tests/EntityTest.cs +++ b/Content.IntegrationTests/Tests/EntityTest.cs @@ -19,7 +19,7 @@ namespace Content.IntegrationTests.Tests public class EntityTest : ContentIntegrationTest { [Test] - public async Task Test() + public async Task SpawnTest() { var server = StartServerDummyTicker(); await server.WaitIdleAsync(); @@ -41,43 +41,65 @@ namespace Content.IntegrationTests.Tests }); server.Assert(() => + { + var testLocation = new GridCoordinates(new Vector2(0, 0), grid); + + //Generate list of non-abstract prototypes to test + foreach (var prototype in prototypeMan.EnumeratePrototypes()) { - var testLocation = new GridCoordinates(new Vector2(0, 0), grid); - - //Generate list of non-abstract prototypes to test - foreach (var prototype in prototypeMan.EnumeratePrototypes()) + if (prototype.Abstract) { - if (prototype.Abstract) - { - continue; - } - prototypes.Add(prototype); + continue; + } + prototypes.Add(prototype); + } + + //Iterate list of prototypes to spawn + foreach (var prototype in prototypes) + { + try + { + Logger.LogS(LogLevel.Debug, "EntityTest", "Testing: " + prototype.ID); + testEntity = entityMan.SpawnEntity(prototype.ID, testLocation); + server.RunTicks(2); + Assert.That(testEntity.Initialized); + entityMan.DeleteEntity(testEntity.Uid); } - //Iterate list of prototypes to spawn - foreach (var prototype in prototypes) + //Fail any exceptions thrown on spawn + catch (Exception e) { - try - { - Logger.LogS(LogLevel.Debug, "EntityTest", "Testing: " + prototype.ID); - testEntity = entityMan.SpawnEntity(prototype.ID, testLocation); - server.RunTicks(2); - Assert.That(testEntity.Initialized); - entityMan.DeleteEntity(testEntity.Uid); - } - - //Fail any exceptions thrown on spawn - catch (Exception e) - { - Logger.LogS(LogLevel.Error, "EntityTest", "Entity '" + prototype.ID + "' threw: " + e.Message); - //Assert.Fail(); - throw; - } + Logger.LogS(LogLevel.Error, "EntityTest", "Entity '" + prototype.ID + "' threw: " + e.Message); + //Assert.Fail(); + throw; } - }); + } + }); await server.WaitIdleAsync(); } + [Test] + public async Task NotAbstractIconTest() + { + var client = StartClient(); + await client.WaitIdleAsync(); + var prototypeMan = client.ResolveDependency(); + + client.Assert(() => + { + foreach (var prototype in prototypeMan.EnumeratePrototypes()) + { + if (prototype.Abstract) + { + continue; + } + + Assert.That(prototype.Components.ContainsKey("Icon"), $"Entity {prototype.ID} does not have an Icon component, but is not abstract"); + } + }); + + await client.WaitIdleAsync(); + } } } 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/AiLogic/UtilityAI.cs b/Content.Server/AI/Utility/AiLogic/UtilityAI.cs index 005a30a9bc..b92d55ce1f 100644 --- a/Content.Server/AI/Utility/AiLogic/UtilityAI.cs +++ b/Content.Server/AI/Utility/AiLogic/UtilityAI.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Threading; using Content.Server.AI.Operators; @@ -6,11 +6,13 @@ using Content.Server.AI.Utility.Actions; using Content.Server.AI.Utility.BehaviorSets; using Content.Server.AI.WorldState; using Content.Server.AI.WorldState.States.Utility; -using Content.Server.GameObjects.Components.Damage; -using Content.Server.GameObjects.Components.Mobs; +using Content.Server.GameObjects.EntitySystems.AI; using Content.Server.GameObjects.EntitySystems.AI.LoadBalancer; using Content.Server.GameObjects.EntitySystems.JobQueues; +using Content.Shared.GameObjects.Components.Damage; using Robust.Server.AI; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Log; @@ -63,6 +65,13 @@ namespace Content.Server.AI.Utility.AiLogic { SortActions(); } + + if (BehaviorSets.Count == 1 && !EntitySystem.Get().IsAwake(this)) + { + IoCManager.Resolve() + .EventBus + .RaiseEvent(EventSource.Local, new SleepAiMessage(this, false)); + } } public void RemoveBehaviorSet(Type behaviorSet) @@ -74,6 +83,13 @@ namespace Content.Server.AI.Utility.AiLogic BehaviorSets.Remove(behaviorSet); SortActions(); } + + if (BehaviorSets.Count == 0) + { + IoCManager.Resolve() + .EventBus + .RaiseEvent(EventSource.Local, new SleepAiMessage(this, true)); + } } /// @@ -114,38 +130,42 @@ namespace Content.Server.AI.Utility.AiLogic _planCooldownRemaining = PlanCooldown; _blackboard = new Blackboard(SelfEntity); _planner = IoCManager.Resolve().GetEntitySystem(); - if (SelfEntity.TryGetComponent(out DamageableComponent damageableComponent)) + if (SelfEntity.TryGetComponent(out IDamageableComponent damageableComponent)) { - damageableComponent.DamageThresholdPassed += DamageThresholdHandle; + damageableComponent.HealthChangedEvent += DeathHandle; } } public override void Shutdown() { // TODO: If DamageableComponent removed still need to unsubscribe? - if (SelfEntity.TryGetComponent(out DamageableComponent damageableComponent)) + if (SelfEntity.TryGetComponent(out IDamageableComponent damageableComponent)) { - damageableComponent.DamageThresholdPassed -= DamageThresholdHandle; + damageableComponent.HealthChangedEvent -= DeathHandle; } var currentOp = CurrentAction?.ActionOperators.Peek(); currentOp?.Shutdown(Outcome.Failed); } - private void DamageThresholdHandle(object sender, DamageThresholdPassedEventArgs eventArgs) + private void DeathHandle(HealthChangedEventArgs eventArgs) { - if (!SelfEntity.TryGetComponent(out SpeciesComponent speciesComponent)) - { - return; - } + var oldDeadState = _isDead; + _isDead = eventArgs.Damageable.CurrentDamageState == DamageState.Dead || eventArgs.Damageable.CurrentDamageState == DamageState.Critical; - if (speciesComponent.CurrentDamageState is DeadState) + if (oldDeadState != _isDead) { - _isDead = true; - } - else - { - _isDead = false; + var entityManager = IoCManager.Resolve(); + + switch (_isDead) + { + case true: + entityManager.EventBus.RaiseEvent(EventSource.Local, new SleepAiMessage(this, true)); + break; + case false: + entityManager.EventBus.RaiseEvent(EventSource.Local, new SleepAiMessage(this, false)); + break; + } } } @@ -180,16 +200,6 @@ namespace Content.Server.AI.Utility.AiLogic public override void Update(float frameTime) { - // If we can't do anything then there's no point thinking - if (_isDead || BehaviorSets.Count == 0) - { - _actionCancellation?.Cancel(); - _blackboard.GetState().SetValue(0.0f); - CurrentAction?.Shutdown(); - CurrentAction = null; - return; - } - // If we asked for a new action we don't want to dump the existing one. if (_actionRequest != null) { diff --git a/Content.Server/AI/Utility/Considerations/Combat/TargetHealthCon.cs b/Content.Server/AI/Utility/Considerations/Combat/TargetHealthCon.cs index 8e70bb9200..64fce8bf56 100644 --- a/Content.Server/AI/Utility/Considerations/Combat/TargetHealthCon.cs +++ b/Content.Server/AI/Utility/Considerations/Combat/TargetHealthCon.cs @@ -1,4 +1,4 @@ -using Content.Server.AI.WorldState; +using Content.Server.AI.WorldState; using Content.Server.AI.WorldState.States; using Content.Server.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Damage; @@ -11,13 +11,12 @@ namespace Content.Server.AI.Utility.Considerations.Combat { var target = context.GetState().GetValue(); - if (target == null || !target.TryGetComponent(out DamageableComponent damageableComponent)) + if (target == null || !target.TryGetComponent(out IDamageableComponent damageableComponent)) { return 0.0f; } - // Just went with max health - return damageableComponent.CurrentDamage[DamageType.Total] / 300.0f; + return damageableComponent.TotalDamage / 300.0f; } } } diff --git a/Content.Server/AI/Utility/Considerations/Combat/TargetIsCritCon.cs b/Content.Server/AI/Utility/Considerations/Combat/TargetIsCritCon.cs index 97ed516326..856a53958d 100644 --- a/Content.Server/AI/Utility/Considerations/Combat/TargetIsCritCon.cs +++ b/Content.Server/AI/Utility/Considerations/Combat/TargetIsCritCon.cs @@ -1,6 +1,6 @@ -using Content.Server.AI.WorldState; +using Content.Server.AI.WorldState; using Content.Server.AI.WorldState.States; -using Content.Server.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Damage; namespace Content.Server.AI.Utility.Considerations.Combat { @@ -10,12 +10,12 @@ namespace Content.Server.AI.Utility.Considerations.Combat { var target = context.GetState().GetValue(); - if (target == null || !target.TryGetComponent(out SpeciesComponent speciesComponent)) + if (target == null || !target.TryGetComponent(out IDamageableComponent damageableComponent)) { return 0.0f; } - if (speciesComponent.CurrentDamageState is CriticalState) + if (damageableComponent.CurrentDamageState == DamageState.Critical) { return 1.0f; } diff --git a/Content.Server/AI/Utility/Considerations/Combat/TargetIsDeadCon.cs b/Content.Server/AI/Utility/Considerations/Combat/TargetIsDeadCon.cs index e973538e79..d4d582e6b1 100644 --- a/Content.Server/AI/Utility/Considerations/Combat/TargetIsDeadCon.cs +++ b/Content.Server/AI/Utility/Considerations/Combat/TargetIsDeadCon.cs @@ -1,6 +1,6 @@ -using Content.Server.AI.WorldState; +using Content.Server.AI.WorldState; using Content.Server.AI.WorldState.States; -using Content.Server.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Damage; namespace Content.Server.AI.Utility.Considerations.Combat { @@ -10,12 +10,12 @@ namespace Content.Server.AI.Utility.Considerations.Combat { var target = context.GetState().GetValue(); - if (target == null || !target.TryGetComponent(out SpeciesComponent speciesComponent)) + if (target == null || !target.TryGetComponent(out IDamageableComponent damageableComponent)) { return 0.0f; } - if (speciesComponent.CurrentDamageState is DeadState) + if (damageableComponent.CurrentDamageState == DamageState.Dead) { return 1.0f; } diff --git a/Content.Server/AI/Utility/Considerations/Consideration.cs b/Content.Server/AI/Utility/Considerations/Consideration.cs index 6fc9966607..80860b07c1 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 MathHelper.Clamp(adjustedScore, 0.0f, 1.0f); + var adjustedScore = MathF.Pow(score, 1 / (float) considerationsCount); + return FloatMath.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) diff --git a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbyPlayerExp.cs b/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbyPlayerExp.cs index b54aed6274..cde979be3e 100644 --- a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbyPlayerExp.cs +++ b/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbyPlayerExp.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Content.Server.AI.Utility.Actions; using Content.Server.AI.Utility.Actions.Combat.Melee; @@ -7,8 +7,8 @@ using Content.Server.AI.Utility.Considerations.Combat.Melee; using Content.Server.AI.Utils; using Content.Server.AI.WorldState; using Content.Server.AI.WorldState.States; -using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Movement; +using Content.Shared.GameObjects.Components.Damage; using Robust.Server.GameObjects; using Robust.Shared.IoC; @@ -37,7 +37,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee throw new InvalidOperationException(); } - foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(SpeciesComponent), + foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(IDamageableComponent), controller.VisionRadius)) { if (entity.HasComponent() && entity != owner) diff --git a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbySpeciesExp.cs b/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbySpeciesExp.cs index 29937b0270..bd78ab74ec 100644 --- a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbySpeciesExp.cs +++ b/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/MeleeAttackNearbySpeciesExp.cs @@ -15,7 +15,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee { var owner = context.GetState().GetValue(); - foreach (var entity in context.GetState().GetValue()) + foreach (var entity in context.GetState().GetValue()) { yield return new MeleeWeaponAttackEntity(owner, entity, Bonus); } diff --git a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/UnarmedAttackNearbyPlayerExp.cs b/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/UnarmedAttackNearbyPlayerExp.cs index 74696e13e5..46f1f79479 100644 --- a/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/UnarmedAttackNearbyPlayerExp.cs +++ b/Content.Server/AI/Utility/ExpandableActions/Combat/Melee/UnarmedAttackNearbyPlayerExp.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Content.Server.AI.Utility.Actions; using Content.Server.AI.Utility.Actions.Combat.Melee; @@ -7,8 +7,8 @@ using Content.Server.AI.Utility.Considerations.Combat.Melee; using Content.Server.AI.Utils; using Content.Server.AI.WorldState; using Content.Server.AI.WorldState.States; -using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Movement; +using Content.Shared.GameObjects.Components.Body; using Robust.Server.GameObjects; using Robust.Shared.IoC; @@ -37,7 +37,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee throw new InvalidOperationException(); } - foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(SpeciesComponent), + foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(IBodyManagerComponent), controller.VisionRadius)) { if (entity.HasComponent() && entity != owner) 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/AI/WorldState/States/Mobs/NearbySpeciesState.cs b/Content.Server/AI/WorldState/States/Mobs/NearbyBodiesState.cs similarity index 70% rename from Content.Server/AI/WorldState/States/Mobs/NearbySpeciesState.cs rename to Content.Server/AI/WorldState/States/Mobs/NearbyBodiesState.cs index a72786d326..2554c4cf53 100644 --- a/Content.Server/AI/WorldState/States/Mobs/NearbySpeciesState.cs +++ b/Content.Server/AI/WorldState/States/Mobs/NearbyBodiesState.cs @@ -1,16 +1,16 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Content.Server.AI.Utils; -using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Movement; +using Content.Shared.GameObjects.Components.Body; using JetBrains.Annotations; using Robust.Shared.Interfaces.GameObjects; namespace Content.Server.AI.WorldState.States.Mobs { [UsedImplicitly] - public sealed class NearbySpeciesState : CachedStateData> + public sealed class NearbyBodiesState : CachedStateData> { - public override string Name => "NearbySpecies"; + public override string Name => "NearbyBodies"; protected override List GetTrueValue() { @@ -21,7 +21,7 @@ namespace Content.Server.AI.WorldState.States.Mobs return result; } - foreach (var entity in Visibility.GetEntitiesInRange(Owner.Transform.GridPosition, typeof(SpeciesComponent), controller.VisionRadius)) + foreach (var entity in Visibility.GetEntitiesInRange(Owner.Transform.GridPosition, typeof(IBodyManagerComponent), controller.VisionRadius)) { if (entity == Owner) continue; result.Add(entity); diff --git a/Content.Server/AI/WorldState/States/Mobs/NearbyPlayersState.cs b/Content.Server/AI/WorldState/States/Mobs/NearbyPlayersState.cs index f1c7bbae1a..1ba739e3aa 100644 --- a/Content.Server/AI/WorldState/States/Mobs/NearbyPlayersState.cs +++ b/Content.Server/AI/WorldState/States/Mobs/NearbyPlayersState.cs @@ -1,6 +1,6 @@ -using System.Collections.Generic; -using Content.Server.GameObjects.Components.Mobs; +using System.Collections.Generic; using Content.Server.GameObjects.Components.Movement; +using Content.Shared.GameObjects.Components.Damage; using JetBrains.Annotations; using Robust.Server.Interfaces.Player; using Robust.Shared.Interfaces.GameObjects; @@ -27,7 +27,7 @@ namespace Content.Server.AI.WorldState.States.Mobs foreach (var player in nearbyPlayers) { - if (player.AttachedEntity != Owner && player.AttachedEntity.HasComponent()) + if (player.AttachedEntity != Owner && player.AttachedEntity.HasComponent()) { result.Add(player.AttachedEntity); } 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/AtmosCommands.cs b/Content.Server/Atmos/AtmosCommands.cs index 29525acaae..c53a0862d8 100644 --- a/Content.Server/Atmos/AtmosCommands.cs +++ b/Content.Server/Atmos/AtmosCommands.cs @@ -138,7 +138,7 @@ namespace Content.Server.Atmos } } - public class FillGas : IClientCommand + public class FillGas : IClientCommand { public string Command => "fillgas"; public string Description => "Adds gas to all tiles in a grid."; diff --git a/Content.Server/Atmos/AtmosHelpers.cs b/Content.Server/Atmos/AtmosHelpers.cs index 5561b70639..28c8e165c5 100644 --- a/Content.Server/Atmos/AtmosHelpers.cs +++ b/Content.Server/Atmos/AtmosHelpers.cs @@ -20,14 +20,16 @@ namespace Content.Server.Atmos return coordinates.GetTileAtmosphere()?.Air; } - public static bool TryGetTileAtmosphere(this GridCoordinates coordinates, [NotNullWhen(true)] out TileAtmosphere atmosphere) + public static bool TryGetTileAtmosphere(this GridCoordinates coordinates, [MaybeNullWhen(false)] out TileAtmosphere atmosphere) { - return (atmosphere = coordinates.GetTileAtmosphere()!) != default; + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + return !Equals(atmosphere = coordinates.GetTileAtmosphere()!, default); } - public static bool TryGetTileAir(this GridCoordinates coordinates, [NotNullWhen(true)] out GasMixture air) + public static bool TryGetTileAir(this GridCoordinates coordinates, [MaybeNullWhen(false)] out GasMixture air) { - return !(air = coordinates.GetTileAir()!).Equals(default); + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + return !Equals(air = coordinates.GetTileAir()!, default); } public static TileAtmosphere? GetTileAtmosphere(this MapIndices indices, GridId gridId) @@ -43,14 +45,16 @@ namespace Content.Server.Atmos } public static bool TryGetTileAtmosphere(this MapIndices indices, GridId gridId, - [NotNullWhen(true)] out TileAtmosphere atmosphere) + [MaybeNullWhen(false)] out TileAtmosphere atmosphere) { - return (atmosphere = indices.GetTileAtmosphere(gridId)!) != default; + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + return !Equals(atmosphere = indices.GetTileAtmosphere(gridId)!, default); } - public static bool TryGetTileAir(this MapIndices indices, GridId gridId, [NotNullWhen(true)] out GasMixture air) + public static bool TryGetTileAir(this MapIndices indices, GridId gridId, [MaybeNullWhen(false)] out GasMixture air) { - return !(air = indices.GetTileAir(gridId)!).Equals(default); + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + return !Equals(air = indices.GetTileAir(gridId)!, default); } } } diff --git a/Content.Server/Atmos/GasSprayerComponent.cs b/Content.Server/Atmos/GasSprayerComponent.cs new file mode 100644 index 0000000000..4b78d413a9 --- /dev/null +++ b/Content.Server/Atmos/GasSprayerComponent.cs @@ -0,0 +1,74 @@ +using Content.Server.GameObjects.Components.Chemistry; +using Content.Server.Interfaces; +using Content.Shared.Chemistry; +using Content.Shared.GameObjects.Components; +using Content.Shared.GameObjects.Components.Pointing; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Server.GameObjects; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Server.Interfaces.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Maths; +using Robust.Shared.Serialization; + + +namespace Content.Server.Atmos +{ + [RegisterComponent] + public class GasSprayerComponent : Component, IAfterInteract + { +#pragma warning disable 649 + [Dependency] private readonly IServerNotifyManager _notifyManager = default!; + [Dependency] private readonly IServerEntityManager _serverEntityManager = default!; +#pragma warning restore 649 + + //TODO: create a function that can create a gas based on a solution mix + public override string Name => "GasSprayer"; + + private string _spraySound; + private string _sprayType; + private string _fuelType; + private string _fuelName; + private int _fuelCost; + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _spraySound, "spraySound", string.Empty); + serializer.DataField(ref _sprayType, "sprayType", string.Empty); + serializer.DataField(ref _fuelType, "fuelType", string.Empty); + serializer.DataField(ref _fuelName, "fuelName", "fuel"); + serializer.DataField(ref _fuelCost, "fuelCost", 50); + } + + public void AfterInteract(AfterInteractEventArgs eventArgs) + { + if (!Owner.TryGetComponent(out SolutionComponent tank)) + return; + + if (tank.Solution.GetReagentQuantity(_fuelType) == 0) + { + _notifyManager.PopupMessage(Owner, eventArgs.User, + Loc.GetString("{0:theName} is out of {1}!", Owner, _fuelName)); + } + else + { + tank.TryRemoveReagent(_fuelType, ReagentUnit.New(_fuelCost)); + + var playerPos = eventArgs.User.Transform.GridPosition; + var direction = (eventArgs.ClickLocation.Position - playerPos.Position).Normalized; + playerPos.Offset(direction/2); + + var spray = _serverEntityManager.SpawnEntity(_sprayType, playerPos); + spray.GetComponent() + .SetData(ExtinguisherVisuals.Rotation, direction.ToAngle().Degrees); + spray.GetComponent().StartMove(direction, 5); + + EntitySystem.Get().PlayFromEntity(_spraySound, Owner); + } + } + } +} diff --git a/Content.Server/Atmos/GasVaporComponent.cs b/Content.Server/Atmos/GasVaporComponent.cs new file mode 100644 index 0000000000..b28d2396a6 --- /dev/null +++ b/Content.Server/Atmos/GasVaporComponent.cs @@ -0,0 +1,120 @@ +using Content.Shared.Physics; +using Content.Server.Atmos.Reactions; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.IoC; +using Robust.Shared.Maths; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; +using Content.Server.GameObjects.Components.Atmos; +using Content.Server.Interfaces; +using Content.Shared.Atmos; + +namespace Content.Server.Atmos +{ + [RegisterComponent] + class GasVaporComponent : Component, ICollideBehavior, IGasMixtureHolder + { + [Dependency] private readonly IMapManager _mapManager = default!; + public override string Name => "GasVapor"; + + [ViewVariables] public GasMixture Air { get; set; } + + private bool _running; + private Vector2 _direction; + private float _velocity; + private float _disspateTimer = 0; + private float _dissipationInterval; + private Gas _gas; + private float _gasVolume; + private float _gasTemperature; + private float _gasAmount; + + public override void Initialize() + { + base.Initialize(); + Air = new GasMixture(_gasVolume){Temperature = _gasTemperature}; + Air.SetMoles(_gas,_gasAmount); + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _dissipationInterval, "dissipationInterval", 1); + serializer.DataField(ref _gas, "gas", Gas.WaterVapor); + serializer.DataField(ref _gasVolume, "gasVolume", 200); + serializer.DataField(ref _gasTemperature, "gasTemperature", Atmospherics.T20C); + serializer.DataField(ref _gasAmount, "gasAmount", 20); + } + + public void StartMove(Vector2 dir, float velocity) + { + _running = true; + _direction = dir; + _velocity = velocity; + + if (Owner.TryGetComponent(out ICollidableComponent collidable)) + { + var controller = collidable.EnsureController(); + controller.Move(_direction, _velocity); + } + } + + public void Update(float frameTime) + { + if (!_running) + return; + + if (Owner.TryGetComponent(out ICollidableComponent collidable)) + { + var worldBounds = collidable.WorldAABB; + var mapGrid = _mapManager.GetGrid(Owner.Transform.GridID); + + var tiles = mapGrid.GetTilesIntersecting(worldBounds); + + foreach (var tile in tiles) + { + var pos = tile.GridIndices.ToGridCoordinates(_mapManager, tile.GridIndex); + var atmos = AtmosHelpers.GetTileAtmosphere(pos); + + if (atmos.Air == null) + { + return; + } + + if (atmos.Air.React(this) != ReactionResult.NoReaction) + { + Owner.Delete(); + } + } + } + + _disspateTimer += frameTime; + if (_disspateTimer > _dissipationInterval) + { + Air.SetMoles(_gas, Air.TotalMoles/2 ); + } + + if (Air.TotalMoles < 1) + { + Owner.Delete(); + } + } + + void ICollideBehavior.CollideWith(IEntity collidedWith) + { + // Check for collision with a impassable object (e.g. wall) and stop + if (collidedWith.TryGetComponent(out ICollidableComponent collidable) && + (collidable.CollisionLayer & (int) CollisionGroup.Impassable) != 0 && + collidable.Hard && + Owner.TryGetComponent(out ICollidableComponent coll)) + { + var controller = coll.EnsureController(); + controller.Stop(); + Owner.Delete(); + } + } + } +} diff --git a/Content.Server/Atmos/Hotspot.cs b/Content.Server/Atmos/Hotspot.cs index 2178a6bea8..4597813c6f 100644 --- a/Content.Server/Atmos/Hotspot.cs +++ b/Content.Server/Atmos/Hotspot.cs @@ -23,7 +23,7 @@ namespace Content.Server.Atmos /// State for the fire sprite. /// [ViewVariables] - public int State; + public byte State; public void Start() { diff --git a/Content.Server/Atmos/TileAtmosphere.cs b/Content.Server/Atmos/TileAtmosphere.cs index 9cf67015ac..6f93d0c9a0 100644 --- a/Content.Server/Atmos/TileAtmosphere.cs +++ b/Content.Server/Atmos/TileAtmosphere.cs @@ -5,6 +5,7 @@ using System.Runtime.CompilerServices; using Content.Server.Atmos.Reactions; using Content.Server.GameObjects.Components.Atmos; using Content.Server.GameObjects.EntitySystems; +using Content.Server.GameObjects.EntitySystems.Atmos; using Content.Server.Interfaces; using Content.Shared.Atmos; using Content.Shared.Audio; @@ -332,32 +333,40 @@ namespace Content.Server.Atmos var tile = tiles[i]; tile._tileAtmosInfo.FastDone = true; if (!(tile._tileAtmosInfo.MoleDelta > 0)) continue; - var eligibleDirections = new List(); - var amtEligibleAdj = 0; + var eligibleDirections = ArrayPool.Shared.Rent(4); + var eligibleDirectionCount = 0; foreach (var direction in Cardinal) { if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue; - // skip anything that isn't part of our current processing block. Original one didn't do this unfortunately, which probably cause some massive lag. + // skip anything that isn't part of our current processing block. if (tile2._tileAtmosInfo.FastDone || tile2._tileAtmosInfo.LastQueueCycle != queueCycle) continue; - eligibleDirections.Add(direction); - amtEligibleAdj++; + eligibleDirections[eligibleDirectionCount++] = direction; } - if (amtEligibleAdj <= 0) + if (eligibleDirectionCount <= 0) continue; // Oof we've painted ourselves into a corner. Bad luck. Next part will handle this. - var molesToMove = tile._tileAtmosInfo.MoleDelta / amtEligibleAdj; + var molesToMove = tile._tileAtmosInfo.MoleDelta / eligibleDirectionCount; foreach (var direction in Cardinal) { - if (eligibleDirections.Contains(direction) || - !tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue; + var hasDirection = false; + for (var j = 0; j < eligibleDirectionCount; j++) + { + if (eligibleDirections[j] != direction) continue; + hasDirection = true; + break; + } + + if (hasDirection || !tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue; tile.AdjustEqMovement(direction, molesToMove); tile._tileAtmosInfo.MoleDelta -= molesToMove; tile2._tileAtmosInfo.MoleDelta += molesToMove; } + + ArrayPool.Shared.Return(eligibleDirections); } giverTilesLength = 0; @@ -446,7 +455,7 @@ namespace Content.Server.Atmos } } - ArrayPool.Shared.Return(queue, true); + ArrayPool.Shared.Return(queue); } else { @@ -516,7 +525,7 @@ namespace Content.Server.Atmos } } - ArrayPool.Shared.Return(queue, true); + ArrayPool.Shared.Return(queue); } for (var i = 0; i < tileCount; i++) @@ -537,9 +546,9 @@ namespace Content.Server.Atmos } } - ArrayPool.Shared.Return(tiles, true); - ArrayPool.Shared.Return(giverTiles, true); - ArrayPool.Shared.Return(takerTiles, true); + ArrayPool.Shared.Return(tiles); + ArrayPool.Shared.Return(giverTiles); + ArrayPool.Shared.Return(takerTiles); } } @@ -737,7 +746,7 @@ namespace Content.Server.Atmos } else { - Hotspot.State = Hotspot.Volume > Atmospherics.CellVolume * 0.4f ? 2 : 1; + Hotspot.State = (byte) (Hotspot.Volume > Atmospherics.CellVolume * 0.4f ? 2 : 1); } if (Hotspot.Temperature > MaxFireTemperatureSustained) @@ -925,16 +934,22 @@ namespace Content.Server.Atmos public void ExplosivelyDepressurize(int cycleNum) { if (Air == null) return; + + const int limit = Atmospherics.ZumosTileLimit; + var totalGasesRemoved = 0f; var queueCycle = ++_gridAtmosphereComponent.EqualizationQueueCycleControl; - var tiles = new List(); - var spaceTiles = new List(); - tiles.Add(this); + var tiles = ArrayPool.Shared.Rent(limit); + var spaceTiles = ArrayPool.Shared.Rent(limit); + + var tileCount = 0; + var spaceTileCount = 0; + + tiles[tileCount++] = this; ResetTileAtmosInfo(); _tileAtmosInfo.LastQueueCycle = queueCycle; - var tileCount = 1; for (var i = 0; i < tileCount; i++) { var tile = tiles[i]; @@ -942,40 +957,44 @@ namespace Content.Server.Atmos tile._tileAtmosInfo.CurrentTransferDirection = Direction.Invalid; if (tile.Air.Immutable) { - spaceTiles.Add(tile); + spaceTiles[spaceTileCount++] = tile; tile.PressureSpecificTarget = tile; } else { - if (i > Atmospherics.ZumosTileLimit) continue; foreach (var direction in Cardinal) { if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue; - if (tile2?.Air == null) continue; + if (tile2.Air == null) continue; if (tile2._tileAtmosInfo.LastQueueCycle == queueCycle) continue; + tile.ConsiderFirelocks(tile2); - if (tile._adjacentTiles[direction]?.Air != null) - { - tile2.ResetTileAtmosInfo(); - tile2._tileAtmosInfo.LastQueueCycle = queueCycle; - tiles.Add(tile2); - tileCount++; - } + + // The firelocks might have closed on us. + if (tile._adjacentTiles[direction]?.Air == null) continue; + tile2.ResetTileAtmosInfo(); + tile2._tileAtmosInfo.LastQueueCycle = queueCycle; + tiles[tileCount++] = tile2; } } + + if (tileCount >= limit || spaceTileCount >= limit) + break; } var queueCycleSlow = ++_gridAtmosphereComponent.EqualizationQueueCycleControl; - var progressionOrder = new List(); - foreach (var tile in spaceTiles) + var progressionOrder = ArrayPool.Shared.Rent(limit * 2); + var progressionCount = 0; + + for (var i = 0; i < spaceTileCount; i++) { - progressionOrder.Add(tile); + var tile = spaceTiles[i]; + progressionOrder[progressionCount++] = tile; tile._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow; tile._tileAtmosInfo.CurrentTransferDirection = Direction.Invalid; } - var progressionCount = progressionOrder.Count; - for (int i = 0; i < progressionCount; i++) + for (var i = 0; i < progressionCount; i++) { var tile = progressionOrder[i]; foreach (var direction in Cardinal) @@ -988,8 +1007,7 @@ namespace Content.Server.Atmos tile2._tileAtmosInfo.CurrentTransferAmount = 0; tile2.PressureSpecificTarget = tile.PressureSpecificTarget; tile2._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow; - progressionOrder.Add(tile2); - progressionCount++; + progressionOrder[progressionCount++] = tile2; } } @@ -1017,6 +1035,10 @@ namespace Content.Server.Atmos tile.UpdateVisuals(); tile.HandleDecompressionFloorRip(sum); } + + ArrayPool.Shared.Return(tiles); + ArrayPool.Shared.Return(spaceTiles); + ArrayPool.Shared.Return(progressionOrder); } private void HandleDecompressionFloorRip(float sum) @@ -1029,7 +1051,6 @@ namespace Content.Server.Atmos private void ConsiderFirelocks(TileAtmosphere other) { // TODO ATMOS firelocks! - //throw new NotImplementedException(); } private void React() diff --git a/Content.Server/Body/BodyCommands.cs b/Content.Server/Body/BodyCommands.cs new file mode 100644 index 0000000000..9809a5845f --- /dev/null +++ b/Content.Server/Body/BodyCommands.cs @@ -0,0 +1,147 @@ +#nullable enable +using System.Linq; +using Content.Server.GameObjects.Components.Body; +using Content.Shared.Body.Part; +using Content.Shared.GameObjects.Components.Body; +using Robust.Server.Interfaces.Console; +using Robust.Server.Interfaces.Player; +using Robust.Shared.Interfaces.Random; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.Body +{ + class AddHandCommand : IClientCommand + { + public string Command => "addhand"; + public string Description => "Adds a hand to your entity."; + public string Help => $"Usage: {Command}"; + + public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + { + if (player == null) + { + shell.SendText(player, "Only a player can run this command."); + return; + } + + if (player.AttachedEntity == null) + { + shell.SendText(player, "You have no entity."); + return; + } + + 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." : ".")}"; + + shell.SendText(player, text); + return; + } + + var prototypeManager = IoCManager.Resolve(); + prototypeManager.TryIndex("bodyPart.Hand.BasicHuman", out BodyPartPrototype prototype); + + var part = new BodyPart(prototype); + var slot = part.GetHashCode().ToString(); + + body.Template.Slots.Add(slot, BodyPartType.Hand); + body.InstallBodyPart(part, slot); + } + } + + class RemoveHandCommand : IClientCommand + { + public string Command => "removehand"; + public string Description => "Removes a hand from your entity."; + public string Help => $"Usage: {Command}"; + + public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + { + if (player == null) + { + shell.SendText(player, "Only a player can run this command."); + return; + } + + if (player.AttachedEntity == null) + { + shell.SendText(player, "You have no entity."); + return; + } + + 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." : ".")}"; + + shell.SendText(player, text); + return; + } + + var hand = body.Parts.FirstOrDefault(x => x.Value.PartType == BodyPartType.Hand); + if (hand.Value == null) + { + shell.SendText(player, "You have no hands."); + } + else + { + body.DisconnectBodyPart(hand.Value, true); + } + } + } + + class DestroyMechanismCommand : IClientCommand + { + public string Command => "destroymechanism"; + public string Description => "Destroys a mechanism from your entity"; + public string Help => $"Usage: {Command} "; + + public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) + { + if (player == null) + { + shell.SendText(player, "Only a player can run this command."); + return; + } + + if (args.Length == 0) + { + shell.SendText(player, Help); + return; + } + + if (player.AttachedEntity == null) + { + shell.SendText(player, "You have no entity."); + return; + } + + 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." : ".")}"; + + shell.SendText(player, text); + return; + } + + var mechanismName = string.Join(" ", args).ToLowerInvariant(); + + foreach (var part in body.Parts.Values) + foreach (var mechanism in part.Mechanisms) + { + if (mechanism.Name.ToLowerInvariant() == mechanismName) + { + part.DestroyMechanism(mechanism); + shell.SendText(player, $"Mechanism with name {mechanismName} has been destroyed."); + return; + } + } + + shell.SendText(player, $"No mechanism was found with name {mechanismName}."); + } + } +} diff --git a/Content.Server/Body/BodyPart.cs b/Content.Server/Body/BodyPart.cs new file mode 100644 index 0000000000..32121f056a --- /dev/null +++ b/Content.Server/Body/BodyPart.cs @@ -0,0 +1,602 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Server.Body.Mechanisms; +using Content.Server.Body.Surgery; +using Content.Server.GameObjects.Components.Body; +using Content.Server.GameObjects.Components.Metabolism; +using Content.Shared.Body.Mechanism; +using Content.Shared.Body.Part; +using Content.Shared.Body.Part.Properties; +using Content.Shared.Damage.DamageContainer; +using Content.Shared.Damage.ResistanceSet; +using Content.Shared.GameObjects.Components.Body; +using Content.Shared.GameObjects.Components.Damage; +using Robust.Server.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Reflection; +using Robust.Shared.Interfaces.Serialization; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; + +namespace Content.Server.Body +{ + /// + /// Data class representing a singular limb such as an arm or a leg. + /// Typically held within either a , + /// which coordinates functions between BodyParts, or a + /// . + /// + public class BodyPart + { + /// + /// The body that this body part is in, if any. + /// + private BodyManagerComponent? _body; + + /// + /// Set of all currently inside this + /// . + /// To add and remove from this list see and + /// + /// + private readonly HashSet _mechanisms = new HashSet(); + + public BodyPart(BodyPartPrototype data) + { + SurgeryData = null!; + Properties = new HashSet(); + Name = null!; + Plural = null!; + RSIPath = null!; + RSIState = null!; + RSIMap = null!; + Damage = null!; + Resistances = null!; + + LoadFromPrototype(data); + } + + /// + /// The body that this body part is in, if any. + /// + [ViewVariables] + public BodyManagerComponent? Body + { + get => _body; + set + { + var old = _body; + _body = value; + + if (value == null && old != null) + { + foreach (var mechanism in Mechanisms) + { + mechanism.RemovedFromBody(old); + } + } + else + { + foreach (var mechanism in Mechanisms) + { + mechanism.InstalledIntoBody(); + } + } + } + } + + /// + /// The class currently representing this BodyPart's + /// surgery status. + /// + [ViewVariables] private SurgeryData SurgeryData { get; set; } + + /// + /// How much space is currently taken up by Mechanisms in this BodyPart. + /// + [ViewVariables] private int SizeUsed { get; set; } + + /// + /// List of properties, allowing for additional + /// data classes to be attached to a limb, such as a "length" class to an arm. + /// + [ViewVariables] + private HashSet Properties { get; } + + /// + /// The name of this , often displayed to the user. + /// For example, it could be named "advanced robotic arm". + /// + [ViewVariables] + public string Name { get; private set; } + + /// + /// Plural version of this name. + /// + [ViewVariables] + public string Plural { get; private set; } + + /// + /// Path to the RSI that represents this . + /// + [ViewVariables] + public string RSIPath { get; private set; } + + /// + /// RSI state that represents this . + /// + [ViewVariables] + public string RSIState { get; private set; } + + /// + /// RSI map keys that this body part changes on the sprite. + /// + [ViewVariables] + public Enum? RSIMap { get; set; } + + /// + /// RSI color of this body part. + /// + // TODO: SpriteComponent rework + public Color? RSIColor { get; set; } + + /// + /// that this is considered + /// to be. + /// For example, . + /// + [ViewVariables] + public BodyPartType PartType { get; private set; } + + /// + /// Determines many things: how many mechanisms can be fit inside this + /// , whether a body can fit through tiny crevices, etc. + /// + [ViewVariables] + private int Size { get; set; } + + /// + /// Max HP of this . + /// + [ViewVariables] + public int MaxDurability { get; private set; } + + /// + /// Current HP of this based on sum of all damage types. + /// + [ViewVariables] + public int CurrentDurability => MaxDurability - Damage.TotalDamage; + + // TODO: Individual body part damage + /// + /// Current damage dealt to this . + /// + [ViewVariables] + public DamageContainer Damage { get; private set; } + + /// + /// Armor of this against damages. + /// + [ViewVariables] + public ResistanceSet Resistances { get; private set; } + + /// + /// At what HP this destroyed. + /// + [ViewVariables] + public int DestroyThreshold { get; private set; } + + /// + /// What types of BodyParts this can easily attach to. + /// For the most part, most limbs aren't universal and require extra work to + /// attach between types. + /// + [ViewVariables] + public BodyPartCompatibility Compatibility { get; private set; } + + /// + /// Set of all currently inside this + /// . + /// + [ViewVariables] + public IReadOnlyCollection Mechanisms => _mechanisms; + + /// + /// This method is called by + /// before is called. + /// + public void PreMetabolism(float frameTime) + { + foreach (var mechanism in Mechanisms) + { + mechanism.PreMetabolism(frameTime); + } + } + + /// + /// This method is called by + /// after is called. + /// + public void PostMetabolism(float frameTime) + { + foreach (var mechanism in Mechanisms) + { + mechanism.PreMetabolism(frameTime); + } + } + + /// + /// Attempts to add the given . + /// + /// + /// True if a of that type doesn't exist, + /// false otherwise. + /// + public bool TryAddProperty(BodyPartProperty property) + { + if (HasProperty(property.GetType())) + { + return false; + } + + Properties.Add(property); + return true; + } + + /// + /// Attempts to retrieve the given type. + /// The resulting will be null if unsuccessful. + /// + /// The property if found, null otherwise. + /// The type of the property to find. + /// True if successful, false otherwise. + public bool TryGetProperty(out T property) + { + property = (T) Properties.First(x => x.GetType() == typeof(T)); + return property != null; + } + + /// + /// Attempts to retrieve the given type. + /// The resulting will be null if unsuccessful. + /// + /// True if successful, false otherwise. + public bool TryGetProperty(Type propertyType, out BodyPartProperty property) + { + property = (BodyPartProperty) Properties.First(x => x.GetType() == propertyType); + return property != null; + } + + /// + /// Checks if the given type is on this . + /// + /// + /// The subtype of to look for. + /// + /// + /// True if this has a property of type + /// , false otherwise. + /// + public bool HasProperty() where T : BodyPartProperty + { + return Properties.Count(x => x.GetType() == typeof(T)) > 0; + } + + /// + /// Checks if a subtype of is on this + /// . + /// + /// + /// The subtype of to look for. + /// + /// + /// True if this has a property of type + /// , false otherwise. + /// + public bool HasProperty(Type propertyType) + { + return Properties.Count(x => x.GetType() == propertyType) > 0; + } + + /// + /// Checks if another can be connected to this one. + /// + /// The part to connect. + /// True if it can be connected, false otherwise. + public bool CanAttachBodyPart(BodyPart toBeConnected) + { + return SurgeryData.CanAttachBodyPart(toBeConnected); + } + + /// + /// Checks if a can be installed on this + /// . + /// + /// True if it can be installed, false otherwise. + public bool CanInstallMechanism(Mechanism mechanism) + { + return SizeUsed + mechanism.Size <= Size && + SurgeryData.CanInstallMechanism(mechanism); + } + + /// + /// Tries to install a mechanism onto this body part. + /// Call instead if you want to + /// easily install an with a + /// . + /// + /// The mechanism to try to install. + /// + /// True if successful, false if there was an error + /// (e.g. not enough room in ). + /// + private bool TryInstallMechanism(Mechanism mechanism) + { + if (!CanInstallMechanism(mechanism)) + { + return false; + } + + AddMechanism(mechanism); + + return true; + } + + /// + /// Tries to install a into this + /// , potentially deleting the dropped + /// . + /// + /// The mechanism to install. + /// + /// True if successful, false if there was an error + /// (e.g. not enough room in ). + /// + public bool TryInstallDroppedMechanism(DroppedMechanismComponent droppedMechanism) + { + if (!TryInstallMechanism(droppedMechanism.ContainedMechanism)) + { + return false; //Installing the mechanism failed for some reason. + } + + droppedMechanism.Owner.Delete(); + return true; + } + + /// + /// Tries to remove the given reference from + /// this . + /// + /// + /// The newly spawned , or null + /// if there was an error in spawning the entity or removing the mechanism. + /// + public bool TryDropMechanism(IEntity dropLocation, Mechanism mechanismTarget, + [NotNullWhen(true)] out DroppedMechanismComponent dropped) + { + dropped = null!; + + if (!_mechanisms.Remove(mechanismTarget)) + { + return false; + } + + SizeUsed -= mechanismTarget.Size; + + var entityManager = IoCManager.Resolve(); + var position = dropLocation.Transform.GridPosition; + var mechanismEntity = entityManager.SpawnEntity("BaseDroppedMechanism", position); + + dropped = mechanismEntity.GetComponent(); + dropped.InitializeDroppedMechanism(mechanismTarget); + + return true; + } + + /// + /// Tries to destroy the given in this + /// . Does NOT spawn a dropped entity. + /// + /// + /// Tries to destroy the given in this + /// . + /// + /// The mechanism to destroy. + /// True if successful, false otherwise. + public bool DestroyMechanism(Mechanism mechanismTarget) + { + if (!RemoveMechanism(mechanismTarget)) + { + return false; + } + + return true; + } + + /// + /// Checks if the given can be used on + /// the current state of this . + /// + /// True if it can be used, false otherwise. + public bool SurgeryCheck(SurgeryType toolType) + { + return SurgeryData.CheckSurgery(toolType); + } + + /// + /// Attempts to perform surgery on this with the given + /// tool. + /// + /// True if successful, false if there was an error. + public bool AttemptSurgery(SurgeryType toolType, IBodyPartContainer target, ISurgeon surgeon, IEntity performer) + { + return SurgeryData.PerformSurgery(toolType, target, surgeon, performer); + } + + private void AddMechanism(Mechanism mechanism) + { + DebugTools.AssertNotNull(mechanism); + + _mechanisms.Add(mechanism); + SizeUsed += mechanism.Size; + mechanism.Part = this; + + mechanism.EnsureInitialize(); + + if (Body == null) + { + return; + } + + if (!Body.Template.MechanismLayers.TryGetValue(mechanism.Id, out var mapString)) + { + return; + } + + if (!IoCManager.Resolve().TryParseEnumReference(mapString, out var @enum)) + { + Logger.Warning($"Template {Body.Template.Name} has an invalid RSI map key {mapString} for mechanism {mechanism.Id}."); + return; + } + + var message = new MechanismSpriteAddedMessage(@enum); + + Body.Owner.SendNetworkMessage(Body, message); + } + + /// + /// Tries to remove the given from this + /// . + /// + /// The mechanism to remove. + /// True if it was removed, false otherwise. + private bool RemoveMechanism(Mechanism mechanism) + { + DebugTools.AssertNotNull(mechanism); + + if (!_mechanisms.Remove(mechanism)) + { + return false; + } + + SizeUsed -= mechanism.Size; + mechanism.Part = null; + + if (Body == null) + { + return true; + } + + if (!Body.Template.MechanismLayers.TryGetValue(mechanism.Id, out var mapString)) + { + return true; + } + + if (!IoCManager.Resolve().TryParseEnumReference(mapString, out var @enum)) + { + Logger.Warning($"Template {Body.Template.Name} has an invalid RSI map key {mapString} for mechanism {mechanism.Id}."); + return true; + } + + var message = new MechanismSpriteRemovedMessage(@enum); + + Body.Owner.SendNetworkMessage(Body, message); + + return true; + } + + /// + /// Loads the given . + /// Current data on this will be overwritten! + /// + protected virtual void LoadFromPrototype(BodyPartPrototype data) + { + var prototypeManager = IoCManager.Resolve(); + + Name = data.Name; + Plural = data.Plural; + PartType = data.PartType; + RSIPath = data.RSIPath; + RSIState = data.RSIState; + MaxDurability = data.Durability; + + if (!prototypeManager.TryIndex(data.DamageContainerPresetId, + out DamageContainerPrototype damageContainerData)) + { + throw new InvalidOperationException( + $"No {nameof(DamageContainerPrototype)} found with id {data.DamageContainerPresetId}"); + } + + Damage = new DamageContainer(OnHealthChanged, damageContainerData); + + if (!prototypeManager.TryIndex(data.ResistanceSetId, out ResistanceSetPrototype resistancesData)) + { + throw new InvalidOperationException( + $"No {nameof(ResistanceSetPrototype)} found with id {data.ResistanceSetId}"); + } + + Resistances = new ResistanceSet(resistancesData); + Size = data.Size; + Compatibility = data.Compatibility; + + Properties.Clear(); + Properties.UnionWith(data.Properties); + + var surgeryDataType = Type.GetType(data.SurgeryDataName); + + if (surgeryDataType == null) + { + throw new InvalidOperationException($"No {nameof(Surgery.SurgeryData)} found with name {data.SurgeryDataName}"); + } + + if (!surgeryDataType.IsSubclassOf(typeof(SurgeryData))) + { + throw new InvalidOperationException( + $"Class {data.SurgeryDataName} is not a subtype of {nameof(Surgery.SurgeryData)} with id {data.ID}"); + } + + SurgeryData = IoCManager.Resolve().CreateInstance(surgeryDataType, new object[] {this}); + + foreach (var id in data.Mechanisms) + { + if (!prototypeManager.TryIndex(id, out MechanismPrototype mechanismData)) + { + throw new InvalidOperationException($"No {nameof(MechanismPrototype)} found with id {id}"); + } + + var mechanism = new Mechanism(mechanismData); + + AddMechanism(mechanism); + } + } + + private void OnHealthChanged(List changes) + { + // TODO + } + + public bool SpawnDropped([NotNullWhen(true)] out IEntity dropped) + { + dropped = default!; + + if (Body == null) + { + return false; + } + + dropped = IoCManager.Resolve().SpawnEntity("BaseDroppedBodyPart", Body.Owner.Transform.GridPosition); + + dropped.GetComponent().TransferBodyPartData(this); + + return true; + } + } +} diff --git a/Content.Server/Body/BodyPreset.cs b/Content.Server/Body/BodyPreset.cs new file mode 100644 index 0000000000..12e67c53e9 --- /dev/null +++ b/Content.Server/Body/BodyPreset.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using Content.Shared.Body.Part; +using Content.Shared.Body.Preset; +using Robust.Shared.ViewVariables; + +namespace Content.Server.Body +{ + /// + /// Stores data on what should + /// fill a BodyTemplate. + /// Used for loading complete body presets, like a "basic human" with all + /// human limbs. + /// + public class BodyPreset + { + public BodyPreset(BodyPresetPrototype data) + { + LoadFromPrototype(data); + } + + [ViewVariables] public string Name { get; private set; } + + /// + /// Maps a template slot to the ID of the that should + /// fill it. E.g. "right arm" : "BodyPart.arm.basic_human". + /// + [ViewVariables] + public Dictionary PartIDs { get; private set; } + + protected virtual void LoadFromPrototype(BodyPresetPrototype data) + { + Name = data.Name; + PartIDs = data.PartIDs; + } + } +} diff --git a/Content.Server/Body/BodyTemplate.cs b/Content.Server/Body/BodyTemplate.cs new file mode 100644 index 0000000000..ae32c02688 --- /dev/null +++ b/Content.Server/Body/BodyTemplate.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Server.GameObjects.Components.Body; +using Content.Shared.Body.Template; +using Content.Shared.GameObjects.Components.Body; +using Robust.Shared.ViewVariables; + +namespace Content.Server.Body +{ + /// + /// This class is a data capsule representing the standard format of a + /// . + /// For instance, the "humanoid" BodyTemplate defines two arms, each connected to + /// a torso and so on. + /// Capable of loading data from a . + /// + public class BodyTemplate + { + public BodyTemplate() + { + Name = "empty"; + CenterSlot = ""; + Slots = new Dictionary(); + Connections = new Dictionary>(); + Layers = new Dictionary(); + MechanismLayers = new Dictionary(); + } + + public BodyTemplate(BodyTemplatePrototype data) + { + LoadFromPrototype(data); + } + + [ViewVariables] public string Name { get; private set; } + + /// + /// The name of the center BodyPart. For humans, this is set to "torso". + /// Used in many calculations. + /// + [ViewVariables] + public string CenterSlot { get; set; } + + /// + /// Maps all parts on this template to its BodyPartType. + /// For instance, "right arm" is mapped to "BodyPartType.arm" on the humanoid + /// template. + /// + [ViewVariables] + public Dictionary Slots { get; private set; } + + /// + /// Maps limb name to the list of their connections to other limbs. + /// For instance, on the humanoid template "torso" is mapped to a list + /// containing "right arm", "left arm", "left leg", and "right leg". + /// This is mapped both ways during runtime, but in the prototype only one + /// way has to be defined, i.e., "torso" to "left arm" will automatically + /// map "left arm" to "torso". + /// + [ViewVariables] + public Dictionary> Connections { get; private set; } + + [ViewVariables] + public Dictionary Layers { get; private set; } + + [ViewVariables] + public Dictionary MechanismLayers { get; private set; } + + public bool Equals(BodyTemplate other) + { + return GetHashCode() == other.GetHashCode(); + } + + /// + /// Checks if the given slot exists in this . + /// + /// True if it does, false otherwise. + public bool SlotExists(string slotName) + { + return Slots.Keys.Any(slot => slot == slotName); + } + + /// + /// Calculates the hash code for this instance of . + /// It does not matter in which order the Connections or Slots are defined. + /// + /// + /// An integer unique to this 's layout. + /// + public override int GetHashCode() + { + var slotsHash = 0; + var connectionsHash = 0; + + foreach (var (key, value) in Slots) + { + var slot = key.GetHashCode(); + slot = HashCode.Combine(slot, value.GetHashCode()); + slotsHash ^= slot; + } + + var connections = new List(); + foreach (var (key, value) in Connections) + { + foreach (var targetBodyPart in value) + { + var connection = key.GetHashCode() ^ targetBodyPart.GetHashCode(); + if (!connections.Contains(connection)) + { + connections.Add(connection); + } + } + } + + foreach (var connection in connections) + { + connectionsHash ^= connection; + } + + // One of the unit tests considers 0 to be an error, but it will be 0 if + // the BodyTemplate is empty, so let's shift that up to 1. + var hash = HashCode.Combine( + CenterSlot.GetHashCode(), + slotsHash, + connectionsHash); + + if (hash == 0) + { + hash++; + } + + return hash; + } + + protected virtual void LoadFromPrototype(BodyTemplatePrototype data) + { + Name = data.Name; + CenterSlot = data.CenterSlot; + Slots = data.Slots; + Connections = data.Connections; + Layers = data.Layers; + MechanismLayers = data.MechanismLayers; + } + } +} diff --git a/Content.Server/Body/IBodyPartContainer.cs b/Content.Server/Body/IBodyPartContainer.cs new file mode 100644 index 0000000000..fdc4c402e9 --- /dev/null +++ b/Content.Server/Body/IBodyPartContainer.cs @@ -0,0 +1,19 @@ +using Content.Server.Body.Surgery; +using Content.Server.GameObjects.Components.Body; + +namespace Content.Server.Body +{ + /// + /// Making a class inherit from this interface allows you to do many things with + /// it in the class. + /// This includes passing it as an argument to a + /// delegate, as to later typecast it back + /// to the original class type. + /// Every BodyPart also needs an to be its parent + /// (i.e. the holds many , + /// each of which have an upward reference to it). + /// + public interface IBodyPartContainer + { + } +} diff --git a/Content.Server/Body/Mechanisms/Behaviors/BrainBehavior.cs b/Content.Server/Body/Mechanisms/Behaviors/BrainBehavior.cs new file mode 100644 index 0000000000..869534803e --- /dev/null +++ b/Content.Server/Body/Mechanisms/Behaviors/BrainBehavior.cs @@ -0,0 +1,9 @@ +namespace Content.Server.Body.Mechanisms.Behaviors +{ + /// + /// The behaviors of a brain, inhabitable by a player. + /// + public class BrainBehavior : MechanismBehavior + { + } +} diff --git a/Content.Server/Body/Mechanisms/Behaviors/HeartBehavior.cs b/Content.Server/Body/Mechanisms/Behaviors/HeartBehavior.cs new file mode 100644 index 0000000000..2229357bfc --- /dev/null +++ b/Content.Server/Body/Mechanisms/Behaviors/HeartBehavior.cs @@ -0,0 +1,38 @@ +#nullable enable +using System; +using Content.Server.Body.Network; +using Content.Server.GameObjects.Components.Body.Circulatory; +using JetBrains.Annotations; + +namespace Content.Server.Body.Mechanisms.Behaviors +{ + [UsedImplicitly] + public class HeartBehavior : MechanismBehavior + { + private float _accumulatedFrameTime; + + protected override Type? Network => typeof(CirculatoryNetwork); + + public override void PreMetabolism(float frameTime) + { + // TODO do between pre and metabolism + base.PreMetabolism(frameTime); + + if (Mechanism.Body == null || + !Mechanism.Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream)) + { + return; + } + + // Update at most once per second + _accumulatedFrameTime += frameTime; + + // TODO: Move/accept/process bloodstream reagents only when the heart is pumping + if (_accumulatedFrameTime >= 1) + { + // bloodstream.Update(_accumulatedFrameTime); + _accumulatedFrameTime -= 1; + } + } + } +} diff --git a/Content.Server/Body/Mechanisms/Behaviors/LungBehavior.cs b/Content.Server/Body/Mechanisms/Behaviors/LungBehavior.cs new file mode 100644 index 0000000000..bc2591c387 --- /dev/null +++ b/Content.Server/Body/Mechanisms/Behaviors/LungBehavior.cs @@ -0,0 +1,27 @@ +#nullable enable +using System; +using Content.Server.Body.Network; +using Content.Server.GameObjects.Components.Body.Respiratory; +using JetBrains.Annotations; + +namespace Content.Server.Body.Mechanisms.Behaviors +{ + [UsedImplicitly] + public class LungBehavior : MechanismBehavior + { + protected override Type? Network => typeof(RespiratoryNetwork); + + public override void PreMetabolism(float frameTime) + { + base.PreMetabolism(frameTime); + + if (Mechanism.Body == null || + !Mechanism.Body.Owner.TryGetComponent(out LungComponent? lung)) + { + return; + } + + lung.Update(frameTime); + } + } +} diff --git a/Content.Server/Body/Mechanisms/Behaviors/MechanismBehavior.cs b/Content.Server/Body/Mechanisms/Behaviors/MechanismBehavior.cs new file mode 100644 index 0000000000..77c3d5e982 --- /dev/null +++ b/Content.Server/Body/Mechanisms/Behaviors/MechanismBehavior.cs @@ -0,0 +1,185 @@ +#nullable enable +using System; +using Content.Server.GameObjects.Components.Body; +using Content.Server.GameObjects.Components.Metabolism; + +namespace Content.Server.Body.Mechanisms.Behaviors +{ + /// + /// The behaviors a mechanism performs. + /// + public abstract class MechanismBehavior + { + private bool Initialized { get; set; } + + private bool Removed { get; set; } + + /// + /// The network, if any, that this behavior forms when its mechanism is + /// added and destroys when its mechanism is removed. + /// + protected virtual Type? Network { get; } = null; + + /// + /// Upward reference to the parent that this + /// behavior is attached to. + /// + protected Mechanism Mechanism { get; private set; } = null!; + + /// + /// Called by a to initialize this behavior. + /// + /// The mechanism that owns this behavior. + /// + /// If the mechanism has already been initialized. + /// + public void Initialize(Mechanism mechanism) + { + if (Initialized) + { + throw new InvalidOperationException("This mechanism has already been initialized."); + } + + Mechanism = mechanism; + + Initialize(); + + if (Mechanism.Body != null) + { + OnInstalledIntoBody(); + } + + if (Mechanism.Part != null) + { + OnInstalledIntoPart(); + } + + Initialized = true; + } + + /// + /// Called when a behavior is removed from a . + /// + public void Remove() + { + OnRemove(); + TryRemoveNetwork(Mechanism.Body); + + Mechanism = null!; + Removed = true; + } + + /// + /// Called when the containing is attached to a + /// . + /// For instance, attaching a head to a body will call this on the brain inside. + /// + public void InstalledIntoBody() + { + TryAddNetwork(); + OnInstalledIntoBody(); + } + + /// + /// Called when the parent is + /// installed into a . + /// For instance, putting a brain into an empty head. + /// + public void InstalledIntoPart() + { + TryAddNetwork(); + OnInstalledIntoPart(); + } + + /// + /// Called when the containing is removed from a + /// . + /// For instance, cutting off ones head will call this on the brain inside. + /// + public void RemovedFromBody(BodyManagerComponent old) + { + OnRemovedFromBody(old); + TryRemoveNetwork(old); + } + + /// + /// Called when the parent is removed from a + /// . + /// For instance, taking a brain out of ones head. + /// + public void RemovedFromPart(BodyPart old) + { + OnRemovedFromPart(old); + TryRemoveNetwork(old.Body); + } + + private void TryAddNetwork() + { + if (Network != null) + { + Mechanism.Body?.EnsureNetwork(Network); + } + } + + private void TryRemoveNetwork(BodyManagerComponent? body) + { + if (Network != null) + { + body?.RemoveNetwork(Network); + } + } + + /// + /// Called by when this behavior is first initialized. + /// + protected virtual void Initialize() { } + + protected virtual void OnRemove() { } + + /// + /// Called when the containing is attached to a + /// . + /// For instance, attaching a head to a body will call this on the brain inside. + /// + protected virtual void OnInstalledIntoBody() { } + + /// + /// Called when the parent is + /// installed into a . + /// For instance, putting a brain into an empty head. + /// + protected virtual void OnInstalledIntoPart() { } + + /// + /// Called when the containing is removed from a + /// . + /// For instance, cutting off ones head will call this on the brain inside. + /// + protected virtual void OnRemovedFromBody(BodyManagerComponent old) { } + + /// + /// Called when the parent is removed from a + /// . + /// For instance, taking a brain out of ones head. + /// + protected virtual void OnRemovedFromPart(BodyPart old) { } + + /// + /// Called every update when this behavior is connected to a + /// , but not while in a + /// or + /// , + /// before is called. + /// + public virtual void PreMetabolism(float frameTime) { } + + /// + /// Called every update when this behavior is connected to a + /// , but not while in a + /// or + /// , + /// after is called. + /// + public virtual void PostMetabolism(float frameTime) { } + } +} diff --git a/Content.Server/Body/Mechanisms/Behaviors/StomachBehavior.cs b/Content.Server/Body/Mechanisms/Behaviors/StomachBehavior.cs new file mode 100644 index 0000000000..ce6a2bcf43 --- /dev/null +++ b/Content.Server/Body/Mechanisms/Behaviors/StomachBehavior.cs @@ -0,0 +1,36 @@ +#nullable enable +using System; +using Content.Server.Body.Network; +using Content.Server.GameObjects.Components.Body.Digestive; +using JetBrains.Annotations; + +namespace Content.Server.Body.Mechanisms.Behaviors +{ + [UsedImplicitly] + public class StomachBehavior : MechanismBehavior + { + private float _accumulatedFrameTime; + + protected override Type? Network => typeof(DigestiveNetwork); + + public override void PreMetabolism(float frameTime) + { + base.PreMetabolism(frameTime); + + if (Mechanism.Body == null || + !Mechanism.Body.Owner.TryGetComponent(out StomachComponent? stomach)) + { + return; + } + + // Update at most once per second + _accumulatedFrameTime += frameTime; + + if (_accumulatedFrameTime >= 1) + { + stomach.Update(_accumulatedFrameTime); + _accumulatedFrameTime -= 1; + } + } + } +} diff --git a/Content.Server/Body/Mechanisms/Mechanism.cs b/Content.Server/Body/Mechanisms/Mechanism.cs new file mode 100644 index 0000000000..9c88b0425b --- /dev/null +++ b/Content.Server/Body/Mechanisms/Mechanism.cs @@ -0,0 +1,249 @@ +#nullable enable +using System; +using System.Collections.Generic; +using Content.Server.Body.Mechanisms.Behaviors; +using Content.Server.GameObjects.Components.Body; +using Content.Server.GameObjects.Components.Metabolism; +using Content.Shared.Body.Mechanism; +using Content.Shared.GameObjects.Components.Body; +using Robust.Shared.IoC; +using Robust.Shared.ViewVariables; + +namespace Content.Server.Body.Mechanisms +{ + /// + /// Data class representing a persistent item inside a . + /// This includes livers, eyes, cameras, brains, explosive implants, + /// binary communicators, and other things. + /// + public class Mechanism + { + private BodyPart? _part; + + public Mechanism(MechanismPrototype data) + { + Data = data; + Id = null!; + Name = null!; + Description = null!; + ExamineMessage = null!; + RSIPath = null!; + RSIState = null!; + Behaviors = new List(); + } + + [ViewVariables] private bool Initialized { get; set; } + + [ViewVariables] private MechanismPrototype Data { get; set; } + + [ViewVariables] public string Id { get; private set; } + + [ViewVariables] public string Name { get; set; } + + /// + /// Professional description of the . + /// + [ViewVariables] + public string Description { get; set; } + + /// + /// The message to display upon examining a mob with this Mechanism installed. + /// If the string is empty (""), no message will be displayed. + /// + [ViewVariables] + public string ExamineMessage { get; set; } + + /// + /// Path to the RSI that represents this . + /// + [ViewVariables] + public string RSIPath { get; set; } + + /// + /// RSI state that represents this . + /// + [ViewVariables] + public string RSIState { get; set; } + + /// + /// Max HP of this . + /// + [ViewVariables] + public int MaxDurability { get; set; } + + /// + /// Current HP of this . + /// + [ViewVariables] + public int CurrentDurability { get; set; } + + /// + /// At what HP this is completely destroyed. + /// + [ViewVariables] + public int DestroyThreshold { get; set; } + + /// + /// Armor of this against attacks. + /// + [ViewVariables] + public int Resistance { get; set; } + + /// + /// Determines a handful of things - mostly whether this + /// can fit into a . + /// + [ViewVariables] + public int Size { get; set; } + + /// + /// What kind of this can be + /// easily installed into. + /// + [ViewVariables] + public BodyPartCompatibility Compatibility { get; set; } + + /// + /// The behaviors that this performs. + /// + [ViewVariables] + private List Behaviors { get; } + + public BodyManagerComponent? Body => Part?.Body; + + public BodyPart? Part + { + get => _part; + set + { + var old = _part; + _part = value; + + if (value == null && old != null) + { + foreach (var behavior in Behaviors) + { + behavior.RemovedFromPart(old); + } + } + else + { + foreach (var behavior in Behaviors) + { + behavior.InstalledIntoPart(); + } + } + } + } + + public void EnsureInitialize() + { + if (Initialized) + { + return; + } + + LoadFromPrototype(Data); + Initialized = true; + } + + /// + /// Loads the given . + /// Current data on this will be overwritten! + /// + private void LoadFromPrototype(MechanismPrototype data) + { + Data = data; + Id = data.ID; + Name = data.Name; + Description = data.Description; + ExamineMessage = data.ExamineMessage; + RSIPath = data.RSIPath; + RSIState = data.RSIState; + MaxDurability = data.Durability; + CurrentDurability = MaxDurability; + DestroyThreshold = data.DestroyThreshold; + Resistance = data.Resistance; + Size = data.Size; + Compatibility = data.Compatibility; + + foreach (var behavior in Behaviors.ToArray()) + { + RemoveBehavior(behavior); + } + + foreach (var mechanismBehaviorName in data.BehaviorClasses) + { + var mechanismBehaviorType = Type.GetType(mechanismBehaviorName); + + if (mechanismBehaviorType == null) + { + throw new InvalidOperationException( + $"No {nameof(MechanismBehavior)} found with name {mechanismBehaviorName}"); + } + + if (!mechanismBehaviorType.IsSubclassOf(typeof(MechanismBehavior))) + { + throw new InvalidOperationException( + $"Class {mechanismBehaviorName} is not a subtype of {nameof(MechanismBehavior)} for mechanism prototype {data.ID}"); + } + + var newBehavior = IoCManager.Resolve().CreateInstance(mechanismBehaviorType); + + AddBehavior(newBehavior); + } + } + + public void InstalledIntoBody() + { + foreach (var behavior in Behaviors) + { + behavior.InstalledIntoBody(); + } + } + + public void RemovedFromBody(BodyManagerComponent old) + { + foreach (var behavior in Behaviors) + { + behavior.RemovedFromBody(old); + } + } + + /// + /// This method is called by before + /// is called. + /// + public void PreMetabolism(float frameTime) + { + foreach (var behavior in Behaviors) + { + behavior.PreMetabolism(frameTime); + } + } + + /// + /// This method is called by after + /// is called. + /// + public void PostMetabolism(float frameTime) + { + foreach (var behavior in Behaviors) + { + behavior.PostMetabolism(frameTime); + } + } + + private void AddBehavior(MechanismBehavior behavior) + { + Behaviors.Add(behavior); + behavior.Initialize(this); + } + + private bool RemoveBehavior(MechanismBehavior behavior) + { + behavior.Remove(); + return Behaviors.Remove(behavior); + } + } +} diff --git a/Content.Server/Body/Network/BodyNetwork.cs b/Content.Server/Body/Network/BodyNetwork.cs new file mode 100644 index 0000000000..911bfbde75 --- /dev/null +++ b/Content.Server/Body/Network/BodyNetwork.cs @@ -0,0 +1,76 @@ +using System; +using Content.Server.GameObjects.Components.Body; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Serialization; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Server.Body.Network +{ + /// + /// Represents a "network" such as a bloodstream or electrical power that + /// is coordinated throughout an entire . + /// + public abstract class BodyNetwork : IExposeData + { + [ViewVariables] + public abstract string Name { get; } + + protected IEntity Owner { get; private set; } + + public virtual void ExposeData(ObjectSerializer serializer) { } + + public void OnAdd(IEntity entity) + { + Owner = entity; + OnAdd(); + } + + protected virtual void OnAdd() { } + + public virtual void OnRemove() { } + + /// + /// Called every update by . + /// + public virtual void Update(float frameTime) { } + } + + public static class BodyNetworkExtensions + { + public static void TryAddNetwork(this IEntity entity, Type type) + { + if (!entity.TryGetComponent(out BodyManagerComponent body)) + { + return; + } + + body.EnsureNetwork(type); + } + + public static void TryAddNetwork(this IEntity entity) where T : BodyNetwork + { + if (!entity.TryGetComponent(out BodyManagerComponent body)) + { + return; + } + + body.EnsureNetwork(); + } + + public static bool TryGetBodyNetwork(this IEntity entity, Type type, out BodyNetwork network) + { + network = null; + + return entity.TryGetComponent(out BodyManagerComponent body) && + body.TryGetNetwork(type, out network); + } + + public static bool TryGetBodyNetwork(this IEntity entity, out T network) where T : BodyNetwork + { + entity.TryGetBodyNetwork(typeof(T), out var unCastNetwork); + network = (T) unCastNetwork; + return network != null; + } + } +} diff --git a/Content.Server/Body/Network/BodyNetworkFactory.cs b/Content.Server/Body/Network/BodyNetworkFactory.cs new file mode 100644 index 0000000000..8b5ec0cc7f --- /dev/null +++ b/Content.Server/Body/Network/BodyNetworkFactory.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using Robust.Shared.Interfaces.Reflection; +using Robust.Shared.IoC; + +namespace Content.Server.Body.Network +{ + public class BodyNetworkFactory : IBodyNetworkFactory + { + [Dependency] private readonly IDynamicTypeFactory _typeFactory = default!; + [Dependency] private readonly IReflectionManager _reflectionManager = default!; + + /// + /// Mapping of body network names to their types. + /// + private readonly Dictionary _names = new Dictionary(); + + private void Register(Type type) + { + if (_names.ContainsValue(type)) + { + throw new InvalidOperationException($"Type is already registered: {type}"); + } + + if (!type.IsSubclassOf(typeof(BodyNetwork))) + { + throw new InvalidOperationException($"{type} is not a subclass of {nameof(BodyNetwork)}"); + } + + var dummy = _typeFactory.CreateInstance(type); + + if (dummy == null) + { + throw new NullReferenceException(); + } + + var name = dummy.Name; + + if (name == null) + { + throw new NullReferenceException($"{type}'s name cannot be null."); + } + + if (_names.ContainsKey(name)) + { + throw new InvalidOperationException($"{name} is already registered."); + } + + _names.Add(name, type); + } + + public void DoAutoRegistrations() + { + var bodyNetwork = typeof(BodyNetwork); + + foreach (var child in _reflectionManager.GetAllChildren(bodyNetwork)) + { + Register(child); + } + } + + public BodyNetwork GetNetwork(string name) + { + Type type; + + try + { + type = _names[name]; + } + catch (KeyNotFoundException) + { + throw new ArgumentException($"No {nameof(BodyNetwork)} exists with name {name}"); + } + + return _typeFactory.CreateInstance(type); + } + + public BodyNetwork GetNetwork(Type type) + { + if (!_names.ContainsValue(type)) + { + throw new ArgumentException($"{type} is not registered."); + } + + return _typeFactory.CreateInstance(type); + } + } +} diff --git a/Content.Server/Body/Network/CirculatoryNetwork.cs b/Content.Server/Body/Network/CirculatoryNetwork.cs new file mode 100644 index 0000000000..c67bc6e201 --- /dev/null +++ b/Content.Server/Body/Network/CirculatoryNetwork.cs @@ -0,0 +1,25 @@ +using Content.Server.GameObjects.Components.Body.Circulatory; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; + +namespace Content.Server.Body.Network +{ + [UsedImplicitly] + public class CirculatoryNetwork : BodyNetwork + { + public override string Name => "Circulatory"; + + protected override void OnAdd() + { + Owner.EnsureComponent(); + } + + public override void OnRemove() + { + if (Owner.HasComponent()) + { + Owner.RemoveComponent(); + } + } + } +} diff --git a/Content.Server/Body/Network/DigestiveNetwork.cs b/Content.Server/Body/Network/DigestiveNetwork.cs new file mode 100644 index 0000000000..6362b55072 --- /dev/null +++ b/Content.Server/Body/Network/DigestiveNetwork.cs @@ -0,0 +1,28 @@ +using Content.Server.GameObjects.Components.Body.Digestive; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; + +namespace Content.Server.Body.Network +{ + /// + /// Represents the system that processes food, liquids, and the reagents inside them. + /// + [UsedImplicitly] + public class DigestiveNetwork : BodyNetwork + { + public override string Name => "Digestive"; + + protected override void OnAdd() + { + Owner.EnsureComponent(); + } + + public override void OnRemove() + { + if (Owner.HasComponent()) + { + Owner.RemoveComponent(); + } + } + } +} diff --git a/Content.Server/Body/Network/IBodyNetworkFactory.cs b/Content.Server/Body/Network/IBodyNetworkFactory.cs new file mode 100644 index 0000000000..29d883f21b --- /dev/null +++ b/Content.Server/Body/Network/IBodyNetworkFactory.cs @@ -0,0 +1,13 @@ +using System; + +namespace Content.Server.Body.Network +{ + public interface IBodyNetworkFactory + { + void DoAutoRegistrations(); + + BodyNetwork GetNetwork(string name); + + BodyNetwork GetNetwork(Type type); + } +} diff --git a/Content.Server/Body/Network/RespiratoryNetwork.cs b/Content.Server/Body/Network/RespiratoryNetwork.cs new file mode 100644 index 0000000000..92c974f2e3 --- /dev/null +++ b/Content.Server/Body/Network/RespiratoryNetwork.cs @@ -0,0 +1,25 @@ +using Content.Server.GameObjects.Components.Body.Respiratory; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; + +namespace Content.Server.Body.Network +{ + [UsedImplicitly] + public class RespiratoryNetwork : BodyNetwork + { + public override string Name => "Respiratory"; + + protected override void OnAdd() + { + Owner.EnsureComponent(); + } + + public override void OnRemove() + { + if (Owner.HasComponent()) + { + Owner.RemoveComponent(); + } + } + } +} diff --git a/Content.Server/Body/Surgery/BiologicalSurgeryData.cs b/Content.Server/Body/Surgery/BiologicalSurgeryData.cs new file mode 100644 index 0000000000..37fbb57b29 --- /dev/null +++ b/Content.Server/Body/Surgery/BiologicalSurgeryData.cs @@ -0,0 +1,250 @@ +#nullable enable +using System.Collections.Generic; +using System.Linq; +using Content.Server.Body.Mechanisms; +using Content.Server.GameObjects.Components.Body; +using Content.Shared.GameObjects.Components.Body; +using Content.Shared.Interfaces; +using JetBrains.Annotations; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Localization; + +namespace Content.Server.Body.Surgery +{ + /// + /// Data class representing the surgery state of a biological entity. + /// + [UsedImplicitly] + public class BiologicalSurgeryData : SurgeryData + { + private readonly List _disconnectedOrgans = new List(); + + private bool _skinOpened; + private bool _skinRetracted; + private bool _vesselsClamped; + + public BiologicalSurgeryData(BodyPart parent) : base(parent) { } + + protected override SurgeryAction? GetSurgeryStep(SurgeryType toolType) + { + if (toolType == SurgeryType.Amputation) + { + return RemoveBodyPartSurgery; + } + + if (!_skinOpened) + { + // Case: skin is normal. + if (toolType == SurgeryType.Incision) + { + return OpenSkinSurgery; + } + } + else if (!_vesselsClamped) + { + // Case: skin is opened, but not clamped. + switch (toolType) + { + case SurgeryType.VesselCompression: + return ClampVesselsSurgery; + case SurgeryType.Cauterization: + return CauterizeIncisionSurgery; + } + } + else if (!_skinRetracted) + { + // Case: skin is opened and clamped, but not retracted. + switch (toolType) + { + case SurgeryType.Retraction: + return RetractSkinSurgery; + case SurgeryType.Cauterization: + return CauterizeIncisionSurgery; + } + } + else + { + // Case: skin is fully open. + if (Parent.Mechanisms.Count > 0 && + toolType == SurgeryType.VesselCompression) + { + if (_disconnectedOrgans.Except(Parent.Mechanisms).Count() != 0 || + Parent.Mechanisms.Except(_disconnectedOrgans).Count() != 0) + { + return LoosenOrganSurgery; + } + } + + if (_disconnectedOrgans.Count > 0 && toolType == SurgeryType.Incision) + { + return RemoveOrganSurgery; + } + + if (toolType == SurgeryType.Cauterization) + { + return CauterizeIncisionSurgery; + } + } + + return null; + } + + public override string GetDescription(IEntity target) + { + var toReturn = ""; + + if (_skinOpened && !_vesselsClamped) + { + // Case: skin is opened, but not clamped. + toReturn += Loc.GetString("The skin on {0:their} {1} has an incision, but it is prone to bleeding.\n", + target, Parent.Name); + } + else if (_skinOpened && _vesselsClamped && !_skinRetracted) + { + // Case: skin is opened and clamped, but not retracted. + toReturn += Loc.GetString("The skin on {0:their} {1} has an incision, but it is not retracted.\n", + target, Parent.Name); + } + else if (_skinOpened && _vesselsClamped && _skinRetracted) + { + // Case: skin is fully open. + toReturn += Loc.GetString("There is an incision on {0:their} {1}.\n", target, Parent.Name); + foreach (var mechanism in _disconnectedOrgans) + { + toReturn += Loc.GetString("{0:their} {1} is loose.\n", target, mechanism.Name); + } + } + + return toReturn; + } + + public override bool CanInstallMechanism(Mechanism mechanism) + { + return _skinOpened && _vesselsClamped && _skinRetracted; + } + + public override bool CanAttachBodyPart(BodyPart part) + { + return true; + // TODO: if a bodypart is disconnected, you should have to do some surgery to allow another bodypart to be attached. + } + + private void OpenSkinSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) + { + performer.PopupMessage(performer, Loc.GetString("Cut open the skin...")); + + // TODO do_after: Delay + _skinOpened = true; + } + + private void ClampVesselsSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) + { + performer.PopupMessage(performer, Loc.GetString("Clamp the vessels...")); + + // TODO do_after: Delay + _vesselsClamped = true; + } + + private void RetractSkinSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) + { + performer.PopupMessage(performer, Loc.GetString("Retract the skin...")); + + // TODO do_after: Delay + _skinRetracted = true; + } + + private void CauterizeIncisionSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) + { + performer.PopupMessage(performer, Loc.GetString("Cauterize the incision...")); + + // TODO do_after: Delay + _skinOpened = false; + _vesselsClamped = false; + _skinRetracted = false; + } + + private void LoosenOrganSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) + { + if (Parent.Mechanisms.Count <= 0) + { + return; + } + + var toSend = new List(); + foreach (var mechanism in Parent.Mechanisms) + { + if (!_disconnectedOrgans.Contains(mechanism)) + { + toSend.Add(mechanism); + } + } + + if (toSend.Count > 0) + { + surgeon.RequestMechanism(toSend, LoosenOrganSurgeryCallback); + } + } + + private void LoosenOrganSurgeryCallback(Mechanism target, IBodyPartContainer container, ISurgeon surgeon, + IEntity performer) + { + if (target == null || !Parent.Mechanisms.Contains(target)) + { + return; + } + + performer.PopupMessage(performer, Loc.GetString("Loosen the organ...")); + + // TODO do_after: Delay + _disconnectedOrgans.Add(target); + } + + private void RemoveOrganSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) + { + if (_disconnectedOrgans.Count <= 0) + { + return; + } + + if (_disconnectedOrgans.Count == 1) + { + RemoveOrganSurgeryCallback(_disconnectedOrgans[0], container, surgeon, performer); + } + else + { + surgeon.RequestMechanism(_disconnectedOrgans, RemoveOrganSurgeryCallback); + } + } + + private void RemoveOrganSurgeryCallback(Mechanism target, IBodyPartContainer container, + ISurgeon surgeon, + IEntity performer) + { + if (target == null || !Parent.Mechanisms.Contains(target)) + { + return; + } + + performer.PopupMessage(performer, Loc.GetString("Remove the organ...")); + + // TODO do_after: Delay + Parent.TryDropMechanism(performer, target, out _); + _disconnectedOrgans.Remove(target); + } + + private void RemoveBodyPartSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) + { + // This surgery requires a DroppedBodyPartComponent. + if (!(container is BodyManagerComponent)) + { + return; + } + + var bmTarget = (BodyManagerComponent) container; + performer.PopupMessage(performer, Loc.GetString("Saw off the limb!")); + + // TODO do_after: Delay + bmTarget.DisconnectBodyPart(Parent, true); + } + } +} diff --git a/Content.Server/Body/Surgery/ISurgeon.cs b/Content.Server/Body/Surgery/ISurgeon.cs new file mode 100644 index 0000000000..be2ed1135c --- /dev/null +++ b/Content.Server/Body/Surgery/ISurgeon.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using Content.Server.Body.Mechanisms; +using Content.Server.GameObjects.Components.Body; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Server.Body.Surgery +{ + /// + /// Interface representing an entity capable of performing surgery (performing operations on an + /// class). + /// For an example see , which inherits from this class. + /// + public interface ISurgeon + { + public delegate void MechanismRequestCallback( + Mechanism target, + IBodyPartContainer container, + ISurgeon surgeon, + IEntity performer); + + /// + /// How long it takes to perform a single surgery step (in seconds). + /// + public float BaseOperationTime { get; set; } + + /// + /// When performing a surgery, the may sometimes require selecting from a set of Mechanisms + /// to operate on. + /// This function is called in that scenario, and it is expected that you call the callback with one mechanism from the + /// provided list. + /// + public void RequestMechanism(IEnumerable options, MechanismRequestCallback callback); + } +} diff --git a/Content.Server/Body/Surgery/SurgeryData.cs b/Content.Server/Body/Surgery/SurgeryData.cs new file mode 100644 index 0000000000..a4274d0042 --- /dev/null +++ b/Content.Server/Body/Surgery/SurgeryData.cs @@ -0,0 +1,91 @@ +#nullable enable +using Content.Server.Body.Mechanisms; +using Content.Shared.GameObjects.Components.Body; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Server.Body.Surgery +{ + /// + /// This data class represents the state of a in regards to everything surgery related - + /// whether there's an incision on it, whether the bone is broken, etc. + /// + public abstract class SurgeryData + { + protected delegate void SurgeryAction(IBodyPartContainer container, ISurgeon surgeon, IEntity performer); + + /// + /// The this surgeryData is attached to. + /// The class should not exist without a + /// that it represents, and will throw errors if it + /// is null. + /// + protected readonly BodyPart Parent; + + protected SurgeryData(BodyPart parent) + { + Parent = parent; + } + + /// + /// The of the parent . + /// + protected BodyPartType ParentType => Parent.PartType; + + /// + /// Returns the description of this current to be shown + /// upon observing the given entity. + /// + public abstract string GetDescription(IEntity target); + + /// + /// Returns whether a can be installed into the + /// this represents. + /// + public abstract bool CanInstallMechanism(Mechanism mechanism); + + /// + /// Returns whether the given can be connected to the + /// this represents. + /// + public abstract bool CanAttachBodyPart(BodyPart part); + + /// + /// Gets the delegate corresponding to the surgery step using the given + /// . + /// + /// + /// The corresponding surgery action or null if no step can be performed. + /// + protected abstract SurgeryAction? GetSurgeryStep(SurgeryType toolType); + + /// + /// Returns whether the given can be used to perform a surgery on the BodyPart this + /// represents. + /// + public bool CheckSurgery(SurgeryType toolType) + { + return GetSurgeryStep(toolType) != null; + } + + /// + /// Attempts to perform surgery of the given . Returns whether the operation was successful. + /// + /// The used for this surgery. + /// The container where the surgery is being done. + /// The entity being used to perform the surgery. + /// The entity performing the surgery. + public bool PerformSurgery(SurgeryType surgeryType, IBodyPartContainer container, ISurgeon surgeon, + IEntity performer) + { + var step = GetSurgeryStep(surgeryType); + + if (step == null) + { + return false; + } + + step(container, surgeon, performer); + return true; + } + } +} diff --git a/Content.Server/Chat/ChatCommands.cs b/Content.Server/Chat/ChatCommands.cs index 9d3ff7c995..c5f194d429 100644 --- a/Content.Server/Chat/ChatCommands.cs +++ b/Content.Server/Chat/ChatCommands.cs @@ -1,10 +1,11 @@ -using System.Linq; -using Content.Server.GameObjects.Components.Damage; +using System; +using System.Linq; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Observer; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameObjects; +using Content.Server.Observer; using Content.Server.Players; using Content.Shared.GameObjects.Components.Damage; using Robust.Server.Interfaces.Console; @@ -13,6 +14,7 @@ using Robust.Shared.Enums; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; +using Content.Shared.Damage; namespace Content.Server.Chat { @@ -30,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); @@ -59,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); @@ -76,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); } } @@ -89,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); } } @@ -105,24 +125,24 @@ namespace Content.Server.Chat "If that fails, it will attempt to use an object in the environment.\n" + "Finally, if neither of the above worked, you will die by biting your tongue."; - private void DealDamage(ISuicideAct suicide, IChatManager chat, DamageableComponent damageableComponent, IEntity source, IEntity target) + private void DealDamage(ISuicideAct suicide, IChatManager chat, IDamageableComponent damageableComponent, IEntity source, IEntity target) { SuicideKind kind = suicide.Suicide(target, chat); if (kind != SuicideKind.Special) { - damageableComponent.TakeDamage(kind switch - { - SuicideKind.Brute => DamageType.Brute, - SuicideKind.Heat => DamageType.Heat, - SuicideKind.Cold => DamageType.Cold, - SuicideKind.Acid => DamageType.Acid, - SuicideKind.Toxic => DamageType.Toxic, - SuicideKind.Electric => DamageType.Electric, - _ => DamageType.Brute - }, - 500, //TODO: needs to be a max damage of some sorts - source, - target); + damageableComponent.ChangeDamage(kind switch + { + SuicideKind.Blunt => DamageType.Blunt, + SuicideKind.Piercing => DamageType.Piercing, + SuicideKind.Heat => DamageType.Heat, + SuicideKind.Disintegration => DamageType.Disintegration, + SuicideKind.Cellular => DamageType.Cellular, + SuicideKind.DNA => DamageType.DNA, + SuicideKind.Asphyxiation => DamageType.Asphyxiation, + _ => DamageType.Blunt + }, + 500, + true, source); } } @@ -133,7 +153,7 @@ namespace Content.Server.Chat var chat = IoCManager.Resolve(); var owner = player.ContentData().Mind.OwnedMob.Owner; - var dmgComponent = owner.GetComponent(); + var dmgComponent = owner.GetComponent(); //TODO: needs to check if the mob is actually alive //TODO: maybe set a suicided flag to prevent ressurection? @@ -167,7 +187,11 @@ namespace Content.Server.Chat } // Default suicide, bite your tongue chat.EntityMe(owner, Loc.GetString("is attempting to bite {0:their} own tongue, looks like {0:theyre} trying to commit suicide!", owner)); //TODO: theyre macro - dmgComponent.TakeDamage(DamageType.Brute, 500, owner, owner); //TODO: dmg value needs to be a max damage of some sorts + dmgComponent.ChangeDamage(DamageType.Piercing, 500, true, owner); + + // Prevent the player from returning to the body. Yes, this is an ugly hack. + var ghost = new Ghost(){CanReturn = false}; + ghost.Execute(shell, player, Array.Empty()); } } } diff --git a/Content.Server/Chat/ChatManager.cs b/Content.Server/Chat/ChatManager.cs index 1a035fbb5c..a6e0296b04 100644 --- a/Content.Server/Chat/ChatManager.cs +++ b/Content.Server/Chat/ChatManager.cs @@ -5,7 +5,9 @@ using Content.Server.Interfaces; using Content.Server.Interfaces.Chat; using Content.Shared.Chat; using Content.Shared.GameObjects.EntitySystems; +using NFluidsynth; using Robust.Server.Console; +using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.Player; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Network; @@ -19,8 +21,18 @@ namespace Content.Server.Chat /// internal sealed class ChatManager : IChatManager { + /// + /// The maximum length a player-sent message can be sent + /// + public int MaxMessageLength = 1000; + private const int VoiceRange = 7; // how far voice goes in world units + /// + /// The message displayed to the player when it exceeds the chat character limit + /// + private const string MaxLengthExceededMessage = "Your message exceeded {0} character limit"; + #pragma warning disable 649 [Dependency] private readonly IEntitySystemManager _entitySystemManager; [Dependency] private readonly IServerNetManager _netManager; @@ -33,6 +45,12 @@ namespace Content.Server.Chat public void Initialize() { _netManager.RegisterNetMessage(MsgChatMessage.NAME); + _netManager.RegisterNetMessage(ChatMaxMsgLengthMessage.NAME, _onMaxLengthRequest); + + // Tell all the connected players the chat's character limit + var msg = _netManager.CreateNetMessage(); + msg.MaxMessageLength = MaxMessageLength; + _netManager.ServerSendToAll(msg); } public void DispatchServerAnnouncement(string message) @@ -69,6 +87,17 @@ namespace Content.Server.Chat return; } + // Get entity's PlayerSession + IPlayerSession playerSession = source.GetComponent().playerSession; + + // Check if message exceeds the character limit if the sender is a player + if (playerSession != null) + if (message.Length > MaxMessageLength) + { + DispatchServerMessage(playerSession, Loc.GetString(MaxLengthExceededMessage, MaxMessageLength)); + return; + } + var pos = source.Transform.GridPosition; var clients = _playerManager.GetPlayersInRange(pos, VoiceRange).Select(p => p.ConnectedClient); @@ -90,6 +119,17 @@ namespace Content.Server.Chat return; } + // Check if entity is a player + IPlayerSession playerSession = source.GetComponent().playerSession; + + // Check if message exceeds the character limit + if (playerSession != null) + if (action.Length > MaxMessageLength) + { + DispatchServerMessage(playerSession, Loc.GetString(MaxLengthExceededMessage, MaxMessageLength)); + return; + } + var pos = source.Transform.GridPosition; var clients = _playerManager.GetPlayersInRange(pos, VoiceRange).Select(p => p.ConnectedClient); @@ -103,6 +143,13 @@ namespace Content.Server.Chat public void SendOOC(IPlayerSession player, string message) { + // Check if message exceeds the character limi + if (message.Length > MaxMessageLength) + { + DispatchServerMessage(player, Loc.GetString(MaxLengthExceededMessage, MaxMessageLength)); + return; + } + var msg = _netManager.CreateNetMessage(); msg.Channel = ChatChannel.OOC; msg.Message = message; @@ -114,6 +161,13 @@ namespace Content.Server.Chat public void SendDeadChat(IPlayerSession player, string message) { + // Check if message exceeds the character limit + if (message.Length > MaxMessageLength) + { + DispatchServerMessage(player, Loc.GetString(MaxLengthExceededMessage, MaxMessageLength)); + return; + } + var clients = _playerManager.GetPlayersBy(x => x.AttachedEntity != null && x.AttachedEntity.HasComponent()).Select(p => p.ConnectedClient);; var msg = _netManager.CreateNetMessage(); @@ -126,7 +180,14 @@ namespace Content.Server.Chat public void SendAdminChat(IPlayerSession player, string message) { - if(!_conGroupController.CanCommand(player, "asay")) + // Check if message exceeds the character limit + if (message.Length > MaxMessageLength) + { + DispatchServerMessage(player, Loc.GetString(MaxLengthExceededMessage, MaxMessageLength)); + return; + } + + if (!_conGroupController.CanCommand(player, "asay")) { SendOOC(player, message); return; @@ -149,5 +210,12 @@ namespace Content.Server.Chat msg.MessageWrap = $"OOC: (D){sender}: {{0}}"; _netManager.ServerSendToAll(msg); } + + private void _onMaxLengthRequest(ChatMaxMsgLengthMessage msg) + { + var response = _netManager.CreateNetMessage(); + response.MaxMessageLength = MaxMessageLength; + _netManager.ServerSendMessage(response, msg.MsgChannel); + } } } diff --git a/Content.Server/Content.Server.csproj b/Content.Server/Content.Server.csproj index 0c9be50c54..9eae91acb0 100644 --- a/Content.Server/Content.Server.csproj +++ b/Content.Server/Content.Server.csproj @@ -9,6 +9,7 @@ ..\bin\Content.Server\ true Exe + 1998 diff --git a/Content.Server/EntryPoint.cs b/Content.Server/EntryPoint.cs index e83771204d..120b364352 100644 --- a/Content.Server/EntryPoint.cs +++ b/Content.Server/EntryPoint.cs @@ -1,8 +1,9 @@ -using Content.Server.AI.Utility.Considerations; +using Content.Server.AI.Utility.Considerations; using Content.Server.AI.WorldState; using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Content.Server.Interfaces; using Content.Server.Interfaces.Chat; +using Content.Server.Body.Network; using Content.Server.Interfaces.GameTicking; using Content.Server.Interfaces.PDA; using Content.Server.Sandbox; @@ -46,6 +47,8 @@ namespace Content.Server IoCManager.BuildGraph(); + IoCManager.Resolve().DoAutoRegistrations(); + _gameTicker = IoCManager.Resolve(); IoCManager.Resolve().Initialize(); diff --git a/Content.Server/Explosions/ExplosionHelper.cs b/Content.Server/Explosions/ExplosionHelper.cs index 277713bf28..1b61f037f3 100644 --- a/Content.Server/Explosions/ExplosionHelper.cs +++ b/Content.Server/Explosions/ExplosionHelper.cs @@ -1,7 +1,7 @@ using System; using System.Linq; using Content.Server.GameObjects.Components.Mobs; -using Content.Server.GameObjects.EntitySystems; +using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Maps; using Robust.Server.GameObjects.EntitySystems; using Robust.Server.Interfaces.GameObjects; 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 6ad088bdbe..224ce59886 100644 --- a/Content.Server/GameObjects/Components/AnchorableComponent.cs +++ b/Content.Server/GameObjects/Components/AnchorableComponent.cs @@ -1,5 +1,6 @@ #nullable enable using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.Interactable; using Content.Shared.GameObjects.Components.Interactable; using Content.Shared.Interfaces.GameObjects.Components; @@ -14,17 +15,18 @@ namespace Content.Server.GameObjects.Components { public override string Name => "Anchorable"; + int IInteractUsing.Priority => 1; + /// /// Checks if a tool can change the anchored status. /// /// The user doing the action /// The tool being used, can be null if forcing it - /// The physics component of the owning entity /// Whether or not to check if the tool is valid /// true if it is valid, false otherwise - private bool Valid(IEntity user, IEntity? utilizing, [MaybeNullWhen(false)] out ICollidableComponent collidable, bool force = false) + private async Task Valid(IEntity user, IEntity? utilizing, [MaybeNullWhen(false)] bool force = false) { - if (!Owner.TryGetComponent(out collidable)) + if (!Owner.HasComponent()) { return false; } @@ -32,8 +34,8 @@ namespace Content.Server.GameObjects.Components if (!force) { if (utilizing == null || - !utilizing.TryGetComponent(out ToolComponent tool) || - !tool.UseTool(user, Owner, ToolQuality.Anchoring)) + !utilizing.TryGetComponent(out ToolComponent? tool) || + !(await tool.UseTool(user, Owner, 0.5f, ToolQuality.Anchoring))) { return false; } @@ -49,13 +51,14 @@ namespace Content.Server.GameObjects.Components /// The tool being used, if any /// Whether or not to ignore valid tool checks /// true if anchored, false otherwise - public bool TryAnchor(IEntity user, IEntity? utilizing = null, bool force = false) + public async Task TryAnchor(IEntity user, IEntity? utilizing = null, bool force = false) { - if (!Valid(user, utilizing, out var physics, force)) + if (!(await Valid(user, utilizing, force))) { return false; } + var physics = Owner.GetComponent(); physics.Anchored = true; return true; @@ -68,13 +71,14 @@ namespace Content.Server.GameObjects.Components /// The tool being used, if any /// Whether or not to ignore valid tool checks /// true if unanchored, false otherwise - public bool TryUnAnchor(IEntity user, IEntity? utilizing = null, bool force = false) + public async Task TryUnAnchor(IEntity user, IEntity? utilizing = null, bool force = false) { - if (!Valid(user, utilizing, out var physics, force)) + if (!(await Valid(user, utilizing, force))) { return false; } + var physics = Owner.GetComponent(); physics.Anchored = false; return true; @@ -87,16 +91,16 @@ namespace Content.Server.GameObjects.Components /// The tool being used, if any /// Whether or not to ignore valid tool checks /// true if toggled, false otherwise - private bool TryToggleAnchor(IEntity user, IEntity? utilizing = null, bool force = false) + 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; } return collidable.Anchored ? - TryUnAnchor(user, utilizing, force) : - TryAnchor(user, utilizing, force); + await TryUnAnchor(user, utilizing, force) : + await TryAnchor(user, utilizing, force); } public override void Initialize() @@ -105,9 +109,9 @@ namespace Content.Server.GameObjects.Components Owner.EnsureComponent(); } - bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) + async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { - return TryToggleAnchor(eventArgs.User, eventArgs.Using); + return await TryToggleAnchor(eventArgs.User, eventArgs.Using); } } } diff --git a/Content.Server/GameObjects/Components/Atmos/BarotraumaComponent.cs b/Content.Server/GameObjects/Components/Atmos/BarotraumaComponent.cs index 4beaa374b1..cabaf72d5a 100644 --- a/Content.Server/GameObjects/Components/Atmos/BarotraumaComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/BarotraumaComponent.cs @@ -1,11 +1,11 @@ using System; using System.Runtime.CompilerServices; -using Content.Server.GameObjects.Components.Damage; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces.GameObjects; using Content.Shared.Atmos; using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Mobs; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; @@ -23,7 +23,7 @@ namespace Content.Server.GameObjects.Components.Atmos [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(float frameTime) { - if (!Owner.TryGetComponent(out DamageableComponent damageable)) return; + if (!Owner.TryGetComponent(out IDamageableComponent damageable)) return; Owner.TryGetComponent(out ServerStatusEffectsComponent status); var coordinates = Owner.Transform.GridPosition; @@ -52,7 +52,7 @@ namespace Content.Server.GameObjects.Components.Atmos if(pressure > Atmospherics.WarningLowPressure) goto default; - damageable.TakeDamage(DamageType.Brute, Atmospherics.LowPressureDamage, Owner); + damageable.ChangeDamage(DamageType.Blunt, Atmospherics.LowPressureDamage, false, Owner); if (status == null) break; @@ -74,7 +74,7 @@ namespace Content.Server.GameObjects.Components.Atmos var damage = (int) MathF.Min((pressure / Atmospherics.HazardHighPressure) * Atmospherics.PressureDamageCoefficient, Atmospherics.MaxHighPressureDamage); - damageable.TakeDamage(DamageType.Brute, damage, Owner); + damageable.ChangeDamage(DamageType.Blunt, damage, false, Owner); if (status == null) break; 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/Atmos/GasMixtureComponent.cs b/Content.Server/GameObjects/Components/Atmos/GasMixtureComponent.cs index 68e59e0869..c60ad08efa 100644 --- a/Content.Server/GameObjects/Components/Atmos/GasMixtureComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/GasMixtureComponent.cs @@ -1,6 +1,7 @@ using Content.Server.Atmos; using Robust.Shared.GameObjects; using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Atmos { @@ -8,7 +9,8 @@ namespace Content.Server.GameObjects.Components.Atmos public class GasMixtureComponent : Component { public override string Name => "GasMixture"; - public GasMixture GasMixture { get; set; } = new GasMixture(); + + [ViewVariables] public GasMixture GasMixture { get; set; } = new GasMixture(); public override void ExposeData(ObjectSerializer serializer) { diff --git a/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs b/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs new file mode 100644 index 0000000000..5c14e4da0c --- /dev/null +++ b/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs @@ -0,0 +1,1015 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Server.Body; +using Content.Server.Body.Network; +using Content.Server.GameObjects.Components.Metabolism; +using Content.Server.GameObjects.EntitySystems; +using Content.Server.Interfaces.GameObjects.Components.Interaction; +using Content.Server.Mobs; +using Content.Server.Observer; +using Content.Shared.Body.Part; +using Content.Shared.Body.Part.Properties.Movement; +using Content.Shared.Body.Part.Properties.Other; +using Content.Shared.Body.Preset; +using Content.Shared.Body.Template; +using Content.Shared.GameObjects.Components.Body; +using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.Components.Movement; +using Robust.Server.GameObjects; +using Robust.Server.Interfaces.Player; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Reflection; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Maths; +using Robust.Shared.Players; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Body +{ + /// + /// Component representing a collection of + /// attached to each other. + /// + [RegisterComponent] + [ComponentReference(typeof(IDamageableComponent))] + [ComponentReference(typeof(IBodyManagerComponent))] + public class BodyManagerComponent : SharedBodyManagerComponent, IBodyPartContainer, IRelayMoveInput + { +#pragma warning disable CS0649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IBodyNetworkFactory _bodyNetworkFactory = default!; + [Dependency] private readonly IReflectionManager _reflectionManager = default!; +#pragma warning restore + + [ViewVariables] private string _presetName = default!; + + private readonly Dictionary _parts = new Dictionary(); + + [ViewVariables] private readonly Dictionary _networks = new Dictionary(); + + /// + /// All with + /// that are currently affecting move speed, mapped to how big that leg + /// they're on is. + /// + [ViewVariables] + private readonly Dictionary _activeLegs = new Dictionary(); + + /// + /// The that this + /// is adhering to. + /// + [ViewVariables] + public BodyTemplate Template { get; private set; } = default!; + + /// + /// The that this + /// is adhering to. + /// + [ViewVariables] + public BodyPreset Preset { get; private set; } = default!; + + /// + /// Maps slot name to the + /// object filling it (if there is one). + /// + [ViewVariables] + public IReadOnlyDictionary Parts => _parts; + + /// + /// List of all slots in this body, taken from the keys of + /// slots. + /// + public IEnumerable AllSlots => Template.Slots.Keys; + + /// + /// List of all occupied slots in this body, taken from the values of + /// . + /// + public IEnumerable OccupiedSlots => Parts.Keys; + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataReadWriteFunction( + "baseTemplate", + "bodyTemplate.Humanoid", + template => + { + if (!_prototypeManager.TryIndex(template, out BodyTemplatePrototype templateData)) + { + // Invalid prototype + throw new InvalidOperationException( + $"No {nameof(BodyTemplatePrototype)} found with name {template}"); + } + + Template = new BodyTemplate(templateData); + }, + () => Template.Name); + + serializer.DataReadWriteFunction( + "basePreset", + "bodyPreset.BasicHuman", + preset => + { + if (!_prototypeManager.TryIndex(preset, out BodyPresetPrototype presetData)) + { + // Invalid prototype + throw new InvalidOperationException( + $"No {nameof(BodyPresetPrototype)} found with name {preset}"); + } + + Preset = new BodyPreset(presetData); + }, + () => _presetName); + } + + public override void Initialize() + { + base.Initialize(); + + LoadBodyPreset(Preset); + + foreach (var behavior in Owner.GetAllComponents()) + { + HealthChangedEvent += behavior.OnHealthChanged; + } + } + + protected override void Startup() + { + base.Startup(); + + // Just in case something activates at default health. + ForceHealthChangedEvent(); + } + + private void LoadBodyPreset(BodyPreset preset) + { + _presetName = preset.Name; + + foreach (var slotName in Template.Slots.Keys) + { + // For each slot in our BodyManagerComponent's template, + // try and grab what the ID of what the preset says should be inside it. + if (!preset.PartIDs.TryGetValue(slotName, out var partId)) + { + // If the preset doesn't define anything for it, continue. + continue; + } + + // Get the BodyPartPrototype corresponding to the BodyPart ID we grabbed. + if (!_prototypeManager.TryIndex(partId, out BodyPartPrototype newPartData)) + { + throw new InvalidOperationException($"No {nameof(BodyPartPrototype)} prototype found with ID {partId}"); + } + + // Try and remove an existing limb if that exists. + RemoveBodyPart(slotName, false); + + // Add a new BodyPart with the BodyPartPrototype as a baseline to our + // BodyComponent. + var addedPart = new BodyPart(newPartData); + AddBodyPart(addedPart, slotName); + } + + OnBodyChanged(); // TODO: Duplicate code + } + + /// + /// Changes the current to the given + /// . + /// Attempts to keep previous if there is a slot for + /// them in both . + /// + public void ChangeBodyTemplate(BodyTemplatePrototype newTemplate) + { + foreach (var part in Parts) + { + // TODO: Make this work. + } + + OnBodyChanged(); + } + + /// + /// This method is called by before + /// is called. + /// + public void PreMetabolism(float frameTime) + { + if (CurrentDamageState == DamageState.Dead) + { + return; + } + + foreach (var part in Parts.Values) + { + part.PreMetabolism(frameTime); + } + + foreach (var network in _networks.Values) + { + network.Update(frameTime); + } + } + + /// + /// This method is called by after + /// is called. + /// + public void PostMetabolism(float frameTime) + { + if (CurrentDamageState == DamageState.Dead) + { + return; + } + + foreach (var part in Parts.Values) + { + part.PostMetabolism(frameTime); + } + + foreach (var network in _networks.Values) + { + network.Update(frameTime); + } + } + + /// + /// Called when the layout of this body changes. + /// + private void OnBodyChanged() + { + // Calculate move speed based on this body. + if (Owner.HasComponent()) + { + _activeLegs.Clear(); + var legParts = Parts.Values.Where(x => x.HasProperty(typeof(LegProperty))); + + foreach (var part in legParts) + { + var footDistance = DistanceToNearestFoot(this, part); + + if (Math.Abs(footDistance - float.MinValue) > 0.001f) + { + _activeLegs.Add(part, footDistance); + } + } + + CalculateSpeed(); + } + } + + private void CalculateSpeed() + { + if (!Owner.TryGetComponent(out MovementSpeedModifierComponent? playerMover)) + { + return; + } + + float speedSum = 0; + foreach (var part in _activeLegs.Keys) + { + if (!part.HasProperty()) + { + _activeLegs.Remove(part); + } + } + + foreach (var (key, value) in _activeLegs) + { + if (key.TryGetProperty(out LegProperty legProperty)) + { + // Speed of a leg = base speed * (1+log1024(leg length)) + speedSum += legProperty.Speed * (1 + (float) Math.Log(value, 1024.0)); + } + } + + if (speedSum <= 0.001f || _activeLegs.Count <= 0) + { + // Case: no way of moving. Fall down. + StandingStateHelper.Down(Owner); + playerMover.BaseWalkSpeed = 0.8f; + playerMover.BaseSprintSpeed = 2.0f; + } + else + { + // Case: have at least one leg. Set move speed. + StandingStateHelper.Standing(Owner); + + // Extra legs stack diminishingly. + // Final speed = speed sum/(leg count-log4(leg count)) + playerMover.BaseWalkSpeed = + speedSum / (_activeLegs.Count - (float) Math.Log(_activeLegs.Count, 4.0)); + + playerMover.BaseSprintSpeed = playerMover.BaseWalkSpeed * 1.75f; + } + } + + void IRelayMoveInput.MoveInputPressed(ICommonSession session) + { + if (CurrentDamageState == DamageState.Dead) + { + new Ghost().Execute(null, (IPlayerSession) session, null); + } + } + + #region BodyPart Functions + + /// + /// Recursively searches for if is connected to + /// the center. Not efficient (O(n^2)), but most bodies don't have a ton + /// of s. + /// + /// The body part to find the center for. + /// True if it is connected to the center, false otherwise. + private bool ConnectedToCenterPart(BodyPart target) + { + var searchedSlots = new List(); + + return TryGetSlotName(target, out var result) && + ConnectedToCenterPartRecursion(searchedSlots, result); + } + + private bool ConnectedToCenterPartRecursion(ICollection searchedSlots, string slotName) + { + TryGetBodyPart(slotName, out var part); + + if (part == null) + { + return false; + } + + if (part == GetCenterBodyPart()) + { + return true; + } + + searchedSlots.Add(slotName); + + if (!TryGetBodyPartConnections(slotName, out List connections)) + { + return false; + } + + foreach (var connection in connections) + { + if (!searchedSlots.Contains(connection) && + ConnectedToCenterPartRecursion(searchedSlots, connection)) + { + return true; + } + } + + return false; + } + + /// + /// Finds the central , if any, of this body based on + /// the . For humans, this is the torso. + /// + /// The if one exists, null otherwise. + private BodyPart? GetCenterBodyPart() + { + Parts.TryGetValue(Template.CenterSlot, out var center); + return center!; + } + + /// + /// Returns whether the given slot name exists within the current + /// . + /// + private bool SlotExists(string slotName) + { + return Template.SlotExists(slotName); + } + + /// + /// Finds the in the given if + /// one exists. + /// + /// The slot to search in. + /// The body part in that slot, if any. + /// True if found, false otherwise. + private bool TryGetBodyPart(string slotName, [NotNullWhen(true)] out BodyPart result) + { + return Parts.TryGetValue(slotName, out result!); + } + + /// + /// Finds the slotName that the given resides in. + /// + /// The to find the slot for. + /// The slot found, if any. + /// True if a slot was found, false otherwise + private bool TryGetSlotName(BodyPart part, [NotNullWhen(true)] out string result) + { + // We enforce that there is only one of each value in the dictionary, + // so we can iterate through the dictionary values to get the key from there. + result = Parts.FirstOrDefault(x => x.Value == part).Key; + return result != null; + } + + /// + /// Finds the in the given + /// if one exists. + /// + /// The slot to search in. + /// + /// The of that slot, if any. + /// + /// True if found, false otherwise. + public bool TryGetSlotType(string slotName, out BodyPartType result) + { + return Template.Slots.TryGetValue(slotName, out result); + } + + /// + /// Finds the names of all slots connected to the given + /// for the template. + /// + /// The slot to search in. + /// The connections found, if any. + /// True if the connections are found, false otherwise. + private bool TryGetBodyPartConnections(string slotName, [NotNullWhen(true)] out List connections) + { + return Template.Connections.TryGetValue(slotName, out connections!); + } + + /// + /// Grabs all occupied slots connected to the given slot, + /// regardless of whether the given is occupied. + /// + /// The slot name to find connections from. + /// The connected body parts, if any. + /// + /// True if successful, false if there was an error or no connected + /// s were found. + /// + public bool TryGetBodyPartConnections(string slotName, [NotNullWhen(true)] out List result) + { + result = null!; + + if (!Template.Connections.TryGetValue(slotName, out var connections)) + { + return false; + } + + var toReturn = new List(); + foreach (var connection in connections) + { + if (TryGetBodyPart(connection, out var bodyPartResult)) + { + toReturn.Add(bodyPartResult); + } + } + + if (toReturn.Count <= 0) + { + return false; + } + + result = toReturn; + return true; + } + + /// + /// Grabs all parts connected to the given , regardless + /// of whether the given is occupied. + /// + /// + /// True if successful, false if there was an error or no connected + /// s were found. + /// + private bool TryGetBodyPartConnections(BodyPart part, [NotNullWhen(true)] out List result) + { + result = null!; + + return TryGetSlotName(part, out var slotName) && + TryGetBodyPartConnections(slotName, out result); + } + + /// + /// Grabs all of the given type in this body. + /// + public List GetBodyPartsOfType(BodyPartType type) + { + var toReturn = new List(); + + foreach (var part in Parts.Values) + { + if (part.PartType == type) + { + toReturn.Add(part); + } + } + + return toReturn; + } + + /// + /// Installs the given into the given slot. + /// + /// True if successful, false otherwise. + public bool InstallBodyPart(BodyPart part, string slotName) + { + DebugTools.AssertNotNull(part); + + // Make sure the given slot exists + if (!SlotExists(slotName)) + { + return false; + } + + // And that nothing is in it + if (TryGetBodyPart(slotName, out _)) + { + return false; + } + + AddBodyPart(part, slotName); // TODO: Sort this duplicate out + OnBodyChanged(); + + return true; + } + + /// + /// Installs the given into the + /// given slot, deleting the afterwards. + /// + /// True if successful, false otherwise. + public bool InstallDroppedBodyPart(DroppedBodyPartComponent part, string slotName) + { + DebugTools.AssertNotNull(part); + + if (!InstallBodyPart(part.ContainedBodyPart, slotName)) + { + return false; + } + + part.Owner.Delete(); + return true; + } + + /// + /// Disconnects the given reference, potentially + /// dropping other BodyParts if they were hanging + /// off of it. + /// + /// + /// The representing the dropped + /// , or null if none was dropped. + /// + public IEntity? DropPart(BodyPart part) + { + DebugTools.AssertNotNull(part); + + if (!_parts.ContainsValue(part)) + { + return null; + } + + if (!RemoveBodyPart(part, out var slotName)) + { + return null; + } + + // Call disconnect on all limbs that were hanging off this limb. + if (TryGetBodyPartConnections(slotName, out List connections)) + { + // This loop is an unoptimized travesty. TODO: optimize to be less shit + foreach (var connectionName in connections) + { + if (TryGetBodyPart(connectionName, out var result) && !ConnectedToCenterPart(result)) + { + DisconnectBodyPart(connectionName, true); + } + } + } + + part.SpawnDropped(out var dropped); + + OnBodyChanged(); + return dropped; + } + + /// + /// Disconnects the given reference, potentially + /// dropping other BodyParts if they were hanging + /// off of it. + /// + public void DisconnectBodyPart(BodyPart part, bool dropEntity) + { + DebugTools.AssertNotNull(part); + + if (!_parts.ContainsValue(part)) + { + return; + } + + var slotName = Parts.FirstOrDefault(x => x.Value == part).Key; + RemoveBodyPart(slotName, dropEntity); + + // Call disconnect on all limbs that were hanging off this limb + if (TryGetBodyPartConnections(slotName, out List connections)) + { + // TODO: Optimize + foreach (var connectionName in connections) + { + if (TryGetBodyPart(connectionName, out var result) && !ConnectedToCenterPart(result)) + { + DisconnectBodyPart(connectionName, dropEntity); + } + } + } + + OnBodyChanged(); + } + + /// + /// Disconnects a body part in the given slot if one exists, + /// optionally dropping it. + /// + /// The slot to remove the body part from + /// + /// Whether or not to drop the body part as an entity if it exists. + /// + private void DisconnectBodyPart(string slotName, bool dropEntity) + { + DebugTools.AssertNotNull(slotName); + + if (!TryGetBodyPart(slotName, out var part)) + { + return; + } + + if (part == null) + { + return; + } + + RemoveBodyPart(slotName, dropEntity); + + if (TryGetBodyPartConnections(slotName, out List connections)) + { + foreach (var connectionName in connections) + { + if (TryGetBodyPart(connectionName, out var result) && !ConnectedToCenterPart(result)) + { + DisconnectBodyPart(connectionName, dropEntity); + } + } + } + + OnBodyChanged(); + } + + private void AddBodyPart(BodyPart part, string slotName) + { + DebugTools.AssertNotNull(part); + DebugTools.AssertNotNull(slotName); + + _parts.Add(slotName, part); + + part.Body = this; + + var argsAdded = new BodyPartAddedEventArgs(part, slotName); + + foreach (var component in Owner.GetAllComponents().ToArray()) + { + component.BodyPartAdded(argsAdded); + } + + if (!Template.Layers.TryGetValue(slotName, out var partMap) || + !_reflectionManager.TryParseEnumReference(partMap, out var partEnum)) + { + Logger.Warning($"Template {Template.Name} has an invalid RSI map key {partMap} for body part {part.Name}."); + return; + } + + part.RSIMap = partEnum; + + var partMessage = new BodyPartAddedMessage(part.RSIPath, part.RSIState, partEnum); + + SendNetworkMessage(partMessage); + + foreach (var mechanism in part.Mechanisms) + { + if (!Template.MechanismLayers.TryGetValue(mechanism.Id, out var mechanismMap)) + { + continue; + } + + if (!_reflectionManager.TryParseEnumReference(mechanismMap, out var mechanismEnum)) + { + Logger.Warning($"Template {Template.Name} has an invalid RSI map key {mechanismMap} for mechanism {mechanism.Id}."); + continue; + } + + var mechanismMessage = new MechanismSpriteAddedMessage(mechanismEnum); + + SendNetworkMessage(mechanismMessage); + } + } + + /// + /// Removes the body part in slot from this body, + /// if one exists. + /// + /// The slot to remove it from. + /// + /// Whether or not to drop the removed . + /// + /// + private bool RemoveBodyPart(string slotName, bool drop) + { + DebugTools.AssertNotNull(slotName); + + if (!_parts.Remove(slotName, out var part)) + { + return false; + } + + IEntity? dropped = null; + if (drop) + { + part.SpawnDropped(out dropped); + } + + part.Body = null; + + var args = new BodyPartRemovedEventArgs(part, slotName); + + foreach (var component in Owner.GetAllComponents()) + { + component.BodyPartRemoved(args); + } + + if (part.RSIMap != null) + { + var message = new BodyPartRemovedMessage(part.RSIMap, dropped?.Uid); + SendNetworkMessage(message); + } + + foreach (var mechanism in part.Mechanisms) + { + if (!Template.MechanismLayers.TryGetValue(mechanism.Id, out var mechanismMap)) + { + continue; + } + + if (!_reflectionManager.TryParseEnumReference(mechanismMap, out var mechanismEnum)) + { + Logger.Warning($"Template {Template.Name} has an invalid RSI map key {mechanismMap} for mechanism {mechanism.Id}."); + continue; + } + + var mechanismMessage = new MechanismSpriteRemovedMessage(mechanismEnum); + + SendNetworkMessage(mechanismMessage); + } + + return true; + } + + /// + /// Removes the body part from this body, if one exists. + /// + /// The part to remove from this body. + /// The slot that the part was in, if any. + /// True if was removed, false otherwise. + private bool RemoveBodyPart(BodyPart part, [NotNullWhen(true)] out string slotName) + { + DebugTools.AssertNotNull(part); + + slotName = _parts.FirstOrDefault(pair => pair.Value == part).Key; + + if (slotName == null) + { + return false; + } + + return RemoveBodyPart(slotName, false); + } + + #endregion + + #region BodyNetwork Functions + + private bool EnsureNetwork(BodyNetwork network) + { + DebugTools.AssertNotNull(network); + + if (_networks.ContainsKey(network.GetType())) + { + return false; + } + + _networks.Add(network.GetType(), network); + network.OnAdd(Owner); + + return true; + } + + /// + /// Attempts to add a of the given type to this body. + /// + /// + /// True if successful, false if there was an error + /// (such as passing in an invalid type or a network of that type already + /// existing). + /// + public bool EnsureNetwork(Type networkType) + { + DebugTools.Assert(networkType.IsSubclassOf(typeof(BodyNetwork))); + + var network = _bodyNetworkFactory.GetNetwork(networkType); + return EnsureNetwork(network); + } + + /// + /// Attempts to add a of the given type to + /// this body. + /// + /// The type of network to add. + /// + /// True if successful, false if there was an error + /// (such as passing in an invalid type or a network of that type already + /// existing). + /// + public bool EnsureNetwork() where T : BodyNetwork + { + return EnsureNetwork(typeof(T)); + } + + /// + /// Attempts to add a of the given name to + /// this body. + /// + /// + /// True if successful, false if there was an error + /// (such as passing in an invalid type or a network of that type already + /// existing). + /// + private bool EnsureNetwork(string networkName) + { + DebugTools.AssertNotNull(networkName); + + var network = _bodyNetworkFactory.GetNetwork(networkName); + return EnsureNetwork(network); + } + + /// + /// Removes the of the given type in this body, + /// if there is one. + /// + /// The type of the network to remove. + public void RemoveNetwork(Type type) + { + DebugTools.AssertNotNull(type); + + if (_networks.Remove(type, out var network)) + { + network.OnRemove(); + } + } + + /// + /// Removes the of the given type in this body, + /// if one exists. + /// + /// The type of the network to remove. + public void RemoveNetwork() where T : BodyNetwork + { + RemoveNetwork(typeof(T)); + } + + /// + /// Removes the with the given name in this body, + /// if there is one. + /// + private void RemoveNetwork(string networkName) + { + var type = _bodyNetworkFactory.GetNetwork(networkName).GetType(); + + if (_networks.Remove(type, out var network)) + { + network.OnRemove(); + } + } + + /// + /// Attempts to get the of the given type in this body. + /// + /// The type to search for. + /// + /// The if found, null otherwise. + /// + /// True if found, false otherwise. + public bool TryGetNetwork(Type networkType, [NotNullWhen(true)] out BodyNetwork result) + { + return _networks.TryGetValue(networkType, out result!); + } + + #endregion + + #region Recursion Functions + + /// + /// Returns the combined length of the distance to the nearest with a + /// . Returns + /// if there is no foot found. If you consider a a node map, then it will look for + /// a foot node from the given node. It can + /// only search through BodyParts with . + /// + private static float DistanceToNearestFoot(BodyManagerComponent body, BodyPart source) + { + if (source.HasProperty() && source.TryGetProperty(out var property)) + { + return property.ReachDistance; + } + + return LookForFootRecursion(body, source, new List()); + } + + private static float LookForFootRecursion(BodyManagerComponent body, BodyPart current, + ICollection searchedParts) + { + if (!current.TryGetProperty(out var extProperty)) + { + return float.MinValue; + } + + // Get all connected parts if the current part has an extension property + if (!body.TryGetBodyPartConnections(current, out var connections)) + { + return float.MinValue; + } + + // If a connected BodyPart is a foot, return this BodyPart's length. + foreach (var connection in connections) + { + if (!searchedParts.Contains(connection) && connection.HasProperty()) + { + return extProperty.ReachDistance; + } + } + + // Otherwise, get the recursion values of all connected BodyParts and + // store them in a list. + var distances = new List(); + foreach (var connection in connections) + { + if (!searchedParts.Contains(connection)) + { + continue; + } + + var result = LookForFootRecursion(body, connection, searchedParts); + + if (Math.Abs(result - float.MinValue) > 0.001f) + { + distances.Add(result); + } + } + + // If one or more of the searches found a foot, return the smallest one + // and add this ones length. + if (distances.Count > 0) + { + return distances.Min() + extProperty.ReachDistance; + } + + return float.MinValue; + + // No extension property, no go. + } + + #endregion + } + + public interface IBodyManagerHealthChangeParams + { + BodyPartType Part { get; } + } + + public class BodyManagerHealthChangeParams : HealthChangeParams, IBodyManagerHealthChangeParams + { + public BodyManagerHealthChangeParams(BodyPartType part) + { + Part = part; + } + + public BodyPartType Part { get; } + } +} diff --git a/Content.Server/GameObjects/Components/Body/BodyScannerComponent.cs b/Content.Server/GameObjects/Components/Body/BodyScannerComponent.cs new file mode 100644 index 0000000000..2c49665757 --- /dev/null +++ b/Content.Server/GameObjects/Components/Body/BodyScannerComponent.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using Content.Server.Body; +using Content.Shared.Body.Scanner; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Server.GameObjects.Components.UserInterface; +using Robust.Server.Interfaces.GameObjects; +using Robust.Shared.GameObjects; + +namespace Content.Server.GameObjects.Components.Body +{ + [RegisterComponent] + [ComponentReference(typeof(IActivate))] + public class BodyScannerComponent : Component, IActivate + { + private BoundUserInterface _userInterface; + public sealed override string Name => "BodyScanner"; + + void IActivate.Activate(ActivateEventArgs eventArgs) + { + if (!eventArgs.User.TryGetComponent(out IActorComponent actor) || + actor.playerSession.AttachedEntity == null) + { + return; + } + + if (actor.playerSession.AttachedEntity.TryGetComponent(out BodyManagerComponent attempt)) + { + var state = InterfaceState(attempt.Template, attempt.Parts); + _userInterface.SetState(state); + } + + _userInterface.Open(actor.playerSession); + } + + public override void Initialize() + { + base.Initialize(); + _userInterface = Owner.GetComponent() + .GetBoundUserInterface(BodyScannerUiKey.Key); + _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + } + + private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg) { } + + /// + /// Copy BodyTemplate and BodyPart data into a common data class that the client can read. + /// + private BodyScannerInterfaceState InterfaceState(BodyTemplate template, IReadOnlyDictionary bodyParts) + { + var partsData = new Dictionary(); + + foreach (var (slotName, part) in bodyParts) + { + var mechanismData = new List(); + + foreach (var mechanism in part.Mechanisms) + { + mechanismData.Add(new BodyScannerMechanismData(mechanism.Name, mechanism.Description, + mechanism.RSIPath, + mechanism.RSIState, mechanism.MaxDurability, mechanism.CurrentDurability)); + } + + partsData.Add(slotName, + new BodyScannerBodyPartData(part.Name, part.RSIPath, part.RSIState, part.MaxDurability, + part.CurrentDurability, mechanismData)); + } + + var templateData = new BodyScannerTemplateData(template.Name, template.Slots); + + return new BodyScannerInterfaceState(partsData, templateData); + } + } +} diff --git a/Content.Server/GameObjects/Components/Body/Circulatory/BloodstreamComponent.cs b/Content.Server/GameObjects/Components/Body/Circulatory/BloodstreamComponent.cs new file mode 100644 index 0000000000..9bf5f7e906 --- /dev/null +++ b/Content.Server/GameObjects/Components/Body/Circulatory/BloodstreamComponent.cs @@ -0,0 +1,83 @@ +using Content.Server.Atmos; +using Content.Server.GameObjects.Components.Chemistry; +using Content.Server.GameObjects.Components.Metabolism; +using Content.Server.Interfaces; +using Content.Shared.Chemistry; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Body.Circulatory +{ + [RegisterComponent] + public class BloodstreamComponent : Component, IGasMixtureHolder + { + public override string Name => "Bloodstream"; + + /// + /// Max volume of internal solution storage + /// + [ViewVariables] private ReagentUnit _initialMaxVolume; + + /// + /// Internal solution for reagent storage + /// + [ViewVariables] private SolutionComponent _internalSolution; + + /// + /// Empty volume of internal solution + /// + [ViewVariables] public ReagentUnit EmptyVolume => _internalSolution.EmptyVolume; + + [ViewVariables] public GasMixture Air { get; set; } = new GasMixture(6); + + [ViewVariables] public SolutionComponent Solution => _internalSolution; + + public override void Initialize() + { + base.Initialize(); + + _internalSolution = Owner.EnsureComponent(); + _internalSolution.MaxVolume = _initialMaxVolume; + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(ref _initialMaxVolume, "maxVolume", ReagentUnit.New(250)); + } + + /// + /// Attempt to transfer provided solution to internal solution. + /// Only supports complete transfers + /// + /// Solution to be transferred + /// Whether or not transfer was a success + public bool TryTransferSolution(Solution solution) + { + // For now doesn't support partial transfers + if (solution.TotalVolume + _internalSolution.CurrentVolume > _internalSolution.MaxVolume) + { + return false; + } + + _internalSolution.TryAddSolution(solution, false, true); + return true; + } + + public void PumpToxins(GasMixture into, float pressure) + { + if (!Owner.TryGetComponent(out MetabolismComponent metabolism)) + { + Air.PumpGasTo(into, pressure); + return; + } + + var toxins = metabolism.Clean(this); + + toxins.PumpGasTo(into, pressure); + Air.Merge(toxins); + } + } +} diff --git a/Content.Server/GameObjects/Components/Nutrition/StomachComponent.cs b/Content.Server/GameObjects/Components/Body/Digestive/StomachComponent.cs similarity index 65% rename from Content.Server/GameObjects/Components/Nutrition/StomachComponent.cs rename to Content.Server/GameObjects/Components/Body/Digestive/StomachComponent.cs index db6e16831e..fa6b76caea 100644 --- a/Content.Server/GameObjects/Components/Nutrition/StomachComponent.cs +++ b/Content.Server/GameObjects/Components/Body/Digestive/StomachComponent.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; +using Content.Server.GameObjects.Components.Body.Circulatory; using Content.Server.GameObjects.Components.Chemistry; -using Content.Server.GameObjects.Components.Metabolism; using Content.Shared.Chemistry; using Content.Shared.GameObjects.Components.Nutrition; using Robust.Shared.GameObjects; @@ -11,7 +11,7 @@ using Robust.Shared.Log; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; -namespace Content.Server.GameObjects.Components.Nutrition +namespace Content.Server.GameObjects.Components.Body.Digestive { /// /// Where reagents go when ingested. Tracks ingested reagents over time, and @@ -25,7 +25,7 @@ namespace Content.Server.GameObjects.Components.Nutrition #pragma warning restore 649 /// - /// Max volume of internal solution storage + /// Max volume of internal solution storage /// public ReagentUnit MaxVolume { @@ -34,33 +34,29 @@ namespace Content.Server.GameObjects.Components.Nutrition } /// - /// Internal solution storage + /// Internal solution storage /// [ViewVariables] private SolutionComponent _stomachContents; /// - /// Initial internal solution storage volume + /// Initial internal solution storage volume /// [ViewVariables] private ReagentUnit _initialMaxVolume; /// - /// Time in seconds between reagents being ingested and them being transferred to + /// Time in seconds between reagents being ingested and them being transferred + /// to /// [ViewVariables] private float _digestionDelay; /// - /// Used to track how long each reagent has been in the stomach + /// Used to track how long each reagent has been in the stomach /// private readonly List _reagentDeltas = new List(); - /// - /// Reference to bloodstream where digested reagents are transferred to - /// - private BloodstreamComponent _bloodstream; - public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); @@ -70,14 +66,10 @@ namespace Content.Server.GameObjects.Components.Nutrition protected override void Startup() { + base.Startup(); + _stomachContents = Owner.GetComponent(); _stomachContents.MaxVolume = _initialMaxVolume; - if (!Owner.TryGetComponent(out _bloodstream)) - { - Logger.Warning(_localizationManager.GetString( - "StomachComponent entity does not have a BloodstreamComponent, which is required for it to function. Owner entity name: {0}", - Owner.Name)); - } } public bool TryTransferSolution(Solution solution) @@ -88,9 +80,9 @@ namespace Content.Server.GameObjects.Components.Nutrition return false; } - //Add solution to _stomachContents + // Add solution to _stomachContents _stomachContents.TryAddSolution(solution, false, true); - //Add each reagent to _reagentDeltas. Used to track how long each reagent has been in the stomach + // Add each reagent to _reagentDeltas. Used to track how long each reagent has been in the stomach foreach (var reagent in solution.Contents) { _reagentDeltas.Add(new ReagentDelta(reagent.ReagentId, reagent.Quantity)); @@ -100,23 +92,26 @@ namespace Content.Server.GameObjects.Components.Nutrition } /// - /// Updates digestion status of ingested reagents. Once reagents surpass _digestionDelay - /// they are moved to the bloodstream, where they are then metabolized. + /// Updates digestion status of ingested reagents. + /// Once reagents surpass _digestionDelay they are moved to the bloodstream, + /// where they are then metabolized. /// - /// The time since the last update in seconds. - public void OnUpdate(float tickTime) + /// The time since the last update in seconds. + public void Update(float frameTime) { - if (_bloodstream == null) + if (!Owner.TryGetComponent(out BloodstreamComponent bloodstream)) { return; } - //Add reagents ready for transfer to bloodstream to transferSolution + // Add reagents ready for transfer to bloodstream to transferSolution var transferSolution = new Solution(); - foreach (var delta in _reagentDeltas.ToList()) //Use ToList here to remove entries while iterating + + // Use ToList here to remove entries while iterating + foreach (var delta in _reagentDeltas.ToList()) { //Increment lifetime of reagents - delta.Increment(tickTime); + delta.Increment(frameTime); if (delta.Lifetime > _digestionDelay) { _stomachContents.TryRemoveReagent(delta.ReagentId, delta.Quantity); @@ -124,12 +119,13 @@ namespace Content.Server.GameObjects.Components.Nutrition _reagentDeltas.Remove(delta); } } - //Transfer digested reagents to bloodstream - _bloodstream.TryTransferSolution(transferSolution); + + // Transfer digested reagents to bloodstream + bloodstream.TryTransferSolution(transferSolution); } /// - /// Used to track quantity changes when ingesting & digesting reagents + /// Used to track quantity changes when ingesting & digesting reagents /// private class ReagentDelta { diff --git a/Content.Server/Health/BodySystem/BodyPart/DroppedBodyPartComponent.cs b/Content.Server/GameObjects/Components/Body/DroppedBodyPartComponent.cs similarity index 52% rename from Content.Server/Health/BodySystem/BodyPart/DroppedBodyPartComponent.cs rename to Content.Server/GameObjects/Components/Body/DroppedBodyPartComponent.cs index a49ba2c656..431f60e026 100644 --- a/Content.Server/Health/BodySystem/BodyPart/DroppedBodyPartComponent.cs +++ b/Content.Server/GameObjects/Components/Body/DroppedBodyPartComponent.cs @@ -1,10 +1,9 @@ using System.Collections.Generic; -using System.Globalization; using System.Linq; -using Content.Shared.Health.BodySystem; -using Content.Shared.Health.BodySystem.Surgery; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; +using Content.Server.Body; +using Content.Shared.Body.Surgery; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.UserInterface; using Robust.Server.Interfaces.Player; @@ -14,98 +13,114 @@ using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.ViewVariables; -namespace Content.Server.Health.BodySystem.BodyPart +namespace Content.Server.GameObjects.Components.Body { - /// - /// Component representing a dropped, tangible entity. + /// Component representing a dropped, tangible entity. /// [RegisterComponent] public class DroppedBodyPartComponent : Component, IAfterInteract, IBodyPartContainer { - #pragma warning disable 649 [Dependency] private readonly ISharedNotifyManager _sharedNotifyManager; #pragma warning restore 649 - public sealed override string Name => "DroppedBodyPart"; - - [ViewVariables] - public BodyPart ContainedBodyPart { get; set; } + private readonly Dictionary _optionsCache = new Dictionary(); + private BodyManagerComponent _bodyManagerComponentCache; + private int _idHash; + private IEntity _performerCache; private BoundUserInterface _userInterface; - private Dictionary _optionsCache = new Dictionary(); - private IEntity _performerCache; - private BodyManagerComponent _bodyManagerComponentCache; - private int _idHash = 0; - public override void Initialize() - { - base.Initialize(); - _userInterface = Owner.GetComponent().GetBoundUserInterface(GenericSurgeryUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; - } + public sealed override string Name => "DroppedBodyPart"; - public void TransferBodyPartData(BodyPart data) - { - ContainedBodyPart = data; - Owner.Name = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(ContainedBodyPart.Name); - if (Owner.TryGetComponent(out SpriteComponent component)) - { - component.LayerSetRSI(0, data.RSIPath); - component.LayerSetState(0, data.RSIState); - } - } + [ViewVariables] public BodyPart ContainedBodyPart { get; private set; } void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { if (eventArgs.Target == null) + { return; + } CloseAllSurgeryUIs(); _optionsCache.Clear(); _performerCache = null; _bodyManagerComponentCache = null; - if (eventArgs.Target.TryGetComponent(out BodyManagerComponent bodyManager)) + if (eventArgs.Target.TryGetComponent(out BodyManagerComponent bodyManager)) { SendBodySlotListToUser(eventArgs, bodyManager); } } - private void SendBodySlotListToUser(AfterInteractEventArgs eventArgs, BodyManagerComponent bodyManager) + public override void Initialize() { - var toSend = new Dictionary(); //Create dictionary to send to client (text to be shown : data sent back if selected) + base.Initialize(); - //Here we are trying to grab a list of all empty BodySlots adjancent to an existing BodyPart that can be attached to. i.e. an empty left hand slot, connected to an occupied left arm slot would be valid. - List unoccupiedSlots = bodyManager.AllSlots.ToList().Except(bodyManager.OccupiedSlots.ToList()).ToList(); - foreach (string slot in unoccupiedSlots) + _userInterface = Owner.GetComponent() + .GetBoundUserInterface(GenericSurgeryUiKey.Key); + _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + } + + public void TransferBodyPartData(BodyPart data) + { + ContainedBodyPart = data; + Owner.Name = Loc.GetString(ContainedBodyPart.Name); + + if (Owner.TryGetComponent(out SpriteComponent component)) { - if (bodyManager.TryGetSlotType(slot, out BodyPartType typeResult) && typeResult == ContainedBodyPart.PartType) + component.LayerSetRSI(0, data.RSIPath); + component.LayerSetState(0, data.RSIState); + + if (data.RSIColor.HasValue) { - if (bodyManager.TryGetBodyPartConnections(slot, out List bodypartResult)) - { - foreach (BodyPart connectedPart in bodypartResult) - { - if (connectedPart.CanAttachBodyPart(ContainedBodyPart)) - { - _optionsCache.Add(_idHash, slot); - toSend.Add(slot, _idHash++); - } - } - } + component.LayerSetColor(0, data.RSIColor.Value); } } + } + + private void SendBodySlotListToUser(AfterInteractEventArgs eventArgs, BodyManagerComponent bodyManager) + { + // Create dictionary to send to client (text to be shown : data sent back if selected) + var toSend = new Dictionary(); + + // Here we are trying to grab a list of all empty BodySlots adjacent to an existing BodyPart that can be + // attached to. i.e. an empty left hand slot, connected to an occupied left arm slot would be valid. + var unoccupiedSlots = bodyManager.AllSlots.ToList().Except(bodyManager.OccupiedSlots.ToList()).ToList(); + foreach (var slot in unoccupiedSlots) + { + if (!bodyManager.TryGetSlotType(slot, out var typeResult) || + typeResult != ContainedBodyPart.PartType || + !bodyManager.TryGetBodyPartConnections(slot, out var parts)) + { + continue; + } + + foreach (var connectedPart in parts) + { + if (!connectedPart.CanAttachBodyPart(ContainedBodyPart)) + { + continue; + } + + _optionsCache.Add(_idHash, slot); + toSend.Add(slot, _idHash++); + } + } + if (_optionsCache.Count > 0) { OpenSurgeryUI(eventArgs.User.GetComponent().playerSession); - UpdateSurgeryUIBodyPartSlotRequest(eventArgs.User.GetComponent().playerSession, toSend); + UpdateSurgeryUIBodyPartSlotRequest(eventArgs.User.GetComponent().playerSession, + toSend); _performerCache = eventArgs.User; _bodyManagerComponentCache = bodyManager; } - else //If surgery cannot be performed, show message saying so. + else // If surgery cannot be performed, show message saying so. { - _sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User, Loc.GetString("You see no way to install {0:theName}.", Owner)); + _sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User, + Loc.GetString("You see no way to install {0:theName}.", Owner)); } } @@ -115,47 +130,50 @@ namespace Content.Server.Health.BodySystem.BodyPart private void HandleReceiveBodyPartSlot(int key) { CloseSurgeryUI(_performerCache.GetComponent().playerSession); - //TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc - if (!_optionsCache.TryGetValue(key, out object targetObject)) + + // TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc + if (!_optionsCache.TryGetValue(key, out var targetObject)) { - _sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, Loc.GetString("You see no useful way to attach {0:theName} anymore.", Owner)); - } - string target = targetObject as string; - if (!_bodyManagerComponentCache.InstallDroppedBodyPart(this, target)) - { - _sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, Loc.GetString("You can't attach it!")); - } - else - { - _sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, Loc.GetString("You attach {0:theName}.", ContainedBodyPart)); + _sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, + Loc.GetString("You see no useful way to attach {0:theName} anymore.", Owner)); } + + var target = targetObject as string; + + _sharedNotifyManager.PopupMessage( + _bodyManagerComponentCache.Owner, + _performerCache, + !_bodyManagerComponentCache.InstallDroppedBodyPart(this, target) + ? Loc.GetString("You can't attach it!") + : Loc.GetString("You attach {0:theName}.", ContainedBodyPart)); } - - public void OpenSurgeryUI(IPlayerSession session) + private void OpenSurgeryUI(IPlayerSession session) { _userInterface.Open(session); } - public void UpdateSurgeryUIBodyPartSlotRequest(IPlayerSession session, Dictionary options) + + private void UpdateSurgeryUIBodyPartSlotRequest(IPlayerSession session, Dictionary options) { _userInterface.SendMessage(new RequestBodyPartSlotSurgeryUIMessage(options), session); } - public void CloseSurgeryUI(IPlayerSession session) + + private void CloseSurgeryUI(IPlayerSession session) { _userInterface.Close(session); } - public void CloseAllSurgeryUIs() + + private void CloseAllSurgeryUIs() { _userInterface.CloseAll(); } - private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message) { switch (message.Message) { case ReceiveBodyPartSlotSurgeryUIMessage msg: - HandleReceiveBodyPartSlot(msg.SelectedOptionID); + HandleReceiveBodyPartSlot(msg.SelectedOptionId); break; } } diff --git a/Content.Server/Health/BodySystem/Mechanism/DroppedMechanismComponent.cs b/Content.Server/GameObjects/Components/Body/DroppedMechanismComponent.cs similarity index 60% rename from Content.Server/Health/BodySystem/Mechanism/DroppedMechanismComponent.cs rename to Content.Server/GameObjects/Components/Body/DroppedMechanismComponent.cs index 788082c0d9..58cab75d74 100644 --- a/Content.Server/Health/BodySystem/Mechanism/DroppedMechanismComponent.cs +++ b/Content.Server/GameObjects/Components/Body/DroppedMechanismComponent.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; -using System.Globalization; -using Content.Server.Health.BodySystem.BodyPart; -using Content.Shared.Health.BodySystem.Mechanism; -using Content.Shared.Health.BodySystem.Surgery; +using Content.Server.Body; +using Content.Server.Body.Mechanisms; +using Content.Shared.Body.Mechanism; +using Content.Shared.Body.Surgery; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; @@ -18,15 +18,14 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; -namespace Content.Server.Health.BodySystem.Mechanism { - +namespace Content.Server.GameObjects.Components.Body +{ /// - /// Component representing a dropped, tangible entity. + /// Component representing a dropped, tangible entity. /// [RegisterComponent] public class DroppedMechanismComponent : Component, IAfterInteract { - #pragma warning disable 649 [Dependency] private readonly ISharedNotifyManager _sharedNotifyManager; [Dependency] private IPrototypeManager _prototypeManager; @@ -34,98 +33,120 @@ namespace Content.Server.Health.BodySystem.Mechanism { public sealed override string Name => "DroppedMechanism"; - [ViewVariables] - public Mechanism ContainedMechanism { get; private set; } + private readonly Dictionary _optionsCache = new Dictionary(); + + private BodyManagerComponent _bodyManagerComponentCache; + + private int _idHash; + + private IEntity _performerCache; private BoundUserInterface _userInterface; - private Dictionary _optionsCache = new Dictionary(); - private IEntity _performerCache; - private BodyManagerComponent _bodyManagerComponentCache; - private int _idHash = 0; - public override void Initialize() - { - base.Initialize(); - _userInterface = Owner.GetComponent().GetBoundUserInterface(GenericSurgeryUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; - } - - public void InitializeDroppedMechanism(Mechanism data) - { - ContainedMechanism = data; - Owner.Name = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(ContainedMechanism.Name); - if (Owner.TryGetComponent(out SpriteComponent component)) - { - component.LayerSetRSI(0, data.RSIPath); - component.LayerSetState(0, data.RSIState); - } - } + [ViewVariables] public Mechanism ContainedMechanism { get; private set; } void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { if (eventArgs.Target == null) + { return; + } CloseAllSurgeryUIs(); _optionsCache.Clear(); _performerCache = null; _bodyManagerComponentCache = null; - if (eventArgs.Target.TryGetComponent(out BodyManagerComponent bodyManager)) + if (eventArgs.Target.TryGetComponent(out var bodyManager)) { SendBodyPartListToUser(eventArgs, bodyManager); } - else if (eventArgs.Target.TryGetComponent(out DroppedBodyPartComponent droppedBodyPart)) + else if (eventArgs.Target.TryGetComponent(out var droppedBodyPart)) { if (droppedBodyPart.ContainedBodyPart == null) { - Logger.Debug("Installing a mechanism was attempted on an IEntity with a DroppedBodyPartComponent that doesn't have a BodyPart in it!"); + Logger.Debug( + "Installing a mechanism was attempted on an IEntity with a DroppedBodyPartComponent that doesn't have a BodyPart in it!"); throw new InvalidOperationException("A DroppedBodyPartComponent exists without a BodyPart in it!"); } + if (!droppedBodyPart.ContainedBodyPart.TryInstallDroppedMechanism(this)) { - _sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User, Loc.GetString("You can't fit it in!")); + _sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User, + Loc.GetString("You can't fit it in!")); } } } + public override void Initialize() + { + base.Initialize(); + + _userInterface = Owner.GetComponent() + .GetBoundUserInterface(GenericSurgeryUiKey.Key); + _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + } + + public void InitializeDroppedMechanism(Mechanism data) + { + ContainedMechanism = data; + Owner.Name = Loc.GetString(ContainedMechanism.Name); + + if (Owner.TryGetComponent(out SpriteComponent component)) + { + component.LayerSetRSI(0, data.RSIPath); + component.LayerSetState(0, data.RSIState); + } + } + public override void ExposeData(ObjectSerializer serializer) { - //This is a temporary way to have spawnable hard-coded DroppedMechanismComponent prototypes - //In the future (when it becomes possible) DroppedMechanismComponent should be auto-generated from the Mechanism prototypes - string debugLoadMechanismData = ""; + // This is a temporary way to have spawnable hard-coded DroppedMechanismComponent prototypes + // In the future (when it becomes possible) DroppedMechanismComponent should be auto-generated from + // the Mechanism prototypes + var debugLoadMechanismData = ""; base.ExposeData(serializer); + serializer.DataField(ref debugLoadMechanismData, "debugLoadMechanismData", ""); + if (serializer.Reading && debugLoadMechanismData != "") { _prototypeManager.TryIndex(debugLoadMechanismData, out MechanismPrototype data); - InitializeDroppedMechanism(new Mechanism(data)); + + var mechanism = new Mechanism(data); + mechanism.EnsureInitialize(); + + InitializeDroppedMechanism(mechanism); } } - - private void SendBodyPartListToUser(AfterInteractEventArgs eventArgs, BodyManagerComponent bodyManager) { - var toSend = new Dictionary(); //Create dictionary to send to client (text to be shown : data sent back if selected) - foreach (var (key, value) in bodyManager.PartDictionary) - { //For each limb in the target, add it to our cache if it is a valid option. + // Create dictionary to send to client (text to be shown : data sent back if selected) + var toSend = new Dictionary(); + + foreach (var (key, value) in bodyManager.Parts) + { + // For each limb in the target, add it to our cache if it is a valid option. if (value.CanInstallMechanism(ContainedMechanism)) { _optionsCache.Add(_idHash, value); toSend.Add(key + ": " + value.Name, _idHash++); } } + if (_optionsCache.Count > 0) { OpenSurgeryUI(eventArgs.User.GetComponent().playerSession); - UpdateSurgeryUIBodyPartRequest(eventArgs.User.GetComponent().playerSession, toSend); + UpdateSurgeryUIBodyPartRequest(eventArgs.User.GetComponent().playerSession, + toSend); _performerCache = eventArgs.User; _bodyManagerComponentCache = bodyManager; } - else //If surgery cannot be performed, show message saying so. + else // If surgery cannot be performed, show message saying so. { - _sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User, Loc.GetString("You see no way to install the {0}.", Owner.Name)); + _sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User, + Loc.GetString("You see no way to install the {0}.", Owner.Name)); } } @@ -135,52 +156,55 @@ namespace Content.Server.Health.BodySystem.Mechanism { private void HandleReceiveBodyPart(int key) { CloseSurgeryUI(_performerCache.GetComponent().playerSession); - //TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc - if (!_optionsCache.TryGetValue(key, out object targetObject)) + + // TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc + if (!_optionsCache.TryGetValue(key, out var targetObject)) { - _sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, Loc.GetString("You see no useful way to use the {0} anymore.", Owner.Name)); - } - BodyPart.BodyPart target = targetObject as BodyPart.BodyPart; - if (!target.TryInstallDroppedMechanism(this)) - { - _sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, Loc.GetString("You can't fit it in!")); - } - else - { - _sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, Loc.GetString("You jam the {1} inside {0:them}.", _performerCache, ContainedMechanism.Name)); + _sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, + Loc.GetString("You see no useful way to use the {0} anymore.", Owner.Name)); + return; } + + var target = targetObject as BodyPart; + + _sharedNotifyManager.PopupMessage( + _bodyManagerComponentCache.Owner, + _performerCache, + !target.TryInstallDroppedMechanism(this) + ? Loc.GetString("You can't fit it in!") + : Loc.GetString("You jam the {1} inside {0:them}.", _performerCache, ContainedMechanism.Name)); + + // TODO: {1:theName} } - - - - public void OpenSurgeryUI(IPlayerSession session) + private void OpenSurgeryUI(IPlayerSession session) { _userInterface.Open(session); } - public void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary options) + + private void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary options) { _userInterface.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session); } - public void CloseSurgeryUI(IPlayerSession session) + + private void CloseSurgeryUI(IPlayerSession session) { _userInterface.Close(session); } - public void CloseAllSurgeryUIs() + + private void CloseAllSurgeryUIs() { _userInterface.CloseAll(); } - private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message) { switch (message.Message) { case ReceiveBodyPartSurgeryUIMessage msg: - HandleReceiveBodyPart(msg.SelectedOptionID); + HandleReceiveBodyPart(msg.SelectedOptionId); break; } } } } - diff --git a/Content.Server/GameObjects/Components/Body/Respiratory/LungComponent.cs b/Content.Server/GameObjects/Components/Body/Respiratory/LungComponent.cs new file mode 100644 index 0000000000..eb625d7a64 --- /dev/null +++ b/Content.Server/GameObjects/Components/Body/Respiratory/LungComponent.cs @@ -0,0 +1,127 @@ +using System; +using Content.Server.Atmos; +using Content.Server.GameObjects.Components.Body.Circulatory; +using Content.Server.Interfaces; +using Content.Shared.Atmos; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Body.Respiratory +{ + [RegisterComponent] + public class LungComponent : Component, IGasMixtureHolder + { + public override string Name => "Lung"; + + private float _accumulatedFrameTime; + + /// + /// The pressure that this lung exerts on the air around it + /// + [ViewVariables(VVAccess.ReadWrite)] private float Pressure { get; set; } + + [ViewVariables] public GasMixture Air { get; set; } = new GasMixture(); + + [ViewVariables] public LungStatus Status { get; set; } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataReadWriteFunction( + "volume", + 6, + vol => Air.Volume = vol, + () => Air.Volume); + serializer.DataField(this, l => l.Pressure, "pressure", 100); + } + + public void Update(float frameTime) + { + if (Status == LungStatus.None) + { + Status = LungStatus.Inhaling; + } + + _accumulatedFrameTime += Status switch + { + LungStatus.Inhaling => frameTime, + LungStatus.Exhaling => -frameTime, + _ => throw new ArgumentOutOfRangeException() + }; + + var absoluteTime = Math.Abs(_accumulatedFrameTime); + if (absoluteTime < 2) + { + return; + } + + switch (Status) + { + case LungStatus.Inhaling: + Inhale(absoluteTime); + Status = LungStatus.Exhaling; + break; + case LungStatus.Exhaling: + Exhale(absoluteTime); + Status = LungStatus.Inhaling; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + _accumulatedFrameTime = absoluteTime - 2; + } + + public void Inhale(float frameTime) + { + if (!Owner.TryGetComponent(out BloodstreamComponent bloodstream)) + { + return; + } + + if (!Owner.Transform.GridPosition.TryGetTileAir(out var tileAir)) + { + return; + } + + var amount = Atmospherics.BreathPercentage * frameTime; + var volumeRatio = amount / tileAir.Volume; + var temp = tileAir.RemoveRatio(volumeRatio); + + temp.PumpGasTo(Air, Pressure); + Air.PumpGasTo(bloodstream.Air, Pressure); + tileAir.Merge(temp); + } + + public void Exhale(float frameTime) + { + if (!Owner.TryGetComponent(out BloodstreamComponent bloodstream)) + { + return; + } + + if (!Owner.Transform.GridPosition.TryGetTileAir(out var tileAir)) + { + return; + } + + bloodstream.PumpToxins(Air, Pressure); + + var amount = Atmospherics.BreathPercentage * frameTime; + var volumeRatio = amount / tileAir.Volume; + var temp = tileAir.RemoveRatio(volumeRatio); + + temp.PumpGasTo(tileAir, Pressure); + Air.Merge(temp); + } + } + + public enum LungStatus + { + None = 0, + Inhaling, + Exhaling + } +} diff --git a/Content.Server/Health/BodySystem/Surgery/Surgeon/SurgeryToolComponent.cs b/Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs similarity index 55% rename from Content.Server/Health/BodySystem/Surgery/Surgeon/SurgeryToolComponent.cs rename to Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs index 42e9e3f2ca..7f4ece43a0 100644 --- a/Content.Server/Health/BodySystem/Surgery/Surgeon/SurgeryToolComponent.cs +++ b/Content.Server/GameObjects/Components/Body/SurgeryToolComponent.cs @@ -1,14 +1,16 @@ using System; using System.Collections.Generic; -using Content.Server.Health.BodySystem.BodyPart; -using Content.Server.Health.BodySystem.Mechanism; +using Content.Server.Body; +using Content.Server.Body.Mechanisms; +using Content.Server.Body.Surgery; +using Content.Shared.Body.Surgery; using Content.Shared.GameObjects; -using Content.Shared.Health.BodySystem; -using Content.Shared.Health.BodySystem.Surgery; +using Content.Shared.GameObjects.Components.Body; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.UserInterface; +using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; @@ -17,110 +19,136 @@ using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.Serialization; -namespace Content.Server.Health.BodySystem.Surgery.Surgeon +namespace Content.Server.GameObjects.Components.Body { - //TODO: add checks to close UI if user walks too far away from tool or target. + // TODO: add checks to close UI if user walks too far away from tool or target. /// - /// Server-side component representing a generic tool capable of performing surgery. For instance, the scalpel. + /// Server-side component representing a generic tool capable of performing surgery. + /// For instance, the scalpel. /// [RegisterComponent] public class SurgeryToolComponent : Component, ISurgeon, IAfterInteract { - public override string Name => "SurgeryTool"; - public override uint? NetID => ContentNetIDs.SURGERY; - #pragma warning disable 649 [Dependency] private readonly ISharedNotifyManager _sharedNotifyManager; #pragma warning restore 649 - public float BaseOperationTime { get => _baseOperateTime; set => _baseOperateTime = value; } + public override string Name => "SurgeryTool"; + public override uint? NetID => ContentNetIDs.SURGERY; + + private readonly Dictionary _optionsCache = new Dictionary(); + private float _baseOperateTime; - private SurgeryType _surgeryType; - private HashSet _subscribedSessions = new HashSet(); - private BoundUserInterface _userInterface; - private Dictionary _optionsCache = new Dictionary(); - private IEntity _performerCache; private BodyManagerComponent _bodyManagerComponentCache; - private ISurgeon.MechanismRequestCallback _callbackCache; - private int _idHash = 0; - public override void Initialize() - { - base.Initialize(); - _userInterface = Owner.GetComponent().GetBoundUserInterface(GenericSurgeryUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; - } + private ISurgeon.MechanismRequestCallback _callbackCache; + + private int _idHash; + + private IEntity _performerCache; + + private SurgeryType _surgeryType; + + private BoundUserInterface _userInterface; void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) { if (eventArgs.Target == null) + { return; + } + + if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) + { + return; + } CloseAllSurgeryUIs(); _optionsCache.Clear(); + _performerCache = null; _bodyManagerComponentCache = null; _callbackCache = null; - if (eventArgs.Target.TryGetComponent(out BodyManagerComponent bodyManager)) //Attempt surgery on a BodyManagerComponent by sending a list of operatable BodyParts to the client to choose from + // Attempt surgery on a BodyManagerComponent by sending a list of operable BodyParts to the client to choose from + if (eventArgs.Target.TryGetComponent(out BodyManagerComponent body)) { - var toSend = new Dictionary(); //Create dictionary to send to client (text to be shown : data sent back if selected) - foreach (var(key, value) in bodyManager.PartDictionary) { //For each limb in the target, add it to our cache if it is a valid option. + // Create dictionary to send to client (text to be shown : data sent back if selected) + var toSend = new Dictionary(); + + foreach (var (key, value) in body.Parts) + { + // For each limb in the target, add it to our cache if it is a valid option. if (value.SurgeryCheck(_surgeryType)) { _optionsCache.Add(_idHash, value); toSend.Add(key + ": " + value.Name, _idHash++); } } + if (_optionsCache.Count > 0) { - OpenSurgeryUI(eventArgs.User.GetComponent().playerSession); - UpdateSurgeryUIBodyPartRequest(eventArgs.User.GetComponent().playerSession, toSend); - _performerCache = eventArgs.User; //Also, cache the data. - _bodyManagerComponentCache = bodyManager; + OpenSurgeryUI(actor.playerSession); + UpdateSurgeryUIBodyPartRequest(actor.playerSession, toSend); + _performerCache = eventArgs.User; // Also, cache the data. + _bodyManagerComponentCache = body; } - else //If surgery cannot be performed, show message saying so. + else // If surgery cannot be performed, show message saying so. { SendNoUsefulWayToUsePopup(); } } - else if (eventArgs.Target.TryGetComponent(out DroppedBodyPartComponent droppedBodyPart)) //Attempt surgery on a DroppedBodyPart - there's only one possible target so no need for selection UI + else if (eventArgs.Target.TryGetComponent(out var droppedBodyPart)) { + // Attempt surgery on a DroppedBodyPart - there's only one possible target so no need for selection UI _performerCache = eventArgs.User; - if (droppedBodyPart.ContainedBodyPart == null) //Throw error if the DroppedBodyPart has no data in it. + + if (droppedBodyPart.ContainedBodyPart == null) { - Logger.Debug("Surgery was attempted on an IEntity with a DroppedBodyPartComponent that doesn't have a BodyPart in it!"); + // Throw error if the DroppedBodyPart has no data in it. + Logger.Debug( + "Surgery was attempted on an IEntity with a DroppedBodyPartComponent that doesn't have a BodyPart in it!"); throw new InvalidOperationException("A DroppedBodyPartComponent exists without a BodyPart in it!"); } - if (droppedBodyPart.ContainedBodyPart.SurgeryCheck(_surgeryType)) //If surgery can be performed... - { - if (!droppedBodyPart.ContainedBodyPart.AttemptSurgery(_surgeryType, droppedBodyPart, this, eventArgs.User)) //...do the surgery. - { - Logger.Debug("Error when trying to perform surgery on bodypart " + eventArgs.User.Name + "!"); //Log error if the surgery fails somehow. - throw new InvalidOperationException(); - } - } - else //If surgery cannot be performed, show message saying so. + + // If surgery can be performed... + if (!droppedBodyPart.ContainedBodyPart.SurgeryCheck(_surgeryType)) { SendNoUsefulWayToUsePopup(); + return; } + + //...do the surgery. + if (droppedBodyPart.ContainedBodyPart.AttemptSurgery(_surgeryType, droppedBodyPart, this, + eventArgs.User)) + { + return; + } + + // Log error if the surgery fails somehow. + Logger.Debug($"Error when trying to perform surgery on ${nameof(BodyPart)} {eventArgs.User.Name}"); + throw new InvalidOperationException(); } } - public void RequestMechanism(List options, ISurgeon.MechanismRequestCallback callback) + public float BaseOperationTime { get => _baseOperateTime; set => _baseOperateTime = value; } + + public void RequestMechanism(IEnumerable options, ISurgeon.MechanismRequestCallback callback) { - var toSend = new Dictionary (); - foreach (Mechanism.Mechanism mechanism in options) + var toSend = new Dictionary(); + foreach (var mechanism in options) { _optionsCache.Add(_idHash, mechanism); toSend.Add(mechanism.Name, _idHash++); } + if (_optionsCache.Count > 0) { OpenSurgeryUI(_performerCache.GetComponent().playerSession); - UpdateSurgeryUIMechanismRequest(_performerCache.GetComponent().playerSession, toSend); + UpdateSurgeryUIMechanismRequest(_performerCache.GetComponent().playerSession, + toSend); _callbackCache = callback; } else @@ -130,95 +158,112 @@ namespace Content.Server.Health.BodySystem.Surgery.Surgeon } } + public override void Initialize() + { + base.Initialize(); + _userInterface = Owner.GetComponent() + .GetBoundUserInterface(GenericSurgeryUiKey.Key); + _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + } - - - - - public void OpenSurgeryUI(IPlayerSession session) + private void OpenSurgeryUI(IPlayerSession session) { _userInterface.Open(session); } - public void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary options) + + private void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary options) { _userInterface.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session); } - public void UpdateSurgeryUIMechanismRequest(IPlayerSession session, Dictionary options) + + private void UpdateSurgeryUIMechanismRequest(IPlayerSession session, Dictionary options) { _userInterface.SendMessage(new RequestMechanismSurgeryUIMessage(options), session); } - public void CloseSurgeryUI(IPlayerSession session) + + private void CloseSurgeryUI(IPlayerSession session) { _userInterface.Close(session); } - public void CloseAllSurgeryUIs() + + private void CloseAllSurgeryUIs() { _userInterface.CloseAll(); } - - - private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message) { switch (message.Message) { case ReceiveBodyPartSurgeryUIMessage msg: - HandleReceiveBodyPart(msg.SelectedOptionID); + HandleReceiveBodyPart(msg.SelectedOptionId); break; case ReceiveMechanismSurgeryUIMessage msg: - HandleReceiveMechanism(msg.SelectedOptionID); + HandleReceiveMechanism(msg.SelectedOptionId); break; } } /// - /// Called after the client chooses from a list of possible BodyParts that can be operated on. + /// Called after the client chooses from a list of possible + /// that can be operated on. /// private void HandleReceiveBodyPart(int key) { CloseSurgeryUI(_performerCache.GetComponent().playerSession); - //TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc - if (!_optionsCache.TryGetValue(key, out object targetObject)) - { - SendNoUsefulWayToUseAnymorePopup(); - } - BodyPart.BodyPart target = targetObject as BodyPart.BodyPart; - if (!target.AttemptSurgery(_surgeryType, _bodyManagerComponentCache, this, _performerCache)) + // TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc + if (!_optionsCache.TryGetValue(key, out var targetObject)) { SendNoUsefulWayToUseAnymorePopup(); } - } - /// - /// Called after the client chooses from a list of possible Mechanisms to choose from. - /// - private void HandleReceiveMechanism(int key) - { - //TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc - if (!_optionsCache.TryGetValue(key, out object targetObject)) + var target = targetObject as BodyPart; + + if (!target.AttemptSurgery(_surgeryType, _bodyManagerComponentCache, this, _performerCache)) { SendNoUsefulWayToUseAnymorePopup(); } - Mechanism.Mechanism target = targetObject as Mechanism.Mechanism; + } + + /// + /// Called after the client chooses from a list of possible + /// to choose from. + /// + private void HandleReceiveMechanism(int key) + { + // TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc + if (!_optionsCache.TryGetValue(key, out var targetObject)) + { + SendNoUsefulWayToUseAnymorePopup(); + } + + var target = targetObject as Mechanism; + CloseSurgeryUI(_performerCache.GetComponent().playerSession); _callbackCache(target, _bodyManagerComponentCache, this, _performerCache); } private void SendNoUsefulWayToUsePopup() { - _sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, Loc.GetString("You see no useful way to use {0:theName}.", Owner)); + _sharedNotifyManager.PopupMessage( + _bodyManagerComponentCache.Owner, + _performerCache, + Loc.GetString("You see no useful way to use {0:theName}.", Owner)); } private void SendNoUsefulWayToUseAnymorePopup() { - _sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, Loc.GetString("You see no useful way to use {0:theName} anymore.", Owner)); + _sharedNotifyManager.PopupMessage( + _bodyManagerComponentCache.Owner, + _performerCache, + Loc.GetString("You see no useful way to use {0:theName} anymore.", Owner)); } public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); + serializer.DataField(ref _surgeryType, "surgeryType", SurgeryType.Incision); serializer.DataField(ref _baseOperateTime, "baseOperateTime", 5); } diff --git a/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs b/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs index 5c9870c53f..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 SpeciesComponent species)) + if (Owner.TryGetComponent(out MobStateManagerComponent? stateManager)) { - species.CurrentDamageState.EnterState(Owner); + 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/Chemistry/ChemMasterComponent.cs b/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs index 68551cc0e2..156b9efefb 100644 --- a/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Power.ApcNetComponents; @@ -376,7 +377,7 @@ namespace Content.Server.GameObjects.Components.Chemistry /// /// Data relevant to the event such as the actor which triggered it. /// - bool IInteractUsing.InteractUsing(InteractUsingEventArgs args) + async Task IInteractUsing.InteractUsing(InteractUsingEventArgs args) { if (!args.User.TryGetComponent(out IHandsComponent hands)) { diff --git a/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs b/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs index 9f78004304..03ac3855e7 100644 --- a/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs @@ -1,5 +1,5 @@ using System; -using Content.Server.GameObjects.Components.Metabolism; +using Content.Server.GameObjects.Components.Body.Circulatory; using Content.Server.Interfaces; using Content.Server.Utility; using Content.Shared.Chemistry; @@ -134,7 +134,8 @@ namespace Content.Server.GameObjects.Components.Chemistry } else //Handle injecting into bloodstream { - if (targetEntity.TryGetComponent(out var bloodstream) && _toggleState == InjectorToggleMode.Inject) + if (targetEntity.TryGetComponent(out BloodstreamComponent bloodstream) && + _toggleState == InjectorToggleMode.Inject) { TryInjectIntoBloodstream(bloodstream, eventArgs.User); } diff --git a/Content.Server/GameObjects/Components/Chemistry/PillComponent.cs b/Content.Server/GameObjects/Components/Chemistry/PillComponent.cs index 6d682e594f..7ff05c4398 100644 --- a/Content.Server/GameObjects/Components/Chemistry/PillComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/PillComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.Nutrition; +using Content.Server.GameObjects.Components.Body.Digestive; +using Content.Server.GameObjects.Components.Nutrition; using Content.Server.GameObjects.Components.Utensil; using Content.Server.Utility; using Content.Shared.Chemistry; diff --git a/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs b/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs index 13fd1e2081..e11d7d7438 100644 --- a/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.Interfaces; +using System.Threading.Tasks; +using Content.Server.Interfaces; using Content.Shared.Chemistry; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Shared.GameObjects; @@ -50,7 +51,7 @@ namespace Content.Server.GameObjects.Components.Chemistry /// /// Attack event args /// - bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) + async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { //Get target solution component if (!Owner.TryGetComponent(out var targetSolution)) diff --git a/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs b/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs index ea864a4b40..d32ae288ad 100644 --- a/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Power.ApcNetComponents; @@ -290,7 +291,7 @@ namespace Content.Server.GameObjects.Components.Chemistry /// /// Data relevant to the event such as the actor which triggered it. /// - bool IInteractUsing.InteractUsing(InteractUsingEventArgs args) + async Task IInteractUsing.InteractUsing(InteractUsingEventArgs args) { if (!args.User.TryGetComponent(out IHandsComponent hands)) { diff --git a/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs b/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs index 5218f996ce..53e3ca2463 100644 --- a/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs @@ -27,7 +27,7 @@ namespace Content.Server.GameObjects.Components.Chemistry /// ECS component that manages a liquid solution of reagents. /// [RegisterComponent] - internal class SolutionComponent : SharedSolutionComponent, IExamine + public class SolutionComponent : SharedSolutionComponent, IExamine { #pragma warning disable 649 [Dependency] private readonly IPrototypeManager _prototypeManager; diff --git a/Content.Server/GameObjects/Components/Conveyor/ConveyorComponent.cs b/Content.Server/GameObjects/Components/Conveyor/ConveyorComponent.cs index 6d6a192359..689e3f8bea 100644 --- a/Content.Server/GameObjects/Components/Conveyor/ConveyorComponent.cs +++ b/Content.Server/GameObjects/Components/Conveyor/ConveyorComponent.cs @@ -1,6 +1,7 @@ #nullable enable using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.Interactable; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Power.ApcNetComponents; @@ -59,7 +60,7 @@ namespace Content.Server.GameObjects.Components.Conveyor { _state = value; - if (!Owner.TryGetComponent(out AppearanceComponent appearance)) + if (!Owner.TryGetComponent(out AppearanceComponent? appearance)) { return; } @@ -92,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; @@ -113,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; @@ -154,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); @@ -162,10 +163,10 @@ namespace Content.Server.GameObjects.Components.Conveyor } } - private bool ToolUsed(IEntity user, ToolComponent tool) + private async Task ToolUsed(IEntity user, ToolComponent tool) { if (!Owner.HasComponent() && - tool.UseTool(user, Owner, ToolQuality.Prying)) + await tool.UseTool(user, Owner, 0.5f, ToolQuality.Prying)) { State = ConveyorState.Loose; @@ -224,7 +225,7 @@ namespace Content.Server.GameObjects.Components.Conveyor continue; } - if (!@switch.TryGetComponent(out ConveyorSwitchComponent component)) + if (!@switch.TryGetComponent(out ConveyorSwitchComponent? component)) { continue; } @@ -244,17 +245,17 @@ namespace Content.Server.GameObjects.Components.Conveyor Disconnect(); } - bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) + 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 ToolUsed(eventArgs.User, tool); + return await ToolUsed(eventArgs.User, tool); } return false; diff --git a/Content.Server/GameObjects/Components/Conveyor/ConveyorSwitchComponent.cs b/Content.Server/GameObjects/Components/Conveyor/ConveyorSwitchComponent.cs index 208b0ceeec..ae5e2c3aa1 100644 --- a/Content.Server/GameObjects/Components/Conveyor/ConveyorSwitchComponent.cs +++ b/Content.Server/GameObjects/Components/Conveyor/ConveyorSwitchComponent.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Content.Server.GameObjects.EntitySystems; using Content.Shared.GameObjects.Components.Conveyor; using Content.Shared.Interfaces; @@ -33,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); } @@ -144,7 +145,7 @@ namespace Content.Server.GameObjects.Components.Conveyor continue; } - if (!conveyor.TryGetComponent(out ConveyorComponent component)) + if (!conveyor.TryGetComponent(out ConveyorComponent? component)) { continue; } @@ -171,7 +172,7 @@ namespace Content.Server.GameObjects.Components.Conveyor continue; } - if (!@switch.TryGetComponent(out ConveyorSwitchComponent component)) + if (!@switch.TryGetComponent(out ConveyorSwitchComponent? component)) { continue; } @@ -193,15 +194,15 @@ namespace Content.Server.GameObjects.Components.Conveyor return NextState(); } - bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) + 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/Damage/BreakableComponent.cs b/Content.Server/GameObjects/Components/Damage/BreakableComponent.cs index 0a9e53e03a..15ad3e8a35 100644 --- a/Content.Server/GameObjects/Components/Damage/BreakableComponent.cs +++ b/Content.Server/GameObjects/Components/Damage/BreakableComponent.cs @@ -1,39 +1,59 @@ using System.Collections.Generic; -using Content.Server.GameObjects.EntitySystems; -using Content.Server.Interfaces.GameObjects; using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.EntitySystems; +using Robust.Server.GameObjects.EntitySystems; using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Random; using Robust.Shared.IoC; using Robust.Shared.Random; -using Robust.Shared.Serialization; namespace Content.Server.GameObjects.Components.Damage { + // TODO: Repair needs to set CurrentDamageState to DamageState.Alive, but it doesn't exist... should be easy enough if it's just an interface you can slap on BreakableComponent + + /// + /// When attached to an , allows it to take damage and sets it to a "broken state" after taking + /// enough damage. + /// [RegisterComponent] - public class BreakableComponent : Component, IOnDamageBehavior, IExAct + [ComponentReference(typeof(IDamageableComponent))] + public class BreakableComponent : RuinableComponent, IExAct { - - #pragma warning disable 649 +#pragma warning disable 649 [Dependency] private readonly IEntitySystemManager _entitySystemManager; - #pragma warning restore 649 - /// - public override string Name => "Breakable"; - public DamageThreshold Threshold { get; private set; } + [Dependency] private readonly IRobustRandom _random; +#pragma warning restore 649 - public DamageType damageType = DamageType.Total; - public int damageValue = 0; - public bool broken = false; + public override string Name => "Breakable"; private ActSystem _actSystem; + private DamageState _currentDamageState; - public override void ExposeData(ObjectSerializer serializer) + public override List SupportedDamageStates => + new List {DamageState.Alive, DamageState.Dead}; + + public override DamageState CurrentDamageState => _currentDamageState; + + void IExAct.OnExplosion(ExplosionEventArgs eventArgs) { - base.ExposeData(serializer); + switch (eventArgs.Severity) + { + case ExplosionSeverity.Destruction: + PerformDestruction(); + break; + case ExplosionSeverity.Heavy: + PerformDestruction(); + break; + case ExplosionSeverity.Light: + if (_random.Prob(0.5f)) + { + PerformDestruction(); + } - serializer.DataField(ref damageValue, "thresholdvalue", 100); - serializer.DataField(ref damageType, "thresholdtype", DamageType.Total); + break; + } } public override void Initialize() @@ -42,38 +62,21 @@ namespace Content.Server.GameObjects.Components.Damage _actSystem = _entitySystemManager.GetEntitySystem(); } - public List GetAllDamageThresholds() + // Might want to move this down and have a more standardized method of revival + public void FixAllDamage() { - Threshold = new DamageThreshold(damageType, damageValue, ThresholdType.Breakage); - return new List() {Threshold}; + Heal(); + _currentDamageState = DamageState.Alive; } - public void OnDamageThresholdPassed(object obj, DamageThresholdPassedEventArgs e) + protected override void DestructionBehavior() { - if (e.Passed && e.DamageThreshold == Threshold && broken == false) + _actSystem.HandleBreakage(Owner); + if (!Owner.Deleted && DestroySound != string.Empty) { - broken = true; - _actSystem.HandleBreakage(Owner); + var pos = Owner.Transform.GridPosition; + EntitySystem.Get().PlayAtCoords(DestroySound, pos); } } - - public void OnExplosion(ExplosionEventArgs eventArgs) - { - var prob = IoCManager.Resolve(); - switch (eventArgs.Severity) - { - case ExplosionSeverity.Destruction: - _actSystem.HandleBreakage(Owner); - break; - case ExplosionSeverity.Heavy: - _actSystem.HandleBreakage(Owner); - break; - case ExplosionSeverity.Light: - if(prob.Prob(0.4f)) - _actSystem.HandleBreakage(Owner); - break; - } - } - } } diff --git a/Content.Server/GameObjects/Components/Damage/DamageOnHighSpeedImpactComponent.cs b/Content.Server/GameObjects/Components/Damage/DamageOnHighSpeedImpactComponent.cs index 0dff2a90f2..756e417e0e 100644 --- a/Content.Server/GameObjects/Components/Damage/DamageOnHighSpeedImpactComponent.cs +++ b/Content.Server/GameObjects/Components/Damage/DamageOnHighSpeedImpactComponent.cs @@ -1,6 +1,7 @@ using System; using Content.Server.GameObjects.Components.Mobs; using Content.Shared.Audio; +using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Damage; using Robust.Server.GameObjects.EntitySystems; using Robust.Shared.GameObjects; @@ -23,7 +24,7 @@ namespace Content.Server.GameObjects.Components.Damage public override string Name => "DamageOnHighSpeedImpact"; - public DamageType Damage { get; set; } = DamageType.Brute; + public DamageType Damage { get; set; } = DamageType.Blunt; public float MinimumSpeed { get; set; } = 20f; public int BaseDamage { get; set; } = 5; public float Factor { get; set; } = 0.75f; @@ -38,7 +39,7 @@ namespace Content.Server.GameObjects.Components.Damage { base.ExposeData(serializer); - serializer.DataField(this, x => Damage, "damage", DamageType.Brute); + serializer.DataField(this, x => Damage, "damage", DamageType.Blunt); serializer.DataField(this, x => MinimumSpeed, "minimumSpeed", 20f); serializer.DataField(this, x => BaseDamage, "baseDamage", 5); serializer.DataField(this, x => Factor, "factor", 1f); @@ -51,7 +52,7 @@ namespace Content.Server.GameObjects.Components.Damage public void CollideWith(IEntity collidedWith) { - if (!Owner.TryGetComponent(out ICollidableComponent collidable) || !Owner.TryGetComponent(out DamageableComponent damageable)) return; + if (!Owner.TryGetComponent(out ICollidableComponent collidable) || !Owner.TryGetComponent(out IDamageableComponent damageable)) return; var speed = collidable.LinearVelocity.Length; @@ -70,7 +71,7 @@ namespace Content.Server.GameObjects.Components.Damage if (Owner.TryGetComponent(out StunnableComponent stun) && _robustRandom.Prob(StunChance)) stun.Stun(StunSeconds); - damageable.TakeDamage(Damage, damage, collidedWith, Owner); + damageable.ChangeDamage(Damage, damage, false, collidedWith); } } } diff --git a/Content.Server/GameObjects/Components/Damage/DamageOnToolInteractComponent.cs b/Content.Server/GameObjects/Components/Damage/DamageOnToolInteractComponent.cs index 836dd9d000..72043c215a 100644 --- a/Content.Server/GameObjects/Components/Damage/DamageOnToolInteractComponent.cs +++ b/Content.Server/GameObjects/Components/Damage/DamageOnToolInteractComponent.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.Interactable; -using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Interactable; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Shared.GameObjects; @@ -9,7 +10,7 @@ using Robust.Shared.Serialization; namespace Content.Server.GameObjects.Components.Damage { [RegisterComponent] - class DamageOnToolInteractComponent : Component, IInteractUsing + public class DamageOnToolInteractComponent : Component, IInteractUsing { public override string Name => "DamageOnToolInteract"; @@ -29,10 +30,10 @@ namespace Content.Server.GameObjects.Components.Damage public override void Initialize() { base.Initialize(); - Owner.EnsureComponent(); + Owner.EnsureComponent(); } - public bool InteractUsing(InteractUsingEventArgs eventArgs) + public async Task InteractUsing(InteractUsingEventArgs eventArgs) { if (eventArgs.Using.TryGetComponent(out var tool)) { @@ -40,12 +41,12 @@ namespace Content.Server.GameObjects.Components.Damage { if (tool.HasQuality(ToolQuality.Welding) && toolQuality == ToolQuality.Welding) { - if (eventArgs.Using.TryGetComponent(out WelderComponent welder)) - { + if (eventArgs.Using.TryGetComponent(out WelderComponent welder)) + { if (welder.WelderLit) return CallDamage(eventArgs, tool); - } + } break; //If the tool quality is welding and its not lit or its not actually a welder that can be lit then its pointless to continue. - } + } if (tool.HasQuality(toolQuality)) return CallDamage(eventArgs, tool); } @@ -55,14 +56,17 @@ namespace Content.Server.GameObjects.Components.Damage protected bool CallDamage(InteractUsingEventArgs eventArgs, ToolComponent tool) { - if (eventArgs.Target.TryGetComponent(out var damageable)) + if (eventArgs.Target.TryGetComponent(out var damageable)) { - if(tool.HasQuality(ToolQuality.Welding)) damageable.TakeDamage(DamageType.Heat, Damage, eventArgs.Using, eventArgs.User); - else - damageable.TakeDamage(DamageType.Brute, Damage, eventArgs.Using, eventArgs.User); + damageable.ChangeDamage(tool.HasQuality(ToolQuality.Welding) + ? DamageType.Heat + : DamageType.Blunt, + Damage, false, eventArgs.User); + return true; } - return false; + + return false; } } } diff --git a/Content.Server/GameObjects/Components/Damage/DamageThreshold.cs b/Content.Server/GameObjects/Components/Damage/DamageThreshold.cs deleted file mode 100644 index 1efd2ba7a4..0000000000 --- a/Content.Server/GameObjects/Components/Damage/DamageThreshold.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using Content.Shared.GameObjects.Components.Damage; -using Robust.Shared.Interfaces.GameObjects; - -namespace Content.Server.GameObjects.Components.Damage -{ - /// - /// Triggers an event when values rise above or drop below this threshold - /// - public struct DamageThreshold - { - public DamageType DamageType { get; } - public int Value { get; } - public ThresholdType ThresholdType { get; } - - public DamageThreshold(DamageType damageType, int value, ThresholdType thresholdType) - { - DamageType = damageType; - Value = value; - ThresholdType = thresholdType; - } - - public override bool Equals(Object obj) - { - return obj is DamageThreshold threshold && this == threshold; - } - public override int GetHashCode() - { - return DamageType.GetHashCode() ^ Value.GetHashCode(); - } - public static bool operator ==(DamageThreshold x, DamageThreshold y) - { - return x.DamageType == y.DamageType && x.Value == y.Value; - } - public static bool operator !=(DamageThreshold x, DamageThreshold y) - { - return !(x == y); - } - } - - public enum ThresholdType - { - None, - Destruction, - Death, - Critical, - HUDUpdate, - Breakage, - } - - public class DamageThresholdPassedEventArgs : EventArgs - { - public DamageThreshold DamageThreshold { get; } - public bool Passed { get; } - public int ExcessDamage { get; } - - public DamageThresholdPassedEventArgs(DamageThreshold threshold, bool passed, int excess) - { - DamageThreshold = threshold; - Passed = passed; - ExcessDamage = excess; - } - } - - public class DamageEventArgs : EventArgs - { - /// - /// Type of damage. - /// - public DamageType Type { get; } - - /// - /// Change in damage. - /// - public int Damage { get; } - - /// - /// The entity that damaged this one. - /// Could be null. - /// - public IEntity Source { get; } - - /// - /// The mob entity that damaged this one. - /// Could be null. - /// - public IEntity SourceMob { get; } - - public DamageEventArgs(DamageType type, int damage, IEntity source, IEntity sourceMob) - { - Type = type; - Damage = damage; - Source = source; - SourceMob = sourceMob; - } - } -} - diff --git a/Content.Server/GameObjects/Components/Damage/DamageableComponent.cs b/Content.Server/GameObjects/Components/Damage/DamageableComponent.cs deleted file mode 100644 index 613355bce2..0000000000 --- a/Content.Server/GameObjects/Components/Damage/DamageableComponent.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Content.Server.Interfaces.GameObjects; -using Content.Server.Interfaces.GameObjects.Components.Damage; -using Content.Shared.GameObjects.Components.Damage; -using Robust.Shared.GameObjects; -using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Serialization; -using Robust.Shared.ViewVariables; - -namespace Content.Server.GameObjects.Components.Damage -{ - //TODO: add support for component add/remove - - /// - /// A component that handles receiving damage and healing, - /// as well as informing other components of it. - /// - [RegisterComponent] - public class DamageableComponent : SharedDamageableComponent, IDamageableComponent - { - /// - public override string Name => "Damageable"; - - /// - /// The resistance set of this object. - /// Affects receiving damage of various types. - /// - [ViewVariables] - public ResistanceSet Resistances { get; private set; } - - [ViewVariables] - public IReadOnlyDictionary CurrentDamage => _currentDamage; - private Dictionary _currentDamage = new Dictionary(); - - [ViewVariables] - public Dictionary> Thresholds = new Dictionary>(); - - public event EventHandler DamageThresholdPassed; - public event EventHandler Damaged; - - public override ComponentState GetComponentState() - { - return new DamageComponentState(_currentDamage); - } - - public override void ExposeData(ObjectSerializer serializer) - { - base.ExposeData(serializer); - serializer.DataField(this, x => Resistances, "resistances", ResistanceSet.DefaultResistanceSet); - } - - public bool IsDead() - { - var currentDamage = _currentDamage[DamageType.Total]; - foreach (var threshold in Thresholds[DamageType.Total]) - { - if (threshold.Value <= currentDamage) - { - if (threshold.ThresholdType != ThresholdType.Death) continue; - return true; - } - } - - return false; - } - - /// - public override void Initialize() - { - base.Initialize(); - InitializeDamageType(DamageType.Total); - - foreach (var damagebehavior in Owner.GetAllComponents()) - { - AddThresholdsFrom(damagebehavior); - Damaged += damagebehavior.OnDamaged; - } - - RecalculateComponentThresholds(); - } - - /// - public void TakeDamage(DamageType damageType, int amount, IEntity source = null, IEntity sourceMob = null) - { - if (damageType == DamageType.Total) - { - foreach (DamageType e in Enum.GetValues(typeof(DamageType))) - { - if (e == damageType) continue; - TakeDamage(e, amount, source, sourceMob); - } - - return; - } - InitializeDamageType(damageType); - - int oldValue = _currentDamage[damageType]; - int oldTotalValue = -1; - - if (amount == 0) - { - return; - } - - amount = Resistances.CalculateDamage(damageType, amount); - _currentDamage[damageType] = Math.Max(0, _currentDamage[damageType] + amount); - UpdateForDamageType(damageType, oldValue); - - Damaged?.Invoke(this, new DamageEventArgs(damageType, amount, source, sourceMob)); - - if (Resistances.AppliesToTotal(damageType)) - { - oldTotalValue = _currentDamage[DamageType.Total]; - _currentDamage[DamageType.Total] = Math.Max(0, _currentDamage[DamageType.Total] + amount); - UpdateForDamageType(DamageType.Total, oldTotalValue); - } - } - - /// - public void TakeHealing(DamageType damageType, int amount, IEntity source = null, IEntity sourceMob = null) - { - if (damageType == DamageType.Total) - { - throw new ArgumentException("Cannot heal for DamageType.Total"); - } - TakeDamage(damageType, -amount, source, sourceMob); - } - - public void HealAllDamage() - { - var values = Enum.GetValues(typeof(DamageType)).Cast(); - foreach (var damageType in values) - { - if (CurrentDamage.ContainsKey(damageType) && damageType != DamageType.Total) - { - TakeHealing(damageType, CurrentDamage[damageType]); - } - } - } - - void UpdateForDamageType(DamageType damageType, int oldValue) - { - int change = _currentDamage[damageType] - oldValue; - - if (change == 0) - { - return; - } - - int changeSign = Math.Sign(change); - - foreach (var threshold in Thresholds[damageType]) - { - var value = threshold.Value; - if (((value * changeSign) > (oldValue * changeSign)) && ((value * changeSign) <= (_currentDamage[damageType] * changeSign))) - { - var excessDamage = change - value; - var typeOfDamage = damageType; - if (change - value < 0) - { - excessDamage = 0; - } - var args = new DamageThresholdPassedEventArgs(threshold, (changeSign > 0), excessDamage); - DamageThresholdPassed?.Invoke(this, args); - } - } - } - - void RecalculateComponentThresholds() - { - foreach (IOnDamageBehavior onDamageBehaviorComponent in Owner.GetAllComponents()) - { - AddThresholdsFrom(onDamageBehaviorComponent); - } - } - - void AddThresholdsFrom(IOnDamageBehavior onDamageBehavior) - { - if (onDamageBehavior == null) - { - throw new ArgumentNullException(nameof(onDamageBehavior)); - } - - List thresholds = onDamageBehavior.GetAllDamageThresholds(); - - if (thresholds == null) - return; - - foreach (DamageThreshold threshold in thresholds) - { - if (!Thresholds[threshold.DamageType].Contains(threshold)) - { - Thresholds[threshold.DamageType].Add(threshold); - } - } - - DamageThresholdPassed += onDamageBehavior.OnDamageThresholdPassed; - } - - void InitializeDamageType(DamageType damageType) - { - if (!_currentDamage.ContainsKey(damageType)) - { - _currentDamage.Add(damageType, 0); - Thresholds.Add(damageType, new List()); - } - } - } -} - diff --git a/Content.Server/GameObjects/Components/Damage/DestructibleComponent.cs b/Content.Server/GameObjects/Components/Damage/DestructibleComponent.cs index 86a7abb03f..1d62b1670e 100644 --- a/Content.Server/GameObjects/Components/Damage/DestructibleComponent.cs +++ b/Content.Server/GameObjects/Components/Damage/DestructibleComponent.cs @@ -1,114 +1,67 @@ -using System.Collections.Generic; -using Content.Server.GameObjects.EntitySystems; -using Content.Server.Interfaces.GameObjects; -using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.EntitySystems; using Robust.Server.GameObjects.EntitySystems; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Interfaces.Random; using Robust.Shared.IoC; -using Robust.Shared.Random; using Robust.Shared.Serialization; -using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Damage { /// - /// Deletes the entity once a certain damage threshold has been reached. + /// When attached to an , allows it to take damage and deletes it after taking enough damage. /// [RegisterComponent] - public class DestructibleComponent : Component, IOnDamageBehavior, IDestroyAct, IExAct + [ComponentReference(typeof(IDamageableComponent))] + public class DestructibleComponent : RuinableComponent, IDestroyAct { - #pragma warning disable 649 +#pragma warning disable 649 [Dependency] private readonly IEntitySystemManager _entitySystemManager; - #pragma warning restore 649 +#pragma warning restore 649 + + protected ActSystem ActSystem; /// public override string Name => "Destructible"; /// - /// Damage threshold calculated from the values - /// given in the prototype declaration. + /// Entity spawned upon destruction. /// - [ViewVariables] - public DamageThreshold Threshold { get; private set; } + public string SpawnOnDestroy { get; set; } - public DamageType damageType = DamageType.Total; - public int damageValue = 0; - public string spawnOnDestroy = ""; - public string destroySound = ""; - public bool destroyed = false; - - ActSystem _actSystem; + void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs) + { + if (!string.IsNullOrWhiteSpace(SpawnOnDestroy) && eventArgs.IsSpawnWreck) + { + Owner.EntityManager.SpawnEntity(SpawnOnDestroy, Owner.Transform.GridPosition); + } + } public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); - - serializer.DataField(ref damageValue, "thresholdvalue", 100); - serializer.DataField(ref damageType, "thresholdtype", DamageType.Total); - serializer.DataField(ref spawnOnDestroy, "spawnondestroy", ""); - serializer.DataField(ref destroySound, "destroysound", ""); + serializer.DataField(this, d => d.SpawnOnDestroy, "spawnondestroy", string.Empty); } public override void Initialize() { base.Initialize(); - _actSystem = _entitySystemManager.GetEntitySystem(); + ActSystem = _entitySystemManager.GetEntitySystem(); } - /// - List IOnDamageBehavior.GetAllDamageThresholds() - { - Threshold = new DamageThreshold(damageType, damageValue, ThresholdType.Destruction); - return new List() { Threshold }; - } - /// - void IOnDamageBehavior.OnDamageThresholdPassed(object obj, DamageThresholdPassedEventArgs e) + protected override void DestructionBehavior() { - if (e.Passed && e.DamageThreshold == Threshold && destroyed == false) + if (!Owner.Deleted) { - destroyed = true; var pos = Owner.Transform.GridPosition; - _actSystem.HandleDestruction(Owner, true); - if(destroySound != string.Empty) + ActSystem.HandleDestruction(Owner, + true); //This will call IDestroyAct.OnDestroy on this component (and all other components on this entity) + if (DestroySound != string.Empty) { - EntitySystem.Get().PlayAtCoords(destroySound, pos); + EntitySystem.Get().PlayAtCoords(DestroySound, pos); } - - - } - - } - - void IExAct.OnExplosion(ExplosionEventArgs eventArgs) - { - var prob = IoCManager.Resolve(); - switch (eventArgs.Severity) - { - case ExplosionSeverity.Destruction: - _actSystem.HandleDestruction(Owner, false); - break; - case ExplosionSeverity.Heavy: - var spawnWreckOnHeavy = prob.Prob(0.5f); - _actSystem.HandleDestruction(Owner, spawnWreckOnHeavy); - break; - case ExplosionSeverity.Light: - if (prob.Prob(0.4f)) - _actSystem.HandleDestruction(Owner, true); - break; - } - - } - - - void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs) - { - if (!string.IsNullOrWhiteSpace(spawnOnDestroy) && eventArgs.IsSpawnWreck) - { - Owner.EntityManager.SpawnEntity(spawnOnDestroy, Owner.Transform.GridPosition); } } } diff --git a/Content.Server/GameObjects/Components/Damage/ResistanceSet.cs b/Content.Server/GameObjects/Components/Damage/ResistanceSet.cs deleted file mode 100644 index e9a5e941a1..0000000000 --- a/Content.Server/GameObjects/Components/Damage/ResistanceSet.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Collections.Generic; -using Content.Shared.GameObjects.Components.Damage; -using Robust.Shared.Interfaces.Serialization; -using Robust.Shared.Serialization; -using Robust.Shared.ViewVariables; - -namespace Content.Server.GameObjects.Components.Damage -{ - /// - /// Resistance set used by damageable objects. - /// For each damage type, has a coefficient, damage reduction and "included in total" value. - /// - public class ResistanceSet : IExposeData - { - public static ResistanceSet DefaultResistanceSet = new ResistanceSet(); - - [ViewVariables] - private readonly Dictionary _resistances = new Dictionary(); - - public ResistanceSet() - { - foreach (DamageType damageType in Enum.GetValues(typeof(DamageType))) - { - _resistances[damageType] = new ResistanceSetSettings(); - } - } - - public void ExposeData(ObjectSerializer serializer) - { - foreach (DamageType damageType in Enum.GetValues(typeof(DamageType))) - { - var resistanceName = damageType.ToString().ToLower(); - serializer.DataReadFunction(resistanceName, new ResistanceSetSettings(), resistanceSetting => - { - _resistances[damageType] = resistanceSetting; - }); - } - } - - /// - /// Adjusts input damage with the resistance set values. - /// - /// Type of the damage. - /// Incoming amount of the damage. - /// Damage adjusted by the resistance set. - public int CalculateDamage(DamageType damageType, int amount) - { - if (amount > 0) //if it's damage, reduction applies - { - amount -= _resistances[damageType].DamageReduction; - - if (amount <= 0) - return 0; - } - - amount = (int)Math.Floor(amount * _resistances[damageType].Coefficient); - - return amount; - } - - public bool AppliesToTotal(DamageType damageType) - { - //Damage that goes straight to total (for whatever reason) never applies twice - - return damageType != DamageType.Total && _resistances[damageType].AppliesToTotal; - } - - /// - /// Settings for a specific damage type in a resistance set. - /// - public class ResistanceSetSettings : IExposeData - { - public float Coefficient { get; private set; } = 1; - public int DamageReduction { get; private set; } = 0; - public bool AppliesToTotal { get; private set; } = true; - - public void ExposeData(ObjectSerializer serializer) - { - serializer.DataField(this, x => Coefficient, "coefficient", 1); - serializer.DataField(this, x => DamageReduction, "damageReduction", 0); - serializer.DataField(this, x => AppliesToTotal, "appliesToTotal", true); - } - } - } -} diff --git a/Content.Server/GameObjects/Components/Damage/RuinableComponent.cs b/Content.Server/GameObjects/Components/Damage/RuinableComponent.cs new file mode 100644 index 0000000000..4d94d29f1a --- /dev/null +++ b/Content.Server/GameObjects/Components/Damage/RuinableComponent.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using Content.Shared.GameObjects.Components.Damage; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Damage +{ + /// + /// When attached to an , allows it to take damage and + /// "ruins" or "destroys" it after enough damage is taken. + /// + [ComponentReference(typeof(IDamageableComponent))] + public abstract class RuinableComponent : DamageableComponent + { + private DamageState _currentDamageState; + + /// + /// How much HP this component can sustain before triggering + /// . + /// + [ViewVariables(VVAccess.ReadWrite)] + public int MaxHp { get; private set; } + + /// + /// Sound played upon destruction. + /// + protected string DestroySound { get; private set; } + + public override List SupportedDamageStates => + new List {DamageState.Alive, DamageState.Dead}; + + public override DamageState CurrentDamageState => _currentDamageState; + + public override void Initialize() + { + base.Initialize(); + HealthChangedEvent += OnHealthChanged; + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(this, ruinable => ruinable.MaxHp, "maxHP", 100); + serializer.DataField(this, ruinable => ruinable.DestroySound, "destroySound", string.Empty); + } + + public override void OnRemove() + { + base.OnRemove(); + HealthChangedEvent -= OnHealthChanged; + } + + private void OnHealthChanged(HealthChangedEventArgs e) + { + if (CurrentDamageState != DamageState.Dead && TotalDamage >= MaxHp) + { + PerformDestruction(); + } + } + + /// + /// Destroys the Owner , setting + /// to + /// + /// + protected void PerformDestruction() + { + _currentDamageState = DamageState.Dead; + + if (!Owner.Deleted && DestroySound != string.Empty) + { + var pos = Owner.Transform.GridPosition; + EntitySystem.Get().PlayAtCoords(DestroySound, pos); + } + + DestructionBehavior(); + } + + protected abstract void DestructionBehavior(); + } +} 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 f74eecb284..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.Server.GameObjects.Components.Mobs; +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,8 +56,14 @@ namespace Content.Server.GameObjects.Components.Disposal return false; } + if (!entity.TryGetComponent(out ICollidableComponent? collidable) || + !collidable.CanCollide) + { + return false; + } + return entity.HasComponent() || - entity.HasComponent(); + entity.HasComponent(); } public bool TryInsert(IEntity entity) @@ -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 29d5132925..94dccce0a7 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs @@ -1,9 +1,9 @@ #nullable enable using System; using System.Linq; -using Content.Server.GameObjects.EntitySystems; using Content.Shared.GameObjects.Components.Disposal; using Content.Shared.GameObjects.Verbs; +using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces; using Robust.Server.Console; using Robust.Server.GameObjects; @@ -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 89a6049b66..2e420ef91e 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs @@ -3,12 +3,13 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; -using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects.Components.Items; +using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Disposal; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.Verbs; @@ -85,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] @@ -121,8 +122,14 @@ namespace Content.Server.GameObjects.Components.Disposal return false; } + if (!entity.TryGetComponent(out ICollidableComponent? collidable) || + !collidable.CanCollide) + { + return false; + } + if (!entity.HasComponent() && - !entity.HasComponent()) + !entity.HasComponent()) { return false; } @@ -152,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); } @@ -174,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; } @@ -266,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; } @@ -345,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; } @@ -481,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; } @@ -491,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; } @@ -523,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) { @@ -552,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; } @@ -568,7 +575,7 @@ namespace Content.Server.GameObjects.Components.Disposal return true; } - bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) + async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { return TryDrop(eventArgs.User, eventArgs.Using); } 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 ade77fb937..6f8f94d217 100644 --- a/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs +++ b/Content.Server/GameObjects/Components/Doors/AirlockComponent.cs @@ -1,5 +1,6 @@ using System; using System.Threading; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.Interactable; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.Components.VendingMachines; @@ -379,7 +380,7 @@ namespace Content.Server.GameObjects.Components.Doors return _powerReceiver.Powered; } - public bool InteractUsing(InteractUsingEventArgs eventArgs) + public async Task InteractUsing(InteractUsingEventArgs eventArgs) { if (!eventArgs.Using.TryGetComponent(out var tool)) return false; @@ -397,22 +398,27 @@ namespace Content.Server.GameObjects.Components.Doors } } - if (!tool.UseTool(eventArgs.User, Owner, ToolQuality.Prying)) return false; - - if (IsBolted()) + bool AirlockCheck() { - var notify = IoCManager.Resolve(); - notify.PopupMessage(Owner, eventArgs.User, - Loc.GetString("The airlock's bolts prevent it from being forced!")); + if (IsBolted()) + { + var notify = IoCManager.Resolve(); + notify.PopupMessage(Owner, eventArgs.User, + Loc.GetString("The airlock's bolts prevent it from being forced!")); + return false; + } + + if (IsPowered()) + { + var notify = IoCManager.Resolve(); + notify.PopupMessage(Owner, eventArgs.User, Loc.GetString("The powered motors block your efforts!")); + return false; + } + return true; } - if (IsPowered()) - { - var notify = IoCManager.Resolve(); - notify.PopupMessage(Owner, eventArgs.User, Loc.GetString("The powered motors block your efforts!")); - return true; - } + if (!await tool.UseTool(eventArgs.User, Owner, 3f, ToolQuality.Prying, AirlockCheck)) return false; if (State == DoorState.Closed) Open(); diff --git a/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs b/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs index bcd49e5c89..ba2bc39377 100644 --- a/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs +++ b/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs @@ -3,9 +3,10 @@ using System.Linq; using System.Threading; using Content.Server.GameObjects.Components.Access; using Content.Server.GameObjects.Components.Atmos; -using Content.Server.GameObjects.Components.Damage; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Mobs; +using Content.Shared.Damage; +using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Doors; using Content.Shared.GameObjects.Components.Movement; @@ -58,8 +59,7 @@ namespace Content.Server.GameObjects.Components.Doors private const float DoorStunTime = 5f; protected bool Safety = true; - [ViewVariables] - private bool _occludes; + [ViewVariables] private bool _occludes; public override void ExposeData(ObjectSerializer serializer) { @@ -111,18 +111,25 @@ namespace Content.Server.GameObjects.Components.Doors { return; } - if (entity.HasComponent(typeof(SpeciesComponent))) + + // Disabled because it makes it suck hard to walk through double doors. + + if (entity.HasComponent()) { if (!entity.TryGetComponent(out var mover)) return; + /* // TODO: temporary hack to fix the physics system raising collision events akwardly. // E.g. when moving parallel to a door by going off the side of a wall. var (walking, sprinting) = mover.VelocityDir; // Also TODO: walking and sprint dir are added together here // instead of calculating their contribution correctly. var dotProduct = Vector2.Dot((sprinting + walking).Normalized, (entity.Transform.WorldPosition - Owner.Transform.WorldPosition).Normalized); - if (dotProduct <= -0.9f) + if (dotProduct <= -0.85f) TryOpen(entity); + */ + + TryOpen(entity); } } @@ -144,6 +151,7 @@ namespace Content.Server.GameObjects.Components.Doors { return true; } + return accessReader.IsAllowed(user); } @@ -204,6 +212,7 @@ namespace Content.Server.GameObjects.Components.Doors { return true; } + return accessReader.IsAllowed(user); } @@ -214,6 +223,7 @@ namespace Content.Server.GameObjects.Components.Doors Deny(); return; } + Close(); } @@ -228,7 +238,7 @@ namespace Content.Server.GameObjects.Components.Doors foreach (var e in collidesWith) { if (!e.TryGetComponent(out StunnableComponent stun) - || !e.TryGetComponent(out DamageableComponent damage) + || !e.TryGetComponent(out IDamageableComponent damage) || !e.TryGetComponent(out ICollidableComponent otherBody) || !Owner.TryGetComponent(out ICollidableComponent body)) continue; @@ -238,10 +248,11 @@ namespace Content.Server.GameObjects.Components.Doors if (percentage < 0.1f) continue; - damage.TakeDamage(DamageType.Brute, DoorCrushDamage); + damage.ChangeDamage(DamageType.Blunt, DoorCrushDamage, false, Owner); stun.Paralyze(DoorStunTime); hitSomeone = true; } + // If we hit someone, open up after stun (opens right when stun ends) if (hitSomeone) { diff --git a/Content.Server/GameObjects/Components/Explosion/ExplosiveComponent.cs b/Content.Server/GameObjects/Components/Explosion/ExplosiveComponent.cs index f23c611e24..39b49c13fb 100644 --- a/Content.Server/GameObjects/Components/Explosion/ExplosiveComponent.cs +++ b/Content.Server/GameObjects/Components/Explosion/ExplosiveComponent.cs @@ -1,5 +1,6 @@ using Content.Server.Explosions; using Content.Server.GameObjects.EntitySystems; +using Content.Shared.GameObjects.EntitySystems; using Robust.Shared.GameObjects; using Robust.Shared.Serialization; diff --git a/Content.Server/GameObjects/Components/Explosion/FlashExplosiveComponent.cs b/Content.Server/GameObjects/Components/Explosion/FlashExplosiveComponent.cs index a547e5f0b3..c8d6120edd 100644 --- a/Content.Server/GameObjects/Components/Explosion/FlashExplosiveComponent.cs +++ b/Content.Server/GameObjects/Components/Explosion/FlashExplosiveComponent.cs @@ -1,6 +1,7 @@ using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Weapon; using Content.Server.GameObjects.EntitySystems; +using Content.Shared.GameObjects.EntitySystems; using Robust.Server.GameObjects.EntitySystems; using Robust.Shared.Containers; using Robust.Shared.GameObjects; diff --git a/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs b/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs index f0e6c07018..11c3281a94 100644 --- a/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs +++ b/Content.Server/GameObjects/Components/Fluids/BucketComponent.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.Chemistry; using Content.Shared.Chemistry; using Content.Shared.Interfaces; @@ -70,7 +71,7 @@ namespace Content.Server.GameObjects.Components.Fluids return true; } - public bool InteractUsing(InteractUsingEventArgs eventArgs) + public async Task InteractUsing(InteractUsingEventArgs eventArgs) { if (!eventArgs.Using.TryGetComponent(out MopComponent mopComponent)) { 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 ea75c7f382..69fe0be734 100644 --- a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs @@ -8,17 +8,19 @@ using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Movement; using Content.Server.GameObjects.EntitySystems.Click; using Content.Server.Interfaces.GameObjects.Components.Interaction; +using Content.Shared.GameObjects.Components.Body; using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Shared.GameObjects.Components.Items; using Content.Shared.GameObjects.Components.Mobs; -using Content.Shared.Health.BodySystem; -using Content.Shared.Physics; +using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.Physics.Pull; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.EntitySystemMessages; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; +using Robust.Shared.GameObjects.Components.Transform; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Network; using Robust.Shared.IoC; @@ -140,11 +142,11 @@ namespace Content.Server.GameObjects.Components.GUI } } - public bool PutInHand(ItemComponent item) + public bool PutInHand(ItemComponent item, bool mobCheck = true) { foreach (var hand in ActivePriorityEnumerable()) { - if (PutInHand(item, hand, false)) + if (PutInHand(item, hand, false, mobCheck)) { OnItemChanged?.Invoke(); @@ -155,10 +157,10 @@ namespace Content.Server.GameObjects.Components.GUI return false; } - public bool PutInHand(ItemComponent item, string index, bool fallback = true) + public bool PutInHand(ItemComponent item, string index, bool fallback = true, bool mobChecks = true) { var hand = GetHand(index); - if (!CanPutInHand(item, index) || hand == null) + if (!CanPutInHand(item, index, mobChecks) || hand == null) { return fallback && PutInHand(item); } @@ -176,19 +178,23 @@ namespace Content.Server.GameObjects.Components.GUI return success; } - public void PutInHandOrDrop(ItemComponent item) + public void PutInHandOrDrop(ItemComponent item, bool mobCheck = true) { - if (!PutInHand(item)) + if (!PutInHand(item, mobCheck)) { item.Owner.Transform.GridPosition = Owner.Transform.GridPosition; } } - public bool CanPutInHand(ItemComponent item) + public bool CanPutInHand(ItemComponent item, bool mobCheck = true) { + if (mobCheck && !ActionBlockerSystem.CanPickup(Owner)) + return false; + foreach (var handName in ActivePriorityEnumerable()) { - if (CanPutInHand(item, handName)) + // We already did a mobCheck, so let's not waste cycles. + if (CanPutInHand(item, handName, false)) { return true; } @@ -197,8 +203,11 @@ namespace Content.Server.GameObjects.Components.GUI return false; } - public bool CanPutInHand(ItemComponent item, string index) + public bool CanPutInHand(ItemComponent item, string index, bool mobCheck = true) { + if (mobCheck && !ActionBlockerSystem.CanPickup(Owner)) + return false; + return GetHand(index)?.Container.CanInsert(item.Owner) == true; } @@ -284,17 +293,17 @@ namespace Content.Server.GameObjects.Components.GUI return Drop(slot, coords, doMobChecks); } - public bool Drop(string slot, bool doMobChecks = true) + public bool Drop(string slot, bool mobChecks = true) { var hand = GetHand(slot); - if (!CanDrop(slot) || hand?.Entity == null) + if (!CanDrop(slot, mobChecks) || hand?.Entity == null) { return false; } var item = hand.Entity.GetComponent(); - if (!DroppedInteraction(item, doMobChecks)) + if (!DroppedInteraction(item, mobChecks)) return false; if (!hand.Container.Remove(hand.Entity)) @@ -321,7 +330,7 @@ namespace Content.Server.GameObjects.Components.GUI return true; } - public bool Drop(IEntity entity, bool doMobChecks = true) + public bool Drop(IEntity entity, bool mobChecks = true) { if (entity == null) { @@ -333,7 +342,7 @@ namespace Content.Server.GameObjects.Components.GUI throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity)); } - return Drop(slot, doMobChecks); + return Drop(slot, mobChecks); } public bool Drop(string slot, BaseContainer targetContainer, bool doMobChecks = true) @@ -409,13 +418,15 @@ namespace Content.Server.GameObjects.Components.GUI /// /// True if there is an item in the slot and it can be dropped, false otherwise. /// - public bool CanDrop(string name) + public bool CanDrop(string name, bool mobCheck = true) { var hand = GetHand(name); - if (hand?.Entity == null) - { + + if (mobCheck && !ActionBlockerSystem.CanDrop(Owner)) + return false; + + if (hand?.Entity == null) return false; - } return hand.Container.CanRemove(hand.Entity); } @@ -537,15 +548,9 @@ namespace Content.Server.GameObjects.Components.GUI return; } - var isOwnerContained = ContainerHelpers.TryGetContainer(Owner, out var ownerContainer); - var isPullableContained = ContainerHelpers.TryGetContainer(pullable.Owner, out var pullableContainer); - - if (isOwnerContained || isPullableContained) + if (!Owner.IsInSameOrNoContainer(pullable.Owner)) { - if (ownerContainer != pullableContainer) - { - return; - } + return; } if (IsPulling) @@ -554,10 +559,8 @@ namespace Content.Server.GameObjects.Components.GUI } PulledObject = pullable.Owner.GetComponent(); - var controller = PulledObject!.EnsureController(); - controller!.StartPull(Owner.GetComponent()); - - AddPullingStatuses(); + var controller = PulledObject.EnsureController(); + controller.StartPull(Owner.GetComponent()); } public void MovePulledObject(GridCoordinates puller, GridCoordinates to) @@ -569,6 +572,46 @@ namespace Content.Server.GameObjects.Components.GUI } } + private void MoveEvent(MoveEvent moveEvent) + { + if (moveEvent.Sender != Owner) + { + return; + } + + if (!IsPulling) + { + return; + } + + PulledObject!.WakeBody(); + } + + public override void HandleMessage(ComponentMessage message, IComponent? component) + { + base.HandleMessage(message, component); + + if (!(message is PullMessage pullMessage) || + pullMessage.Puller.Owner != Owner) + { + return; + } + + switch (message) + { + case PullStartedMessage msg: + Owner.EntityManager.EventBus.SubscribeEvent(EventSource.Local, this, MoveEvent); + + AddPullingStatuses(msg.Pulled.Owner); + break; + case PullStoppedMessage msg: + Owner.EntityManager.EventBus.UnsubscribeEvent(EventSource.Local, this); + + RemovePullingStatuses(msg.Pulled.Owner); + break; + } + } + public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null) { base.HandleNetworkMessage(message, channel, session); @@ -668,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; } @@ -679,42 +722,34 @@ namespace Content.Server.GameObjects.Components.GUI } } - private void AddPullingStatuses() + private void AddPullingStatuses(IEntity pulled) { - if (PulledObject?.Owner != null && - PulledObject.Owner.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"); } } - private void RemovePullingStatuses() + private void RemovePullingStatuses(IEntity pulled) { - if (PulledObject?.Owner != null && - PulledObject.Owner.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); } } - public override void StopPull() - { - RemovePullingStatuses(); - base.StopPull(); - } - void IBodyPartAdded.BodyPartAdded(BodyPartAddedEventArgs eventArgs) { if (eventArgs.Part.PartType != BodyPartType.Hand) diff --git a/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs b/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs index 1d1e52b6a2..57ab715eb5 100644 --- a/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs @@ -173,9 +173,10 @@ namespace Content.Server.GameObjects.Components.GUI /// /// The slot to put the item in. /// The item to insert into the slot. + /// Whether to perform an ActionBlocker check to the entity. /// The translated reason why the item cannot be equipped, if this function returns false. Can be null. /// True if the item was successfully inserted, false otherwise. - public bool Equip(Slots slot, ItemComponent item, out string reason) + public bool Equip(Slots slot, ItemComponent item, bool mobCheck, out string reason) { if (item == null) { @@ -183,7 +184,7 @@ namespace Content.Server.GameObjects.Components.GUI "Clothing must be passed here. To remove some clothing from a slot, use Unequip()"); } - if (!CanEquip(slot, item, out reason)) + if (!CanEquip(slot, item, mobCheck, out reason)) { return false; } @@ -203,9 +204,9 @@ namespace Content.Server.GameObjects.Components.GUI return true; } - public bool Equip(Slots slot, ItemComponent item) => Equip(slot, item, out var _); + public bool Equip(Slots slot, ItemComponent item, bool mobCheck = true) => Equip(slot, item, mobCheck, out var _); - public bool Equip(Slots slot, IEntity entity) => Equip(slot, entity.GetComponent()); + public bool Equip(Slots slot, IEntity entity, bool mobCheck = true) => Equip(slot, entity.GetComponent(), mobCheck); /// /// Checks whether an item can be put in the specified slot. @@ -214,12 +215,12 @@ namespace Content.Server.GameObjects.Components.GUI /// The item to check for. /// The translated reason why the item cannot be equiped, if this function returns false. Can be null. /// True if the item can be inserted into the specified slot. - public bool CanEquip(Slots slot, ItemComponent item, out string reason) + public bool CanEquip(Slots slot, ItemComponent item, bool mobCheck, out string reason) { var pass = false; reason = null; - if (!ActionBlockerSystem.CanEquip(Owner)) + if (mobCheck && !ActionBlockerSystem.CanEquip(Owner)) return false; if (item is ClothingComponent clothing) @@ -248,18 +249,19 @@ namespace Content.Server.GameObjects.Components.GUI return pass && _slotContainers[slot].CanInsert(item.Owner); } - public bool CanEquip(Slots slot, ItemComponent item) => CanEquip(slot, item, out var _); + public bool CanEquip(Slots slot, ItemComponent item, bool mobCheck = true) => CanEquip(slot, item, mobCheck, out var _); - public bool CanEquip(Slots slot, IEntity entity) => CanEquip(slot, entity.GetComponent()); + public bool CanEquip(Slots slot, IEntity entity, bool mobCheck = true) => CanEquip(slot, entity.GetComponent(), mobCheck); /// /// Drops the item in a slot. /// /// The slot to drop the item from. /// True if an item was dropped, false otherwise. - public bool Unequip(Slots slot) + /// Whether to perform an ActionBlocker check to the entity. + public bool Unequip(Slots slot, bool mobCheck = true) { - if (!CanUnequip(slot)) + if (!CanUnequip(slot, mobCheck)) { return false; } @@ -288,16 +290,17 @@ namespace Content.Server.GameObjects.Components.GUI /// Checks whether an item can be dropped from the specified slot. /// /// The slot to check for. + /// Whether to perform an ActionBlocker check to the entity. /// /// True if there is an item in the slot and it can be dropped, false otherwise. /// - public bool CanUnequip(Slots slot) + public bool CanUnequip(Slots slot, bool mobCheck = true) { - if (!ActionBlockerSystem.CanUnequip(Owner)) + if (mobCheck && !ActionBlockerSystem.CanUnequip(Owner)) return false; - var InventorySlot = _slotContainers[slot]; - return InventorySlot.ContainedEntity != null && InventorySlot.CanRemove(InventorySlot.ContainedEntity); + var inventorySlot = _slotContainers[slot]; + return inventorySlot.ContainedEntity != null && inventorySlot.CanRemove(inventorySlot.ContainedEntity); } /// @@ -398,7 +401,7 @@ namespace Content.Server.GameObjects.Components.GUI if (activeHand != null && activeHand.Owner.TryGetComponent(out ItemComponent clothing)) { hands.Drop(hands.ActiveHand); - if (!Equip(msg.Inventoryslot, clothing, out var reason)) + if (!Equip(msg.Inventoryslot, clothing, true, out var reason)) { hands.PutInHand(clothing); @@ -434,7 +437,7 @@ namespace Content.Server.GameObjects.Components.GUI var activeHand = hands.GetActiveHand; if (activeHand != null && GetSlotItem(msg.Inventoryslot) == null) { - var canEquip = CanEquip(msg.Inventoryslot, activeHand, out var reason); + var canEquip = CanEquip(msg.Inventoryslot, activeHand, true, out var reason); _hoverEntity = new KeyValuePair(msg.Inventoryslot, (activeHand.Owner.Uid, canEquip)); Dirty(); diff --git a/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs b/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs index e045fd2b5a..0363fee424 100644 --- a/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/StrippableComponent.cs @@ -137,7 +137,7 @@ namespace Content.Server.GameObjects.Components.GUI return false; } - if (!inventory.CanEquip(slot, item)) + if (!inventory.CanEquip(slot, item, false)) { _notifyManager.PopupMessageCursor(user, Loc.GetString("{0:They} cannot equip that there!", Owner)); return false; @@ -162,7 +162,7 @@ namespace Content.Server.GameObjects.Components.GUI if (result != DoAfterStatus.Finished) return; userHands.Drop(item!.Owner, false); - inventory.Equip(slot, item!.Owner); + inventory.Equip(slot, item!.Owner, false); UpdateSubscribed(); } @@ -202,7 +202,7 @@ namespace Content.Server.GameObjects.Components.GUI return false; } - if (!hands.CanPutInHand(item, hand)) + if (!hands.CanPutInHand(item, hand, false)) { _notifyManager.PopupMessageCursor(user, Loc.GetString("{0:They} cannot put that there!", Owner)); return false; @@ -227,7 +227,7 @@ namespace Content.Server.GameObjects.Components.GUI if (result != DoAfterStatus.Finished) return; userHands.Drop(hand, false); - hands.PutInHand(item, hand, false); + hands.PutInHand(item!, hand, false, false); UpdateSubscribed(); } @@ -253,7 +253,7 @@ namespace Content.Server.GameObjects.Components.GUI return false; } - if (!inventory.CanUnequip(slot)) + if (!inventory.CanUnequip(slot, false)) { _notifyManager.PopupMessageCursor(user, Loc.GetString("{0:They} cannot unequip that!", Owner)); return false; @@ -277,7 +277,7 @@ namespace Content.Server.GameObjects.Components.GUI if (result != DoAfterStatus.Finished) return; var item = inventory.GetSlotItem(slot); - inventory.Unequip(slot); + inventory.Unequip(slot, false); userHands.PutInHandOrDrop(item); UpdateSubscribed(); } @@ -304,7 +304,7 @@ namespace Content.Server.GameObjects.Components.GUI return false; } - if (!hands.CanDrop(hand)) + if (!hands.CanDrop(hand, false)) { _notifyManager.PopupMessageCursor(user, Loc.GetString("{0:They} cannot drop that!", Owner)); return false; @@ -329,7 +329,7 @@ namespace Content.Server.GameObjects.Components.GUI var item = hands.GetItem(hand); hands.Drop(hand, false); - userHands.PutInHandOrDrop(item); + userHands.PutInHandOrDrop(item!); UpdateSubscribed(); } @@ -364,8 +364,6 @@ namespace Content.Server.GameObjects.Components.GUI else TakeItemFromHands(user, handMessage.Hand); break; - default: - break; } } } diff --git a/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs b/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs index 637933dd06..55e6acfe92 100644 --- a/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs +++ b/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs @@ -1,10 +1,12 @@ -using Content.Server.GameObjects.Components.Damage; +using System.Threading.Tasks; +using Content.Server.GameObjects.Components.Damage; using Content.Server.GameObjects.Components.Interactable; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; using Content.Shared.GameObjects.Components.Gravity; using Content.Shared.GameObjects.Components.Interactable; +using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.UserInterface; @@ -18,7 +20,7 @@ using Robust.Shared.Serialization; namespace Content.Server.GameObjects.Components.Gravity { [RegisterComponent] - public class GravityGeneratorComponent: SharedGravityGeneratorComponent, IInteractUsing, IBreakAct, IInteractHand + public class GravityGeneratorComponent : SharedGravityGeneratorComponent, IInteractUsing, IBreakAct, IInteractHand { private BoundUserInterface _userInterface; @@ -97,19 +99,17 @@ namespace Content.Server.GameObjects.Components.Gravity return true; } - public bool InteractUsing(InteractUsingEventArgs eventArgs) + public async Task InteractUsing(InteractUsingEventArgs eventArgs) { if (!eventArgs.Using.TryGetComponent(out WelderComponent tool)) return false; - if (!tool.UseTool(eventArgs.User, Owner, ToolQuality.Welding, 5f)) + if (!await tool.UseTool(eventArgs.User, Owner, 2f, ToolQuality.Welding, 5f)) return false; // Repair generator - var damageable = Owner.GetComponent(); var breakable = Owner.GetComponent(); - damageable.HealAllDamage(); - breakable.broken = false; + breakable.FixAllDamage(); _intact = true; var notifyManager = IoCManager.Resolve(); @@ -130,13 +130,16 @@ namespace Content.Server.GameObjects.Components.Gravity if (!Intact) { MakeBroken(); - } else if (!Powered) + } + else if (!Powered) { MakeUnpowered(); - } else if (!SwitchedOn) + } + else if (!SwitchedOn) { MakeOff(); - } else + } + else { MakeOn(); } diff --git a/Content.Server/GameObjects/Components/Healing/HealingComponent.cs b/Content.Server/GameObjects/Components/Healing/HealingComponent.cs deleted file mode 100644 index 65bbf4154a..0000000000 --- a/Content.Server/GameObjects/Components/Healing/HealingComponent.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Content.Server.GameObjects.Components.Damage; -using Content.Server.GameObjects.Components.Stack; -using Content.Server.Utility; -using Content.Shared.GameObjects.Components.Damage; -using Content.Shared.Interfaces.GameObjects.Components; -using Robust.Shared.GameObjects; -using Robust.Shared.Serialization; - -namespace Content.Server.GameObjects.Components.Healing -{ - [RegisterComponent] - public class HealingComponent : Component, IAfterInteract, IUse - { - public override string Name => "Healing"; - - public int Heal = 100; - public DamageType Damage = DamageType.Brute; - - public override void ExposeData(ObjectSerializer serializer) - { - base.ExposeData(serializer); - - serializer.DataField(ref Heal, "heal", 100); - serializer.DataField(ref Damage, "damage", DamageType.Brute); - } - - void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) - { - if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return; - - if (eventArgs.Target == null) - { - return; - } - - if (!eventArgs.Target.TryGetComponent(out DamageableComponent damagecomponent)) return; - if (Owner.TryGetComponent(out StackComponent stackComponent)) - { - if (!stackComponent.Use(1)) - { - Owner.Delete(); - return; - } - - damagecomponent.TakeHealing(Damage, Heal); - return; - } - damagecomponent.TakeHealing(Damage, Heal); - Owner.Delete(); - } - - bool IUse.UseEntity(UseEntityEventArgs eventArgs) - { - if (!eventArgs.User.TryGetComponent(out DamageableComponent damagecomponent)) return false; - if (Owner.TryGetComponent(out StackComponent stackComponent)) - { - if (!stackComponent.Use(1)) - { - Owner.Delete(); - return false; - } - - damagecomponent.TakeHealing(Damage, Heal); - return false; - } - damagecomponent.TakeHealing(Damage, Heal); - Owner.Delete(); - return false; - } - } -} diff --git a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs index f8b7bf1d47..e1a0bd2415 100644 --- a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.GUI; +using System.Threading.Tasks; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Clothing; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Power; @@ -57,7 +58,7 @@ namespace Content.Server.GameObjects.Components.Interactable [ViewVariables] public bool Activated { get; private set; } - bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) + async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { if (!eventArgs.Using.HasComponent()) return false; @@ -276,7 +277,7 @@ namespace Content.Server.GameObjects.Components.Interactable return; } - var cell = Owner.EntityManager.SpawnEntity("PowerCellSmallHyper", Owner.Transform.GridPosition); + var cell = Owner.EntityManager.SpawnEntity("PowerCellSmallStandard", Owner.Transform.GridPosition); _cellContainer.Insert(cell); } } diff --git a/Content.Server/GameObjects/Components/Interactable/TilePryingComponent.cs b/Content.Server/GameObjects/Components/Interactable/TilePryingComponent.cs index 2d3d59884b..b2d6cbb98c 100644 --- a/Content.Server/GameObjects/Components/Interactable/TilePryingComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/TilePryingComponent.cs @@ -34,7 +34,7 @@ namespace Content.Server.GameObjects.Components.Interactable serializer.DataField(ref _toolComponentNeeded, "toolComponentNeeded", true); } - public void TryPryTile(IEntity user, GridCoordinates clickLocation) + public async void TryPryTile(IEntity user, GridCoordinates clickLocation) { if (!Owner.TryGetComponent(out var tool) && _toolComponentNeeded) return; @@ -51,7 +51,7 @@ namespace Content.Server.GameObjects.Components.Interactable if (!tileDef.CanCrowbar) return; - if (_toolComponentNeeded && !tool.UseTool(user, null, ToolQuality.Prying)) + if (_toolComponentNeeded && !await tool!.UseTool(user, null, 0f, ToolQuality.Prying)) return; var underplating = _tileDefinitionManager["underplating"]; 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/Interactable/ToolComponent.cs b/Content.Server/GameObjects/Components/Interactable/ToolComponent.cs index 0b1b6ad0ab..7bc3c0739a 100644 --- a/Content.Server/GameObjects/Components/Interactable/ToolComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/ToolComponent.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; +using Content.Server.GameObjects.EntitySystems.DoAfter; using Content.Shared.Audio; using Content.Shared.GameObjects.Components.Interactable; using Content.Shared.GameObjects.EntitySystems; @@ -89,11 +91,31 @@ namespace Content.Server.GameObjects.Components.Interactable serializer.DataField(this, collection => UseSoundCollection, "useSoundCollection", string.Empty); } - public virtual bool UseTool(IEntity user, IEntity target, ToolQuality toolQualityNeeded) + public virtual async Task UseTool(IEntity user, IEntity target, float doAfterDelay, ToolQuality toolQualityNeeded, Func doAfterCheck = null) { if (!HasQuality(toolQualityNeeded) || !ActionBlockerSystem.CanInteract(user)) return false; + if (doAfterDelay > 0f) + { + var doAfterSystem = EntitySystem.Get(); + + var doAfterArgs = new DoAfterEventArgs(user, doAfterDelay / SpeedModifier, default, target) + { + ExtraCheck = doAfterCheck, + BreakOnDamage = false, // TODO: Change this to true once breathing is fixed. + BreakOnStun = true, + BreakOnTargetMove = true, + BreakOnUserMove = true, + NeedHand = true, + }; + + var result = await doAfterSystem.DoAfter(doAfterArgs); + + if (result == DoAfterStatus.Cancelled) + return false; + } + PlayUseSound(); return true; diff --git a/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs b/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs index f85e7b64ea..785e19e6b0 100644 --- a/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.Threading.Tasks; using Content.Server.Atmos; using Content.Server.GameObjects.Components.Chemistry; using Content.Server.GameObjects.Components.Items.Storage; @@ -97,16 +98,37 @@ namespace Content.Server.GameObjects.Components.Interactable return new WelderComponentState(FuelCapacity, Fuel, WelderLit); } - public override bool UseTool(IEntity user, IEntity target, ToolQuality toolQualityNeeded) + public override async Task UseTool(IEntity user, IEntity target, float doAfterDelay, ToolQuality toolQualityNeeded, Func? doAfterCheck = null) { - var canUse = base.UseTool(user, target, toolQualityNeeded); + bool ExtraCheck() + { + var extraCheck = doAfterCheck?.Invoke() ?? true; + + if (!CanWeld(DefaultFuelCost)) + { + _notifyManager.PopupMessage(target, user, "Can't weld!"); + + return false; + } + + return extraCheck; + } + + var canUse = await base.UseTool(user, target, doAfterDelay, toolQualityNeeded, ExtraCheck); return toolQualityNeeded.HasFlag(ToolQuality.Welding) ? canUse && TryWeld(DefaultFuelCost, user) : canUse; } - public bool UseTool(IEntity user, IEntity target, ToolQuality toolQualityNeeded, float fuelConsumed) + public async Task UseTool(IEntity user, IEntity target, float doAfterDelay, ToolQuality toolQualityNeeded, float fuelConsumed, Func? doAfterCheck = null) { - return base.UseTool(user, target, toolQualityNeeded) && TryWeld(fuelConsumed, user); + bool ExtraCheck() + { + var extraCheck = doAfterCheck?.Invoke() ?? true; + + return extraCheck && CanWeld(fuelConsumed); + } + + return await base.UseTool(user, target, doAfterDelay, toolQualityNeeded, ExtraCheck) && TryWeld(fuelConsumed, user); } private bool TryWeld(float value, IEntity? user = null, bool silent = false) @@ -236,11 +258,12 @@ namespace Content.Server.GameObjects.Components.Interactable if (TryWeld(5, victim, silent: true)) { PlaySoundCollection(WeldSoundCollection); - chat.EntityMe(victim, Loc.GetString("welds {0:their} every orifice closed! It looks like {0:theyre} trying to commit suicide!", victim)); //TODO: theyre macro + chat.EntityMe(victim, Loc.GetString("welds {0:their} every orifice closed! It looks like {0:theyre} trying to commit suicide!", victim)); return SuicideKind.Heat; } + chat.EntityMe(victim, Loc.GetString("bashes {0:themselves} with the {1}!", victim, Owner.Name)); - return SuicideKind.Brute; + return SuicideKind.Blunt; } public void SolutionChanged(SolutionChangeEventArgs eventArgs) diff --git a/Content.Server/GameObjects/Components/Items/Clothing/ClothingComponent.cs b/Content.Server/GameObjects/Components/Items/Clothing/ClothingComponent.cs index a94c5982bc..7de3236ec9 100644 --- a/Content.Server/GameObjects/Components/Items/Clothing/ClothingComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Clothing/ClothingComponent.cs @@ -112,7 +112,7 @@ namespace Content.Server.GameObjects.Components.Items.Clothing private bool TryEquip(InventoryComponent inv, Slots slot, IEntity user) { - if (!inv.Equip(slot, this, out var reason)) + if (!inv.Equip(slot, this, true, out var reason)) { if (reason != null) _serverNotifyManager.PopupMessage(Owner, user, reason); diff --git a/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs index 73d1fd09ca..3bb2cd1161 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs @@ -1,10 +1,13 @@ using System; using System.Linq; +using System.Threading.Tasks; +using Content.Server.GameObjects.Components.Body; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Interactable; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.EntitySystems; using Content.Shared.GameObjects.Components.Interactable; +using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.Components.Storage; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.Verbs; @@ -168,7 +171,8 @@ namespace Content.Server.GameObjects.Components.Items.Storage continue; // only items that can be stored in an inventory, or a mob, can be eaten by a locker - if (!entity.HasComponent() && !entity.HasComponent()) + if (!entity.HasComponent() && + !entity.HasComponent()) continue; if (!AddToContents(entity)) @@ -356,7 +360,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage return Contents.CanInsert(entity); } - bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) + async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { if (Open) @@ -374,10 +378,9 @@ namespace Content.Server.GameObjects.Components.Items.Storage if (!eventArgs.Using.TryGetComponent(out WelderComponent tool)) return false; - if (!tool.UseTool(eventArgs.User, Owner, ToolQuality.Welding, 1f)) + if (!await tool.UseTool(eventArgs.User, Owner, 1f, ToolQuality.Welding, 1f)) return false; - IsWeldedShut ^= true; return true; } diff --git a/Content.Server/GameObjects/Components/Items/Storage/ItemComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/ItemComponent.cs index 67eca347f2..a82183da4f 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/ItemComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/ItemComponent.cs @@ -91,7 +91,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage return false; } - if (Owner.TryGetComponent(out CollidableComponent physics) && + if (Owner.TryGetComponent(out ICollidableComponent physics) && physics.Anchored) { return false; diff --git a/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs index 5359953f39..24272ed72c 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces.GameObjects.Components.Items; @@ -100,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; @@ -163,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}"); @@ -185,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; @@ -316,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); } @@ -381,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; } @@ -435,7 +436,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage /// /// /// true if inserted, false otherwise - bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) + async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { Logger.DebugS(LoggerName, $"Storage (UID {Owner.Uid}) attacked by user (UID {eventArgs.User.Uid}) with entity (UID {eventArgs.Using.Uid})."); @@ -505,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/Kitchen/MicrowaveComponent.cs b/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs index 51b79141f9..470fb19ec6 100644 --- a/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs +++ b/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs @@ -1,18 +1,21 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using Content.Server.GameObjects.Components.Body; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.ViewVariables; using Content.Server.GameObjects.Components.Chemistry; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.EntitySystems; -using Content.Server.Health.BodySystem; using Content.Server.Interfaces; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameObjects; using Content.Shared.Chemistry; using Content.Shared.GameObjects.Components.Power; -using Content.Shared.Health.BodySystem; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Kitchen; @@ -23,14 +26,12 @@ 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.Systems; +using Content.Shared.GameObjects.Components.Body; using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Serialization; using Robust.Shared.Timers; -using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.Kitchen { @@ -206,7 +207,7 @@ namespace Content.Server.GameObjects.Components.Kitchen _userInterface.Open(actor.playerSession); } - public bool InteractUsing(InteractUsingEventArgs eventArgs) + public async Task InteractUsing(InteractUsingEventArgs eventArgs) { if (!_powered) { @@ -456,13 +457,19 @@ namespace Content.Server.GameObjects.Components.Kitchen public SuicideKind Suicide(IEntity victim, IChatManager chat) { - int headCount = 0; + var headCount = 0; if (victim.TryGetComponent(out var bodyManagerComponent)) { var heads = bodyManagerComponent.GetBodyPartsOfType(BodyPartType.Head); foreach (var head in heads) { - var droppedHead = bodyManagerComponent.DropBodyPart(head); + var droppedHead = bodyManagerComponent.DropPart(head); + + if (droppedHead == null) + { + continue; + } + _storage.Insert(droppedHead); headCount++; } diff --git a/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs b/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs index c6a051b2c4..d50513e374 100644 --- a/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs +++ b/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; -using Content.Server.GameObjects.Components.Damage; -using Content.Server.GameObjects.Components.Mobs; 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; @@ -15,7 +14,8 @@ using Robust.Server.Interfaces.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Maths; -using Robust.Shared.Utility; +using Content.Shared.Damage; +using Robust.Shared.Localization; namespace Content.Server.GameObjects.Components.Medical { @@ -35,19 +35,27 @@ namespace Content.Server.GameObjects.Components.Medical public override void Initialize() { base.Initialize(); + _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(); } private static readonly MedicalScannerBoundUserInterfaceState EmptyUIState = new MedicalScannerBoundUserInterfaceState( - 0, - 0, - null); + null, + new Dictionary(), + new Dictionary(), + false); private MedicalScannerBoundUserInterfaceState GetUserInterfaceState() { @@ -58,58 +66,51 @@ namespace Content.Server.GameObjects.Components.Medical return EmptyUIState; } - var damageable = body.GetComponent(); - var species = body.GetComponent(); - var deathThreshold = - species.DamageTemplate.DamageThresholds.FirstOrNull(x => x.ThresholdType == ThresholdType.Death); - if (!deathThreshold.HasValue) + if (!body.TryGetComponent(out IDamageableComponent damageable) || + damageable.CurrentDamageState == DamageState.Dead) { return EmptyUIState; } - var deathThresholdValue = deathThreshold.Value.Value; - var currentHealth = damageable.CurrentDamage[DamageType.Total]; + var classes = new Dictionary(damageable.DamageClasses); + var types = new Dictionary(damageable.DamageTypes); - var dmgDict = new Dictionary(); - - foreach (var dmgType in (DamageType[]) Enum.GetValues(typeof(DamageType))) - { - if (damageable.CurrentDamage.TryGetValue(dmgType, out var amount)) - { - dmgDict[dmgType.ToString()] = amount; - } - } - - return new MedicalScannerBoundUserInterfaceState( - deathThresholdValue - currentHealth, - deathThresholdValue, - dmgDict); + return new MedicalScannerBoundUserInterfaceState(body.Uid, classes, types, CloningSystem.HasUid(body.Uid)); } private void UpdateUserInterface() { if (!Powered) + { return; + } + var newState = GetUserInterfaceState(); _userInterface.SetState(newState); } - private MedicalScannerStatus GetStatusFromDamageState(IDamageState damageState) + private MedicalScannerStatus GetStatusFromDamageState(DamageState damageState) { switch (damageState) { - case NormalState _: return MedicalScannerStatus.Green; - case CriticalState _: return MedicalScannerStatus.Red; - case DeadState _: return MedicalScannerStatus.Death; + case DamageState.Alive: return MedicalScannerStatus.Green; + case DamageState.Critical: return MedicalScannerStatus.Red; + case DamageState.Dead: return MedicalScannerStatus.Death; 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() @@ -141,7 +142,7 @@ namespace Content.Server.GameObjects.Components.Medical return; } - data.Text = "Enter"; + data.Text = Loc.GetString("Enter"); data.Visibility = component.IsOccupied ? VerbVisibility.Invisible : VerbVisibility.Visible; } @@ -162,7 +163,7 @@ namespace Content.Server.GameObjects.Components.Medical return; } - data.Text = "Eject"; + data.Text = Loc.GetString("Eject"); data.Visibility = component.IsOccupied ? VerbVisibility.Visible : VerbVisibility.Invisible; } @@ -190,13 +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/BloodstreamComponent.cs b/Content.Server/GameObjects/Components/Metabolism/BloodstreamComponent.cs deleted file mode 100644 index e428c72f17..0000000000 --- a/Content.Server/GameObjects/Components/Metabolism/BloodstreamComponent.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System.Linq; -using Content.Server.GameObjects.Components.Chemistry; -using Content.Server.GameObjects.EntitySystems; -using Content.Shared.Chemistry; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; -using Robust.Shared.ViewVariables; - -namespace Content.Server.GameObjects.Components.Metabolism -{ - /// - /// Handles all metabolism for mobs. All delivery methods eventually bring reagents - /// to the bloodstream. For example, injectors put reagents directly into the bloodstream, - /// and the stomach does with some delay. - /// - [RegisterComponent] - public class BloodstreamComponent : Component - { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; -#pragma warning restore 649 - - public override string Name => "Bloodstream"; - - /// - /// Internal solution for reagent storage - /// - [ViewVariables] - private SolutionComponent _internalSolution; - - /// - /// Max volume of internal solution storage - /// - [ViewVariables] - private ReagentUnit _initialMaxVolume; - - /// - /// Empty volume of internal solution - /// - public ReagentUnit EmptyVolume => _internalSolution.EmptyVolume; - - public override void ExposeData(ObjectSerializer serializer) - { - base.ExposeData(serializer); - serializer.DataField(ref _initialMaxVolume, "maxVolume", ReagentUnit.New(250)); - } - - protected override void Startup() - { - base.Startup(); - _internalSolution = Owner.GetComponent(); - _internalSolution.MaxVolume = _initialMaxVolume; - } - - /// - /// Attempt to transfer provided solution to internal solution. Only supports complete transfers - /// - /// Solution to be transferred - /// Whether or not transfer was a success - public bool TryTransferSolution(Solution solution) - { - //For now doesn't support partial transfers - if (solution.TotalVolume + _internalSolution.CurrentVolume > _internalSolution.MaxVolume) - { - return false; - } - - _internalSolution.TryAddSolution(solution, false, true); - return true; - } - - /// - /// Loops through each reagent in _internalSolution, and calls the IMetabolizable for each of them./> - /// - /// The time since the last metabolism tick in seconds. - private void Metabolize(float tickTime) - { - if (_internalSolution.CurrentVolume == 0) - { - return; - } - - //Run metabolism for each reagent, remove metabolized reagents - foreach (var reagent in _internalSolution.ReagentList.ToList()) //Using ToList here lets us edit reagents while iterating - { - if (!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto)) - { - continue; - } - - //Run metabolism code for each reagent - foreach (var metabolizable in proto.Metabolism) - { - var reagentDelta = metabolizable.Metabolize(Owner, reagent.ReagentId, tickTime); - _internalSolution.TryRemoveReagent(reagent.ReagentId, reagentDelta); - } - } - } - - /// - /// Triggers metabolism of the reagents inside _internalSolution. Called by - /// - /// The time since the last metabolism tick in seconds. - public void OnUpdate(float tickTime) - { - Metabolize(tickTime); - } - } -} diff --git a/Content.Server/GameObjects/Components/Metabolism/MetabolismComponent.cs b/Content.Server/GameObjects/Components/Metabolism/MetabolismComponent.cs new file mode 100644 index 0000000000..10e9e2c592 --- /dev/null +++ b/Content.Server/GameObjects/Components/Metabolism/MetabolismComponent.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Server.Atmos; +using Content.Server.GameObjects.Components.Atmos; +using Content.Server.GameObjects.Components.Body.Circulatory; +using Content.Shared.Atmos; +using Content.Shared.Chemistry; +using Content.Shared.Damage; +using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.Interfaces.Chemistry; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Metabolism +{ + [RegisterComponent] + public class MetabolismComponent : Component + { +#pragma warning disable 649 + [Dependency] private readonly IPrototypeManager _prototypeManager; +#pragma warning restore 649 + + public override string Name => "Metabolism"; + + private float _accumulatedFrameTime; + + [ViewVariables(VVAccess.ReadWrite)] private int _suffocationDamage; + + [ViewVariables] public Dictionary NeedsGases { get; set; } + + [ViewVariables] public Dictionary ProducesGases { get; set; } + + [ViewVariables] public Dictionary DeficitGases { get; set; } + + [ViewVariables] public bool Suffocating => SuffocatingPercentage() > 0; + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(this, b => b.NeedsGases, "needsGases", new Dictionary()); + serializer.DataField(this, b => b.ProducesGases, "producesGases", new Dictionary()); + serializer.DataField(this, b => b.DeficitGases, "deficitGases", new Dictionary()); + serializer.DataField(ref _suffocationDamage, "suffocationDamage", 1); + } + + private Dictionary NeedsAndDeficit(float frameTime) + { + var needs = new Dictionary(NeedsGases); + foreach (var (gas, amount) in DeficitGases) + { + var newAmount = (needs.GetValueOrDefault(gas) + amount) * frameTime; + needs[gas] = newAmount; + } + + return needs; + } + + private void ClampDeficit() + { + var deficitGases = new Dictionary(DeficitGases); + + foreach (var (gas, deficit) in deficitGases) + { + if (!NeedsGases.TryGetValue(gas, out var need)) + { + DeficitGases.Remove(gas); + continue; + } + + if (deficit > need) + { + DeficitGases[gas] = need; + } + } + } + + private float SuffocatingPercentage() + { + var percentages = new float[Atmospherics.TotalNumberOfGases]; + + foreach (var (gas, deficit) in DeficitGases) + { + if (!NeedsGases.TryGetValue(gas, out var needed)) + { + percentages[(int) gas] = 1; + continue; + } + + percentages[(int) gas] = deficit / needed; + } + + return percentages.Average(); + } + + private float GasProducedMultiplier(Gas gas, float usedAverage) + { + if (!NeedsGases.TryGetValue(gas, out var needs) || + !ProducesGases.TryGetValue(gas, out var produces)) + { + return 0; + } + + return needs * produces * usedAverage; + } + + private Dictionary GasProduced(float usedAverage) + { + return ProducesGases.ToDictionary(pair => pair.Key, pair => GasProducedMultiplier(pair.Key, usedAverage)); + } + + private void ProcessGases(float frameTime) + { + if (!Owner.TryGetComponent(out BloodstreamComponent bloodstream)) + { + return; + } + + var usedPercentages = new float[Atmospherics.TotalNumberOfGases]; + var needs = NeedsAndDeficit(frameTime); + foreach (var (gas, amountNeeded) in needs) + { + var bloodstreamAmount = bloodstream.Air.GetMoles(gas); + var deficit = 0f; + + if (bloodstreamAmount >= amountNeeded) + { + bloodstream.Air.AdjustMoles(gas, -amountNeeded); + } + else + { + deficit = amountNeeded - bloodstreamAmount; + bloodstream.Air.SetMoles(gas, 0); + } + + DeficitGases[gas] = deficit; + + var used = amountNeeded - deficit; + usedPercentages[(int) gas] = used / amountNeeded; + } + + var usedAverage = usedPercentages.Average(); + var produced = GasProduced(usedAverage); + + foreach (var (gas, amountProduced) in produced) + { + bloodstream.Air.AdjustMoles(gas, amountProduced); + } + + ClampDeficit(); + } + + /// + /// Loops through each reagent in _internalSolution, + /// and calls for each of them. + /// + /// The time since the last metabolism tick in seconds. + private void ProcessNutrients(float frameTime) + { + if (!Owner.TryGetComponent(out BloodstreamComponent bloodstream)) + { + return; + } + + if (bloodstream.Solution.CurrentVolume == 0) + { + return; + } + + // Run metabolism for each reagent, remove metabolized reagents + // Using ToList here lets us edit reagents while iterating + foreach (var reagent in bloodstream.Solution.ReagentList.ToList()) + { + if (!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype prototype)) + { + continue; + } + + // Run metabolism code for each reagent + foreach (var metabolizable in prototype.Metabolism) + { + var reagentDelta = metabolizable.Metabolize(Owner, reagent.ReagentId, frameTime); + bloodstream.Solution.TryRemoveReagent(reagent.ReagentId, reagentDelta); + } + } + } + + /// + /// Processes gases in the bloodstream and triggers metabolism of the + /// reagents inside of it. + /// + /// + /// The time since the last metabolism tick in seconds. + /// + public void Update(float frameTime) + { + _accumulatedFrameTime += frameTime; + + if (_accumulatedFrameTime < 1) + { + return; + } + + _accumulatedFrameTime -= 1; + + ProcessGases(frameTime); + ProcessNutrients(frameTime); + + if (Suffocating && + Owner.TryGetComponent(out IDamageableComponent damageable)) + { + // damageable.ChangeDamage(DamageClass.Airloss, _suffocationDamage, false); + } + } + + public void Transfer(BloodstreamComponent @from, GasMixture to, Gas gas, float pressure) + { + var transfer = new GasMixture(); + var molesInBlood = @from.Air.GetMoles(gas); + + transfer.SetMoles(gas, molesInBlood); + transfer.ReleaseGasTo(to, pressure); + + @from.Air.Merge(transfer); + } + + public GasMixture Clean(BloodstreamComponent bloodstream, float pressure = 100) + { + var gasMixture = new GasMixture(bloodstream.Air.Volume); + + for (Gas gas = 0; gas < (Gas) Atmospherics.TotalNumberOfGases; gas++) + { + if (NeedsGases.TryGetValue(gas, out var needed) && + bloodstream.Air.GetMoles(gas) < needed * 1.5f) + { + continue; + } + + Transfer(bloodstream, gasMixture, gas, pressure); + } + + return gasMixture; + } + } +} diff --git a/Content.Server/GameObjects/Components/Mining/AsteroidRockComponent.cs b/Content.Server/GameObjects/Components/Mining/AsteroidRockComponent.cs index a7fee5d76b..08a4bffd48 100644 --- a/Content.Server/GameObjects/Components/Mining/AsteroidRockComponent.cs +++ b/Content.Server/GameObjects/Components/Mining/AsteroidRockComponent.cs @@ -1,6 +1,7 @@ -using Content.Server.GameObjects.Components.Damage; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.Weapon.Melee; using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.Damage; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; using Robust.Server.GameObjects.EntitySystems; @@ -30,12 +31,12 @@ namespace Content.Server.GameObjects.Components.Mining spriteComponent.LayerSetState(0, _random.Pick(SpriteStates)); } - bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) + async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { var item = eventArgs.Using; if (!item.TryGetComponent(out MeleeWeaponComponent meleeWeaponComponent)) return false; - Owner.GetComponent().TakeDamage(DamageType.Brute, meleeWeaponComponent.Damage, item, eventArgs.User); + Owner.GetComponent().ChangeDamage(DamageType.Blunt, meleeWeaponComponent.Damage, false, item); if (!item.TryGetComponent(out PickaxeComponent pickaxeComponent)) return true; if (!string.IsNullOrWhiteSpace(pickaxeComponent.MiningSound)) diff --git a/Content.Server/GameObjects/Components/Mobs/DamageStates.cs b/Content.Server/GameObjects/Components/Mobs/DamageStates.cs deleted file mode 100644 index 89aea5beb8..0000000000 --- a/Content.Server/GameObjects/Components/Mobs/DamageStates.cs +++ /dev/null @@ -1,275 +0,0 @@ -using Content.Server.Mobs; -using Content.Shared.GameObjects.Components.Mobs; -using Content.Shared.GameObjects.EntitySystems; -using Robust.Server.GameObjects; -using Robust.Shared.GameObjects.Components; -using Robust.Shared.Interfaces.GameObjects; - -namespace Content.Server.GameObjects.Components.Mobs -{ - /// - /// Defines the blocking effect of each damage state, and what effects to apply upon entering or exiting the state - /// - public interface IDamageState : IActionBlocker - { - void EnterState(IEntity entity); - - void ExitState(IEntity entity); - - bool IsConscious { get; } - } - - /// - /// Standard state that a species is at with no damage or negative effect - /// - public struct NormalState : IDamageState - { - public void EnterState(IEntity entity) - { - entity.TryGetComponent(out AppearanceComponent appearanceComponent); - appearanceComponent?.SetData(DamageStateVisuals.State, DamageStateVisualData.Normal); - } - - public void ExitState(IEntity entity) - { - } - - public bool IsConscious => true; - - bool IActionBlocker.CanInteract() - { - return true; - } - - bool IActionBlocker.CanMove() - { - return true; - } - - bool IActionBlocker.CanUse() - { - return true; - } - - bool IActionBlocker.CanThrow() - { - return true; - } - - bool IActionBlocker.CanSpeak() - { - return true; - } - - bool IActionBlocker.CanDrop() - { - return true; - } - - bool IActionBlocker.CanPickup() - { - return true; - } - - bool IActionBlocker.CanEmote() - { - return true; - } - - bool IActionBlocker.CanAttack() - { - return true; - } - - bool IActionBlocker.CanEquip() - { - return true; - } - - bool IActionBlocker.CanUnequip() - { - return true; - } - - bool IActionBlocker.CanChangeDirection() - { - return true; - } - } - - /// - /// A state in which you are disabled from acting due to damage - /// - public struct CriticalState : IDamageState - { - public void EnterState(IEntity entity) - { - if(entity.TryGetComponent(out StunnableComponent stun)) - stun.CancelAll(); - - entity.TryGetComponent(out AppearanceComponent appearanceComponent); - appearanceComponent?.SetData(DamageStateVisuals.State, DamageStateVisualData.Crit); - StandingStateHelper.Down(entity); - } - - public void ExitState(IEntity entity) - { - StandingStateHelper.Standing(entity); - } - - public bool IsConscious => false; - - bool IActionBlocker.CanInteract() - { - return false; - } - - bool IActionBlocker.CanMove() - { - return false; - } - - bool IActionBlocker.CanUse() - { - return false; - } - - bool IActionBlocker.CanThrow() - { - return false; - } - - bool IActionBlocker.CanSpeak() - { - return false; - } - - bool IActionBlocker.CanDrop() - { - return false; - } - - bool IActionBlocker.CanPickup() - { - return false; - } - - bool IActionBlocker.CanEmote() - { - return false; - } - - bool IActionBlocker.CanAttack() - { - return false; - } - - bool IActionBlocker.CanEquip() - { - return false; - } - - bool IActionBlocker.CanUnequip() - { - return false; - } - - bool IActionBlocker.CanChangeDirection() - { - return true; - } - } - - /// - /// A damage state which will allow ghosting out of mobs - /// - public struct DeadState : IDamageState - { - public void EnterState(IEntity entity) - { - if(entity.TryGetComponent(out StunnableComponent stun)) - stun.CancelAll(); - - StandingStateHelper.Down(entity); - entity.TryGetComponent(out AppearanceComponent appearanceComponent); - appearanceComponent?.SetData(DamageStateVisuals.State, DamageStateVisualData.Dead); - - if (entity.TryGetComponent(out ICollidableComponent collidable)) - { - collidable.CanCollide = false; - } - } - - public void ExitState(IEntity entity) - { - StandingStateHelper.Standing(entity); - - if (entity.TryGetComponent(out ICollidableComponent collidable)) - { - collidable.CanCollide = true; - } - } - - public bool IsConscious => false; - - bool IActionBlocker.CanInteract() - { - return false; - } - - bool IActionBlocker.CanMove() - { - return false; - } - - bool IActionBlocker.CanUse() - { - return false; - } - - bool IActionBlocker.CanThrow() - { - return false; - } - - bool IActionBlocker.CanSpeak() - { - return false; - } - - bool IActionBlocker.CanDrop() - { - return false; - } - - bool IActionBlocker.CanPickup() - { - return false; - } - - bool IActionBlocker.CanEmote() - { - return false; - } - - bool IActionBlocker.CanAttack() - { - return false; - } - - bool IActionBlocker.CanEquip() - { - return false; - } - - bool IActionBlocker.CanUnequip() - { - return false; - } - - bool IActionBlocker.CanChangeDirection() - { - return false; - } - } -} diff --git a/Content.Server/GameObjects/Components/Mobs/DamageThresholdTemplates/DamageThresholdTemplates.cs b/Content.Server/GameObjects/Components/Mobs/DamageThresholdTemplates/DamageThresholdTemplates.cs deleted file mode 100644 index 80295df021..0000000000 --- a/Content.Server/GameObjects/Components/Mobs/DamageThresholdTemplates/DamageThresholdTemplates.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Collections.Generic; -using Content.Server.GameObjects.Components.Damage; -using Content.Shared.GameObjects.Components.Damage; - -namespace Content.Server.GameObjects.Components.Mobs.DamageThresholdTemplates -{ - /// - /// Defines the threshold values for each damage state for any kind of species - /// - public abstract class DamageTemplates - { - public abstract List HealthHudThresholds { get; } - - /// - /// Changes the hud state when a threshold is reached - /// - /// - /// - public abstract void ChangeHudState(DamageableComponent damage); - - //public abstract ResistanceSet resistanceset { get; } - - /// - /// Shows allowed states, ordered by priority, closest to last value to have threshold reached is preferred - /// - public abstract List<(DamageType, int, ThresholdType)> AllowedStates { get; } - - /// - /// Map of ALL POSSIBLE damage states to the threshold enum value that will trigger them, normal state wont be triggered by this value but is a default that is fell back onto - /// - public static Dictionary StateThresholdMap = new Dictionary() - { - { ThresholdType.None, new NormalState() }, - { ThresholdType.Critical, new CriticalState() }, - { ThresholdType.Death, new DeadState() } - }; - - public List DamageThresholds - { - get - { - List thresholds = new List(); - foreach (var element in AllowedStates) - { - thresholds.Add(new DamageThreshold(element.Item1, element.Item2, element.Item3)); - } - return thresholds; - } - } - - public ThresholdType CalculateDamageState(DamageableComponent damage) - { - ThresholdType healthstate = ThresholdType.None; - foreach(var element in AllowedStates) - { - if(damage.CurrentDamage[element.Item1] >= element.Item2) - { - healthstate = element.Item3; - } - } - - return healthstate; - } - } -} diff --git a/Content.Server/GameObjects/Components/Mobs/DamageThresholdTemplates/HumanTemplate.cs b/Content.Server/GameObjects/Components/Mobs/DamageThresholdTemplates/HumanTemplate.cs deleted file mode 100644 index 8045758b89..0000000000 --- a/Content.Server/GameObjects/Components/Mobs/DamageThresholdTemplates/HumanTemplate.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Collections.Generic; -using Content.Server.GameObjects.Components.Damage; -using Content.Shared.GameObjects.Components.Damage; -using Content.Shared.GameObjects.Components.Mobs; -using JetBrains.Annotations; - -namespace Content.Server.GameObjects.Components.Mobs.DamageThresholdTemplates -{ - [UsedImplicitly] - public class Human : DamageTemplates - { - int critvalue = 200; - int normalstates = 6; - //string startsprite = "human0"; - - public override List<(DamageType, int, ThresholdType)> AllowedStates => new List<(DamageType, int, ThresholdType)>() - { - (DamageType.Total, critvalue-1, ThresholdType.None), - (DamageType.Total, critvalue, ThresholdType.Critical), - (DamageType.Total, 300, ThresholdType.Death), - }; - - public override List HealthHudThresholds - { - get - { - List thresholds = new List(); - thresholds.Add(new DamageThreshold(DamageType.Total, 1, ThresholdType.HUDUpdate)); - for (var i = 1; i <= normalstates; i++) - { - thresholds.Add(new DamageThreshold(DamageType.Total, i * critvalue / normalstates, ThresholdType.HUDUpdate)); - } - return thresholds; //we don't need to respecify the state damage thresholds since we'll update hud on damage state changes as well - } - } - - // for shared string dict, since we don't define these anywhere in content - [UsedImplicitly] - public static readonly string[] _humanStatusImages = - { - "/Textures/Interface/StatusEffects/Human/human0.png", - "/Textures/Interface/StatusEffects/Human/human1.png", - "/Textures/Interface/StatusEffects/Human/human2.png", - "/Textures/Interface/StatusEffects/Human/human3.png", - "/Textures/Interface/StatusEffects/Human/human4.png", - "/Textures/Interface/StatusEffects/Human/human5.png", - "/Textures/Interface/StatusEffects/Human/human6-0.png", - "/Textures/Interface/StatusEffects/Human/human6-1.png", - "/Textures/Interface/StatusEffects/Human/humancrit-0.png", - "/Textures/Interface/StatusEffects/Human/humancrit-1.png", - "/Textures/Interface/StatusEffects/Human/humandead.png", - }; - - public override void ChangeHudState(DamageableComponent damage) - { - ThresholdType healthstate = CalculateDamageState(damage); - damage.Owner.TryGetComponent(out ServerStatusEffectsComponent statusEffectsComponent); - damage.Owner.TryGetComponent(out ServerOverlayEffectsComponent overlayComponent); - switch (healthstate) - { - case ThresholdType.None: - var totaldamage = damage.CurrentDamage[DamageType.Total]; - if (totaldamage > critvalue) - { - throw new InvalidOperationException(); //these should all be below the crit value, possibly going over multiple thresholds at once? - } - var modifier = totaldamage / (critvalue / normalstates); //integer division floors towards zero - statusEffectsComponent?.ChangeStatusEffectIcon(StatusEffect.Health, - "/Textures/Interface/StatusEffects/Human/human" + modifier + ".png"); - - overlayComponent?.RemoveOverlay(SharedOverlayID.GradientCircleMaskOverlay); - overlayComponent?.RemoveOverlay(SharedOverlayID.CircleMaskOverlay); - - return; - case ThresholdType.Critical: - statusEffectsComponent?.ChangeStatusEffectIcon( - StatusEffect.Health, - "/Textures/Interface/StatusEffects/Human/humancrit-0.png"); - overlayComponent?.AddOverlay(SharedOverlayID.GradientCircleMaskOverlay); - overlayComponent?.RemoveOverlay(SharedOverlayID.CircleMaskOverlay); - - return; - case ThresholdType.Death: - statusEffectsComponent?.ChangeStatusEffectIcon( - StatusEffect.Health, - "/Textures/Interface/StatusEffects/Human/humandead.png"); - overlayComponent?.RemoveOverlay(SharedOverlayID.GradientCircleMaskOverlay); - overlayComponent?.AddOverlay(SharedOverlayID.CircleMaskOverlay); - - return; - default: - throw new InvalidOperationException(); - } - } - } -} diff --git a/Content.Server/GameObjects/Components/Mobs/HeatResistanceComponent.cs b/Content.Server/GameObjects/Components/Mobs/HeatResistanceComponent.cs index 7d21ba3124..b0423e455b 100644 --- a/Content.Server/GameObjects/Components/Mobs/HeatResistanceComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/HeatResistanceComponent.cs @@ -13,10 +13,9 @@ namespace Content.Server.GameObjects.Components.Mobs public int GetHeatResistance() { - if (Owner.GetComponent().TryGetSlotItem(EquipmentSlotDefines.Slots.GLOVES, itemComponent: out ClothingComponent gloves) - | Owner.TryGetComponent(out SpeciesComponent speciesComponent)) + if (Owner.GetComponent().TryGetSlotItem(EquipmentSlotDefines.Slots.GLOVES, itemComponent: out ClothingComponent gloves)) { - return Math.Max(gloves?.HeatResistance ?? int.MinValue, speciesComponent?.HeatResistance ?? int.MinValue); + return gloves?.HeatResistance ?? int.MinValue; } return int.MinValue; } diff --git a/Content.Server/GameObjects/Components/Mobs/MindComponent.cs b/Content.Server/GameObjects/Components/Mobs/MindComponent.cs index d148490bd9..b5f9eba7e1 100644 --- a/Content.Server/GameObjects/Components/Mobs/MindComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/MindComponent.cs @@ -2,6 +2,7 @@ using Content.Server.GameObjects.Components.Observer; using Content.Server.Interfaces.GameTicking; using Content.Server.Mobs; +using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.EntitySystems; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; @@ -78,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; } @@ -126,8 +127,8 @@ namespace Content.Server.GameObjects.Components.Mobs } var dead = - Owner.TryGetComponent(out var species) && - species.CurrentDamageState is DeadState; + Owner.TryGetComponent(out var damageable) && + damageable.CurrentDamageState == DamageState.Dead; if (!HasMind) { @@ -137,7 +138,8 @@ namespace Content.Server.GameObjects.Components.Mobs } else if (Mind?.Session == null) { - message.AddMarkup("[color=yellow]" + Loc.GetString("{0:They} {0:have} a blank, absent-minded stare and appears completely unresponsive to anything. {0:They} may snap out of it soon.", Owner) + "[/color]"); + if(!dead) + message.AddMarkup("[color=yellow]" + Loc.GetString("{0:They} {0:have} a blank, absent-minded stare and appears completely unresponsive to anything. {0:They} may snap out of it soon.", Owner) + "[/color]"); } } } diff --git a/Content.Server/GameObjects/Components/Mobs/MobStateManager.cs b/Content.Server/GameObjects/Components/Mobs/MobStateManager.cs new file mode 100644 index 0000000000..acb501a1ea --- /dev/null +++ b/Content.Server/GameObjects/Components/Mobs/MobStateManager.cs @@ -0,0 +1,488 @@ +using System.Collections.Generic; +using Content.Server.GameObjects.Components.Body; +using Content.Server.GameObjects.Components.Damage; +using Content.Server.Mobs; +using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.EntitySystems; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Server.GameObjects.Components.Mobs +{ + /// + /// When attacked to an , this component will handle critical and death behaviors + /// for mobs. + /// Additionally, it handles sending effects to clients (such as blur effect for unconsciousness) and managing the + /// health HUD. + /// + [RegisterComponent] + internal class MobStateManagerComponent : Component, IOnHealthChangedBehavior, IActionBlocker + { + private readonly Dictionary _behavior = new Dictionary + { + {DamageState.Alive, new NormalState()}, + {DamageState.Critical, new CriticalState()}, + {DamageState.Dead, new DeadState()} + }; + + public override string Name => "MobStateManager"; + + private DamageState _currentDamageState; + + public IMobState CurrentMobState { get; private set; } = new NormalState(); + + bool IActionBlocker.CanInteract() + { + return CurrentMobState.CanInteract(); + } + + bool IActionBlocker.CanMove() + { + return CurrentMobState.CanMove(); + } + + bool IActionBlocker.CanUse() + { + return CurrentMobState.CanUse(); + } + + bool IActionBlocker.CanThrow() + { + return CurrentMobState.CanThrow(); + } + + bool IActionBlocker.CanSpeak() + { + return CurrentMobState.CanSpeak(); + } + + bool IActionBlocker.CanDrop() + { + return CurrentMobState.CanDrop(); + } + + bool IActionBlocker.CanPickup() + { + return CurrentMobState.CanPickup(); + } + + bool IActionBlocker.CanEmote() + { + return CurrentMobState.CanEmote(); + } + + bool IActionBlocker.CanAttack() + { + return CurrentMobState.CanAttack(); + } + + bool IActionBlocker.CanEquip() + { + return CurrentMobState.CanEquip(); + } + + bool IActionBlocker.CanUnequip() + { + return CurrentMobState.CanUnequip(); + } + + bool IActionBlocker.CanChangeDirection() + { + return CurrentMobState.CanChangeDirection(); + } + + public void OnHealthChanged(HealthChangedEventArgs e) + { + if (e.Damageable.CurrentDamageState != _currentDamageState) + { + _currentDamageState = e.Damageable.CurrentDamageState; + CurrentMobState.ExitState(Owner); + CurrentMobState = _behavior[_currentDamageState]; + CurrentMobState.EnterState(Owner); + } + + CurrentMobState.UpdateState(Owner); + } + + public override void Initialize() + { + base.Initialize(); + + _currentDamageState = DamageState.Alive; + CurrentMobState = _behavior[_currentDamageState]; + CurrentMobState.EnterState(Owner); + CurrentMobState.UpdateState(Owner); + } + + public override void OnRemove() + { + // TODO: Might want to add an OnRemove() to IMobState since those are where these components are being used + base.OnRemove(); + + if (Owner.TryGetComponent(out ServerStatusEffectsComponent status)) + { + status.RemoveStatusEffect(StatusEffect.Health); + } + + if (Owner.TryGetComponent(out ServerOverlayEffectsComponent overlay)) + { + overlay.ClearOverlays(); + } + } + } + + /// + /// Defines the blocking effects of an associated + /// (i.e. Normal, Critical, Dead) and what effects to apply upon entering or + /// exiting the state. + /// + public interface IMobState : IActionBlocker + { + /// + /// Called when this state is entered. + /// + void EnterState(IEntity entity); + + /// + /// Called when this state is left for a different state. + /// + void ExitState(IEntity entity); + + /// + /// Called when this state is updated. + /// + void UpdateState(IEntity entity); + } + + /// + /// The standard state an entity is in; no negative effects. + /// + public struct NormalState : IMobState + { + public void EnterState(IEntity entity) + { + UpdateState(entity); + } + + public void ExitState(IEntity entity) { } + + public void UpdateState(IEntity entity) + { + if (!entity.TryGetComponent(out ServerStatusEffectsComponent status)) + { + return; + } + + if (!entity.TryGetComponent(out IDamageableComponent damageable)) + { + status.ChangeStatusEffectIcon(StatusEffect.Health, + "/Textures/Interface/StatusEffects/Human/human0.png"); + return; + } + + // TODO + switch (damageable) + { + case RuinableComponent ruinable: + { + var modifier = (int) (ruinable.TotalDamage / (ruinable.MaxHp / 7f)); + + status.ChangeStatusEffectIcon(StatusEffect.Health, + "/Textures/Interface/StatusEffects/Human/human" + modifier + ".png"); + + break; + } + case BodyManagerComponent body: + { + // TODO: Declare body max normal damage (currently 100) + var modifier = (int) (body.TotalDamage / (100f / 7f)); + + status.ChangeStatusEffectIcon(StatusEffect.Health, + "/Textures/Interface/StatusEffects/Human/human" + modifier + ".png"); + + break; + } + default: + { + status.ChangeStatusEffectIcon(StatusEffect.Health, + "/Textures/Interface/StatusEffects/Human/human0.png"); + break; + } + } + } + + bool IActionBlocker.CanInteract() + { + return true; + } + + bool IActionBlocker.CanMove() + { + return true; + } + + bool IActionBlocker.CanUse() + { + return true; + } + + bool IActionBlocker.CanThrow() + { + return true; + } + + bool IActionBlocker.CanSpeak() + { + return true; + } + + bool IActionBlocker.CanDrop() + { + return true; + } + + bool IActionBlocker.CanPickup() + { + return true; + } + + bool IActionBlocker.CanEmote() + { + return true; + } + + bool IActionBlocker.CanAttack() + { + return true; + } + + bool IActionBlocker.CanEquip() + { + return true; + } + + bool IActionBlocker.CanUnequip() + { + return true; + } + + bool IActionBlocker.CanChangeDirection() + { + return true; + } + } + + /// + /// A state in which an entity is disabled from acting due to sufficient damage (considered unconscious). + /// + public struct CriticalState : IMobState + { + public void EnterState(IEntity entity) + { + if (entity.TryGetComponent(out ServerStatusEffectsComponent status)) + { + status.ChangeStatusEffectIcon(StatusEffect.Health, + "/Textures/Interface/StatusEffects/Human/humancrit-0.png"); //Todo: combine humancrit-0 and humancrit-1 into a gif and display it + } + + if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay)) + { + overlay.AddOverlay(SharedOverlayID.GradientCircleMaskOverlay); + } + + if (entity.TryGetComponent(out StunnableComponent stun)) + { + stun.CancelAll(); + } + + StandingStateHelper.Down(entity); + } + + public void ExitState(IEntity entity) + { + StandingStateHelper.Standing(entity); + + if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay)) + { + overlay.ClearOverlays(); + } + } + + public void UpdateState(IEntity entity) + { + } + + bool IActionBlocker.CanInteract() + { + return false; + } + + bool IActionBlocker.CanMove() + { + return false; + } + + bool IActionBlocker.CanUse() + { + return false; + } + + bool IActionBlocker.CanThrow() + { + return false; + } + + bool IActionBlocker.CanSpeak() + { + return false; + } + + bool IActionBlocker.CanDrop() + { + return false; + } + + bool IActionBlocker.CanPickup() + { + return false; + } + + bool IActionBlocker.CanEmote() + { + return false; + } + + bool IActionBlocker.CanAttack() + { + return false; + } + + bool IActionBlocker.CanEquip() + { + return false; + } + + bool IActionBlocker.CanUnequip() + { + return false; + } + + bool IActionBlocker.CanChangeDirection() + { + return false; + } + } + + /// + /// The state representing a dead entity; allows for ghosting. + /// + public struct DeadState : IMobState + { + public void EnterState(IEntity entity) + { + if (entity.TryGetComponent(out ServerStatusEffectsComponent status)) + { + status.ChangeStatusEffectIcon(StatusEffect.Health, + "/Textures/Interface/StatusEffects/Human/humandead.png"); + } + + if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlayComponent)) + { + overlayComponent.AddOverlay(SharedOverlayID.CircleMaskOverlay); + } + + if (entity.TryGetComponent(out StunnableComponent stun)) + { + stun.CancelAll(); + } + + StandingStateHelper.Down(entity); + + if (entity.TryGetComponent(out CollidableComponent collidable)) + { + collidable.CanCollide = false; + } + } + + public void ExitState(IEntity entity) + { + StandingStateHelper.Standing(entity); + + if (entity.TryGetComponent(out CollidableComponent collidable)) + { + collidable.CanCollide = true; + } + + if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay)) + { + overlay.ClearOverlays(); + } + } + + public void UpdateState(IEntity entity) + { + } + + bool IActionBlocker.CanInteract() + { + return false; + } + + bool IActionBlocker.CanMove() + { + return false; + } + + bool IActionBlocker.CanUse() + { + return false; + } + + bool IActionBlocker.CanThrow() + { + return false; + } + + bool IActionBlocker.CanSpeak() + { + return false; + } + + bool IActionBlocker.CanDrop() + { + return false; + } + + bool IActionBlocker.CanPickup() + { + return false; + } + + bool IActionBlocker.CanEmote() + { + return false; + } + + bool IActionBlocker.CanAttack() + { + return false; + } + + bool IActionBlocker.CanEquip() + { + return false; + } + + bool IActionBlocker.CanUnequip() + { + return false; + } + + bool IActionBlocker.CanChangeDirection() + { + return false; + } + } +} diff --git a/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs b/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs deleted file mode 100644 index 61e1f671fe..0000000000 --- a/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs +++ /dev/null @@ -1,247 +0,0 @@ -using System; -using System.Collections.Generic; -using Content.Server.GameObjects.Components.Damage; -using Content.Server.GameObjects.Components.Mobs.DamageThresholdTemplates; -using Content.Server.GameObjects.EntitySystems; -using Content.Server.Interfaces.GameObjects; -using Content.Server.Observer; -using Content.Shared.GameObjects.Components.Damage; -using Content.Shared.GameObjects.Components.Mobs; -using Content.Shared.GameObjects.Components.Movement; -using Content.Shared.GameObjects.EntitySystems; -using Robust.Server.GameObjects; -using Robust.Server.Interfaces.Player; -using Robust.Shared.GameObjects; -using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Players; -using Robust.Shared.Serialization; - -namespace Content.Server.GameObjects.Components.Mobs -{ - [RegisterComponent] - [ComponentReference(typeof(SharedSpeciesComponent))] - public class SpeciesComponent : SharedSpeciesComponent, IActionBlocker, IOnDamageBehavior, IExAct, IRelayMoveInput - { - /// - /// Damagestates are reached by reaching a certain damage threshold, they will block actions after being reached - /// - public IDamageState CurrentDamageState { get; private set; } = new NormalState(); - - /// - /// Damage state enum for current health, set only via change damage state //TODO: SETTER - /// - private ThresholdType currentstate = ThresholdType.None; - - /// - /// Holds the damage template which controls the threshold and resistance settings for this species type - /// - public DamageTemplates DamageTemplate { get; private set; } - - /// - /// Variable for serialization - /// - private string templatename; - - private int _heatResistance; - public int HeatResistance => _heatResistance; - - public override void ExposeData(ObjectSerializer serializer) - { - base.ExposeData(serializer); - - serializer.DataField(ref templatename, "Template", "Human"); - - var type = typeof(SpeciesComponent).Assembly.GetType("Content.Server.GameObjects.Components.Mobs.DamageThresholdTemplates." + templatename); - DamageTemplate = (DamageTemplates) Activator.CreateInstance(type); - serializer.DataFieldCached(ref _heatResistance, "HeatResistance", 323); - } - - public override void HandleMessage(ComponentMessage message, IComponent component) - { - base.HandleMessage(message, component); - - switch (message) - { - case PlayerAttachedMsg _: - if (CanReceiveStatusEffect(Owner)) { - DamageableComponent damage = Owner.GetComponent(); - DamageTemplate.ChangeHudState(damage); - } - break; - case PlayerDetachedMsg _: - break; - } - } - - public override void OnRemove() - { - base.OnRemove(); - Owner.TryGetComponent(out ServerStatusEffectsComponent statusEffectsComponent); - statusEffectsComponent?.RemoveStatusEffect(StatusEffect.Health); - - Owner.TryGetComponent(out ServerOverlayEffectsComponent overlayEffectsComponent); - overlayEffectsComponent?.ClearOverlays(); - } - - bool IActionBlocker.CanMove() - { - return CurrentDamageState.CanMove(); - } - - bool IActionBlocker.CanInteract() - { - return CurrentDamageState.CanInteract(); - } - - bool IActionBlocker.CanUse() - { - return CurrentDamageState.CanUse(); - } - - bool IActionBlocker.CanThrow() - { - return CurrentDamageState.CanThrow(); - } - - bool IActionBlocker.CanSpeak() - { - return CurrentDamageState.CanSpeak(); - } - - bool IActionBlocker.CanDrop() - { - return CurrentDamageState.CanDrop(); - } - - bool IActionBlocker.CanPickup() - { - return CurrentDamageState.CanPickup(); - } - - bool IActionBlocker.CanEmote() - { - return CurrentDamageState.CanEmote(); - } - - bool IActionBlocker.CanAttack() - { - return CurrentDamageState.CanAttack(); - } - - bool IActionBlocker.CanEquip() - { - return CurrentDamageState.CanEquip(); - } - - bool IActionBlocker.CanUnequip() - { - return CurrentDamageState.CanUnequip(); - } - - bool IActionBlocker.CanChangeDirection() - { - return CurrentDamageState.CanChangeDirection(); - } - - List IOnDamageBehavior.GetAllDamageThresholds() - { - var thresholdlist = DamageTemplate.DamageThresholds; - thresholdlist.AddRange(DamageTemplate.HealthHudThresholds); - return thresholdlist; - } - - void IOnDamageBehavior.OnDamageThresholdPassed(object damageable, DamageThresholdPassedEventArgs e) - { - DamageableComponent damage = (DamageableComponent) damageable; - - if (e.DamageThreshold.ThresholdType != ThresholdType.HUDUpdate) - { - ChangeDamageState(DamageTemplate.CalculateDamageState(damage)); - } - - //specifies if we have a client to update the hud for - if (CanReceiveStatusEffect(Owner)) - { - DamageTemplate.ChangeHudState(damage); - } - } - - private bool CanReceiveStatusEffect(IEntity user) - { - if (!user.HasComponent() && - !user.HasComponent()) - { - return false; - } - if (user.HasComponent()) - { - return true; - } - - return false; - } - - private void ChangeDamageState(ThresholdType threshold) - { - if (threshold == currentstate) - { - return; - } - - CurrentDamageState.ExitState(Owner); - CurrentDamageState = DamageTemplates.StateThresholdMap[threshold]; - CurrentDamageState.EnterState(Owner); - - currentstate = threshold; - - var toRaise = new MobDamageStateChangedMessage(this); - Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, toRaise); - } - - void IExAct.OnExplosion(ExplosionEventArgs eventArgs) - { - var burnDamage = 0; - var bruteDamage = 0; - switch(eventArgs.Severity) - { - case ExplosionSeverity.Destruction: - bruteDamage += 250; - burnDamage += 250; - break; - case ExplosionSeverity.Heavy: - bruteDamage += 60; - burnDamage += 60; - break; - case ExplosionSeverity.Light: - bruteDamage += 30; - break; - } - Owner.GetComponent().TakeDamage(DamageType.Brute, bruteDamage, null); - Owner.GetComponent().TakeDamage(DamageType.Heat, burnDamage, null); - } - - void IRelayMoveInput.MoveInputPressed(ICommonSession session) - { - if (CurrentDamageState is DeadState) - { - new Ghost().Execute(null, (IPlayerSession) session, null); - } - } - } - - /// - /// Fired when changes. - /// - public sealed class MobDamageStateChangedMessage : EntitySystemMessage - { - public MobDamageStateChangedMessage(SpeciesComponent species) - { - Species = species; - } - - /// - /// The species component that was changed. - /// - public SpeciesComponent Species { get; } - } -} diff --git a/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs b/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs index 26b450ca47..ac635153e2 100644 --- a/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs @@ -1,7 +1,9 @@ -using Content.Shared.GameObjects.Components.Movement; +using Content.Server.GameObjects.EntitySystems.AI; +using Content.Shared.GameObjects.Components.Movement; using Robust.Server.AI; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; +using Robust.Shared.GameObjects.Systems; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Serialization; @@ -45,6 +47,8 @@ namespace Content.Server.GameObjects.Components.Movement // This component requires a collidable component. if (!Owner.HasComponent()) Owner.AddComponent(); + + EntitySystem.Get().ProcessorInitialize(this); } /// diff --git a/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs b/Content.Server/GameObjects/Components/Movement/ClimbableComponent.cs new file mode 100644 index 0000000000..498f4afffb --- /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:them} forces {1:them} onto {2:theName}!", user, entityToMove, Owner), 15); + _notifyManager.PopupMessage(user, user, Loc.GetString("You force {0:them} 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:them} 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/Nutrition/DrinkComponent.cs b/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs index 482d460886..a52ebc1d48 100644 --- a/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs +++ b/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.Chemistry; +using Content.Server.GameObjects.Components.Body.Digestive; +using Content.Server.GameObjects.Components.Chemistry; using Content.Server.GameObjects.Components.Fluids; using Content.Server.GameObjects.EntitySystems; using Content.Shared.Audio; diff --git a/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs b/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs index 3354839430..e4440aefa9 100644 --- a/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs +++ b/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Content.Server.GameObjects.Components.Body.Digestive; using Content.Server.GameObjects.Components.Chemistry; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; diff --git a/Content.Server/GameObjects/Components/Nutrition/HungerComponent.cs b/Content.Server/GameObjects/Components/Nutrition/HungerComponent.cs index 47643417bb..0633289de8 100644 --- a/Content.Server/GameObjects/Components/Nutrition/HungerComponent.cs +++ b/Content.Server/GameObjects/Components/Nutrition/HungerComponent.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -using Content.Server.GameObjects.Components.Damage; using Content.Server.GameObjects.Components.Mobs; +using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.Components.Movement; @@ -180,11 +180,11 @@ namespace Content.Server.GameObjects.Components.Nutrition } if (_currentHungerThreshold == HungerThreshold.Dead) { - if (Owner.TryGetComponent(out DamageableComponent damage)) + if (Owner.TryGetComponent(out IDamageableComponent damageable)) { - if (!damage.IsDead()) + if (damageable.CurrentDamageState != DamageState.Dead) { - damage.TakeDamage(DamageType.Brute, 2); + damageable.ChangeDamage(DamageType.Blunt, 2, true, null); } } } diff --git a/Content.Server/GameObjects/Components/Nutrition/ThirstComponent.cs b/Content.Server/GameObjects/Components/Nutrition/ThirstComponent.cs index 94f7b5e022..379432e4f1 100644 --- a/Content.Server/GameObjects/Components/Nutrition/ThirstComponent.cs +++ b/Content.Server/GameObjects/Components/Nutrition/ThirstComponent.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -using Content.Server.GameObjects.Components.Damage; using Content.Server.GameObjects.Components.Mobs; +using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.Components.Movement; @@ -181,11 +181,11 @@ namespace Content.Server.GameObjects.Components.Nutrition if (_currentThirstThreshold == ThirstThreshold.Dead) { - if (Owner.TryGetComponent(out DamageableComponent damage)) + if (Owner.TryGetComponent(out IDamageableComponent damageable)) { - if (!damage.IsDead()) + if (damageable.CurrentDamageState != DamageState.Dead) { - damage.TakeDamage(DamageType.Brute, 2); + damageable.ChangeDamage(DamageType.Blunt, 2, true, null); } } } diff --git a/Content.Server/GameObjects/Components/PDA/PDAComponent.cs b/Content.Server/GameObjects/Components/PDA/PDAComponent.cs index b3edacdfac..1aa9f2f776 100644 --- a/Content.Server/GameObjects/Components/PDA/PDAComponent.cs +++ b/Content.Server/GameObjects/Components/PDA/PDAComponent.cs @@ -3,6 +3,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.Access; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; @@ -140,10 +141,10 @@ namespace Content.Server.GameObjects.Components.PDA private void UpdatePDAAppearance() { - _appearance?.SetData(PDAVisuals.ScreenLit, _lightOn); + _appearance?.SetData(PDAVisuals.FlashlightLit, _lightOn); } - public bool InteractUsing(InteractUsingEventArgs eventArgs) + public async Task InteractUsing(InteractUsingEventArgs eventArgs) { var item = eventArgs.Using; if (!IdSlotEmpty) @@ -163,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; } @@ -174,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/Paper/PaperComponent.cs b/Content.Server/GameObjects/Components/Paper/PaperComponent.cs index 0f2b48b905..54193918d9 100644 --- a/Content.Server/GameObjects/Components/Paper/PaperComponent.cs +++ b/Content.Server/GameObjects/Components/Paper/PaperComponent.cs @@ -1,4 +1,5 @@ -using Content.Shared.GameObjects.Components; +using System.Threading.Tasks; +using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; @@ -66,7 +67,7 @@ namespace Content.Server.GameObjects.Components.Paper UpdateUserInterface(); } - public bool InteractUsing(InteractUsingEventArgs eventArgs) + public async Task InteractUsing(InteractUsingEventArgs eventArgs) { if (!eventArgs.Using.HasComponent()) return false; diff --git a/Content.Server/GameObjects/Components/PlaceableSurfaceComponent.cs b/Content.Server/GameObjects/Components/PlaceableSurfaceComponent.cs index 3ac23365fe..ae7947a800 100644 --- a/Content.Server/GameObjects/Components/PlaceableSurfaceComponent.cs +++ b/Content.Server/GameObjects/Components/PlaceableSurfaceComponent.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Shared.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components; @@ -12,6 +13,8 @@ namespace Content.Server.GameObjects.Components private bool _isPlaceable; public bool IsPlaceable { get => _isPlaceable; set => _isPlaceable = value; } + int IInteractUsing.Priority => 1; + public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); @@ -19,7 +22,8 @@ namespace Content.Server.GameObjects.Components serializer.DataField(ref _isPlaceable, "IsPlaceable", true); } - public bool InteractUsing(InteractUsingEventArgs eventArgs) + + public async Task InteractUsing(InteractUsingEventArgs eventArgs) { if (!IsPlaceable) 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/PottedPlantHideComponent.cs b/Content.Server/GameObjects/Components/PottedPlantHideComponent.cs index a25e6730b3..bd7dd1a28b 100644 --- a/Content.Server/GameObjects/Components/PottedPlantHideComponent.cs +++ b/Content.Server/GameObjects/Components/PottedPlantHideComponent.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.Interfaces.GameObjects.Components.Items; @@ -30,7 +31,7 @@ namespace Content.Server.GameObjects.Components ContainerManagerComponent.Ensure("potted_plant_hide", Owner, out _); } - bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) + async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { if (_itemContainer.ContainedEntity != null) { diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/BaseCharger.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/BaseCharger.cs index 0bfc844474..674a33dd1f 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/BaseCharger.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/BaseCharger.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels; @@ -65,7 +66,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece base.OnRemove(); } - bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) + async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { var result = TryInsertItem(eventArgs.Using); if (!result) diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs index a8d60ea43d..5f6edb59e6 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs @@ -1,11 +1,12 @@ using System; -using Content.Server.GameObjects.Components.Damage; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.Damage; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.Container; @@ -69,14 +70,14 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece } } - public bool InteractUsing(InteractUsingEventArgs eventArgs) + public async Task InteractUsing(InteractUsingEventArgs eventArgs) { return InsertBulb(eventArgs.Using); } public bool InteractHand(InteractHandEventArgs eventArgs) { - if (!eventArgs.User.TryGetComponent(out DamageableComponent damageableComponent)) + if (!eventArgs.User.TryGetComponent(out IDamageableComponent damageableComponent)) { Eject(); return false; @@ -99,7 +100,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece void Burn() { - damageableComponent.TakeDamage(DamageType.Heat, 20, Owner); + damageableComponent.ChangeDamage(DamageType.Heat, 20, false, Owner); var audioSystem = EntitySystem.Get(); audioSystem.PlayFromEntity("/Audio/Effects/lightburn.ogg", Owner); } diff --git a/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarPanelComponent.cs b/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarPanelComponent.cs index a86dff1b87..6f63859193 100644 --- a/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarPanelComponent.cs +++ b/Content.Server/GameObjects/Components/Power/PowerNetComponents/SolarPanelComponent.cs @@ -1,5 +1,6 @@ using System; using Content.Server.GameObjects.EntitySystems; +using Content.Shared.GameObjects.EntitySystems; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.Timing; diff --git a/Content.Server/GameObjects/Components/Power/WireComponent.cs b/Content.Server/GameObjects/Components/Power/WireComponent.cs index 7b551c1727..ad9cf19602 100644 --- a/Content.Server/GameObjects/Components/Power/WireComponent.cs +++ b/Content.Server/GameObjects/Components/Power/WireComponent.cs @@ -1,4 +1,5 @@ -using Content.Server.GameObjects.Components.Interactable; +using System.Threading.Tasks; +using Content.Server.GameObjects.Components.Interactable; using Content.Server.GameObjects.Components.Stack; using Content.Shared.GameObjects.Components.Interactable; using Content.Shared.Interfaces.GameObjects.Components; @@ -34,10 +35,10 @@ namespace Content.Server.GameObjects.Components.Power serializer.DataField(ref _wireType, "wireType", WireType.HighVoltage); } - public bool InteractUsing(InteractUsingEventArgs eventArgs) + public async Task InteractUsing(InteractUsingEventArgs eventArgs) { if (!eventArgs.Using.TryGetComponent(out ToolComponent tool)) return false; - if (!tool.UseTool(eventArgs.User, Owner, ToolQuality.Cutting)) return false; + if (!await tool.UseTool(eventArgs.User, Owner, 0.25f, ToolQuality.Cutting)) return false; Owner.Delete(); var droppedEnt = Owner.EntityManager.SpawnEntity(_wireDroppedOnCutPrototype, eventArgs.ClickLocation); diff --git a/Content.Server/GameObjects/Components/Projectiles/HitscanComponent.cs b/Content.Server/GameObjects/Components/Projectiles/HitscanComponent.cs index 4d0bde9372..509de63d73 100644 --- a/Content.Server/GameObjects/Components/Projectiles/HitscanComponent.cs +++ b/Content.Server/GameObjects/Components/Projectiles/HitscanComponent.cs @@ -1,5 +1,5 @@ using System; -using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.Damage; using Content.Shared.Physics; using Robust.Server.GameObjects.EntitySystems; using Robust.Shared.GameObjects; diff --git a/Content.Server/GameObjects/Components/Projectiles/ProjectileComponent.cs b/Content.Server/GameObjects/Components/Projectiles/ProjectileComponent.cs index 9b633dc688..d3395a5ca2 100644 --- a/Content.Server/GameObjects/Components/Projectiles/ProjectileComponent.cs +++ b/Content.Server/GameObjects/Components/Projectiles/ProjectileComponent.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -using Content.Server.GameObjects.Components.Damage; using Content.Server.GameObjects.Components.Mobs; +using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Projectiles; using Robust.Server.GameObjects.EntitySystems; @@ -71,9 +71,11 @@ namespace Content.Server.GameObjects.Components.Projectiles return; } else + { _deleteOnCollide = true; + } - if (_soundHitSpecies != null && entity.HasComponent()) + if (_soundHitSpecies != null && entity.HasComponent()) { EntitySystem.Get().PlayAtCoords(_soundHitSpecies, entity.Transform.GridPosition); } else if (_soundHit != null) @@ -81,13 +83,13 @@ namespace Content.Server.GameObjects.Components.Projectiles EntitySystem.Get().PlayAtCoords(_soundHit, entity.Transform.GridPosition); } - if (entity.TryGetComponent(out DamageableComponent damage)) + if (entity.TryGetComponent(out IDamageableComponent damage)) { Owner.EntityManager.TryGetEntity(_shooter, out var shooter); foreach (var (damageType, amount) in _damages) { - damage.TakeDamage(damageType, amount, Owner, shooter); + damage.ChangeDamage(damageType, amount, false, shooter); } } diff --git a/Content.Server/GameObjects/Components/Projectiles/ThrownItemComponent.cs b/Content.Server/GameObjects/Components/Projectiles/ThrownItemComponent.cs index b072072bc5..df97af93e8 100644 --- a/Content.Server/GameObjects/Components/Projectiles/ThrownItemComponent.cs +++ b/Content.Server/GameObjects/Components/Projectiles/ThrownItemComponent.cs @@ -1,5 +1,5 @@ -using Content.Server.GameObjects.Components.Damage; -using Content.Server.GameObjects.EntitySystems.Click; +using Content.Server.GameObjects.EntitySystems.Click; +using Content.Shared.Damage; using Content.Shared.GameObjects; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.Physics; @@ -40,9 +40,9 @@ namespace Content.Server.GameObjects.Components.Projectiles _shouldStop = true; // hit something hard => stop after this collision } - if (entity.TryGetComponent(out DamageableComponent damage)) + if (entity.TryGetComponent(out IDamageableComponent damage)) { - damage.TakeDamage(DamageType.Brute, 10, Owner, User); + damage.ChangeDamage(DamageType.Blunt, 10, false, Owner); } // Stop colliding with mobs, this mimics not having enough velocity to do damage diff --git a/Content.Server/GameObjects/Components/Recycling/RecyclerComponent.cs b/Content.Server/GameObjects/Components/Recycling/RecyclerComponent.cs index 8b2f31c6c4..124911e98d 100644 --- a/Content.Server/GameObjects/Components/Recycling/RecyclerComponent.cs +++ b/Content.Server/GameObjects/Components/Recycling/RecyclerComponent.cs @@ -5,7 +5,10 @@ using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.EntitySystems; using Content.Shared.Construction; +using Content.Shared.GameObjects.Components.Body; +using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.Components.Recycling; +using Content.Shared.GameObjects.Components.Rotation; using Content.Shared.Physics; using Robust.Server.GameObjects; using Robust.Shared.Containers; @@ -65,9 +68,7 @@ namespace Content.Server.GameObjects.Components.Recycling private bool CanGib(IEntity entity) { - return entity.HasComponent() && - !_safe && - Powered; + return entity.HasComponent() && !_safe && Powered; } private bool CanRecycle(IEntity entity, [MaybeNullWhen(false)] out ConstructionPrototype prototype) diff --git a/Content.Server/GameObjects/Components/Research/LatheComponent.cs b/Content.Server/GameObjects/Components/Research/LatheComponent.cs index e848cb66ee..55b2ddbbcb 100644 --- a/Content.Server/GameObjects/Components/Research/LatheComponent.cs +++ b/Content.Server/GameObjects/Components/Research/LatheComponent.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.Components.Stack; using Content.Shared.GameObjects.Components.Materials; @@ -149,7 +150,8 @@ namespace Content.Server.GameObjects.Components.Research OpenUserInterface(actor.playerSession); } - bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) + + async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { if (!Owner.TryGetComponent(out MaterialStorageComponent storage) || !eventArgs.Using.TryGetComponent(out MaterialComponent material)) return false; 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/Stack/StackComponent.cs b/Content.Server/GameObjects/Components/Stack/StackComponent.cs index aedff7d2df..e38af7a857 100644 --- a/Content.Server/GameObjects/Components/Stack/StackComponent.cs +++ b/Content.Server/GameObjects/Components/Stack/StackComponent.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces; @@ -61,7 +62,7 @@ namespace Content.Server.GameObjects.Components.Stack return false; } - public bool InteractUsing(InteractUsingEventArgs eventArgs) + public async Task InteractUsing(InteractUsingEventArgs eventArgs) { if (eventArgs.Using.TryGetComponent(out var stack)) { diff --git a/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs b/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs new file mode 100644 index 0000000000..d982130afa --- /dev/null +++ b/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs @@ -0,0 +1,43 @@ +using Content.Server.GameObjects.Components.Mobs; +using Content.Server.Mobs.Roles; +using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.EntitySystems; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; +using Robust.Shared.Utility; + +namespace Content.Server.GameObjects.Components.Suspicion +{ + [RegisterComponent] + public class SuspicionRoleComponent : Component, IExamine + { + public override string Name => "SuspicionRole"; + + public bool IsDead() + { + return Owner.TryGetComponent(out IDamageableComponent damageable) && + damageable.CurrentDamageState == DamageState.Dead; + } + + public bool IsTraitor() + { + return Owner.TryGetComponent(out MindComponent mind) && + mind.HasMind && + mind.Mind!.HasRole(); + } + + void IExamine.Examine(FormattedMessage message, bool inDetailsRange) + { + if (!IsDead()) + { + return; + } + + var tooltip = IsTraitor() + ? Loc.GetString($"They were a [color=red]traitor[/color]!") + : Loc.GetString($"They were an [color=green]innocent[/color]!"); + + message.AddMarkup(tooltip); + } + } +} diff --git a/Content.Server/GameObjects/Components/Temperature/TemperatureComponent.cs b/Content.Server/GameObjects/Components/Temperature/TemperatureComponent.cs index 4e7e901ff2..3c80f17bca 100644 --- a/Content.Server/GameObjects/Components/Temperature/TemperatureComponent.cs +++ b/Content.Server/GameObjects/Components/Temperature/TemperatureComponent.cs @@ -1,5 +1,5 @@ using System; -using Content.Server.GameObjects.Components.Damage; +using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.Maths; using Robust.Shared.GameObjects; @@ -44,16 +44,16 @@ namespace Content.Server.GameObjects.Components.Temperature /// public void OnUpdate(float frameTime) { - int fireDamage = + var fireDamage = (int) Math.Floor(Math.Max(0, CurrentTemperature - _fireDamageThreshold) / _fireDamageCoefficient); _secondsSinceLastDamageUpdate += frameTime; - Owner.TryGetComponent(out DamageableComponent component); + Owner.TryGetComponent(out IDamageableComponent component); while (_secondsSinceLastDamageUpdate >= 1) { - component?.TakeDamage(DamageType.Heat, fireDamage); + component?.ChangeDamage(DamageType.Heat, fireDamage, false, null); _secondsSinceLastDamageUpdate -= 1; } } diff --git a/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs b/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs index e0dc32b98f..5d055f8d4a 100644 --- a/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using Content.Server.GameObjects.Components.Damage; using Content.Server.GameObjects.EntitySystems; -using Content.Server.Interfaces.GameObjects.Components.Interaction; -using Content.Shared.GameObjects; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Items; using Robust.Server.GameObjects.EntitySystems; @@ -18,6 +15,7 @@ using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; +using Content.Shared.Damage; using Content.Shared.Interfaces.GameObjects.Components; namespace Content.Server.GameObjects.Components.Weapon.Melee @@ -116,9 +114,9 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee if (!entity.Transform.IsMapTransform || entity == eventArgs.User) continue; - if (entity.TryGetComponent(out DamageableComponent damageComponent)) + if (entity.TryGetComponent(out IDamageableComponent damageComponent)) { - damageComponent.TakeDamage(DamageType.Brute, Damage, Owner, eventArgs.User); + damageComponent.ChangeDamage(DamageType.Blunt, Damage, false, Owner); hitEntities.Add(entity); } } diff --git a/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs b/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs index 4d52c9382e..2a3fc9df37 100644 --- a/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Mobs; @@ -192,7 +193,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee return true; } - public bool InteractUsing(InteractUsingEventArgs eventArgs) + public async Task InteractUsing(InteractUsingEventArgs eventArgs) { if (!eventArgs.Using.HasComponent()) return false; diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/AmmoBoxComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/AmmoBoxComponent.cs index 14d433e616..839c356ecf 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/AmmoBoxComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/AmmoBoxComponent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels; @@ -119,7 +120,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition return true; } - bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) + async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { if (eventArgs.Using.HasComponent()) { diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/RangedMagazineComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/RangedMagazineComponent.cs index 594d1d6689..5747132bb8 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/RangedMagazineComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/RangedMagazineComponent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels; @@ -136,7 +137,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition return ammo; } - bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) + async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { return TryInsertAmmo(eventArgs.User, eventArgs.Using); } diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/SpeedLoaderComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/SpeedLoaderComponent.cs index 25696a9e0d..e5bd9de782 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/SpeedLoaderComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/SpeedLoaderComponent.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels; @@ -204,7 +205,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition } } - bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) + async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { return TryInsertAmmo(eventArgs.User, eventArgs.Using); } diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs index 75130f5246..913b56027f 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/BoltActionBarrelComponent.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition; using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; using Content.Shared.GameObjects.EntitySystems; @@ -291,7 +292,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels return true; } - public override bool InteractUsing(InteractUsingEventArgs eventArgs) + public override async Task InteractUsing(InteractUsingEventArgs eventArgs) { return TryInsertBullet(eventArgs.User, eventArgs.Using); } diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs index 3c4acb1e04..2139767dc0 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/PumpBarrelComponent.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition; using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; using Content.Shared.Interfaces; @@ -206,7 +207,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels return true; } - public override bool InteractUsing(InteractUsingEventArgs eventArgs) + public override async Task InteractUsing(InteractUsingEventArgs eventArgs) { return TryInsertBullet(eventArgs); } diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs index fe8488952c..004e742b71 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/RevolverBarrelComponent.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition; using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; using Content.Shared.GameObjects.EntitySystems; @@ -231,7 +232,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels return true; } - public override bool InteractUsing(InteractUsingEventArgs eventArgs) + public override async Task InteractUsing(InteractUsingEventArgs eventArgs) { return TryInsertBullet(eventArgs.User, eventArgs.Using); } diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs index bdb9b88761..486d2071b1 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Power; using Content.Server.GameObjects.Components.Projectiles; -using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; @@ -260,7 +261,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels return true; } - public override bool InteractUsing(InteractUsingEventArgs eventArgs) + public override async Task InteractUsing(InteractUsingEventArgs eventArgs) { if (!eventArgs.Using.HasComponent()) { diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs index 3623c4b5db..ef8b35ca90 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition; @@ -344,7 +345,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels UpdateAppearance(); } - public override bool InteractUsing(InteractUsingEventArgs eventArgs) + public override async Task InteractUsing(InteractUsingEventArgs eventArgs) { // Insert magazine if (eventArgs.Using.TryGetComponent(out RangedMagazineComponent magazineComponent)) diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs index 61cecee6c2..aca55f1921 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using Content.Server.GameObjects.Components.Damage; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Projectiles; using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition; @@ -15,7 +15,6 @@ using Robust.Shared.Audio; using Robust.Shared.GameObjects.Components; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Physics; using Robust.Shared.Interfaces.Random; using Robust.Shared.Interfaces.Timing; @@ -28,6 +27,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Serialization; using Robust.Shared.Utility; +using Content.Shared.GameObjects.Components.Damage; namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels { @@ -187,7 +187,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels } public abstract bool UseEntity(UseEntityEventArgs eventArgs); - public abstract bool InteractUsing(InteractUsingEventArgs eventArgs); + + public abstract Task InteractUsing(InteractUsingEventArgs eventArgs); public void ChangeFireSelector(FireRateSelector rateSelector) { @@ -430,16 +431,12 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels var distance = result.HitEntity != null ? result.Distance : hitscan.MaxLength; hitscan.FireEffects(shooter, distance, angle, result.HitEntity); - if (result.HitEntity == null || !result.HitEntity.TryGetComponent(out DamageableComponent damageable)) + if (result.HitEntity == null || !result.HitEntity.TryGetComponent(out IDamageableComponent damageable)) { return; } - damageable.TakeDamage( - hitscan.DamageType, - (int)Math.Round(hitscan.Damage, MidpointRounding.AwayFromZero), - Owner, - shooter); + damageable.ChangeDamage(hitscan.DamageType, (int)Math.Round(hitscan.Damage, MidpointRounding.AwayFromZero), false, Owner); //I used Math.Round over Convert.toInt32, as toInt32 always rounds to //even numbers if halfway between two numbers, rather than rounding to nearest } diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/ServerRangedWeaponComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/ServerRangedWeaponComponent.cs index e87bacbe71..5c8370b7e8 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/ServerRangedWeaponComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/ServerRangedWeaponComponent.cs @@ -1,9 +1,8 @@ using System; -using Content.Server.GameObjects.Components.Damage; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels; -using Content.Shared.GameObjects; +using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Weapons.Ranged; using Content.Shared.GameObjects.EntitySystems; @@ -168,10 +167,10 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged soundSystem.PlayAtCoords("/Audio/Weapons/Guns/Gunshots/bang.ogg", Owner.Transform.GridPosition, AudioParams.Default, 5); - if (user.TryGetComponent(out DamageableComponent health)) + if (user.TryGetComponent(out IDamageableComponent health)) { - health.TakeDamage(DamageType.Brute, 10); - health.TakeDamage(DamageType.Heat, 5); + health.ChangeDamage(DamageType.Blunt, 10, false, user); + health.ChangeDamage(DamageType.Heat, 5, false, user); } if (user.TryGetComponent(out StunnableComponent stun)) diff --git a/Content.Server/GameObjects/Components/WiresComponent.cs b/Content.Server/GameObjects/Components/WiresComponent.cs index cc9b0aba06..6867052599 100644 --- a/Content.Server/GameObjects/Components/WiresComponent.cs +++ b/Content.Server/GameObjects/Components/WiresComponent.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.Interactable; using Content.Server.GameObjects.Components.VendingMachines; using Content.Server.GameObjects.EntitySystems; @@ -372,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.")); @@ -468,11 +469,11 @@ namespace Content.Server.GameObjects.Components serializer.DataField(ref _layoutId, "LayoutId", null); } - bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) + async Task IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) { if (!eventArgs.Using.TryGetComponent(out var tool)) return false; - if (!tool.UseTool(eventArgs.User, Owner, ToolQuality.Screwing)) + if (!await tool.UseTool(eventArgs.User, Owner, 0.5f, ToolQuality.Screwing)) return false; IsPanelOpen = !IsPanelOpen; diff --git a/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs b/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs index 01139bf7af..b04aca780e 100644 --- a/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/AI/AiSystem.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using Content.Server.GameObjects.Components.Movement; using Content.Shared.GameObjects.Components.Movement; @@ -19,23 +20,31 @@ namespace Content.Server.GameObjects.EntitySystems.AI [UsedImplicitly] internal class AiSystem : EntitySystem { -#pragma warning disable 649 - [Dependency] private readonly IPauseManager _pauseManager; - [Dependency] private readonly IDynamicTypeFactory _typeFactory; - [Dependency] private readonly IReflectionManager _reflectionManager; -#pragma warning restore 649 + [Dependency] private readonly IDynamicTypeFactory _typeFactory = default!; + [Dependency] private readonly IReflectionManager _reflectionManager = default!; private readonly Dictionary _processorTypes = new Dictionary(); + + /// + /// To avoid iterating over dead AI continuously they can wake and sleep themselves when necessary. + /// + private readonly HashSet _awakeAi = new HashSet(); + + // To avoid modifying awakeAi while iterating over it. + private readonly List _queuedSleepMessages = new List(); + public bool IsAwake(AiLogicProcessor processor) => _awakeAi.Contains(processor); + /// public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(HandleAiSleep); var processors = _reflectionManager.GetAllChildren(); foreach (var processor in processors) { - var att = (AiLogicProcessorAttribute)Attribute.GetCustomAttribute(processor, typeof(AiLogicProcessorAttribute)); + var att = (AiLogicProcessorAttribute) Attribute.GetCustomAttribute(processor, typeof(AiLogicProcessorAttribute))!; // Tests should pick this up DebugTools.AssertNotNull(att); _processorTypes.Add(att.SerializeName, processor); @@ -45,23 +54,35 @@ namespace Content.Server.GameObjects.EntitySystems.AI /// public override void Update(float frameTime) { - foreach (var comp in ComponentManager.EntityQuery()) + foreach (var message in _queuedSleepMessages) { - if (_pauseManager.IsEntityPaused(comp.Owner)) + switch (message.Sleep) { - continue; + case true: + _awakeAi.Remove(message.Processor); + break; + case false: + _awakeAi.Add(message.Processor); + break; } - - ProcessorInitialize(comp); - - var processor = comp.Processor; - + } + + _queuedSleepMessages.Clear(); + + foreach (var processor in _awakeAi) + { processor.Update(frameTime); } } + private void HandleAiSleep(SleepAiMessage message) + { + _queuedSleepMessages.Add(message); + } + /// - /// Will start up the controller's processor if not already done so + /// Will start up the controller's processor if not already done so. + /// Also add them to the awakeAi for updates. /// /// public void ProcessorInitialize(AiControllerComponent controller) @@ -70,6 +91,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI controller.Processor = CreateProcessor(controller.LogicName); controller.Processor.SelfEntity = controller.Owner; controller.Processor.Setup(); + _awakeAi.Add(controller.Processor); } private AiLogicProcessor CreateProcessor(string name) @@ -94,7 +116,7 @@ namespace Content.Server.GameObjects.EntitySystems.AI + "\n processorId: Class that inherits AiLogicProcessor and has an AiLogicProcessor attribute." + "\n entityID: Uid of entity to add the AiControllerComponent to. Open its VV menu to find this."; - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) { if(args.Length != 2) { diff --git a/Content.Server/GameObjects/EntitySystems/AI/SleepAiMessage.cs b/Content.Server/GameObjects/EntitySystems/AI/SleepAiMessage.cs new file mode 100644 index 0000000000..28f6674a80 --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/AI/SleepAiMessage.cs @@ -0,0 +1,24 @@ +using Robust.Server.AI; +using Robust.Shared.GameObjects; + +namespace Content.Server.GameObjects.EntitySystems.AI +{ + /// + /// Indicates whether an AI should be updated by the AiSystem or not. + /// Useful to sleep AI when they die or otherwise should be inactive. + /// + internal sealed class SleepAiMessage : EntitySystemMessage + { + /// + /// Sleep or awake. + /// + public bool Sleep { get; } + public AiLogicProcessor Processor { get; } + + public SleepAiMessage(AiLogicProcessor processor, bool sleep) + { + Processor = processor; + Sleep = sleep; + } + } +} \ No newline at end of file diff --git a/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs b/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs new file mode 100644 index 0000000000..f1cb864dbd --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/Atmos/GasTileOverlaySystem.cs @@ -0,0 +1,475 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using Content.Server.GameObjects.Components.Atmos; +using Content.Server.Interfaces.GameTicking; +using Content.Shared.Atmos; +using Content.Shared.GameObjects.EntitySystems.Atmos; +using JetBrains.Annotations; +using Robust.Server.Interfaces.Player; +using Robust.Server.Player; +using Robust.Shared.Enums; +using Robust.Shared.Interfaces.Configuration; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.Interfaces.Timing; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Timing; + +namespace Content.Server.GameObjects.EntitySystems.Atmos +{ + [UsedImplicitly] + internal sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem + { + [Robust.Shared.IoC.Dependency] private readonly IGameTiming _gameTiming = default!; + [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 = + new Dictionary(); + + /// + /// Gas data stored in chunks to make PVS / bubbling easier. + /// + private Dictionary> _overlay = + new Dictionary>(); + + /// + /// How far away do we update gas overlays (minimum; due to chunking further away tiles may also be updated). + /// + private float _updateRange; + // 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. + /// + private float _updateCooldown; + + public override void Initialize() + { + base.Initialize(); + + _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; + _mapManager.OnGridRemoved += OnGridRemoved; + _configManager.RegisterCVar("net.gasoverlaytickrate", 3.0f); + } + + public override void Shutdown() + { + base.Shutdown(); + _playerManager.PlayerStatusChanged -= OnPlayerStatusChanged; + _mapManager.OnGridRemoved -= OnGridRemoved; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Invalidate(GridId gridIndex, MapIndices indices) + { + if (!_invalidTiles.TryGetValue(gridIndex, out var existing)) + { + existing = new HashSet(); + _invalidTiles[gridIndex] = existing; + } + + existing.Add(indices); + } + + private GasOverlayChunk GetOrCreateChunk(GridId gridIndex, MapIndices indices) + { + if (!_overlay.TryGetValue(gridIndex, out var chunks)) + { + chunks = new Dictionary(); + _overlay[gridIndex] = chunks; + } + + var chunkIndices = GetGasChunkIndices(indices); + + if (!chunks.TryGetValue(chunkIndices, out var chunk)) + { + chunk = new GasOverlayChunk(gridIndex, chunkIndices); + chunks[chunkIndices] = chunk; + } + + return chunk; + } + + private void OnGridRemoved(GridId gridId) + { + if (_overlay.ContainsKey(gridId)) + { + _overlay.Remove(gridId); + } + } + + public void ResettingCleanup() + { + _invalidTiles.Clear(); + _overlay.Clear(); + + foreach (var (_, data) in _knownPlayerChunks) + { + data.Reset(); + } + } + + private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) + { + if (e.NewStatus != SessionStatus.InGame) + { + if (_knownPlayerChunks.ContainsKey(e.Session)) + { + _knownPlayerChunks.Remove(e.Session); + } + + return; + } + + if (!_knownPlayerChunks.ContainsKey(e.Session)) + { + _knownPlayerChunks[e.Session] = new PlayerGasOverlay(); + } + } + + /// + /// Checks whether the overlay-relevant data for a gas tile has been updated. + /// + /// + /// + /// + /// + /// true if updated + private bool TryRefreshTile(GridAtmosphereComponent gam, GasOverlayData oldTile, MapIndices indices, out GasOverlayData overlayData) + { + var tile = gam.GetTile(indices); + var tileData = new List(); + + for (byte i = 0; i < Atmospherics.TotalNumberOfGases; i++) + { + var gas = Atmospherics.GetGas(i); + var overlay = Atmospherics.GetOverlay(i); + if (overlay == null || tile.Air == null) continue; + + var moles = tile.Air.Gases[i]; + + if (moles < gas.GasMolesVisible) continue; + + var data = new GasData(i, (byte) (FloatMath.Clamp01(moles / gas.GasMolesVisibleMax) * 255)); + tileData.Add(data); + } + + overlayData = new GasOverlayData(tile.Hotspot.State, tile.Hotspot.Temperature, tileData.Count == 0 ? null : tileData.ToArray()); + + if (overlayData.Equals(oldTile)) + { + return false; + } + + return true; + } + + /// + /// Get every chunk in range of our entity that exists, including on other grids. + /// + /// + /// + 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)); + + foreach (var grid in _mapManager.FindGridsIntersecting(entity.Transform.MapID, worldBounds)) + { + if (!_overlay.TryGetValue(grid.Index, out var chunks)) + { + continue; + } + + var entityTile = grid.GetTileRef(entity.Transform.GridPosition).GridIndices; + + for (var x = -maxXDiff; x <= maxXDiff; x++) + { + for (var y = -maxYDiff; y <= maxYDiff; y++) + { + 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). + + var (xDiff, yDiff) = (chunkIndices.X - entityTile.X, chunkIndices.Y - entityTile.Y); + if (xDiff > 0 && xDiff > _updateRange || + yDiff > 0 && yDiff > _updateRange || + xDiff < 0 && Math.Abs(xDiff + ChunkSize) > _updateRange || + yDiff < 0 && Math.Abs(yDiff + ChunkSize) > _updateRange) continue; + + inRange.Add(chunk); + } + } + } + + return inRange; + } + + public override void Update(float frameTime) + { + AccumulatedFrameTime += frameTime; + _updateCooldown = 1 / _configManager.GetCVar("net.gasoverlaytickrate"); + + if (AccumulatedFrameTime < _updateCooldown) + { + 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. + foreach (var (gridId, indices) in _invalidTiles) + { + var gridEntityId = _mapManager.GetGrid(gridId).GridEntityId; + + if (!EntityManager.GetEntity(gridEntityId).TryGetComponent(out GridAtmosphereComponent? gam)) + { + continue; + } + + // If it's being invalidated it should have this right? + // At any rate we'll cache it for here + the AddChunk + if (!gridAtmosComponents.ContainsKey(gridId)) + { + gridAtmosComponents[gridId] = gam; + } + + foreach (var invalid in indices) + { + var chunk = GetOrCreateChunk(gridId, invalid); + + if (!TryRefreshTile(gam, chunk.GetData(invalid), invalid, out var data)) continue; + + if (!updatedTiles.TryGetValue(chunk, out var tiles)) + { + tiles = new HashSet(); + updatedTiles[chunk] = tiles; + } + + updatedTiles[chunk].Add(invalid); + chunk.Update(data, invalid); + } + } + + var currentTick = _gameTiming.CurTick; + + // Set the LastUpdate for chunks. + foreach (var (chunk, _) in updatedTiles) + { + chunk.Dirty(currentTick); + } + + // Now we'll go through each player, then through each chunk in range of that player checking if the player is still in range + // If they are, check if they need the new data to send (i.e. if there's an overlay for the gas). + // Afterwards we reset all the chunk data for the next time we tick. + 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)) + { + chunksToAdd.Add(chunk); + } + } + + foreach (var chunk in knownChunks) + { + if (!chunksInRange.Contains(chunk)) + { + chunksToRemove.Add(chunk); + } + } + + foreach (var chunk in chunksToAdd) + { + var message = overlay.AddChunk(currentTick, chunk); + if (message != null) + { + RaiseNetworkEvent(message, session.ConnectedClient); + } + } + + foreach (var chunk in chunksToRemove) + { + overlay.RemoveChunk(chunk); + } + + var clientInvalids = new Dictionary>(); + + // Check for any dirty chunks in range and bundle the data to send to the client. + foreach (var chunk in chunksInRange) + { + if (!updatedTiles.TryGetValue(chunk, out var invalids)) continue; + + if (!clientInvalids.TryGetValue(chunk.GridIndices, out var existingData)) + { + existingData = new List<(MapIndices, GasOverlayData)>(); + clientInvalids[chunk.GridIndices] = existingData; + } + + chunk.GetData(existingData, invalids); + } + + foreach (var (grid, data) in clientInvalids) + { + RaiseNetworkEvent(overlay.UpdateClient(grid, data), session.ConnectedClient); + } + } + + // Cleanup + _invalidTiles.Clear(); + } + private sealed class PlayerGasOverlay + { + private readonly Dictionary> _data = + new Dictionary>(); + + private readonly Dictionary _lastSent = + new Dictionary(); + + public GasOverlayMessage UpdateClient(GridId grid, List<(MapIndices, GasOverlayData)> data) + { + return new GasOverlayMessage(grid, data); + } + + public void Reset() + { + _data.Clear(); + _lastSent.Clear(); + } + + public List GetKnownChunks() + { + var known = new List(); + + foreach (var (_, chunks) in _data) + { + foreach (var (_, chunk) in chunks) + { + known.Add(chunk); + } + } + + return known; + } + + public GasOverlayMessage? AddChunk(GameTick currentTick, GasOverlayChunk chunk) + { + if (!_data.TryGetValue(chunk.GridIndices, out var chunks)) + { + chunks = new Dictionary(); + _data[chunk.GridIndices] = chunks; + } + + if (_lastSent.TryGetValue(chunk, out var last) && last >= chunk.LastUpdate) + { + return null; + } + + _lastSent[chunk] = currentTick; + var message = ChunkToMessage(chunk); + + return message; + } + + public void RemoveChunk(GasOverlayChunk chunk) + { + // Don't need to sync to client as they can manage it themself. + if (!_data.TryGetValue(chunk.GridIndices, out var chunks)) + { + return; + } + + if (chunks.ContainsKey(chunk.MapIndices)) + { + chunks.Remove(chunk.MapIndices); + } + } + + /// + /// Retrieve a whole chunk as a message, only getting the relevant tiles for the gas overlay. + /// + /// + /// + private GasOverlayMessage? ChunkToMessage(GasOverlayChunk chunk) + { + // 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++) + { + for (var y = 0; y < ChunkSize; y++) + { + // TODO: Check could be more robust I think. + var data = chunk.TileData[x, y]; + if ((data.Gas == null || data.Gas.Length == 0) && data.FireState == 0 && data.FireTemperature == 0.0f) + { + continue; + } + + var indices = new MapIndices(chunk.MapIndices.X + x, chunk.MapIndices.Y + y); + tileData.Add((indices, data)); + } + } + + if (tileData.Count == 0) + { + 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/BloodstreamSystem.cs b/Content.Server/GameObjects/EntitySystems/BloodstreamSystem.cs deleted file mode 100644 index fc6a69f325..0000000000 --- a/Content.Server/GameObjects/EntitySystems/BloodstreamSystem.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Content.Server.GameObjects.Components.Metabolism; -using JetBrains.Annotations; -using Robust.Shared.GameObjects.Systems; - -namespace Content.Server.GameObjects.EntitySystems -{ - /// - /// Triggers metabolism updates for - /// - [UsedImplicitly] - internal sealed class BloodstreamSystem : EntitySystem - { - private float _accumulatedFrameTime; - - public override void Update(float frameTime) - { - //Trigger metabolism updates at most once per second - _accumulatedFrameTime += frameTime; - if (_accumulatedFrameTime > 1.0f) - { - foreach (var component in ComponentManager.EntityQuery()) - { - component.OnUpdate(_accumulatedFrameTime); - } - _accumulatedFrameTime -= 1.0f; - } - } - } -} diff --git a/Content.Server/GameObjects/EntitySystems/BodySystem.cs b/Content.Server/GameObjects/EntitySystems/BodySystem.cs new file mode 100644 index 0000000000..32467e1844 --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/BodySystem.cs @@ -0,0 +1,29 @@ +using Content.Server.GameObjects.Components.Body; +using Content.Server.GameObjects.Components.Metabolism; +using JetBrains.Annotations; +using Robust.Shared.GameObjects.Systems; + +namespace Content.Server.GameObjects.EntitySystems +{ + [UsedImplicitly] + public class BodySystem : EntitySystem + { + public override void Update(float frameTime) + { + foreach (var body in ComponentManager.EntityQuery()) + { + body.PreMetabolism(frameTime); + } + + foreach (var metabolism in ComponentManager.EntityQuery()) + { + metabolism.Update(frameTime); + } + + foreach (var body in ComponentManager.EntityQuery()) + { + body.PostMetabolism(frameTime); + } + } + } +} diff --git a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs index 478f0123d2..d607e79d59 100644 --- a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Movement; @@ -12,6 +13,7 @@ using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Input; using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Physics; +using Content.Shared.Physics.Pull; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Server.Interfaces.Player; @@ -437,7 +439,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click /// Uses a weapon/object on an entity /// Finds components with the InteractUsing interface and calls their function /// - public void Interaction(IEntity user, IEntity weapon, IEntity attacked, GridCoordinates clickLocation) + public async Task Interaction(IEntity user, IEntity weapon, IEntity attacked, GridCoordinates clickLocation) { var attackMsg = new InteractUsingMessage(user, weapon, attacked, clickLocation); RaiseLocalEvent(attackMsg); @@ -446,7 +448,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click return; } - var attackBys = attacked.GetAllComponents().ToList(); + var attackBys = attacked.GetAllComponents().OrderByDescending(x => x.Priority); var attackByEventArgs = new InteractUsingEventArgs { User = user, ClickLocation = clickLocation, Using = weapon, Target = attacked @@ -457,7 +459,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click { foreach (var attackBy in attackBys) { - if (attackBy.InteractUsing(attackByEventArgs)) + if (await attackBy.InteractUsing(attackByEventArgs)) { // If an InteractUsing returns a status completion we finish our attack return; 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/ConstructionSystem.cs b/Content.Server/GameObjects/EntitySystems/ConstructionSystem.cs index b054fd5401..2138e559dc 100644 --- a/Content.Server/GameObjects/EntitySystems/ConstructionSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/ConstructionSystem.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Content.Server.GameObjects.Components.Construction; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Interactable; @@ -76,7 +77,7 @@ namespace Content.Server.GameObjects.EntitySystems TryStartItemConstruction(placingEnt, msg.PrototypeName); } - private void HandleToolInteraction(AfterInteractMessage msg) + private async void HandleToolInteraction(AfterInteractMessage msg) { if(msg.Handled) return; @@ -104,7 +105,7 @@ namespace Content.Server.GameObjects.EntitySystems // the target entity is in the process of being constructed/deconstructed if (msg.Attacked.TryGetComponent(out var constructComp)) { - var result = TryConstructEntity(constructComp, handEnt, msg.User); + var result = await TryConstructEntity(constructComp, handEnt, msg.User); // TryConstructEntity may delete the existing entity @@ -367,7 +368,7 @@ namespace Content.Server.GameObjects.EntitySystems } } - private bool TryConstructEntity(ConstructionComponent constructionComponent, IEntity handTool, IEntity user) + private async Task TryConstructEntity(ConstructionComponent constructionComponent, IEntity handTool, IEntity user) { var constructEntity = constructionComponent.Owner; var spriteComponent = constructEntity.GetComponent(); @@ -384,7 +385,7 @@ namespace Content.Server.GameObjects.EntitySystems var stage = constructPrototype.Stages[constructionComponent.Stage]; - if (TryProcessStep(constructEntity, stage.Forward, handTool, user, transformComponent.GridPosition)) + if (await TryProcessStep(constructEntity, stage.Forward, handTool, user, transformComponent.GridPosition)) { constructionComponent.Stage++; if (constructionComponent.Stage == constructPrototype.Stages.Count - 1) @@ -406,7 +407,7 @@ namespace Content.Server.GameObjects.EntitySystems } } - else if (TryProcessStep(constructEntity, stage.Backward, handTool, user, transformComponent.GridPosition)) + else if (await TryProcessStep(constructEntity, stage.Backward, handTool, user, transformComponent.GridPosition)) { constructionComponent.Stage--; stage = constructPrototype.Stages[constructionComponent.Stage]; @@ -443,7 +444,7 @@ namespace Content.Server.GameObjects.EntitySystems } } - private bool TryProcessStep(IEntity constructEntity, ConstructionStep step, IEntity slapped, IEntity user, GridCoordinates gridCoords) + private async Task TryProcessStep(IEntity constructEntity, ConstructionStep step, IEntity slapped, IEntity user, GridCoordinates gridCoords) { if (step == null) { @@ -473,9 +474,9 @@ namespace Content.Server.GameObjects.EntitySystems // Handle welder manually since tool steps specify fuel amount needed, for some reason. if (toolStep.ToolQuality.HasFlag(ToolQuality.Welding)) return slapped.TryGetComponent(out var welder) - && welder.UseTool(user, constructEntity, toolStep.ToolQuality, toolStep.Amount); + && await welder.UseTool(user, constructEntity, toolStep.DoAfterDelay, toolStep.ToolQuality, toolStep.Amount); - return tool.UseTool(user, constructEntity, toolStep.ToolQuality); + return await tool.UseTool(user, constructEntity, toolStep.DoAfterDelay, toolStep.ToolQuality); default: throw new NotImplementedException(); diff --git a/Content.Server/GameObjects/EntitySystems/DoAfter/DoAfter.cs b/Content.Server/GameObjects/EntitySystems/DoAfter/DoAfter.cs index 2a409d0bfe..2f45195624 100644 --- a/Content.Server/GameObjects/EntitySystems/DoAfter/DoAfter.cs +++ b/Content.Server/GameObjects/EntitySystems/DoAfter/DoAfter.cs @@ -5,6 +5,7 @@ using Content.Server.GameObjects.Components.Damage; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Damage; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Map; @@ -53,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; @@ -63,7 +64,7 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter AsTask = Tcs.Task; } - public void HandleDamage(object? sender, DamageEventArgs eventArgs) + public void HandleDamage(HealthChangedEventArgs args) { _tookDamage = true; } @@ -125,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; @@ -133,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/DoAfter/DoAfterSystem.cs b/Content.Server/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs index 1c01a789a0..1383dfa8e8 100644 --- a/Content.Server/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/DoAfter/DoAfterSystem.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Content.Server.GameObjects.Components; -using Content.Server.GameObjects.Components.Damage; +using Content.Shared.GameObjects.Components.Damage; using JetBrains.Annotations; using Robust.Server.Interfaces.Timing; using Robust.Shared.GameObjects.Systems; @@ -73,19 +73,19 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter // Caller's gonna be responsible for this I guess var doAfterComponent = eventArgs.User.GetComponent(); doAfterComponent.Add(doAfter); - DamageableComponent? damageableComponent = null; + IDamageableComponent? damageableComponent = null; // TODO: If the component's deleted this may not get unsubscribed? if (eventArgs.BreakOnDamage && eventArgs.User.TryGetComponent(out damageableComponent)) { - damageableComponent.Damaged += doAfter.HandleDamage; + damageableComponent.HealthChangedEvent += doAfter.HandleDamage; } await doAfter.AsTask; if (damageableComponent != null) { - damageableComponent.Damaged -= doAfter.HandleDamage; + damageableComponent.HealthChangedEvent -= doAfter.HandleDamage; } return doAfter.Status; diff --git a/Content.Server/GameObjects/EntitySystems/GasTileOverlaySystem.cs b/Content.Server/GameObjects/EntitySystems/GasTileOverlaySystem.cs deleted file mode 100644 index 493ef068ed..0000000000 --- a/Content.Server/GameObjects/EntitySystems/GasTileOverlaySystem.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using Content.Server.GameObjects.Components.Atmos; -using Content.Shared.Atmos; -using Content.Shared.GameObjects.EntitySystems; -using JetBrains.Annotations; -using Robust.Server.Interfaces.Player; -using Robust.Server.Player; -using Robust.Shared.Enums; -using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Interfaces.Map; -using Robust.Shared.IoC; -using Robust.Shared.Map; - -namespace Content.Server.GameObjects.EntitySystems -{ - [UsedImplicitly] - public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem - { - private int _tickTimer = 0; - private HashSet _queue = new HashSet(); - private Dictionary> _invalid = new Dictionary>(); - - private Dictionary> _overlay = - new Dictionary>(); - - [Robust.Shared.IoC.Dependency] private IPlayerManager _playerManager = default!; - - public override void Initialize() - { - base.Initialize(); - - _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Invalidate(GridId gridIndex, MapIndices indices) - { - if (!_invalid.TryGetValue(gridIndex, out var set) || set == null) - { - set = new HashSet(); - _invalid.Add(gridIndex, set); - } - - set.Add(indices); - } - - public void SetTileOverlay(GridId gridIndex, MapIndices indices, GasData[] gasData, int fireState = 0, float fireTemperature = 0f) - { - if(!_overlay.TryGetValue(gridIndex, out var _)) - _overlay[gridIndex] = new Dictionary(); - - _overlay[gridIndex][indices] = new GasOverlayData(fireState, fireTemperature, gasData); - _queue.Add(GetData(gridIndex, indices)); - } - - private void OnPlayerStatusChanged(object sender, SessionStatusEventArgs e) - { - if (e.NewStatus != SessionStatus.InGame) return; - - RaiseNetworkEvent(new GasTileOverlayMessage(GetData(), true), e.Session.ConnectedClient); - } - - private GasTileOverlayData[] GetData() - { - var list = new List(); - - foreach (var (gridId, tiles) in _overlay) - { - foreach (var (indices, _) in tiles) - { - var data = GetData(gridId, indices); - if(data.Data.Gas.Length > 0) - list.Add(data); - } - } - - return list.ToArray(); - } - - private GasTileOverlayData GetData(GridId gridIndex, MapIndices indices) - { - return new GasTileOverlayData(gridIndex, indices, _overlay[gridIndex][indices]); - } - - private void Revalidate() - { - var mapMan = IoCManager.Resolve(); - var entityMan = IoCManager.Resolve(); - var list = new List(); - - foreach (var (gridId, indices) in _invalid) - { - if (!mapMan.GridExists(gridId)) - { - _invalid.Remove(gridId); - return; - } - var grid = entityMan.GetEntity(mapMan.GetGrid(gridId).GridEntityId); - if (!grid.TryGetComponent(out GridAtmosphereComponent gam)) continue; - - foreach (var index in indices) - { - var tile = gam.GetTile(index); - - if (tile?.Air == null) continue; - - list.Clear(); - - for(var i = 0; i < Atmospherics.TotalNumberOfGases; i++) - { - var gas = Atmospherics.GetGas(i); - var overlay = gas.GasOverlay; - if (overlay == null) continue; - var moles = tile.Air.Gases[i]; - if(moles == 0f || moles < gas.GasMolesVisible) continue; - list.Add(new GasData(i, MathF.Max(MathF.Min(1, moles / gas.GasMolesVisibleMax), 0f))); - } - - if (list.Count == 0) continue; - - SetTileOverlay(gridId, index, list.ToArray(), tile.Hotspot.State, tile.Hotspot.Temperature); - } - - indices.Clear(); - } - } - - public override void Update(float frameTime) - { - _tickTimer++; - - Revalidate(); - - if (_tickTimer < 10) return; - - _tickTimer = 0; - if(_queue.Count > 0) - RaiseNetworkEvent(new GasTileOverlayMessage(_queue.ToArray())); - _queue.Clear(); - } - } -} diff --git a/Content.Server/GameObjects/EntitySystems/GasVaporSystem.cs b/Content.Server/GameObjects/EntitySystems/GasVaporSystem.cs new file mode 100644 index 0000000000..a12600f634 --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/GasVaporSystem.cs @@ -0,0 +1,20 @@ +using Content.Server.Atmos; +using Robust.Shared.GameObjects.Systems; + +namespace Content.Server.GameObjects.EntitySystems +{ + public class GasVaporSystem : EntitySystem + { + /// + public override void Update(float frameTime) + { + foreach (var GasVapor in ComponentManager.EntityQuery()) + { + if (GasVapor.Initialized) + { + GasVapor.Update(frameTime); + } + } + } + } +} diff --git a/Content.Server/GameObjects/EntitySystems/HungerSystem.cs b/Content.Server/GameObjects/EntitySystems/HungerSystem.cs index cddc2b76dd..bd8cd6a866 100644 --- a/Content.Server/GameObjects/EntitySystems/HungerSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/HungerSystem.cs @@ -5,20 +5,21 @@ using Robust.Shared.GameObjects.Systems; namespace Content.Server.GameObjects.EntitySystems { [UsedImplicitly] - internal sealed class HungerSystem : EntitySystem + public class HungerSystem : EntitySystem { private float _accumulatedFrameTime; - + public override void Update(float frameTime) { _accumulatedFrameTime += frameTime; - if (_accumulatedFrameTime > 1.0f) + + if (_accumulatedFrameTime > 1) { foreach (var comp in ComponentManager.EntityQuery()) { comp.OnUpdate(_accumulatedFrameTime); } - _accumulatedFrameTime -= 1.0f; + _accumulatedFrameTime -= 1; } } } 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 898ed90475..28efbd4f8a 100644 --- a/Content.Server/GameObjects/EntitySystems/PointingSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/PointingSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Input; using Content.Shared.Interfaces; using JetBrains.Annotations; +using Robust.Server.GameObjects.Components; using Robust.Server.Interfaces.Player; using Robust.Server.Player; using Robust.Shared.Enums; @@ -113,7 +114,13 @@ namespace Content.Server.GameObjects.EntitySystems var viewers = _playerManager.GetPlayersInRange(player.Transform.GridPosition, 15); - EntityManager.SpawnEntity("pointingarrow", coords); + var arrow = EntityManager.SpawnEntity("pointingarrow", coords); + + if (player.TryGetComponent(out VisibilityComponent? playerVisibility)) + { + var arrowVisibility = arrow.EnsureComponent(); + arrowVisibility.Layer = playerVisibility.Layer; + } string selfMessage; string viewerMessage; diff --git a/Content.Server/GameObjects/EntitySystems/StationEvents/RadiationPulseSystem.cs b/Content.Server/GameObjects/EntitySystems/StationEvents/RadiationPulseSystem.cs index 2e48bf7120..a40029da44 100644 --- a/Content.Server/GameObjects/EntitySystems/StationEvents/RadiationPulseSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/StationEvents/RadiationPulseSystem.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using Content.Server.GameObjects.Components.Damage; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.StationEvents; +using Content.Shared.Damage; using Content.Shared.GameObjects; +using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Damage; using JetBrains.Annotations; using Robust.Shared.GameObjects; @@ -28,13 +30,13 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents private const int DamageThreshold = 10; private Dictionary _accumulatedDamage = new Dictionary(); - + public override void Initialize() { base.Initialize(); - _speciesQuery = new TypeEntityQuery(typeof(SpeciesComponent)); + _speciesQuery = new TypeEntityQuery(typeof(IBodyManagerComponent)); } - + public override void Update(float frameTime) { base.Update(frameTime); @@ -72,7 +74,7 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents var damageMultiple = (int) (totalDamage / DamageThreshold); _accumulatedDamage[species] = totalDamage % DamageThreshold; - damageableComponent.TakeDamage(DamageType.Heat, damageMultiple * DamageThreshold, comp.Owner, comp.Owner); + damageableComponent.ChangeDamage(DamageType.Heat, damageMultiple * DamageThreshold, false, comp.Owner); } } @@ -80,10 +82,10 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents { return; } - + // probably don't need to worry about clearing this at roundreset unless you have a radiation pulse at roundstart // (which is currently not possible) _accumulatedDamage.Clear(); } } -} \ No newline at end of file +} diff --git a/Content.Server/GameObjects/EntitySystems/StationEvents/StationEventSystem.cs b/Content.Server/GameObjects/EntitySystems/StationEvents/StationEventSystem.cs index 1885b32231..c34a502b21 100644 --- a/Content.Server/GameObjects/EntitySystems/StationEvents/StationEventSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/StationEvents/StationEventSystem.cs @@ -2,25 +2,34 @@ 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; using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.Network; using Robust.Shared.Interfaces.Random; using Robust.Shared.Interfaces.Reflection; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Localization; +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 { - // Somewhat based off of TG's implementation of events - - public StationEvent CurrentEvent { get; private set; } +#pragma warning disable 649 + [Dependency] private readonly IServerNetManager _netManager; + [Dependency] private readonly IPlayerManager _playerManager; + [Dependency] private readonly IGameTicker _gameTicker; +#pragma warning restore 649 + public StationEvent CurrentEvent { get; private set; } public IReadOnlyCollection StationEvents => _stationEvents; + private List _stationEvents = new List(); private const float MinimumTimeUntilFirstEvent = 600; @@ -154,8 +163,31 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents var stationEvent = (StationEvent) typeFactory.CreateInstance(type); _stationEvents.Add(stationEvent); } + + _netManager.RegisterNetMessage(nameof(MsgGetStationEvents), GetEventReceived); } + private void GetEventReceived(MsgGetStationEvents msg) + { + var player = _playerManager.GetSessionByChannel(msg.MsgChannel); + SendEvents(player); + } + + private void SendEvents(IPlayerSession player) + { + if (!IoCManager.Resolve().CanCommand(player, "events")) + return; + + var newMsg = _netManager.CreateNetMessage(); + newMsg.Events = new List(); + foreach (var e in StationEvents) + { + newMsg.Events.Add(e.Name); + } + _netManager.ServerSendMessage(newMsg, player.ConnectedClient); + } + + public override void Update(float frameTime) { base.Update(frameTime); @@ -164,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) { @@ -318,4 +361,4 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents CurrentEvent?.Shutdown(); } } -} \ No newline at end of file +} diff --git a/Content.Server/GameObjects/EntitySystems/StomachSystem.cs b/Content.Server/GameObjects/EntitySystems/StomachSystem.cs deleted file mode 100644 index ffc16af206..0000000000 --- a/Content.Server/GameObjects/EntitySystems/StomachSystem.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Content.Server.GameObjects.Components.Nutrition; -using JetBrains.Annotations; -using Robust.Shared.GameObjects.Systems; - -namespace Content.Server.GameObjects.EntitySystems -{ - /// - /// Triggers digestion updates on - /// - [UsedImplicitly] - internal sealed class StomachSystem : EntitySystem - { - private float _accumulatedFrameTime; - - public override void Update(float frameTime) - { - //Update at most once per second - _accumulatedFrameTime += frameTime; - if (_accumulatedFrameTime > 1.0f) - { - foreach (var component in ComponentManager.EntityQuery()) - { - component.OnUpdate(_accumulatedFrameTime); - } - _accumulatedFrameTime -= 1.0f; - } - } - } -} diff --git a/Content.Server/GameObjects/EntitySystems/ThirstSystem.cs b/Content.Server/GameObjects/EntitySystems/ThirstSystem.cs index b6047a273c..dbb8816a49 100644 --- a/Content.Server/GameObjects/EntitySystems/ThirstSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/ThirstSystem.cs @@ -5,20 +5,21 @@ using Robust.Shared.GameObjects.Systems; namespace Content.Server.GameObjects.EntitySystems { [UsedImplicitly] - internal sealed class ThirstSystem : EntitySystem + public class ThirstSystem : EntitySystem { private float _accumulatedFrameTime; public override void Update(float frameTime) { _accumulatedFrameTime += frameTime; - if (_accumulatedFrameTime > 1.0f) + + if (_accumulatedFrameTime > 1) { foreach (var component in ComponentManager.EntityQuery()) { component.OnUpdate(_accumulatedFrameTime); } - _accumulatedFrameTime -= 1.0f; + _accumulatedFrameTime -= 1; } } } diff --git a/Content.Server/GameObjects/EntitySystems/VerbSystem.cs b/Content.Server/GameObjects/EntitySystems/VerbSystem.cs index 6de086226a..761681b1ac 100644 --- a/Content.Server/GameObjects/EntitySystems/VerbSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/VerbSystem.cs @@ -113,8 +113,17 @@ namespace Content.Server.GameObjects.EntitySystems if (verb.RequireInteractionRange && !VerbUtility.InVerbUseRange(userEntity, entity)) continue; - if (verb.BlockedByContainers && !userEntity.IsInSameOrNoContainer(entity)) - continue; + if (verb.BlockedByContainers) + { + if (!userEntity.IsInSameOrNoContainer(entity)) + { + if (!ContainerHelpers.TryGetContainer(entity, out var container) || + container.Owner != userEntity) + { + continue; + } + } + } var verbData = verb.GetData(userEntity, component); if (verbData.IsInvisible) 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 093135da42..c77b8a110e 100644 --- a/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs +++ b/Content.Server/GameTicking/GamePresets/PresetSuspicion.cs @@ -10,11 +10,12 @@ using Robust.Shared.Prototypes; using Robust.Shared.Random; using System.Collections.Generic; using System.Linq; +using Content.Server.GameObjects.Components.Suspicion; using Content.Shared.Roles; +using Robust.Shared.GameObjects; using Robust.Shared.Log; using Robust.Shared.Maths; - namespace Content.Server.GameTicking.GamePresets { public class PresetSuspicion : GamePreset @@ -29,6 +30,9 @@ namespace Content.Server.GameTicking.GamePresets public int MinPlayers { get; set; } = 5; public int MinTraitors { get; set; } = 2; public int PlayersPerTraitor { get; set; } = 5; + + public override bool DisallowLateJoin => true; + private static string TraitorID = "SuspicionTraitor"; private static string InnocentID = "SuspicionInnocent"; @@ -40,6 +44,12 @@ namespace Content.Server.GameTicking.GamePresets return false; } + if (readyPlayers.Count == 0) + { + _chatManager.DispatchServerAnnouncement($"No players readied up! Can't start Suspicion."); + return false; + } + var list = new List(readyPlayers); var prefList = new List(); @@ -54,10 +64,12 @@ namespace Content.Server.GameTicking.GamePresets { prefList.Add(player); } + + player.AttachedEntity?.EnsureComponent(); } - var numTraitors = MathHelper.Clamp(readyPlayers.Count % PlayersPerTraitor, - MinTraitors, readyPlayers.Count); + var numTraitors = FloatMath.Clamp(readyPlayers.Count / PlayersPerTraitor, + MinTraitors, readyPlayers.Count); for (var i = 0; i < numTraitors; i++) { diff --git a/Content.Server/GameTicking/GameRules/RuleDeathMatch.cs b/Content.Server/GameTicking/GameRules/RuleDeathMatch.cs index 05b84f8a1b..cd108324e0 100644 --- a/Content.Server/GameTicking/GameRules/RuleDeathMatch.cs +++ b/Content.Server/GameTicking/GameRules/RuleDeathMatch.cs @@ -1,8 +1,8 @@ using System; using System.Threading; -using Content.Server.GameObjects.Components.Mobs; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameTicking; +using Content.Shared.GameObjects.Components.Damage; using Robust.Server.Interfaces.Player; using Robust.Server.Player; using Robust.Shared.Enums; @@ -34,7 +34,7 @@ namespace Content.Server.GameTicking.GameRules { _chatManager.DispatchServerAnnouncement("The game is now a death match. Kill everybody else to win!"); - _entityManager.EventBus.SubscribeEvent(EventSource.Local, this, _onMobDamageStateChanged); + _entityManager.EventBus.SubscribeEvent(EventSource.Local, this, OnHealthChanged); _playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged; } @@ -42,11 +42,11 @@ namespace Content.Server.GameTicking.GameRules { base.Removed(); - _entityManager.EventBus.UnsubscribeEvent(EventSource.Local, this); + _entityManager.EventBus.UnsubscribeEvent(EventSource.Local, this); _playerManager.PlayerStatusChanged -= PlayerManagerOnPlayerStatusChanged; } - private void _onMobDamageStateChanged(MobDamageStateChangedMessage message) + private void OnHealthChanged(HealthChangedEventArgs message) { _runDelayedCheck(); } @@ -59,12 +59,12 @@ namespace Content.Server.GameTicking.GameRules foreach (var playerSession in _playerManager.GetAllPlayers()) { if (playerSession.AttachedEntity == null - || !playerSession.AttachedEntity.TryGetComponent(out SpeciesComponent species)) + || !playerSession.AttachedEntity.TryGetComponent(out IDamageableComponent damageable)) { continue; } - if (!species.CurrentDamageState.IsConscious) + if (damageable.CurrentDamageState != DamageState.Alive) { continue; } diff --git a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs index 34ddc50f78..026c11516a 100644 --- a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs +++ b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs @@ -1,13 +1,13 @@ -using System; +using System; using System.Threading; -using Content.Server.GameObjects.Components.Mobs; +using Content.Server.GameObjects.Components.Suspicion; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameTicking; using Content.Server.Mobs.Roles; using Content.Server.Players; +using Content.Shared.GameObjects.Components.Damage; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; -using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Timer = Robust.Shared.Timers.Timer; @@ -24,7 +24,6 @@ namespace Content.Server.GameTicking.GameRules #pragma warning disable 649 [Dependency] private readonly IPlayerManager _playerManager; [Dependency] private readonly IChatManager _chatManager; - [Dependency] private readonly IEntityManager _entityManager; [Dependency] private readonly IGameTicker _gameTicker; #pragma warning restore 649 @@ -32,28 +31,11 @@ namespace Content.Server.GameTicking.GameRules public override void Added() { - _entityManager.EventBus.SubscribeEvent(EventSource.Local, this, _onMobDamageStateChanged); + _chatManager.DispatchServerAnnouncement("There are traitors on the station! Find them, and kill them!"); Timer.SpawnRepeating(DeadCheckDelay, _checkWinConditions, _checkTimerCancel.Token); } - private void _onMobDamageStateChanged(MobDamageStateChangedMessage message) - { - var owner = message.Species.Owner; - - if (!(message.Species.CurrentDamageState is DeadState)) - return; - - if (!owner.TryGetComponent(out var mind)) - return; - - if (!mind.HasMind) - return; - - message.Species.Owner.Description += - mind.Mind.HasRole() ? "\nThey were a traitor!" : "\nThey were an innocent!"; - } - public override void Removed() { base.Removed(); @@ -69,15 +51,17 @@ namespace Content.Server.GameTicking.GameRules foreach (var playerSession in _playerManager.GetAllPlayers()) { if (playerSession.AttachedEntity == null - || !playerSession.AttachedEntity.TryGetComponent(out SpeciesComponent species)) + || !playerSession.AttachedEntity.TryGetComponent(out IDamageableComponent damageable) + || !playerSession.AttachedEntity.TryGetComponent(out SuspicionRoleComponent suspicionRole)) { continue; } - if (!species.CurrentDamageState.IsConscious) + if (damageable.CurrentDamageState != DamageState.Alive) { continue; } + if (playerSession.ContentData().Mind.HasRole()) traitorsAlive++; else @@ -87,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 bef68e027d..49fb6c7157 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -13,6 +13,7 @@ using Content.Server.GameObjects.Components.PDA; using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems.AI.Pathfinding; using Content.Server.GameObjects.EntitySystems.AI.Pathfinding.Accessible; +using Content.Server.GameObjects.EntitySystems.Atmos; using Content.Server.GameObjects.EntitySystems.StationEvents; using Content.Server.GameTicking.GamePresets; using Content.Server.Interfaces; @@ -24,6 +25,7 @@ using Content.Server.Players; using Content.Shared; using Content.Shared.Chat; using Content.Shared.GameObjects.Components.PDA; +using Content.Shared.Network.NetMessages; using Content.Shared.Preferences; using Content.Shared.Roles; using Prometheus; @@ -47,6 +49,7 @@ using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; +using Robust.Shared.Network; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; @@ -91,6 +94,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; @@ -136,7 +141,10 @@ namespace Content.Server.GameTicking _netManager.RegisterNetMessage(nameof(MsgTickerLobbyStatus)); _netManager.RegisterNetMessage(nameof(MsgTickerLobbyInfo)); _netManager.RegisterNetMessage(nameof(MsgTickerLobbyCountdown)); + _netManager.RegisterNetMessage(nameof(MsgTickerLobbyReady)); _netManager.RegisterNetMessage(nameof(MsgRoundEndMessage)); + _netManager.RegisterNetMessage(nameof(MsgRequestWindowAttention)); + _netManager.RegisterNetMessage(nameof(MsgTickerLateJoinStatus)); SetStartPreset(_configurationManager.GetCVar("game.defaultpreset")); @@ -206,6 +214,16 @@ namespace Content.Server.GameTicking _roundStartTimeUtc = DateTime.UtcNow + LobbyDuration; _sendStatusToAll(); + + ReqWindowAttentionAll(); + } + } + + private void ReqWindowAttentionAll() + { + foreach (var player in _playerManager.GetAllPlayers()) + { + player.RequestWindowAttention(); } } @@ -270,6 +288,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")); @@ -280,10 +300,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) @@ -298,7 +329,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!"); @@ -308,6 +339,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); @@ -367,6 +399,7 @@ namespace Content.Server.GameTicking _playersInLobby[player] = ready; _netManager.ServerSendMessage(_getStatusMsg(player), player.ConnectedClient); + _netManager.ServerSendToAll(GetReadySingle(player, ready)); } public T AddGameRule() where T : GameRule, new() @@ -383,12 +416,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; } @@ -621,6 +654,7 @@ namespace Content.Server.GameTicking _playerJoinLobby(player); } + EntitySystem.Get().ResettingCleanup(); EntitySystem.Get().ResettingCleanup(); EntitySystem.Get().ResettingCleanup(); EntitySystem.Get().ResetLayouts(); @@ -628,6 +662,7 @@ namespace Content.Server.GameTicking _spawnedPositions.Clear(); _manifest.Clear(); + DisallowLateJoin = false; } private void _preRoundSetup() @@ -758,6 +793,12 @@ namespace Content.Server.GameTicking string jobId = null, bool lateJoin = true) { + if (lateJoin && DisallowLateJoin) + { + MakeObserve(session); + return; + } + _playerJoinGame(session); var data = session.ContentData(); @@ -856,6 +897,7 @@ namespace Content.Server.GameTicking _netManager.ServerSendMessage(_netManager.CreateNetMessage(), session.ConnectedClient); _netManager.ServerSendMessage(_getStatusMsg(session), session.ConnectedClient); _netManager.ServerSendMessage(GetInfoMsg(), session.ConnectedClient); + _netManager.ServerSendMessage(GetReadyStatus(), session.ConnectedClient); } private void _playerJoinGame(IPlayerSession session) @@ -867,6 +909,26 @@ namespace Content.Server.GameTicking _netManager.ServerSendMessage(_netManager.CreateNetMessage(), session.ConnectedClient); } + private MsgTickerLobbyReady GetReadyStatus() + { + var msg = _netManager.CreateNetMessage(); + msg.PlayerReady = new Dictionary(); + foreach (var player in _playersInLobby.Keys) + { + _playersInLobby.TryGetValue(player, out var ready); + msg.PlayerReady.Add(player.SessionId, ready); + } + return msg; + } + + private MsgTickerLobbyReady GetReadySingle(IPlayerSession player, bool ready) + { + var msg = _netManager.CreateNetMessage(); + msg.PlayerReady = new Dictionary(); + msg.PlayerReady.Add(player.SessionId, ready); + return msg; + } + private MsgTickerLobbyStatus _getStatusMsg(IPlayerSession session) { _playersInLobby.TryGetValue(session, out var ready); diff --git a/Content.Server/GameTicking/GameTickerCommands.cs b/Content.Server/GameTicking/GameTickerCommands.cs index 1d37695a78..a43f2d9498 100644 --- a/Content.Server/GameTicking/GameTickerCommands.cs +++ b/Content.Server/GameTicking/GameTickerCommands.cs @@ -1,12 +1,7 @@ using System; using System.Collections.Generic; -using System.Linq; -using Content.Server.Health.BodySystem; -using Content.Server.Health.BodySystem.BodyPart; using Content.Server.Interfaces.GameTicking; using Content.Server.Players; -using Content.Shared.Health.BodySystem; -using Content.Shared.Health.BodySystem.BodyPart; using Content.Shared.Maps; using Content.Shared.Roles; using Robust.Server.Interfaces.Console; @@ -14,12 +9,10 @@ using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects.Components.Transform; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; -using Robust.Shared.Interfaces.Random; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Prototypes; -using Robust.Shared.Random; using Robust.Shared.Utility; namespace Content.Server.GameTicking @@ -334,80 +327,6 @@ namespace Content.Server.GameTicking } } - class AddHandCommand : IClientCommand - { - public string Command => "addhand"; - public string Description => "Adds a hand to your entity."; - public string Help => $"Usage: {Command}"; - - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) - { - if (player == null) - { - shell.SendText((IPlayerSession) null, "Only a player can run this command."); - return; - } - - if (player.AttachedEntity == null) - { - shell.SendText(player, "You have no entity."); - return; - } - - 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." : ".")}"; - - shell.SendText(player, text); - return; - } - - var prototypeManager = IoCManager.Resolve(); - prototypeManager.TryIndex("bodyPart.Hand.BasicHuman", out BodyPartPrototype prototype); - - var part = new BodyPart(prototype); - var slot = part.GetHashCode().ToString(); - - body.Template.Slots.Add(slot, BodyPartType.Hand); - body.InstallBodyPart(part, slot); - } - } - - class RemoveHandCommand : IClientCommand - { - public string Command => "removehand"; - public string Description => "Removes a hand from your entity."; - public string Help => $"Usage: {Command}"; - - public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) - { - if (player == null) - { - shell.SendText((IPlayerSession) null, "Only a player can run this command."); - return; - } - - if (player.AttachedEntity == null) - { - shell.SendText(player, "You have no entity."); - return; - } - - var manager = player.AttachedEntity.GetComponent(); - var hand = manager.PartDictionary.FirstOrDefault(x => x.Value.PartType == BodyPartType.Hand); - if (hand.Value == null) - { - shell.SendText(player, "You have no hands."); - return; - } - else - { - manager.DisconnectBodyPart(hand.Value, true); - } - } - } - class TileWallsCommand : IClientCommand { // ReSharper disable once StringLiteralTypo diff --git a/Content.Server/GlobalVerbs/PullingVerb.cs b/Content.Server/GlobalVerbs/PullingVerb.cs index 0b864eb075..6213d443a5 100644 --- a/Content.Server/GlobalVerbs/PullingVerb.cs +++ b/Content.Server/GlobalVerbs/PullingVerb.cs @@ -4,6 +4,7 @@ using Content.Shared.GameObjects.Components.Items; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.Verbs; using Content.Shared.Physics; +using Content.Shared.Physics.Pull; using Robust.Server.Interfaces.GameObjects; using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.GameObjects; diff --git a/Content.Server/GlobalVerbs/RejuvenateVerb.cs b/Content.Server/GlobalVerbs/RejuvenateVerb.cs index 5f074e085b..8d49222da8 100644 --- a/Content.Server/GlobalVerbs/RejuvenateVerb.cs +++ b/Content.Server/GlobalVerbs/RejuvenateVerb.cs @@ -1,6 +1,6 @@ -using Content.Server.GameObjects.Components.Damage; -using Content.Server.GameObjects.Components.Mobs; +using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Nutrition; +using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Verbs; using Robust.Server.Console; using Robust.Server.Interfaces.GameObjects; @@ -28,7 +28,7 @@ namespace Content.Server.GlobalVerbs if (user.TryGetComponent(out var player)) { - if (!target.HasComponent() && !target.HasComponent() && + if (!target.HasComponent() && !target.HasComponent() && !target.HasComponent()) { return; @@ -50,20 +50,24 @@ namespace Content.Server.GlobalVerbs PerformRejuvenate(target); } } + public static void PerformRejuvenate(IEntity target) { - if (target.TryGetComponent(out DamageableComponent damage)) + if (target.TryGetComponent(out IDamageableComponent damage)) { - damage.HealAllDamage(); + damage.Heal(); } + if (target.TryGetComponent(out HungerComponent hunger)) { hunger.ResetFood(); } + if (target.TryGetComponent(out ThirstComponent thirst)) { thirst.ResetThirst(); } + if (target.TryGetComponent(out StunnableComponent stun)) { stun.ResetStuns(); diff --git a/Content.Server/Health/BodySystem/BodyManagerComponent.cs b/Content.Server/Health/BodySystem/BodyManagerComponent.cs deleted file mode 100644 index c60ed9091c..0000000000 --- a/Content.Server/Health/BodySystem/BodyManagerComponent.cs +++ /dev/null @@ -1,402 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Content.Server.Health.BodySystem.BodyPart; -using Content.Server.Health.BodySystem.BodyPreset; -using Content.Server.Health.BodySystem.BodyTemplate; -using Content.Server.Interfaces.GameObjects.Components.Interaction; -using Content.Shared.Health.BodySystem; -using Content.Shared.Health.BodySystem.BodyPart; -using Content.Shared.Health.BodySystem.BodyPreset; -using Content.Shared.Health.BodySystem.BodyTemplate; -using Robust.Shared.GameObjects; -using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; -using Robust.Shared.ViewVariables; - -namespace Content.Server.Health.BodySystem { - - /// - /// Component representing a collection of BodyParts attached to each other. - /// - [RegisterComponent] - public class BodyManagerComponent : Component, IBodyPartContainer { - - public sealed override string Name => "BodyManager"; -#pragma warning disable CS0649 - [Dependency] private IPrototypeManager _prototypeManager; -#pragma warning restore - - [ViewVariables] - private BodyTemplate.BodyTemplate _template; - - [ViewVariables] - private string _presetName; - - [ViewVariables] - private Dictionary _partDictionary = new Dictionary(); - - /// - /// The that this BodyManagerComponent is adhering to. - /// - public BodyTemplate.BodyTemplate Template => _template; - - /// - /// Maps slot name to the object filling it (if there is one). - /// - public Dictionary PartDictionary => _partDictionary; - - /// - /// List of all occupied slots in this body, taken from the values of _parts. - /// - public IEnumerable AllSlots - { - get - { - return _template.Slots.Keys; - } - } - - /// - /// List of all occupied slots in this body, taken from the values of _parts. - /// - public IEnumerable OccupiedSlots - { - get - { - return _partDictionary.Keys; - } - } - - /// - /// List of all BodyParts in this body, taken from the keys of _parts. - /// - public IEnumerable Parts { - get { - return _partDictionary.Values; - } - } - - /// - /// Recursive search that returns whether a given is connected to the center . - /// Not efficient (O(n^2)), but most bodies don't have a ton of BodyParts. - /// - public bool ConnectedToCenterPart(BodyPart.BodyPart target) { - List searchedSlots = new List { }; - if (TryGetSlotName(target, out string result)) - return false; - return ConnectedToCenterPartRecursion(searchedSlots, result); - } - private bool ConnectedToCenterPartRecursion(List searchedSlots, string slotName) { - TryGetBodyPart(slotName, out BodyPart.BodyPart part); - if (part != null && part == GetCenterBodyPart()) - return true; - searchedSlots.Add(slotName); - if (TryGetBodyPartConnections(slotName, out List connections)) { - foreach (string connection in connections) { - if (!searchedSlots.Contains(connection) && ConnectedToCenterPartRecursion(searchedSlots, connection)) - return true; - } - } - return false; - - } - - /// - /// Returns the central of this body based on the . For humans, this is the torso. Returns null if not found. - /// - public BodyPart.BodyPart GetCenterBodyPart() { - _partDictionary.TryGetValue(_template.CenterSlot, out BodyPart.BodyPart center); - return center; - } - - /// - /// Returns whether the given slot name exists within the current . - /// - public bool SlotExists(string slotName) - { - return _template.SlotExists(slotName); - } - - - /// - /// Grabs the in the given slotName if there is one. Returns true if a is found, - /// false otherwise. If false, result will be null. - /// - public bool TryGetBodyPart(string slotName, out BodyPart.BodyPart result) { - return _partDictionary.TryGetValue(slotName, out result); - } - - /// - /// Grabs the slotName that the given resides in. Returns true if the is - /// part of this body and a slot is found, false otherwise. If false, result will be null. - /// - public bool TryGetSlotName(BodyPart.BodyPart part, out string result) { - result = _partDictionary.FirstOrDefault(x => x.Value == part).Key; //We enforce that there is only one of each value in the dictionary, so we can iterate through the dictionary values to get the key from there. - return result == null; - } - - /// - /// Grabs the of the given slotName if there is one. Returns true if the slot is found, false otherwise. If false, result will be null. - /// - public bool TryGetSlotType(string slotName, out BodyPartType result) - { - return _template.Slots.TryGetValue(slotName, out result); - } - - /// - /// Grabs the names of all connected slots to the given slotName from the template. Returns true if connections are found to the slotName, false otherwise. If false, connections will be null. - /// - public bool TryGetBodyPartConnections(string slotName, out List connections) { - return _template.Connections.TryGetValue(slotName, out connections); - } - - /// - /// Grabs all occupied slots connected to the given slot, regardless of whether the target slot is occupied. Returns true if successful, false if there was an error or no connected BodyParts were found. - /// - public bool TryGetBodyPartConnections(string slotName, out List result) - { - result = null; - if (!_template.Connections.TryGetValue(slotName, out List connections)) - return false; - List toReturn = new List(); - foreach (string connection in connections) - { - if (TryGetBodyPart(connection, out BodyPart.BodyPart bodyPartResult)) - { - toReturn.Add(bodyPartResult); - } - } - if (toReturn.Count <= 0) - return false; - result = toReturn; - return true; - } - - - - - - - - ///////// - ///////// Server-specific stuff - ///////// - - public override void ExposeData(ObjectSerializer serializer) { - base.ExposeData(serializer); - - serializer.DataReadWriteFunction( - "BaseTemplate", - "bodyTemplate.Humanoid", - template => - { - if (!_prototypeManager.TryIndex(template, out BodyTemplatePrototype templateData)) - { - throw new InvalidOperationException("No BodyTemplatePrototype was found with the name " + template + " while loading a BodyTemplate!"); //Should never happen unless you fuck up the prototype. - } - - _template = new BodyTemplate.BodyTemplate(templateData); - }, - () => _template.Name); - - serializer.DataReadWriteFunction( - "BasePreset", - "bodyPreset.BasicHuman", - preset => - { - if (!_prototypeManager.TryIndex(preset, out BodyPresetPrototype presetData)) - { - throw new InvalidOperationException("No BodyPresetPrototype was found with the name " + preset + " while loading a BodyPreset!"); //Should never happen unless you fuck up the prototype. - } - - LoadBodyPreset(new BodyPreset.BodyPreset(presetData)); - }, - () => _presetName); - } - - /// - /// Loads the given - forcefully changes all limbs found in both the preset and this template! - /// - public void LoadBodyPreset(BodyPreset.BodyPreset preset) - { - _presetName = preset.Name; - - foreach (var (slotName, type) in _template.Slots) { - if (!preset.PartIDs.TryGetValue(slotName, out string partID)) { //For each slot in our BodyManagerComponent's template, try and grab what the ID of what the preset says should be inside it. - continue; //If the preset doesn't define anything for it, continue. - } - if (!_prototypeManager.TryIndex(partID, out BodyPartPrototype newPartData)) { //Get the BodyPartPrototype corresponding to the BodyPart ID we grabbed. - throw new InvalidOperationException("BodyPart prototype with ID " + partID + " could not be found!"); - } - - //Try and remove an existing limb if that exists. - if (_partDictionary.Remove(slotName, out var removedPart)) - { - BodyPartRemoved(removedPart, slotName); - } - - var addedPart = new BodyPart.BodyPart(newPartData); - _partDictionary.Add(slotName, addedPart); //Add a new BodyPart with the BodyPartPrototype as a baseline to our BodyComponent. - BodyPartAdded(addedPart, slotName); - } - } - - /// - /// Changes the current to the given . Attempts to keep previous BodyParts - /// if there is a slot for them in both . - /// - public void ChangeBodyTemplate(BodyTemplatePrototype newTemplate) { - foreach (KeyValuePair part in _partDictionary) { - //TODO: Make this work. - } - } - - /// - /// Grabs all BodyParts of the given type in this body. - /// - public List GetBodyPartsOfType(BodyPartType type) { - List toReturn = new List(); - foreach (var (slotName, bodyPart) in _partDictionary) { - if (bodyPart.PartType == type) - toReturn.Add(bodyPart); - } - return toReturn; - } - - - - /// - /// Installs the given into the given slot. Returns true if successful, false otherwise. - /// - public bool InstallBodyPart(BodyPart.BodyPart part, string slotName) - { - if (!SlotExists(slotName)) //Make sure the given slot exists - return false; - if (TryGetBodyPart(slotName, out BodyPart.BodyPart result)) //And that nothing is in it - return false; - _partDictionary.Add(slotName, part); - BodyPartAdded(part, slotName); - - return true; - } - /// - /// Installs the given into the given slot, deleting the afterwards. Returns true if successful, false otherwise. - /// - public bool InstallDroppedBodyPart(DroppedBodyPartComponent part, string slotName) - { - if (!InstallBodyPart(part.ContainedBodyPart, slotName)) - return false; - part.Owner.Delete(); - return true; - } - - - - /// - /// Disconnects the given reference, potentially dropping other BodyParts - /// if they were hanging off it. Returns the IEntity representing the dropped BodyPart. - /// - public IEntity DropBodyPart(BodyPart.BodyPart part) - { - if (!_partDictionary.ContainsValue(part)) - return null; - if (part != null) - { - string slotName = _partDictionary.FirstOrDefault(x => x.Value == part).Key; - _partDictionary.Remove(slotName); - if (TryGetBodyPartConnections(slotName, out List connections)) //Call disconnect on all limbs that were hanging off this limb. - { - foreach (string connectionName in connections) //This loop is an unoptimized travesty. TODO: optimize to be less shit - { - if (TryGetBodyPart(connectionName, out BodyPart.BodyPart result) && !ConnectedToCenterPart(result)) - { - DisconnectBodyPartByName(connectionName, true); - } - } - } - var partEntity = Owner.EntityManager.SpawnEntity("BaseDroppedBodyPart", Owner.Transform.GridPosition); - partEntity.GetComponent().TransferBodyPartData(part); - return partEntity; - } - return null; - } - - /// - /// Disconnects the given reference, potentially dropping other BodyParts if they were hanging off it. - /// - public void DisconnectBodyPart(BodyPart.BodyPart part, bool dropEntity) { - if (!_partDictionary.ContainsValue(part)) - return; - if (part != null) { - string slotName = _partDictionary.FirstOrDefault(x => x.Value == part).Key; - if (_partDictionary.Remove(slotName, out var partRemoved)) - { - BodyPartRemoved(partRemoved, slotName); - } - - if (TryGetBodyPartConnections(slotName, out List connections)) //Call disconnect on all limbs that were hanging off this limb. - { - foreach (string connectionName in connections) //This loop is an unoptimized travesty. TODO: optimize to be less shit - { - if (TryGetBodyPart(connectionName, out BodyPart.BodyPart result) && !ConnectedToCenterPart(result)) { - DisconnectBodyPartByName(connectionName, dropEntity); - } - } - } - if (dropEntity) { - var partEntity = Owner.EntityManager.SpawnEntity("BaseDroppedBodyPart", Owner.Transform.GridPosition); - partEntity.GetComponent().TransferBodyPartData(part); - } - } - } - - /// - /// Internal string version of DisconnectBodyPart for performance purposes. Yes, it is actually more performant. - /// - private void DisconnectBodyPartByName(string name, bool dropEntity) { - if (!TryGetBodyPart(name, out BodyPart.BodyPart part)) - return; - if (part != null) { - if (_partDictionary.Remove(name, out var partRemoved)) - { - BodyPartRemoved(partRemoved, name); - } - - if (TryGetBodyPartConnections(name, out List connections)) { - foreach (string connectionName in connections) { - if (TryGetBodyPart(connectionName, out BodyPart.BodyPart result) && !ConnectedToCenterPart(result)) { - DisconnectBodyPartByName(connectionName, dropEntity); - } - } - } - if (dropEntity) { - var partEntity = Owner.EntityManager.SpawnEntity("BaseDroppedBodyPart", Owner.Transform.GridPosition); - partEntity.GetComponent().TransferBodyPartData(part); - } - } - } - - private void BodyPartAdded(BodyPart.BodyPart part, string slotName) - { - var argsAdded = new BodyPartAddedEventArgs(part, slotName); - - foreach (var component in Owner.GetAllComponents().ToArray()) - { - component.BodyPartAdded(argsAdded); - } - } - - private void BodyPartRemoved(BodyPart.BodyPart part, string slotName) - { - var args = new BodyPartRemovedEventArgs(part, slotName); - - foreach (var component in Owner.GetAllComponents()) - { - component.BodyPartRemoved(args); - } - } - } -} diff --git a/Content.Server/Health/BodySystem/BodyPart/BodyPart.cs b/Content.Server/Health/BodySystem/BodyPart/BodyPart.cs deleted file mode 100644 index 92addbbebd..0000000000 --- a/Content.Server/Health/BodySystem/BodyPart/BodyPart.cs +++ /dev/null @@ -1,260 +0,0 @@ -using System; -using System.Collections.Generic; -using Content.Server.Health.BodySystem.Mechanism; -using Content.Server.Health.BodySystem.Surgery.Surgeon; -using Content.Server.Health.BodySystem.Surgery.SurgeryData; -using Content.Shared.Health.BodySystem; -using Content.Shared.Health.BodySystem.BodyPart; -using Content.Shared.Health.BodySystem.Mechanism; -using Content.Shared.Health.DamageContainer; -using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Interfaces.Serialization; -using Robust.Shared.IoC; -using Robust.Shared.Prototypes; -using Robust.Shared.ViewVariables; - -namespace Content.Server.Health.BodySystem.BodyPart -{ - - - /// - /// Data class representing a singular limb such as an arm or a leg. Typically held within either a , - /// which coordinates functions between BodyParts, or a . - /// - public class BodyPart - { - - [ViewVariables] - private ISurgeryData _surgeryData; - - [ViewVariables] - private List _mechanisms = new List(); - - [ViewVariables] - private int _sizeUsed = 0; - - /// - /// The name of this BodyPart, often displayed to the user. For example, it could be named "advanced robotic arm". - /// - [ViewVariables] - public string Name { get; set; } - - /// - /// Plural version of this BodyPart name. - /// - [ViewVariables] - public string Plural { get; set; } - - /// - /// Path to the RSI that represents this BodyPart. - /// - [ViewVariables] - public string RSIPath { get; set; } - - /// - /// RSI state that represents this BodyPart. - /// - [ViewVariables] - public string RSIState { get; set; } - - /// - /// that this BodyPart is considered to be. For example, BodyPartType.Arm. - /// - [ViewVariables] - public BodyPartType PartType { get; set; } - - /// - /// Max HP of this BodyPart. - /// - [ViewVariables] - public int MaxDurability { get; set; } - - /// - /// Current HP of this BodyPart based on sum of all damage types. - /// - [ViewVariables] - public int CurrentDurability => MaxDurability - CurrentDamages.Damage; - - /// - /// Current damage dealt to this BodyPart. - /// - [ViewVariables] - public AbstractDamageContainer CurrentDamages { get; set; } - - /// - /// At what HP this BodyPartis completely destroyed. - /// - [ViewVariables] - public int DestroyThreshold { get; set; } - - /// - /// Armor of this BodyPart against attacks. - /// - [ViewVariables] - public float Resistance { get; set; } - - /// - /// Determines many things: how many mechanisms can be fit inside this BodyPart, whether a body can fit through tiny crevices, etc. - /// - [ViewVariables] - public int Size { get; set; } - - /// - /// What types of BodyParts this BodyPart can easily attach to. For the most part, most limbs aren't universal and require extra work to attach between types. - /// - [ViewVariables] - public BodyPartCompatibility Compatibility { get; set; } - - /// - /// List of properties, allowing for additional data classes to be attached to a limb, such as a "length" class to an arm. - /// - [ViewVariables] - public List Properties { get; set; } - - /// - /// List of all Mechanisms currently inside this BodyPart. - /// - [ViewVariables] - public List Mechanisms => _mechanisms; - - public BodyPart() { } - - public BodyPart(BodyPartPrototype data) - { - LoadFromPrototype(data); - } - - - - - - - public bool CanAttachBodyPart(BodyPart toBeConnected) - { - return _surgeryData.CanAttachBodyPart(toBeConnected); - } - - - - - - - /// - /// Returns whether the given can be installed on this BodyPart. - /// - public bool CanInstallMechanism(Mechanism.Mechanism mechanism) - { - if (_sizeUsed + mechanism.Size > Size) - return false; //No space - return _surgeryData.CanInstallMechanism(mechanism); - } - - /// - /// Attempts to add a . Returns true if successful, false if there was an error (e.g. not enough room in BodyPart). Call InstallDroppedMechanism instead if you want to easily install an IEntity with a DroppedMechanismComponent. - /// - public bool TryInstallMechanism(Mechanism.Mechanism mechanism) - { - if (CanInstallMechanism(mechanism)) - { - _mechanisms.Add(mechanism); - _sizeUsed += mechanism.Size; - return true; - } - return false; - } - - /// - /// Attempts to install a into the given limb, potentially deleting the dropped . Returns true if successful, false if there was an error (e.g. not enough room in BodyPart). - /// - public bool TryInstallDroppedMechanism(DroppedMechanismComponent droppedMechanism) - { - if (!TryInstallMechanism(droppedMechanism.ContainedMechanism)) - return false; //Installing the mechanism failed for some reason. - droppedMechanism.Owner.Delete(); - return true; - } - - /// - /// Tries to remove the given reference from this BodyPart. Returns null if there was an error in spawning the entity or removing the mechanism, otherwise returns a reference to the on the newly spawned entity. - /// - public DroppedMechanismComponent DropMechanism(IEntity dropLocation, Mechanism.Mechanism mechanismTarget) - { - if (!_mechanisms.Contains(mechanismTarget)) - return null; - _mechanisms.Remove(mechanismTarget); - _sizeUsed -= mechanismTarget.Size; - IEntityManager entityManager = IoCManager.Resolve(); - var mechanismEntity = entityManager.SpawnEntity("BaseDroppedMechanism", dropLocation.Transform.GridPosition); - var droppedMechanism = mechanismEntity.GetComponent(); - droppedMechanism.InitializeDroppedMechanism(mechanismTarget); - return droppedMechanism; - } - - /// - /// Tries to destroy the given in the given BodyPart. Returns false if there was an error, true otherwise. Does NOT spawn a dropped entity. - /// - public bool DestroyMechanism(BodyPart bodyPartTarget, Mechanism.Mechanism mechanismTarget) - { - if (!_mechanisms.Contains(mechanismTarget)) - return false; - _mechanisms.Remove(mechanismTarget); - _sizeUsed -= mechanismTarget.Size; - return true; - } - - - - - - /// - /// Returns whether the given can be used on the current state of this BodyPart. - /// - public bool SurgeryCheck(SurgeryType toolType) - { - return _surgeryData.CheckSurgery(toolType); - } - - /// - /// Attempts to perform surgery on this BodyPart with the given tool. Returns false if there was an error, true if successful. - /// - public bool AttemptSurgery(SurgeryType toolType, IBodyPartContainer target, ISurgeon surgeon, IEntity performer) - { - return _surgeryData.PerformSurgery(toolType, target, surgeon, performer); - } - - - - - - /// - /// Loads the given - current data on this will be overwritten! - /// - public virtual void LoadFromPrototype(BodyPartPrototype data) - { - Name = data.Name; - Plural = data.Plural; - PartType = data.PartType; - RSIPath = data.RSIPath; - RSIState = data.RSIState; - MaxDurability = data.Durability; - CurrentDamages = new BiologicalDamageContainer(); - Resistance = data.Resistance; - Size = data.Size; - Compatibility = data.Compatibility; - Properties = data.Properties; - //_surgeryData = (ISurgeryData) Activator.CreateInstance(null, data.SurgeryDataName); - //TODO: figure out a way to convert a string name in the YAML to the proper class (reflection won't work for reasons) - _surgeryData = new BiologicalSurgeryData(this); - IPrototypeManager prototypeManager = IoCManager.Resolve(); - foreach (string mechanismPrototypeID in data.Mechanisms) - { - if (!prototypeManager.TryIndex(mechanismPrototypeID, out MechanismPrototype mechanismData)) - { - throw new InvalidOperationException("No MechanismPrototype was found with the name " + mechanismPrototypeID + " while loading a BodyPartPrototype!"); - } - _mechanisms.Add(new Mechanism.Mechanism(mechanismData)); - } - - } - } -} diff --git a/Content.Server/Health/BodySystem/BodyPreset/BodyPreset.cs b/Content.Server/Health/BodySystem/BodyPreset/BodyPreset.cs deleted file mode 100644 index 83bd17a1b8..0000000000 --- a/Content.Server/Health/BodySystem/BodyPreset/BodyPreset.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; -using Content.Server.Health.BodySystem.BodyPart; -using Content.Shared.Health.BodySystem.BodyPart; -using Content.Shared.Health.BodySystem.BodyPreset; -using Robust.Shared.ViewVariables; - -namespace Content.Server.Health.BodySystem.BodyPreset { - - /// - /// Stores data on what BodyPartPrototypes should fill a BodyTemplate. Used for loading complete body presets, like a "basic human" with all human limbs. - /// - public class BodyPreset { - private string _name; - private Dictionary _partIDs; - - [ViewVariables] - public string Name => _name; - - /// - /// Maps a template slot to the ID of the that should fill it. E.g. "right arm" : "BodyPart.arm.basic_human". - /// - [ViewVariables] - public Dictionary PartIDs => _partIDs; - - public BodyPreset(BodyPresetPrototype data) - { - LoadFromPrototype(data); - } - - public virtual void LoadFromPrototype(BodyPresetPrototype data) - { - _name = data.Name; - _partIDs = data.PartIDs; - } - } -} diff --git a/Content.Server/Health/BodySystem/BodyScanner/BodyScannerComponent.cs b/Content.Server/Health/BodySystem/BodyScanner/BodyScannerComponent.cs deleted file mode 100644 index ee0f7a34c6..0000000000 --- a/Content.Server/Health/BodySystem/BodyScanner/BodyScannerComponent.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Collections.Generic; -using Content.Shared.Health.BodySystem.BodyScanner; -using Content.Shared.Interfaces.GameObjects.Components; -using Robust.Server.GameObjects.Components.UserInterface; -using Robust.Server.Interfaces.GameObjects; -using Robust.Shared.GameObjects; - -namespace Content.Server.Health.BodySystem.BodyScanner -{ - [RegisterComponent] - [ComponentReference(typeof(IActivate))] - public class BodyScannerComponent : Component, IActivate - { - public sealed override string Name => "BodyScanner"; - - private BoundUserInterface _userInterface; - - public override void Initialize() - { - base.Initialize(); - _userInterface = Owner.GetComponent().GetBoundUserInterface(BodyScannerUiKey.Key); - _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; - } - - private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg) - { - - } - - void IActivate.Activate(ActivateEventArgs eventArgs) - { - if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) - { - return; - } - - if (actor.playerSession.AttachedEntity.TryGetComponent(out BodyManagerComponent attempt)) - { - _userInterface.SetState(PrepareBodyScannerInterfaceState(attempt.Template, attempt.PartDictionary)); - } - _userInterface.Open(actor.playerSession); - } - - - /// - /// Copy BodyTemplate and BodyPart data into a common data class that the client can read. - /// - private BodyScannerInterfaceState PrepareBodyScannerInterfaceState(BodyTemplate.BodyTemplate template, Dictionary bodyParts) - { - Dictionary partsData = new Dictionary(); - foreach (var(slotname, bpart) in bodyParts) { - List mechanismData = new List(); - foreach (var mech in bpart.Mechanisms) - { - mechanismData.Add(new BodyScannerMechanismData(mech.Name, mech.Description, mech.RSIPath, mech.RSIState, mech.MaxDurability, mech.CurrentDurability)); - } - partsData.Add(slotname, new BodyScannerBodyPartData(bpart.Name, bpart.RSIPath, bpart.RSIState, bpart.MaxDurability, bpart.CurrentDurability, mechanismData)); - } - BodyScannerTemplateData templateData = new BodyScannerTemplateData(template.Name, template.Slots); - return new BodyScannerInterfaceState(partsData, templateData); - } - - } -} diff --git a/Content.Server/Health/BodySystem/BodyTemplate/BodyTemplate.cs b/Content.Server/Health/BodySystem/BodyTemplate/BodyTemplate.cs deleted file mode 100644 index a5a7498543..0000000000 --- a/Content.Server/Health/BodySystem/BodyTemplate/BodyTemplate.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using System.Collections.Generic; -using Content.Shared.Health.BodySystem; -using Content.Shared.Health.BodySystem.BodyTemplate; -using Robust.Shared.ViewVariables; - -namespace Content.Server.Health.BodySystem.BodyTemplate { - - /// - /// This class is a data capsule representing the standard format of a . For instance, the "humanoid" BodyTemplate - /// defines two arms, each connected to a torso and so on. Capable of loading data from a . - /// - public class BodyTemplate { - - [ViewVariables] - public string Name; - - /// - /// The name of the center BodyPart. For humans, this is set to "torso". Used in many calculations. - /// - [ViewVariables] - public string CenterSlot { get; set; } - - /// - /// Maps all parts on this template to its BodyPartType. For instance, "right arm" is mapped to "BodyPartType.arm" on the humanoid template. - /// - [ViewVariables] - public Dictionary Slots { get; set; } - - /// - /// Maps limb name to the list of their connections to other limbs. For instance, on the humanoid template "torso" is mapped to a list containing "right arm", "left arm", - /// "left leg", and "right leg". This is mapped both ways during runtime, but in the prototype only one way has to be defined, i.e., "torso" to "left arm" will automatically - /// map "left arm" to "torso". - /// - [ViewVariables] - public Dictionary> Connections { get; set; } - - public BodyTemplate() - { - Name = "empty"; - Slots = new Dictionary(); - Connections = new Dictionary>(); - CenterSlot = ""; - } - - public BodyTemplate(BodyTemplatePrototype data) - { - LoadFromPrototype(data); - } - - public bool Equals(BodyTemplate other) - { - return GetHashCode() == other.GetHashCode(); - } - - /// - /// Returns whether the given slot exists in this BodyTemplate. - /// - public bool SlotExists(string slotName) - { - foreach (string slot in Slots.Keys) - { - if (slot == slotName) //string comparison xd - return true; - } - return false; - } - - /// - /// Returns an integer unique to this BodyTemplate's layout. It does not matter in which order the Connections or Slots are defined. - /// - public override int GetHashCode() - { - int slotsHash = 0; - int connectionsHash = 0; - - foreach (var(key, value) in Slots) - { - int slot = key.GetHashCode(); - slot = HashCode.Combine(slot, value.GetHashCode()); - slotsHash = slotsHash ^ slot; - } - - List connections = new List(); - foreach (var (key, value) in Connections) - { - foreach (var targetBodyPart in value) - { - int connection = key.GetHashCode() ^ targetBodyPart.GetHashCode(); - if (!connections.Contains(connection)) - connections.Add(connection); - } - } - foreach (int connection in connections) - { - connectionsHash = connectionsHash ^ connection; - } - - int hash = HashCode.Combine(slotsHash, connectionsHash, CenterSlot.GetHashCode()); - if (hash == 0) //One of the unit tests considers 0 to be an error, but it will be 0 if the BodyTemplate is empty, so let's shift that up to 1. - hash++; - return hash; - } - - public virtual void LoadFromPrototype(BodyTemplatePrototype data) - { - Name = data.Name; - CenterSlot = data.CenterSlot; - Slots = data.Slots; - Connections = data.Connections; - } - } -} diff --git a/Content.Server/Health/BodySystem/IBodyPartContainer.cs b/Content.Server/Health/BodySystem/IBodyPartContainer.cs deleted file mode 100644 index c23b8ec1f5..0000000000 --- a/Content.Server/Health/BodySystem/IBodyPartContainer.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Content.Server.Health.BodySystem.Surgery.SurgeryData; - -namespace Content.Server.Health.BodySystem -{ - - /// - /// Making a class inherit from this interface allows you to do many things with it in the class. This includes passing - /// it as an argument to a delegate, as to later typecast it back to the original class type. Every BodyPart also needs an - /// IBodyPartContainer to be its parent (i.e. the BodyManagerComponent holds many BodyParts, each of which have an upward reference to it). - /// - - public interface IBodyPartContainer - { - - } -} diff --git a/Content.Server/Health/BodySystem/Mechanism/Mechanism.cs b/Content.Server/Health/BodySystem/Mechanism/Mechanism.cs deleted file mode 100644 index 1ea508a782..0000000000 --- a/Content.Server/Health/BodySystem/Mechanism/Mechanism.cs +++ /dev/null @@ -1,105 +0,0 @@ -using Content.Server.Health.BodySystem.BodyPart; -using Content.Shared.Health.BodySystem; -using Content.Shared.Health.BodySystem.Mechanism; -using Robust.Shared.ViewVariables; - -namespace Content.Server.Health.BodySystem.Mechanism { - - /// - /// Data class representing a persistent item inside a . This includes livers, eyes, cameras, brains, explosive implants, binary communicators, and other things. - /// - public class Mechanism { - - [ViewVariables] - public string Name { get; set; } - - /// - /// Professional description of the Mechanism. - /// - [ViewVariables] - public string Description { get; set; } - - /// - /// The message to display upon examining a mob with this Mechanism installed. If the string is empty (""), no message will be displayed. - /// - [ViewVariables] - public string ExamineMessage { get; set; } - - /// - /// Path to the RSI that represents this Mechanism. - /// - [ViewVariables] - public string RSIPath { get; set; } - - /// - /// RSI state that represents this Mechanism. - /// - [ViewVariables] - public string RSIState { get; set; } - - /// - /// Max HP of this Mechanism. - /// - [ViewVariables] - public int MaxDurability { get; set; } - - /// - /// Current HP of this Mechanism. - /// - [ViewVariables] - public int CurrentDurability { get; set; } - - /// - /// At what HP this Mechanism is completely destroyed. - /// - [ViewVariables] - public int DestroyThreshold { get; set; } - - /// - /// Armor of this Mechanism against attacks. - /// - [ViewVariables] - public int Resistance { get; set; } - - /// - /// Determines a handful of things - mostly whether this Mechanism can fit into a BodyPart. - /// - [ViewVariables] - public int Size { get; set; } - - /// - /// What kind of BodyParts this Mechanism can be easily installed into. - /// - [ViewVariables] - public BodyPartCompatibility Compatibility { get; set; } - - public Mechanism(MechanismPrototype data) - { - LoadFromPrototype(data); - } - - - - - - - /// - /// Loads the given - current data on this Mechanism will be overwritten! - /// - public void LoadFromPrototype(MechanismPrototype data) - { - Name = data.Name; - Description = data.Description; - ExamineMessage = data.ExamineMessage; - RSIPath = data.RSIPath; - RSIState = data.RSIState; - MaxDurability = data.Durability; - CurrentDurability = MaxDurability; - DestroyThreshold = data.DestroyThreshold; - Resistance = data.Resistance; - Size = data.Size; - Compatibility = data.Compatibility; - } - } -} - diff --git a/Content.Server/Health/BodySystem/Surgery/Surgeon/ISurgeon.cs b/Content.Server/Health/BodySystem/Surgery/Surgeon/ISurgeon.cs deleted file mode 100644 index 6d586b01e5..0000000000 --- a/Content.Server/Health/BodySystem/Surgery/Surgeon/ISurgeon.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using Content.Server.Health.BodySystem.Surgery.SurgeryData; -using Robust.Shared.Interfaces.GameObjects; - -namespace Content.Server.Health.BodySystem.Surgery.Surgeon -{ - - /// - /// Interface representing an entity capable of performing surgery (performing operations on an class). - /// For an example see , which inherits from this class. - /// - public interface ISurgeon - { - /// - /// How long it takes to perform a single surgery step (in seconds). - /// - public float BaseOperationTime { get; set; } - - - public delegate void MechanismRequestCallback(Mechanism.Mechanism target, IBodyPartContainer container, ISurgeon surgeon, IEntity performer); - - /// - /// When performing a surgery, the may sometimes require selecting from a set of Mechanisms to operate on. - /// This function is called in that scenario, and it is expected that you call the callback with one mechanism from the provided list. - /// - public void RequestMechanism(List options, MechanismRequestCallback callback); - } - -} diff --git a/Content.Server/Health/BodySystem/Surgery/SurgeryData/BiologicalSurgeryData.cs b/Content.Server/Health/BodySystem/Surgery/SurgeryData/BiologicalSurgeryData.cs deleted file mode 100644 index bb3917a7dc..0000000000 --- a/Content.Server/Health/BodySystem/Surgery/SurgeryData/BiologicalSurgeryData.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Content.Server.Health.BodySystem.Surgery.Surgeon; -using Content.Shared.Health.BodySystem; -using Content.Shared.Interfaces; -using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Localization; - -namespace Content.Server.Health.BodySystem.Surgery.SurgeryData -{ - - /// - /// Data class representing the surgery state of a biological entity. - /// - public class BiologicalSurgeryData : ISurgeryData - { - - protected bool _skinOpened = false; - protected bool _vesselsClamped = false; - protected bool _skinRetracted = false; - protected List _disconnectedOrgans = new List(); - - public BiologicalSurgeryData(BodyPart.BodyPart parent) : base(parent) { } - - public override SurgeryAction GetSurgeryStep(SurgeryType toolType) - { - if (toolType == SurgeryType.Amputation) - { - return RemoveBodyPartSurgery; - } - if (!_skinOpened) //Case: skin is normal. - { - if (toolType == SurgeryType.Incision) - return OpenSkinSurgery; - } - else if (!_vesselsClamped) //Case: skin is opened, but not clamped. - { - if (toolType == SurgeryType.VesselCompression) - return ClampVesselsSurgery; - else if (toolType == SurgeryType.Cauterization) - return CautizerizeIncisionSurgery; - } - else if (!_skinRetracted) //Case: skin is opened and clamped, but not retracted. - { - if (toolType == SurgeryType.Retraction) - return RetractSkinSurgery; - else if (toolType == SurgeryType.Cauterization) - return CautizerizeIncisionSurgery; - } - else //Case: skin is fully open. - { - if (_parent.Mechanisms.Count > 0 && toolType == SurgeryType.VesselCompression && (_disconnectedOrgans.Except(_parent.Mechanisms).Count() != 0 || _parent.Mechanisms.Except(_disconnectedOrgans).Count() != 0)) - return LoosenOrganSurgery; - else if (_disconnectedOrgans.Count > 0 && toolType == SurgeryType.Incision) - return RemoveOrganSurgery; - else if (toolType == SurgeryType.Cauterization) - return CautizerizeIncisionSurgery; - } - return null; - } - - public override string GetDescription(IEntity target) - { - string toReturn = ""; - if (_skinOpened && !_vesselsClamped) //Case: skin is opened, but not clamped. - { - toReturn += Loc.GetString("The skin on {0:their} {1} has an incision, but it is prone to bleeding.\n", target, _parent.Name); - } - else if (_skinOpened && _vesselsClamped && !_skinRetracted) //Case: skin is opened and clamped, but not retracted. - { - toReturn += Loc.GetString("The skin on {0:their} {1} has an incision, but it is not retracted.\n", target, _parent.Name); - } - else if (_skinOpened && _vesselsClamped && _skinRetracted) //Case: skin is fully open. - { - toReturn += Loc.GetString("There is an incision on {0:their} {1}.\n", target, _parent.Name); - foreach (Mechanism.Mechanism mechanism in _disconnectedOrgans) - { - toReturn += Loc.GetString("{0:their} {1} is loose.\n", target, mechanism.Name); - } - } - return toReturn; - } - - public override bool CanInstallMechanism(Mechanism.Mechanism toBeInstalled) - { - return _skinOpened && _vesselsClamped && _skinRetracted; - } - - public override bool CanAttachBodyPart(BodyPart.BodyPart toBeConnected) - { - return true; - //TODO: if a bodypart is disconnected, you should have to do some surgery to allow another bodypart to be attached. - } - - - - - - - protected void OpenSkinSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) - { - performer.PopupMessage(performer, Loc.GetString("Cut open the skin...")); - //Delay? - _skinOpened = true; - } - - - protected void ClampVesselsSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) - { - performer.PopupMessage(performer, Loc.GetString("Clamp the vessels...")); - //Delay? - _vesselsClamped = true; - } - - - protected void RetractSkinSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) - { - performer.PopupMessage(performer, Loc.GetString("Retract the skin...")); - //Delay? - _skinRetracted = true; - } - - - protected void CautizerizeIncisionSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) - { - performer.PopupMessage(performer, Loc.GetString("Cauterize the incision...")); - //Delay? - _skinOpened = false; - _vesselsClamped = false; - _skinRetracted = false; - } - - - protected void LoosenOrganSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) - { - if (_parent.Mechanisms.Count <= 0) - return; - List toSend = new List(); - foreach (Mechanism.Mechanism mechanism in _parent.Mechanisms) - { - if (!_disconnectedOrgans.Contains(mechanism)) - toSend.Add(mechanism); - } - if (toSend.Count > 0) - surgeon.RequestMechanism(toSend, LoosenOrganSurgeryCallback); - } - public void LoosenOrganSurgeryCallback(Mechanism.Mechanism target, IBodyPartContainer container, ISurgeon surgeon, IEntity performer) - { - if (target != null && _parent.Mechanisms.Contains(target)) - { - performer.PopupMessage(performer, Loc.GetString("Loosen the organ...")); - //Delay? - _disconnectedOrgans.Add(target); - } - } - - - protected void RemoveOrganSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) - { - if (_disconnectedOrgans.Count <= 0) - return; - if (_disconnectedOrgans.Count == 1) - RemoveOrganSurgeryCallback(_disconnectedOrgans[0], container, surgeon, performer); - else - surgeon.RequestMechanism(_disconnectedOrgans, RemoveOrganSurgeryCallback); - - - } - public void RemoveOrganSurgeryCallback(Mechanism.Mechanism target, IBodyPartContainer container, ISurgeon surgeon, IEntity performer) - { - if (target != null && _parent.Mechanisms.Contains(target)) - { - performer.PopupMessage(performer, Loc.GetString("Remove the organ...")); - //Delay? - _parent.DropMechanism(performer, target); - _disconnectedOrgans.Remove(target); - } - } - - - protected void RemoveBodyPartSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) - { - if (!(container is BodyManagerComponent)) //This surgery requires a DroppedBodyPartComponent. - return; - BodyManagerComponent bmTarget = (BodyManagerComponent) container; - performer.PopupMessage(performer, Loc.GetString("Saw off the limb!")); - //Delay? - bmTarget.DisconnectBodyPart(_parent, true); - } - - - } -} diff --git a/Content.Server/Health/BodySystem/Surgery/SurgeryData/ISurgeryData.cs b/Content.Server/Health/BodySystem/Surgery/SurgeryData/ISurgeryData.cs deleted file mode 100644 index 47a0aba4c1..0000000000 --- a/Content.Server/Health/BodySystem/Surgery/SurgeryData/ISurgeryData.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Content.Server.Health.BodySystem.BodyPart; -using Content.Server.Health.BodySystem.Mechanism; -using Content.Server.Health.BodySystem.Surgery.Surgeon; -using Content.Shared.Health.BodySystem; -using Robust.Shared.Interfaces.GameObjects; - -namespace Content.Server.Health.BodySystem.Surgery.SurgeryData -{ - - - - /// - /// This data class represents the state of a in regards to everything surgery related - whether there's an incision on it, whether the bone is broken, etc. - /// - public abstract class ISurgeryData - { - - /// - /// The this surgeryData is attached to. The ISurgeryData class should not exist without a that it - /// represents, and will throw errors if it is null. - /// - protected BodyPart.BodyPart _parent; - - /// - /// The of the parent . - /// - protected BodyPartType _parentType => _parent.PartType; - - public delegate void SurgeryAction(IBodyPartContainer container, ISurgeon surgeon, IEntity performer); - - - - public ISurgeryData(BodyPart.BodyPart parent) - { - _parent = parent; - } - - /// - /// Returns the description of this current to be shown upon observing the given entity. - /// - public abstract string GetDescription(IEntity target); - - /// - /// Returns whether a can be installed into the this ISurgeryData represents. - /// - public abstract bool CanInstallMechanism(Mechanism.Mechanism toBeInstalled); - - /// - /// Returns whether the given can be connected to the this ISurgeryData represents. - /// - public abstract bool CanAttachBodyPart(BodyPart.BodyPart toBeConnected); - - /// - /// Gets the delegate corresponding to the surgery step using the given . Returns null if no surgery step can be performed. - /// - public abstract SurgeryAction GetSurgeryStep(SurgeryType toolType); - - /// - /// Returns whether the given can be used to perform a surgery on the BodyPart this represents. - /// - public bool CheckSurgery(SurgeryType toolType) - { - return GetSurgeryStep(toolType) != null; - } - - /// - /// Attempts to perform surgery of the given . Returns whether the operation was successful. - /// - /// The used for this surgery. - /// The entity performing the surgery. - public bool PerformSurgery(SurgeryType surgeryType, IBodyPartContainer container, ISurgeon surgeon, IEntity performer) - { - SurgeryAction step = GetSurgeryStep(surgeryType); - if (step == null) - return false; - step(container, surgeon, performer); - return true; - } - - } -} diff --git a/Content.Server/IgnoredComponents.cs b/Content.Server/IgnoredComponents.cs index 4aa536c30d..cd9713f35d 100644 --- a/Content.Server/IgnoredComponents.cs +++ b/Content.Server/IgnoredComponents.cs @@ -19,7 +19,6 @@ "Marker", "EmergencyLight", "Clickable", - "CanSeeGases", "RadiatingLight", }; diff --git a/Content.Server/Interfaces/GameObjects/Components/Damage/IDamageableComponent.cs b/Content.Server/Interfaces/GameObjects/Components/Damage/IDamageableComponent.cs deleted file mode 100644 index ad88349bdd..0000000000 --- a/Content.Server/Interfaces/GameObjects/Components/Damage/IDamageableComponent.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using Content.Server.GameObjects.Components.Damage; -using Content.Shared.GameObjects.Components.Damage; -using Robust.Shared.Interfaces.GameObjects; - -namespace Content.Server.Interfaces.GameObjects.Components.Damage -{ - public interface IDamageableComponent : IComponent - { - event EventHandler DamageThresholdPassed; - ResistanceSet Resistances { get; } - - /// - /// The function that handles receiving damage. - /// Converts damage via the resistance set then applies it - /// and informs components of thresholds passed as necessary. - /// - /// Type of damage being received. - /// Amount of damage being received. - /// Entity that damaged this entity. - /// Mob that damaged this entity. - void TakeDamage(DamageType damageType, int amount, IEntity source, IEntity sourceMob); - - /// - /// Handles receiving healing. - /// Converts healing via the resistance set then applies it - /// and informs components of thresholds passed as necessary. - /// - /// Type of healing being received. - /// Amount of healing being received. - /// Entity that healed this entity. - /// Mob that healed this entity. - void TakeHealing(DamageType damageType, int amount, IEntity source, IEntity sourceMob); - } -} diff --git a/Content.Server/Interfaces/GameObjects/Components/Interaction/IBodyPartAdded.cs b/Content.Server/Interfaces/GameObjects/Components/Interaction/IBodyPartAdded.cs index 0cca658e90..f261d6edc5 100644 --- a/Content.Server/Interfaces/GameObjects/Components/Interaction/IBodyPartAdded.cs +++ b/Content.Server/Interfaces/GameObjects/Components/Interaction/IBodyPartAdded.cs @@ -1,5 +1,5 @@ using System; -using Content.Server.Health.BodySystem.BodyPart; +using Content.Server.Body; namespace Content.Server.Interfaces.GameObjects.Components.Interaction { diff --git a/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs b/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs index e716533285..5765098531 100644 --- a/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs +++ b/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs @@ -61,8 +61,9 @@ namespace Content.Server.Interfaces.GameObjects.Components.Items /// Puts an item into any empty hand, preferring the active hand. /// /// The item to put in a hand. + /// Whether to perform an ActionBlocker check to the entity. /// True if the item was inserted, false otherwise. - bool PutInHand(ItemComponent item); + bool PutInHand(ItemComponent item, bool mobCheck = true); /// /// Puts an item into a specific hand. @@ -71,24 +72,27 @@ namespace Content.Server.Interfaces.GameObjects.Components.Items /// The name of the hand to put the item into. /// /// If true and the provided hand is full, the method will fall back to + /// Whether to perform an ActionBlocker check to the entity. /// /// True if the item was inserted into a hand, false otherwise. - bool PutInHand(ItemComponent item, string index, bool fallback=true); + bool PutInHand(ItemComponent item, string index, bool fallback=true, bool mobCheck = true); /// /// Checks to see if an item can be put in any hand. /// /// The item to check for. + /// Whether to perform an ActionBlocker check to the entity. /// True if the item can be inserted, false otherwise. - bool CanPutInHand(ItemComponent item); + bool CanPutInHand(ItemComponent item, bool mobCheck = true); /// /// Checks to see if an item can be put in the specified hand. /// /// The item to check for. /// The name for the hand to check for. + /// Whether to perform an ActionBlocker check to the entity. /// True if the item can be inserted, false otherwise. - bool CanPutInHand(ItemComponent item, string index); + bool CanPutInHand(ItemComponent item, string index, bool mobCheck = true); /// /// Finds the hand slot holding the specified entity, if any. @@ -107,15 +111,15 @@ namespace Content.Server.Interfaces.GameObjects.Components.Items /// Drops the item contained in the slot to the same position as our entity. /// /// The slot of which to drop to drop the item. - /// Whether to check the for the mob or not. + /// Whether to check the for the mob or not. /// True on success, false if something blocked the drop. - bool Drop(string slot, bool doMobChecks = true); + bool Drop(string slot, bool mobChecks = true); /// /// Drops an item held by one of our hand slots to the same position as our owning entity. /// /// The item to drop. - /// Whether to check the for the mob or not. + /// Whether to check the for the mob or not. /// True on success, false if something blocked the drop. /// /// Thrown if is null. @@ -123,7 +127,7 @@ namespace Content.Server.Interfaces.GameObjects.Components.Items /// /// Thrown if is not actually held in any hand. /// - bool Drop(IEntity entity, bool doMobChecks = true); + bool Drop(IEntity entity, bool mobChecks = true); /// /// Drops the item in a slot. @@ -194,10 +198,11 @@ namespace Content.Server.Interfaces.GameObjects.Components.Items /// Checks whether the item in the specified hand can be dropped. /// /// The hand to check for. + /// Whether to perform an ActionBlocker check to the entity. /// /// True if the item can be dropped, false if the hand is empty or the item in the hand cannot be dropped. /// - bool CanDrop(string name); + bool CanDrop(string name, bool mobCheck = true); /// /// Adds a new hand to this hands component. diff --git a/Content.Server/Interfaces/GameObjects/IOnDamageBehavior.cs b/Content.Server/Interfaces/GameObjects/IOnDamageBehavior.cs deleted file mode 100644 index 911d30b031..0000000000 --- a/Content.Server/Interfaces/GameObjects/IOnDamageBehavior.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; -using Content.Server.GameObjects.Components.Damage; - -namespace Content.Server.Interfaces.GameObjects -{ - /// - /// Any component/entity that has behaviour linked to taking damage should implement this interface. - /// TODO: Don't know how to work around this currently, but due to how events work - /// you need to hook it up to the DamageableComponent via Initialize(). - /// See DestructibleComponent.Initialize() for an example. - /// - interface IOnDamageBehavior - { - /// - /// Gets a list of all DamageThresholds this component/entity are interested in. - /// - /// List of DamageThresholds to be added to DamageableComponent for watching. - List GetAllDamageThresholds() => null; - - /// - /// Damage threshold passed event hookup. - /// - /// Damageable component. - /// Damage threshold and whether it's passed in one way or another. - void OnDamageThresholdPassed(object obj, DamageThresholdPassedEventArgs e) { } - - /// - /// Called when the entity is damaged. - /// - /// Damageable component. - /// DamageEventArgs - void OnDamaged(object obj, DamageEventArgs e) { } - } -} diff --git a/Content.Server/Interfaces/GameObjects/ISuicideAct.cs b/Content.Server/Interfaces/GameObjects/ISuicideAct.cs index fb6ed4ade5..78d0d9974f 100644 --- a/Content.Server/Interfaces/GameObjects/ISuicideAct.cs +++ b/Content.Server/Interfaces/GameObjects/ISuicideAct.cs @@ -13,11 +13,13 @@ namespace Content.Server.Interfaces.GameObjects Special, //Doesn't damage the mob, used for "weird" suicides like gibbing //Damage type suicides - Brute, + Blunt, + Piercing, Heat, - Cold, - Acid, - Toxic, - Electric + Disintegration, + Cellular, + DNA, + Asphyxiation + } } 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..5279fae886 100644 --- a/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs +++ b/Content.Server/Mobs/Roles/SuspicionTraitorRole.cs @@ -1,5 +1,8 @@ +using Content.Server.GameObjects.Components.Suspicion; using Content.Server.Interfaces.Chat; using Content.Shared.Roles; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; namespace Content.Server.Mobs.Roles @@ -26,6 +29,19 @@ namespace Content.Server.Mobs.Roles var chat = IoCManager.Resolve(); chat.DispatchServerMessage(Mind.Session, $"You're a {Name}!"); chat.DispatchServerMessage(Mind.Session, $"Objective: {Objective}"); + + var traitors = ""; + + foreach (var sus in IoCManager.Resolve().EntityQuery()) + { + if (!sus.IsTraitor()) continue; + if (traitors.Length > 0) + traitors += $", {sus.Owner.Name}"; + else + traitors += sus.Owner.Name; + } + + chat.DispatchServerMessage(Mind.Session, $"The traitors are: {traitors}"); } } } diff --git a/Content.Server/Mobs/StandingStateHelper.cs b/Content.Server/Mobs/StandingStateHelper.cs index 1f6f692a0f..f008b6ccc9 100644 --- a/Content.Server/Mobs/StandingStateHelper.cs +++ b/Content.Server/Mobs/StandingStateHelper.cs @@ -1,6 +1,6 @@ using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Shared.Audio; -using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Rotation; using Content.Shared.GameObjects.EntitySystems; using Robust.Server.GameObjects; using Robust.Server.GameObjects.EntitySystems; @@ -31,12 +31,12 @@ namespace Content.Server.Mobs return false; } - var newState = SharedSpeciesComponent.MobState.Down; - appearance.TryGetData(SharedSpeciesComponent.MobVisuals.RotationState, out var oldState); + var newState = RotationState.Horizontal; + appearance.TryGetData(RotationVisuals.RotationState, out var oldState); if (newState != oldState) { - appearance.SetData(SharedSpeciesComponent.MobVisuals.RotationState, newState); + appearance.SetData(RotationVisuals.RotationState, newState); } if (playSound) @@ -61,12 +61,12 @@ namespace Content.Server.Mobs public static bool Standing(IEntity entity) { if (!entity.TryGetComponent(out AppearanceComponent appearance)) return false; - appearance.TryGetData(SharedSpeciesComponent.MobVisuals.RotationState, out var oldState); - var newState = SharedSpeciesComponent.MobState.Standing; + appearance.TryGetData(RotationVisuals.RotationState, out var oldState); + var newState = RotationState.Vertical; if (newState == oldState) return false; - appearance.SetData(SharedSpeciesComponent.MobVisuals.RotationState, newState); + appearance.SetData(RotationVisuals.RotationState, newState); return true; } diff --git a/Content.Server/Observer/Ghost.cs b/Content.Server/Observer/Ghost.cs index 80480b958a..6c71882c9e 100644 --- a/Content.Server/Observer/Ghost.cs +++ b/Content.Server/Observer/Ghost.cs @@ -1,8 +1,8 @@ -using Content.Server.GameObjects.Components.Damage; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Observer; using Content.Server.Interfaces.GameTicking; using Content.Server.Players; +using Content.Shared.Damage; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Mobs; using Robust.Server.Interfaces.Console; @@ -17,6 +17,7 @@ namespace Content.Server.Observer public string Command => "ghost"; public string Description => "Give up on life and become a ghost."; public string Help => "ghost"; + public bool CanReturn { get; set; } = true; public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) { @@ -27,7 +28,7 @@ namespace Content.Server.Observer } var mind = player.ContentData().Mind; - var canReturn = player.AttachedEntity != null; + var canReturn = player.AttachedEntity != null && CanReturn; var name = player.AttachedEntity?.Name ?? player.Name; if (player.AttachedEntity != null && player.AttachedEntity.HasComponent()) @@ -41,19 +42,16 @@ namespace Content.Server.Observer var position = player.AttachedEntity?.Transform.GridPosition ?? IoCManager.Resolve().GetObserverSpawnPoint(); - - - if (canReturn && player.AttachedEntity.TryGetComponent(out SpeciesComponent species)) + if (canReturn && player.AttachedEntity.TryGetComponent(out IDamageableComponent damageable)) { - switch (species.CurrentDamageState) + switch (damageable.CurrentDamageState) { - case DeadState _: + case DamageState.Dead: canReturn = true; break; - case CriticalState _: + case DamageState.Critical: canReturn = true; - if (!player.AttachedEntity.TryGetComponent(out DamageableComponent damageable)) break; - damageable.TakeDamage(DamageType.Total, 100); // TODO: Use airloss/oxyloss instead + damageable.ChangeDamage(DamageType.Asphyxiation, 100, true, null); //todo: what if they dont breathe lol break; default: canReturn = false; diff --git a/Content.Server/Players/PlayerSessionExt.cs b/Content.Server/Players/PlayerSessionExt.cs new file mode 100644 index 0000000000..3b06788869 --- /dev/null +++ b/Content.Server/Players/PlayerSessionExt.cs @@ -0,0 +1,14 @@ +using Content.Shared.Network.NetMessages; +using Robust.Server.Interfaces.Player; + +namespace Content.Server.Players +{ + public static class PlayerSessionExt + { + public static void RequestWindowAttention(this IPlayerSession session) + { + var msg = session.ConnectedClient.CreateNetMessage(); + session.ConnectedClient.SendMessage(msg); + } + } +} 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/ServerContentIoC.cs b/Content.Server/ServerContentIoC.cs index c9d618b004..0083b4c8bd 100644 --- a/Content.Server/ServerContentIoC.cs +++ b/Content.Server/ServerContentIoC.cs @@ -1,5 +1,6 @@ using Content.Server.AI.Utility.Considerations; using Content.Server.AI.WorldState; +using Content.Server.Body.Network; using Content.Server.Cargo; using Content.Server.Chat; using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; @@ -39,6 +40,7 @@ namespace Content.Server IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } } diff --git a/Content.Server/StationEvents/StationEventCommand.cs b/Content.Server/StationEvents/StationEventCommand.cs index 39331c6d5b..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; +#nullable enable using Content.Server.GameObjects.EntitySystems.StationEvents; using JetBrains.Annotations; using Robust.Server.Interfaces.Console; @@ -14,11 +13,10 @@ namespace Content.Client.Commands { public string Command => "events"; public string Description => "Provides admin control to station events"; - public string Help => "events >\n" + + public string Help => "events >\n" + "list: return all event names that can be run\n " + "pause: stop all random events from running\n" + "resume: allow random events to run again\n" + - "random: choose a random event that is valid and run it\n" + "run: start a particular event now; is case-insensitive and not localized"; public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) { @@ -99,4 +97,4 @@ namespace Content.Client.Commands return; } } -} \ No newline at end of file +} diff --git a/Content.Shared/Atmos/Atmospherics.cs b/Content.Shared/Atmos/Atmospherics.cs index 2b1e2a5dc6..c39b6b7d5d 100644 --- a/Content.Shared/Atmos/Atmospherics.cs +++ b/Content.Shared/Atmos/Atmospherics.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Robust.Shared.IoC; using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Content.Shared.Atmos { @@ -14,10 +15,18 @@ namespace Content.Shared.Atmos var protoMan = IoCManager.Resolve(); GasPrototypes = new GasPrototype[TotalNumberOfGases]; + GasOverlays = new SpriteSpecifier[TotalNumberOfGases]; for (var i = 0; i < TotalNumberOfGases; i++) { - GasPrototypes[i] = protoMan.Index(i.ToString()); + var gasPrototype = protoMan.Index(i.ToString()); + GasPrototypes[i] = gasPrototype; + + if(string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayTexture)) + GasOverlays[i] = new SpriteSpecifier.Texture(new ResourcePath(gasPrototype.GasOverlayTexture)); + + if(!string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayState)) + GasOverlays[i] = new SpriteSpecifier.Rsi(new ResourcePath(gasPrototype.GasOverlaySprite), gasPrototype.GasOverlayState); } } @@ -27,6 +36,10 @@ namespace Content.Shared.Atmos public static GasPrototype GetGas(Gas gasId) => GasPrototypes[(int) gasId]; public static IEnumerable Gases => GasPrototypes; + private static readonly SpriteSpecifier[] GasOverlays; + + public static SpriteSpecifier GetOverlay(int overlayId) => GasOverlays[overlayId]; + #region ATMOS /// /// The universal gas constant, in kPa*L/(K*mol) @@ -58,6 +71,12 @@ namespace Content.Shared.Atmos /// public const float CellVolume = 2500f; + // Liters in a normal breath + public const float BreathVolume = 0.5f; + + // Amount of air to take from a tile + public const float BreathPercentage = BreathVolume / CellVolume; + /// /// Moles in a 2.5 m^3 cell at 101.325 kPa and 20ºC /// @@ -160,7 +179,7 @@ namespace Content.Shared.Atmos /// /// Total number of gases. Increase this if you want to add more! /// - public const int TotalNumberOfGases = 6; + public const byte TotalNumberOfGases = 6; /// /// Amount of heat released per mole of burnt hydrogen or tritium (hydrogen isotope) diff --git a/Content.Shared/Atmos/GasPrototype.cs b/Content.Shared/Atmos/GasPrototype.cs index 8a71811760..31827d2447 100644 --- a/Content.Shared/Atmos/GasPrototype.cs +++ b/Content.Shared/Atmos/GasPrototype.cs @@ -45,23 +45,6 @@ namespace Content.Shared.Atmos /// public string GasOverlaySprite { get; set; } - /// - /// Sprite specifier for the gas overlay. - /// - public SpriteSpecifier GasOverlay - { - get - { - if(string.IsNullOrEmpty(GasOverlaySprite) && !string.IsNullOrEmpty(GasOverlayTexture)) - return new SpriteSpecifier.Texture(new ResourcePath(GasOverlayTexture)); - - if(!string.IsNullOrEmpty(GasOverlaySprite) && !string.IsNullOrEmpty(GasOverlayState)) - return new SpriteSpecifier.Rsi(new ResourcePath(GasOverlaySprite), GasOverlayState); - - return null; - } - } - /// /// Path to the tile overlay used when this gas appears visible. /// diff --git a/Content.Shared/Health/BodySystem/Mechanism/MechanismPrototype.cs b/Content.Shared/Body/Mechanism/MechanismPrototype.cs similarity index 60% rename from Content.Shared/Health/BodySystem/Mechanism/MechanismPrototype.cs rename to Content.Shared/Body/Mechanism/MechanismPrototype.cs index acf2711f28..fc84807182 100644 --- a/Content.Shared/Health/BodySystem/Mechanism/MechanismPrototype.cs +++ b/Content.Shared/Body/Mechanism/MechanismPrototype.cs @@ -1,63 +1,56 @@ using System; +using System.Collections.Generic; +using Content.Shared.GameObjects.Components.Body; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; using YamlDotNet.RepresentationModel; -namespace Content.Shared.Health.BodySystem.Mechanism +namespace Content.Shared.Body.Mechanism { - /// - /// Prototype for the Mechanism class. + /// Prototype for the Mechanism class. /// [Prototype("mechanism")] - [NetSerializable, Serializable] + [Serializable, NetSerializable] public class MechanismPrototype : IPrototype, IIndexedPrototype { + private List _behaviorClasses; + private BodyPartCompatibility _compatibility; + private string _description; + private int _destroyThreshold; + private int _durability; + private string _examineMessage; private string _id; private string _name; - private string _description; - private string _examineMessage; + private int _resistance; private string _rsiPath; private string _rsiState; - private int _durability; - private int _destroyThreshold; - private int _resistance; private int _size; - private BodyPartCompatibility _compatibility; - [ViewVariables] - public string ID => _id; + [ViewVariables] public string Name => _name; - [ViewVariables] - public string Name => _name; + [ViewVariables] public string Description => _description; - [ViewVariables] - public string Description => _description; + [ViewVariables] public string ExamineMessage => _examineMessage; - [ViewVariables] - public string ExamineMessage => _examineMessage; + [ViewVariables] public string RSIPath => _rsiPath; - [ViewVariables] - public string RSIPath => _rsiPath; + [ViewVariables] public string RSIState => _rsiState; - [ViewVariables] - public string RSIState => _rsiState; + [ViewVariables] public int Durability => _durability; - [ViewVariables] - public int Durability => _durability; + [ViewVariables] public int DestroyThreshold => _destroyThreshold; - [ViewVariables] - public int DestroyThreshold => _destroyThreshold; + [ViewVariables] public int Resistance => _resistance; - [ViewVariables] - public int Resistance => _resistance; + [ViewVariables] public int Size => _size; - [ViewVariables] - public int Size => _size; + [ViewVariables] public BodyPartCompatibility Compatibility => _compatibility; - [ViewVariables] - public BodyPartCompatibility Compatibility => _compatibility; + [ViewVariables] public List BehaviorClasses => _behaviorClasses; + + [ViewVariables] public string ID => _id; public virtual void LoadFrom(YamlMappingNode mapping) { @@ -74,7 +67,7 @@ namespace Content.Shared.Health.BodySystem.Mechanism serializer.DataField(ref _resistance, "resistance", 0); serializer.DataField(ref _size, "size", 2); serializer.DataField(ref _compatibility, "compatibility", BodyPartCompatibility.Universal); + serializer.DataField(ref _behaviorClasses, "behaviors", new List()); } } } - diff --git a/Content.Shared/Body/Part/BodyPartPrototype.cs b/Content.Shared/Body/Part/BodyPartPrototype.cs new file mode 100644 index 0000000000..7d6158dce3 --- /dev/null +++ b/Content.Shared/Body/Part/BodyPartPrototype.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Shared.GameObjects.Components.Body; +using Robust.Shared.Interfaces.Serialization; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; +using YamlDotNet.RepresentationModel; + +namespace Content.Shared.Body.Part +{ + /// + /// Prototype for the BodyPart class. + /// + [Prototype("bodyPart")] + [Serializable, NetSerializable] + public class BodyPartPrototype : IPrototype, IIndexedPrototype + { + private BodyPartCompatibility _compatibility; + private string _damageContainerPresetId; + private int _destroyThreshold; + private int _durability; + private string _id; + private List _mechanisms; + private string _name; + private BodyPartType _partType; + private string _plural; + private List _properties; + private float _resistance; + private string _resistanceSetId; + private string _rsiPath; + private string _rsiState; + private int _size; + private string _surgeryDataName; + + [ViewVariables] public string Name => _name; + + [ViewVariables] public string Plural => _plural; + + [ViewVariables] public string RSIPath => _rsiPath; + + [ViewVariables] public string RSIState => _rsiState; + + [ViewVariables] public BodyPartType PartType => _partType; + + [ViewVariables] public int Durability => _durability; + + [ViewVariables] public int DestroyThreshold => _destroyThreshold; + + [ViewVariables] public float Resistance => _resistance; + + [ViewVariables] public int Size => _size; + + [ViewVariables] public BodyPartCompatibility Compatibility => _compatibility; + + [ViewVariables] public string DamageContainerPresetId => _damageContainerPresetId; + + [ViewVariables] public string ResistanceSetId => _resistanceSetId; + + [ViewVariables] public string SurgeryDataName => _surgeryDataName; + + [ViewVariables] public List Properties => _properties; + + [ViewVariables] public List Mechanisms => _mechanisms; + + [ViewVariables] public string ID => _id; + + public virtual void LoadFrom(YamlMappingNode mapping) + { + var serializer = YamlObjectSerializer.NewReader(mapping); + + serializer.DataField(ref _name, "name", string.Empty); + serializer.DataField(ref _id, "id", string.Empty); + serializer.DataField(ref _plural, "plural", string.Empty); + serializer.DataField(ref _rsiPath, "rsiPath", string.Empty); + serializer.DataField(ref _rsiState, "rsiState", string.Empty); + serializer.DataField(ref _partType, "partType", BodyPartType.Other); + serializer.DataField(ref _surgeryDataName, "surgeryDataType", "BiologicalSurgeryData"); + serializer.DataField(ref _durability, "durability", 50); + serializer.DataField(ref _destroyThreshold, "destroyThreshold", -50); + serializer.DataField(ref _resistance, "resistance", 0f); + serializer.DataField(ref _size, "size", 0); + serializer.DataField(ref _compatibility, "compatibility", BodyPartCompatibility.Universal); + serializer.DataField(ref _damageContainerPresetId, "damageContainer", string.Empty); + serializer.DataField(ref _resistanceSetId, "resistances", string.Empty); + serializer.DataField(ref _properties, "properties", new List()); + serializer.DataField(ref _mechanisms, "mechanisms", new List()); + + foreach (var property in _properties) + { + if (_properties.Count(x => x.GetType() == property.GetType()) > 1) + { + throw new InvalidOperationException( + $"Multiple {nameof(BodyPartPrototype)} of the same type were defined in prototype {ID}"); + } + } + } + } +} diff --git a/Content.Shared/Body/Part/Properties/BodyPartProperty.cs b/Content.Shared/Body/Part/Properties/BodyPartProperty.cs new file mode 100644 index 0000000000..8da251a565 --- /dev/null +++ b/Content.Shared/Body/Part/Properties/BodyPartProperty.cs @@ -0,0 +1,23 @@ +using Robust.Shared.Interfaces.Serialization; +using Robust.Shared.Serialization; + +namespace Content.Shared.Body.Part.Properties +{ + /// + /// Property attachable to a . + /// For example, this is used to define the speed capabilities of a + /// leg. The movement system will look for a LegProperty on all BodyParts. + /// + public abstract class BodyPartProperty : IExposeData + { + /// + /// Whether this property is currently active. + /// + public bool Active; + + public virtual void ExposeData(ObjectSerializer serializer) + { + serializer.DataField(ref Active, "active", true); + } + } +} diff --git a/Content.Shared/Body/Part/Properties/Movement/FootProperty.cs b/Content.Shared/Body/Part/Properties/Movement/FootProperty.cs new file mode 100644 index 0000000000..95432cbfe6 --- /dev/null +++ b/Content.Shared/Body/Part/Properties/Movement/FootProperty.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Body.Part.Properties.Movement +{ + /// + /// Defines the leg-based locomotion ability of a BodyPart. Required for humanoid-like movement. Must be connected to a + /// with + /// and to work. + /// + public class FootProperty : BodyPartProperty + { + } +} diff --git a/Content.Shared/Body/Part/Properties/Movement/LegProperty.cs b/Content.Shared/Body/Part/Properties/Movement/LegProperty.cs new file mode 100644 index 0000000000..77ba06f888 --- /dev/null +++ b/Content.Shared/Body/Part/Properties/Movement/LegProperty.cs @@ -0,0 +1,23 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Body.Part.Properties.Movement +{ + /// + /// Defines the speed of humanoid-like movement. Must be connected to a + /// with and have + /// on the same organ and down to the foot to work. + /// + public class LegProperty : BodyPartProperty + { + /// + /// Speed (in tiles per second). + /// + public float Speed; + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref Speed, "speed", 1f); + } + } +} diff --git a/Content.Shared/Body/Part/Properties/Other/ExtensionProperty.cs b/Content.Shared/Body/Part/Properties/Other/ExtensionProperty.cs new file mode 100644 index 0000000000..52d5965083 --- /dev/null +++ b/Content.Shared/Body/Part/Properties/Other/ExtensionProperty.cs @@ -0,0 +1,21 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Body.Part.Properties.Other +{ + /// + /// Defines the extension ability of a BodyPart. Used to determine things like reach distance and running speed. + /// + public class ExtensionProperty : BodyPartProperty + { + /// + /// Current reach distance (in tiles). + /// + public float ReachDistance; + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref ReachDistance, "reachDistance", 2f); + } + } +} diff --git a/Content.Shared/Body/Part/Properties/Other/GraspProperty.cs b/Content.Shared/Body/Part/Properties/Other/GraspProperty.cs new file mode 100644 index 0000000000..b3a774918e --- /dev/null +++ b/Content.Shared/Body/Part/Properties/Other/GraspProperty.cs @@ -0,0 +1,10 @@ +namespace Content.Shared.Body.Part.Properties.Other +{ + /// + /// Defines the item grasping ability of a BodyPart. Distance is determined by the + /// ExtensionProperties of the current BodyPart or attached BodyParts. + /// + public class GraspProperty : BodyPartProperty + { + } +} diff --git a/Content.Shared/Health/BodySystem/BodyPreset/BodyPresetPrototype.cs b/Content.Shared/Body/Preset/BodyPresetPrototype.cs similarity index 56% rename from Content.Shared/Health/BodySystem/BodyPreset/BodyPresetPrototype.cs rename to Content.Shared/Body/Preset/BodyPresetPrototype.cs index f36202a5a4..6fb56cc3ba 100644 --- a/Content.Shared/Health/BodySystem/BodyPreset/BodyPresetPrototype.cs +++ b/Content.Shared/Body/Preset/BodyPresetPrototype.cs @@ -5,32 +5,31 @@ using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; using YamlDotNet.RepresentationModel; -namespace Content.Shared.Health.BodySystem.BodyPreset { - +namespace Content.Shared.Body.Preset +{ /// /// Prototype for the BodyPreset class. /// [Prototype("bodyPreset")] - [NetSerializable, Serializable] - public class BodyPresetPrototype : IPrototype, IIndexedPrototype { + [Serializable, NetSerializable] + public class BodyPresetPrototype : IPrototype, IIndexedPrototype + { private string _id; private string _name; - private Dictionary _partIDs; + private Dictionary _partIDs; - [ViewVariables] - public string ID => _id; + [ViewVariables] public string ID => _id; - [ViewVariables] - public string Name => _name; + [ViewVariables] public string Name => _name; - [ViewVariables] - public Dictionary PartIDs => _partIDs; + [ViewVariables] public Dictionary PartIDs => _partIDs; - public virtual void LoadFrom(YamlMappingNode mapping){ + public virtual void LoadFrom(YamlMappingNode mapping) + { var serializer = YamlObjectSerializer.NewReader(mapping); serializer.DataField(ref _id, "id", string.Empty); serializer.DataField(ref _name, "name", string.Empty); - serializer.DataField(ref _partIDs, "partIDs", new Dictionary()); + serializer.DataField(ref _partIDs, "partIDs", new Dictionary()); } } } diff --git a/Content.Shared/Health/BodySystem/BodyScanner/BodyScannerSharedValues.cs b/Content.Shared/Body/Scanner/BodyScannerSharedValues.cs similarity index 79% rename from Content.Shared/Health/BodySystem/BodyScanner/BodyScannerSharedValues.cs rename to Content.Shared/Body/Scanner/BodyScannerSharedValues.cs index 0f9f83cf6c..83b11556ba 100644 --- a/Content.Shared/Health/BodySystem/BodyScanner/BodyScannerSharedValues.cs +++ b/Content.Shared/Body/Scanner/BodyScannerSharedValues.cs @@ -1,40 +1,43 @@ using System; using System.Collections.Generic; +using Content.Shared.GameObjects.Components.Body; using Robust.Shared.GameObjects.Components.UserInterface; using Robust.Shared.Serialization; -namespace Content.Shared.Health.BodySystem.BodyScanner +namespace Content.Shared.Body.Scanner { - - - [NetSerializable, Serializable] + [Serializable, NetSerializable] public enum BodyScannerUiKey { Key } - [NetSerializable, Serializable] + [Serializable, NetSerializable] public class BodyScannerInterfaceState : BoundUserInterfaceState { public readonly Dictionary Parts; public readonly BodyScannerTemplateData Template; - public BodyScannerInterfaceState(Dictionary parts, BodyScannerTemplateData template) + + public BodyScannerInterfaceState(Dictionary parts, + BodyScannerTemplateData template) { Template = template; Parts = parts; } } - [NetSerializable, Serializable] + [Serializable, NetSerializable] public class BodyScannerBodyPartData { + public readonly int CurrentDurability; + public readonly int MaxDurability; + public readonly List Mechanisms; public readonly string Name; public readonly string RSIPath; public readonly string RSIState; - public readonly int MaxDurability; - public readonly int CurrentDurability; - public readonly List Mechanisms; - public BodyScannerBodyPartData(string name, string rsiPath, string rsiState, int maxDurability, int currentDurability, List mechanisms) + + public BodyScannerBodyPartData(string name, string rsiPath, string rsiState, int maxDurability, + int currentDurability, List mechanisms) { Name = name; RSIPath = rsiPath; @@ -45,16 +48,18 @@ namespace Content.Shared.Health.BodySystem.BodyScanner } } - [NetSerializable, Serializable] + [Serializable, NetSerializable] public class BodyScannerMechanismData { - public readonly string Name; + public readonly int CurrentDurability; public readonly string Description; + public readonly int MaxDurability; + public readonly string Name; public readonly string RSIPath; public readonly string RSIState; - public readonly int MaxDurability; - public readonly int CurrentDurability; - public BodyScannerMechanismData(string name, string description, string rsiPath, string rsiState, int maxDurability, int currentDurability) + + public BodyScannerMechanismData(string name, string description, string rsiPath, string rsiState, + int maxDurability, int currentDurability) { Name = name; Description = description; @@ -65,11 +70,12 @@ namespace Content.Shared.Health.BodySystem.BodyScanner } } - [NetSerializable, Serializable] + [Serializable, NetSerializable] public class BodyScannerTemplateData { public readonly string Name; public readonly Dictionary Slots; + public BodyScannerTemplateData(string name, Dictionary slots) { Name = name; @@ -77,5 +83,3 @@ namespace Content.Shared.Health.BodySystem.BodyScanner } } } - - diff --git a/Content.Shared/Health/BodySystem/Surgery/GenericSurgeryUIMessages.cs b/Content.Shared/Body/Surgery/GenericSurgeryUIMessages.cs similarity index 71% rename from Content.Shared/Health/BodySystem/Surgery/GenericSurgeryUIMessages.cs rename to Content.Shared/Body/Surgery/GenericSurgeryUIMessages.cs index eee26afde0..6a6f224bb1 100644 --- a/Content.Shared/Health/BodySystem/Surgery/GenericSurgeryUIMessages.cs +++ b/Content.Shared/Body/Surgery/GenericSurgeryUIMessages.cs @@ -3,31 +3,38 @@ using System.Collections.Generic; using Robust.Shared.GameObjects.Components.UserInterface; using Robust.Shared.Serialization; -namespace Content.Shared.Health.BodySystem.Surgery +namespace Content.Shared.Body.Surgery { - - [Serializable, NetSerializable] + [Serializable] + [NetSerializable] public class RequestBodyPartSurgeryUIMessage : BoundUserInterfaceMessage { public Dictionary Targets; + public RequestBodyPartSurgeryUIMessage(Dictionary targets) { Targets = targets; } } - [Serializable, NetSerializable] + + [Serializable] + [NetSerializable] public class RequestMechanismSurgeryUIMessage : BoundUserInterfaceMessage { public Dictionary Targets; + public RequestMechanismSurgeryUIMessage(Dictionary targets) { Targets = targets; } } - [Serializable, NetSerializable] + + [Serializable] + [NetSerializable] public class RequestBodyPartSlotSurgeryUIMessage : BoundUserInterfaceMessage { public Dictionary Targets; + public RequestBodyPartSlotSurgeryUIMessage(Dictionary targets) { Targets = targets; @@ -35,43 +42,47 @@ namespace Content.Shared.Health.BodySystem.Surgery } - - - - [Serializable, NetSerializable] + [Serializable] + [NetSerializable] public class ReceiveBodyPartSurgeryUIMessage : BoundUserInterfaceMessage { - public int SelectedOptionID; - public ReceiveBodyPartSurgeryUIMessage(int selectedOptionID) + public int SelectedOptionId; + + public ReceiveBodyPartSurgeryUIMessage(int selectedOptionId) { - SelectedOptionID = selectedOptionID; + SelectedOptionId = selectedOptionId; } } - [Serializable, NetSerializable] + + [Serializable] + [NetSerializable] public class ReceiveMechanismSurgeryUIMessage : BoundUserInterfaceMessage { - public int SelectedOptionID; - public ReceiveMechanismSurgeryUIMessage(int selectedOptionID) + public int SelectedOptionId; + + public ReceiveMechanismSurgeryUIMessage(int selectedOptionId) { - SelectedOptionID = selectedOptionID; + SelectedOptionId = selectedOptionId; } } - [Serializable, NetSerializable] + + [Serializable] + [NetSerializable] public class ReceiveBodyPartSlotSurgeryUIMessage : BoundUserInterfaceMessage { - public int SelectedOptionID; - public ReceiveBodyPartSlotSurgeryUIMessage(int selectedOptionID) + public int SelectedOptionId; + + public ReceiveBodyPartSlotSurgeryUIMessage(int selectedOptionId) { - SelectedOptionID = selectedOptionID; + SelectedOptionId = selectedOptionId; } } - - - [NetSerializable, Serializable] + [NetSerializable] + [Serializable] public enum GenericSurgeryUiKey { - Key, + Key } } diff --git a/Content.Shared/Body/Template/BodyTemplatePrototype.cs b/Content.Shared/Body/Template/BodyTemplatePrototype.cs new file mode 100644 index 0000000000..d899432cdd --- /dev/null +++ b/Content.Shared/Body/Template/BodyTemplatePrototype.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Shared.GameObjects.Components.Body; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; +using YamlDotNet.RepresentationModel; + +namespace Content.Shared.Body.Template +{ + /// + /// Prototype for the BodyTemplate class. + /// + [Prototype("bodyTemplate")] + [Serializable, NetSerializable] + public class BodyTemplatePrototype : IPrototype, IIndexedPrototype + { + private string _id; + private string _name; + private string _centerSlot; + private Dictionary _slots; + private Dictionary> _connections; + private Dictionary _layers; + private Dictionary _mechanismLayers; + + [ViewVariables] public string ID => _id; + + [ViewVariables] public string Name => _name; + + [ViewVariables] public string CenterSlot => _centerSlot; + + [ViewVariables] public Dictionary Slots => new Dictionary(_slots); + + [ViewVariables] + public Dictionary> Connections => + _connections.ToDictionary(x => x.Key, x => x.Value.ToList()); + + [ViewVariables] public Dictionary Layers => new Dictionary(_layers); + + [ViewVariables] public Dictionary MechanismLayers => new Dictionary(_mechanismLayers); + + public virtual void LoadFrom(YamlMappingNode mapping) + { + var serializer = YamlObjectSerializer.NewReader(mapping); + serializer.DataField(ref _id, "id", string.Empty); + serializer.DataField(ref _name, "name", string.Empty); + serializer.DataField(ref _centerSlot, "centerSlot", string.Empty); + serializer.DataField(ref _slots, "slots", new Dictionary()); + serializer.DataField(ref _connections, "connections", new Dictionary>()); + serializer.DataField(ref _layers, "layers", new Dictionary()); + serializer.DataField(ref _mechanismLayers, "mechanismLayers", new Dictionary()); + + //Our prototypes don't force the user to define a BodyPart connection twice. E.g. Head: Torso v.s. Torso: Head. + //The user only has to do one. We want it to be that way in the code, though, so this cleans that up. + var cleanedConnections = new Dictionary>(); + foreach (var targetSlotName in _slots.Keys) + { + var tempConnections = new List(); + foreach (var (slotName, slotConnections) in _connections) + { + if (slotName == targetSlotName) + { + foreach (var connection in slotConnections) + { + if (!tempConnections.Contains(connection)) + { + tempConnections.Add(connection); + } + } + } + else if (slotConnections.Contains(targetSlotName)) + { + tempConnections.Add(slotName); + } + } + + if (tempConnections.Count > 0) + { + cleanedConnections.Add(targetSlotName, tempConnections); + } + } + + _connections = cleanedConnections; + } + } +} diff --git a/Content.Shared/Chat/ChatMaxMsgLengthMessage.cs b/Content.Shared/Chat/ChatMaxMsgLengthMessage.cs new file mode 100644 index 0000000000..6e183ebca6 --- /dev/null +++ b/Content.Shared/Chat/ChatMaxMsgLengthMessage.cs @@ -0,0 +1,39 @@ +using Lidgren.Network; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.Network; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Content.Shared.Chat +{ + /// + /// This message is sent by the server to let clients know what is the chat's character limit for this server. + /// It is first sent by the client as a request + /// + public sealed class ChatMaxMsgLengthMessage : NetMessage + { + #region REQUIRED + + public const MsgGroups GROUP = MsgGroups.Command; + public const string NAME = nameof(ChatMaxMsgLengthMessage); + public ChatMaxMsgLengthMessage(INetChannel channel) : base(NAME, GROUP) { } + + #endregion + + /// + /// The max length a player-sent message can get + /// + public int MaxMessageLength { get; set; } + + public override void ReadFromBuffer(NetIncomingMessage buffer) + { + MaxMessageLength = buffer.ReadInt32(); + } + + public override void WriteToBuffer(NetOutgoingMessage buffer) + { + buffer.Write(MaxMessageLength); + } + } +} diff --git a/Content.Shared/Construction/ConstructionPrototype.cs b/Content.Shared/Construction/ConstructionPrototype.cs index 04dd6829df..c32960bf63 100644 --- a/Content.Shared/Construction/ConstructionPrototype.cs +++ b/Content.Shared/Construction/ConstructionPrototype.cs @@ -181,11 +181,13 @@ namespace Content.Shared.Construction public abstract class ConstructionStep { - public readonly int Amount = 1; + public readonly int Amount; + public readonly float DoAfterDelay; - protected ConstructionStep(int amount) + protected ConstructionStep(int amount, float doAfterDelay = 0f) { Amount = amount; + DoAfterDelay = doAfterDelay; } } diff --git a/Content.Shared/Damage/DamageClass.cs b/Content.Shared/Damage/DamageClass.cs new file mode 100644 index 0000000000..393b999486 --- /dev/null +++ b/Content.Shared/Damage/DamageClass.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Robust.Shared.Serialization; + +namespace Content.Shared.Damage +{ + [Serializable, NetSerializable] + public enum DamageClass + { + Brute, + Burn, + Toxin, + Airloss + } + + public static class DamageClassExtensions + { + private static readonly ImmutableDictionary> ClassToType = + new Dictionary> + { + {DamageClass.Brute, new List {DamageType.Blunt, DamageType.Piercing}}, + {DamageClass.Burn, new List {DamageType.Heat, DamageType.Disintegration}}, + {DamageClass.Toxin, new List {DamageType.Cellular, DamageType.DNA}}, + {DamageClass.Airloss, new List {DamageType.Asphyxiation}} + }.ToImmutableDictionary(); + + public static List ToTypes(this DamageClass @class) + { + return ClassToType[@class]; + } + + public static Dictionary ToDictionary() + { + return Enum.GetValues(typeof(DamageClass)) + .Cast() + .ToDictionary(@class => @class, type => 0); + } + } +} diff --git a/Content.Shared/Damage/DamageContainer/DamageContainer.cs b/Content.Shared/Damage/DamageContainer/DamageContainer.cs new file mode 100644 index 0000000000..1f185a7c38 --- /dev/null +++ b/Content.Shared/Damage/DamageContainer/DamageContainer.cs @@ -0,0 +1,293 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Shared.GameObjects.Components.Damage; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Shared.Damage.DamageContainer +{ + /// + /// Holds the information regarding the various forms of damage an object has + /// taken (i.e. brute, burn, or toxic damage). + /// + [Serializable, NetSerializable] + public class DamageContainer + { + private Dictionary _damageList = DamageTypeExtensions.ToDictionary(); + + public delegate void HealthChangedDelegate(List changes); + + [NonSerialized] public readonly HealthChangedDelegate OnHealthChanged; + + public DamageContainer(HealthChangedDelegate onHealthChanged, DamageContainerPrototype data) + { + OnHealthChanged = onHealthChanged; + SupportedClasses = data.ActiveDamageClasses; + } + + public DamageContainer(HealthChangedDelegate onHealthChanged, List supportedClasses) + { + OnHealthChanged = onHealthChanged; + SupportedClasses = supportedClasses; + } + + public DamageContainer(HealthChangedDelegate onHealthChanged) + { + OnHealthChanged = onHealthChanged; + } + + [ViewVariables] public virtual List SupportedClasses { get; } + + [ViewVariables] + public virtual List SupportedTypes + { + get + { + var toReturn = new List(); + foreach (var @class in SupportedClasses) + { + toReturn.AddRange(@class.ToTypes()); + } + + return toReturn; + } + } + + /// + /// Sum of all damages kept on record. + /// + [ViewVariables] + public int TotalDamage => _damageList.Values.Sum(); + + public IReadOnlyDictionary DamageClasses => + DamageTypeExtensions.ToClassDictionary(DamageTypes); + + public IReadOnlyDictionary DamageTypes => _damageList; + + public bool SupportsDamageClass(DamageClass @class) + { + return SupportedClasses.Contains(@class); + } + + public bool SupportsDamageType(DamageType type) + { + return SupportedClasses.Contains(type.ToClass()); + } + + /// + /// Attempts to grab the damage value for the given . + /// + /// + /// False if the container does not support that type, true otherwise. + /// + public bool TryGetDamageValue(DamageType type, [NotNullWhen(true)] out int damage) + { + return _damageList.TryGetValue(type, out damage); + } + + /// + /// Grabs the damage value for the given . + /// + public int GetDamageValue(DamageType type) + { + return TryGetDamageValue(type, out var damage) ? damage : 0; + } + + /// + /// Attempts to grab the sum of damage values for the given + /// . + /// + /// The class to get the sum for. + /// The resulting amount of damage, if any. + /// + /// True if the class is supported in this container, false otherwise. + /// + public bool TryGetDamageClassSum(DamageClass @class, [NotNullWhen(true)] out int damage) + { + damage = 0; + + if (SupportsDamageClass(@class)) + { + foreach (var type in @class.ToTypes()) + { + damage += GetDamageValue(type); + } + + return true; + } + + return false; + } + + /// + /// Grabs the sum of damage values for the given . + /// + public int GetDamageClassSum(DamageClass damageClass) + { + var sum = 0; + + foreach (var type in damageClass.ToTypes()) + { + sum += GetDamageValue(type); + } + + return sum; + } + + /// + /// Attempts to change the damage value for the given + /// + /// + /// + /// True if successful, false if this container does not support that type. + /// + public bool TryChangeDamageValue(DamageType type, int delta) + { + var damageClass = type.ToClass(); + + if (SupportsDamageClass(damageClass)) + { + var current = _damageList[type]; + current = _damageList[type] = current + delta; + + if (_damageList[type] < 0) + { + _damageList[type] = 0; + delta = -current; + current = 0; + } + + var datum = new HealthChangeData(type, current, delta); + var data = new List {datum}; + + OnHealthChanged(data); + + return true; + } + + return false; + } + + /// + /// Changes the damage value for the given . + /// + /// The type of damage to change. + /// The amount to change it by. + /// + /// Whether or not to suppress the health change event. + /// + /// + /// True if successful, false if this container does not support that type. + /// + public bool ChangeDamageValue(DamageType type, int delta, bool quiet = false) + { + if (!_damageList.TryGetValue(type, out var current)) + { + return false; + } + + _damageList[type] = current + delta; + + if (_damageList[type] < 0) + { + _damageList[type] = 0; + delta = -current; + } + + current = _damageList[type]; + + var datum = new HealthChangeData(type, current, delta); + var data = new List {datum}; + + OnHealthChanged(data); + + return true; + } + + /// + /// Attempts to set the damage value for the given . + /// + /// + /// True if successful, false if this container does not support that type. + /// + public bool TrySetDamageValue(DamageType type, int newValue) + { + if (newValue < 0) + { + return false; + } + + var damageClass = type.ToClass(); + + if (SupportedClasses.Contains(damageClass)) + { + var old = _damageList[type] = newValue; + _damageList[type] = newValue; + + var delta = newValue - old; + var datum = new HealthChangeData(type, newValue, delta); + var data = new List {datum}; + + OnHealthChanged(data); + + return true; + } + + return false; + } + + /// + /// Tries to set the damage value for the given . + /// + /// The type of damage to set. + /// The value to set it to. + /// + /// Whether or not to suppress the health changed event. + /// + /// True if successful, false otherwise. + public bool SetDamageValue(DamageType type, int newValue, bool quiet = false) + { + if (newValue < 0) + { + return false; + } + + if (!_damageList.ContainsKey(type)) + { + return false; + } + + var old = _damageList[type]; + _damageList[type] = newValue; + + if (!quiet) + { + var delta = newValue - old; + var datum = new HealthChangeData(type, 0, delta); + var data = new List {datum}; + + OnHealthChanged(data); + } + + return true; + } + + public void Heal() + { + var data = new List(); + + foreach (var type in SupportedTypes) + { + var delta = -GetDamageValue(type); + var datum = new HealthChangeData(type, 0, delta); + + data.Add(datum); + SetDamageValue(type, 0, true); + } + + OnHealthChanged(data); + } + } +} diff --git a/Content.Shared/Damage/DamageContainer/DamageContainerPrototype.cs b/Content.Shared/Damage/DamageContainer/DamageContainerPrototype.cs new file mode 100644 index 0000000000..cde45fb999 --- /dev/null +++ b/Content.Shared/Damage/DamageContainer/DamageContainerPrototype.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; +using YamlDotNet.RepresentationModel; + +namespace Content.Shared.Damage.DamageContainer +{ + /// + /// Prototype for the DamageContainer class. + /// + [Prototype("damageContainer")] + [NetSerializable] + [Serializable] + public class DamageContainerPrototype : IPrototype, IIndexedPrototype + { + private List _activeDamageClasses; + private string _id; + + [ViewVariables] public List ActiveDamageClasses => _activeDamageClasses; + + [ViewVariables] public string ID => _id; + + public virtual void LoadFrom(YamlMappingNode mapping) + { + var serializer = YamlObjectSerializer.NewReader(mapping); + serializer.DataField(ref _id, "id", string.Empty); + serializer.DataField(ref _activeDamageClasses, "activeDamageClasses", new List()); + } + } +} diff --git a/Content.Shared/Damage/DamageType.cs b/Content.Shared/Damage/DamageType.cs new file mode 100644 index 0000000000..96cfb2eecb --- /dev/null +++ b/Content.Shared/Damage/DamageType.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Robust.Shared.Serialization; + +namespace Content.Shared.Damage +{ + [Serializable, NetSerializable] + public enum DamageType + { + Blunt, + Piercing, + Heat, + Disintegration, + Cellular, + DNA, + Asphyxiation + } + + public static class DamageTypeExtensions + { + // TODO: Automatically generate this + private static readonly ImmutableDictionary TypeToClass = + new Dictionary + { + {DamageType.Blunt, DamageClass.Brute}, + {DamageType.Piercing, DamageClass.Brute}, + {DamageType.Heat, DamageClass.Burn}, + {DamageType.Disintegration, DamageClass.Burn}, + {DamageType.Cellular, DamageClass.Toxin}, + {DamageType.DNA, DamageClass.Toxin}, + {DamageType.Asphyxiation, DamageClass.Airloss} + }.ToImmutableDictionary(); + + public static DamageClass ToClass(this DamageType type) + { + return TypeToClass[type]; + } + + public static Dictionary ToDictionary() + { + return Enum.GetValues(typeof(DamageType)) + .Cast() + .ToDictionary(type => type, type => 0); + } + + public static Dictionary ToClassDictionary(IReadOnlyDictionary types) + { + var classes = DamageClassExtensions.ToDictionary(); + + foreach (var @class in classes.Keys.ToList()) + foreach (var type in @class.ToTypes()) + { + if (!types.TryGetValue(type, out var damage)) + { + continue; + } + + classes[@class] += damage; + } + + return classes; + } + } +} diff --git a/Content.Shared/Damage/ResistanceSet/ResistanceSet.cs b/Content.Shared/Damage/ResistanceSet/ResistanceSet.cs new file mode 100644 index 0000000000..8aa29edf3d --- /dev/null +++ b/Content.Shared/Damage/ResistanceSet/ResistanceSet.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Shared.Damage.ResistanceSet +{ + /// + /// Set of resistances used by damageable objects. Each DamageType has a multiplier and flat damage reduction value. + /// + [NetSerializable] + [Serializable] + public class ResistanceSet + { + [ViewVariables] + private Dictionary _resistances = + new Dictionary(); + + public ResistanceSet() + { + foreach (var damageType in (DamageType[]) Enum.GetValues(typeof(DamageType))) + { + _resistances.Add(damageType, new ResistanceSetSettings(1f, 0)); + } + } + + public ResistanceSet(ResistanceSetPrototype data) + { + _resistances = data.Resistances; + } + + /// + /// Adjusts input damage with the resistance set values. Only applies reduction if the amount is damage (positive), not + /// healing (negative). + /// + /// Type of damage. + /// Incoming amount of damage. + public int CalculateDamage(DamageType damageType, int amount) + { + if (amount > 0) //Only apply reduction if it's healing, not damage. + { + amount -= _resistances[damageType].FlatReduction; + + if (amount <= 0) + { + return 0; + } + } + + amount = (int) Math.Ceiling(amount * _resistances[damageType].Coefficient); + + return amount; + } + } + + /// + /// Settings for a specific damage type in a resistance set. Flat reduction is applied before the coefficient. + /// + [NetSerializable] + [Serializable] + public struct ResistanceSetSettings + { + [ViewVariables] public float Coefficient { get; private set; } + + [ViewVariables] public int FlatReduction { get; private set; } + + public ResistanceSetSettings(float coefficient, int flatReduction) + { + Coefficient = coefficient; + FlatReduction = flatReduction; + } + } +} diff --git a/Content.Shared/Damage/ResistanceSet/ResistanceSetPrototype.cs b/Content.Shared/Damage/ResistanceSet/ResistanceSetPrototype.cs new file mode 100644 index 0000000000..27ae9a0db7 --- /dev/null +++ b/Content.Shared/Damage/ResistanceSet/ResistanceSetPrototype.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; +using YamlDotNet.RepresentationModel; + +namespace Content.Shared.Damage.ResistanceSet +{ + /// + /// Prototype for the BodyPart class. + /// + [Prototype("resistanceSet")] + [NetSerializable] + [Serializable] + public class ResistanceSetPrototype : IPrototype, IIndexedPrototype + { + private Dictionary _coefficients; + private Dictionary _flatReductions; + private string _id; + + [ViewVariables] public Dictionary Coefficients => _coefficients; + + [ViewVariables] public Dictionary FlatReductions => _flatReductions; + + [ViewVariables] public Dictionary Resistances { get; private set; } + + [ViewVariables] public string ID => _id; + + public virtual void LoadFrom(YamlMappingNode mapping) + { + var serializer = YamlObjectSerializer.NewReader(mapping); + + serializer.DataField(ref _id, "id", string.Empty); + serializer.DataField(ref _coefficients, "coefficients", null); + serializer.DataField(ref _flatReductions, "flatReductions", null); + + Resistances = new Dictionary(); + foreach (var damageType in (DamageType[]) Enum.GetValues(typeof(DamageType))) + { + Resistances.Add(damageType, + new ResistanceSetSettings(_coefficients[damageType], _flatReductions[damageType])); + } + } + } +} diff --git a/Content.Shared/GameObjects/Components/Body/IBodyManagerComponent.cs b/Content.Shared/GameObjects/Components/Body/IBodyManagerComponent.cs new file mode 100644 index 0000000000..bc3ee631f2 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Body/IBodyManagerComponent.cs @@ -0,0 +1,9 @@ +using Content.Shared.GameObjects.Components.Damage; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Shared.GameObjects.Components.Body +{ + public interface IBodyManagerComponent : IDamageableComponent + { + } +} diff --git a/Content.Shared/GameObjects/Components/Body/SharedBodyManagerComponent.cs b/Content.Shared/GameObjects/Components/Body/SharedBodyManagerComponent.cs new file mode 100644 index 0000000000..dcc3ae7dc5 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Body/SharedBodyManagerComponent.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using Content.Shared.GameObjects.Components.Damage; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Body +{ + public abstract class SharedBodyManagerComponent : DamageableComponent, IBodyManagerComponent + { + public override string Name => "BodyManager"; + + public override uint? NetID => ContentNetIDs.BODY_MANAGER; + + public override List SupportedDamageStates => new List {DamageState.Alive, DamageState.Critical, DamageState.Dead}; + + public override DamageState CurrentDamageState => + CurrentDamageState = TotalDamage > 200 + ? DamageState.Dead + : TotalDamage > 100 + ? DamageState.Critical + : DamageState.Alive; + } + + [Serializable, NetSerializable] + public sealed class BodyPartAddedMessage : ComponentMessage + { + public readonly string RSIPath; + public readonly string RSIState; + public readonly Enum RSIMap; + + public BodyPartAddedMessage(string rsiPath, string rsiState, Enum rsiMap) + { + Directed = true; + RSIPath = rsiPath; + RSIState = rsiState; + RSIMap = rsiMap; + } + } + + [Serializable, NetSerializable] + public sealed class BodyPartRemovedMessage : ComponentMessage + { + public readonly Enum RSIMap; + public readonly EntityUid? Dropped; + + public BodyPartRemovedMessage(Enum rsiMap, EntityUid? dropped = null) + { + Directed = true; + RSIMap = rsiMap; + Dropped = dropped; + } + } + + [Serializable, NetSerializable] + public sealed class MechanismSpriteAddedMessage : ComponentMessage + { + public readonly Enum RSIMap; + + public MechanismSpriteAddedMessage(Enum rsiMap) + { + Directed = true; + RSIMap = rsiMap; + } + } + + [Serializable, NetSerializable] + public sealed class MechanismSpriteRemovedMessage : ComponentMessage + { + public readonly Enum RSIMap; + + public MechanismSpriteRemovedMessage(Enum rsiMap) + { + Directed = true; + RSIMap = rsiMap; + } + } + + /// + /// Used to determine whether a BodyPart can connect to another BodyPart. + /// + [Serializable, NetSerializable] + public enum BodyPartCompatibility + { + Universal = 0, + Biological, + Mechanical + } + + /// + /// Each BodyPart has a BodyPartType used to determine a variety of things. + /// For instance, what slots it can fit into. + /// + [Serializable, NetSerializable] + public enum BodyPartType + { + Other = 0, + Torso, + Head, + Arm, + Hand, + Leg, + Foot + } + + /// + /// Defines a surgery operation that can be performed. + /// + [Serializable, NetSerializable] + public enum SurgeryType + { + None = 0, + Incision, + Retraction, + Cauterization, + VesselCompression, + Drilling, + Amputation + } +} diff --git a/Content.Shared/GameObjects/Components/Damage/DamageState.cs b/Content.Shared/GameObjects/Components/Damage/DamageState.cs new file mode 100644 index 0000000000..843c97be57 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Damage/DamageState.cs @@ -0,0 +1,20 @@ +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Shared.GameObjects.Components.Damage +{ + // TODO: Fix summary + /// + /// Defines what state an with a + /// is in. + /// Not all states must be supported - for instance, the + /// only supports + /// and , + /// as inanimate objects don't go into crit. + /// + public enum DamageState + { + Alive, + Critical, + Dead + } +} diff --git a/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs b/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs index 13cf0b0e93..2d31eeb42b 100644 --- a/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs +++ b/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs @@ -1,40 +1,228 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; +using Content.Shared.Damage; +using Content.Shared.Damage.DamageContainer; +using Content.Shared.Damage.ResistanceSet; using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; namespace Content.Shared.GameObjects.Components.Damage { - public abstract class SharedDamageableComponent : Component + /// + /// Component that allows attached entities to take damage. + /// This basic version never dies (thus can take an indefinite amount of damage). + /// + [RegisterComponent] + [ComponentReference(typeof(IDamageableComponent))] + public class DamageableComponent : Component, IDamageableComponent { +#pragma warning disable 649 + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; +#pragma warning restore 649 + public override string Name => "Damageable"; - public sealed override uint? NetID => ContentNetIDs.DAMAGEABLE; - } - // The IDs of the items get synced over the network. - [Serializable, NetSerializable] - public class DamageComponentState : ComponentState - { - public Dictionary CurrentDamage = new Dictionary(); + public event Action HealthChangedEvent = default!; - public DamageComponentState(Dictionary damage) : base(ContentNetIDs.DAMAGEABLE) + [ViewVariables] private ResistanceSet Resistance { get; set; } = default!; + + [ViewVariables] private DamageContainer Damage { get; set; } = default!; + + public virtual List SupportedDamageStates => new List {DamageState.Alive}; + + public virtual DamageState CurrentDamageState { get; protected set; } = DamageState.Alive; + + [ViewVariables] public int TotalDamage => Damage.TotalDamage; + + public IReadOnlyDictionary DamageClasses => Damage.DamageClasses; + + public IReadOnlyDictionary DamageTypes => Damage.DamageTypes; + + public override void ExposeData(ObjectSerializer serializer) { - CurrentDamage = damage; + base.ExposeData(serializer); + + if (serializer.Reading) + { + // Doesn't write to file, TODO? + // Yes, TODO + var containerId = "biologicalDamageContainer"; + var resistanceId = "defaultResistances"; + + serializer.DataField(ref containerId, "damageContainer", "biologicalDamageContainer"); + serializer.DataField(ref resistanceId, "resistances", "defaultResistances"); + + if (!_prototypeManager.TryIndex(containerId!, out DamageContainerPrototype damage)) + { + throw new InvalidOperationException( + $"No {nameof(DamageContainerPrototype)} found with name {containerId}"); + } + + Damage = new DamageContainer(OnHealthChanged, damage); + + if (!_prototypeManager.TryIndex(resistanceId!, out ResistanceSetPrototype resistance)) + { + throw new InvalidOperationException( + $"No {nameof(ResistanceSetPrototype)} found with name {resistanceId}"); + } + + Resistance = new ResistanceSet(resistance); + } + } + + public bool TryGetDamage(DamageType type, out int damage) + { + return Damage.TryGetDamageValue(type, out damage); + } + + public bool ChangeDamage(DamageType type, int amount, bool ignoreResistances, + IEntity? source = null, + HealthChangeParams? extraParams = null) + { + if (Damage.SupportsDamageType(type)) + { + var finalDamage = amount; + if (!ignoreResistances) + { + finalDamage = Resistance.CalculateDamage(type, amount); + } + + Damage.ChangeDamageValue(type, finalDamage); + + return true; + } + + return false; + } + + public bool ChangeDamage(DamageClass @class, int amount, bool ignoreResistances, + IEntity? source = null, + HealthChangeParams? extraParams = null) + { + if (Damage.SupportsDamageClass(@class)) + { + var types = @class.ToTypes(); + + if (amount < 0) + { + // Changing multiple types is a bit more complicated. Might be a better way (formula?) to do this, + // but essentially just loops between each damage category until all healing is used up. + var healingLeft = amount; + var healThisCycle = 1; + + // While we have healing left... + while (healingLeft > 0 && healThisCycle != 0) + { + // Infinite loop fallback, if no healing was done in a cycle + // then exit + healThisCycle = 0; + + int healPerType; + if (healingLeft > -types.Count && healingLeft < 0) + { + // Say we were to distribute 2 healing between 3 + // this will distribute 1 to each (and stop after 2 are given) + healPerType = -1; + } + else + { + // Say we were to distribute 62 healing between 3 + // this will distribute 20 to each, leaving 2 for next loop + healPerType = healingLeft / types.Count; + } + + foreach (var type in types) + { + var healAmount = + Math.Max(Math.Max(healPerType, -Damage.GetDamageValue(type)), + healingLeft); + + Damage.ChangeDamageValue(type, healAmount); + healThisCycle += healAmount; + healingLeft -= healAmount; + } + } + + return true; + } + + var damageLeft = amount; + + while (damageLeft > 0) + { + int damagePerType; + + if (damageLeft < types.Count && damageLeft > 0) + { + damagePerType = 1; + } + else + { + damagePerType = damageLeft / types.Count; + } + + foreach (var type in types) + { + var damageAmount = Math.Min(damagePerType, damageLeft); + Damage.ChangeDamageValue(type, damageAmount); + damageLeft -= damageAmount; + } + } + + return true; + } + + return false; + } + + public bool SetDamage(DamageType type, int newValue, IEntity? source = null, + HealthChangeParams? extraParams = null) + { + if (Damage.SupportsDamageType(type)) + { + Damage.SetDamageValue(type, newValue); + + return true; + } + + return false; + } + + public void Heal() + { + Damage.Heal(); + } + + public void ForceHealthChangedEvent() + { + var data = new List(); + + foreach (var type in Damage.SupportedTypes) + { + var damage = Damage.GetDamageValue(type); + var datum = new HealthChangeData(type, damage, 0); + data.Add(datum); + } + + OnHealthChanged(data); + } + + private void OnHealthChanged(List changes) + { + var args = new HealthChangedEventArgs(this, changes); + OnHealthChanged(args); + } + + protected virtual void OnHealthChanged(HealthChangedEventArgs e) + { + Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, e); + HealthChangedEvent?.Invoke(e); + Dirty(); } } - - /// - /// Damage types used in-game. - /// Total should never be used directly - it's a derived value. - /// - public enum DamageType - { - Total, - Brute, - Heat, - Cold, - Acid, - Toxic, - Electric - } } diff --git a/Content.Shared/GameObjects/Components/Damage/IDamageableComponent.cs b/Content.Shared/GameObjects/Components/Damage/IDamageableComponent.cs new file mode 100644 index 0000000000..f979d326c6 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Damage/IDamageableComponent.cs @@ -0,0 +1,223 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Damage; +using Content.Shared.GameObjects.Components.Body; +using Content.Shared.GameObjects.EntitySystems; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Shared.GameObjects.Components.Damage +{ + public interface IDamageableComponent : IComponent, IExAct + { + /// + /// Called when the entity's values change. + /// Of note is that a "deal 0 damage" call will still trigger this event + /// (including both damage negated by resistance or simply inputting 0 as + /// the amount of damage to deal). + /// + event Action HealthChangedEvent; + + /// + /// List of all DamageStates that + /// can be. + /// + List SupportedDamageStates { get; } + + /// + /// The currently representing this component. + /// + DamageState CurrentDamageState { get; } + + /// + /// Sum of all damages taken. + /// + int TotalDamage { get; } + + /// + /// The amount of damage mapped by . + /// + IReadOnlyDictionary DamageClasses { get; } + + /// + /// The amount of damage mapped by . + /// + IReadOnlyDictionary DamageTypes { get; } + + /// + /// Gets the amount of damage of a type. + /// + /// The type to get the damage of. + /// The amount of damage of that type. + /// + /// True if the given is supported, false otherwise. + /// + bool TryGetDamage(DamageType type, [NotNullWhen(true)] out int damage); + + /// + /// Changes the specified , applying + /// resistance values only if it is damage. + /// + /// Type of damage being changed. + /// + /// Amount of damage being received (positive for damage, negative for heals). + /// + /// + /// Whether or not to ignore resistances. + /// Healing always ignores resistances, regardless of this input. + /// + /// + /// The entity that dealt or healed the damage, if any. + /// + /// + /// Extra parameters that some components may require, such as a specific limb to target. + /// + /// + /// False if the given type is not supported or improper + /// were provided; true otherwise. + /// + bool ChangeDamage(DamageType type, int amount, bool ignoreResistances, IEntity? source = null, + HealthChangeParams? extraParams = null); + + /// + /// Changes the specified , applying + /// resistance values only if it is damage. + /// Spreads amount evenly between the s + /// represented by that class. + /// + /// Class of damage being changed. + /// + /// Amount of damage being received (positive for damage, negative for heals). + /// + /// + /// Whether to ignore resistances. + /// Healing always ignores resistances, regardless of this input. + /// + /// Entity that dealt or healed the damage, if any. + /// + /// Extra parameters that some components may require, + /// such as a specific limb to target. + /// + /// + /// Returns false if the given class is not supported or improper + /// were provided; true otherwise. + /// + bool ChangeDamage(DamageClass @class, int amount, bool ignoreResistances, IEntity? source = null, + HealthChangeParams? extraParams = null); + + /// + /// Forcefully sets the specified to the given + /// value, ignoring resistance values. + /// + /// Type of damage being changed. + /// New damage value to be set. + /// Entity that set the new damage value. + /// + /// Extra parameters that some components may require, + /// such as a specific limb to target. + /// + /// + /// Returns false if the given type is not supported or improper + /// were provided; true otherwise. + /// + bool SetDamage(DamageType type, int newValue, IEntity? source = null, HealthChangeParams? extraParams = null); + + /// + /// Sets all damage values to zero. + /// + void Heal(); + + /// + /// Invokes the HealthChangedEvent with the current values of health. + /// + void ForceHealthChangedEvent(); + + void IExAct.OnExplosion(ExplosionEventArgs eventArgs) + { + var damage = eventArgs.Severity switch + { + ExplosionSeverity.Light => 20, + ExplosionSeverity.Heavy => 60, + ExplosionSeverity.Destruction => 250, + _ => throw new ArgumentOutOfRangeException() + }; + + ChangeDamage(DamageType.Piercing, damage, false, null); + ChangeDamage(DamageType.Heat, damage, false, null); + } + } + + /// + /// Data class with information on how to damage a + /// . + /// While not necessary to damage for all instances, classes such as + /// may require it for extra data + /// (such as selecting which limb to target). + /// + public class HealthChangeParams : EventArgs + { + } + + /// + /// Data class with information on how the + /// values of a have changed. + /// + public class HealthChangedEventArgs : EventArgs + { + /// + /// Reference to the that invoked the event. + /// + public readonly IDamageableComponent Damageable; + + /// + /// List containing data on each that was changed. + /// + public readonly List Data; + + public HealthChangedEventArgs(IDamageableComponent damageable, List data) + { + Damageable = damageable; + Data = data; + } + + public HealthChangedEventArgs(IDamageableComponent damageable, DamageType type, int newValue, int delta) + { + Damageable = damageable; + + var datum = new HealthChangeData(type, newValue, delta); + var data = new List {datum}; + + Data = data; + } + } + + /// + /// Data class with information on how the value of a + /// single has changed. + /// + public struct HealthChangeData + { + /// + /// Type of damage that changed. + /// + public DamageType Type; + + /// + /// The new current value for that damage. + /// + public int NewValue; + + /// + /// How much the health value changed from its last value (negative is heals, positive is damage). + /// + public int Delta; + + public HealthChangeData(DamageType type, int newValue, int delta) + { + Type = type; + NewValue = newValue; + Delta = delta; + } + } +} diff --git a/Content.Shared/GameObjects/Components/Damage/IOnHealthChangedBehavior.cs b/Content.Shared/GameObjects/Components/Damage/IOnHealthChangedBehavior.cs new file mode 100644 index 0000000000..64d31971c9 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Damage/IOnHealthChangedBehavior.cs @@ -0,0 +1,22 @@ +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Shared.GameObjects.Components.Damage +{ + // TODO + /// + /// Component interface that gets triggered after the values of a + /// on the same change. + /// + public interface IOnHealthChangedBehavior + { + /// + /// Called when the entity's + /// is healed or hurt. + /// Of note is that a "deal 0 damage" call will still trigger + /// this function (including both damage negated by resistance or + /// simply inputting 0 as the amount of damage to deal). + /// + /// Details of how the health changed. + public void OnHealthChanged(HealthChangedEventArgs e); + } +} 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/Items/SharedHandsComponent.cs b/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs index 47a15e5f10..02919bcf8e 100644 --- a/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs +++ b/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs @@ -1,8 +1,9 @@ #nullable enable using System; -using Content.Shared.Physics; +using Content.Shared.Physics.Pull; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; +using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -26,8 +27,27 @@ namespace Content.Shared.GameObjects.Components.Items { controller.StopPull(); } + } - PulledObject = null; + public override void HandleMessage(ComponentMessage message, IComponent? component) + { + base.HandleMessage(message, component); + + if (!(message is PullMessage pullMessage) || + pullMessage.Puller.Owner != Owner) + { + return; + } + + switch (message) + { + case PullStartedMessage msg: + PulledObject = msg.Pulled; + break; + case PullStoppedMessage _: + PulledObject = null; + break; + } } } diff --git a/Content.Shared/GameObjects/Components/Medical/SharedMedicalScannerComponent.cs b/Content.Shared/GameObjects/Components/Medical/SharedMedicalScannerComponent.cs index ee0a4f029a..10262d1fd2 100644 --- a/Content.Shared/GameObjects/Components/Medical/SharedMedicalScannerComponent.cs +++ b/Content.Shared/GameObjects/Components/Medical/SharedMedicalScannerComponent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Content.Shared.Damage; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components.UserInterface; using Robust.Shared.Serialization; @@ -13,18 +14,26 @@ namespace Content.Shared.GameObjects.Components.Medical [Serializable, NetSerializable] public class MedicalScannerBoundUserInterfaceState : BoundUserInterfaceState { - public readonly int CurrentHealth; - public readonly int MaxHealth; - public readonly Dictionary DamageDictionary; + public readonly EntityUid? Entity; + public readonly Dictionary DamageClasses; + public readonly Dictionary DamageTypes; + public readonly bool IsScanned; public MedicalScannerBoundUserInterfaceState( - int currentHealth, - int maxHealth, - Dictionary damageDictionary) + EntityUid? entity, + Dictionary damageClasses, + Dictionary damageTypes, + bool isScanned) { - CurrentHealth = currentHealth; - MaxHealth = maxHealth; - DamageDictionary = damageDictionary; + Entity = entity; + DamageClasses = damageClasses; + DamageTypes = damageTypes; + IsScanned = isScanned; + } + + public bool HasDamage() + { + return DamageClasses.Count > 0 || DamageTypes.Count > 0; } } @@ -50,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/Mobs/SharedSpeciesComponent.cs b/Content.Shared/GameObjects/Components/Mobs/SharedSpeciesComponent.cs deleted file mode 100644 index 0f23bbda31..0000000000 --- a/Content.Shared/GameObjects/Components/Mobs/SharedSpeciesComponent.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using Robust.Shared.GameObjects; -using Robust.Shared.Serialization; - -namespace Content.Shared.GameObjects.Components.Mobs -{ - public abstract class SharedSpeciesComponent : Component - { - public sealed override string Name => "Species"; - - [Serializable, NetSerializable] - public enum MobVisuals - { - RotationState - } - - [Serializable, NetSerializable] - public enum MobState - { - /// - /// Mob is standing up - /// - Standing, - - /// - /// Mob is laying down - /// - Down, - } - } -} diff --git a/Content.Shared/GameObjects/Components/Movement/MovementSpeedModifierComponent.cs b/Content.Shared/GameObjects/Components/Movement/MovementSpeedModifierComponent.cs index cdc70f8435..dd9a1e391a 100644 --- a/Content.Shared/GameObjects/Components/Movement/MovementSpeedModifierComponent.cs +++ b/Content.Shared/GameObjects/Components/Movement/MovementSpeedModifierComponent.cs @@ -1,4 +1,4 @@ -using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -10,6 +10,7 @@ namespace Content.Shared.GameObjects.Components.Movement public const float DefaultBaseWalkSpeed = 4.0f; public const float DefaultBaseSprintSpeed = 7.0f; + public override string Name => "MovementSpeedModifier"; private float _cachedWalkSpeedModifier = 1.0f; 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 9c74d666bc..a3b3794d3e 100644 --- a/Content.Shared/GameObjects/Components/Movement/SharedPlayerInputMoverComponent.cs +++ b/Content.Shared/GameObjects/Components/Movement/SharedPlayerInputMoverComponent.cs @@ -1,6 +1,8 @@ #nullable enable using System; +using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Rotation; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.Configuration; @@ -54,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; } @@ -67,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; } @@ -268,7 +270,7 @@ namespace Content.Shared.GameObjects.Components.Movement bool ICollideSpecial.PreventCollide(IPhysBody collidedWith) { // Don't collide with other mobs - return collidedWith.Entity.HasComponent(); + return collidedWith.Entity.HasComponent(); } [Serializable, NetSerializable] 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/Components/Rotation/RotationComponent.cs b/Content.Shared/GameObjects/Components/Rotation/RotationComponent.cs new file mode 100644 index 0000000000..150ed62a30 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Rotation/RotationComponent.cs @@ -0,0 +1,26 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Rotation +{ + [Serializable, NetSerializable] + public enum RotationVisuals + { + RotationState + } + + [Serializable, NetSerializable] + public enum RotationState + { + /// + /// Standing up + /// + Vertical, + + /// + /// Laying down + /// + Horizontal, + } +} diff --git a/Content.Shared/GameObjects/Components/SharedGasSprayerComponent.cs b/Content.Shared/GameObjects/Components/SharedGasSprayerComponent.cs new file mode 100644 index 0000000000..a15f92882c --- /dev/null +++ b/Content.Shared/GameObjects/Components/SharedGasSprayerComponent.cs @@ -0,0 +1,17 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components +{ + public class SharedGasSprayerComponent : Component + { + public sealed override string Name => "GasSprayer"; + } + + [Serializable, NetSerializable] + public enum ExtinguisherVisuals + { + Rotation + } +} diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index 9ecd4a918b..7d7bcfdc4f 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -3,7 +3,7 @@ // Starting from 1000 to avoid crossover with engine. public static class ContentNetIDs { - public const uint DAMAGEABLE = 1000; + // 1000 public const uint DESTRUCTIBLE = 1001; public const uint MAGAZINE_BARREL = 1002; public const uint HANDS = 1003; @@ -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; @@ -64,6 +63,8 @@ public const uint GAS_ANALYZER = 1057; 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.Server/GameObjects/EntitySystems/ActSystem.cs b/Content.Shared/GameObjects/EntitySystems/ActSystem.cs similarity index 98% rename from Content.Server/GameObjects/EntitySystems/ActSystem.cs rename to Content.Shared/GameObjects/EntitySystems/ActSystem.cs index 56be8389ea..a5e6aea7e7 100644 --- a/Content.Server/GameObjects/EntitySystems/ActSystem.cs +++ b/Content.Shared/GameObjects/EntitySystems/ActSystem.cs @@ -5,7 +5,7 @@ using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Map; -namespace Content.Server.GameObjects.EntitySystems +namespace Content.Shared.GameObjects.EntitySystems { /// /// This interface gives components behavior on getting destoyed. @@ -100,6 +100,7 @@ namespace Content.Server.GameObjects.EntitySystems } } } + public enum ExplosionSeverity { Light, diff --git a/Content.Shared/GameObjects/EntitySystems/ActionBlockerSystem.cs b/Content.Shared/GameObjects/EntitySystems/ActionBlockerSystem.cs index b5e1b50281..e7a32bf4fe 100644 --- a/Content.Shared/GameObjects/EntitySystems/ActionBlockerSystem.cs +++ b/Content.Shared/GameObjects/EntitySystems/ActionBlockerSystem.cs @@ -10,24 +10,19 @@ 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; + bool CanUnequip() => true; + bool CanChangeDirection() => true; } diff --git a/Content.Shared/GameObjects/EntitySystems/Atmos/GasOverlayChunk.cs b/Content.Shared/GameObjects/EntitySystems/Atmos/GasOverlayChunk.cs new file mode 100644 index 0000000000..cc4ce2988c --- /dev/null +++ b/Content.Shared/GameObjects/EntitySystems/Atmos/GasOverlayChunk.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Robust.Shared.Interfaces.Timing; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Shared.GameObjects.EntitySystems.Atmos +{ + public sealed class GasOverlayChunk + { + /// + /// Grid for this chunk + /// + public GridId GridIndices { get; } + + /// + /// Origin of this chunk + /// + public MapIndices MapIndices { get; } + + public SharedGasTileOverlaySystem.GasOverlayData[,] TileData = new SharedGasTileOverlaySystem.GasOverlayData[SharedGasTileOverlaySystem.ChunkSize, SharedGasTileOverlaySystem.ChunkSize]; + + public GameTick LastUpdate { get; private set; } + + public GasOverlayChunk(GridId gridIndices, MapIndices mapIndices) + { + GridIndices = gridIndices; + MapIndices = mapIndices; + } + + public void Dirty(GameTick currentTick) + { + LastUpdate = currentTick; + } + + /// + /// Flags Dirty if the data is different. + /// + /// + /// + public void Update(SharedGasTileOverlaySystem.GasOverlayData data, MapIndices indices) + { + DebugTools.Assert(InBounds(indices)); + var (offsetX, offsetY) = (indices.X - MapIndices.X, + indices.Y - MapIndices.Y); + + TileData[offsetX, offsetY] = data; + } + + public void Update(SharedGasTileOverlaySystem.GasOverlayData data, byte x, byte y) + { + DebugTools.Assert(x < SharedGasTileOverlaySystem.ChunkSize && y < SharedGasTileOverlaySystem.ChunkSize); + + TileData[x, y] = data; + } + + public IEnumerable GetAllData() + { + for (var x = 0; x < SharedGasTileOverlaySystem.ChunkSize; x++) + { + for (var y = 0; y < SharedGasTileOverlaySystem.ChunkSize; y++) + { + yield return TileData[x, y]; + } + } + } + + public void GetData(List<(MapIndices, SharedGasTileOverlaySystem.GasOverlayData)> existingData, HashSet indices) + { + foreach (var index in indices) + { + existingData.Add((index, GetData(index))); + } + } + + public IEnumerable GetAllIndices() + { + for (var x = 0; x < SharedGasTileOverlaySystem.ChunkSize; x++) + { + for (var y = 0; y < SharedGasTileOverlaySystem.ChunkSize; y++) + { + yield return new MapIndices(MapIndices.X + x, MapIndices.Y + y); + } + } + } + + public SharedGasTileOverlaySystem.GasOverlayData GetData(MapIndices indices) + { + DebugTools.Assert(InBounds(indices)); + return TileData[indices.X - MapIndices.X, indices.Y - MapIndices.Y]; + } + + private bool InBounds(MapIndices indices) + { + if (indices.X < MapIndices.X || indices.Y < MapIndices.Y) return false; + if (indices.X >= MapIndices.X + SharedGasTileOverlaySystem.ChunkSize || indices.Y >= MapIndices.Y + SharedGasTileOverlaySystem.ChunkSize) return false; + return true; + } + } +} \ No newline at end of file diff --git a/Content.Shared/GameObjects/EntitySystems/Atmos/SharedGasTileOverlaySystem.cs b/Content.Shared/GameObjects/EntitySystems/Atmos/SharedGasTileOverlaySystem.cs new file mode 100644 index 0000000000..6ff74584d0 --- /dev/null +++ b/Content.Shared/GameObjects/EntitySystems/Atmos/SharedGasTileOverlaySystem.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Map; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared.GameObjects.EntitySystems.Atmos +{ + public abstract class SharedGasTileOverlaySystem : EntitySystem + { + public const byte ChunkSize = 8; + protected float AccumulatedFrameTime; + + public static MapIndices GetGasChunkIndices(MapIndices indices) + { + return new MapIndices((int) Math.Floor((float) indices.X / ChunkSize) * ChunkSize, (int) MathF.Floor((float) indices.Y / ChunkSize) * ChunkSize); + } + + [Serializable, NetSerializable] + public struct GasData + { + public byte Index { get; set; } + public byte Opacity { get; set; } + + public GasData(byte gasId, byte opacity) + { + Index = gasId; + Opacity = opacity; + } + } + + [Serializable, NetSerializable] + public readonly struct GasOverlayData : IEquatable + { + public readonly byte FireState; + public readonly float FireTemperature; + public readonly GasData[] Gas; + + public GasOverlayData(byte fireState, float fireTemperature, GasData[] gas) + { + FireState = fireState; + FireTemperature = fireTemperature; + Gas = gas; + } + + public bool Equals(GasOverlayData other) + { + // TODO: Moony had a suggestion on how to do this faster with the hash + // https://discordapp.com/channels/310555209753690112/310555209753690112/744080145219846204 + // Aside from that I can't really see any low-hanging fruit CPU perf wise. + if (Gas?.Length != other.Gas?.Length) return false; + if (FireState != other.FireState) return false; + if (FireTemperature != other.FireTemperature) return false; + + if (Gas == null) + { + return true; + } + + DebugTools.Assert(other.Gas != null); + + for (var i = 0; i < Gas.Length; i++) + { + var thisGas = Gas[i]; + var otherGas = other.Gas[i]; + + if (!thisGas.Equals(otherGas)) + { + return false; + } + } + + return true; + } + } + + /// + /// Invalid tiles for the gas overlay. + /// No point re-sending every tile if only a subset might have been updated. + /// + [Serializable, NetSerializable] + public sealed class GasOverlayMessage : EntitySystemMessage + { + public GridId GridId { get; } + + public List<(MapIndices, GasOverlayData)> OverlayData { get; } + + public GasOverlayMessage(GridId gridIndices, List<(MapIndices,GasOverlayData)> overlayData) + { + GridId = gridIndices; + OverlayData = overlayData; + } + } + } +} diff --git a/Content.Shared/GameObjects/EntitySystems/SharedGasTileOverlaySystem.cs b/Content.Shared/GameObjects/EntitySystems/SharedGasTileOverlaySystem.cs deleted file mode 100644 index 346f44d733..0000000000 --- a/Content.Shared/GameObjects/EntitySystems/SharedGasTileOverlaySystem.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using Robust.Shared.GameObjects; -using Robust.Shared.GameObjects.Systems; -using Robust.Shared.Map; -using Robust.Shared.Serialization; - -namespace Content.Shared.GameObjects.EntitySystems -{ - public abstract class SharedGasTileOverlaySystem : EntitySystem - { - [Serializable, NetSerializable] - public struct GasData - { - public int Index { get; set; } - public float Opacity { get; set; } - - public GasData(int gasId, float opacity) - { - Index = gasId; - Opacity = opacity; - } - } - - [Serializable, NetSerializable] - public readonly struct GasOverlayData - { - public readonly int FireState; - public readonly float FireTemperature; - public readonly GasData[] Gas; - - public GasOverlayData(int fireState, float fireTemperature, GasData[] gas) - { - FireState = fireState; - FireTemperature = fireTemperature; - Gas = gas; - } - } - - [Serializable, NetSerializable] - public readonly struct GasTileOverlayData - { - public readonly GridId GridIndex; - public readonly MapIndices GridIndices; - public readonly GasOverlayData Data; - - public GasTileOverlayData(GridId gridIndex, MapIndices gridIndices, GasOverlayData data) - { - GridIndex = gridIndex; - GridIndices = gridIndices; - Data = data; - } - - public override int GetHashCode() - { - return GridIndex.GetHashCode() ^ GridIndices.GetHashCode() ^ Data.GetHashCode(); - } - } - - [Serializable, NetSerializable] - public class GasTileOverlayMessage : EntitySystemMessage - { - public GasTileOverlayData[] OverlayData { get; } - public bool ClearAllOtherOverlays { get; } - - public GasTileOverlayMessage(GasTileOverlayData[] overlayData, bool clearAllOtherOverlays = false) - { - OverlayData = overlayData; - ClearAllOtherOverlays = clearAllOtherOverlays; - } - } - } -} diff --git a/Content.Shared/GameObjects/EntitySystems/SharedMoverSystem.cs b/Content.Shared/GameObjects/EntitySystems/SharedMoverSystem.cs index 7c9a4537a2..57c66cb3f5 100644 --- a/Content.Shared/GameObjects/EntitySystems/SharedMoverSystem.cs +++ b/Content.Shared/GameObjects/EntitySystems/SharedMoverSystem.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using Content.Shared.GameObjects.Components.Items; using Content.Shared.GameObjects.Components.Movement; using Content.Shared.Physics; +using Content.Shared.Physics.Pull; using Robust.Shared.Configuration; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; @@ -178,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; @@ -187,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/Verb.cs b/Content.Shared/GameObjects/Verbs/Verb.cs index 99cf50b24d..723f90f1f3 100644 --- a/Content.Shared/GameObjects/Verbs/Verb.cs +++ b/Content.Shared/GameObjects/Verbs/Verb.cs @@ -23,6 +23,7 @@ namespace Content.Shared.GameObjects.Verbs /// /// 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/Health/BodySystem/BodyPart/BodyPartProperties/ArmLength.cs b/Content.Shared/Health/BodySystem/BodyPart/BodyPartProperties/ArmLength.cs deleted file mode 100644 index 617bd45050..0000000000 --- a/Content.Shared/Health/BodySystem/BodyPart/BodyPartProperties/ArmLength.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Robust.Shared.Interfaces.Serialization; -using Robust.Shared.Serialization; - -namespace Content.Shared.Health.BodySystem.BodyPart.BodyPartProperties { - - [NetSerializable, Serializable] - class ArmLength : IExposeData { - private float _length; - - public void ExposeData(ObjectSerializer serializer){ - serializer.DataField(ref _length, "length", 2f); - } - } -} diff --git a/Content.Shared/Health/BodySystem/BodyPart/BodyPartPrototype.cs b/Content.Shared/Health/BodySystem/BodyPart/BodyPartPrototype.cs deleted file mode 100644 index d400501f6d..0000000000 --- a/Content.Shared/Health/BodySystem/BodyPart/BodyPartPrototype.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.Generic; -using Robust.Shared.Interfaces.Serialization; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; -using Robust.Shared.ViewVariables; -using YamlDotNet.RepresentationModel; - -namespace Content.Shared.Health.BodySystem.BodyPart { - - - /// - /// Prototype for the BodyPart class. - /// - [Prototype("bodyPart")] - [NetSerializable, Serializable] - public class BodyPartPrototype : IPrototype, IIndexedPrototype { - private string _id; - private string _name; - private string _plural; - private string _rsiPath; - private string _rsiState; - private BodyPartType _partType; - private int _durability; - private int _destroyThreshold; - private float _resistance; - private int _size; - private BodyPartCompatibility _compatibility; - private string _surgeryDataName; - private List _properties; - private List _mechanisms; - - [ViewVariables] - public string ID => _id; - - [ViewVariables] - public string Name => _name; - - [ViewVariables] - public string Plural => _plural; - - [ViewVariables] - public string RSIPath => _rsiPath; - - [ViewVariables] - public string RSIState => _rsiState; - - [ViewVariables] - public BodyPartType PartType => _partType; - - [ViewVariables] - public int Durability => _durability; - - [ViewVariables] - public int DestroyThreshold => _destroyThreshold; - - [ViewVariables] - public float Resistance => _resistance; - - [ViewVariables] - public int Size => _size; - - [ViewVariables] - public BodyPartCompatibility Compatibility => _compatibility; - - [ViewVariables] - public string SurgeryDataName => _surgeryDataName; - - [ViewVariables] - public List Properties => _properties; - - [ViewVariables] - public List Mechanisms => _mechanisms; - - public virtual void LoadFrom(YamlMappingNode mapping){ - var serializer = YamlObjectSerializer.NewReader(mapping); - - serializer.DataField(ref _name, "name", string.Empty); - serializer.DataField(ref _id, "id", string.Empty); - serializer.DataField(ref _plural, "plural", string.Empty); - serializer.DataField(ref _rsiPath, "rsiPath", string.Empty); - serializer.DataField(ref _rsiState, "rsiState", string.Empty); - serializer.DataField(ref _partType, "partType", BodyPartType.Other); - serializer.DataField(ref _surgeryDataName, "surgeryDataType", "BiologicalSurgeryData"); - serializer.DataField(ref _durability, "durability", 50); - serializer.DataField(ref _destroyThreshold, "destroyThreshold", -50); - serializer.DataField(ref _resistance, "resistance", 0f); - serializer.DataField(ref _size, "size", 0); - serializer.DataField(ref _compatibility, "compatibility", BodyPartCompatibility.Universal); - serializer.DataField(ref _properties, "properties", new List()); - serializer.DataField(ref _mechanisms, "mechanisms", new List()); - } - } -} diff --git a/Content.Shared/Health/BodySystem/BodyTemplate/BodyTemplatePrototype.cs b/Content.Shared/Health/BodySystem/BodyTemplate/BodyTemplatePrototype.cs deleted file mode 100644 index 50ac37c065..0000000000 --- a/Content.Shared/Health/BodySystem/BodyTemplate/BodyTemplatePrototype.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; -using Robust.Shared.ViewVariables; -using YamlDotNet.RepresentationModel; - -namespace Content.Shared.Health.BodySystem.BodyTemplate { - - /// - /// Prototype for the BodyTemplate class. - /// - [Prototype("bodyTemplate")] - [NetSerializable, Serializable] - public class BodyTemplatePrototype : IPrototype, IIndexedPrototype { - private string _id; - private string _name; - private string _centerSlot; - private Dictionary _slots; - private Dictionary> _connections; - - [ViewVariables] - public string ID => _id; - - [ViewVariables] - public string Name => _name; - - [ViewVariables] - public string CenterSlot => _centerSlot; - - [ViewVariables] - public Dictionary Slots => _slots; - - [ViewVariables] - public Dictionary> Connections => _connections; - - public virtual void LoadFrom(YamlMappingNode mapping){ - var serializer = YamlObjectSerializer.NewReader(mapping); - serializer.DataField(ref _name, "name", string.Empty); - serializer.DataField(ref _id, "id", string.Empty); - serializer.DataField(ref _centerSlot, "centerSlot", string.Empty); - serializer.DataField(ref _slots, "slots", new Dictionary()); - serializer.DataField(ref _connections, "connections", new Dictionary>()); - - - //Our prototypes don't force the user to define a BodyPart connection twice. E.g. Head: Torso v.s. Torso: Head. - //The user only has to do one. We want it to be that way in the code, though, so this cleans that up. - Dictionary> cleanedConnections = new Dictionary>(); - foreach (var (targetSlotName, slotType) in _slots) - { - List tempConnections = new List(); - foreach (var (slotName, slotConnections) in _connections) - { - if (slotName == targetSlotName){ - foreach (string connection in slotConnections) { - if (!tempConnections.Contains(connection)) - tempConnections.Add(connection); - } - } - else if (slotConnections.Contains(targetSlotName)) - { - tempConnections.Add(slotName); - } - } - if(tempConnections.Count > 0) - cleanedConnections.Add(targetSlotName, tempConnections); - } - _connections = cleanedConnections; - } - } -} diff --git a/Content.Shared/Health/BodySystem/BodysystemValues.cs b/Content.Shared/Health/BodySystem/BodysystemValues.cs deleted file mode 100644 index ce16122f8d..0000000000 --- a/Content.Shared/Health/BodySystem/BodysystemValues.cs +++ /dev/null @@ -1,20 +0,0 @@ - -namespace Content.Shared.Health.BodySystem -{ - /// - /// Used to determine whether a BodyPart can connect to another BodyPart. - /// - public enum BodyPartCompatibility { Universal, Biological, Mechanical }; - - /// - /// Each BodyPart has a BodyPartType used to determine a variety of things - for instance, what slots it can fit into. - /// - public enum BodyPartType { Other, Torso, Head, Arm, Hand, Leg, Foot }; - - /// - /// Defines a surgery operation that can be performed. - /// - public enum SurgeryType { Incision, Retraction, Cauterization, VesselCompression, Drilling, Amputation } - - -} diff --git a/Content.Shared/Health/DamageContainer/AbstractDamageContainer.cs b/Content.Shared/Health/DamageContainer/AbstractDamageContainer.cs deleted file mode 100644 index fb146adf46..0000000000 --- a/Content.Shared/Health/DamageContainer/AbstractDamageContainer.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Robust.Shared.Serialization; -using Robust.Shared.ViewVariables; - -namespace Content.Shared.Health.DamageContainer -{ - public enum DamageClass { Brute, Burn, Toxin, Airloss } - public enum DamageType { Blunt, Piercing, Heat, Disintegration, Cellular, DNA, Airloss } - - public static class DamageContainerValues - { - public static readonly Dictionary> DamageClassToType = new Dictionary> - { - { DamageClass.Brute, new List{ DamageType.Blunt, DamageType.Piercing }}, - { DamageClass.Burn, new List{ DamageType.Heat, DamageType.Disintegration }}, - { DamageClass.Toxin, new List{ DamageType.Cellular, DamageType.DNA}}, - { DamageClass.Airloss, new List{ DamageType.Airloss }} - }; - - //TODO: autogenerate this lol - public static readonly Dictionary DamageTypeToClass = new Dictionary - { - { DamageType.Blunt, DamageClass.Brute }, - { DamageType.Piercing, DamageClass.Brute }, - { DamageType.Heat, DamageClass.Burn }, - { DamageType.Disintegration, DamageClass.Burn }, - { DamageType.Cellular, DamageClass.Toxin }, - { DamageType.DNA, DamageClass.Toxin }, - { DamageType.Airloss, DamageClass.Airloss } - }; - } - - /// - /// Abstract class for all damage container classes. - /// - [NetSerializable, Serializable] - public abstract class AbstractDamageContainer - { - [ViewVariables] - abstract public List SupportedDamageClasses { get; } - - private Dictionary _damageList = new Dictionary(); - [ViewVariables] - public Dictionary DamageList => _damageList; - - /// - /// Sum of all damages kept on record. - /// - [ViewVariables] - public int Damage - { - get - { - return _damageList.Sum(x => x.Value); - } - } - - public AbstractDamageContainer() - { - foreach(DamageClass damageClass in SupportedDamageClasses){ - DamageContainerValues.DamageClassToType.TryGetValue(damageClass, out List childrenDamageTypes); - foreach (DamageType damageType in childrenDamageTypes) - { - _damageList.Add(damageType, 0); - } - } - } - - - - /// - /// Attempts to grab the damage value for the given DamageType - returns false if the container does not support that type. - /// - public bool TryGetDamageValue(DamageType type, out int damage) - { - return _damageList.TryGetValue(type, out damage); - } - - /// - /// Attempts to set the damage value for the given DamageType - returns false if the container does not support that type. - /// - public bool TrySetDamageValue(DamageType type, int target) - { - DamageContainerValues.DamageTypeToClass.TryGetValue(type, out DamageClass classType); - if (SupportedDamageClasses.Contains(classType)) - { - _damageList[type] = target; - return true; - } - return false; - } - - /// - /// Attempts to change the damage value for the given DamageType - returns false if the container does not support that type. - /// - public bool TryChangeDamageValue(DamageType type, int delta) - { - DamageContainerValues.DamageTypeToClass.TryGetValue(type, out DamageClass classType); - if (SupportedDamageClasses.Contains(classType)) - { - _damageList[type] = _damageList[type] + delta; - return true; - } - return false; - } - } -} diff --git a/Content.Shared/Health/DamageContainer/BiologicalDamageContainer.cs b/Content.Shared/Health/DamageContainer/BiologicalDamageContainer.cs deleted file mode 100644 index e2b26b04e5..0000000000 --- a/Content.Shared/Health/DamageContainer/BiologicalDamageContainer.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using Robust.Shared.Serialization; - -namespace Content.Shared.Health.DamageContainer -{ - [NetSerializable, Serializable] - public class BiologicalDamageContainer : AbstractDamageContainer - { - public override List SupportedDamageClasses { - get { return new List { DamageClass.Brute, DamageClass.Burn, DamageClass.Toxin, DamageClass.Airloss }; } - } - } -} - diff --git a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IInteractUsing.cs b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IInteractUsing.cs index 79890756a0..5b28a693e0 100644 --- a/Content.Shared/Interfaces/GameObjects/Components/Interaction/IInteractUsing.cs +++ b/Content.Shared/Interfaces/GameObjects/Components/Interaction/IInteractUsing.cs @@ -1,4 +1,6 @@ using System; +using System.Threading.Tasks; +using System.Collections.Generic; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; @@ -12,10 +14,16 @@ namespace Content.Shared.Interfaces.GameObjects.Components /// public interface IInteractUsing { + /// + /// The interaction priority. Higher numbers get called first. + /// + /// Priority defaults to 0 + int Priority => 0; + /// /// Called when using one object on another when user is in range of the target entity. /// - bool InteractUsing(InteractUsingEventArgs eventArgs); + Task InteractUsing(InteractUsingEventArgs eventArgs); } public class InteractUsingEventArgs : EventArgs, ITargetedInteractEventArgs diff --git a/Content.Shared/Network/NetMessages/MsgRequestWindowAttention.cs b/Content.Shared/Network/NetMessages/MsgRequestWindowAttention.cs new file mode 100644 index 0000000000..e14ca862e5 --- /dev/null +++ b/Content.Shared/Network/NetMessages/MsgRequestWindowAttention.cs @@ -0,0 +1,27 @@ +using Lidgren.Network; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.Network; + +namespace Content.Shared.Network.NetMessages +{ + public sealed class MsgRequestWindowAttention : NetMessage + { + #region REQUIRED + + public const MsgGroups GROUP = MsgGroups.Command; + public const string NAME = nameof(MsgRequestWindowAttention); + public MsgRequestWindowAttention(INetChannel channel) : base(NAME, GROUP) { } + + #endregion + + public override void ReadFromBuffer(NetIncomingMessage buffer) + { + // Nothing + } + + public override void WriteToBuffer(NetOutgoingMessage buffer) + { + // Nothing + } + } +} 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/Physics/GasVaporController.cs b/Content.Shared/Physics/GasVaporController.cs new file mode 100644 index 0000000000..d58c15176d --- /dev/null +++ b/Content.Shared/Physics/GasVaporController.cs @@ -0,0 +1,16 @@ +using Robust.Shared.Maths; +using Robust.Shared.Physics; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Content.Shared.Physics +{ + public class GasVaporController : VirtualController + { + public void Move(Vector2 velocityDirection, float speed) + { + LinearVelocity = velocityDirection * speed; + } + } +} diff --git a/Content.Shared/Physics/PullController.cs b/Content.Shared/Physics/Pull/PullController.cs similarity index 66% rename from Content.Shared/Physics/PullController.cs rename to Content.Shared/Physics/Pull/PullController.cs index c0ea728571..8bb76473be 100644 --- a/Content.Shared/Physics/PullController.cs +++ b/Content.Shared/Physics/Pull/PullController.cs @@ -1,15 +1,16 @@ #nullable enable using System; -using Content.Shared.GameObjects.Components.Items; using Content.Shared.GameObjects.EntitySystems; +using Robust.Shared.Containers; using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.Map; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Physics; +using Robust.Shared.Utility; -namespace Content.Shared.Physics +namespace Content.Shared.Physics.Pull { public class PullController : VirtualController { @@ -25,15 +26,54 @@ namespace Content.Shared.Physics public ICollidableComponent? Puller => _puller; - public void StartPull(ICollidableComponent? pull) + public void StartPull(ICollidableComponent puller) { - _puller = pull; + DebugTools.AssertNotNull(puller); + + if (_puller == puller) + { + return; + } + + _puller = puller; + + if (ControlledComponent == null) + { + return; + } + + ControlledComponent.WakeBody(); + + var message = new PullStartedMessage(this, _puller, ControlledComponent); + + _puller.Owner.SendMessage(null, message); + ControlledComponent.Owner.SendMessage(null, message); } public void StopPull() { + var oldPuller = _puller; + + if (oldPuller == null) + { + return; + } + _puller = null; - ControlledComponent?.TryRemoveController(); + + if (ControlledComponent == null) + { + return; + } + + ControlledComponent.WakeBody(); + + var message = new PullStoppedMessage(this, oldPuller, ControlledComponent); + + oldPuller.Owner.SendMessage(null, message); + ControlledComponent.Owner.SendMessage(null, message); + + ControlledComponent.TryRemoveController(); } public void TryMoveTo(GridCoordinates from, GridCoordinates to) @@ -50,6 +90,8 @@ namespace Content.Shared.Physics return; } + ControlledComponent.WakeBody(); + var dist = _puller.Owner.Transform.GridPosition.Position - to.Position; if (Math.Sqrt(dist.LengthSquared) > DistBeforeStopPull || @@ -68,12 +110,18 @@ namespace Content.Shared.Physics return; } + if (!_puller.Owner.IsInSameOrNoContainer(ControlledComponent.Owner)) + { + StopPull(); + return; + } + // Are we outside of pulling range? var dist = _puller.Owner.Transform.WorldPosition - ControlledComponent.Owner.Transform.WorldPosition; if (dist.Length > DistBeforeStopPull) { - _puller.Owner.GetComponent().StopPull(); + StopPull(); } else if (_movingTo.HasValue) { diff --git a/Content.Shared/Physics/Pull/PullMessage.cs b/Content.Shared/Physics/Pull/PullMessage.cs new file mode 100644 index 0000000000..811eae404e --- /dev/null +++ b/Content.Shared/Physics/Pull/PullMessage.cs @@ -0,0 +1,19 @@ +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components; + +namespace Content.Shared.Physics.Pull +{ + public class PullMessage : ComponentMessage + { + public readonly PullController Controller; + public readonly ICollidableComponent Puller; + public readonly ICollidableComponent Pulled; + + protected PullMessage(PullController controller, ICollidableComponent puller, ICollidableComponent pulled) + { + Controller = controller; + Puller = puller; + Pulled = pulled; + } + } +} diff --git a/Content.Shared/Physics/Pull/PullStartedMessage.cs b/Content.Shared/Physics/Pull/PullStartedMessage.cs new file mode 100644 index 0000000000..263c0b8db1 --- /dev/null +++ b/Content.Shared/Physics/Pull/PullStartedMessage.cs @@ -0,0 +1,12 @@ +using Robust.Shared.GameObjects.Components; + +namespace Content.Shared.Physics.Pull +{ + public class PullStartedMessage : PullMessage + { + public PullStartedMessage(PullController controller, ICollidableComponent puller, ICollidableComponent pulled) : + base(controller, puller, pulled) + { + } + } +} diff --git a/Content.Shared/Physics/Pull/PullStoppedMessage.cs b/Content.Shared/Physics/Pull/PullStoppedMessage.cs new file mode 100644 index 0000000000..c018558341 --- /dev/null +++ b/Content.Shared/Physics/Pull/PullStoppedMessage.cs @@ -0,0 +1,12 @@ +using Robust.Shared.GameObjects.Components; + +namespace Content.Shared.Physics.Pull +{ + public class PullStoppedMessage : PullMessage + { + public PullStoppedMessage(PullController controller, ICollidableComponent puller, ICollidableComponent pulled) : + base(controller, puller, pulled) + { + } + } +} diff --git a/Content.Shared/Preferences/Appearance/HumanoidCharacterAppearance.cs b/Content.Shared/Preferences/Appearance/HumanoidCharacterAppearance.cs index 265803fb5e..72b53675c1 100644 --- a/Content.Shared/Preferences/Appearance/HumanoidCharacterAppearance.cs +++ b/Content.Shared/Preferences/Appearance/HumanoidCharacterAppearance.cs @@ -1,17 +1,24 @@ +using System; +using Robust.Shared.Serialization; + namespace Content.Shared.Preferences.Appearance { + [Serializable, NetSerializable] public enum HumanoidVisualLayers { Hair, FacialHair, Chest, Head, + Eyes, RArm, LArm, RHand, LHand, RLeg, LLeg, + RFoot, + LFoot, StencilMask } } diff --git a/Content.Shared/SharedGameTicker.cs b/Content.Shared/SharedGameTicker.cs index cd16b79191..d29cc0f84e 100644 --- a/Content.Shared/SharedGameTicker.cs +++ b/Content.Shared/SharedGameTicker.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.IO; using Lidgren.Network; using Robust.Shared.Interfaces.Network; +using Robust.Shared.Interfaces.Serialization; +using Robust.Shared.IoC; using Robust.Shared.Network; namespace Content.Shared @@ -51,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 @@ -152,6 +180,58 @@ namespace Content.Shared } } + protected class MsgTickerLobbyReady : NetMessage + { + #region REQUIRED + + public const MsgGroups GROUP = MsgGroups.Command; + public const string NAME = nameof(MsgTickerLobbyReady); + public MsgTickerLobbyReady(INetChannel channel) : base(NAME, GROUP) { } + + #endregion + + /// + /// The Players Ready (SessionID:ready) + /// + public Dictionary PlayerReady { get; set; } + + public override void ReadFromBuffer(NetIncomingMessage buffer) + { + PlayerReady = new Dictionary(); + var length = buffer.ReadInt32(); + for (int i = 0; i < length; i++) + { + var serializer = IoCManager.Resolve(); + var byteLength = buffer.ReadVariableInt32(); + NetSessionId sessionID; + using (var stream = buffer.ReadAsStream(byteLength)) + { + serializer.DeserializeDirect(stream, out sessionID); + } + var ready = buffer.ReadBoolean(); + PlayerReady.Add(sessionID, ready); + } + } + + public override void WriteToBuffer(NetOutgoingMessage buffer) + { + var serializer = IoCManager.Resolve(); + buffer.Write(PlayerReady.Count); + foreach (var p in PlayerReady) + { + using (var stream = new MemoryStream()) + { + serializer.SerializeDirect(stream, p.Key); + buffer.WriteVariableInt32((int) stream.Length); + stream.TryGetBuffer(out var segment); + buffer.Write(segment); + } + buffer.Write(p.Value); + } + } + } + + public struct RoundEndPlayerInfo { public string PlayerOOCName; @@ -173,25 +253,27 @@ namespace Content.Shared #endregion public string GamemodeTitle; + public string RoundEndText; public TimeSpan RoundDuration; - public uint PlayerCount; + public int PlayerCount; public List AllPlayersEndInfo; public override void ReadFromBuffer(NetIncomingMessage buffer) { GamemodeTitle = buffer.ReadString(); + RoundEndText = buffer.ReadString(); var hours = buffer.ReadInt32(); var mins = buffer.ReadInt32(); var seconds = buffer.ReadInt32(); RoundDuration = new TimeSpan(hours, mins, seconds); - PlayerCount = buffer.ReadUInt32(); + PlayerCount = buffer.ReadInt32(); AllPlayersEndInfo = new List(); - for(var i = 0; i < PlayerCount + 1; i++) + for(var i = 0; i < PlayerCount; i++) { var readPlayerData = new RoundEndPlayerInfo { @@ -209,12 +291,13 @@ 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); - buffer.Write(PlayerCount); + buffer.Write(AllPlayersEndInfo.Count); foreach(var playerEndInfo in AllPlayersEndInfo) { buffer.Write(playerEndInfo.PlayerOOCName); diff --git a/Content.Shared/StationEvents/SharedStationEvent.cs b/Content.Shared/StationEvents/SharedStationEvent.cs new file mode 100644 index 0000000000..8cecfb14df --- /dev/null +++ b/Content.Shared/StationEvents/SharedStationEvent.cs @@ -0,0 +1,46 @@ +using Lidgren.Network; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.Interfaces.Serialization; +using Robust.Shared.IoC; +using Robust.Shared.Network; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Content.Shared.StationEvents +{ + public class SharedStationEvent + { + public class MsgGetStationEvents : NetMessage + { + #region REQUIRED + + public const MsgGroups GROUP = MsgGroups.Command; + public const string NAME = nameof(MsgGetStationEvents); + public MsgGetStationEvents(INetChannel channel) : base(NAME, GROUP) { } + + #endregion + public List Events; + public override void ReadFromBuffer(NetIncomingMessage buffer) + { + var serializer = IoCManager.Resolve(); + var length = buffer.ReadVariableInt32(); + using var stream = buffer.ReadAsStream(length); + Events = serializer.Deserialize>(stream); + } + + public override void WriteToBuffer(NetOutgoingMessage buffer) + { + var serializer = IoCManager.Resolve(); + using (var stream = new MemoryStream()) + { + serializer.Serialize(stream, Events); + buffer.WriteVariableInt32((int)stream.Length); + stream.TryGetBuffer(out var segment); + buffer.Write(segment); + } + } + } + } +} diff --git a/Content.Tests/Shared/BodyTemplateTest.cs b/Content.Tests/Shared/BodySystem/BodyTemplateTest.cs similarity index 80% rename from Content.Tests/Shared/BodyTemplateTest.cs rename to Content.Tests/Shared/BodySystem/BodyTemplateTest.cs index 87b3455989..df0240b8d4 100644 --- a/Content.Tests/Shared/BodyTemplateTest.cs +++ b/Content.Tests/Shared/BodySystem/BodyTemplateTest.cs @@ -1,23 +1,22 @@ using System.Collections.Generic; -using Content.Server.Health.BodySystem.BodyTemplate; -using Content.Shared.Health.BodySystem; +using Content.Server.Body; +using Content.Shared.GameObjects.Components.Body; using NUnit.Framework; using Robust.UnitTesting; -namespace Content.Tests.Shared +namespace Content.Tests.Shared.BodySystem { [TestFixture, Parallelizable, TestOf(typeof(BodyTemplate))] public class BodyTemplateTest : RobustUnitTest { [Test] - public void CheckBodyTemplateHashingWorks() + public void BodyTemplateHashCodeTest() { - - BodyTemplate a = new BodyTemplate(); - BodyTemplate b = new BodyTemplate(); - BodyTemplate c = new BodyTemplate(); - BodyTemplate d = new BodyTemplate(); - BodyTemplate e = new BodyTemplate(); + var a = new BodyTemplate(); + var b = new BodyTemplate(); + var c = new BodyTemplate(); + var d = new BodyTemplate(); + var e = new BodyTemplate(); a.Slots.Add("torso", BodyPartType.Torso); a.Slots.Add("left arm", BodyPartType.Arm); diff --git a/Resources/Groups/groups.yml b/Resources/Groups/groups.yml index 1cf921b5f8..e18adbfd95 100644 --- a/Resources/Groups/groups.yml +++ b/Resources/Groups/groups.yml @@ -95,6 +95,8 @@ - tubeconnections - tilewalls - events + - destroymechanism + - readyall CanViewVar: true CanAdminPlace: true @@ -184,6 +186,8 @@ - setatmostemp - tilewalls - events + - destroymechanism + - readyall CanViewVar: true CanAdminPlace: true CanScript: true diff --git a/Resources/Maps/saltern.yml b/Resources/Maps/saltern.yml index e387f022c3..f3ccd2b9b1 100644 --- a/Resources/Maps/saltern.yml +++ b/Resources/Maps/saltern.yml @@ -33466,10 +33466,10 @@ entities: rot: -1.5707963267948966 rad type: Transform - uid: 2877 - type: TrashSpawner + type: SuspicionPistolMagazineSpawner components: - parent: 15 - pos: -21.5,-12.5 + pos: -35.5,0.5 rot: -1.5707963267948966 rad type: Transform - uid: 2878 @@ -37344,7 +37344,6 @@ entities: pos: -13.672081,21.719378 rot: -1.5707963267948966 rad type: Transform - - type: BatteryBarrel - anchored: False type: Collidable - containers: @@ -37371,7 +37370,6 @@ entities: pos: -14.515831,21.735003 rot: -1.5707963267948966 rad type: Transform - - type: BatteryBarrel - anchored: False type: Collidable - containers: @@ -37840,10 +37838,10 @@ entities: type: Robust.Server.GameObjects.Components.Container.Container type: ContainerContainer - uid: 3282 - type: metal_wall + type: SuspicionPistolSpawner components: - parent: 15 - pos: 13.293709,-8.309891 + pos: 8.5,-4.5 rot: -1.5707963267948966 rad type: Transform - uid: 3283 @@ -38099,4 +38097,312 @@ entities: type: Transform - anchored: False type: Collidable +- uid: 3310 + type: SuspicionShotgunMagazineSpawner + components: + - parent: 15 + pos: -6.5,-3.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3311 + type: SuspicionShotgunMagazineSpawner + components: + - parent: 15 + pos: -7.5,-3.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3312 + type: SuspicionShotgunSpawner + components: + - parent: 15 + pos: -7.5,-6.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3313 + type: SuspicionSniperSpawner + components: + - parent: 15 + pos: -8.5,-12.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3314 + type: SuspicionRifleMagazineSpawner + components: + - parent: 15 + pos: -13.5,-6.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3315 + type: SuspicionGrenadesSpawner + components: + - parent: 15 + pos: 15.5,-0.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3316 + type: SuspicionGrenadesSpawner + components: + - parent: 15 + pos: 18.5,1.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3317 + type: SuspicionHitscanSpawner + components: + - parent: 15 + pos: 25.5,0.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3318 + type: SuspicionGrenadesSpawner + components: + - parent: 15 + pos: 0.5,-15.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3319 + type: SuspicionGrenadesSpawner + components: + - parent: 15 + pos: -8.5,-17.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3320 + type: SuspicionMagnumMagazineSpawner + components: + - parent: 15 + pos: 14.5,-2.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3321 + type: SuspicionRevolverSpawner + components: + - parent: 15 + pos: 15.5,-3.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3322 + type: SuspicionMagnumMagazineSpawner + components: + - parent: 15 + pos: 16.5,7.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3323 + type: SuspicionSMGSpawner + components: + - parent: 15 + pos: -7.5,8.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3324 + type: SuspicionRifleMagazineSpawner + components: + - parent: 15 + pos: -7.5,7.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3325 + type: SuspicionShotgunSpawner + components: + - parent: 15 + pos: -29.5,-5.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3326 + type: SuspicionShotgunMagazineSpawner + components: + - parent: 15 + pos: -30.5,-4.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3327 + type: SuspicionShotgunMagazineSpawner + components: + - parent: 15 + pos: -29.5,-4.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3328 + type: SuspicionPistolMagazineSpawner + components: + - parent: 15 + pos: -3.5,0.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3329 + type: SuspicionPistolMagazineSpawner + components: + - parent: 15 + pos: -10.5,1.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3330 + type: SuspicionPistolMagazineSpawner + components: + - parent: 15 + pos: -9.5,6.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3331 + type: SuspicionPistolMagazineSpawner + components: + - parent: 15 + pos: -29.5,9.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3332 + type: SuspicionPistolMagazineSpawner + components: + - parent: 15 + pos: -31.5,13.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3333 + type: SuspicionPistolSpawner + components: + - parent: 15 + pos: -5.5,24.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3334 + type: SuspicionPistolSpawner + components: + - parent: 15 + pos: -10.5,-0.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3335 + type: SuspicionHitscanSpawner + components: + - parent: 15 + pos: -14.5,18.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3336 + type: SuspicionRevolverSpawner + components: + - parent: 15 + pos: -14.5,19.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3337 + type: SuspicionPistolSpawner + components: + - parent: 15 + pos: -13.5,18.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3338 + type: SuspicionRifleSpawner + components: + - parent: 15 + pos: -13.5,19.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3339 + type: SuspicionSMGSpawner + components: + - parent: 15 + pos: -13.5,20.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3340 + type: SuspicionShotgunSpawner + components: + - parent: 15 + pos: -12.5,20.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3341 + type: SuspicionSniperSpawner + components: + - parent: 15 + pos: -12.5,19.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3342 + type: SuspicionLaunchersSpawner + components: + - parent: 15 + pos: -12.5,18.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3343 + type: SuspicionGrenadesSpawner + components: + - parent: 15 + pos: -11.5,20.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3344 + type: SuspicionGrenadesSpawner + components: + - parent: 15 + pos: -11.5,19.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3345 + type: SuspicionGrenadesSpawner + components: + - parent: 15 + pos: -11.5,18.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3346 + type: SuspicionShotgunMagazineSpawner + components: + - parent: 15 + pos: -35.5,-7.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3347 + type: SuspicionShotgunMagazineSpawner + components: + - parent: 15 + pos: -35.5,-6.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3348 + type: SuspicionRifleMagazineSpawner + components: + - parent: 15 + pos: -35.5,-4.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3349 + type: SuspicionRifleMagazineSpawner + components: + - parent: 15 + pos: -35.5,-3.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3350 + type: SuspicionPistolMagazineSpawner + components: + - parent: 15 + pos: -35.5,-0.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3351 + type: SuspicionShotgunMagazineSpawner + components: + - parent: 15 + pos: -23.5,-13.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3352 + type: SuspicionShotgunSpawner + components: + - parent: 15 + pos: -15.5,-14.5 + rot: -1.5707963267948966 rad + type: Transform +- uid: 3353 + type: SuspicionHitscanSpawner + components: + - parent: 15 + pos: -15.5,-11.5 + rot: -1.5707963267948966 rad + type: Transform ... diff --git a/Resources/Prototypes/BodySystem/Mechanisms/basic_human_organs.yml b/Resources/Prototypes/Body/Mechanisms/basic_human_organs.yml similarity index 58% rename from Resources/Prototypes/BodySystem/Mechanisms/basic_human_organs.yml rename to Resources/Prototypes/Body/Mechanisms/basic_human_organs.yml index 86a6df972f..49a4c025ce 100644 --- a/Resources/Prototypes/BodySystem/Mechanisms/basic_human_organs.yml +++ b/Resources/Prototypes/Body/Mechanisms/basic_human_organs.yml @@ -4,32 +4,33 @@ name: "human heart (debug)" components: - type: Sprite - sprite: Mobs/Parts/organs_human.rsi - state: heart_human + sprite: Mobs/Species/Human/organs.rsi + state: heart-on - type: Icon - sprite: Mobs/Parts/organs_human.rsi - state: heart_human + sprite: Mobs/Species/Human/organs.rsi + state: heart-on - type: DroppedMechanism debugLoadMechanismData: mechanism.Heart.BasicHuman - - - + behaviors: + - Content.Server.Body.Mechanisms.Behaviors.HeartBehavior - type: mechanism id: mechanism.Brain.BasicHuman name: "human brain" - rsiPath: Mobs/Parts/organs_human.rsi + rsiPath: Mobs/Species/Human/organs.rsi rsiState: "brain_human" description: "The source of incredible, unending intelligence. Honk." durability: 10 size: 1 compatibility: Biological + behaviors: + - Content.Server.Body.Mechanisms.Behaviors.BrainBehavior - type: mechanism id: mechanism.Eyes.BasicHuman name: "human eyes" - rsiPath: Mobs/Parts/organs_human.rsi - rsiState: "eyes_human" + rsiPath: Mobs/Species/Human/organs.rsi + rsiState: "eyeballs" description: "Ocular organ capable of turning light into a colorful visual." durability: 10 size: 1 @@ -38,28 +39,44 @@ - type: mechanism id: mechanism.Heart.BasicHuman name: "human heart" - rsiPath: Mobs/Parts/organs_human.rsi - rsiState: "heart_human" + rsiPath: Mobs/Species/Human/organs.rsi + rsiState: "heart-on" description: "Pumps blood throughout a body. Essential for any entity with blood." durability: 10 size: 1 compatibility: Biological + behaviors: + - Content.Server.Body.Mechanisms.Behaviors.HeartBehavior - type: mechanism id: mechanism.Lungs.BasicHuman name: "human lungs" - rsiPath: Mobs/Parts/organs_human.rsi - rsiState: "lungs_human" + rsiPath: Mobs/Species/Human/organs.rsi + rsiState: "lungs" description: "Filters oxygen from an atmosphere, which is then sent into the bloodstream to be used as an electron carrier." durability: 13 size: 1 compatibility: Biological + behaviors: + - Content.Server.Body.Mechanisms.Behaviors.LungBehavior + +- type: mechanism + id: mechanism.Stomach.BasicHuman + name: "human stomach" + rsiPath: Mobs/Species/Human/organs.rsi + rsiState: "stomach" + description: "Gross. This is hard to stomach." + durability: 13 + size: 1 + compatibility: Biological + behaviors: + - Content.Server.Body.Mechanisms.Behaviors.StomachBehavior - type: mechanism id: mechanism.Liver.BasicHuman name: "human liver" - rsiPath: Mobs/Parts/organs_human.rsi - rsiState: "liver_human" + rsiPath: Mobs/Species/Human/organs.rsi + rsiState: "liver" description: "Filters impurities out of a bloodstream and provides other important functionality to a human." durability: 15 size: 1 @@ -68,8 +85,8 @@ - type: mechanism id: mechanism.Kidneys.BasicHuman name: "human kidneys" - rsiPath: Mobs/Parts/organs_human.rsi - rsiState: "kidneys_human" + rsiPath: Mobs/Species/Human/organs.rsi + rsiState: "kidneys" description: "Filters toxins out of a bloodstream." durability: 20 size: 1 diff --git a/Resources/Prototypes/BodySystem/Mechanisms/basic_tests.yml b/Resources/Prototypes/Body/Mechanisms/basic_tests.yml similarity index 95% rename from Resources/Prototypes/BodySystem/Mechanisms/basic_tests.yml rename to Resources/Prototypes/Body/Mechanisms/basic_tests.yml index 4114a322f6..2c591bbfcd 100644 --- a/Resources/Prototypes/BodySystem/Mechanisms/basic_tests.yml +++ b/Resources/Prototypes/Body/Mechanisms/basic_tests.yml @@ -6,9 +6,8 @@ size: 4 compatibility: Universal implantableParts: - - Arm - - Hand - + - Arm + - Hand - type: mechanism id: mechanism.HonkModule @@ -17,4 +16,3 @@ durability: 50 size: 3 compatibility: Universal - diff --git a/Resources/Prototypes/Body/Parts/humanoid_parts.yml b/Resources/Prototypes/Body/Parts/humanoid_parts.yml new file mode 100644 index 0000000000..5437b3e6a3 --- /dev/null +++ b/Resources/Prototypes/Body/Parts/humanoid_parts.yml @@ -0,0 +1,192 @@ +- type: bodyPart + id: bodyPart.Torso.BasicHuman + name: "human torso" + plural: "human torsos" + rsiPath: Mobs/Species/Human/parts.rsi + rsiState: "torso_m" + partType: Torso + durability: 100 + destroyThreshold: -150 + size: 14 + compatibility: Biological + damageContainer: biologicalDamageContainer + resistances: defaultResistances + surgeryDataType: Content.Server.Body.Surgery.BiologicalSurgeryData + mechanisms: + - mechanism.Heart.BasicHuman + - mechanism.Lungs.BasicHuman + - mechanism.Stomach.BasicHuman + - mechanism.Liver.BasicHuman + - mechanism.Kidneys.BasicHuman + +- type: bodyPart + id: bodyPart.Head.BasicHuman + name: "human head" + plural: "human heads" + rsiPath: Mobs/Species/Human/parts.rsi + rsiState: head_m + partType: Head + durability: 50 + destroyThreshold: -120 + size: 7 + compatibility: Biological + damageContainer: biologicalDamageContainer + resistances: defaultResistances + surgeryDataType: Content.Server.Body.Surgery.BiologicalSurgeryData + mechanisms: + - mechanism.Brain.BasicHuman + - mechanism.Eyes.BasicHuman + +- type: bodyPart + id: bodyPart.LArm.BasicHuman + name: "left human arm" + plural: "left human arms" + rsiPath: Mobs/Species/Human/parts.rsi + rsiState: l_arm + partType: Arm + durability: 40 + destroyThreshold: -80 + size: 5 + compatibility: Biological + damageContainer: biologicalDamageContainer + resistances: defaultResistances + surgeryDataType: Content.Server.Body.Surgery.BiologicalSurgeryData + properties: + - !type:ExtensionProperty + active: true + reachDistance: 2.4 + +- type: bodyPart + id: bodyPart.RArm.BasicHuman + name: "right human arm" + plural: "right human arms" + rsiPath: Mobs/Species/Human/parts.rsi + rsiState: r_arm + partType: Arm + durability: 40 + destroyThreshold: -80 + size: 5 + compatibility: Biological + damageContainer: biologicalDamageContainer + resistances: defaultResistances + surgeryDataType: Content.Server.Body.Surgery.BiologicalSurgeryData + properties: + - !type:ExtensionProperty + active: true + reachDistance: 2.4 + +- type: bodyPart + id: bodyPart.LHand.BasicHuman + name: "left human hand" + plural: "left human hands" + rsiPath: Mobs/Species/Human/parts.rsi + rsiState: l_hand + partType: Hand + durability: 30 + destroyThreshold: -60 + size: 3 + compatibility: Biological + damageContainer: biologicalDamageContainer + resistances: defaultResistances + surgeryDataType: Content.Server.Body.Surgery.BiologicalSurgeryData + properties: + - !type:GraspProperty + active: true + +- type: bodyPart + id: bodyPart.RHand.BasicHuman + name: "right human hand" + plural: "right human hands" + rsiPath: Mobs/Species/Human/parts.rsi + rsiState: r_hand + partType: Hand + durability: 30 + destroyThreshold: -60 + size: 3 + compatibility: Biological + damageContainer: biologicalDamageContainer + resistances: defaultResistances + surgeryDataType: Content.Server.Body.Surgery.BiologicalSurgeryData + properties: + - !type:GraspProperty + active: true + +- type: bodyPart + id: bodyPart.LLeg.BasicHuman + name: "left human leg" + plural: "left human legs" + rsiPath: Mobs/Species/Human/parts.rsi + rsiState: l_leg + partType: Leg + durability: 45 + destroyThreshold: -90 + size: 6 + compatibility: Biological + damageContainer: biologicalDamageContainer + resistances: defaultResistances + surgeryDataType: Content.Server.Body.Surgery.BiologicalSurgeryData + properties: + - !type:ExtensionProperty + active: true + reachDistance: 3.0 + - !type:LegProperty + active: true + speed: 2.6 + +- type: bodyPart + id: bodyPart.RLeg.BasicHuman + name: "right human leg" + plural: "right human legs" + rsiPath: Mobs/Species/Human/parts.rsi + rsiState: r_leg + partType: Leg + durability: 45 + destroyThreshold: -90 + size: 6 + compatibility: Biological + damageContainer: biologicalDamageContainer + resistances: defaultResistances + surgeryDataType: Content.Server.Body.Surgery.BiologicalSurgeryData + properties: + - !type:ExtensionProperty + active: true + reachDistance: 3.0 + - !type:LegProperty + active: true + speed: 2.6 + +- type: bodyPart + id: bodyPart.LFoot.BasicHuman + name: "left human foot" + plural: "left human feet" + rsiPath: Mobs/Species/Human/parts.rsi + rsiState: l_foot + partType: Foot + durability: 30 + destroyThreshold: -60 + size: 2 + compatibility: Biological + damageContainer: biologicalDamageContainer + resistances: defaultResistances + surgeryDataType: Content.Server.Body.Surgery.BiologicalSurgeryData + properties: + - !type:FootProperty + active: true + +- type: bodyPart + id: bodyPart.RFoot.BasicHuman + name: "right human foot" + plural: "right human feet" + rsiPath: Mobs/Species/Human/parts.rsi + rsiState: r_foot + partType: Foot + durability: 30 + destroyThreshold: -60 + size: 2 + compatibility: Biological + damageContainer: biologicalDamageContainer + resistances: defaultResistances + surgeryDataType: Content.Server.Body.Surgery.BiologicalSurgeryData + properties: + - !type:FootProperty + active: true diff --git a/Resources/Prototypes/Body/Presets/basic_human.yml b/Resources/Prototypes/Body/Presets/basic_human.yml new file mode 100644 index 0000000000..38e65813df --- /dev/null +++ b/Resources/Prototypes/Body/Presets/basic_human.yml @@ -0,0 +1,14 @@ +- type: bodyPreset + name: "basic human" + id: bodyPreset.BasicHuman + partIDs: + head: bodyPart.Head.BasicHuman + torso: bodyPart.Torso.BasicHuman + right arm: bodyPart.RArm.BasicHuman + left arm: bodyPart.LArm.BasicHuman + right hand: bodyPart.RHand.BasicHuman + left hand: bodyPart.LHand.BasicHuman + right leg: bodyPart.RLeg.BasicHuman + left leg: bodyPart.LLeg.BasicHuman + right foot: bodyPart.RFoot.BasicHuman + left foot: bodyPart.LFoot.BasicHuman diff --git a/Resources/Prototypes/Body/Templates/humanoid.yml b/Resources/Prototypes/Body/Templates/humanoid.yml new file mode 100644 index 0000000000..856c995139 --- /dev/null +++ b/Resources/Prototypes/Body/Templates/humanoid.yml @@ -0,0 +1,44 @@ +- type: bodyTemplate + id: bodyTemplate.Humanoid + name: "humanoid" + centerSlot: "torso" + slots: + head: Head + torso: Torso + left arm: Arm + left hand: Hand + right arm: Arm + right hand: Hand + left leg: Leg + left foot: Foot + right leg: Leg + right foot: Foot + connections: + head: + - torso + torso: + - left arm + - right arm + - left leg + - right leg + left arm: + - left hand + right arm: + - right hand + left leg: + - left foot + right leg: + - right foot + layers: + head: "enum.HumanoidVisualLayers.Head" + torso: "enum.HumanoidVisualLayers.Chest" + left arm: "enum.HumanoidVisualLayers.LArm" + left hand: "enum.HumanoidVisualLayers.LHand" + right arm: "enum.HumanoidVisualLayers.RArm" + right hand: "enum.HumanoidVisualLayers.RHand" + left leg: "enum.HumanoidVisualLayers.LLeg" + left foot: "enum.HumanoidVisualLayers.LFoot" + right leg: "enum.HumanoidVisualLayers.RLeg" + right foot: "enum.HumanoidVisualLayers.RFoot" + mechanismLayers: + mechanism.Eyes.BasicHuman: "enum.HumanoidVisualLayers.Eyes" diff --git a/Resources/Prototypes/BodySystem/BodyTemplates/quadrupedal.yml b/Resources/Prototypes/Body/Templates/quadrupedal.yml similarity index 69% rename from Resources/Prototypes/BodySystem/BodyTemplates/quadrupedal.yml rename to Resources/Prototypes/Body/Templates/quadrupedal.yml index 6229e57c69..2eec9a12b6 100644 --- a/Resources/Prototypes/BodySystem/BodyTemplates/quadrupedal.yml +++ b/Resources/Prototypes/Body/Templates/quadrupedal.yml @@ -15,19 +15,17 @@ right hind paw: Foot connections: head: - - torso + - torso torso: - - left front leg - - right front leg - - left hind leg - - right hind paw + - left front leg + - right front leg + - left hind leg + - right hind paw left front leg: - - left front paw + - left front paw right front leg: - - right front paw + - right front paw left hind leg: - - left hind paw + - left hind paw right hind leg: - - right hind paw - - + - right hind paw diff --git a/Resources/Prototypes/BodySystem/BodyParts/humanoid_parts.yml b/Resources/Prototypes/BodySystem/BodyParts/humanoid_parts.yml deleted file mode 100644 index 6af6e71493..0000000000 --- a/Resources/Prototypes/BodySystem/BodyParts/humanoid_parts.yml +++ /dev/null @@ -1,95 +0,0 @@ -- type: bodyPart - id: bodyPart.Torso.BasicHuman - name: "human torso" - plural: "human torsos" - rsiPath: Mobs/Parts/body_human.rsi - rsiState: "torso_m" - partType: Torso - durability: 100 - destroyThreshold: -150 - resistance: 4.0 - size: 14 - compatibility: Biological - surgeryDataType: BiologicalsurgeryDataType - mechanisms: - - mechanism.Heart.BasicHuman - - mechanism.Lungs.BasicHuman - - mechanism.Liver.BasicHuman - - mechanism.Kidneys.BasicHuman - -- type: bodyPart - id: bodyPart.Head.BasicHuman - name: "human head" - plural: "human heads" - rsiPath: Mobs/Parts/body_human.rsi - rsiState: head_m - partType: Head - durability: 50 - destroyThreshold: -120 - resistance: 7.0 - size: 7 - compatibility: Biological - surgeryDataType: BiologicalsurgeryDataType - mechanisms: - - mechanism.Brain.BasicHuman - - mechanism.Eyes.BasicHuman - -- type: bodyPart - id: bodyPart.Arm.BasicHuman - name: "human arm" - plural: "human arms" - rsiPath: Mobs/Parts/body_human.rsi - rsiState: r_arm - partType: Arm - durability: 40 - destroyThreshold: -80 - resistance: 3.0 - size: 5 - compatibility: Biological - surgeryDataType: BiologicalsurgeryDataType - properties: - - !type:ArmLength - length: 2.4 - -- type: bodyPart - id: bodyPart.Hand.BasicHuman - name: "human hand" - plural: "human hands" - rsiPath: Mobs/Parts/body_human.rsi - rsiState: r_hand - partType: Hand - durability: 30 - destroyThreshold: -60 - resistance: 2.0 - size: 3 - compatibility: Biological - surgeryDataType: BiologicalsurgeryDataType - -- type: bodyPart - id: bodyPart.Leg.BasicHuman - name: "human leg" - plural: "human legs" - rsiPath: Mobs/Parts/body_human.rsi - rsiState: r_leg - partType: Leg - durability: 45 - destroyThreshold: -90 - resistance: 2.0 - size: 6 - compatibility: Biological - surgeryDataType: BiologicalsurgeryDataType - -- type: bodyPart - id: bodyPart.Foot.BasicHuman - name: "human foot" - plural: "human feet" - rsiPath: Mobs/Parts/body_human.rsi - rsiState: r_foot - partType: Foot - durability: 30 - destroyThreshold: -60 - resistance: 3.0 - size: 2 - compatibility: Biological - surgeryDataType: BiologicalsurgeryDataType - diff --git a/Resources/Prototypes/BodySystem/BodyPresets/basic_human.yml b/Resources/Prototypes/BodySystem/BodyPresets/basic_human.yml deleted file mode 100644 index 032a315804..0000000000 --- a/Resources/Prototypes/BodySystem/BodyPresets/basic_human.yml +++ /dev/null @@ -1,15 +0,0 @@ -- type: bodyPreset - name: "basic human" - id: bodyPreset.BasicHuman - partIDs: - head: bodyPart.Head.BasicHuman - torso: bodyPart.Torso.BasicHuman - right arm: bodyPart.Arm.BasicHuman - left arm: bodyPart.Arm.BasicHuman - right hand: bodyPart.Hand.BasicHuman - left hand: bodyPart.Hand.BasicHuman - right leg: bodyPart.Leg.BasicHuman - left leg: bodyPart.Leg.BasicHuman - right foot: bodyPart.Foot.BasicHuman - left foot: bodyPart.Foot.BasicHuman - diff --git a/Resources/Prototypes/BodySystem/BodyTemplates/humanoid.yml b/Resources/Prototypes/BodySystem/BodyTemplates/humanoid.yml deleted file mode 100644 index f583a7781a..0000000000 --- a/Resources/Prototypes/BodySystem/BodyTemplates/humanoid.yml +++ /dev/null @@ -1,33 +0,0 @@ -- type: bodyTemplate - id: bodyTemplate.Humanoid - name: "humanoid" - centerSlot: "torso" - slots: - head: Head - torso: Torso - left arm: Arm - left hand: Hand - right arm: Arm - right hand: Hand - left leg: Leg - left foot: Foot - right leg: Leg - right foot: Foot - connections: - head: - - torso - torso: - - left arm - - right arm - - left leg - - right leg - left arm: - - left hand - right arm: - - right hand - left leg: - - left foot - right leg: - - right foot - - diff --git a/Resources/Prototypes/Damage/damage_containers.yml b/Resources/Prototypes/Damage/damage_containers.yml new file mode 100644 index 0000000000..bb950b47f1 --- /dev/null +++ b/Resources/Prototypes/Damage/damage_containers.yml @@ -0,0 +1,13 @@ +- type: damageContainer + id: biologicalDamageContainer + activeDamageClasses: + - Brute + - Burn + - Toxin + - Airloss + +- type: damageContainer + id: metallicDamageContainer + activeDamageClasses: + - Brute + - Burn diff --git a/Resources/Prototypes/Damage/resistance_sets.yml b/Resources/Prototypes/Damage/resistance_sets.yml new file mode 100644 index 0000000000..5b829d48df --- /dev/null +++ b/Resources/Prototypes/Damage/resistance_sets.yml @@ -0,0 +1,56 @@ +- type: resistanceSet + id: defaultResistances + coefficients: + Blunt: 1.0 + Piercing: 1.0 + Heat: 1.0 + Disintegration: 1.0 + Cellular: 1.0 + DNA: 1.0 + Asphyxiation: 1.0 + flatReductions: + Blunt: 0 + Piercing: 0 + Heat: 0 + Disintegration: 0 + Cellular: 0 + DNA: 0 + Asphyxiation: 0 + +- type: resistanceSet + id: dionaResistances + coefficients: + Blunt: 0.5 + Piercing: 0.7 + Heat: 1.8 + Disintegration: 1.8 + Cellular: 1.0 + DNA: 1.0 + Asphyxiation: 1.0 + flatReductions: + Blunt: 0 + Piercing: 0 + Heat: 0 + Disintegration: 0 + Cellular: 0 + DNA: 0 + Asphyxiation: 0 + +- type: resistanceSet + id: metallicResistances + coefficients: + Blunt: 0.7 + Piercing: 0.7 + Heat: 1.0 + Disintegration: 1.0 + Cellular: 0.0 + DNA: 0.0 + Asphyxiation: 0.0 + flatReductions: + Blunt: 0 + Piercing: 0 + Heat: 0 + Disintegration: 0 + Cellular: 0 + DNA: 0 + Asphyxiation: 0 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Constructible/Ground/instruments.yml b/Resources/Prototypes/Entities/Constructible/Ground/instruments.yml index 2b2c34f589..2d4fb5b564 100644 --- a/Resources/Prototypes/Entities/Constructible/Ground/instruments.yml +++ b/Resources/Prototypes/Entities/Constructible/Ground/instruments.yml @@ -17,9 +17,8 @@ - VaultImpassable - type: SnapGrid offset: Center - - type: Damageable - type: Destructible - thresholdvalue: 50 + maxHP: 50 - type: UserInterface interfaces: - key: enum.InstrumentUiKey.Key diff --git a/Resources/Prototypes/Entities/Constructible/Ground/pilot_chair.yml b/Resources/Prototypes/Entities/Constructible/Ground/pilot_chair.yml index 2789e1ce21..f986636ee2 100644 --- a/Resources/Prototypes/Entities/Constructible/Ground/pilot_chair.yml +++ b/Resources/Prototypes/Entities/Constructible/Ground/pilot_chair.yml @@ -13,9 +13,8 @@ - type: Collidable - type: Clickable - type: InteractionOutline - - type: Damageable - type: Destructible - thresholdvalue: 100 + maxHP: 100 - type: Physics - type: ShuttleController - type: Strap diff --git a/Resources/Prototypes/Entities/Constructible/Ground/table.yml b/Resources/Prototypes/Entities/Constructible/Ground/table.yml index 8c28f0aef9..1dd92f308e 100644 --- a/Resources/Prototypes/Entities/Constructible/Ground/table.yml +++ b/Resources/Prototypes/Entities/Constructible/Ground/table.yml @@ -22,10 +22,10 @@ - type: IconSmooth key: generic base: solid_ - - type: Damageable + - type: Climbable - type: Destructible - thresholdvalue: 50 - spawnondestroy: SteelSheet1 + maxHP: 50 + spawnOnDestroy: SteelSheet1 # TODO: drop wood instead of steel when destroyed when that's added - type: entity @@ -39,7 +39,6 @@ - type: IconSmooth key: wood base: wood_ - - type: Damageable - type: Destructible - thresholdvalue: 50 - spawnondestroy: SteelSheet1 + maxHP: 50 + spawnOnDestroy: SteelSheet1 diff --git a/Resources/Prototypes/Entities/Constructible/Power/gravity_generator.yml b/Resources/Prototypes/Entities/Constructible/Power/gravity_generator.yml index 35c0b773cf..4163f5dfdd 100644 --- a/Resources/Prototypes/Entities/Constructible/Power/gravity_generator.yml +++ b/Resources/Prototypes/Entities/Constructible/Power/gravity_generator.yml @@ -28,9 +28,8 @@ - VaultImpassable - type: Clickable - type: InteractionOutline - - type: Damageable - type: Breakable - threshold: 150 + maxHP: 150 - type: GravityGenerator - type: UserInterface interfaces: diff --git a/Resources/Prototypes/Entities/Constructible/Power/medical_scanner.yml b/Resources/Prototypes/Entities/Constructible/Power/medical_scanner.yml index 481080a288..390cdca4d0 100644 --- a/Resources/Prototypes/Entities/Constructible/Power/medical_scanner.yml +++ b/Resources/Prototypes/Entities/Constructible/Power/medical_scanner.yml @@ -34,9 +34,8 @@ - type: SnapGrid offset: Center - type: MedicalScanner - - type: Damageable - type: Destructible - thresholdvalue: 100 + maxHP: 100 - type: Appearance visuals: - type: MedicalScannerVisualizer diff --git a/Resources/Prototypes/Entities/Constructible/Power/power.yml b/Resources/Prototypes/Entities/Constructible/Power/power.yml index dde2124bad..3c7b740fbd 100644 --- a/Resources/Prototypes/Entities/Constructible/Power/power.yml +++ b/Resources/Prototypes/Entities/Constructible/Power/power.yml @@ -50,9 +50,8 @@ nodeGroupID: HVPower - type: PowerConsumer drawRate: 50 - - type: Damageable - type: Breakable - thresholdvalue: 100 + maxHP: 100 - type: Anchorable - type: entity @@ -290,9 +289,8 @@ supply: 1500 - type: SnapGrid offset: Center - - type: Damageable - type: Breakable - thresholdvalue: 100 + maxHP: 100 #Depriciated, to be removed from maps diff --git a/Resources/Prototypes/Entities/Constructible/Power/turret.yml b/Resources/Prototypes/Entities/Constructible/Power/turret.yml index b7f7b86492..33be165066 100644 --- a/Resources/Prototypes/Entities/Constructible/Power/turret.yml +++ b/Resources/Prototypes/Entities/Constructible/Power/turret.yml @@ -9,6 +9,8 @@ - type: Collidable - type: Sprite texture: Constructible/Misc/TurrBase.png + - type: Icon + texture: Constructible/Misc/TurrBase.png - type: entity id: TurretTopGun @@ -23,6 +25,8 @@ drawdepth: WallMountedItems texture: Constructible/Misc/TurrTop.png directional: false + - type: Icon + texture: Constructible/Misc/TurrTop.png - type: entity id: TurretTopLight @@ -35,6 +39,8 @@ drawdepth: WallMountedItems texture: Constructible/Misc/TurrLamp.png directional: false + - type: Icon + texture: Constructible/Misc/TurrLamp.png - type: PointLight radius: 512 mask: flashlight_mask diff --git a/Resources/Prototypes/Entities/Constructible/Power/vending_machines.yml b/Resources/Prototypes/Entities/Constructible/Power/vending_machines.yml index 83b0f92fa1..3042c86ce9 100644 --- a/Resources/Prototypes/Entities/Constructible/Power/vending_machines.yml +++ b/Resources/Prototypes/Entities/Constructible/Power/vending_machines.yml @@ -30,8 +30,8 @@ - SmallImpassable - type: SnapGrid offset: Center - - type: Damageable - type: Breakable + maxHP: 50 - type: UserInterface interfaces: - key: enum.VendingMachineUiKey.Key diff --git a/Resources/Prototypes/Entities/Constructible/Storage/Closets/closet.yml b/Resources/Prototypes/Entities/Constructible/Storage/Closets/closet.yml index 2833854a0d..21f2f49e97 100644 --- a/Resources/Prototypes/Entities/Constructible/Storage/Closets/closet.yml +++ b/Resources/Prototypes/Entities/Constructible/Storage/Closets/closet.yml @@ -41,9 +41,8 @@ anchored: false - type: EntityStorage - type: PlaceableSurface - - type: Damageable - type: Destructible - thresholdvalue: 100 + maxHP: 100 - type: Appearance visuals: - type: StorageVisualizer diff --git a/Resources/Prototypes/Entities/Constructible/Storage/StorageTanks/storage_tank.yml b/Resources/Prototypes/Entities/Constructible/Storage/StorageTanks/storage_tank.yml index 87a316a0b1..44f8c3180f 100644 --- a/Resources/Prototypes/Entities/Constructible/Storage/StorageTanks/storage_tank.yml +++ b/Resources/Prototypes/Entities/Constructible/Storage/StorageTanks/storage_tank.yml @@ -24,10 +24,9 @@ IsScrapingFloor: true - type: Physics mass: 15 - anchored: false - - type: Damageable + Anchored: false - type: Destructible - thresholdvalue: 10 + maxHP: 10 - type: Solution maxVol: 1500 caps: 2 diff --git a/Resources/Prototypes/Entities/Constructible/Storage/crate_base.yml b/Resources/Prototypes/Entities/Constructible/Storage/crate_base.yml index 711f2f4ac9..6c4047f99c 100644 --- a/Resources/Prototypes/Entities/Constructible/Storage/crate_base.yml +++ b/Resources/Prototypes/Entities/Constructible/Storage/crate_base.yml @@ -36,9 +36,8 @@ capacity: 60 CanWeldShut: false - type: PlaceableSurface - - type: Damageable - type: Destructible - thresholdvalue: 100 + maxHP: 100 - type: Appearance visuals: - type: StorageVisualizer diff --git a/Resources/Prototypes/Entities/Constructible/Walls/asteroid.yml b/Resources/Prototypes/Entities/Constructible/Walls/asteroid.yml index 448ff223d6..73437dc525 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/asteroid.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/asteroid.yml @@ -16,9 +16,8 @@ shapes: - !type:PhysShapeAabb layer: [MobMask] - - type: Damageable - type: Destructible - thresholdvalue: 100 + maxHP: 100 - type: Occluder sizeX: 32 sizeY: 32 diff --git a/Resources/Prototypes/Entities/Constructible/Walls/girder.yml b/Resources/Prototypes/Entities/Constructible/Walls/girder.yml index c19ff0eb6e..50a4dfa3f2 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/girder.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/girder.yml @@ -14,10 +14,9 @@ shapes: - !type:PhysShapeAabb layer: [MobMask, Opaque] - - type: Damageable - type: Destructible - thresholdvalue: 50 - spawnondestroy: SteelSheet1 + maxHP: 50 + spawnOnDestroy: SteelSheet1 - type: SnapGrid offset: Edge diff --git a/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml b/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml index c381fae699..7c1bb1e5a4 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml @@ -24,9 +24,8 @@ layer: - VaultImpassable - SmallImpassable - - type: Damageable - type: Destructible - thresholdvalue: 100 + maxHP: 100 - type: SnapGrid offset: Center - type: LowWall diff --git a/Resources/Prototypes/Entities/Constructible/Walls/signs.yml b/Resources/Prototypes/Entities/Constructible/Walls/signs.yml index 3d9cb3c09f..222ff68297 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/signs.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/signs.yml @@ -8,7 +8,6 @@ - type: Collidable shapes: - !type:PhysShapeAabb - - type: Damageable - type: Destructible thresholdvalue: 5 - type: Sprite diff --git a/Resources/Prototypes/Entities/Constructible/Walls/walls.yml b/Resources/Prototypes/Entities/Constructible/Walls/walls.yml index 7f82a01ce2..a33763fa3d 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/walls.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/walls.yml @@ -25,10 +25,9 @@ - MobImpassable - VaultImpassable - SmallImpassable - - type: Damageable - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder + maxHP: 500 + spawnOnDestroy: Girder - type: Occluder sizeX: 32 sizeY: 32 @@ -50,8 +49,8 @@ - type: Icon sprite: Constructible/Structures/Walls/brick.rsi - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder + maxHP: 300 + spawnOnDestroy: Girder - type: IconSmooth key: walls base: brick @@ -66,8 +65,8 @@ - type: Icon sprite: Constructible/Structures/Walls/clock.rsi - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder + maxHP: 300 + spawnOnDestroy: Girder - type: IconSmooth key: walls base: clock @@ -82,8 +81,8 @@ - type: Icon sprite: Constructible/Structures/Walls/clown.rsi - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder + maxHP: 300 + spawnOnDestroy: Girder - type: IconSmooth key: walls base: clown @@ -99,8 +98,8 @@ - type: Icon sprite: Constructible/Structures/Walls/cult.rsi - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder + maxHP: 300 + spawnOnDestroy: Girder - type: IconSmooth key: walls base: cult @@ -115,8 +114,8 @@ - type: Icon sprite: Constructible/Structures/Walls/debug.rsi - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder + maxHP: 300 + spawnOnDestroy: Girder - type: IconSmooth key: walls base: debug @@ -131,8 +130,8 @@ - type: Icon sprite: Constructible/Structures/Walls/diamond.rsi - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder + maxHP: 300 + spawnOnDestroy: Girder - type: IconSmooth key: walls base: diamond @@ -148,8 +147,8 @@ - type: Icon sprite: Constructible/Structures/Walls/gold.rsi - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder + maxHP: 300 + spawnOnDestroy: Girder - type: IconSmooth key: walls base: gold @@ -164,8 +163,8 @@ - type: Icon sprite: Constructible/Structures/Walls/ice.rsi - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder + maxHP: 300 + spawnOnDestroy: Girder - type: IconSmooth key: walls base: ice @@ -180,8 +179,8 @@ - type: Icon sprite: Constructible/Structures/Walls/metal.rsi - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder + maxHP: 300 + spawnOnDestroy: Girder - type: IconSmooth key: walls base: metal @@ -196,8 +195,8 @@ - type: Icon sprite: Constructible/Structures/Walls/plasma.rsi - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder + maxHP: 300 + spawnOnDestroy: Girder - type: IconSmooth key: walls base: plasma @@ -212,8 +211,8 @@ - type: Icon sprite: Constructible/Structures/Walls/plastic.rsi - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder + maxHP: 300 + spawnOnDestroy: Girder - type: IconSmooth key: walls base: plastic @@ -230,8 +229,8 @@ sprite: Constructible/Structures/Walls/solid.rsi state: rgeneric - type: Destructible - thresholdvalue: 300 - spawnondestroy: Girder + maxHP: 600 + spawnOnDestroy: Girder - type: ReinforcedWall key: walls base: solid @@ -248,8 +247,8 @@ - type: Icon sprite: Constructible/Structures/Walls/riveted.rsi - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder + maxHP: 1000 + spawnOnDestroy: Girder - type: IconSmooth key: walls base: riveted @@ -264,8 +263,8 @@ - type: Icon sprite: Constructible/Structures/Walls/sandstone.rsi - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder + maxHP: 300 + spawnOnDestroy: Girder - type: IconSmooth key: walls base: sandstone @@ -280,8 +279,8 @@ - type: Icon sprite: Constructible/Structures/Walls/silver.rsi - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder + maxHP: 300 + spawnOnDestroy: Girder - type: IconSmooth key: walls base: silver @@ -297,9 +296,9 @@ - type: Icon sprite: Constructible/Structures/Walls/solid.rsi - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder - destroysound: /Audio/Effects/metalbreak.ogg + maxHP: 300 + spawnOnDestroy: Girder + destroySound: /Audio/Effects/metalbreak.ogg - type: IconSmooth key: walls base: solid @@ -314,8 +313,8 @@ - type: Icon sprite: Constructible/Structures/Walls/uranium.rsi - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder + maxHP: 300 + spawnOnDestroy: Girder - type: IconSmooth key: walls base: uranium @@ -330,8 +329,8 @@ - type: Icon sprite: Constructible/Structures/Walls/wood.rsi - type: Destructible - thresholdvalue: 100 - spawnondestroy: Girder + maxHP: 300 + spawnOnDestroy: Girder - type: IconSmooth key: walls base: wood diff --git a/Resources/Prototypes/Entities/Constructible/Walls/windows.yml b/Resources/Prototypes/Entities/Constructible/Walls/windows.yml index 280139d3e6..e0f231f161 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/windows.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/windows.yml @@ -27,9 +27,8 @@ - MobImpassable - VaultImpassable - SmallImpassable - - type: Damageable - type: Destructible - thresholdvalue: 100 + maxHP: 100 - type: SnapGrid offset: Center - type: Airtight diff --git a/Resources/Prototypes/Entities/Constructible/disposal.yml b/Resources/Prototypes/Entities/Constructible/disposal.yml index 738ec4d1b7..1d85dd33a6 100644 --- a/Resources/Prototypes/Entities/Constructible/disposal.yml +++ b/Resources/Prototypes/Entities/Constructible/disposal.yml @@ -13,12 +13,12 @@ - type: SnapGrid offset: Center - type: Anchorable - - type: Damageable - type: Breakable - type: Rotatable - type: entity id: DisposalHolder + abstract: true name: disposal holder components: - type: DisposalHolder @@ -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/Effects/Markers/construction_ghost.yml b/Resources/Prototypes/Entities/Effects/Markers/construction_ghost.yml index d6240e27fb..8a93730b3b 100644 --- a/Resources/Prototypes/Entities/Effects/Markers/construction_ghost.yml +++ b/Resources/Prototypes/Entities/Effects/Markers/construction_ghost.yml @@ -1,6 +1,7 @@ - type: entity - name: spooky ghost + name: construction ghost id: constructionghost + abstract: true components: - type: Sprite color: '#3F38' @@ -12,6 +13,7 @@ - type: entity name: somebody-messed-up frame id: structureconstructionframe + abstract: true components: - type: Sprite - type: Construction diff --git a/Resources/Prototypes/Entities/Effects/Markers/drag_shadow.yml b/Resources/Prototypes/Entities/Effects/Markers/drag_shadow.yml index 29643c4204..e8b4b568bb 100644 --- a/Resources/Prototypes/Entities/Effects/Markers/drag_shadow.yml +++ b/Resources/Prototypes/Entities/Effects/Markers/drag_shadow.yml @@ -1,6 +1,7 @@ - type: entity name: drag shadow id: dragshadow + abstract: true components: - type: Sprite layers: diff --git a/Resources/Prototypes/Entities/Effects/Markers/gamemode_conditional_spawners.yml b/Resources/Prototypes/Entities/Effects/Markers/gamemode_conditional_spawners.yml index f5a840c392..1fe1cb94e2 100644 --- a/Resources/Prototypes/Entities/Effects/Markers/gamemode_conditional_spawners.yml +++ b/Resources/Prototypes/Entities/Effects/Markers/gamemode_conditional_spawners.yml @@ -71,7 +71,7 @@ - PistolMolly - PistolOlivaw - PistolPaco - chance: 0.75 + chance: 0.95 gameRules: - RuleSuspicion @@ -96,7 +96,7 @@ - ToolboxEmergency - CrowbarRed - Stunbaton - chance: 0.75 + chance: 0.95 gameRules: - RuleSuspicion @@ -118,7 +118,7 @@ - RevolverDeckard - RevolverInspector - RevolverMateba - chance: 0.75 + chance: 0.95 gameRules: - RuleSuspicion @@ -144,7 +144,7 @@ - ShotgunRegulator - ShotgunPump - ShotgunSawn - chance: 0.75 + chance: 0.95 gameRules: - RuleSuspicion @@ -169,7 +169,7 @@ - SmgStraylight - SmgWt550 - SmgZoric - chance: 0.75 + chance: 0.95 gameRules: - RuleSuspicion @@ -191,7 +191,7 @@ - SniperBoltGun - SniperBoltGunWood - SniperHeavy - chance: 0.75 + chance: 0.95 gameRules: - RuleSuspicion @@ -217,7 +217,7 @@ - LaserCannon - XrayCannon - TaserGun - chance: 0.75 + chance: 0.85 gameRules: - RuleSuspicion @@ -260,6 +260,117 @@ - ExGrenade - GrenadeFlashBang - SyndieMiniBomb + - GrenadeFlash + - GrenadeBlast + - GrenadeFrag + - GrenadeBaton chance: 0.75 gameRules: - RuleSuspicion + +- type: entity + name: Suspicion Rifle Ammo Spawner + id: SuspicionRifleMagazineSpawner + parent: BaseConditionalSpawner + components: + - type: Sprite + netsync: false + visible: false + sprite: Interface/Misc/markers.rsi + state: spawner_rifle_ammo + - type: Icon + sprite: Interface/Misc/markers.rsi + state: spawner_rifle_ammo + - type: ConditionalSpawner + prototypes: + - MagazineSRifle + - MagazineClRifle + - MagazineLRifle + chance: 0.95 + gameRules: + - RuleSuspicion + +- type: entity + name: Suspicion Shotgun Ammo Spawner + id: SuspicionShotgunMagazineSpawner + parent: BaseConditionalSpawner + components: + - type: Sprite + netsync: false + visible: false + sprite: Interface/Misc/markers.rsi + state: spawner_shotgun_ammo + - type: Icon + sprite: Interface/Misc/markers.rsi + state: spawner_shotgun_ammo + - type: ConditionalSpawner + prototypes: + - MagazineShotgun + chance: 0.95 + gameRules: + - RuleSuspicion + +- type: entity + name: Suspicion Pistol Ammo Spawner + id: SuspicionPistolMagazineSpawner + parent: BaseConditionalSpawner + components: + - type: Sprite + netsync: false + visible: false + sprite: Interface/Misc/markers.rsi + state: spawner_pistol_ammo + - type: Icon + sprite: Interface/Misc/markers.rsi + state: spawner_pistol_ammo + - type: ConditionalSpawner + prototypes: + - MagazinePistol + - MagazineHCPistol + - MagazinePistolSmg + - MagazineClRiflePistol + chance: 0.95 + gameRules: + - RuleSuspicion + +- type: entity + name: Suspicion Magnum Ammo Spawner + id: SuspicionMagnumMagazineSpawner + parent: BaseConditionalSpawner + components: + - type: Sprite + netsync: false + visible: false + sprite: Interface/Misc/markers.rsi + state: spawner_magnum_ammo + - type: Icon + sprite: Interface/Misc/markers.rsi + state: spawner_magnum_ammo + - type: ConditionalSpawner + prototypes: + - MagazineMagnum + - MagazineMagnumSmg + chance: 0.95 + gameRules: + - RuleSuspicion + +- type: entity + name: Suspicion Launcher Ammo Spawner + id: SuspicionLauncherAmmoSpawner + parent: BaseConditionalSpawner + components: + - type: Sprite + netsync: false + visible: false + sprite: Interface/Misc/markers.rsi + state: spawner_launcher_ammo + - type: Icon + sprite: Interface/Misc/markers.rsi + state: spawner_launcher_ammo + - type: ConditionalSpawner + prototypes: + - RocketAmmo + - GrenadeFrag + chance: 0.95 + gameRules: + - RuleSuspicion diff --git a/Resources/Prototypes/Entities/Effects/Markers/hover_entity.yml b/Resources/Prototypes/Entities/Effects/Markers/hover_entity.yml index f3eb6bc998..5ba4269602 100644 --- a/Resources/Prototypes/Entities/Effects/Markers/hover_entity.yml +++ b/Resources/Prototypes/Entities/Effects/Markers/hover_entity.yml @@ -1,6 +1,7 @@ - type: entity name: hover entity id: hoverentity + abstract: true components: - type: Sprite layers: diff --git a/Resources/Prototypes/Entities/Effects/Markers/pointing.yml b/Resources/Prototypes/Entities/Effects/Markers/pointing.yml index afceefcbc4..2d21b2804c 100644 --- a/Resources/Prototypes/Entities/Effects/Markers/pointing.yml +++ b/Resources/Prototypes/Entities/Effects/Markers/pointing.yml @@ -6,6 +6,9 @@ netsync: false sprite: Interface/Misc/pointing.rsi state: pointing + - type: Icon + sprite: Interface/Misc/pointing.rsi + state: pointing - type: PointingArrow duration: 4 step: 0.5 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml new file mode 100644 index 0000000000..9ab31e8bcb --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -0,0 +1,544 @@ +# Bounds Guide +#1D2L3U4R + +- type: entity + save: false + name: monkey + id: MonkeyMob_Content + description: A description. + drawdepth: Mobs + suffix: AI + components: + - type: AiController + logic: Civilian + - type: MovementSpeedModifier + baseWalkSpeed : 4 + baseSprintSpeed : 4 + - type: InteractionOutline + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: monkey + sprite: Mobs/Animals/monkey.rsi + - type: Icon + sprite: Mobs/Animals/monkey.rsi + state: monkey + - type: Clickable + - type: Physics + mass: 50 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.30,-0.25,0.40,0.25" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: BodyManager + BaseTemplate: bodyTemplate.Humanoid + BasePreset: bodyPreset.BasicHuman + - type: HeatResistance + - type: CombatMode + - type: Teleportable + - type: Stunnable + - type: UnarmedCombat + range: 1.5 + arcwidth: 0 + arc: bite + damage: 50 + - type: MovementIgnoreGravity + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: monkey + dead: dead + - type: Pullable + +- type: entity + save: false + name: gorilla + parent: MonkeyMob_Content + id: GorillaMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: crawling + sprite: Mobs/Animals/gorilla.rsi + - type: Icon + sprite: Mobs/Animals/gorilla.rsi + state: icon + - type: Physics + mass: 90 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.90,-0.50,0.05,0.50" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: crawling + dead: dead + +- type: entity + save: false + name: chicken + parent: MonkeyMob_Content + id: ChickenMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: chicken-0 + sprite: Mobs/Animals/chicken.rsi + - type: Icon + sprite: Mobs/Animals/chicken.rsi + state: icon-0 + - type: Physics + mass: 20 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.45,-0.20,0.10,0.20" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: chicken-0 + dead: dead-0 + +- type: entity + save: false + name: butterfly + parent: MonkeyMob_Content + id: ButterflyMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: MovementSpeedModifier + baseWalkSpeed : 6 + baseSprintSpeed : 6 + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: butterfly + sprite: Mobs/Animals/butterfly.rsi + - type: Icon + sprite: Mobs/Animals/butterfly.rsi + state: butterfly + - type: Physics + mass: 5 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.20,-0.20,0.20,0.20" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: RandomSpriteColor + state: butterfly + colors: + blue: "#1861d5" + red: "#951710" + pink: "#d5188d" + brown: "#a05212" + green: "#0e7f1b" + cyan: "#18a2d5" + yellow: "#d58c18" + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: butterfly + dead: dead + +- type: entity + save: false + name: bat + parent: MonkeyMob_Content + id: BatMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: MovementSpeedModifier + baseWalkSpeed : 6 + baseSprintSpeed : 6 + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: bat + sprite: Mobs/Animals/bat.rsi + - type: Icon + sprite: Mobs/Animals/bat.rsi + state: bat + - type: Physics + mass: 5 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.20,-0.20,0.20,0.20" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: bat + dead: dead + +- type: entity + save: false + name: bee + parent: MonkeyMob_Content + id: BeeMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: MovementSpeedModifier + baseWalkSpeed : 7 + baseSprintSpeed : 7 + - type: AsteroidRock + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: 0 + sprite: Mobs/Animals/bee.rsi + - type: Icon + sprite: Objects/Fun/toys.rsi + state: plushie_h + - type: Physics + mass: 5 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.10,-0.10,0.10,0.10" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: 0 + dead: dead + +- type: entity + save: false + name: goat + parent: MonkeyMob_Content + id: GoatMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: goat + sprite: Mobs/Animals/goat.rsi + - type: Icon + sprite: Mobs/Animals/goat.rsi + state: goat + - type: Physics + mass: 20 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.60,-0.50,0.05,0.50" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: goat + dead: dead + +# Note that we gotta make this bitch vomit someday when you feed it anthrax or sumthin +- type: entity + save: false + name: goose + parent: MonkeyMob_Content + id: GooseMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: goose + sprite: Mobs/Animals/goose.rsi + - type: Icon + sprite: Mobs/Animals/goose.rsi + state: goose + - type: Physics + mass: 20 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.45,-0.35,0.45,0.35" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: goose + dead: dead + +# Would be cool to have some functionality for the parrot to be able to sit on stuff +- type: entity + save: false + name: parrot + parent: MonkeyMob_Content + id: ParrotMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: MovementSpeedModifier + baseWalkSpeed : 6 + baseSprintSpeed : 6 + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: parrot + sprite: Mobs/Animals/parrot.rsi + - type: Icon + sprite: Mobs/Animals/parrot.rsi + state: icon + - type: Physics + mass: 20 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.30,-0.35,0.35,0.35" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: parrot + dead: dead + +- type: entity + save: false + name: snake + parent: MonkeyMob_Content + id: SnakeMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: snake + sprite: Mobs/Animals/snake.rsi + - type: Icon + sprite: Mobs/Animals/snake.rsi + state: icon + - type: Physics + mass: 10 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.30,-0.30,0.25,0.30" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: snake +# It's death animation is animated so hopefully this should push for separation between "dying" and "death" states. + dead: dead + +# Code unique spider prototypes or combine them all into one spider and get a +# random sprite state when you spawn it. +- type: entity + save: false + name: tarantula + parent: MonkeyMob_Content + id: GiantSpiderMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: tarantula + sprite: Mobs/Animals/spider.rsi + - type: Icon + sprite: Mobs/Animals/spider.rsi + state: tarantula + - type: Physics + mass: 10 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.30,-0.40,0.45,0.40" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: tarantula + dead: dead + +- type: entity + save: false + name: crab + parent: MonkeyMob_Content + id: CrabMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: crab + sprite: Mobs/Animals/crab.rsi + - type: Icon + sprite: Mobs/Animals/crab.rsi + state: crab + - type: Physics + mass: 10 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.40,-0.35,0.20,0.35" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: crab + dead: dead + +- type: entity + save: false + name: penguin + parent: MonkeyMob_Content + id: PenguinMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: penguin + sprite: Mobs/Animals/penguin.rsi + - type: Icon + sprite: Mobs/Animals/penguin.rsi + state: penguin + - type: Physics + mass: 10 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.50,-0.30,0.35,0.30" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: penguin + dead: penguin_dead diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml new file mode 100644 index 0000000000..00d7371892 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml @@ -0,0 +1,106 @@ +- type: entity + save: false + name: space carp + id: CarpMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: AiController + logic: Xeno + - type: MovementSpeedModifier + - type: InteractionOutline + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: carp + sprite: Mobs/Aliens/Carps/carp_space.rsi + - type: Icon + sprite: Mobs/Aliens/Carps/carp_space.rsi + state: icon + - type: Clickable + - type: Physics + mass: 50 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.35,-0.35,0.35,0.35" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: BodyManager + BaseTemplate: bodyTemplate.Humanoid + BasePreset: bodyPreset.BasicHuman + - type: HeatResistance + - type: CombatMode + - type: Teleportable + - type: Stunnable + - type: UnarmedCombat + range: 1.5 + arcwidth: 0 + arc: bite + damage: 50 + - type: MovementIgnoreGravity + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: carp + dead: dead + +- type: entity + save: false + name: magicarp + parent: CarpMob_Content + id: MagicarpMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: magicarp + sprite: Mobs/Aliens/Carps/carp_magic.rsi + - type: Icon + sprite: Mobs/Aliens/Carps/carp_magic.rsi + state: icon + - type: Physics + mass: 60 + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: magicarp + dead: dead + +- type: entity + save: false + name: holocarp + parent: CarpMob_Content + id: HolocarpMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: holocarp + sprite: Mobs/Aliens/Carps/carp_holo.rsi + - type: Icon + sprite: Mobs/Aliens/Carps/carp_holo.rsi + state: icon + - type: Physics + mass: 5 + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: holocarp + dead: dead diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml b/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml index 1b3fecdafb..9f34c6b0a8 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml @@ -34,14 +34,11 @@ layer: - Opaque - MobImpassable - - type: Species - Template: Human - HeatResistance: 323 - type: BodyManager - BaseTemplate: bodyTemplate.Humanoid - BasePreset: bodyPreset.BasicHuman + baseTemplate: bodyTemplate.Humanoid + basePreset: bodyPreset.BasicHuman + - type: MobStateManager - type: HeatResistance - - type: Damageable - type: CombatMode - type: Teleportable - type: CharacterInfo diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml new file mode 100644 index 0000000000..fc01a43eb8 --- /dev/null +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -0,0 +1,207 @@ +# Bounds Guide +#1D2L3U4R + +- type: entity + save: false + name: based on what? + abstract: true + id: PetBaseMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: AiController + logic: Civilian + - type: MovementSpeedModifier + baseWalkSpeed : 5 + baseSprintSpeed : 5 + - type: InteractionOutline + - type: Clickable + - type: Physics + mass: 50 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.30,-0.35,0.45,0.35" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: BodyManager + BaseTemplate: bodyTemplate.Humanoid + BasePreset: bodyPreset.BasicHuman + - type: HeatResistance + - type: CombatMode + - type: Teleportable + - type: Stunnable + - type: UnarmedCombat + range: 1.5 + arcwidth: 0 + arc: bite + damage: 50 + - type: MovementIgnoreGravity + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: monkey + dead: dead + - type: Pullable + +- type: entity + save: false + name: corgi + parent: PetBaseMob_Content + id: CorgiMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Pets/corgi.rsi + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: corgi + - type: Icon + sprite: Mobs/Pets/corgi.rsi + state: corgi + - type: Physics + mass: 10 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.50,-0.25,0.30,0.25" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: corgi + dead: corgi_dead + +- type: entity + save: false + name: ian + parent: PetBaseMob_Content + id: IanMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Pets/corgi.rsi + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: ian + - type: Icon + sprite: Mobs/Pets/corgi.rsi + state: ian + - type: Physics + mass: 10 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.50,-0.25,0.30,0.25" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: ian + dead: ian_dead + +- type: entity + save: false + name: cat + parent: PetBaseMob_Content + id: CatMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Pets/cat.rsi + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: cat + - type: Icon + sprite: Mobs/Pets/cat.rsi + state: cat + - type: Physics + mass: 10 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.45,-0.20,0.30,0.20" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: cat + dead: cat_dead + +- type: entity + save: false + name: sloth + parent: PetBaseMob_Content + id: SlothMob_Content + description: + drawdepth: Mobs + suffix: AI + components: + - type: MovementSpeedModifier + baseWalkSpeed : 1 + baseSprintSpeed : 1 + - type: Sprite + drawdepth: Mobs + sprite: Mobs/Pets/sloth.rsi + layers: + - map: ["enum.DamageStateVisualLayers.Base"] + state: sloth + - type: Icon + sprite: Mobs/Pets/sloth.rsi + state: sloth + - type: Physics + mass: 10 + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.45,-0.35,0.15,0.35" + mask: + - Impassable + - MobImpassable + - VaultImpassable + - SmallImpassable + layer: + - Opaque + - MobImpassable + - type: Appearance + visuals: + - type: DamageStateVisualizer + normal: sloth + dead: sloth_dead diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index f7f2d3a9ce..ada753f860 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -17,9 +17,9 @@ layers: - map: ["enum.DamageStateVisualLayers.Base"] state: running - sprite: Mobs/Species/xeno_hunter.rsi + sprite: Mobs/Aliens/Xenos/xeno_hunter.rsi - type: Icon - sprite: Mobs/Species/xeno_hunter.rsi + sprite: Mobs/Aliens/Xenos/xeno_hunter.rsi state: standing - type: Clickable - type: Physics @@ -36,14 +36,12 @@ layer: - Opaque - MobImpassable - - type: Species - Template: Human - HeatResistance: 323 - type: BodyManager - BaseTemplate: bodyTemplate.Humanoid - BasePreset: bodyPreset.BasicHuman + baseTemplate: bodyTemplate.Humanoid + basePreset: bodyPreset.BasicHuman + - type: Metabolism + - type: MobStateManager - type: HeatResistance - - type: Damageable - type: CombatMode - type: Teleportable - type: FootstepSound @@ -52,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/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index 736f388b67..f559eaab3d 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -2,3 +2,5 @@ parent: MobObserver save: false id: AdminObserver + name: admin observer + abstract: true diff --git a/Resources/Prototypes/Entities/Mobs/Player/observer.yml b/Resources/Prototypes/Entities/Mobs/Player/observer.yml index 4b6fea353d..6fb36573fb 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/observer.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/observer.yml @@ -1,6 +1,7 @@ - type: entity id: MobObserver name: observer + abstract: true save: false description: Boo! components: @@ -22,7 +23,6 @@ DoRangeCheck: false - type: IgnorePause - type: Ghost - - type: CanSeeGases - type: Sprite netsync: false drawdepth: Ghosts diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 9259c28043..e104ad5ccb 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -28,7 +28,7 @@ digestionDelay: 20 # StatusEffects - type: Stunnable - # EndStatusEffects + # Other - type: Inventory - type: Clickable - type: InteractionOutline @@ -38,34 +38,35 @@ layers: - map: ["enum.HumanoidVisualLayers.Chest"] color: "#e8b59b" - sprite: Mobs/Species/human.rsi - state: human_chest_m + sprite: Mobs/Species/Human/parts.rsi + state: torso_m - map: ["enum.HumanoidVisualLayers.Head"] color: "#e8b59b" - sprite: Mobs/Species/human.rsi - state: human_head_m - - sprite: Mobs/Customization/eyes.rsi - state: eyes + sprite: Mobs/Species/Human/parts.rsi + state: head_m + - map: ["enum.HumanoidVisualLayers.Eyes"] color: "#008800" + sprite: Mobs/Customization/eyes.rsi + state: eyes - map: ["enum.HumanoidVisualLayers.RArm"] color: "#e8b59b" - sprite: Mobs/Species/human.rsi - state: human_r_arm + sprite: Mobs/Species/Human/parts.rsi + state: r_arm - map: ["enum.HumanoidVisualLayers.LArm"] color: "#e8b59b" - sprite: Mobs/Species/human.rsi - state: human_l_arm + sprite: Mobs/Species/Human/parts.rsi + state: l_arm - map: ["enum.HumanoidVisualLayers.RLeg"] color: "#e8b59b" - sprite: Mobs/Species/human.rsi - state: human_r_leg + sprite: Mobs/Species/Human/parts.rsi + state: r_leg - map: ["enum.HumanoidVisualLayers.LLeg"] color: "#e8b59b" - sprite: Mobs/Species/human.rsi - state: human_l_leg + sprite: Mobs/Species/Human/parts.rsi + state: l_leg - shader: StencilClear - sprite: Mobs/Species/human.rsi - state: human_l_leg + sprite: Mobs/Species/Human/parts.rsi + state: l_leg - shader: StencilMask map: ["enum.HumanoidVisualLayers.StencilMask"] sprite: Mobs/Customization/masking_helpers.rsi @@ -75,12 +76,20 @@ shader: StencilDraw - map: ["enum.HumanoidVisualLayers.LHand"] color: "#e8b59b" - sprite: Mobs/Species/human.rsi - state: human_l_hand + sprite: Mobs/Species/Human/parts.rsi + state: l_hand - map: ["enum.HumanoidVisualLayers.RHand"] color: "#e8b59b" - sprite: Mobs/Species/human.rsi - state: human_r_hand + sprite: Mobs/Species/Human/parts.rsi + state: r_hand + - map: ["enum.HumanoidVisualLayers.LFoot"] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: l_foot + - map: ["enum.HumanoidVisualLayers.RFoot"] + color: "#e8b59b" + sprite: Mobs/Species/Human/parts.rsi + state: r_foot - map: ["enum.Slots.IDCARD"] - map: ["enum.Slots.GLOVES"] - map: ["enum.Slots.SHOES"] @@ -101,8 +110,8 @@ - map: ["hand-left"] - map: ["hand-right"] - type: Icon - sprite: Mobs/Species/human.rsi - state: human_basic + sprite: Mobs/Species/Human/parts.rsi + state: full - type: Physics mass: 85 - type: Collidable @@ -116,19 +125,23 @@ layer: - Opaque - MobImpassable - - type: Species - Template: Human - HeatResistance: 323 - type: BodyManager - BaseTemplate: bodyTemplate.Humanoid - BasePreset: bodyPreset.BasicHuman + baseTemplate: bodyTemplate.Humanoid + basePreset: bodyPreset.BasicHuman + - type: Metabolism + needsGases: + Oxygen: 0.006365740 + producesGases: + Oxygen: 0.004774305 + CarbonDioxide: 0.001591435 + - type: MobStateManager - type: HeatResistance - - type: Damageable - type: Appearance visuals: - - type: SpeciesVisualizer + - type: RotationVisualizer - type: BuckleVisualizer - type: CombatMode + - type: Climbing - type: Teleportable - type: CharacterInfo - type: FootstepSound @@ -142,7 +155,6 @@ - type: Grammar proper: true - type: Pullable - - type: CanSeeGases - type: DoAfter - type: Strippable - type: UserInterface @@ -163,55 +175,51 @@ - type: Sprite netsync: false drawdepth: Mobs - layers: - map: ["enum.HumanoidVisualLayers.Chest"] color: "#e8b59b" - sprite: Mobs/Species/human.rsi - state: human_chest_m + sprite: Mobs/Species/Human/parts.rsi + state: torso_m - map: ["enum.HumanoidVisualLayers.Head"] color: "#e8b59b" - sprite: Mobs/Species/human.rsi - state: human_head_m - - sprite: Mobs/Customization/eyes.rsi - state: eyes + sprite: Mobs/Species/Human/parts.rsi + state: head_m + - map: ["enum.HumanoidVisualLayers.Eyes"] color: "#008800" + sprite: Mobs/Customization/eyes.rsi + state: eyes - map: ["enum.HumanoidVisualLayers.RArm"] color: "#e8b59b" - sprite: Mobs/Species/human.rsi - state: human_r_arm + sprite: Mobs/Species/Human/parts.rsi + state: r_arm - map: ["enum.HumanoidVisualLayers.LArm"] color: "#e8b59b" - sprite: Mobs/Species/human.rsi - state: human_l_arm + sprite: Mobs/Species/Human/parts.rsi + state: l_arm - map: ["enum.HumanoidVisualLayers.RLeg"] color: "#e8b59b" - sprite: Mobs/Species/human.rsi - state: human_r_leg + sprite: Mobs/Species/Human/parts.rsi + state: r_leg - map: ["enum.HumanoidVisualLayers.LLeg"] color: "#e8b59b" - sprite: Mobs/Species/human.rsi - state: human_l_leg - + sprite: Mobs/Species/Human/parts.rsi + state: l_leg - shader: StencilClear - shader: StencilMask map: ["enum.HumanoidVisualLayers.StencilMask"] sprite: Mobs/Customization/masking_helpers.rsi state: female_full visible: false - - map: ["enum.Slots.INNERCLOTHING"] shader: StencilDraw - - map: ["enum.HumanoidVisualLayers.LHand"] color: "#e8b59b" - sprite: Mobs/Species/human.rsi - state: human_l_hand + sprite: Mobs/Species/Human/parts.rsi + state: l_hand - map: ["enum.HumanoidVisualLayers.RHand"] color: "#e8b59b" - sprite: Mobs/Species/human.rsi - state: human_r_hand - + sprite: Mobs/Species/Human/parts.rsi + state: r_hand - map: ["enum.Slots.IDCARD"] - map: ["enum.Slots.GLOVES"] - map: ["enum.Slots.SHOES"] @@ -231,14 +239,11 @@ - map: ["enum.Slots.HEAD"] - map: ["hand-left"] - map: ["hand-right"] - - type: Icon - sprite: Mobs/Species/human.rsi - state: human_basic - + sprite: Mobs/Species/Human/parts.rsi + state: full - type: Physics mass: 85 - - type: Collidable shapes: - !type:PhysShapeAabb @@ -250,17 +255,13 @@ - SmallImpassable layer: - MobImpassable - - - type: Species - Template: Human - HeatResistance: 323 - - type: Damageable - + - type: BodyManager + baseTemplate: bodyTemplate.Humanoid + basePreset: bodyPreset.BasicHuman + - type: MobStateManager - type: Appearance visuals: - - type: SpeciesVisualizer - + - type: RotationVisualizer - type: HumanoidAppearance - - type: Grammar proper: true diff --git a/Resources/Prototypes/BodySystem/body_system_dropped_abstract.yml b/Resources/Prototypes/Entities/Mobs/body_system_dropped_abstract.yml similarity index 79% rename from Resources/Prototypes/BodySystem/body_system_dropped_abstract.yml rename to Resources/Prototypes/Entities/Mobs/body_system_dropped_abstract.yml index 4ebfd084ef..2ba5ab2934 100644 --- a/Resources/Prototypes/BodySystem/body_system_dropped_abstract.yml +++ b/Resources/Prototypes/Entities/Mobs/body_system_dropped_abstract.yml @@ -6,7 +6,7 @@ components: - type: DroppedBodyPart - type: Sprite - texture: Mobs/Parts/Organs/eyes_grey.png + texture: Mobs/Species/Human/parts.rsi/torso_m.png - type: Icon - type: UserInterface interfaces: @@ -21,9 +21,9 @@ components: - type: DroppedMechanism - type: Sprite - texture: Mobs/Parts/Organs/eyes_grey.png + texture: Mobs/Parts/Organs/eyeballs.png - type: Icon - type: UserInterface interfaces: - key: enum.GenericSurgeryUiKey.Key - type: GenericSurgeryBoundUserInterface \ No newline at end of file + type: GenericSurgeryBoundUserInterface 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/Entities/Objects/Misc/extinguisher_spray.yml b/Resources/Prototypes/Entities/Objects/Misc/extinguisher_spray.yml new file mode 100644 index 0000000000..da84fe7e02 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Misc/extinguisher_spray.yml @@ -0,0 +1,28 @@ +- type: entity + name: Extinguisher Spray + id: ExtinguisherSpray + description: Extinguisher Spray + components: + - type: Sprite + sprite: Effects/extinguisherSpray.rsi + layers: + - state: extinguish + - type: Icon + sprite: Effects/extinguisherSpray.rsi + state: extinguish + - type: GasVapor + dissipationInterval: 1 + gas: WaterVapor + gasVolume: 200 + gasTemperature: 293.15 + gasAmount: 20 + - type: Physics + - type: Collidable + shapes: + - !type:PhysShapeAabb + bounds: "-0.25,-0.25,0.25,0.25" + mask: + - Impassable + - type: Appearance + visuals: + - type: ExtinguisherVisualizer diff --git a/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml b/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml index a6c264b1d7..c341ef56d0 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml @@ -4,9 +4,26 @@ id: FireExtinguisher description: Extinguishes fires. components: - - type: Sprite - texture: Objects/Misc/fire_extinguisher.png - - type: Icon - texture: Objects/Misc/fire_extinguisher.png - - type: Item - size: 10 + - type: Sprite + sprite: Objects/Misc/fire_extinguisher.rsi + layers: + - state: fire_extinguisher_open + - type: Icon + sprite: Objects/Misc/fire_extinguisher.rsi + state: fire_extinguisher_open + - type: Item + sprite: Objects/Misc/fire_extinguisher.rsi + size: 10 + - type: Solution + maxVol: 1000 + caps: 9 + contents: + reagents: + - ReagentId: chem.H2O + Quantity: 1000 + - type: GasSprayer + spraySound: /Audio/Effects/spray.ogg + sprayType: ExtinguisherSpray + fuelType: chem.H2O + fuelName: water + fuelCost: 50 diff --git a/Resources/Prototypes/Entities/Objects/Power/powercells.yml b/Resources/Prototypes/Entities/Objects/Power/powercells.yml index 102fa7239c..98bb8936e3 100644 --- a/Resources/Prototypes/Entities/Objects/Power/powercells.yml +++ b/Resources/Prototypes/Entities/Objects/Power/powercells.yml @@ -27,8 +27,8 @@ sprite: Objects/Power/PowerCells/power_cell_small_st.rsi state: s_st - type: PowerCell - capacity: 1500 - charge: 1500 + maxCharge: 15000 + startingCharge: 15000 - type: Appearance visuals: - type: PowerCellVisualizer @@ -48,8 +48,8 @@ sprite: Objects/Power/PowerCells/power_cell_small_hi.rsi state: s_hi - type: PowerCell - capacity: 3000 - charge: 3000 + maxCharge: 30000 + startingCharge: 30000 - type: Appearance visuals: - type: PowerCellVisualizer @@ -69,8 +69,8 @@ sprite: Objects/Power/PowerCells/power_cell_small_sup.rsi state: s_sup - type: PowerCell - capacity: 6000 - charge: 6000 + maxCharge: 60000 + startingCharge: 60000 - type: Appearance visuals: - type: PowerCellVisualizer @@ -90,8 +90,8 @@ sprite: Objects/Power/PowerCells/power_cell_small_hy.rsi state: s_hy - type: PowerCell - capacity: 10000 - charge: 10000 + maxCharge: 80000 + startingCharge: 80000 - type: Appearance visuals: - type: PowerCellVisualizer diff --git a/Resources/Prototypes/Entities/Objects/Specific/medical.yml b/Resources/Prototypes/Entities/Objects/Specific/medical.yml index f004518590..5e7b8af68c 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/medical.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/medical.yml @@ -27,7 +27,7 @@ components: - type: Stack - type: Item - - type: Healing + #- type: Healing - type: entity name: ointment @@ -39,9 +39,9 @@ texture: Objects/Specific/Medical/ointment.png - type: Icon texture: Objects/Specific/Medical/ointment.png - - type: Healing - heal: 10 - damage: Heat + #- type: Healing + # heal: 10 + # damage: Heat - type: Stack max: 5 count: 5 @@ -57,9 +57,9 @@ texture: Objects/Specific/Medical/brutepack.png - type: Icon texture: Objects/Specific/Medical/brutepack.png - - type: Healing - heal: 10 - damage: Brute + #- type: Healing + # heal: 10 + # damage: Brute - type: Stack max: 5 count: 5 diff --git a/Resources/Prototypes/BodySystem/Surgery/surgery_tools.yml b/Resources/Prototypes/Entities/Objects/Tools/surgery_tools.yml similarity index 98% rename from Resources/Prototypes/BodySystem/Surgery/surgery_tools.yml rename to Resources/Prototypes/Entities/Objects/Tools/surgery_tools.yml index 3e490290df..5e1ecf0b39 100644 --- a/Resources/Prototypes/BodySystem/Surgery/surgery_tools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/surgery_tools.yml @@ -11,9 +11,6 @@ - key: enum.GenericSurgeryUiKey.Key type: GenericSurgeryBoundUserInterface - - - - type: entity name: scalpel parent: BaseSurgeryTool @@ -23,19 +20,15 @@ - type: SurgeryTool surgeryType: Incision baseOperateTime: 3 - - type: Sprite sprite: Objects/Specific/Medical/surgery_tools.rsi state: scalpel - type: Icon sprite: Objects/Specific/Medical/surgery_tools.rsi state: scalpel - - type: ItemCooldown - type: MeleeWeapon - - - type: entity name: retractor parent: BaseSurgeryTool @@ -45,19 +38,15 @@ - type: SurgeryTool surgeryType: Retraction baseOperateTime: 3 - - type: Sprite sprite: Objects/Specific/Medical/surgery_tools.rsi state: retractor - type: Icon sprite: Objects/Specific/Medical/surgery_tools.rsi state: retractor - - type: ItemCooldown - type: MeleeWeapon - - - type: entity name: cautery parent: BaseSurgeryTool @@ -67,19 +56,15 @@ - type: SurgeryTool surgeryType: Cauterization baseOperateTime: 3 - - type: Sprite sprite: Objects/Specific/Medical/surgery_tools.rsi state: cautery - type: Icon sprite: Objects/Specific/Medical/surgery_tools.rsi state: cautery - - type: ItemCooldown - type: MeleeWeapon - - - type: entity name: drill parent: BaseSurgeryTool @@ -89,19 +74,15 @@ - type: SurgeryTool surgeryType: Drilling baseOperateTime: 3 - - type: Sprite sprite: Objects/Specific/Medical/surgery_tools.rsi state: drill - type: Icon sprite: Objects/Specific/Medical/surgery_tools.rsi state: drill - - type: ItemCooldown - type: MeleeWeapon - - - type: entity name: bone saw parent: BaseSurgeryTool @@ -111,19 +92,15 @@ - type: SurgeryTool surgeryType: Amputation baseOperateTime: 3 - - type: Sprite sprite: Objects/Specific/Medical/surgery_tools.rsi state: bone_saw - type: Icon sprite: Objects/Specific/Medical/surgery_tools.rsi state: bone_saw - - type: ItemCooldown - type: MeleeWeapon - - - type: entity name: hemostat parent: BaseSurgeryTool @@ -133,14 +110,11 @@ - type: SurgeryTool surgeryType: VesselCompression baseOperateTime: 3 - - type: Sprite sprite: Objects/Specific/Medical/surgery_tools.rsi state: hemostat - type: Icon sprite: Objects/Specific/Medical/surgery_tools.rsi state: hemostat - - type: ItemCooldown - - type: MeleeWeapon - + - type: MeleeWeapon \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/AntiMaterial/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/AntiMaterial/projectiles.yml index 8459ae653d..1cef82866c 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/AntiMaterial/projectiles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/AntiMaterial/projectiles.yml @@ -6,4 +6,4 @@ components: - type: Projectile damages: - Brute: 70 + Piercing: 70 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/ClRifle/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/ClRifle/projectiles.yml index b1f01dcc2b..3eae35edfa 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/ClRifle/projectiles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/ClRifle/projectiles.yml @@ -6,7 +6,7 @@ components: - type: Projectile damages: - Brute: 27 + Piercing: 27 - type: entity id: BulletClRifleFlash @@ -16,7 +16,7 @@ components: - type: Projectile damages: - Brute: 27 + Piercing: 27 - type: entity id: BulletClRifleHV @@ -26,7 +26,7 @@ components: - type: Projectile damages: - Brute: 32 + Piercing: 32 - type: entity id: BulletClRiflePractice @@ -36,7 +36,7 @@ components: - type: Projectile damages: - Brute: 2 + Blunt: 2 - type: entity id: BulletClRifleRubber @@ -46,4 +46,4 @@ components: - type: Projectile damages: - Brute: 3 + Blunt: 3 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/LRifle/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/LRifle/projectiles.yml index 5752d74505..082f492862 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/LRifle/projectiles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/LRifle/projectiles.yml @@ -6,7 +6,7 @@ components: - type: Projectile damages: - Brute: 28 + Piercing: 28 - type: entity id: BulletLRifleFlash @@ -16,7 +16,7 @@ components: - type: Projectile damages: - Brute: 28 + Piercing: 28 - type: entity id: BulletLRifleHV @@ -26,7 +26,7 @@ components: - type: Projectile damages: - Brute: 30 + Piercing: 30 - type: entity id: BulletLRiflePractice @@ -36,7 +36,7 @@ components: - type: Projectile damages: - Brute: 2 + Blunt: 2 - type: entity id: BulletLRifleRubber @@ -46,4 +46,4 @@ components: - type: Projectile damages: - Brute: 3 + Blunt: 3 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magnum/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magnum/projectiles.yml index 63b79a8161..041120110e 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magnum/projectiles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magnum/projectiles.yml @@ -6,7 +6,7 @@ components: - type: Projectile damages: - Brute: 32 + Piercing: 32 - type: entity id: BulletMagnumFlash @@ -16,7 +16,7 @@ components: - type: Projectile damages: - Brute: 32 + Piercing: 32 - type: entity id: BulletMagnumHV @@ -26,7 +26,7 @@ components: - type: Projectile damages: - Brute: 35 + Piercing: 35 - type: entity id: BulletMagnumPractice @@ -36,7 +36,7 @@ components: - type: Projectile damages: - Brute: 1 + Blunt: 1 - type: entity id: BulletMagnumRubber @@ -46,4 +46,4 @@ components: - type: Projectile damages: - Brute: 3 + Blunt: 3 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Pistol/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Pistol/projectiles.yml index 3af7a3e30a..47394893de 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Pistol/projectiles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Pistol/projectiles.yml @@ -6,7 +6,7 @@ components: - type: Projectile damages: - Brute: 24 + Piercing: 24 - type: entity id: BulletPistolFlash @@ -16,7 +16,7 @@ components: - type: Projectile damages: - Brute: 24 + Piercing: 24 - type: entity id: BulletPistolHV @@ -26,7 +26,7 @@ components: - type: Projectile damages: - Brute: 28 + Piercing: 28 - type: entity id: BulletPistolPractice @@ -36,7 +36,7 @@ components: - type: Projectile damages: - Brute: 2 + Blunt: 2 - type: entity id: BulletPistolRubber @@ -46,4 +46,4 @@ components: - type: Projectile damages: - Brute: 3 + Blunt: 3 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/SRifle/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/SRifle/projectiles.yml index a5f687aad9..a86bde78ba 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/SRifle/projectiles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/SRifle/projectiles.yml @@ -6,7 +6,7 @@ components: - type: Projectile damages: - Brute: 25 + Piercing: 25 - type: entity id: BulletSRifleFlash @@ -16,7 +16,7 @@ components: - type: Projectile damages: - Brute: 25 + Piercing: 25 - type: entity id: BulletSRifleHV @@ -26,7 +26,7 @@ components: - type: Projectile damages: - Brute: 30 + Piercing: 30 - type: entity id: BulletSRiflePractice @@ -36,7 +36,7 @@ components: - type: Projectile damages: - Brute: 2 + Blunt: 2 - type: entity id: BulletSRifleRubber @@ -46,4 +46,4 @@ components: - type: Projectile damages: - Brute: 3 + Blunt: 3 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Shotgun/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Shotgun/projectiles.yml index 9b5fb5fef3..238d47d201 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Shotgun/projectiles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Shotgun/projectiles.yml @@ -9,7 +9,7 @@ state: base - type: Projectile damages: - Brute: 13 + Piercing: 13 - type: entity id: PelletShotgunBeanbag @@ -22,7 +22,7 @@ state: base - type: Projectile damages: - Brute: 10 + Blunt: 10 - type: StunnableProjectile - type: entity @@ -36,7 +36,7 @@ state: base - type: Projectile damages: - Brute: 13 + Piercing: 13 - type: entity id: PelletShotgunFlash @@ -49,7 +49,7 @@ state: base - type: Projectile damages: - Brute: 13 + Blunt: 13 - type: entity id: PelletShotgunIncendiary @@ -62,7 +62,7 @@ state: base - type: Projectile damages: - Brute: 13 + Blunt: 13 - type: entity id: PelletShotgunPractice @@ -75,4 +75,4 @@ state: base - type: Projectile damages: - Brute: 1 + Blunt: 1 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Toy/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Toy/projectiles.yml index 4f5a5c3267..4951cc008a 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Toy/projectiles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Toy/projectiles.yml @@ -6,7 +6,7 @@ components: - type: Projectile damages: - Brute: 1 + Blunt: 1 - type: entity id: BulletDonkSoft @@ -27,4 +27,4 @@ state: foamdart - type: Projectile damages: - Brute: 1 + Blunt: 1 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Explosives/grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Explosives/grenades.yml index 3e945ab5aa..ffe5c80017 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Explosives/grenades.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Explosives/grenades.yml @@ -21,9 +21,8 @@ heavyImpactRange: 2 lightImpactRange: 4 flashRange: 7 - - type: Damageable - type: Destructible - thresholdvalue: 10 + maxHP: 10 - type: Appearance visuals: - type: TimerTriggerVisualizer @@ -48,9 +47,8 @@ - type: OnUseTimerTrigger delay: 3.5 - type: FlashExplosive - - type: Damageable - type: Destructible - thresholdvalue: 10 + maxHP: 10 - type: Appearance visuals: - type: TimerTriggerVisualizer @@ -79,9 +77,8 @@ heavyImpactRange: 5 lightImpactRange: 7 flashRange: 10 - - type: Damageable - type: Destructible - thresholdvalue: 10 + maxHP: 10 - type: Appearance visuals: - type: TimerTriggerVisualizer @@ -109,7 +106,6 @@ devastationRange: 25 heavyImpactRange: 25 flashRange: 50 - - type: Damageable - type: Destructible thresholdvalue: 50 - type: Appearance diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml index a2778f7e50..f3513ded94 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml @@ -7,6 +7,7 @@ spriteName: Objects/Weapons/Guns/Projectiles/laser.png muzzleFlash: Objects/Weapons/Guns/Projectiles/laser_muzzle.png impactFlash: Objects/Weapons/Guns/Projectiles/laser_impact.png + damageType: Heat damage: 10 - type: entity @@ -18,6 +19,7 @@ spriteName: Objects/Weapons/Guns/Projectiles/heavy_laser.png muzzleFlash: Objects/Weapons/Guns/Projectiles/heavy_laser_muzzle.png impactFlash: Objects/Weapons/Guns/Projectiles/heavy_laser_impact.png + damageType: Heat damage: 30 - type: entity @@ -29,4 +31,5 @@ spriteName: Objects/Weapons/Guns/Projectiles/xray.png muzzleFlash: Objects/Weapons/Guns/Projectiles/xray_muzzle.png impactFlash: Objects/Weapons/Guns/Projectiles/xray_impact.png + damageType: Heat damage: 60 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml index 0aac56f67c..a0eeec12a4 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml @@ -28,7 +28,7 @@ - type: Projectile soundHit: /Audio/Weapons/Guns/Hits/bullet_hit.ogg damages: - Brute: 20 + Piercing: 20 - type: entity id: BulletBaseFlash @@ -39,7 +39,7 @@ - type: Projectile soundHit: /Audio/Weapons/Guns/Hits/snap.ogg damages: - Brute: 10 + Piercing: 10 - type: FlashProjectile range: 1 @@ -51,7 +51,7 @@ components: - type: Projectile damages: - Brute: 12 + Piercing: 12 - type: entity id: BulletBasePractice @@ -61,7 +61,7 @@ components: - type: Projectile damages: - Brute: 2 + Blunt: 2 - type: entity id: BulletBaseRubber @@ -72,7 +72,7 @@ - type: Projectile soundHit: /Audio/Weapons/Guns/Hits/snap.ogg damages: - Brute: 3 + Blunt: 3 - type: StunnableProjectile paralyzeAmount: 2 @@ -222,7 +222,7 @@ deleteOnCollide: true soundHit: /Audio/Guns/Hits/snap.ogg damages: - Brute: 2 + Blunt: 2 - type: entity id: BulletCap diff --git a/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/armor.png b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/armor.png new file mode 100644 index 0000000000..48e49f91c1 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/armor.png differ diff --git a/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/cardborg.png b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/cardborg.png new file mode 100644 index 0000000000..f0c14cb836 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/cardborg.png differ diff --git a/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/deathsquad.png b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/deathsquad.png new file mode 100644 index 0000000000..05a38386a8 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/deathsquad.png differ diff --git a/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/fire_extinguisher0.png b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/fire_extinguisher0.png new file mode 100644 index 0000000000..58abc6439b Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/fire_extinguisher0.png differ diff --git a/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/fire_extinguisher1.png b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/fire_extinguisher1.png new file mode 100644 index 0000000000..58abc6439b Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/fire_extinguisher1.png differ diff --git a/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/ian.png b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/ian.png new file mode 100644 index 0000000000..f1548bfab2 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/ian.png differ diff --git a/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/meta.json b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/meta.json new file mode 100644 index 0000000000..157fe60c95 --- /dev/null +++ b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/meta.json @@ -0,0 +1 @@ +{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", "states": [{"name": "armor", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "cardborg", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "deathsquad", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "fire_extinguisher0", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "fire_extinguisher1", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "ian", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "oxygen", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "petcollar", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "walkietalkie", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}]} \ No newline at end of file diff --git a/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/oxygen.png b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/oxygen.png new file mode 100644 index 0000000000..4e1ba4acbb Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/oxygen.png differ diff --git a/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/petcollar.png b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/petcollar.png new file mode 100644 index 0000000000..a41aa29918 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/petcollar.png differ diff --git a/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/walkietalkie.png b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/walkietalkie.png new file mode 100644 index 0000000000..5a5ff60380 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Back/corgi_back.rsi/walkietalkie.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/beret.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/beret.png new file mode 100644 index 0000000000..5c2725e910 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/beret.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/blackscarf.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/blackscarf.png new file mode 100644 index 0000000000..45513d7a52 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/blackscarf.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/bunny.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/bunny.png new file mode 100644 index 0000000000..63dd9d103a Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/bunny.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/captain.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/captain.png new file mode 100644 index 0000000000..9da56bc92b Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/captain.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/cardborg_h.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/cardborg_h.png new file mode 100644 index 0000000000..2a8fd7fcf7 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/cardborg_h.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/cargosoft.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/cargosoft.png new file mode 100644 index 0000000000..a64e36d6f2 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/cargosoft.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/chef.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/chef.png new file mode 100644 index 0000000000..edd84cbd84 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/chef.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/christmasscarf.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/christmasscarf.png new file mode 100644 index 0000000000..e179cc2069 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/christmasscarf.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/clown.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/clown.png new file mode 100644 index 0000000000..50f6fc3854 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/clown.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/detective.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/detective.png new file mode 100644 index 0000000000..f3b40d19da Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/detective.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/festive.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/festive.png new file mode 100644 index 0000000000..dc3dccf6c9 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/festive.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat0_cakehat.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat0_cakehat.png new file mode 100644 index 0000000000..b521d905f7 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat0_cakehat.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat0_reindeer.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat0_reindeer.png new file mode 100644 index 0000000000..635cc731f1 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat0_reindeer.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat0_white.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat0_white.png new file mode 100644 index 0000000000..cc1610ed44 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat0_white.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat0_yellow.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat0_yellow.png new file mode 100644 index 0000000000..90c5a20b5e Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat0_yellow.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat1_cakehat.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat1_cakehat.png new file mode 100644 index 0000000000..12296b1d46 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat1_cakehat.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat1_white.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat1_white.png new file mode 100644 index 0000000000..adc52987b4 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat1_white.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat1_yellow.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat1_yellow.png new file mode 100644 index 0000000000..415ac90a4d Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hardhat1_yellow.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/helmet.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/helmet.png new file mode 100644 index 0000000000..f75f4365f7 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/helmet.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hopcap.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hopcap.png new file mode 100644 index 0000000000..0e8bc77887 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/hopcap.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/kitty.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/kitty.png new file mode 100644 index 0000000000..bc30f075d7 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/kitty.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/meta.json b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/meta.json new file mode 100644 index 0000000000..cffa96ab1f --- /dev/null +++ b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/meta.json @@ -0,0 +1 @@ +{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", "states": [{"name": "beret", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "blackscarf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "bunny", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "captain", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "cardborg_h", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "cargosoft", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "chef", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "christmasscarf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "clown", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "detective", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "festive", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "hardhat0_cakehat", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "hardhat0_reindeer", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "hardhat0_white", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "hardhat0_yellow", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "hardhat1_cakehat", "directions": 4, "delays": [[0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1]]}, {"name": "hardhat1_white", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "hardhat1_yellow", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "helmet", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "hopcap", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "kitty", "directions": 4, "delays": [[0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1]]}, {"name": "nursehat", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "paper", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "paper_words", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "pirate", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "policehelm", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "redwizard", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "santahat", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "scarf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "sheet", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "sheriff", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "sombrero", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "sun", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "tophat", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "ushankadown", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "ushankaup", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "wizard", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "wizard-fake", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "zebrascarf", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}]} \ No newline at end of file diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/nursehat.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/nursehat.png new file mode 100644 index 0000000000..e1362a6e93 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/nursehat.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/paper.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/paper.png new file mode 100644 index 0000000000..3924013f33 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/paper.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/paper_words.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/paper_words.png new file mode 100644 index 0000000000..3924013f33 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/paper_words.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/pirate.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/pirate.png new file mode 100644 index 0000000000..d977afefa5 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/pirate.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/policehelm.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/policehelm.png new file mode 100644 index 0000000000..1f505517a8 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/policehelm.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/redwizard.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/redwizard.png new file mode 100644 index 0000000000..5e296fa16a Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/redwizard.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/santahat.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/santahat.png new file mode 100644 index 0000000000..8056d14327 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/santahat.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/scarf.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/scarf.png new file mode 100644 index 0000000000..0108369138 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/scarf.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/sheet.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/sheet.png new file mode 100644 index 0000000000..d71f99f848 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/sheet.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/sheriff.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/sheriff.png new file mode 100644 index 0000000000..ac29b44c02 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/sheriff.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/sombrero.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/sombrero.png new file mode 100644 index 0000000000..2f199b9ef0 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/sombrero.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/sun.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/sun.png new file mode 100644 index 0000000000..730dea902e Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/sun.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/tophat.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/tophat.png new file mode 100644 index 0000000000..f723d36da8 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/tophat.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/ushankadown.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/ushankadown.png new file mode 100644 index 0000000000..9b0b85e2bc Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/ushankadown.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/ushankaup.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/ushankaup.png new file mode 100644 index 0000000000..a4ebe9b463 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/ushankaup.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/wizard-fake.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/wizard-fake.png new file mode 100644 index 0000000000..3cf7642985 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/wizard-fake.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/wizard.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/wizard.png new file mode 100644 index 0000000000..3cf7642985 Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/wizard.png differ diff --git a/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/zebrascarf.png b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/zebrascarf.png new file mode 100644 index 0000000000..4fd0f9603e Binary files /dev/null and b/Resources/Textures/Clothing/Mobs/Hats/corgi_hats.rsi/zebrascarf.png differ 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/Effects/explosion.rsi/meta.json b/Resources/Textures/Effects/explosion.rsi/meta.json index d552172063..c70a3e60f8 100644 --- a/Resources/Textures/Effects/explosion.rsi/meta.json +++ b/Resources/Textures/Effects/explosion.rsi/meta.json @@ -1 +1,25 @@ -{"version": 1, "size": {"x": 96, "y": 96}, "states": [{"name": "explosionfast", "directions": 1, "delays": [[0.06, 0.06, 0.06, 0.06, 0.06, 0.06, 0.06, 0.06]]}]} \ No newline at end of file +{ + "version": 1, + "size": { + "x": 96, + "y": 96 + }, + "states": [ + { + "name": "explosionfast", + "directions": 1, + "delays": [ + [ + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06, + 0.06 + ] + ] + } + ] +} diff --git a/Resources/Textures/Effects/extinguisherSpray.rsi/extinguish.png b/Resources/Textures/Effects/extinguisherSpray.rsi/extinguish.png new file mode 100644 index 0000000000..0eb6d7ebd5 Binary files /dev/null and b/Resources/Textures/Effects/extinguisherSpray.rsi/extinguish.png differ diff --git a/Resources/Textures/Effects/extinguisherSpray.rsi/meta.json b/Resources/Textures/Effects/extinguisherSpray.rsi/meta.json new file mode 100644 index 0000000000..9e3c4a97b6 --- /dev/null +++ b/Resources/Textures/Effects/extinguisherSpray.rsi/meta.json @@ -0,0 +1,63 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "extinguish", + "directions": 8, + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + } + ] +} diff --git a/Resources/Textures/Interface/Misc/markers.rsi/meta.json b/Resources/Textures/Interface/Misc/markers.rsi/meta.json index a1fa3f2b53..3c58904297 100644 --- a/Resources/Textures/Interface/Misc/markers.rsi/meta.json +++ b/Resources/Textures/Interface/Misc/markers.rsi/meta.json @@ -403,6 +403,24 @@ ] ] }, + { + "name": "spawner_launcher_ammo", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "spawner_magnum_ammo", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, { "name": "spawner_melee", "directions": 1, @@ -421,6 +439,15 @@ ] ] }, + { + "name": "spawner_pistol_ammo", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, { "name": "spawner_revolver", "directions": 1, @@ -439,6 +466,15 @@ ] ] }, + { + "name": "spawner_rifle_ammo", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, { "name": "spawner_shotgun", "directions": 1, @@ -448,6 +484,15 @@ ] ] }, + { + "name": "spawner_shotgun_ammo", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, { "name": "spawner_smg", "directions": 1, @@ -466,7 +511,7 @@ ] ] }, - { + { "name": "spawner_trash", "directions": 1, "delays": [ diff --git a/Resources/Textures/Interface/Misc/markers.rsi/spawner_ai.png b/Resources/Textures/Interface/Misc/markers.rsi/spawner_ai.png index c9d18bf907..6e7304d762 100644 Binary files a/Resources/Textures/Interface/Misc/markers.rsi/spawner_ai.png and b/Resources/Textures/Interface/Misc/markers.rsi/spawner_ai.png differ diff --git a/Resources/Textures/Interface/Misc/markers.rsi/spawner_launcher_ammo.png b/Resources/Textures/Interface/Misc/markers.rsi/spawner_launcher_ammo.png new file mode 100644 index 0000000000..b7cffe180e Binary files /dev/null and b/Resources/Textures/Interface/Misc/markers.rsi/spawner_launcher_ammo.png differ diff --git a/Resources/Textures/Interface/Misc/markers.rsi/spawner_magnum_ammo.png b/Resources/Textures/Interface/Misc/markers.rsi/spawner_magnum_ammo.png new file mode 100644 index 0000000000..3b59a3ca46 Binary files /dev/null and b/Resources/Textures/Interface/Misc/markers.rsi/spawner_magnum_ammo.png differ diff --git a/Resources/Textures/Interface/Misc/markers.rsi/spawner_pistol_ammo.png b/Resources/Textures/Interface/Misc/markers.rsi/spawner_pistol_ammo.png new file mode 100644 index 0000000000..1e5330cfe2 Binary files /dev/null and b/Resources/Textures/Interface/Misc/markers.rsi/spawner_pistol_ammo.png differ diff --git a/Resources/Textures/Interface/Misc/markers.rsi/spawner_rifle_ammo.png b/Resources/Textures/Interface/Misc/markers.rsi/spawner_rifle_ammo.png new file mode 100644 index 0000000000..1c9372e9ea Binary files /dev/null and b/Resources/Textures/Interface/Misc/markers.rsi/spawner_rifle_ammo.png differ diff --git a/Resources/Textures/Interface/Misc/markers.rsi/spawner_shotgun_ammo.png b/Resources/Textures/Interface/Misc/markers.rsi/spawner_shotgun_ammo.png new file mode 100644 index 0000000000..2c341f2218 Binary files /dev/null and b/Resources/Textures/Interface/Misc/markers.rsi/spawner_shotgun_ammo.png differ diff --git a/Resources/Textures/Interface/Misc/markers.rsi/spawner_trash.png b/Resources/Textures/Interface/Misc/markers.rsi/spawner_trash.png index b92ba56c3d..0a685dc041 100644 Binary files a/Resources/Textures/Interface/Misc/markers.rsi/spawner_trash.png and b/Resources/Textures/Interface/Misc/markers.rsi/spawner_trash.png differ diff --git a/Resources/Textures/Interface/Misc/markers.rsi/spawner_xenoai.png b/Resources/Textures/Interface/Misc/markers.rsi/spawner_xenoai.png index 7b0db41b65..4f64d33046 100644 Binary files a/Resources/Textures/Interface/Misc/markers.rsi/spawner_xenoai.png and b/Resources/Textures/Interface/Misc/markers.rsi/spawner_xenoai.png differ diff --git a/Resources/Textures/Interface/StatusEffects/Human/human6-0.png b/Resources/Textures/Interface/StatusEffects/Human/human6.png similarity index 100% rename from Resources/Textures/Interface/StatusEffects/Human/human6-0.png rename to Resources/Textures/Interface/StatusEffects/Human/human6.png diff --git a/Resources/Textures/Interface/StatusEffects/Human/human6-1.png b/Resources/Textures/Interface/StatusEffects/Human/human7.png similarity index 100% rename from Resources/Textures/Interface/StatusEffects/Human/human6-1.png rename to Resources/Textures/Interface/StatusEffects/Human/human7.png diff --git a/Resources/Textures/Mobs/Aliens/Carps/carp_holo.rsi/dead.png b/Resources/Textures/Mobs/Aliens/Carps/carp_holo.rsi/dead.png new file mode 100644 index 0000000000..9d40430296 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/Carps/carp_holo.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Aliens/Carps/carp_holo.rsi/holocarp.png b/Resources/Textures/Mobs/Aliens/Carps/carp_holo.rsi/holocarp.png new file mode 100644 index 0000000000..47c26a457f Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/Carps/carp_holo.rsi/holocarp.png differ diff --git a/Resources/Textures/Mobs/Aliens/Carps/carp_holo.rsi/icon.png b/Resources/Textures/Mobs/Aliens/Carps/carp_holo.rsi/icon.png new file mode 100644 index 0000000000..c6520c574d Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/Carps/carp_holo.rsi/icon.png differ diff --git a/Resources/Textures/Mobs/Aliens/Carps/carp_holo.rsi/meta.json b/Resources/Textures/Mobs/Aliens/Carps/carp_holo.rsi/meta.json new file mode 100644 index 0000000000..b26ae858d7 --- /dev/null +++ b/Resources/Textures/Mobs/Aliens/Carps/carp_holo.rsi/meta.json @@ -0,0 +1,103 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/b143783881fb7418213026857cb8c569636e259e#diff-8dd94e19fdb2ff341b57e31bce101298", + "states": [ + { + "name": "icon", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "holocarp", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Aliens/Carps/carp_magic.rsi/dead.png b/Resources/Textures/Mobs/Aliens/Carps/carp_magic.rsi/dead.png new file mode 100644 index 0000000000..f42607ccdd Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/Carps/carp_magic.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Aliens/Carps/carp_magic.rsi/icon.png b/Resources/Textures/Mobs/Aliens/Carps/carp_magic.rsi/icon.png new file mode 100644 index 0000000000..1350ed8543 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/Carps/carp_magic.rsi/icon.png differ diff --git a/Resources/Textures/Mobs/Aliens/Carps/carp_magic.rsi/magicarp.png b/Resources/Textures/Mobs/Aliens/Carps/carp_magic.rsi/magicarp.png new file mode 100644 index 0000000000..ee64f5acf1 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/Carps/carp_magic.rsi/magicarp.png differ diff --git a/Resources/Textures/Mobs/Aliens/Carps/carp_magic.rsi/magicarp_gib.png b/Resources/Textures/Mobs/Aliens/Carps/carp_magic.rsi/magicarp_gib.png new file mode 100644 index 0000000000..a2ce13374f Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/Carps/carp_magic.rsi/magicarp_gib.png differ diff --git a/Resources/Textures/Mobs/Aliens/Carps/carp_magic.rsi/meta.json b/Resources/Textures/Mobs/Aliens/Carps/carp_magic.rsi/meta.json new file mode 100644 index 0000000000..30cc05d941 --- /dev/null +++ b/Resources/Textures/Mobs/Aliens/Carps/carp_magic.rsi/meta.json @@ -0,0 +1,73 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/d3d351dea8d9420cee47514843a02158d0a7266f#diff-8dd94e19fdb2ff341b57e31bce101298", + "states": [ + { + "name": "icon", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "magicarp", + "directions": 4, + "delays": [ + [ + 0.2, + 0.3, + 0.2 + ], + [ + 0.2, + 0.3, + 0.2 + ], + [ + 0.2, + 0.3, + 0.2 + ], + [ + 0.2, + 0.3, + 0.2 + ] + ] + }, + { + "name": "magicarp_gib", + "directions": 1, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Aliens/Carps/carp_space.rsi/carp.png b/Resources/Textures/Mobs/Aliens/Carps/carp_space.rsi/carp.png new file mode 100644 index 0000000000..de6b4c960b Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/Carps/carp_space.rsi/carp.png differ diff --git a/Resources/Textures/Mobs/Aliens/Carps/carp_space.rsi/carp_gib.png b/Resources/Textures/Mobs/Aliens/Carps/carp_space.rsi/carp_gib.png new file mode 100644 index 0000000000..f5d2c96041 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/Carps/carp_space.rsi/carp_gib.png differ diff --git a/Resources/Textures/Mobs/Aliens/Carps/carp_space.rsi/dead.png b/Resources/Textures/Mobs/Aliens/Carps/carp_space.rsi/dead.png new file mode 100644 index 0000000000..610087d8f2 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/Carps/carp_space.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Aliens/Carps/carp_space.rsi/icon.png b/Resources/Textures/Mobs/Aliens/Carps/carp_space.rsi/icon.png new file mode 100644 index 0000000000..c8d59df6e6 Binary files /dev/null and b/Resources/Textures/Mobs/Aliens/Carps/carp_space.rsi/icon.png differ diff --git a/Resources/Textures/Mobs/Aliens/Carps/carp_space.rsi/meta.json b/Resources/Textures/Mobs/Aliens/Carps/carp_space.rsi/meta.json new file mode 100644 index 0000000000..66407ac9a2 --- /dev/null +++ b/Resources/Textures/Mobs/Aliens/Carps/carp_space.rsi/meta.json @@ -0,0 +1,73 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/blob/master/icons/mob/animal.dmi", + "states": [ + { + "name": "icon", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "carp", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "carp_gib", + "directions": 1, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Species/xeno_hunter.rsi/crit.png b/Resources/Textures/Mobs/Aliens/Xenos/xeno_hunter.rsi/crit.png similarity index 100% rename from Resources/Textures/Mobs/Species/xeno_hunter.rsi/crit.png rename to Resources/Textures/Mobs/Aliens/Xenos/xeno_hunter.rsi/crit.png diff --git a/Resources/Textures/Mobs/Species/xeno_hunter.rsi/dead.png b/Resources/Textures/Mobs/Aliens/Xenos/xeno_hunter.rsi/dead.png similarity index 100% rename from Resources/Textures/Mobs/Species/xeno_hunter.rsi/dead.png rename to Resources/Textures/Mobs/Aliens/Xenos/xeno_hunter.rsi/dead.png diff --git a/Resources/Textures/Mobs/Species/xeno_hunter.rsi/meta.json b/Resources/Textures/Mobs/Aliens/Xenos/xeno_hunter.rsi/meta.json similarity index 100% rename from Resources/Textures/Mobs/Species/xeno_hunter.rsi/meta.json rename to Resources/Textures/Mobs/Aliens/Xenos/xeno_hunter.rsi/meta.json diff --git a/Resources/Textures/Mobs/Species/xeno_hunter.rsi/running.png b/Resources/Textures/Mobs/Aliens/Xenos/xeno_hunter.rsi/running.png similarity index 100% rename from Resources/Textures/Mobs/Species/xeno_hunter.rsi/running.png rename to Resources/Textures/Mobs/Aliens/Xenos/xeno_hunter.rsi/running.png diff --git a/Resources/Textures/Mobs/Species/xeno_hunter.rsi/sleeping.png b/Resources/Textures/Mobs/Aliens/Xenos/xeno_hunter.rsi/sleeping.png similarity index 100% rename from Resources/Textures/Mobs/Species/xeno_hunter.rsi/sleeping.png rename to Resources/Textures/Mobs/Aliens/Xenos/xeno_hunter.rsi/sleeping.png diff --git a/Resources/Textures/Mobs/Species/xeno_hunter.rsi/standing.png b/Resources/Textures/Mobs/Aliens/Xenos/xeno_hunter.rsi/standing.png similarity index 100% rename from Resources/Textures/Mobs/Species/xeno_hunter.rsi/standing.png rename to Resources/Textures/Mobs/Aliens/Xenos/xeno_hunter.rsi/standing.png diff --git a/Resources/Textures/Mobs/Animals/bat.rsi/bat.png b/Resources/Textures/Mobs/Animals/bat.rsi/bat.png new file mode 100644 index 0000000000..404ac148c7 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/bat.rsi/bat.png differ diff --git a/Resources/Textures/Mobs/Animals/bat.rsi/dead.png b/Resources/Textures/Mobs/Animals/bat.rsi/dead.png new file mode 100644 index 0000000000..9c0d4e458b Binary files /dev/null and b/Resources/Textures/Mobs/Animals/bat.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Animals/bat.rsi/meta.json b/Resources/Textures/Mobs/Animals/bat.rsi/meta.json new file mode 100644 index 0000000000..127fb60ead --- /dev/null +++ b/Resources/Textures/Mobs/Animals/bat.rsi/meta.json @@ -0,0 +1,50 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "bat", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/bear.rsi/armor_bear.png b/Resources/Textures/Mobs/Animals/bear.rsi/armor_bear.png new file mode 100644 index 0000000000..a9513d2c82 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/bear.rsi/armor_bear.png differ diff --git a/Resources/Textures/Mobs/Animals/bear.rsi/bear.png b/Resources/Textures/Mobs/Animals/bear.rsi/bear.png new file mode 100644 index 0000000000..a2eb0ef3c3 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/bear.rsi/bear.png differ diff --git a/Resources/Textures/Mobs/Animals/bear.rsi/bear_dead.png b/Resources/Textures/Mobs/Animals/bear.rsi/bear_dead.png new file mode 100644 index 0000000000..2e47b74683 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/bear.rsi/bear_dead.png differ diff --git a/Resources/Textures/Mobs/Animals/bear.rsi/bear_gib.png b/Resources/Textures/Mobs/Animals/bear.rsi/bear_gib.png new file mode 100644 index 0000000000..75e436ca6a Binary files /dev/null and b/Resources/Textures/Mobs/Animals/bear.rsi/bear_gib.png differ diff --git a/Resources/Textures/Mobs/Animals/bear.rsi/brownbear.png b/Resources/Textures/Mobs/Animals/bear.rsi/brownbear.png new file mode 100644 index 0000000000..dd8fff69da Binary files /dev/null and b/Resources/Textures/Mobs/Animals/bear.rsi/brownbear.png differ diff --git a/Resources/Textures/Mobs/Animals/bear.rsi/brownbear_dead.png b/Resources/Textures/Mobs/Animals/bear.rsi/brownbear_dead.png new file mode 100644 index 0000000000..afcf4ba574 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/bear.rsi/brownbear_dead.png differ diff --git a/Resources/Textures/Mobs/Animals/bear.rsi/brownbear_gib.png b/Resources/Textures/Mobs/Animals/bear.rsi/brownbear_gib.png new file mode 100644 index 0000000000..8b73affbac Binary files /dev/null and b/Resources/Textures/Mobs/Animals/bear.rsi/brownbear_gib.png differ diff --git a/Resources/Textures/Mobs/Animals/bear.rsi/combatbear.png b/Resources/Textures/Mobs/Animals/bear.rsi/combatbear.png new file mode 100644 index 0000000000..98f1213c4b Binary files /dev/null and b/Resources/Textures/Mobs/Animals/bear.rsi/combatbear.png differ diff --git a/Resources/Textures/Mobs/Animals/bear.rsi/combatbear_dead.png b/Resources/Textures/Mobs/Animals/bear.rsi/combatbear_dead.png new file mode 100644 index 0000000000..06d2112737 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/bear.rsi/combatbear_dead.png differ diff --git a/Resources/Textures/Mobs/Animals/bear.rsi/meta.json b/Resources/Textures/Mobs/Animals/bear.rsi/meta.json new file mode 100644 index 0000000000..36e8f5ff86 --- /dev/null +++ b/Resources/Textures/Mobs/Animals/bear.rsi/meta.json @@ -0,0 +1,138 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "brownbear", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "brownbear_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "brownbear_gib", + "directions": 1, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "combatbear", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "combatbear_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "bear", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "bear_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "bear_gib", + "directions": 1, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/bee.rsi/0.png b/Resources/Textures/Mobs/Animals/bee.rsi/0.png new file mode 100644 index 0000000000..fdd7e9eb47 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/bee.rsi/0.png differ diff --git a/Resources/Textures/Mobs/Animals/bee.rsi/1.png b/Resources/Textures/Mobs/Animals/bee.rsi/1.png new file mode 100644 index 0000000000..901c4762cb Binary files /dev/null and b/Resources/Textures/Mobs/Animals/bee.rsi/1.png differ diff --git a/Resources/Textures/Mobs/Animals/bee.rsi/2.png b/Resources/Textures/Mobs/Animals/bee.rsi/2.png new file mode 100644 index 0000000000..bc356e0388 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/bee.rsi/2.png differ diff --git a/Resources/Textures/Mobs/Animals/bee.rsi/3.png b/Resources/Textures/Mobs/Animals/bee.rsi/3.png new file mode 100644 index 0000000000..fdd7e9eb47 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/bee.rsi/3.png differ diff --git a/Resources/Textures/Mobs/Animals/bee.rsi/4.png b/Resources/Textures/Mobs/Animals/bee.rsi/4.png new file mode 100644 index 0000000000..28b5f8e74c Binary files /dev/null and b/Resources/Textures/Mobs/Animals/bee.rsi/4.png differ diff --git a/Resources/Textures/Mobs/Animals/bee.rsi/dead.png b/Resources/Textures/Mobs/Animals/bee.rsi/dead.png new file mode 100644 index 0000000000..dba0f3b278 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/bee.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Animals/bee.rsi/meta.json b/Resources/Textures/Mobs/Animals/bee.rsi/meta.json new file mode 100644 index 0000000000..aaa57c0f9a --- /dev/null +++ b/Resources/Textures/Mobs/Animals/bee.rsi/meta.json @@ -0,0 +1,105 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "0", + "directions": 1, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "1", + "directions": 1, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "2", + "directions": 1, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "3", + "directions": 1, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "4", + "directions": 1, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/butterfly.rsi/butterfly.png b/Resources/Textures/Mobs/Animals/butterfly.rsi/butterfly.png new file mode 100644 index 0000000000..7226676bc5 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/butterfly.rsi/butterfly.png differ diff --git a/Resources/Textures/Mobs/Animals/butterfly.rsi/dead.png b/Resources/Textures/Mobs/Animals/butterfly.rsi/dead.png new file mode 100644 index 0000000000..10f9a5db9f Binary files /dev/null and b/Resources/Textures/Mobs/Animals/butterfly.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Animals/butterfly.rsi/meta.json b/Resources/Textures/Mobs/Animals/butterfly.rsi/meta.json new file mode 100644 index 0000000000..09456eb791 --- /dev/null +++ b/Resources/Textures/Mobs/Animals/butterfly.rsi/meta.json @@ -0,0 +1,50 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "butterfly", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/chick.rsi/chick.png b/Resources/Textures/Mobs/Animals/chick.rsi/chick.png new file mode 100644 index 0000000000..dc32c312c5 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/chick.rsi/chick.png differ diff --git a/Resources/Textures/Mobs/Animals/chick.rsi/dead.png b/Resources/Textures/Mobs/Animals/chick.rsi/dead.png new file mode 100644 index 0000000000..901a2fd58a Binary files /dev/null and b/Resources/Textures/Mobs/Animals/chick.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Animals/chick.rsi/gib.png b/Resources/Textures/Mobs/Animals/chick.rsi/gib.png new file mode 100644 index 0000000000..b0581ad981 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/chick.rsi/gib.png differ diff --git a/Resources/Textures/Mobs/Animals/chick.rsi/meta.json b/Resources/Textures/Mobs/Animals/chick.rsi/meta.json new file mode 100644 index 0000000000..1d01fd99a0 --- /dev/null +++ b/Resources/Textures/Mobs/Animals/chick.rsi/meta.json @@ -0,0 +1,62 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "cow", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "chick_gib", + "directions": 1, + "delays": [ + [ + 0.6, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/chicken.rsi/chicken-0.png b/Resources/Textures/Mobs/Animals/chicken.rsi/chicken-0.png new file mode 100644 index 0000000000..d5ebdc2de1 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/chicken.rsi/chicken-0.png differ diff --git a/Resources/Textures/Mobs/Animals/chicken.rsi/chicken-1.png b/Resources/Textures/Mobs/Animals/chicken.rsi/chicken-1.png new file mode 100644 index 0000000000..c2f2bd7cf7 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/chicken.rsi/chicken-1.png differ diff --git a/Resources/Textures/Mobs/Animals/chicken.rsi/chicken-2.png b/Resources/Textures/Mobs/Animals/chicken.rsi/chicken-2.png new file mode 100644 index 0000000000..366315fdd9 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/chicken.rsi/chicken-2.png differ diff --git a/Resources/Textures/Mobs/Animals/chicken.rsi/dead-0.png b/Resources/Textures/Mobs/Animals/chicken.rsi/dead-0.png new file mode 100644 index 0000000000..bc4ac071de Binary files /dev/null and b/Resources/Textures/Mobs/Animals/chicken.rsi/dead-0.png differ diff --git a/Resources/Textures/Mobs/Animals/chicken.rsi/dead-1.png b/Resources/Textures/Mobs/Animals/chicken.rsi/dead-1.png new file mode 100644 index 0000000000..5ea03157c9 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/chicken.rsi/dead-1.png differ diff --git a/Resources/Textures/Mobs/Animals/chicken.rsi/dead-2.png b/Resources/Textures/Mobs/Animals/chicken.rsi/dead-2.png new file mode 100644 index 0000000000..87f5d02277 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/chicken.rsi/dead-2.png differ diff --git a/Resources/Textures/Mobs/Animals/chicken.rsi/icon-0.png b/Resources/Textures/Mobs/Animals/chicken.rsi/icon-0.png new file mode 100644 index 0000000000..b63fd80fbe Binary files /dev/null and b/Resources/Textures/Mobs/Animals/chicken.rsi/icon-0.png differ diff --git a/Resources/Textures/Mobs/Animals/chicken.rsi/icon-1.png b/Resources/Textures/Mobs/Animals/chicken.rsi/icon-1.png new file mode 100644 index 0000000000..69360a0393 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/chicken.rsi/icon-1.png differ diff --git a/Resources/Textures/Mobs/Animals/chicken.rsi/icon-2.png b/Resources/Textures/Mobs/Animals/chicken.rsi/icon-2.png new file mode 100644 index 0000000000..7d579a9ed3 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/chicken.rsi/icon-2.png differ diff --git a/Resources/Textures/Mobs/Animals/chicken.rsi/meta.json b/Resources/Textures/Mobs/Animals/chicken.rsi/meta.json new file mode 100644 index 0000000000..e549ea2d01 --- /dev/null +++ b/Resources/Textures/Mobs/Animals/chicken.rsi/meta.json @@ -0,0 +1,143 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/e15c63d100db65eaaa5231133b8a2662ff439131#diff-8dd94e19fdb2ff341b57e31bce101298", + "states": [ + { + "name": "icon-0", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "icon-1", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "icon-2", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "chicken-0", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "chicken-1", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "chicken-2", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "dead-0", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "dead-1", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "dead-2", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/chicken.rsi/note.txt b/Resources/Textures/Mobs/Animals/chicken.rsi/note.txt new file mode 100644 index 0000000000..872109e92f --- /dev/null +++ b/Resources/Textures/Mobs/Animals/chicken.rsi/note.txt @@ -0,0 +1 @@ +Eventually we'll have some kind of unifying "RandomSpriteStateComponent" but til now i'm just leaving these here in protest. \ No newline at end of file diff --git a/Resources/Textures/Mobs/Animals/cockroach.rsi/cockroach.png b/Resources/Textures/Mobs/Animals/cockroach.rsi/cockroach.png new file mode 100644 index 0000000000..e041888cc1 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/cockroach.rsi/cockroach.png differ diff --git a/Resources/Textures/Mobs/Animals/cockroach.rsi/glockroach.png b/Resources/Textures/Mobs/Animals/cockroach.rsi/glockroach.png new file mode 100644 index 0000000000..f3b4c5a533 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/cockroach.rsi/glockroach.png differ diff --git a/Resources/Textures/Mobs/Animals/cockroach.rsi/meta.json b/Resources/Textures/Mobs/Animals/cockroach.rsi/meta.json new file mode 100644 index 0000000000..358ae88446 --- /dev/null +++ b/Resources/Textures/Mobs/Animals/cockroach.rsi/meta.json @@ -0,0 +1,55 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "cockroach", + "directions": 4, + "delays": [ + [ + 0.25, + 0.25 + ], + [ + 0.25, + 0.25 + ], + [ + 0.25, + 0.25 + ], + [ + 0.25, + 0.25 + ] + ] + }, + { + "name": "glockroach", + "directions": 4, + "delays": [ + [ + 0.25, + 0.25 + ], + [ + 0.25, + 0.25 + ], + [ + 0.25, + 0.25 + ], + [ + 0.25, + 0.25 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/cow.rsi/cow.png b/Resources/Textures/Mobs/Animals/cow.rsi/cow.png new file mode 100644 index 0000000000..90bdb9613b Binary files /dev/null and b/Resources/Textures/Mobs/Animals/cow.rsi/cow.png differ diff --git a/Resources/Textures/Mobs/Animals/cow.rsi/dead.png b/Resources/Textures/Mobs/Animals/cow.rsi/dead.png new file mode 100644 index 0000000000..1eff485ebe Binary files /dev/null and b/Resources/Textures/Mobs/Animals/cow.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Animals/cow.rsi/gib.png b/Resources/Textures/Mobs/Animals/cow.rsi/gib.png new file mode 100644 index 0000000000..3b9261631b Binary files /dev/null and b/Resources/Textures/Mobs/Animals/cow.rsi/gib.png differ diff --git a/Resources/Textures/Mobs/Animals/cow.rsi/meta.json b/Resources/Textures/Mobs/Animals/cow.rsi/meta.json new file mode 100644 index 0000000000..caab74e720 --- /dev/null +++ b/Resources/Textures/Mobs/Animals/cow.rsi/meta.json @@ -0,0 +1,58 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "cow", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "gib", + "directions": 1, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/crab.rsi/crab.png b/Resources/Textures/Mobs/Animals/crab.rsi/crab.png new file mode 100644 index 0000000000..9c423c97b9 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/crab.rsi/crab.png differ diff --git a/Resources/Textures/Mobs/Animals/crab.rsi/dead.png b/Resources/Textures/Mobs/Animals/crab.rsi/dead.png new file mode 100644 index 0000000000..c2fa2d91ac Binary files /dev/null and b/Resources/Textures/Mobs/Animals/crab.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Animals/crab.rsi/meta.json b/Resources/Textures/Mobs/Animals/crab.rsi/meta.json new file mode 100644 index 0000000000..73d7ef3bdb --- /dev/null +++ b/Resources/Textures/Mobs/Animals/crab.rsi/meta.json @@ -0,0 +1,46 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "crab", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/frog.rsi/dead.png b/Resources/Textures/Mobs/Animals/frog.rsi/dead.png new file mode 100644 index 0000000000..4d667a1b2a Binary files /dev/null and b/Resources/Textures/Mobs/Animals/frog.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Animals/frog.rsi/frog.png b/Resources/Textures/Mobs/Animals/frog.rsi/frog.png new file mode 100644 index 0000000000..fd401f307a Binary files /dev/null and b/Resources/Textures/Mobs/Animals/frog.rsi/frog.png differ diff --git a/Resources/Textures/Mobs/Animals/frog.rsi/meta.json b/Resources/Textures/Mobs/Animals/frog.rsi/meta.json new file mode 100644 index 0000000000..2d517146ef --- /dev/null +++ b/Resources/Textures/Mobs/Animals/frog.rsi/meta.json @@ -0,0 +1,46 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "frog", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/goat.rsi/dead.png b/Resources/Textures/Mobs/Animals/goat.rsi/dead.png new file mode 100644 index 0000000000..7b85384e80 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/goat.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Animals/goat.rsi/goat.png b/Resources/Textures/Mobs/Animals/goat.rsi/goat.png new file mode 100644 index 0000000000..d5c1514e8d Binary files /dev/null and b/Resources/Textures/Mobs/Animals/goat.rsi/goat.png differ diff --git a/Resources/Textures/Mobs/Animals/goat.rsi/meta.json b/Resources/Textures/Mobs/Animals/goat.rsi/meta.json new file mode 100644 index 0000000000..b362ac4c89 --- /dev/null +++ b/Resources/Textures/Mobs/Animals/goat.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "goat", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/goose.rsi/dead.png b/Resources/Textures/Mobs/Animals/goose.rsi/dead.png new file mode 100644 index 0000000000..c5a9c38213 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/goose.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Animals/goose.rsi/goose.png b/Resources/Textures/Mobs/Animals/goose.rsi/goose.png new file mode 100644 index 0000000000..5c3685ac13 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/goose.rsi/goose.png differ diff --git a/Resources/Textures/Mobs/Animals/goose.rsi/meta.json b/Resources/Textures/Mobs/Animals/goose.rsi/meta.json new file mode 100644 index 0000000000..fb988e5d4d --- /dev/null +++ b/Resources/Textures/Mobs/Animals/goose.rsi/meta.json @@ -0,0 +1,132 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "goose", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "vomit", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ] + ] + }, + { + "name": "vomit_end", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "vomit_start", + "directions": 4, + "delays": [ + [ + 0.5, + 0.1, + 0.1, + 0.1, + 0.2, + 0.1, + 0.1, + 0.1 + ], + [ + 0.5, + 0.1, + 0.1, + 0.1, + 0.2, + 0.1, + 0.1, + 0.1 + ], + [ + 0.5, + 0.1, + 0.1, + 0.1, + 0.2, + 0.1, + 0.1, + 0.1 + ], + [ + 0.5, + 0.1, + 0.1, + 0.1, + 0.2, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/goose.rsi/vomit.png b/Resources/Textures/Mobs/Animals/goose.rsi/vomit.png new file mode 100644 index 0000000000..b0c034029f Binary files /dev/null and b/Resources/Textures/Mobs/Animals/goose.rsi/vomit.png differ diff --git a/Resources/Textures/Mobs/Animals/goose.rsi/vomit_end.png b/Resources/Textures/Mobs/Animals/goose.rsi/vomit_end.png new file mode 100644 index 0000000000..6f2d097e99 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/goose.rsi/vomit_end.png differ diff --git a/Resources/Textures/Mobs/Animals/goose.rsi/vomit_start.png b/Resources/Textures/Mobs/Animals/goose.rsi/vomit_start.png new file mode 100644 index 0000000000..2c2a6c3f6a Binary files /dev/null and b/Resources/Textures/Mobs/Animals/goose.rsi/vomit_start.png differ diff --git a/Resources/Textures/Mobs/Animals/gorilla.rsi/crawling.png b/Resources/Textures/Mobs/Animals/gorilla.rsi/crawling.png new file mode 100644 index 0000000000..2faeab4cde Binary files /dev/null and b/Resources/Textures/Mobs/Animals/gorilla.rsi/crawling.png differ diff --git a/Resources/Textures/Mobs/Animals/gorilla.rsi/dead.png b/Resources/Textures/Mobs/Animals/gorilla.rsi/dead.png new file mode 100644 index 0000000000..0033f8f4d7 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/gorilla.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Animals/gorilla.rsi/icon.png b/Resources/Textures/Mobs/Animals/gorilla.rsi/icon.png new file mode 100644 index 0000000000..ad43fef184 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/gorilla.rsi/icon.png differ diff --git a/Resources/Textures/Mobs/Animals/gorilla.rsi/meta.json b/Resources/Textures/Mobs/Animals/gorilla.rsi/meta.json new file mode 100644 index 0000000000..8a9ee12dc6 --- /dev/null +++ b/Resources/Textures/Mobs/Animals/gorilla.rsi/meta.json @@ -0,0 +1,65 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 64 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "icon", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "crawling", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "standing", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/gorilla.rsi/standing.png b/Resources/Textures/Mobs/Animals/gorilla.rsi/standing.png new file mode 100644 index 0000000000..f5e18f58c0 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/gorilla.rsi/standing.png differ diff --git a/Resources/Textures/Mobs/Animals/lizard.rsi/dead.png b/Resources/Textures/Mobs/Animals/lizard.rsi/dead.png new file mode 100644 index 0000000000..3026938567 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/lizard.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Animals/lizard.rsi/gib.png b/Resources/Textures/Mobs/Animals/lizard.rsi/gib.png new file mode 100644 index 0000000000..b296dbd780 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/lizard.rsi/gib.png differ diff --git a/Resources/Textures/Mobs/Animals/lizard.rsi/lizard.png b/Resources/Textures/Mobs/Animals/lizard.rsi/lizard.png new file mode 100644 index 0000000000..213b025146 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/lizard.rsi/lizard.png differ diff --git a/Resources/Textures/Mobs/Animals/lizard.rsi/meta.json b/Resources/Textures/Mobs/Animals/lizard.rsi/meta.json new file mode 100644 index 0000000000..8680a2713d --- /dev/null +++ b/Resources/Textures/Mobs/Animals/lizard.rsi/meta.json @@ -0,0 +1,75 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "lizard", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "gib", + "directions": 1, + "delays": [ + [ + 0.6, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "space", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/lizard.rsi/space.png b/Resources/Textures/Mobs/Animals/lizard.rsi/space.png new file mode 100644 index 0000000000..a63b48aaed Binary files /dev/null and b/Resources/Textures/Mobs/Animals/lizard.rsi/space.png differ diff --git a/Resources/Textures/Mobs/Animals/monkey.rsi/dead.png b/Resources/Textures/Mobs/Animals/monkey.rsi/dead.png new file mode 100644 index 0000000000..487fddc072 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/monkey.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Animals/monkey.rsi/meta.json b/Resources/Textures/Mobs/Animals/monkey.rsi/meta.json new file mode 100644 index 0000000000..1621265020 --- /dev/null +++ b/Resources/Textures/Mobs/Animals/monkey.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "monkey", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/monkey.rsi/monkey.png b/Resources/Textures/Mobs/Animals/monkey.rsi/monkey.png new file mode 100644 index 0000000000..0aa76fde16 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/monkey.rsi/monkey.png differ diff --git a/Resources/Textures/Mobs/Animals/mouse.rsi/dead-0.png b/Resources/Textures/Mobs/Animals/mouse.rsi/dead-0.png new file mode 100644 index 0000000000..4fd3f66e06 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/mouse.rsi/dead-0.png differ diff --git a/Resources/Textures/Mobs/Animals/mouse.rsi/dead-1.png b/Resources/Textures/Mobs/Animals/mouse.rsi/dead-1.png new file mode 100644 index 0000000000..1be9c8c6c9 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/mouse.rsi/dead-1.png differ diff --git a/Resources/Textures/Mobs/Animals/mouse.rsi/dead-2.png b/Resources/Textures/Mobs/Animals/mouse.rsi/dead-2.png new file mode 100644 index 0000000000..6ac77d6707 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/mouse.rsi/dead-2.png differ diff --git a/Resources/Textures/Mobs/Animals/mouse.rsi/icon-0.png b/Resources/Textures/Mobs/Animals/mouse.rsi/icon-0.png new file mode 100644 index 0000000000..63e17544dd Binary files /dev/null and b/Resources/Textures/Mobs/Animals/mouse.rsi/icon-0.png differ diff --git a/Resources/Textures/Mobs/Animals/mouse.rsi/icon-1.png b/Resources/Textures/Mobs/Animals/mouse.rsi/icon-1.png new file mode 100644 index 0000000000..70a62f1cf9 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/mouse.rsi/icon-1.png differ diff --git a/Resources/Textures/Mobs/Animals/mouse.rsi/icon-2.png b/Resources/Textures/Mobs/Animals/mouse.rsi/icon-2.png new file mode 100644 index 0000000000..d52d3b4c27 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/mouse.rsi/icon-2.png differ diff --git a/Resources/Textures/Mobs/Animals/mouse.rsi/meta.json b/Resources/Textures/Mobs/Animals/mouse.rsi/meta.json new file mode 100644 index 0000000000..f63289117b --- /dev/null +++ b/Resources/Textures/Mobs/Animals/mouse.rsi/meta.json @@ -0,0 +1,170 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/e15c63d100db65eaaa5231133b8a2662ff439131#diff-8dd94e19fdb2ff341b57e31bce101298", + "states": [ + { + "name": "icon-0", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "icon-1", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "icon-2", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "mouse-0", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "mouse-1", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "mouse-2", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "dead-0", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "dead-1", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "dead-2", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "splat-0", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "splat-1", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "splat-2", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/mouse.rsi/mouse-0.png b/Resources/Textures/Mobs/Animals/mouse.rsi/mouse-0.png new file mode 100644 index 0000000000..b82f7affde Binary files /dev/null and b/Resources/Textures/Mobs/Animals/mouse.rsi/mouse-0.png differ diff --git a/Resources/Textures/Mobs/Animals/mouse.rsi/mouse-1.png b/Resources/Textures/Mobs/Animals/mouse.rsi/mouse-1.png new file mode 100644 index 0000000000..073b4ef031 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/mouse.rsi/mouse-1.png differ diff --git a/Resources/Textures/Mobs/Animals/mouse.rsi/mouse-2.png b/Resources/Textures/Mobs/Animals/mouse.rsi/mouse-2.png new file mode 100644 index 0000000000..c70a5a8b16 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/mouse.rsi/mouse-2.png differ diff --git a/Resources/Textures/Mobs/Animals/mouse.rsi/splat-0.png b/Resources/Textures/Mobs/Animals/mouse.rsi/splat-0.png new file mode 100644 index 0000000000..67f508a133 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/mouse.rsi/splat-0.png differ diff --git a/Resources/Textures/Mobs/Animals/mouse.rsi/splat-1.png b/Resources/Textures/Mobs/Animals/mouse.rsi/splat-1.png new file mode 100644 index 0000000000..a5ecc07329 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/mouse.rsi/splat-1.png differ diff --git a/Resources/Textures/Mobs/Animals/mouse.rsi/splat-2.png b/Resources/Textures/Mobs/Animals/mouse.rsi/splat-2.png new file mode 100644 index 0000000000..297f2dbd4f Binary files /dev/null and b/Resources/Textures/Mobs/Animals/mouse.rsi/splat-2.png differ diff --git a/Resources/Textures/Mobs/Animals/parrot.rsi/dead.png b/Resources/Textures/Mobs/Animals/parrot.rsi/dead.png new file mode 100644 index 0000000000..8a87b42a3b Binary files /dev/null and b/Resources/Textures/Mobs/Animals/parrot.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Animals/parrot.rsi/icon.png b/Resources/Textures/Mobs/Animals/parrot.rsi/icon.png new file mode 100644 index 0000000000..10459e3c3c Binary files /dev/null and b/Resources/Textures/Mobs/Animals/parrot.rsi/icon.png differ diff --git a/Resources/Textures/Mobs/Animals/parrot.rsi/meta.json b/Resources/Textures/Mobs/Animals/parrot.rsi/meta.json new file mode 100644 index 0000000000..5a57f8f46e --- /dev/null +++ b/Resources/Textures/Mobs/Animals/parrot.rsi/meta.json @@ -0,0 +1,109 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "icon", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "parrot", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ], + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "sit", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/parrot.rsi/parrot.png b/Resources/Textures/Mobs/Animals/parrot.rsi/parrot.png new file mode 100644 index 0000000000..fd9079fd66 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/parrot.rsi/parrot.png differ diff --git a/Resources/Textures/Mobs/Animals/parrot.rsi/sit.png b/Resources/Textures/Mobs/Animals/parrot.rsi/sit.png new file mode 100644 index 0000000000..fd78a533ee Binary files /dev/null and b/Resources/Textures/Mobs/Animals/parrot.rsi/sit.png differ diff --git a/Resources/Textures/Mobs/Animals/penguin.rsi/meta.json b/Resources/Textures/Mobs/Animals/penguin.rsi/meta.json new file mode 100644 index 0000000000..d19b479808 --- /dev/null +++ b/Resources/Textures/Mobs/Animals/penguin.rsi/meta.json @@ -0,0 +1 @@ +{"version": 1, "size": {"x": 32, "y": 32}, "license": "CCBYNA3", "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", "states": [{"name": "penguin", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "penguin_baby", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "penguin_baby_dead", "directions": 1, "delays": [[1.0]]}, {"name": "penguin_baby_dead_blood", "directions": 1, "delays": [[1.0]]}, {"name": "penguin_dead", "directions": 1, "delays": [[1.0]]}, {"name": "penguin_dead_blood", "directions": 1, "delays": [[1.0]]}, {"name": "penguin_egg", "directions": 1, "delays": [[1.0]]}, {"name": "penguin_egg_broken", "directions": 1, "delays": [[1.0]]}, {"name": "penguin_shamebrero", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}]} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Animals/penguin.rsi/penguin.png b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin.png new file mode 100644 index 0000000000..998bd79d3f Binary files /dev/null and b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin.png differ diff --git a/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_baby.png b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_baby.png new file mode 100644 index 0000000000..25b423bffd Binary files /dev/null and b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_baby.png differ diff --git a/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_baby_dead.png b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_baby_dead.png new file mode 100644 index 0000000000..5d9a12a299 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_baby_dead.png differ diff --git a/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_baby_dead_blood.png b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_baby_dead_blood.png new file mode 100644 index 0000000000..fc9a5c33c3 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_baby_dead_blood.png differ diff --git a/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_dead.png b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_dead.png new file mode 100644 index 0000000000..69245c07d9 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_dead.png differ diff --git a/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_dead_blood.png b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_dead_blood.png new file mode 100644 index 0000000000..fa6a1432ad Binary files /dev/null and b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_dead_blood.png differ diff --git a/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_egg.png b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_egg.png new file mode 100644 index 0000000000..aa81895fef Binary files /dev/null and b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_egg.png differ diff --git a/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_egg_broken.png b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_egg_broken.png new file mode 100644 index 0000000000..e2e4f957de Binary files /dev/null and b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_egg_broken.png differ diff --git a/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_shamebrero.png b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_shamebrero.png new file mode 100644 index 0000000000..99b0170801 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/penguin.rsi/penguin_shamebrero.png differ diff --git a/Resources/Textures/Mobs/Animals/snake.rsi/dead.png b/Resources/Textures/Mobs/Animals/snake.rsi/dead.png new file mode 100644 index 0000000000..62df7fb2c7 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/snake.rsi/dead.png differ diff --git a/Resources/Textures/Mobs/Animals/snake.rsi/icon.png b/Resources/Textures/Mobs/Animals/snake.rsi/icon.png new file mode 100644 index 0000000000..94a546d037 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/snake.rsi/icon.png differ diff --git a/Resources/Textures/Mobs/Animals/snake.rsi/meta.json b/Resources/Textures/Mobs/Animals/snake.rsi/meta.json new file mode 100644 index 0000000000..7177be225b --- /dev/null +++ b/Resources/Textures/Mobs/Animals/snake.rsi/meta.json @@ -0,0 +1,64 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "icon", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "snake", + "directions": 4, + "delays": [ + [ + 2.5, + 0.1, + 0.1, + 0.1 + ], + [ + 2.5, + 0.1, + 0.1, + 0.1 + ], + [ + 2.5, + 0.1, + 0.1, + 0.1 + ], + [ + 2.5, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "dead", + "directions": 1, + "delays": [ + [ + 0.2, + 0.03, + 0.03, + 0.03, + 0.03, + 0.03 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/snake.rsi/snake.png b/Resources/Textures/Mobs/Animals/snake.rsi/snake.png new file mode 100644 index 0000000000..4b92f120ec Binary files /dev/null and b/Resources/Textures/Mobs/Animals/snake.rsi/snake.png differ diff --git a/Resources/Textures/Mobs/Animals/spider.rsi/guard.png b/Resources/Textures/Mobs/Animals/spider.rsi/guard.png new file mode 100644 index 0000000000..0bc66cd834 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/spider.rsi/guard.png differ diff --git a/Resources/Textures/Mobs/Animals/spider.rsi/guard_dead.png b/Resources/Textures/Mobs/Animals/spider.rsi/guard_dead.png new file mode 100644 index 0000000000..39654f9037 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/spider.rsi/guard_dead.png differ diff --git a/Resources/Textures/Mobs/Animals/spider.rsi/hunter.png b/Resources/Textures/Mobs/Animals/spider.rsi/hunter.png new file mode 100644 index 0000000000..e74c8aa459 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/spider.rsi/hunter.png differ diff --git a/Resources/Textures/Mobs/Animals/spider.rsi/hunter_dead.png b/Resources/Textures/Mobs/Animals/spider.rsi/hunter_dead.png new file mode 100644 index 0000000000..a3179e1a5e Binary files /dev/null and b/Resources/Textures/Mobs/Animals/spider.rsi/hunter_dead.png differ diff --git a/Resources/Textures/Mobs/Animals/spider.rsi/meta.json b/Resources/Textures/Mobs/Animals/spider.rsi/meta.json new file mode 100644 index 0000000000..be37234ec3 --- /dev/null +++ b/Resources/Textures/Mobs/Animals/spider.rsi/meta.json @@ -0,0 +1,221 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "hunter", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "midwife", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "viper", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "tarantula", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "nurse", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "guard", + "directions": 4, + "delays": [ + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ], + [ + 0.2, + 0.2, + 0.2 + ] + ] + }, + { + "name": "hunter_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "midwife_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "viper_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "tarantula_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "nurse_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "guard_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Animals/spider.rsi/midwife.png b/Resources/Textures/Mobs/Animals/spider.rsi/midwife.png new file mode 100644 index 0000000000..9c9ef66268 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/spider.rsi/midwife.png differ diff --git a/Resources/Textures/Mobs/Animals/spider.rsi/midwife_dead.png b/Resources/Textures/Mobs/Animals/spider.rsi/midwife_dead.png new file mode 100644 index 0000000000..6bc1b593a6 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/spider.rsi/midwife_dead.png differ diff --git a/Resources/Textures/Mobs/Animals/spider.rsi/nurse.png b/Resources/Textures/Mobs/Animals/spider.rsi/nurse.png new file mode 100644 index 0000000000..33bd6e2681 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/spider.rsi/nurse.png differ diff --git a/Resources/Textures/Mobs/Animals/spider.rsi/nurse_dead.png b/Resources/Textures/Mobs/Animals/spider.rsi/nurse_dead.png new file mode 100644 index 0000000000..8add1b16eb Binary files /dev/null and b/Resources/Textures/Mobs/Animals/spider.rsi/nurse_dead.png differ diff --git a/Resources/Textures/Mobs/Animals/spider.rsi/tarantula.png b/Resources/Textures/Mobs/Animals/spider.rsi/tarantula.png new file mode 100644 index 0000000000..cad7aca897 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/spider.rsi/tarantula.png differ diff --git a/Resources/Textures/Mobs/Animals/spider.rsi/tarantula_dead.png b/Resources/Textures/Mobs/Animals/spider.rsi/tarantula_dead.png new file mode 100644 index 0000000000..6ee05ab4b1 Binary files /dev/null and b/Resources/Textures/Mobs/Animals/spider.rsi/tarantula_dead.png differ diff --git a/Resources/Textures/Mobs/Animals/spider.rsi/viper.png b/Resources/Textures/Mobs/Animals/spider.rsi/viper.png new file mode 100644 index 0000000000..4a3b9b9c7d Binary files /dev/null and b/Resources/Textures/Mobs/Animals/spider.rsi/viper.png differ diff --git a/Resources/Textures/Mobs/Animals/spider.rsi/viper_dead.png b/Resources/Textures/Mobs/Animals/spider.rsi/viper_dead.png new file mode 100644 index 0000000000..8a008976af Binary files /dev/null and b/Resources/Textures/Mobs/Animals/spider.rsi/viper_dead.png differ diff --git a/Resources/Textures/Mobs/Parts/body_human.rsi/head_m.png b/Resources/Textures/Mobs/Parts/body_human.rsi/head_m.png deleted file mode 100644 index 51661693b2..0000000000 Binary files a/Resources/Textures/Mobs/Parts/body_human.rsi/head_m.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/body_human.rsi/l_arm.png b/Resources/Textures/Mobs/Parts/body_human.rsi/l_arm.png deleted file mode 100644 index ff4522a4f2..0000000000 Binary files a/Resources/Textures/Mobs/Parts/body_human.rsi/l_arm.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/body_human.rsi/l_foot.png b/Resources/Textures/Mobs/Parts/body_human.rsi/l_foot.png deleted file mode 100644 index ace230831a..0000000000 Binary files a/Resources/Textures/Mobs/Parts/body_human.rsi/l_foot.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/body_human.rsi/l_hand.png b/Resources/Textures/Mobs/Parts/body_human.rsi/l_hand.png deleted file mode 100644 index 0981260733..0000000000 Binary files a/Resources/Textures/Mobs/Parts/body_human.rsi/l_hand.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/body_human.rsi/l_leg.png b/Resources/Textures/Mobs/Parts/body_human.rsi/l_leg.png deleted file mode 100644 index 362a0eb943..0000000000 Binary files a/Resources/Textures/Mobs/Parts/body_human.rsi/l_leg.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/body_human.rsi/meta.json b/Resources/Textures/Mobs/Parts/body_human.rsi/meta.json deleted file mode 100644 index d0c6866a45..0000000000 --- a/Resources/Textures/Mobs/Parts/body_human.rsi/meta.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "version": 1, - "size": { - "x": 32, - "y": 32 - }, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from https://github.com/vgstation-coders/vgstation13", - "states": [ - { - "name": "head_m", - "directions": 4, - "delays": [ - [1.0], [1.0], [1.0], [1.0] - ] - }, - { - "name": "torso_m", - "directions": 4, - "delays": [ - [1.0], [1.0], [1.0], [1.0] - ] - }, - { - "name": "l_arm", - "directions": 4, - "delays": [ - [1.0], [1.0], [1.0], [1.0] - ] - }, - { - "name": "r_arm", - "directions": 4, - "delays": [ - [1.0], [1.0], [1.0], [1.0] - ] - }, - { - "name": "l_hand", - "directions": 4, - "delays": [ - [1.0], [1.0], [1.0], [1.0] - ] - }, - { - "name": "r_hand", - "directions": 4, - "delays": [ - [1.0], [1.0], [1.0], [1.0] - ] - }, - { - "name": "l_leg", - "directions": 4, - "delays": [ - [1.0], [1.0], [1.0], [1.0] - ] - }, - { - "name": "r_leg", - "directions": 4, - "delays": [ - [1.0], [1.0], [1.0], [1.0] - ] - }, - { - "name": "l_foot", - "directions": 4, - "delays": [ - [1.0], [1.0], [1.0], [1.0] - ] - }, - { - "name": "r_foot", - "directions": 4, - "delays": [ - [1.0], [1.0], [1.0], [1.0] - ] - } - ] -} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Parts/body_human.rsi/r_arm.png b/Resources/Textures/Mobs/Parts/body_human.rsi/r_arm.png deleted file mode 100644 index f9024dbdba..0000000000 Binary files a/Resources/Textures/Mobs/Parts/body_human.rsi/r_arm.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/body_human.rsi/r_foot.png b/Resources/Textures/Mobs/Parts/body_human.rsi/r_foot.png deleted file mode 100644 index 8e93a2e157..0000000000 Binary files a/Resources/Textures/Mobs/Parts/body_human.rsi/r_foot.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/body_human.rsi/r_hand.png b/Resources/Textures/Mobs/Parts/body_human.rsi/r_hand.png deleted file mode 100644 index b73ac3dd0b..0000000000 Binary files a/Resources/Textures/Mobs/Parts/body_human.rsi/r_hand.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/body_human.rsi/r_leg.png b/Resources/Textures/Mobs/Parts/body_human.rsi/r_leg.png deleted file mode 100644 index cc7fc1a144..0000000000 Binary files a/Resources/Textures/Mobs/Parts/body_human.rsi/r_leg.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/body_human.rsi/torso_m.png b/Resources/Textures/Mobs/Parts/body_human.rsi/torso_m.png deleted file mode 100644 index 556a567143..0000000000 Binary files a/Resources/Textures/Mobs/Parts/body_human.rsi/torso_m.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/eyes_advanced.png b/Resources/Textures/Mobs/Parts/eyes_advanced.png deleted file mode 100644 index c435aaf197..0000000000 Binary files a/Resources/Textures/Mobs/Parts/eyes_advanced.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/eyes_grey.png b/Resources/Textures/Mobs/Parts/eyes_grey.png deleted file mode 100644 index 2d2e8472ef..0000000000 Binary files a/Resources/Textures/Mobs/Parts/eyes_grey.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/kidneys_advanced.png b/Resources/Textures/Mobs/Parts/kidneys_advanced.png deleted file mode 100644 index 2e8c150107..0000000000 Binary files a/Resources/Textures/Mobs/Parts/kidneys_advanced.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/lungs_advanced.png b/Resources/Textures/Mobs/Parts/lungs_advanced.png deleted file mode 100644 index 2186b3fe03..0000000000 Binary files a/Resources/Textures/Mobs/Parts/lungs_advanced.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/lungs_plasmaman.png b/Resources/Textures/Mobs/Parts/lungs_phoronman.png similarity index 100% rename from Resources/Textures/Mobs/Parts/lungs_plasmaman.png rename to Resources/Textures/Mobs/Parts/lungs_phoronman.png diff --git a/Resources/Textures/Mobs/Parts/lungs_vox.png b/Resources/Textures/Mobs/Parts/lungs_vox.png deleted file mode 100644 index 780c33e52f..0000000000 Binary files a/Resources/Textures/Mobs/Parts/lungs_vox.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/organs_human.rsi/brain_human.png b/Resources/Textures/Mobs/Parts/organs_human.rsi/brain_human.png deleted file mode 100644 index ba49b3e5e6..0000000000 Binary files a/Resources/Textures/Mobs/Parts/organs_human.rsi/brain_human.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/organs_human.rsi/eyes_human.png b/Resources/Textures/Mobs/Parts/organs_human.rsi/eyes_human.png deleted file mode 100644 index 015d2c4094..0000000000 Binary files a/Resources/Textures/Mobs/Parts/organs_human.rsi/eyes_human.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/organs_human.rsi/heart_human.png b/Resources/Textures/Mobs/Parts/organs_human.rsi/heart_human.png deleted file mode 100644 index 7c24e656b4..0000000000 Binary files a/Resources/Textures/Mobs/Parts/organs_human.rsi/heart_human.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/organs_human.rsi/kidneys_human.png b/Resources/Textures/Mobs/Parts/organs_human.rsi/kidneys_human.png deleted file mode 100644 index 0b324e81ba..0000000000 Binary files a/Resources/Textures/Mobs/Parts/organs_human.rsi/kidneys_human.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/organs_human.rsi/liver_human.png b/Resources/Textures/Mobs/Parts/organs_human.rsi/liver_human.png deleted file mode 100644 index efa7e75f76..0000000000 Binary files a/Resources/Textures/Mobs/Parts/organs_human.rsi/liver_human.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/organs_human.rsi/lungs_human.png b/Resources/Textures/Mobs/Parts/organs_human.rsi/lungs_human.png deleted file mode 100644 index 6e644517a7..0000000000 Binary files a/Resources/Textures/Mobs/Parts/organs_human.rsi/lungs_human.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Parts/organs_human.rsi/meta.json b/Resources/Textures/Mobs/Parts/organs_human.rsi/meta.json deleted file mode 100644 index 46bbb3c601..0000000000 --- a/Resources/Textures/Mobs/Parts/organs_human.rsi/meta.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "version": 1, - "size": { - "x": 32, - "y": 32 - }, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from https://github.com/vgstation-coders/vgstation13", - "states": [ - { - "name": "brain_human", - "directions": 1, - "delays": [ - [1.0] - ] - }, - { - "name": "eyes_human", - "directions": 1, - "delays": [ - [1.0] - ] - }, - { - "name": "heart_human", - "directions": 1, - "delays": [ - [1.0] - ] - }, - { - "name": "kidneys_human", - "directions": 1, - "delays": [ - [1.0] - ] - }, - { - "name": "liver_human", - "directions": 1, - "delays": [ - [1.0] - ] - }, - { - "name": "lungs_human", - "directions": 1, - "delays": [ - [1.0] - ] - }, - ] -} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/cak.png b/Resources/Textures/Mobs/Pets/cat.rsi/cak.png new file mode 100644 index 0000000000..882dbecabc Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/cak.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/cak_dead.png b/Resources/Textures/Mobs/Pets/cat.rsi/cak_dead.png new file mode 100644 index 0000000000..fed1725271 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/cak_dead.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/cak_rest.png b/Resources/Textures/Mobs/Pets/cat.rsi/cak_rest.png new file mode 100644 index 0000000000..9d6d13f93a Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/cak_rest.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/cak_sit.png b/Resources/Textures/Mobs/Pets/cat.rsi/cak_sit.png new file mode 100644 index 0000000000..f8e99f1953 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/cak_sit.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/cat.png b/Resources/Textures/Mobs/Pets/cat.rsi/cat.png new file mode 100644 index 0000000000..37b105b12e Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/cat.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/cat2.png b/Resources/Textures/Mobs/Pets/cat.rsi/cat2.png new file mode 100644 index 0000000000..09eed19efc Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/cat2.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/cat2_dead.png b/Resources/Textures/Mobs/Pets/cat.rsi/cat2_dead.png new file mode 100644 index 0000000000..efef131b8e Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/cat2_dead.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/cat2_rest.png b/Resources/Textures/Mobs/Pets/cat.rsi/cat2_rest.png new file mode 100644 index 0000000000..b1f6676873 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/cat2_rest.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/cat2_sit.png b/Resources/Textures/Mobs/Pets/cat.rsi/cat2_sit.png new file mode 100644 index 0000000000..bfae87b754 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/cat2_sit.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/cat_dead.png b/Resources/Textures/Mobs/Pets/cat.rsi/cat_dead.png new file mode 100644 index 0000000000..5f4ad65721 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/cat_dead.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/cat_deadcollar.png b/Resources/Textures/Mobs/Pets/cat.rsi/cat_deadcollar.png new file mode 100644 index 0000000000..d065e3f898 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/cat_deadcollar.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/cat_deadtag.png b/Resources/Textures/Mobs/Pets/cat.rsi/cat_deadtag.png new file mode 100644 index 0000000000..30c0a77e38 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/cat_deadtag.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/cat_rest.png b/Resources/Textures/Mobs/Pets/cat.rsi/cat_rest.png new file mode 100644 index 0000000000..5650e1d504 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/cat_rest.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/cat_restcollar.png b/Resources/Textures/Mobs/Pets/cat.rsi/cat_restcollar.png new file mode 100644 index 0000000000..d065e3f898 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/cat_restcollar.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/cat_resttag.png b/Resources/Textures/Mobs/Pets/cat.rsi/cat_resttag.png new file mode 100644 index 0000000000..30c0a77e38 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/cat_resttag.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/cat_sit.png b/Resources/Textures/Mobs/Pets/cat.rsi/cat_sit.png new file mode 100644 index 0000000000..36f980d37c Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/cat_sit.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/catcollar.png b/Resources/Textures/Mobs/Pets/cat.rsi/catcollar.png new file mode 100644 index 0000000000..c420a6bdbb Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/catcollar.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/cattag.png b/Resources/Textures/Mobs/Pets/cat.rsi/cattag.png new file mode 100644 index 0000000000..449b89638d Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/cattag.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/kitten.png b/Resources/Textures/Mobs/Pets/cat.rsi/kitten.png new file mode 100644 index 0000000000..875d8d358c Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/kitten.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/kitten_dead.png b/Resources/Textures/Mobs/Pets/cat.rsi/kitten_dead.png new file mode 100644 index 0000000000..330fd0bff5 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/kitten_dead.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/kitten_deadcollar.png b/Resources/Textures/Mobs/Pets/cat.rsi/kitten_deadcollar.png new file mode 100644 index 0000000000..b45fc5cced Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/kitten_deadcollar.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/kitten_deadtag.png b/Resources/Textures/Mobs/Pets/cat.rsi/kitten_deadtag.png new file mode 100644 index 0000000000..2851bfd41e Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/kitten_deadtag.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/kitten_rest.png b/Resources/Textures/Mobs/Pets/cat.rsi/kitten_rest.png new file mode 100644 index 0000000000..3d46e4f45d Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/kitten_rest.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/kitten_restcollar.png b/Resources/Textures/Mobs/Pets/cat.rsi/kitten_restcollar.png new file mode 100644 index 0000000000..b45fc5cced Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/kitten_restcollar.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/kitten_resttag.png b/Resources/Textures/Mobs/Pets/cat.rsi/kitten_resttag.png new file mode 100644 index 0000000000..2851bfd41e Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/kitten_resttag.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/kitten_sit.png b/Resources/Textures/Mobs/Pets/cat.rsi/kitten_sit.png new file mode 100644 index 0000000000..bcaf7b46b1 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/kitten_sit.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/kittencollar.png b/Resources/Textures/Mobs/Pets/cat.rsi/kittencollar.png new file mode 100644 index 0000000000..079698ec85 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/kittencollar.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/kittentag.png b/Resources/Textures/Mobs/Pets/cat.rsi/kittentag.png new file mode 100644 index 0000000000..6f3bf14f5c Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/kittentag.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/meta.json b/Resources/Textures/Mobs/Pets/cat.rsi/meta.json new file mode 100644 index 0000000000..79e5c555bd --- /dev/null +++ b/Resources/Textures/Mobs/Pets/cat.rsi/meta.json @@ -0,0 +1,392 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "cak", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "cak_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "cak_rest", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "cak_sit", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "cat", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "cat2", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "cat2_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "cat2_rest", + "directions": 1, + "delays": [ + [ + 0.1, + 0.2, + 0.1, + 0.2 + ] + ] + }, + { + "name": "cat2_sit", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "cat_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "cat_deadcollar", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "cat_deadtag", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "cat_rest", + "directions": 1, + "delays": [ + [ + 0.1, + 0.2, + 0.1, + 0.2 + ] + ] + }, + { + "name": "cat_restcollar", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "cat_resttag", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "cat_sit", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "catcollar", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "cattag", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "kitten", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "kitten_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "kitten_deadcollar", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "kitten_deadtag", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "kitten_rest", + "directions": 1, + "delays": [ + [ + 0.1, + 0.2, + 0.1, + 0.2 + ] + ] + }, + { + "name": "kitten_restcollar", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "kitten_resttag", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "kitten_sit", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "kittencollar", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "kittentag", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "spacecat", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "spacecat_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "spacecat_rest", + "directions": 1, + "delays": [ + [ + 0.1, + 0.2, + 0.1, + 0.2 + ] + ] + }, + { + "name": "spacecat_sit", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/spacecat.png b/Resources/Textures/Mobs/Pets/cat.rsi/spacecat.png new file mode 100644 index 0000000000..15fd940b54 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/spacecat.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/spacecat_dead.png b/Resources/Textures/Mobs/Pets/cat.rsi/spacecat_dead.png new file mode 100644 index 0000000000..294a31d2d8 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/spacecat_dead.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/spacecat_rest.png b/Resources/Textures/Mobs/Pets/cat.rsi/spacecat_rest.png new file mode 100644 index 0000000000..631f7937e6 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/spacecat_rest.png differ diff --git a/Resources/Textures/Mobs/Pets/cat.rsi/spacecat_sit.png b/Resources/Textures/Mobs/Pets/cat.rsi/spacecat_sit.png new file mode 100644 index 0000000000..09b93e9104 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/cat.rsi/spacecat_sit.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/corgi.png b/Resources/Textures/Mobs/Pets/corgi.rsi/corgi.png new file mode 100644 index 0000000000..585f64cd5e Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/corgi.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/corgi_dead.png b/Resources/Textures/Mobs/Pets/corgi.rsi/corgi_dead.png new file mode 100644 index 0000000000..b52737b4b7 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/corgi_dead.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/corgi_deadcollar.png b/Resources/Textures/Mobs/Pets/corgi.rsi/corgi_deadcollar.png new file mode 100644 index 0000000000..35edb287d9 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/corgi_deadcollar.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/corgi_deadtag.png b/Resources/Textures/Mobs/Pets/corgi.rsi/corgi_deadtag.png new file mode 100644 index 0000000000..dbc4b6c1e4 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/corgi_deadtag.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/corgi_rest.png b/Resources/Textures/Mobs/Pets/corgi.rsi/corgi_rest.png new file mode 100644 index 0000000000..8fa2495489 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/corgi_rest.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/corgicollar.png b/Resources/Textures/Mobs/Pets/corgi.rsi/corgicollar.png new file mode 100644 index 0000000000..bed3c3731e Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/corgicollar.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/corgitag.png b/Resources/Textures/Mobs/Pets/corgi.rsi/corgitag.png new file mode 100644 index 0000000000..461d87f007 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/corgitag.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/ian.png b/Resources/Textures/Mobs/Pets/corgi.rsi/ian.png new file mode 100644 index 0000000000..4ee2f2544b Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/ian.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/ian_dead.png b/Resources/Textures/Mobs/Pets/corgi.rsi/ian_dead.png new file mode 100644 index 0000000000..383a23326f Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/ian_dead.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/ian_shaved.png b/Resources/Textures/Mobs/Pets/corgi.rsi/ian_shaved.png new file mode 100644 index 0000000000..2b71f36e39 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/ian_shaved.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/ian_shaved_dead.png b/Resources/Textures/Mobs/Pets/corgi.rsi/ian_shaved_dead.png new file mode 100644 index 0000000000..b286c5f886 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/ian_shaved_dead.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/lisa.png b/Resources/Textures/Mobs/Pets/corgi.rsi/lisa.png new file mode 100644 index 0000000000..0c517d9530 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/lisa.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/lisa_dead.png b/Resources/Textures/Mobs/Pets/corgi.rsi/lisa_dead.png new file mode 100644 index 0000000000..f698bef30e Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/lisa_dead.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/lisa_shaved.png b/Resources/Textures/Mobs/Pets/corgi.rsi/lisa_shaved.png new file mode 100644 index 0000000000..9094804887 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/lisa_shaved.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/lisa_shaved_dead.png b/Resources/Textures/Mobs/Pets/corgi.rsi/lisa_shaved_dead.png new file mode 100644 index 0000000000..fec42685a0 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/lisa_shaved_dead.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/meta.json b/Resources/Textures/Mobs/Pets/corgi.rsi/meta.json new file mode 100644 index 0000000000..618d63f963 --- /dev/null +++ b/Resources/Textures/Mobs/Pets/corgi.rsi/meta.json @@ -0,0 +1,415 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "corgi", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "corgi_rest", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "corgi_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "ian", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "ian_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "corgi_deadcollar", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "corgi_deadtag", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "ian_shaved", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "ian_shaved_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "corgicollar", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "corgitag", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "lisa", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "lisa_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "lisa_shaved", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "lisa_shaved_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "narsian", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "narsian_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "old_ian", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ] + ] + }, + { + "name": "old_ian_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "old_ian_shaved", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ] + ] + }, + { + "name": "old_ian_shaved_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "puppy", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "puppy_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "puppy_deadcollar", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "puppy_deadtag", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "puppy_shaved", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "puppy_shaved_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "puppycollar", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "puppytag", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/narsian.png b/Resources/Textures/Mobs/Pets/corgi.rsi/narsian.png new file mode 100644 index 0000000000..16f7bc8684 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/narsian.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/narsian_dead.png b/Resources/Textures/Mobs/Pets/corgi.rsi/narsian_dead.png new file mode 100644 index 0000000000..6d792a2b54 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/narsian_dead.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/old_ian.png b/Resources/Textures/Mobs/Pets/corgi.rsi/old_ian.png new file mode 100644 index 0000000000..6bdc878a6c Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/old_ian.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/old_ian_dead.png b/Resources/Textures/Mobs/Pets/corgi.rsi/old_ian_dead.png new file mode 100644 index 0000000000..984298eb24 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/old_ian_dead.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/old_ian_shaved.png b/Resources/Textures/Mobs/Pets/corgi.rsi/old_ian_shaved.png new file mode 100644 index 0000000000..0541d67d6e Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/old_ian_shaved.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/old_ian_shaved_dead.png b/Resources/Textures/Mobs/Pets/corgi.rsi/old_ian_shaved_dead.png new file mode 100644 index 0000000000..f94b46fe72 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/old_ian_shaved_dead.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/puppy.png b/Resources/Textures/Mobs/Pets/corgi.rsi/puppy.png new file mode 100644 index 0000000000..52b36da606 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/puppy.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/puppy_dead.png b/Resources/Textures/Mobs/Pets/corgi.rsi/puppy_dead.png new file mode 100644 index 0000000000..1f80afd4bf Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/puppy_dead.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/puppy_deadcollar.png b/Resources/Textures/Mobs/Pets/corgi.rsi/puppy_deadcollar.png new file mode 100644 index 0000000000..6a34922c42 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/puppy_deadcollar.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/puppy_deadtag.png b/Resources/Textures/Mobs/Pets/corgi.rsi/puppy_deadtag.png new file mode 100644 index 0000000000..0124355b13 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/puppy_deadtag.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/puppy_shaved.png b/Resources/Textures/Mobs/Pets/corgi.rsi/puppy_shaved.png new file mode 100644 index 0000000000..ae84297e04 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/puppy_shaved.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/puppy_shaved_dead.png b/Resources/Textures/Mobs/Pets/corgi.rsi/puppy_shaved_dead.png new file mode 100644 index 0000000000..04584d4d44 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/puppy_shaved_dead.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/puppycollar.png b/Resources/Textures/Mobs/Pets/corgi.rsi/puppycollar.png new file mode 100644 index 0000000000..8aa1939e7a Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/puppycollar.png differ diff --git a/Resources/Textures/Mobs/Pets/corgi.rsi/puppytag.png b/Resources/Textures/Mobs/Pets/corgi.rsi/puppytag.png new file mode 100644 index 0000000000..b7d9d09d57 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/corgi.rsi/puppytag.png differ diff --git a/Resources/Textures/Mobs/Pets/fox.rsi/fox.png b/Resources/Textures/Mobs/Pets/fox.rsi/fox.png new file mode 100644 index 0000000000..912c87ede4 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/fox.rsi/fox.png differ diff --git a/Resources/Textures/Mobs/Pets/fox.rsi/fox_dead.png b/Resources/Textures/Mobs/Pets/fox.rsi/fox_dead.png new file mode 100644 index 0000000000..dfca627054 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/fox.rsi/fox_dead.png differ diff --git a/Resources/Textures/Mobs/Pets/fox.rsi/fox_deadcollar.png b/Resources/Textures/Mobs/Pets/fox.rsi/fox_deadcollar.png new file mode 100644 index 0000000000..258e2b5f06 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/fox.rsi/fox_deadcollar.png differ diff --git a/Resources/Textures/Mobs/Pets/fox.rsi/fox_deadtag.png b/Resources/Textures/Mobs/Pets/fox.rsi/fox_deadtag.png new file mode 100644 index 0000000000..46b597f4dd Binary files /dev/null and b/Resources/Textures/Mobs/Pets/fox.rsi/fox_deadtag.png differ diff --git a/Resources/Textures/Mobs/Pets/fox.rsi/foxcollar.png b/Resources/Textures/Mobs/Pets/fox.rsi/foxcollar.png new file mode 100644 index 0000000000..83a226ba7c Binary files /dev/null and b/Resources/Textures/Mobs/Pets/fox.rsi/foxcollar.png differ diff --git a/Resources/Textures/Mobs/Pets/fox.rsi/foxtag.png b/Resources/Textures/Mobs/Pets/fox.rsi/foxtag.png new file mode 100644 index 0000000000..86254d3959 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/fox.rsi/foxtag.png differ diff --git a/Resources/Textures/Mobs/Pets/fox.rsi/meta.json b/Resources/Textures/Mobs/Pets/fox.rsi/meta.json new file mode 100644 index 0000000000..6e4b003f46 --- /dev/null +++ b/Resources/Textures/Mobs/Pets/fox.rsi/meta.json @@ -0,0 +1,92 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "fox", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "fox_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "fox_deadcollar", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "fox_deadtag", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + }, + { + "name": "foxcollar", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "foxtag", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Pets/sloth.rsi/meta.json b/Resources/Textures/Mobs/Pets/sloth.rsi/meta.json new file mode 100644 index 0000000000..53aabb227e --- /dev/null +++ b/Resources/Textures/Mobs/Pets/sloth.rsi/meta.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/tgstation/tgstation/commit/53d1f1477d22a11a99c6c6924977cd431075761b", + "states": [ + { + "name": "sloth", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "sloth_dead", + "directions": 1, + "delays": [ + [ + 1 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Pets/sloth.rsi/sloth.png b/Resources/Textures/Mobs/Pets/sloth.rsi/sloth.png new file mode 100644 index 0000000000..66d8bb71c7 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/sloth.rsi/sloth.png differ diff --git a/Resources/Textures/Mobs/Pets/sloth.rsi/sloth_dead.png b/Resources/Textures/Mobs/Pets/sloth.rsi/sloth_dead.png new file mode 100644 index 0000000000..6320626c24 Binary files /dev/null and b/Resources/Textures/Mobs/Pets/sloth.rsi/sloth_dead.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/appendix.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/appendix.png new file mode 100644 index 0000000000..4d4fa1293a Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/appendix.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/appendixinflamed.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/appendixinflamed.png new file mode 100644 index 0000000000..f0cf0a2d97 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/appendixinflamed.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/brain.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/brain.png new file mode 100644 index 0000000000..04d04890f6 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/brain.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/ears.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/ears.png new file mode 100644 index 0000000000..ae9ab068eb Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/ears.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/eyeballs.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/eyeballs.png new file mode 100644 index 0000000000..301158f76b Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/eyeballs.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/heart-off.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/heart-off.png new file mode 100644 index 0000000000..b2c6562c8f Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/heart-off.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/heart-on.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/heart-on.png new file mode 100644 index 0000000000..ab786576fe Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/heart-on.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/kidneys.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/kidneys.png new file mode 100644 index 0000000000..4296d5d33a Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/kidneys.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/liver.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/liver.png new file mode 100644 index 0000000000..159339e36c Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/liver.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/lungs.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/lungs.png new file mode 100644 index 0000000000..7a2a48e218 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/lungs.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/meta.json b/Resources/Textures/Mobs/Species/Human/organs.rsi/meta.json new file mode 100644 index 0000000000..13310de52c --- /dev/null +++ b/Resources/Textures/Mobs/Species/Human/organs.rsi/meta.json @@ -0,0 +1,121 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/blob/7489d8e74693f9ca811e4f8a921de10ed96c619e/icons/obj/surgery.dmi", + "states": [ + { + "name": "appendix", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "appendixinflamed", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "brain", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "ears", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "eyeballs", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "heart-off", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "heart-on", + "directions": 1, + "delays": [ + [ + 0.6, + 0.1, + 0.1 + ] + ] + }, + { + "name": "kidneys", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "liver", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "lungs", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "stomach", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "tonguenormal", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/stomach.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/stomach.png new file mode 100644 index 0000000000..0d7be9bfe4 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/stomach.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/tonguenormal.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/tonguenormal.png new file mode 100644 index 0000000000..5cffd4e0e3 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/tonguenormal.png differ diff --git a/Resources/Textures/Mobs/Species/human.rsi/human_basic.png b/Resources/Textures/Mobs/Species/Human/parts.rsi/full.png similarity index 100% rename from Resources/Textures/Mobs/Species/human.rsi/human_basic.png rename to Resources/Textures/Mobs/Species/Human/parts.rsi/full.png diff --git a/Resources/Textures/Mobs/Species/human.rsi/human_head_f.png b/Resources/Textures/Mobs/Species/Human/parts.rsi/head_f.png similarity index 100% rename from Resources/Textures/Mobs/Species/human.rsi/human_head_f.png rename to Resources/Textures/Mobs/Species/Human/parts.rsi/head_f.png diff --git a/Resources/Textures/Mobs/Species/human.rsi/human_head_m.png b/Resources/Textures/Mobs/Species/Human/parts.rsi/head_m.png similarity index 100% rename from Resources/Textures/Mobs/Species/human.rsi/human_head_m.png rename to Resources/Textures/Mobs/Species/Human/parts.rsi/head_m.png diff --git a/Resources/Textures/Mobs/Species/human.rsi/human_l_arm.png b/Resources/Textures/Mobs/Species/Human/parts.rsi/l_arm.png similarity index 100% rename from Resources/Textures/Mobs/Species/human.rsi/human_l_arm.png rename to Resources/Textures/Mobs/Species/Human/parts.rsi/l_arm.png diff --git a/Resources/Textures/Mobs/Species/Human/parts.rsi/l_foot.png b/Resources/Textures/Mobs/Species/Human/parts.rsi/l_foot.png new file mode 100644 index 0000000000..c448837af2 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/parts.rsi/l_foot.png differ diff --git a/Resources/Textures/Mobs/Species/human.rsi/human_l_hand.png b/Resources/Textures/Mobs/Species/Human/parts.rsi/l_hand.png similarity index 100% rename from Resources/Textures/Mobs/Species/human.rsi/human_l_hand.png rename to Resources/Textures/Mobs/Species/Human/parts.rsi/l_hand.png diff --git a/Resources/Textures/Mobs/Species/Human/parts.rsi/l_leg.png b/Resources/Textures/Mobs/Species/Human/parts.rsi/l_leg.png new file mode 100644 index 0000000000..b3ce27ba86 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/parts.rsi/l_leg.png differ diff --git a/Resources/Textures/Mobs/Species/Human/parts.rsi/meta.json b/Resources/Textures/Mobs/Species/Human/parts.rsi/meta.json new file mode 100644 index 0000000000..33281d6f3e --- /dev/null +++ b/Resources/Textures/Mobs/Species/Human/parts.rsi/meta.json @@ -0,0 +1,236 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/tgstation/tgstation/blob/8024397cc81c5f47f74cf4279e35728487d0a1a7/icons/mob/human_parts_greyscale.dmi and modified by DrSmugleaf", + "states": [ + { + "name": "full", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "head_f", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "head_m", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "l_arm", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "l_foot", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "l_hand", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "l_leg", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "r_arm", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "r_foot", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "r_hand", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "r_leg", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "torso_f", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "torso_m", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + } + ] +} diff --git a/Resources/Textures/Mobs/Species/human.rsi/human_r_arm.png b/Resources/Textures/Mobs/Species/Human/parts.rsi/r_arm.png similarity index 100% rename from Resources/Textures/Mobs/Species/human.rsi/human_r_arm.png rename to Resources/Textures/Mobs/Species/Human/parts.rsi/r_arm.png diff --git a/Resources/Textures/Mobs/Species/Human/parts.rsi/r_foot.png b/Resources/Textures/Mobs/Species/Human/parts.rsi/r_foot.png new file mode 100644 index 0000000000..8d51a591a4 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/parts.rsi/r_foot.png differ diff --git a/Resources/Textures/Mobs/Species/human.rsi/human_r_hand.png b/Resources/Textures/Mobs/Species/Human/parts.rsi/r_hand.png similarity index 100% rename from Resources/Textures/Mobs/Species/human.rsi/human_r_hand.png rename to Resources/Textures/Mobs/Species/Human/parts.rsi/r_hand.png diff --git a/Resources/Textures/Mobs/Species/Human/parts.rsi/r_leg.png b/Resources/Textures/Mobs/Species/Human/parts.rsi/r_leg.png new file mode 100644 index 0000000000..67917397a1 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/parts.rsi/r_leg.png differ diff --git a/Resources/Textures/Mobs/Species/human.rsi/human_chest_f.png b/Resources/Textures/Mobs/Species/Human/parts.rsi/torso_f.png similarity index 100% rename from Resources/Textures/Mobs/Species/human.rsi/human_chest_f.png rename to Resources/Textures/Mobs/Species/Human/parts.rsi/torso_f.png diff --git a/Resources/Textures/Mobs/Species/human.rsi/human_chest_m.png b/Resources/Textures/Mobs/Species/Human/parts.rsi/torso_m.png similarity index 100% rename from Resources/Textures/Mobs/Species/human.rsi/human_chest_m.png rename to Resources/Textures/Mobs/Species/Human/parts.rsi/torso_m.png diff --git a/Resources/Textures/Mobs/Species/human.rsi/human_l_leg.png b/Resources/Textures/Mobs/Species/human.rsi/human_l_leg.png deleted file mode 100644 index c54733e42c..0000000000 Binary files a/Resources/Textures/Mobs/Species/human.rsi/human_l_leg.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/human.rsi/human_r_leg.png b/Resources/Textures/Mobs/Species/human.rsi/human_r_leg.png deleted file mode 100644 index 941b96e032..0000000000 Binary files a/Resources/Textures/Mobs/Species/human.rsi/human_r_leg.png and /dev/null differ diff --git a/Resources/Textures/Mobs/Species/human.rsi/meta.json b/Resources/Textures/Mobs/Species/human.rsi/meta.json deleted file mode 100644 index 99a7d1ab13..0000000000 --- a/Resources/Textures/Mobs/Species/human.rsi/meta.json +++ /dev/null @@ -1 +0,0 @@ -{"version": 1, "size": {"x": 32, "y": 32}, "states": [{"name": "human_basic", "directions": 1, "delays": [[1.0]]}, {"name": "human_chest_f", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "human_chest_m", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "human_head_f", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "human_head_m", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "human_l_arm", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "human_l_hand", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "human_l_leg", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "human_r_arm", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "human_r_hand", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "human_r_leg", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}]} \ No newline at end of file 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/Misc/fire_extinguisher.png b/Resources/Textures/Objects/Misc/fire_extinguisher.png deleted file mode 100644 index 87a128c8fe..0000000000 Binary files a/Resources/Textures/Objects/Misc/fire_extinguisher.png and /dev/null differ diff --git a/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/fire_extinguisher_closed.png b/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/fire_extinguisher_closed.png new file mode 100644 index 0000000000..7c99615ffe Binary files /dev/null and b/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/fire_extinguisher_closed.png differ diff --git a/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/fire_extinguisher_open.png b/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/fire_extinguisher_open.png new file mode 100644 index 0000000000..7909d14f26 Binary files /dev/null and b/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/fire_extinguisher_open.png differ diff --git a/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/inhand-left.png b/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/inhand-left.png new file mode 100644 index 0000000000..fb697da356 Binary files /dev/null and b/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/inhand-right.png b/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/inhand-right.png new file mode 100644 index 0000000000..fb697da356 Binary files /dev/null and b/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/meta.json b/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/meta.json new file mode 100644 index 0000000000..d4447ddf98 --- /dev/null +++ b/Resources/Textures/Objects/Misc/fire_extinguisher.rsi/meta.json @@ -0,0 +1,65 @@ +{ + "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": "fire_extinguisher_open", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "fire_extinguisher_closed", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "inhand-right", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + }, + { + "name": "inhand-left", + "directions": 4, + "delays": [ + [ + 1 + ], + [ + 1 + ], + [ + 1 + ], + [ + 1 + ] + ] + } + ] +} 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/Resources/Textures/Objects/Tools/lantern.rsi/meta.json b/Resources/Textures/Objects/Tools/lantern.rsi/meta.json index 2205be15d5..745406c95d 100644 --- a/Resources/Textures/Objects/Tools/lantern.rsi/meta.json +++ b/Resources/Textures/Objects/Tools/lantern.rsi/meta.json @@ -1,6 +1,6 @@ { "version": 1, - "license": "CC BY-SA 3.0", + "license": "CC BY-SA 3.0", "copyright": "Taken from https://github.com/tgstation/tgstation at commit 9bebd81ae0b0a7f952b59886a765c681205de31f", "size": { "x": 32, diff --git a/RobustToolbox b/RobustToolbox index 1934428c95..0d7e6a8e1c 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 1934428c95d44210cfc9c155c7d15405d4a374c4 +Subproject commit 0d7e6a8e1cbc4931ebfd939cdcbdd42d876ad89b diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index c93412bf8d..9a0e2799ef 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -51,6 +51,8 @@ <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Lidgren.Network" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> True True + True + True True True True @@ -58,6 +60,7 @@ True True True + True True True True @@ -67,9 +70,11 @@ True True True + True True True True + True True True True @@ -78,7 +83,7 @@ True True True + True True True True - \ No newline at end of file