diff --git a/Content.Client/GameObjects/Components/Body/BodyManagerComponent.cs b/Content.Client/GameObjects/Components/Body/BodyManagerComponent.cs new file mode 100644 index 0000000000..2948c9191e --- /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/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/MedicalScanner/MedicalScannerBoundUserInterface.cs b/Content.Client/GameObjects/Components/MedicalScanner/MedicalScannerBoundUserInterface.cs index 135a55f992..4b2fa2a1fc 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) diff --git a/Content.Client/GameObjects/Components/MedicalScanner/MedicalScannerWindow.cs b/Content.Client/GameObjects/Components/MedicalScanner/MedicalScannerWindow.cs index 4f6eefb5d9..61adc113f2 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; @@ -14,22 +18,36 @@ namespace Content.Client.GameObjects.Components.MedicalScanner { 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) + if (!state.Entity.HasValue || + !state.HasDamage() || + !IoCManager.Resolve().TryGetEntity(state.Entity.Value, out var entity)) + { + text.Append(Loc.GetString("No patient data.")); + } + else + { + text.Append($"{entity.Name}{Loc.GetString("'s health:")}\n"); + + foreach (var (@class, classAmount) in state.DamageClasses) { - foreach (var (dmgType, amount) in state.DamageDictionary) + text.Append($"\n{Loc.GetString("{0}: {1}", @class, classAmount)}"); + + foreach (var type in @class.ToTypes()) { - text.Append($"\n{dmgType}: {amount}"); + if (!state.DamageTypes.TryGetValue(type, out var typeAmount)) + { + continue; + } + + text.Append($"\n- {Loc.GetString("{0}: {1}", type, typeAmount)}"); } + + text.Append("\n"); } } - Contents.AddChild(new Label(){Text = text.ToString()}); + + Contents.AddChild(new Label() {Text = text.ToString()}); } } } 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/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/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..a032972f02 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -96,7 +96,6 @@ "BarSign", "DroppedBodyPart", "DroppedMechanism", - "BodyManager", "SolarPanel", "BodyScanner", "Stunbaton", @@ -157,6 +156,7 @@ "Vapor", "DamageOnHighSpeedImpact", "Barotrauma", + "MobStateManager", }; } } diff --git a/Content.Server/AI/Utility/AiLogic/UtilityAI.cs b/Content.Server/AI/Utility/AiLogic/UtilityAI.cs index 005a30a9bc..6c15106012 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,10 +6,9 @@ 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.LoadBalancer; using Content.Server.GameObjects.EntitySystems.JobQueues; +using Content.Shared.GameObjects.Components.Damage; using Robust.Server.AI; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; @@ -114,39 +113,27 @@ 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; - } - - if (speciesComponent.CurrentDamageState is DeadState) - { - _isDead = true; - } - else - { - _isDead = false; - } + _isDead = eventArgs.Damageable.CurrentDamageState == DamageState.Dead || eventArgs.Damageable.CurrentDamageState == DamageState.Critical; } private void ReceivedAction() 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/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/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/Body/BodyCommands.cs b/Content.Server/Body/BodyCommands.cs new file mode 100644 index 0000000000..89d6355203 --- /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..51366583bd --- /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..675cc37e76 --- /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..ae1e17b49b --- /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 a6c5404f59..777bba1fe9 100644 --- a/Content.Server/Chat/ChatCommands.cs +++ b/Content.Server/Chat/ChatCommands.cs @@ -1,6 +1,5 @@ -using System; +using System; using System.Linq; -using Content.Server.GameObjects.Components.Damage; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Observer; @@ -15,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 { @@ -107,24 +107,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); } } @@ -135,7 +135,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? @@ -169,7 +169,7 @@ 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}; 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/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/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..ac2f427299 --- /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..877a0c2727 100644 --- a/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs +++ b/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs @@ -373,9 +373,9 @@ 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(); 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/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/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..034d92c9cf 100644 --- a/Content.Server/GameObjects/Components/Damage/DamageOnToolInteractComponent.cs +++ b/Content.Server/GameObjects/Components/Damage/DamageOnToolInteractComponent.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; 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 +9,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,7 +29,7 @@ namespace Content.Server.GameObjects.Components.Damage public override void Initialize() { base.Initialize(); - Owner.EnsureComponent(); + Owner.EnsureComponent(); } public bool InteractUsing(InteractUsingEventArgs eventArgs) @@ -40,12 +40,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 +55,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..b808bc6c76 100644 --- a/Content.Server/GameObjects/Components/Damage/DestructibleComponent.cs +++ b/Content.Server/GameObjects/Components/Damage/DestructibleComponent.cs @@ -1,55 +1,49 @@ -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; + + protected string _spawnOnDestroy; /// 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 => _spawnOnDestroy; - 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(ref _spawnOnDestroy, "spawnondestroy", string.Empty); } public override void Initialize() @@ -58,57 +52,18 @@ namespace Content.Server.GameObjects.Components.Damage _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..7137171f34 --- /dev/null +++ b/Content.Server/GameObjects/Components/Damage/RuinableComponent.cs @@ -0,0 +1,84 @@ +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; + +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 + /// . + /// + 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/DisposalHolderComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalHolderComponent.cs index f74eecb284..ee39a57d76 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalHolderComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalHolderComponent.cs @@ -1,7 +1,7 @@ #nullable enable 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; @@ -49,7 +49,7 @@ namespace Content.Server.GameObjects.Components.Disposal } return entity.HasComponent() || - entity.HasComponent(); + entity.HasComponent(); } public bool TryInsert(IEntity entity) diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalTubeComponent.cs index 29d5132925..37b2f2275e 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; diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs index 89a6049b66..3fd51adb20 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs @@ -5,10 +5,10 @@ using System.Linq; using System.Threading; 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; @@ -122,7 +122,7 @@ namespace Content.Server.GameObjects.Components.Disposal } if (!entity.HasComponent() && - !entity.HasComponent()) + !entity.HasComponent()) { return false; } diff --git a/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs b/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs index 7f4228b272..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; @@ -113,7 +114,7 @@ namespace Content.Server.GameObjects.Components.Doors // Disabled because it makes it suck hard to walk through double doors. - if (entity.HasComponent(typeof(SpeciesComponent))) + if (entity.HasComponent()) { if (!entity.TryGetComponent(out var mover)) return; @@ -237,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; @@ -247,7 +248,7 @@ 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; } 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/GUI/HandsComponent.cs b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs index 6336845970..572e504f53 100644 --- a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs @@ -8,11 +8,11 @@ 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.GameObjects.EntitySystems; -using Content.Shared.Health.BodySystem; using Content.Shared.Physics.Pull; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.Container; diff --git a/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs b/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs index 637933dd06..d47f94fd93 100644 --- a/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs +++ b/Content.Server/GameObjects/Components/Gravity/GravityGeneratorComponent.cs @@ -5,6 +5,7 @@ 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 +19,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; @@ -106,10 +107,8 @@ namespace Content.Server.GameObjects.Components.Gravity 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 +129,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/WelderComponent.cs b/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs index f85e7b64ea..3b96b57733 100644 --- a/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/WelderComponent.cs @@ -239,8 +239,9 @@ namespace Content.Server.GameObjects.Components.Interactable chat.EntityMe(victim, Loc.GetString("welds {0:their} every orifice closed! It looks like {0:theyre} trying to commit suicide!", victim)); //TODO: theyre macro 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/Storage/EntityStorageComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs index 73d1fd09ca..df502ee9aa 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs @@ -1,10 +1,12 @@ using System; using System.Linq; +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 +170,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)) 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/Kitchen/MicrowaveComponent.cs b/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs index 51b79141f9..672f09c14a 100644 --- a/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs +++ b/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs @@ -1,18 +1,20 @@ using System; using System.Collections.Generic; using System.Linq; +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 +25,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 { @@ -456,13 +456,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..4e36ffed71 100644 --- a/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs +++ b/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs @@ -1,7 +1,5 @@ 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.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Medical; @@ -15,7 +13,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 +34,21 @@ namespace Content.Server.GameObjects.Components.Medical public override void Initialize() { base.Initialize(); + _appearance = Owner.GetComponent(); _userInterface = Owner.GetComponent() .GetBoundUserInterface(MedicalScannerUiKey.Key); _bodyContainer = ContainerManagerComponent.Ensure($"{Name}-bodyContainer", Owner); _powerReceiver = Owner.GetComponent(); + UpdateUserInterface(); } private static readonly MedicalScannerBoundUserInterfaceState EmptyUIState = new MedicalScannerBoundUserInterfaceState( - 0, - 0, - null); + null, + new Dictionary(), + new Dictionary()); private MedicalScannerBoundUserInterfaceState GetUserInterfaceState() { @@ -58,49 +59,36 @@ 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); } 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)); } } @@ -109,7 +97,7 @@ namespace Content.Server.GameObjects.Components.Medical var body = _bodyContainer.ContainedEntity; return body == null ? MedicalScannerStatus.Open - : GetStatusFromDamageState(body.GetComponent().CurrentDamageState); + : GetStatusFromDamageState(body.GetComponent().CurrentDamageState); } private void UpdateAppearance() @@ -141,7 +129,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 +150,7 @@ namespace Content.Server.GameObjects.Components.Medical return; } - data.Text = "Eject"; + data.Text = Loc.GetString("Eject"); data.Visibility = component.IsOccupied ? VerbVisibility.Visible : VerbVisibility.Invisible; } @@ -195,6 +183,7 @@ namespace Content.Server.GameObjects.Components.Medical // There's no need to update if there's no one inside return; } + UpdateUserInterface(); UpdateAppearance(); } 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..913d571dd6 --- /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..ec174cca63 100644 --- a/Content.Server/GameObjects/Components/Mining/AsteroidRockComponent.cs +++ b/Content.Server/GameObjects/Components/Mining/AsteroidRockComponent.cs @@ -1,6 +1,6 @@ -using Content.Server.GameObjects.Components.Damage; -using Content.Server.GameObjects.Components.Weapon.Melee; +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; @@ -35,7 +35,7 @@ namespace Content.Server.GameObjects.Components.Mining 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 fe033e2231..fd2fed7f5f 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; @@ -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) { 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/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/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs index a8d60ea43d..a6d81eed88 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs @@ -1,11 +1,11 @@ using System; -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.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; @@ -76,7 +76,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece public bool InteractHand(InteractHandEventArgs eventArgs) { - if (!eventArgs.User.TryGetComponent(out DamageableComponent damageableComponent)) + if (!eventArgs.User.TryGetComponent(out IDamageableComponent damageableComponent)) { Eject(); return false; @@ -99,7 +99,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/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/Suspicion/SuspicionRoleComponent.cs b/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs index 833169a223..d982130afa 100644 --- a/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs +++ b/Content.Server/GameObjects/Components/Suspicion/SuspicionRoleComponent.cs @@ -1,5 +1,6 @@ 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; @@ -14,8 +15,8 @@ namespace Content.Server.GameObjects.Components.Suspicion public bool IsDead() { - return Owner.TryGetComponent(out SpeciesComponent species) && - species.CurrentDamageState is DeadState; + return Owner.TryGetComponent(out IDamageableComponent damageable) && + damageable.CurrentDamageState == DamageState.Dead; } public bool IsTraitor() 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/Ranged/Barrels/ServerBatteryBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs index bdb9b88761..f68aa0758c 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs @@ -4,7 +4,7 @@ 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; diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs index 4b0553fe2b..0d1188505b 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerRangedBarrelComponent.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Content.Server.GameObjects.Components.Damage; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Projectiles; using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition; @@ -15,7 +14,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 +26,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 { @@ -430,16 +429,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/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/DoAfter/DoAfter.cs b/Content.Server/GameObjects/EntitySystems/DoAfter/DoAfter.cs index 2a409d0bfe..3e00f334fd 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; @@ -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; } 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/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/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/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/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 a3d830412e..553df745a2 100644 --- a/Content.Server/GameTicking/GameRules/RuleSuspicion.cs +++ b/Content.Server/GameTicking/GameRules/RuleSuspicion.cs @@ -1,10 +1,10 @@ using System; using System.Threading; -using Content.Server.GameObjects.Components.Mobs; 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.IoC; @@ -30,6 +30,8 @@ namespace Content.Server.GameTicking.GameRules public override void Added() { + _chatManager.DispatchServerAnnouncement("There are traitors on the station! Find them, and kill them!"); + Timer.SpawnRepeating(DeadCheckDelay, _checkWinConditions, _checkTimerCancel.Token); } @@ -48,12 +50,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/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/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/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/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/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 004f5951ee..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; @@ -42,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/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.Shared/Atmos/Atmospherics.cs b/Content.Shared/Atmos/Atmospherics.cs index 2b1e2a5dc6..6872999f85 100644 --- a/Content.Shared/Atmos/Atmospherics.cs +++ b/Content.Shared/Atmos/Atmospherics.cs @@ -58,6 +58,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 /// 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/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..614bf90abd 100644 --- a/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs +++ b/Content.Shared/GameObjects/Components/Damage/DamageableComponent.cs @@ -1,40 +1,227 @@ -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). + /// + [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/Medical/SharedMedicalScannerComponent.cs b/Content.Shared/GameObjects/Components/Medical/SharedMedicalScannerComponent.cs index ee0a4f029a..8573a50c63 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,23 @@ 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 MedicalScannerBoundUserInterfaceState( - int currentHealth, - int maxHealth, - Dictionary damageDictionary) + EntityUid? entity, + Dictionary damageClasses, + Dictionary damageTypes) { - CurrentHealth = currentHealth; - MaxHealth = maxHealth; - DamageDictionary = damageDictionary; + Entity = entity; + DamageClasses = damageClasses; + DamageTypes = damageTypes; + } + + public bool HasDamage() + { + return DamageClasses.Count > 0 || DamageTypes.Count > 0; } } 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/SharedPlayerInputMoverComponent.cs b/Content.Shared/GameObjects/Components/Movement/SharedPlayerInputMoverComponent.cs index 9c74d666bc..6d47299c4d 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; @@ -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/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/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index 9ecd4a918b..337a543627 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; @@ -64,6 +64,7 @@ public const uint GAS_ANALYZER = 1057; public const uint DO_AFTER = 1058; public const uint RADIATION_PULSE = 1059; + public const uint BODY_MANAGER = 1060; // 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..c06963d53f 100644 --- a/Content.Shared/GameObjects/EntitySystems/ActionBlockerSystem.cs +++ b/Content.Shared/GameObjects/EntitySystems/ActionBlockerSystem.cs @@ -26,8 +26,11 @@ namespace Content.Shared.GameObjects.EntitySystems bool CanEmote() => true; bool CanAttack() => true; + bool CanEquip() => true; + bool CanUnequip() => true; + bool CanChangeDirection() => 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/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.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..ea45d9a588 100644 --- a/Resources/Groups/groups.yml +++ b/Resources/Groups/groups.yml @@ -95,6 +95,7 @@ - tubeconnections - tilewalls - events + - destroymechanism CanViewVar: true CanAdminPlace: true @@ -184,6 +185,7 @@ - setatmostemp - tilewalls - events + - destroymechanism CanViewVar: true CanAdminPlace: true CanScript: true 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..1a9307108f 100644 --- a/Resources/Prototypes/Entities/Constructible/Ground/pilot_chair.yml +++ b/Resources/Prototypes/Entities/Constructible/Ground/pilot_chair.yml @@ -15,7 +15,7 @@ - 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..568a1f958d 100644 --- a/Resources/Prototypes/Entities/Constructible/Ground/table.yml +++ b/Resources/Prototypes/Entities/Constructible/Ground/table.yml @@ -22,10 +22,9 @@ - type: IconSmooth key: generic base: solid_ - - type: Damageable - 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 +38,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/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/walls.yml b/Resources/Prototypes/Entities/Constructible/Walls/walls.yml index 7f82a01ce2..9e8d69252f 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: 100 + 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 + health: 100 + 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 + health: 100 + 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 + health: 100 + 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 + health: 100 + 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 + health: 100 + 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 + health: 100 + 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 + health: 100 + 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 + health: 100 + 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 + health: 100 + 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 + health: 100 + 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 + health: 100 + 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 + health: 300 + 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 + health: 100 + 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 + health: 100 + 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 + health: 100 + 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 + health: 100 + 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 + health: 100 + 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 + health: 100 + 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/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/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index ff041ce591..f048fefc25 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -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 diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 9259c28043..040ff8e983 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,17 +125,20 @@ 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: Teleportable @@ -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,14 @@ - SmallImpassable layer: - MobImpassable - - - type: Species - Template: Human - HeatResistance: 323 + - type: BodyManager + baseTemplate: bodyTemplate.Humanoid + basePreset: bodyPreset.BasicHuman + - type: MobStateManager - type: Damageable - - 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/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..e8b4a9dd96 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 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/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/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_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/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/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 14abc1df09..9a0e2799ef 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -52,6 +52,7 @@ True True True + True True True True @@ -59,6 +60,7 @@ True True True + True True True True @@ -68,9 +70,11 @@ True True True + True True True True + True True True True @@ -79,7 +83,7 @@ True True True + True True True True - \ No newline at end of file