Bodysystem and damagesystem rework (#1544)
* Things and stuff with grids, unfinished w/ code debug changes. * Updated submodule and also lost some progress cause I fucked it up xd * First unfinished draft of the BodySystem. Doesn't compile. * More changes to make it compile, but still just a framework. Doesn't do anything at the moment. * Many cleanup changes. * Revert "Merge branch 'master' of https://github.com/GlassEclipse/space-station-14 into body_system" This reverts commit ddd4aebbc76cf2a0b7b102f72b93d55a0816c88c, reversing changes made to 12d0dd752706bdda8879393bd8191a1199a0c978. * Commit human.yml * Updated a lot of things to be more classy, more progress overall, etc. etc. * Latest update with many changes * Minor changes * Fixed Travis build bug * Adds first draft of Body Scanner console, apparently I also forgot to tie Mechanisms into body parts so now a heart just sits in the Torso like a good boy :) * Commit rest of stuff * Latest changes * Latest changes again * 14 naked cowboys * Yay! * Latest changes (probably doesnt compile) * Surgery!!!!!!!!!~1116y * Cleaned some stuff up * More cleanup * Refactoring of code. Basic surgery path now done. * Removed readme, has been added to HackMD * Fixes typo (and thus test errors) * WIP changes, committing so I can pull latest master changes * Still working on that god awful merge * Latest changes * Latest changes!! * Beginning of refactor to BoundUserInterface * Surgery! * Latest changes - fixes pr change requests and random fixes * oops * Fixes bodypart recursion * Beginning of work on revamping the damage system. * More latest changes * Latest changes * Finished merge * Commit before removing old healthcode * Almost done with removing speciescomponent... * It compiles!!! * yahoo more work * Fixes to make it work * Merge conflict fixes * Deleting species visualizer was a mistake * IDE warnings are VERBOTEN * makes the server not kill itself on startup, some cleanup (#1) * Namespaces, comments and exception fixes * Fix conveyor and conveyor switch serialization SS14 in reactive when * Move damage, acts and body to shared Damage cleanup Comment cleanup * Rename SpeciesComponent to RotationComponent and cleanup Damage cleanup Comment cleanup * Fix nullable warnings * Address old reviews Fix off welder suicide damage type, deathmatch and suspicion * Fix new test fail with units being able to accept items when unpowered * Remove RotationComponent, change references to IBodyManagerComponent * Add a bloodstream to humans * More cleanups * Add body conduits, connections, connectors substances and valves * Revert "Add body conduits, connections, connectors substances and valves" This reverts commit 9ab0b50e6b15fe98852d7b0836c0cdbf4bd76d20. * Implement the heart mechanism behavior with the circulatory network * Added network property to mechanism behaviors * Changed human organ sprites and added missing ones * Fix tests * Add individual body part sprite rendering * Fix error where dropped mechanisms are not initialized * Implement client/server body damage * Make DamageContainer take care of raising events * Reimplement medical scanner with the new body system * Improve the medical scanner ui * Merge conflict fixes * Fix crash when colliding with something * Fix microwave suicides and eyes sprite rendering * Fix nullable reference error * Fix up surgery client side * Fix missing using from merge conflict * Add breathing *inhale * Merge conflict fixes * Fix accumulatedframetime being reset to 0 instead of decreased by the threshold https://github.com/space-wizards/space-station-14/pull/1617 * Use and add to the new AtmosHelpers * Fix feet * Add proper coloring to dropped body parts * Fix Urist's lungs being too strong * Merge conflict fixes * Merge conflict fixes * Merge conflict fixes Co-authored-by: GlassEclipse <tsymall5@gmail.com> Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com> Co-authored-by: AJCM-git <60196617+AJCM-git@users.noreply.github.com>
This commit is contained in:
@@ -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<DisposalUnitComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
using System.Collections.Generic;
|
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.Client.GameObjects.Components.UserInterface;
|
||||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Client.Health.BodySystem.BodyScanner
|
namespace Content.Client.GameObjects.Components.Body.Scanner
|
||||||
{
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
public class BodyScannerBoundUserInterface : BoundUserInterface
|
public class BodyScannerBoundUserInterface : BoundUserInterface
|
||||||
{
|
{
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
@@ -17,9 +19,7 @@ namespace Content.Client.Health.BodySystem.BodyScanner
|
|||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private Dictionary<string, BodyScannerBodyPartData> _parts;
|
private Dictionary<string, BodyScannerBodyPartData> _parts;
|
||||||
|
|
||||||
public BodyScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public BodyScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Open()
|
protected override void Open()
|
||||||
{
|
{
|
||||||
@@ -34,7 +34,9 @@ namespace Content.Client.Health.BodySystem.BodyScanner
|
|||||||
base.UpdateState(state);
|
base.UpdateState(state);
|
||||||
|
|
||||||
if (!(state is BodyScannerInterfaceState scannerState))
|
if (!(state is BodyScannerInterfaceState scannerState))
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_template = scannerState.Template;
|
_template = scannerState.Template;
|
||||||
_parts = scannerState.Parts;
|
_parts = scannerState.Parts;
|
||||||
@@ -45,7 +47,13 @@ namespace Content.Client.Health.BodySystem.BodyScanner
|
|||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
}
|
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_display.Dispose();
|
||||||
|
_template = null;
|
||||||
|
_parts.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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<string, BodyScannerBodyPartData> _parts;
|
||||||
|
|
||||||
|
private List<string> _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<string, BodyScannerBodyPartData> parts)
|
||||||
|
{
|
||||||
|
_template = template;
|
||||||
|
_parts = parts;
|
||||||
|
_slots = new List<string>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.Client.GameObjects.Components.UserInterface;
|
||||||
using Robust.Shared.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.
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
public class GenericSurgeryBoundUserInterface : BoundUserInterface
|
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()
|
protected override void Open()
|
||||||
{
|
{
|
||||||
_window = new GenericSurgeryWindow();
|
_window = new GenericSurgeryWindow();
|
||||||
|
|
||||||
_window.OpenCentered();
|
_window.OpenCentered();
|
||||||
|
_window.OnClose += Close;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||||
@@ -44,40 +45,42 @@ namespace Content.Client.Health.BodySystem.Surgery
|
|||||||
|
|
||||||
private void HandleBodyPartRequest(RequestBodyPartSurgeryUIMessage msg)
|
private void HandleBodyPartRequest(RequestBodyPartSurgeryUIMessage msg)
|
||||||
{
|
{
|
||||||
_window.BuildDisplay(msg.Targets, BodyPartSelectedCallback);
|
_window?.BuildDisplay(msg.Targets, BodyPartSelectedCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleMechanismRequest(RequestMechanismSurgeryUIMessage msg)
|
private void HandleMechanismRequest(RequestMechanismSurgeryUIMessage msg)
|
||||||
{
|
{
|
||||||
_window.BuildDisplay(msg.Targets, MechanismSelectedCallback);
|
_window?.BuildDisplay(msg.Targets, MechanismSelectedCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleBodyPartSlotRequest(RequestBodyPartSlotSurgeryUIMessage msg)
|
private void HandleBodyPartSlotRequest(RequestBodyPartSlotSurgeryUIMessage msg)
|
||||||
{
|
{
|
||||||
_window.BuildDisplay(msg.Targets, BodyPartSlotSelectedCallback);
|
_window?.BuildDisplay(msg.Targets, BodyPartSlotSelectedCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void BodyPartSelectedCallback(int selectedOptionData)
|
private void BodyPartSelectedCallback(int selectedOptionData)
|
||||||
{
|
{
|
||||||
SendMessage(new ReceiveBodyPartSurgeryUIMessage(selectedOptionData));
|
SendMessage(new ReceiveBodyPartSurgeryUIMessage(selectedOptionData));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MechanismSelectedCallback(int selectedOptionData)
|
private void MechanismSelectedCallback(int selectedOptionData)
|
||||||
{
|
{
|
||||||
SendMessage(new ReceiveMechanismSurgeryUIMessage(selectedOptionData));
|
SendMessage(new ReceiveMechanismSurgeryUIMessage(selectedOptionData));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BodyPartSlotSelectedCallback(int selectedOptionData)
|
private void BodyPartSlotSelectedCallback(int selectedOptionData)
|
||||||
{
|
{
|
||||||
SendMessage(new ReceiveBodyPartSlotSurgeryUIMessage(selectedOptionData));
|
SendMessage(new ReceiveBodyPartSlotSurgeryUIMessage(selectedOptionData));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
if (!disposing)
|
|
||||||
return;
|
if (disposing)
|
||||||
_window.Dispose();
|
{
|
||||||
|
_window?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,58 +1,62 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
namespace Content.Client.Health.BodySystem.Surgery
|
namespace Content.Client.GameObjects.Components.Body.Surgery
|
||||||
{
|
{
|
||||||
public class GenericSurgeryWindow : SS14Window
|
public class GenericSurgeryWindow : SS14Window
|
||||||
{
|
{
|
||||||
public delegate void CloseCallback();
|
|
||||||
public delegate void OptionSelectedCallback(int selectedOptionData);
|
public delegate void OptionSelectedCallback(int selectedOptionData);
|
||||||
|
|
||||||
private Control _vSplitContainer;
|
private readonly VBoxContainer _optionsBox;
|
||||||
private VBoxContainer _optionsBox;
|
|
||||||
private OptionSelectedCallback _optionSelectedCallback;
|
private OptionSelectedCallback _optionSelectedCallback;
|
||||||
|
|
||||||
|
|
||||||
protected override Vector2? CustomSize => (300, 400);
|
protected override Vector2? CustomSize => (300, 400);
|
||||||
|
|
||||||
public GenericSurgeryWindow()
|
public GenericSurgeryWindow()
|
||||||
{
|
{
|
||||||
Title = Loc.GetString("Select surgery target...");
|
Title = Loc.GetString("Select surgery target...");
|
||||||
RectClipContent = true;
|
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<string, int> data, OptionSelectedCallback callback)
|
public void BuildDisplay(Dictionary<string, int> data, OptionSelectedCallback callback)
|
||||||
{
|
{
|
||||||
_optionsBox.DisposeAllChildren();
|
_optionsBox.DisposeAllChildren();
|
||||||
_optionSelectedCallback = callback;
|
_optionSelectedCallback = callback;
|
||||||
|
|
||||||
foreach (var (displayText, callbackData) in data)
|
foreach (var (displayText, callbackData) in data)
|
||||||
{
|
{
|
||||||
var button = new SurgeryButton(callbackData);
|
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);
|
_optionsBox.AddChild(button);
|
||||||
}
|
}
|
||||||
@@ -60,17 +64,23 @@ namespace Content.Client.Health.BodySystem.Surgery
|
|||||||
|
|
||||||
private void OnButtonPressed(BaseButton.ButtonEventArgs args)
|
private void OnButtonPressed(BaseButton.ButtonEventArgs args)
|
||||||
{
|
{
|
||||||
var pressedButton = (SurgeryButton)args.Button.Parent;
|
if (args.Button.Parent is SurgeryButton surgery)
|
||||||
_optionSelectedCallback(pressedButton.CallbackData);
|
{
|
||||||
|
_optionSelectedCallback(surgery.CallbackData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SurgeryButton : PanelContainer
|
class SurgeryButton : PanelContainer
|
||||||
{
|
{
|
||||||
public Button Button { get; }
|
public Button Button { get; }
|
||||||
|
|
||||||
private SpriteView SpriteView { get; }
|
private SpriteView SpriteView { get; }
|
||||||
|
|
||||||
private Control EntityControl { get; }
|
private Control EntityControl { get; }
|
||||||
|
|
||||||
private Label DisplayText { get; }
|
private Label DisplayText { get; }
|
||||||
|
|
||||||
public int CallbackData { get; }
|
public int CallbackData { get; }
|
||||||
|
|
||||||
public SurgeryButton(int callbackData)
|
public SurgeryButton(int callbackData)
|
||||||
@@ -84,25 +94,28 @@ namespace Content.Client.Health.BodySystem.Surgery
|
|||||||
ToggleMode = true,
|
ToggleMode = true,
|
||||||
MouseFilter = MouseFilterMode.Stop
|
MouseFilter = MouseFilterMode.Stop
|
||||||
};
|
};
|
||||||
|
|
||||||
AddChild(Button);
|
AddChild(Button);
|
||||||
var hBoxContainer = new HBoxContainer();
|
|
||||||
SpriteView = new SpriteView
|
AddChild(new HBoxContainer
|
||||||
{
|
{
|
||||||
CustomMinimumSize = new Vector2(32.0f, 32.0f)
|
Children =
|
||||||
};
|
{
|
||||||
DisplayText = new Label
|
(SpriteView = new SpriteView
|
||||||
{
|
{
|
||||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
CustomMinimumSize = new Vector2(32.0f, 32.0f)
|
||||||
Text = "N/A",
|
}),
|
||||||
};
|
(DisplayText = new Label
|
||||||
hBoxContainer.AddChild(SpriteView);
|
{
|
||||||
hBoxContainer.AddChild(DisplayText);
|
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||||
EntityControl = new Control
|
Text = "N/A",
|
||||||
{
|
}),
|
||||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
(new Control
|
||||||
};
|
{
|
||||||
hBoxContainer.AddChild(EntityControl);
|
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||||
AddChild(hBoxContainer);
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetDisplayText(string text)
|
public void SetDisplayText(string text)
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Client.GameObjects.Components
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Fuck I really hate doing this
|
|
||||||
/// TODO: make sure the client only gets damageable component on the clientside entity for its player mob
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
|
||||||
public class DamageableComponent : SharedDamageableComponent
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string Name => "Damageable";
|
|
||||||
|
|
||||||
public Dictionary<DamageType, int> CurrentDamage = new Dictionary<DamageType, int>();
|
|
||||||
|
|
||||||
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
|
||||||
{
|
|
||||||
base.HandleComponentState(curState, nextState);
|
|
||||||
|
|
||||||
if(curState is DamageComponentState damagestate)
|
|
||||||
{
|
|
||||||
CurrentDamage = damagestate.CurrentDamage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.GameObjects.Components.UserInterface;
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent;
|
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent;
|
||||||
|
|
||||||
namespace Content.Client.GameObjects.Components.MedicalScanner
|
namespace Content.Client.GameObjects.Components.MedicalScanner
|
||||||
{
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
public class MedicalScannerBoundUserInterface : BoundUserInterface
|
public class MedicalScannerBoundUserInterface : BoundUserInterface
|
||||||
{
|
{
|
||||||
public MedicalScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public MedicalScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Content.Shared.Damage;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent;
|
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent;
|
||||||
|
|
||||||
@@ -14,22 +18,36 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
|
|||||||
{
|
{
|
||||||
Contents.RemoveAllChildren();
|
Contents.RemoveAllChildren();
|
||||||
var text = new StringBuilder();
|
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<IEntityManager>().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()});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ namespace Content.Client.GameObjects.Components.Mobs
|
|||||||
sprite.LayerSetColor(HumanoidVisualLayers.Hair, Appearance.HairColor);
|
sprite.LayerSetColor(HumanoidVisualLayers.Hair, Appearance.HairColor);
|
||||||
sprite.LayerSetColor(HumanoidVisualLayers.FacialHair, Appearance.FacialHairColor);
|
sprite.LayerSetColor(HumanoidVisualLayers.FacialHair, Appearance.FacialHairColor);
|
||||||
|
|
||||||
sprite.LayerSetState(HumanoidVisualLayers.Chest, Sex == Sex.Male ? "human_chest_m" : "human_chest_f");
|
sprite.LayerSetState(HumanoidVisualLayers.Chest, Sex == Sex.Male ? "torso_m" : "torso_f");
|
||||||
sprite.LayerSetState(HumanoidVisualLayers.Head, Sex == Sex.Male ? "human_head_m" : "human_head_f");
|
sprite.LayerSetState(HumanoidVisualLayers.Head, Sex == Sex.Male ? "head_m" : "head_f");
|
||||||
|
|
||||||
sprite.LayerSetVisible(HumanoidVisualLayers.StencilMask, Sex == Sex.Female);
|
sprite.LayerSetVisible(HumanoidVisualLayers.StencilMask, Sex == Sex.Female);
|
||||||
|
|
||||||
|
|||||||
@@ -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<DisposalUnitComponent>();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IClientDraggable.ClientCanDrag(CanDragEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Rotation;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.Animations;
|
using Robust.Client.Animations;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.GameObjects.Components.Animations;
|
using Robust.Client.GameObjects.Components.Animations;
|
||||||
@@ -7,22 +8,23 @@ using Robust.Client.Interfaces.GameObjects.Components;
|
|||||||
using Robust.Shared.Animations;
|
using Robust.Shared.Animations;
|
||||||
using Robust.Shared.Maths;
|
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)
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
{
|
{
|
||||||
base.OnChangeData(component);
|
base.OnChangeData(component);
|
||||||
|
|
||||||
if (component.TryGetData<SharedSpeciesComponent.MobState>(SharedSpeciesComponent.MobVisuals.RotationState, out var state))
|
if (component.TryGetData<RotationState>(RotationVisuals.RotationState, out var state))
|
||||||
{
|
{
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case SharedSpeciesComponent.MobState.Standing:
|
case RotationState.Vertical:
|
||||||
SetRotation(component, 0);
|
SetRotation(component, 0);
|
||||||
break;
|
break;
|
||||||
case SharedSpeciesComponent.MobState.Down:
|
case RotationState.Horizontal:
|
||||||
SetRotation(component, Angle.FromDegrees(90));
|
SetRotation(component, Angle.FromDegrees(90));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -40,7 +42,9 @@ namespace Content.Client.GameObjects.Components.Mobs
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (animation.HasRunningAnimation("rotate"))
|
if (animation.HasRunningAnimation("rotate"))
|
||||||
|
{
|
||||||
animation.Stop("rotate");
|
animation.Stop("rotate");
|
||||||
|
}
|
||||||
|
|
||||||
animation.Play(new Animation
|
animation.Play(new Animation
|
||||||
{
|
{
|
||||||
@@ -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<string, BodyScannerBodyPartData> _parts;
|
|
||||||
private List<string> _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<string, BodyScannerBodyPartData> parts)
|
|
||||||
{
|
|
||||||
_template = template;
|
|
||||||
_parts = parts;
|
|
||||||
_slots = new List<string>();
|
|
||||||
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("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -96,7 +96,6 @@
|
|||||||
"BarSign",
|
"BarSign",
|
||||||
"DroppedBodyPart",
|
"DroppedBodyPart",
|
||||||
"DroppedMechanism",
|
"DroppedMechanism",
|
||||||
"BodyManager",
|
|
||||||
"SolarPanel",
|
"SolarPanel",
|
||||||
"BodyScanner",
|
"BodyScanner",
|
||||||
"Stunbaton",
|
"Stunbaton",
|
||||||
@@ -157,6 +156,7 @@
|
|||||||
"Vapor",
|
"Vapor",
|
||||||
"DamageOnHighSpeedImpact",
|
"DamageOnHighSpeedImpact",
|
||||||
"Barotrauma",
|
"Barotrauma",
|
||||||
|
"MobStateManager",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Content.Server.AI.Operators;
|
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.Utility.BehaviorSets;
|
||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
using Content.Server.AI.WorldState.States.Utility;
|
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.AI.LoadBalancer;
|
||||||
using Content.Server.GameObjects.EntitySystems.JobQueues;
|
using Content.Server.GameObjects.EntitySystems.JobQueues;
|
||||||
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Robust.Server.AI;
|
using Robust.Server.AI;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
@@ -114,39 +113,27 @@ namespace Content.Server.AI.Utility.AiLogic
|
|||||||
_planCooldownRemaining = PlanCooldown;
|
_planCooldownRemaining = PlanCooldown;
|
||||||
_blackboard = new Blackboard(SelfEntity);
|
_blackboard = new Blackboard(SelfEntity);
|
||||||
_planner = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AiActionSystem>();
|
_planner = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AiActionSystem>();
|
||||||
if (SelfEntity.TryGetComponent(out DamageableComponent damageableComponent))
|
if (SelfEntity.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||||
{
|
{
|
||||||
damageableComponent.DamageThresholdPassed += DamageThresholdHandle;
|
damageableComponent.HealthChangedEvent += DeathHandle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Shutdown()
|
public override void Shutdown()
|
||||||
{
|
{
|
||||||
// TODO: If DamageableComponent removed still need to unsubscribe?
|
// 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();
|
var currentOp = CurrentAction?.ActionOperators.Peek();
|
||||||
currentOp?.Shutdown(Outcome.Failed);
|
currentOp?.Shutdown(Outcome.Failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DamageThresholdHandle(object sender, DamageThresholdPassedEventArgs eventArgs)
|
private void DeathHandle(HealthChangedEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (!SelfEntity.TryGetComponent(out SpeciesComponent speciesComponent))
|
_isDead = eventArgs.Damageable.CurrentDamageState == DamageState.Dead || eventArgs.Damageable.CurrentDamageState == DamageState.Critical;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (speciesComponent.CurrentDamageState is DeadState)
|
|
||||||
{
|
|
||||||
_isDead = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_isDead = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReceivedAction()
|
private void ReceivedAction()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
using Content.Server.AI.WorldState.States;
|
using Content.Server.AI.WorldState.States;
|
||||||
using Content.Server.GameObjects.Components.Damage;
|
using Content.Server.GameObjects.Components.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
@@ -11,13 +11,12 @@ namespace Content.Server.AI.Utility.Considerations.Combat
|
|||||||
{
|
{
|
||||||
var target = context.GetState<TargetEntityState>().GetValue();
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
|
||||||
if (target == null || !target.TryGetComponent(out DamageableComponent damageableComponent))
|
if (target == null || !target.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||||
{
|
{
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just went with max health
|
return damageableComponent.TotalDamage / 300.0f;
|
||||||
return damageableComponent.CurrentDamage[DamageType.Total] / 300.0f;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
using Content.Server.AI.WorldState.States;
|
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
|
namespace Content.Server.AI.Utility.Considerations.Combat
|
||||||
{
|
{
|
||||||
@@ -10,12 +10,12 @@ namespace Content.Server.AI.Utility.Considerations.Combat
|
|||||||
{
|
{
|
||||||
var target = context.GetState<TargetEntityState>().GetValue();
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
|
||||||
if (target == null || !target.TryGetComponent(out SpeciesComponent speciesComponent))
|
if (target == null || !target.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||||
{
|
{
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (speciesComponent.CurrentDamageState is CriticalState)
|
if (damageableComponent.CurrentDamageState == DamageState.Critical)
|
||||||
{
|
{
|
||||||
return 1.0f;
|
return 1.0f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
using Content.Server.AI.WorldState.States;
|
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
|
namespace Content.Server.AI.Utility.Considerations.Combat
|
||||||
{
|
{
|
||||||
@@ -10,12 +10,12 @@ namespace Content.Server.AI.Utility.Considerations.Combat
|
|||||||
{
|
{
|
||||||
var target = context.GetState<TargetEntityState>().GetValue();
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
|
||||||
if (target == null || !target.TryGetComponent(out SpeciesComponent speciesComponent))
|
if (target == null || !target.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||||
{
|
{
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (speciesComponent.CurrentDamageState is DeadState)
|
if (damageableComponent.CurrentDamageState == DamageState.Dead)
|
||||||
{
|
{
|
||||||
return 1.0f;
|
return 1.0f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.AI.Utility.Actions;
|
using Content.Server.AI.Utility.Actions;
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Melee;
|
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.Utils;
|
||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
using Content.Server.AI.WorldState.States;
|
using Content.Server.AI.WorldState.States;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
|
|||||||
throw new InvalidOperationException();
|
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))
|
controller.VisionRadius))
|
||||||
{
|
{
|
||||||
if (entity.HasComponent<BasicActorComponent>() && entity != owner)
|
if (entity.HasComponent<BasicActorComponent>() && entity != owner)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
|
|||||||
{
|
{
|
||||||
var owner = context.GetState<SelfState>().GetValue();
|
var owner = context.GetState<SelfState>().GetValue();
|
||||||
|
|
||||||
foreach (var entity in context.GetState<NearbySpeciesState>().GetValue())
|
foreach (var entity in context.GetState<NearbyBodiesState>().GetValue())
|
||||||
{
|
{
|
||||||
yield return new MeleeWeaponAttackEntity(owner, entity, Bonus);
|
yield return new MeleeWeaponAttackEntity(owner, entity, Bonus);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.AI.Utility.Actions;
|
using Content.Server.AI.Utility.Actions;
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Melee;
|
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.Utils;
|
||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
using Content.Server.AI.WorldState.States;
|
using Content.Server.AI.WorldState.States;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
|
|||||||
throw new InvalidOperationException();
|
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))
|
controller.VisionRadius))
|
||||||
{
|
{
|
||||||
if (entity.HasComponent<BasicActorComponent>() && entity != owner)
|
if (entity.HasComponent<BasicActorComponent>() && entity != owner)
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.AI.Utils;
|
using Content.Server.AI.Utils;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
namespace Content.Server.AI.WorldState.States.Mobs
|
namespace Content.Server.AI.WorldState.States.Mobs
|
||||||
{
|
{
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class NearbySpeciesState : CachedStateData<List<IEntity>>
|
public sealed class NearbyBodiesState : CachedStateData<List<IEntity>>
|
||||||
{
|
{
|
||||||
public override string Name => "NearbySpecies";
|
public override string Name => "NearbyBodies";
|
||||||
|
|
||||||
protected override List<IEntity> GetTrueValue()
|
protected override List<IEntity> GetTrueValue()
|
||||||
{
|
{
|
||||||
@@ -21,7 +21,7 @@ namespace Content.Server.AI.WorldState.States.Mobs
|
|||||||
return result;
|
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;
|
if (entity == Owner) continue;
|
||||||
result.Add(entity);
|
result.Add(entity);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Server.Interfaces.Player;
|
using Robust.Server.Interfaces.Player;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
@@ -27,7 +27,7 @@ namespace Content.Server.AI.WorldState.States.Mobs
|
|||||||
|
|
||||||
foreach (var player in nearbyPlayers)
|
foreach (var player in nearbyPlayers)
|
||||||
{
|
{
|
||||||
if (player.AttachedEntity != Owner && player.AttachedEntity.HasComponent<SpeciesComponent>())
|
if (player.AttachedEntity != Owner && player.AttachedEntity.HasComponent<IDamageableComponent>())
|
||||||
{
|
{
|
||||||
result.Add(player.AttachedEntity);
|
result.Add(player.AttachedEntity);
|
||||||
}
|
}
|
||||||
|
|||||||
147
Content.Server/Body/BodyCommands.cs
Normal file
147
Content.Server/Body/BodyCommands.cs
Normal file
@@ -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<IRobustRandom>();
|
||||||
|
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
|
||||||
|
|
||||||
|
shell.SendText(player, text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||||
|
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<IRobustRandom>();
|
||||||
|
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} <mechanism>";
|
||||||
|
|
||||||
|
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<IRobustRandom>();
|
||||||
|
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}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
602
Content.Server/Body/BodyPart.cs
Normal file
602
Content.Server/Body/BodyPart.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Data class representing a singular limb such as an arm or a leg.
|
||||||
|
/// Typically held within either a <see cref="BodyManagerComponent"/>,
|
||||||
|
/// which coordinates functions between BodyParts, or a
|
||||||
|
/// <see cref="DroppedBodyPartComponent"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class BodyPart
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The body that this body part is in, if any.
|
||||||
|
/// </summary>
|
||||||
|
private BodyManagerComponent? _body;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set of all <see cref="Mechanism"/> currently inside this
|
||||||
|
/// <see cref="BodyPart"/>.
|
||||||
|
/// To add and remove from this list see <see cref="AddMechanism"/> and
|
||||||
|
/// <see cref="RemoveMechanism"/>
|
||||||
|
/// </summary>
|
||||||
|
private readonly HashSet<Mechanism> _mechanisms = new HashSet<Mechanism>();
|
||||||
|
|
||||||
|
public BodyPart(BodyPartPrototype data)
|
||||||
|
{
|
||||||
|
SurgeryData = null!;
|
||||||
|
Properties = new HashSet<IExposeData>();
|
||||||
|
Name = null!;
|
||||||
|
Plural = null!;
|
||||||
|
RSIPath = null!;
|
||||||
|
RSIState = null!;
|
||||||
|
RSIMap = null!;
|
||||||
|
Damage = null!;
|
||||||
|
Resistances = null!;
|
||||||
|
|
||||||
|
LoadFromPrototype(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The body that this body part is in, if any.
|
||||||
|
/// </summary>
|
||||||
|
[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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="Surgery.SurgeryData"/> class currently representing this BodyPart's
|
||||||
|
/// surgery status.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables] private SurgeryData SurgeryData { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How much space is currently taken up by Mechanisms in this BodyPart.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables] private int SizeUsed { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of <see cref="IExposeData"/> properties, allowing for additional
|
||||||
|
/// data classes to be attached to a limb, such as a "length" class to an arm.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
private HashSet<IExposeData> Properties { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of this <see cref="BodyPart"/>, often displayed to the user.
|
||||||
|
/// For example, it could be named "advanced robotic arm".
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string Name { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plural version of this <see cref="BodyPart"/> name.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string Plural { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Path to the RSI that represents this <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string RSIPath { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RSI state that represents this <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string RSIState { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RSI map keys that this body part changes on the sprite.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public Enum? RSIMap { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RSI color of this body part.
|
||||||
|
/// </summary>
|
||||||
|
// TODO: SpriteComponent rework
|
||||||
|
public Color? RSIColor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="BodyPartType"/> that this <see cref="BodyPart"/> is considered
|
||||||
|
/// to be.
|
||||||
|
/// For example, <see cref="BodyPartType.Arm"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public BodyPartType PartType { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines many things: how many mechanisms can be fit inside this
|
||||||
|
/// <see cref="BodyPart"/>, whether a body can fit through tiny crevices, etc.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
private int Size { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Max HP of this <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public int MaxDurability { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current HP of this <see cref="BodyPart"/> based on sum of all damage types.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public int CurrentDurability => MaxDurability - Damage.TotalDamage;
|
||||||
|
|
||||||
|
// TODO: Individual body part damage
|
||||||
|
/// <summary>
|
||||||
|
/// Current damage dealt to this <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public DamageContainer Damage { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Armor of this <see cref="BodyPart"/> against damages.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public ResistanceSet Resistances { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// At what HP this <see cref="BodyPart"/> destroyed.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public int DestroyThreshold { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What types of BodyParts this <see cref="BodyPart"/> can easily attach to.
|
||||||
|
/// For the most part, most limbs aren't universal and require extra work to
|
||||||
|
/// attach between types.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public BodyPartCompatibility Compatibility { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set of all <see cref="Mechanism"/> currently inside this
|
||||||
|
/// <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public IReadOnlyCollection<Mechanism> Mechanisms => _mechanisms;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method is called by <see cref="BodyManagerComponent.Update"/>
|
||||||
|
/// before <see cref="MetabolismComponent.Update"/> is called.
|
||||||
|
/// </summary>
|
||||||
|
public void PreMetabolism(float frameTime)
|
||||||
|
{
|
||||||
|
foreach (var mechanism in Mechanisms)
|
||||||
|
{
|
||||||
|
mechanism.PreMetabolism(frameTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method is called by <see cref="BodyManagerComponent.Update"/>
|
||||||
|
/// after <see cref="MetabolismComponent.Update"/> is called.
|
||||||
|
/// </summary>
|
||||||
|
public void PostMetabolism(float frameTime)
|
||||||
|
{
|
||||||
|
foreach (var mechanism in Mechanisms)
|
||||||
|
{
|
||||||
|
mechanism.PreMetabolism(frameTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to add the given <see cref="BodyPartProperty"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// True if a <see cref="BodyPartProperty"/> of that type doesn't exist,
|
||||||
|
/// false otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public bool TryAddProperty(BodyPartProperty property)
|
||||||
|
{
|
||||||
|
if (HasProperty(property.GetType()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties.Add(property);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to retrieve the given <see cref="BodyPartProperty"/> type.
|
||||||
|
/// The resulting <see cref="BodyPartProperty"/> will be null if unsuccessful.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="property">The property if found, null otherwise.</param>
|
||||||
|
/// <typeparam name="T">The type of the property to find.</typeparam>
|
||||||
|
/// <returns>True if successful, false otherwise.</returns>
|
||||||
|
public bool TryGetProperty<T>(out T property)
|
||||||
|
{
|
||||||
|
property = (T) Properties.First(x => x.GetType() == typeof(T));
|
||||||
|
return property != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to retrieve the given <see cref="BodyPartProperty"/> type.
|
||||||
|
/// The resulting <see cref="BodyPartProperty"/> will be null if unsuccessful.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if successful, false otherwise.</returns>
|
||||||
|
public bool TryGetProperty(Type propertyType, out BodyPartProperty property)
|
||||||
|
{
|
||||||
|
property = (BodyPartProperty) Properties.First(x => x.GetType() == propertyType);
|
||||||
|
return property != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the given type <see cref="T"/> is on this <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">
|
||||||
|
/// The subtype of <see cref="BodyPartProperty"/> to look for.
|
||||||
|
/// </typeparam>
|
||||||
|
/// <returns>
|
||||||
|
/// True if this <see cref="BodyPart"/> has a property of type
|
||||||
|
/// <see cref="T"/>, false otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public bool HasProperty<T>() where T : BodyPartProperty
|
||||||
|
{
|
||||||
|
return Properties.Count(x => x.GetType() == typeof(T)) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a subtype of <see cref="BodyPartProperty"/> is on this
|
||||||
|
/// <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="propertyType">
|
||||||
|
/// The subtype of <see cref="BodyPartProperty"/> to look for.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if this <see cref="BodyPart"/> has a property of type
|
||||||
|
/// <see cref="propertyType"/>, false otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public bool HasProperty(Type propertyType)
|
||||||
|
{
|
||||||
|
return Properties.Count(x => x.GetType() == propertyType) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if another <see cref="BodyPart"/> can be connected to this one.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="toBeConnected">The part to connect.</param>
|
||||||
|
/// <returns>True if it can be connected, false otherwise.</returns>
|
||||||
|
public bool CanAttachBodyPart(BodyPart toBeConnected)
|
||||||
|
{
|
||||||
|
return SurgeryData.CanAttachBodyPart(toBeConnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a <see cref="Mechanism"/> can be installed on this
|
||||||
|
/// <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if it can be installed, false otherwise.</returns>
|
||||||
|
public bool CanInstallMechanism(Mechanism mechanism)
|
||||||
|
{
|
||||||
|
return SizeUsed + mechanism.Size <= Size &&
|
||||||
|
SurgeryData.CanInstallMechanism(mechanism);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to install a mechanism onto this body part.
|
||||||
|
/// Call <see cref="TryInstallDroppedMechanism"/> instead if you want to
|
||||||
|
/// easily install an <see cref="IEntity"/> with a
|
||||||
|
/// <see cref="DroppedMechanismComponent"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mechanism">The mechanism to try to install.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if successful, false if there was an error
|
||||||
|
/// (e.g. not enough room in <see cref="BodyPart"/>).
|
||||||
|
/// </returns>
|
||||||
|
private bool TryInstallMechanism(Mechanism mechanism)
|
||||||
|
{
|
||||||
|
if (!CanInstallMechanism(mechanism))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddMechanism(mechanism);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to install a <see cref="DroppedMechanismComponent"/> into this
|
||||||
|
/// <see cref="BodyPart"/>, potentially deleting the dropped
|
||||||
|
/// <see cref="IEntity"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="droppedMechanism">The mechanism to install.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if successful, false if there was an error
|
||||||
|
/// (e.g. not enough room in <see cref="BodyPart"/>).
|
||||||
|
/// </returns>
|
||||||
|
public bool TryInstallDroppedMechanism(DroppedMechanismComponent droppedMechanism)
|
||||||
|
{
|
||||||
|
if (!TryInstallMechanism(droppedMechanism.ContainedMechanism))
|
||||||
|
{
|
||||||
|
return false; //Installing the mechanism failed for some reason.
|
||||||
|
}
|
||||||
|
|
||||||
|
droppedMechanism.Owner.Delete();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to remove the given <see cref="Mechanism"/> reference from
|
||||||
|
/// this <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The newly spawned <see cref="DroppedMechanismComponent"/>, or null
|
||||||
|
/// if there was an error in spawning the entity or removing the mechanism.
|
||||||
|
/// </returns>
|
||||||
|
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<IEntityManager>();
|
||||||
|
var position = dropLocation.Transform.GridPosition;
|
||||||
|
var mechanismEntity = entityManager.SpawnEntity("BaseDroppedMechanism", position);
|
||||||
|
|
||||||
|
dropped = mechanismEntity.GetComponent<DroppedMechanismComponent>();
|
||||||
|
dropped.InitializeDroppedMechanism(mechanismTarget);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to destroy the given <see cref="Mechanism"/> in this
|
||||||
|
/// <see cref="BodyPart"/>. Does NOT spawn a dropped entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to destroy the given <see cref="Mechanism"/> in this
|
||||||
|
/// <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mechanismTarget">The mechanism to destroy.</param>
|
||||||
|
/// <returns>True if successful, false otherwise.</returns>
|
||||||
|
public bool DestroyMechanism(Mechanism mechanismTarget)
|
||||||
|
{
|
||||||
|
if (!RemoveMechanism(mechanismTarget))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the given <see cref="SurgeryType"/> can be used on
|
||||||
|
/// the current state of this <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if it can be used, false otherwise.</returns>
|
||||||
|
public bool SurgeryCheck(SurgeryType toolType)
|
||||||
|
{
|
||||||
|
return SurgeryData.CheckSurgery(toolType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to perform surgery on this <see cref="BodyPart"/> with the given
|
||||||
|
/// tool.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if successful, false if there was an error.</returns>
|
||||||
|
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<IReflectionManager>().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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to remove the given <see cref="mechanism"/> from this
|
||||||
|
/// <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mechanism">The mechanism to remove.</param>
|
||||||
|
/// <returns>True if it was removed, false otherwise.</returns>
|
||||||
|
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<IReflectionManager>().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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the given <see cref="BodyPartPrototype"/>.
|
||||||
|
/// Current data on this <see cref="BodyPart"/> will be overwritten!
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void LoadFromPrototype(BodyPartPrototype data)
|
||||||
|
{
|
||||||
|
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||||
|
|
||||||
|
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<IDynamicTypeFactory>().CreateInstance<SurgeryData>(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<HealthChangeData> changes)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SpawnDropped([NotNullWhen(true)] out IEntity dropped)
|
||||||
|
{
|
||||||
|
dropped = default!;
|
||||||
|
|
||||||
|
if (Body == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dropped = IoCManager.Resolve<IEntityManager>().SpawnEntity("BaseDroppedBodyPart", Body.Owner.Transform.GridPosition);
|
||||||
|
|
||||||
|
dropped.GetComponent<DroppedBodyPartComponent>().TransferBodyPartData(this);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Content.Server/Body/BodyPreset.cs
Normal file
36
Content.Server/Body/BodyPreset.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Stores data on what <see cref="BodyPartPrototype"></see> should
|
||||||
|
/// fill a BodyTemplate.
|
||||||
|
/// Used for loading complete body presets, like a "basic human" with all
|
||||||
|
/// human limbs.
|
||||||
|
/// </summary>
|
||||||
|
public class BodyPreset
|
||||||
|
{
|
||||||
|
public BodyPreset(BodyPresetPrototype data)
|
||||||
|
{
|
||||||
|
LoadFromPrototype(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ViewVariables] public string Name { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps a template slot to the ID of the <see cref="BodyPart"/> that should
|
||||||
|
/// fill it. E.g. "right arm" : "BodyPart.arm.basic_human".
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public Dictionary<string, string> PartIDs { get; private set; }
|
||||||
|
|
||||||
|
protected virtual void LoadFromPrototype(BodyPresetPrototype data)
|
||||||
|
{
|
||||||
|
Name = data.Name;
|
||||||
|
PartIDs = data.PartIDs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
145
Content.Server/Body/BodyTemplate.cs
Normal file
145
Content.Server/Body/BodyTemplate.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is a data capsule representing the standard format of a
|
||||||
|
/// <see cref="BodyManagerComponent"/>.
|
||||||
|
/// For instance, the "humanoid" BodyTemplate defines two arms, each connected to
|
||||||
|
/// a torso and so on.
|
||||||
|
/// Capable of loading data from a <see cref="BodyTemplatePrototype"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class BodyTemplate
|
||||||
|
{
|
||||||
|
public BodyTemplate()
|
||||||
|
{
|
||||||
|
Name = "empty";
|
||||||
|
CenterSlot = "";
|
||||||
|
Slots = new Dictionary<string, BodyPartType>();
|
||||||
|
Connections = new Dictionary<string, List<string>>();
|
||||||
|
Layers = new Dictionary<string, string>();
|
||||||
|
MechanismLayers = new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BodyTemplate(BodyTemplatePrototype data)
|
||||||
|
{
|
||||||
|
LoadFromPrototype(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ViewVariables] public string Name { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the center BodyPart. For humans, this is set to "torso".
|
||||||
|
/// Used in many calculations.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string CenterSlot { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps all parts on this template to its BodyPartType.
|
||||||
|
/// For instance, "right arm" is mapped to "BodyPartType.arm" on the humanoid
|
||||||
|
/// template.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public Dictionary<string, BodyPartType> Slots { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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".
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public Dictionary<string, List<string>> Connections { get; private set; }
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public Dictionary<string, string> Layers { get; private set; }
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public Dictionary<string, string> MechanismLayers { get; private set; }
|
||||||
|
|
||||||
|
public bool Equals(BodyTemplate other)
|
||||||
|
{
|
||||||
|
return GetHashCode() == other.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the given slot exists in this <see cref="BodyTemplate"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if it does, false otherwise.</returns>
|
||||||
|
public bool SlotExists(string slotName)
|
||||||
|
{
|
||||||
|
return Slots.Keys.Any(slot => slot == slotName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the hash code for this instance of <see cref="BodyTemplate"/>.
|
||||||
|
/// It does not matter in which order the Connections or Slots are defined.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// An integer unique to this <see cref="BodyTemplate"/>'s layout.
|
||||||
|
/// </returns>
|
||||||
|
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<int>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Content.Server/Body/IBodyPartContainer.cs
Normal file
19
Content.Server/Body/IBodyPartContainer.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Content.Server.Body.Surgery;
|
||||||
|
using Content.Server.GameObjects.Components.Body;
|
||||||
|
|
||||||
|
namespace Content.Server.Body
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Making a class inherit from this interface allows you to do many things with
|
||||||
|
/// it in the <see cref="SurgeryData"/> class.
|
||||||
|
/// This includes passing it as an argument to a
|
||||||
|
/// <see cref="SurgeryData.SurgeryAction"/> delegate, as to later typecast it back
|
||||||
|
/// to the original class type.
|
||||||
|
/// Every BodyPart also needs an <see cref="IBodyPartContainer"/> to be its parent
|
||||||
|
/// (i.e. the <see cref="BodyManagerComponent"/> holds many <see cref="BodyPart"/>,
|
||||||
|
/// each of which have an upward reference to it).
|
||||||
|
/// </summary>
|
||||||
|
public interface IBodyPartContainer
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Content.Server.Body.Mechanisms.Behaviors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The behaviors of a brain, inhabitable by a player.
|
||||||
|
/// </summary>
|
||||||
|
public class BrainBehavior : MechanismBehavior
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
38
Content.Server/Body/Mechanisms/Behaviors/HeartBehavior.cs
Normal file
38
Content.Server/Body/Mechanisms/Behaviors/HeartBehavior.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Content.Server/Body/Mechanisms/Behaviors/LungBehavior.cs
Normal file
27
Content.Server/Body/Mechanisms/Behaviors/LungBehavior.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
185
Content.Server/Body/Mechanisms/Behaviors/MechanismBehavior.cs
Normal file
185
Content.Server/Body/Mechanisms/Behaviors/MechanismBehavior.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The behaviors a mechanism performs.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class MechanismBehavior
|
||||||
|
{
|
||||||
|
private bool Initialized { get; set; }
|
||||||
|
|
||||||
|
private bool Removed { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The network, if any, that this behavior forms when its mechanism is
|
||||||
|
/// added and destroys when its mechanism is removed.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual Type? Network { get; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Upward reference to the parent <see cref="Mechanisms.Mechanism"/> that this
|
||||||
|
/// behavior is attached to.
|
||||||
|
/// </summary>
|
||||||
|
protected Mechanism Mechanism { get; private set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called by a <see cref="Mechanism"/> to initialize this behavior.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mechanism">The mechanism that owns this behavior.</param>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// If the mechanism has already been initialized.
|
||||||
|
/// </exception>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when a behavior is removed from a <see cref="Mechanism"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void Remove()
|
||||||
|
{
|
||||||
|
OnRemove();
|
||||||
|
TryRemoveNetwork(Mechanism.Body);
|
||||||
|
|
||||||
|
Mechanism = null!;
|
||||||
|
Removed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the containing <see cref="BodyPart"/> is attached to a
|
||||||
|
/// <see cref="BodyManagerComponent"/>.
|
||||||
|
/// For instance, attaching a head to a body will call this on the brain inside.
|
||||||
|
/// </summary>
|
||||||
|
public void InstalledIntoBody()
|
||||||
|
{
|
||||||
|
TryAddNetwork();
|
||||||
|
OnInstalledIntoBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is
|
||||||
|
/// installed into a <see cref="BodyPart"/>.
|
||||||
|
/// For instance, putting a brain into an empty head.
|
||||||
|
/// </summary>
|
||||||
|
public void InstalledIntoPart()
|
||||||
|
{
|
||||||
|
TryAddNetwork();
|
||||||
|
OnInstalledIntoPart();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the containing <see cref="BodyPart"/> is removed from a
|
||||||
|
/// <see cref="BodyManagerComponent"/>.
|
||||||
|
/// For instance, cutting off ones head will call this on the brain inside.
|
||||||
|
/// </summary>
|
||||||
|
public void RemovedFromBody(BodyManagerComponent old)
|
||||||
|
{
|
||||||
|
OnRemovedFromBody(old);
|
||||||
|
TryRemoveNetwork(old);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is removed from a
|
||||||
|
/// <see cref="BodyPart"/>.
|
||||||
|
/// For instance, taking a brain out of ones head.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called by <see cref="Initialize"/> when this behavior is first initialized.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void Initialize() { }
|
||||||
|
|
||||||
|
protected virtual void OnRemove() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the containing <see cref="BodyPart"/> is attached to a
|
||||||
|
/// <see cref="BodyManagerComponent"/>.
|
||||||
|
/// For instance, attaching a head to a body will call this on the brain inside.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnInstalledIntoBody() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is
|
||||||
|
/// installed into a <see cref="BodyPart"/>.
|
||||||
|
/// For instance, putting a brain into an empty head.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnInstalledIntoPart() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the containing <see cref="BodyPart"/> is removed from a
|
||||||
|
/// <see cref="BodyManagerComponent"/>.
|
||||||
|
/// For instance, cutting off ones head will call this on the brain inside.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnRemovedFromBody(BodyManagerComponent old) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is removed from a
|
||||||
|
/// <see cref="BodyPart"/>.
|
||||||
|
/// For instance, taking a brain out of ones head.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnRemovedFromPart(BodyPart old) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called every update when this behavior is connected to a
|
||||||
|
/// <see cref="BodyManagerComponent"/>, but not while in a
|
||||||
|
/// <see cref="DroppedMechanismComponent"/> or
|
||||||
|
/// <see cref="DroppedBodyPartComponent"/>,
|
||||||
|
/// before <see cref="MetabolismComponent.Update"/> is called.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void PreMetabolism(float frameTime) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called every update when this behavior is connected to a
|
||||||
|
/// <see cref="BodyManagerComponent"/>, but not while in a
|
||||||
|
/// <see cref="DroppedMechanismComponent"/> or
|
||||||
|
/// <see cref="DroppedBodyPartComponent"/>,
|
||||||
|
/// after <see cref="MetabolismComponent.Update"/> is called.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void PostMetabolism(float frameTime) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Content.Server/Body/Mechanisms/Behaviors/StomachBehavior.cs
Normal file
36
Content.Server/Body/Mechanisms/Behaviors/StomachBehavior.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
249
Content.Server/Body/Mechanisms/Mechanism.cs
Normal file
249
Content.Server/Body/Mechanisms/Mechanism.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Data class representing a persistent item inside a <see cref="BodyPart"/>.
|
||||||
|
/// This includes livers, eyes, cameras, brains, explosive implants,
|
||||||
|
/// binary communicators, and other things.
|
||||||
|
/// </summary>
|
||||||
|
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<MechanismBehavior>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[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; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Professional description of the <see cref="Mechanism"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The message to display upon examining a mob with this Mechanism installed.
|
||||||
|
/// If the string is empty (""), no message will be displayed.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string ExamineMessage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Path to the RSI that represents this <see cref="Mechanism"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string RSIPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RSI state that represents this <see cref="Mechanism"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string RSIState { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Max HP of this <see cref="Mechanism"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public int MaxDurability { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current HP of this <see cref="Mechanism"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public int CurrentDurability { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// At what HP this <see cref="Mechanism"/> is completely destroyed.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public int DestroyThreshold { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Armor of this <see cref="Mechanism"/> against attacks.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public int Resistance { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines a handful of things - mostly whether this
|
||||||
|
/// <see cref="Mechanism"/> can fit into a <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public int Size { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What kind of <see cref="BodyPart"/> this <see cref="Mechanism"/> can be
|
||||||
|
/// easily installed into.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public BodyPartCompatibility Compatibility { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The behaviors that this <see cref="Mechanism"/> performs.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
private List<MechanismBehavior> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the given <see cref="MechanismPrototype"/>.
|
||||||
|
/// Current data on this <see cref="Mechanism"/> will be overwritten!
|
||||||
|
/// </summary>
|
||||||
|
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<IDynamicTypeFactory>().CreateInstance<MechanismBehavior>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method is called by <see cref="BodyPart.PreMetabolism"/> before
|
||||||
|
/// <see cref="MetabolismComponent.Update"/> is called.
|
||||||
|
/// </summary>
|
||||||
|
public void PreMetabolism(float frameTime)
|
||||||
|
{
|
||||||
|
foreach (var behavior in Behaviors)
|
||||||
|
{
|
||||||
|
behavior.PreMetabolism(frameTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method is called by <see cref="BodyPart.PostMetabolism"/> after
|
||||||
|
/// <see cref="MetabolismComponent.Update"/> is called.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
76
Content.Server/Body/Network/BodyNetwork.cs
Normal file
76
Content.Server/Body/Network/BodyNetwork.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a "network" such as a bloodstream or electrical power that
|
||||||
|
/// is coordinated throughout an entire <see cref="BodyManagerComponent"/>.
|
||||||
|
/// </summary>
|
||||||
|
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() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called every update by <see cref="BodyManagerComponent.Update"/>.
|
||||||
|
/// </summary>
|
||||||
|
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<T>(this IEntity entity) where T : BodyNetwork
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out BodyManagerComponent body))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.EnsureNetwork<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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<T>(this IEntity entity, out T network) where T : BodyNetwork
|
||||||
|
{
|
||||||
|
entity.TryGetBodyNetwork(typeof(T), out var unCastNetwork);
|
||||||
|
network = (T) unCastNetwork;
|
||||||
|
return network != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
88
Content.Server/Body/Network/BodyNetworkFactory.cs
Normal file
88
Content.Server/Body/Network/BodyNetworkFactory.cs
Normal file
@@ -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!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mapping of body network names to their types.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Dictionary<string, Type> _names = new Dictionary<string, Type>();
|
||||||
|
|
||||||
|
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<BodyNetwork>(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<BodyNetwork>(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BodyNetwork GetNetwork(Type type)
|
||||||
|
{
|
||||||
|
if (!_names.ContainsValue(type))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"{type} is not registered.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _typeFactory.CreateInstance<BodyNetwork>(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Content.Server/Body/Network/CirculatoryNetwork.cs
Normal file
25
Content.Server/Body/Network/CirculatoryNetwork.cs
Normal file
@@ -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<BloodstreamComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRemove()
|
||||||
|
{
|
||||||
|
if (Owner.HasComponent<BloodstreamComponent>())
|
||||||
|
{
|
||||||
|
Owner.RemoveComponent<BloodstreamComponent>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Content.Server/Body/Network/DigestiveNetwork.cs
Normal file
28
Content.Server/Body/Network/DigestiveNetwork.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Content.Server.GameObjects.Components.Body.Digestive;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Network
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the system that processes food, liquids, and the reagents inside them.
|
||||||
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class DigestiveNetwork : BodyNetwork
|
||||||
|
{
|
||||||
|
public override string Name => "Digestive";
|
||||||
|
|
||||||
|
protected override void OnAdd()
|
||||||
|
{
|
||||||
|
Owner.EnsureComponent<StomachComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRemove()
|
||||||
|
{
|
||||||
|
if (Owner.HasComponent<StomachComponent>())
|
||||||
|
{
|
||||||
|
Owner.RemoveComponent<StomachComponent>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Content.Server/Body/Network/IBodyNetworkFactory.cs
Normal file
13
Content.Server/Body/Network/IBodyNetworkFactory.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Network
|
||||||
|
{
|
||||||
|
public interface IBodyNetworkFactory
|
||||||
|
{
|
||||||
|
void DoAutoRegistrations();
|
||||||
|
|
||||||
|
BodyNetwork GetNetwork(string name);
|
||||||
|
|
||||||
|
BodyNetwork GetNetwork(Type type);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Content.Server/Body/Network/RespiratoryNetwork.cs
Normal file
25
Content.Server/Body/Network/RespiratoryNetwork.cs
Normal file
@@ -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<LungComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRemove()
|
||||||
|
{
|
||||||
|
if (Owner.HasComponent<LungComponent>())
|
||||||
|
{
|
||||||
|
Owner.RemoveComponent<LungComponent>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
250
Content.Server/Body/Surgery/BiologicalSurgeryData.cs
Normal file
250
Content.Server/Body/Surgery/BiologicalSurgeryData.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Data class representing the surgery state of a biological entity.
|
||||||
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class BiologicalSurgeryData : SurgeryData
|
||||||
|
{
|
||||||
|
private readonly List<Mechanism> _disconnectedOrgans = new List<Mechanism>();
|
||||||
|
|
||||||
|
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<Mechanism>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Content.Server/Body/Surgery/ISurgeon.cs
Normal file
34
Content.Server/Body/Surgery/ISurgeon.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface representing an entity capable of performing surgery (performing operations on an
|
||||||
|
/// <see cref="SurgeryData"/> class).
|
||||||
|
/// For an example see <see cref="SurgeryToolComponent"/>, which inherits from this class.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISurgeon
|
||||||
|
{
|
||||||
|
public delegate void MechanismRequestCallback(
|
||||||
|
Mechanism target,
|
||||||
|
IBodyPartContainer container,
|
||||||
|
ISurgeon surgeon,
|
||||||
|
IEntity performer);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long it takes to perform a single surgery step (in seconds).
|
||||||
|
/// </summary>
|
||||||
|
public float BaseOperationTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When performing a surgery, the <see cref="SurgeryData"/> 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.
|
||||||
|
/// </summary>
|
||||||
|
public void RequestMechanism(IEnumerable<Mechanism> options, MechanismRequestCallback callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
91
Content.Server/Body/Surgery/SurgeryData.cs
Normal file
91
Content.Server/Body/Surgery/SurgeryData.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This data class represents the state of a <see cref="BodyPart"/> in regards to everything surgery related -
|
||||||
|
/// whether there's an incision on it, whether the bone is broken, etc.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class SurgeryData
|
||||||
|
{
|
||||||
|
protected delegate void SurgeryAction(IBodyPartContainer container, ISurgeon surgeon, IEntity performer);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="BodyPart"/> this surgeryData is attached to.
|
||||||
|
/// The <see cref="SurgeryData"/> class should not exist without a
|
||||||
|
/// <see cref="BodyPart"/> that it represents, and will throw errors if it
|
||||||
|
/// is null.
|
||||||
|
/// </summary>
|
||||||
|
protected readonly BodyPart Parent;
|
||||||
|
|
||||||
|
protected SurgeryData(BodyPart parent)
|
||||||
|
{
|
||||||
|
Parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="BodyPartType"/> of the parent <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected BodyPartType ParentType => Parent.PartType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the description of this current <see cref="BodyPart"/> to be shown
|
||||||
|
/// upon observing the given entity.
|
||||||
|
/// </summary>
|
||||||
|
public abstract string GetDescription(IEntity target);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether a <see cref="Mechanism"/> can be installed into the
|
||||||
|
/// <see cref="BodyPart"/> this <see cref="SurgeryData"/> represents.
|
||||||
|
/// </summary>
|
||||||
|
public abstract bool CanInstallMechanism(Mechanism mechanism);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether the given <see cref="BodyPart"/> can be connected to the
|
||||||
|
/// <see cref="BodyPart"/> this <see cref="SurgeryData"/> represents.
|
||||||
|
/// </summary>
|
||||||
|
public abstract bool CanAttachBodyPart(BodyPart part);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the delegate corresponding to the surgery step using the given
|
||||||
|
/// <see cref="SurgeryType"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The corresponding surgery action or null if no step can be performed.
|
||||||
|
/// </returns>
|
||||||
|
protected abstract SurgeryAction? GetSurgeryStep(SurgeryType toolType);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether the given <see cref="SurgeryType"/> can be used to perform a surgery on the BodyPart this
|
||||||
|
/// <see cref="SurgeryData"/> represents.
|
||||||
|
/// </summary>
|
||||||
|
public bool CheckSurgery(SurgeryType toolType)
|
||||||
|
{
|
||||||
|
return GetSurgeryStep(toolType) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to perform surgery of the given <see cref="SurgeryType"/>. Returns whether the operation was successful.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="surgeryType">The <see cref="SurgeryType"/> used for this surgery.</param>
|
||||||
|
/// <param name="container">The container where the surgery is being done.</param>
|
||||||
|
/// <param name="surgeon">The entity being used to perform the surgery.</param>
|
||||||
|
/// <param name="performer">The entity performing the surgery.</param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.GameObjects.Components.Damage;
|
|
||||||
using Content.Server.GameObjects.Components.GUI;
|
using Content.Server.GameObjects.Components.GUI;
|
||||||
using Content.Server.GameObjects.Components.Items.Storage;
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
using Content.Server.GameObjects.Components.Observer;
|
using Content.Server.GameObjects.Components.Observer;
|
||||||
@@ -15,6 +14,7 @@ using Robust.Shared.Enums;
|
|||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
|
using Content.Shared.Damage;
|
||||||
|
|
||||||
namespace Content.Server.Chat
|
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" +
|
"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.";
|
"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);
|
SuicideKind kind = suicide.Suicide(target, chat);
|
||||||
if (kind != SuicideKind.Special)
|
if (kind != SuicideKind.Special)
|
||||||
{
|
{
|
||||||
damageableComponent.TakeDamage(kind switch
|
damageableComponent.ChangeDamage(kind switch
|
||||||
{
|
{
|
||||||
SuicideKind.Brute => DamageType.Brute,
|
SuicideKind.Blunt => DamageType.Blunt,
|
||||||
SuicideKind.Heat => DamageType.Heat,
|
SuicideKind.Piercing => DamageType.Piercing,
|
||||||
SuicideKind.Cold => DamageType.Cold,
|
SuicideKind.Heat => DamageType.Heat,
|
||||||
SuicideKind.Acid => DamageType.Acid,
|
SuicideKind.Disintegration => DamageType.Disintegration,
|
||||||
SuicideKind.Toxic => DamageType.Toxic,
|
SuicideKind.Cellular => DamageType.Cellular,
|
||||||
SuicideKind.Electric => DamageType.Electric,
|
SuicideKind.DNA => DamageType.DNA,
|
||||||
_ => DamageType.Brute
|
SuicideKind.Asphyxiation => DamageType.Asphyxiation,
|
||||||
},
|
_ => DamageType.Blunt
|
||||||
500, //TODO: needs to be a max damage of some sorts
|
},
|
||||||
source,
|
500,
|
||||||
target);
|
true, source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ namespace Content.Server.Chat
|
|||||||
|
|
||||||
var chat = IoCManager.Resolve<IChatManager>();
|
var chat = IoCManager.Resolve<IChatManager>();
|
||||||
var owner = player.ContentData().Mind.OwnedMob.Owner;
|
var owner = player.ContentData().Mind.OwnedMob.Owner;
|
||||||
var dmgComponent = owner.GetComponent<DamageableComponent>();
|
var dmgComponent = owner.GetComponent<IDamageableComponent>();
|
||||||
//TODO: needs to check if the mob is actually alive
|
//TODO: needs to check if the mob is actually alive
|
||||||
//TODO: maybe set a suicided flag to prevent ressurection?
|
//TODO: maybe set a suicided flag to prevent ressurection?
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ namespace Content.Server.Chat
|
|||||||
}
|
}
|
||||||
// Default suicide, bite your tongue
|
// 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
|
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.
|
// Prevent the player from returning to the body. Yes, this is an ugly hack.
|
||||||
var ghost = new Ghost(){CanReturn = false};
|
var ghost = new Ghost(){CanReturn = false};
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using Content.Server.AI.Utility.Considerations;
|
using Content.Server.AI.Utility.Considerations;
|
||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
|
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
|
||||||
using Content.Server.Interfaces;
|
using Content.Server.Interfaces;
|
||||||
using Content.Server.Interfaces.Chat;
|
using Content.Server.Interfaces.Chat;
|
||||||
|
using Content.Server.Body.Network;
|
||||||
using Content.Server.Interfaces.GameTicking;
|
using Content.Server.Interfaces.GameTicking;
|
||||||
using Content.Server.Interfaces.PDA;
|
using Content.Server.Interfaces.PDA;
|
||||||
using Content.Server.Sandbox;
|
using Content.Server.Sandbox;
|
||||||
@@ -46,6 +47,8 @@ namespace Content.Server
|
|||||||
|
|
||||||
IoCManager.BuildGraph();
|
IoCManager.BuildGraph();
|
||||||
|
|
||||||
|
IoCManager.Resolve<IBodyNetworkFactory>().DoAutoRegistrations();
|
||||||
|
|
||||||
_gameTicker = IoCManager.Resolve<IGameTicker>();
|
_gameTicker = IoCManager.Resolve<IGameTicker>();
|
||||||
|
|
||||||
IoCManager.Resolve<IServerNotifyManager>().Initialize();
|
IoCManager.Resolve<IServerNotifyManager>().Initialize();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Content.Shared.Maps;
|
using Content.Shared.Maps;
|
||||||
using Robust.Server.GameObjects.EntitySystems;
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
using Robust.Server.Interfaces.GameObjects;
|
using Robust.Server.Interfaces.GameObjects;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Content.Server.GameObjects.Components.Damage;
|
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
using Content.Server.Interfaces.GameObjects;
|
using Content.Server.Interfaces.GameObjects;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.GameObjects.Systems;
|
using Robust.Shared.GameObjects.Systems;
|
||||||
@@ -23,7 +23,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Update(float frameTime)
|
public void Update(float frameTime)
|
||||||
{
|
{
|
||||||
if (!Owner.TryGetComponent(out DamageableComponent damageable)) return;
|
if (!Owner.TryGetComponent(out IDamageableComponent damageable)) return;
|
||||||
Owner.TryGetComponent(out ServerStatusEffectsComponent status);
|
Owner.TryGetComponent(out ServerStatusEffectsComponent status);
|
||||||
|
|
||||||
var coordinates = Owner.Transform.GridPosition;
|
var coordinates = Owner.Transform.GridPosition;
|
||||||
@@ -52,7 +52,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
|||||||
if(pressure > Atmospherics.WarningLowPressure)
|
if(pressure > Atmospherics.WarningLowPressure)
|
||||||
goto default;
|
goto default;
|
||||||
|
|
||||||
damageable.TakeDamage(DamageType.Brute, Atmospherics.LowPressureDamage, Owner);
|
damageable.ChangeDamage(DamageType.Blunt, Atmospherics.LowPressureDamage, false, Owner);
|
||||||
|
|
||||||
if (status == null) break;
|
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);
|
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;
|
if (status == null) break;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Content.Server.Atmos;
|
using Content.Server.Atmos;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Atmos
|
namespace Content.Server.GameObjects.Components.Atmos
|
||||||
{
|
{
|
||||||
@@ -8,7 +9,8 @@ namespace Content.Server.GameObjects.Components.Atmos
|
|||||||
public class GasMixtureComponent : Component
|
public class GasMixtureComponent : Component
|
||||||
{
|
{
|
||||||
public override string Name => "GasMixture";
|
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)
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
{
|
{
|
||||||
|
|||||||
1015
Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs
Normal file
1015
Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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<ServerUserInterfaceComponent>()
|
||||||
|
.GetBoundUserInterface(BodyScannerUiKey.Key);
|
||||||
|
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy BodyTemplate and BodyPart data into a common data class that the client can read.
|
||||||
|
/// </summary>
|
||||||
|
private BodyScannerInterfaceState InterfaceState(BodyTemplate template, IReadOnlyDictionary<string, BodyPart> bodyParts)
|
||||||
|
{
|
||||||
|
var partsData = new Dictionary<string, BodyScannerBodyPartData>();
|
||||||
|
|
||||||
|
foreach (var (slotName, part) in bodyParts)
|
||||||
|
{
|
||||||
|
var mechanismData = new List<BodyScannerMechanismData>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Max volume of internal solution storage
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables] private ReagentUnit _initialMaxVolume;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal solution for reagent storage
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables] private SolutionComponent _internalSolution;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Empty volume of internal solution
|
||||||
|
/// </summary>
|
||||||
|
[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<SolutionComponent>();
|
||||||
|
_internalSolution.MaxVolume = _initialMaxVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
|
serializer.DataField(ref _initialMaxVolume, "maxVolume", ReagentUnit.New(250));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempt to transfer provided solution to internal solution.
|
||||||
|
/// Only supports complete transfers
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="solution">Solution to be transferred</param>
|
||||||
|
/// <returns>Whether or not transfer was a success</returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Content.Server.GameObjects.Components.Body.Circulatory;
|
||||||
using Content.Server.GameObjects.Components.Chemistry;
|
using Content.Server.GameObjects.Components.Chemistry;
|
||||||
using Content.Server.GameObjects.Components.Metabolism;
|
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
using Content.Shared.GameObjects.Components.Nutrition;
|
using Content.Shared.GameObjects.Components.Nutrition;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -11,7 +11,7 @@ using Robust.Shared.Log;
|
|||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Nutrition
|
namespace Content.Server.GameObjects.Components.Body.Digestive
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Where reagents go when ingested. Tracks ingested reagents over time, and
|
/// 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
|
#pragma warning restore 649
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Max volume of internal solution storage
|
/// Max volume of internal solution storage
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ReagentUnit MaxVolume
|
public ReagentUnit MaxVolume
|
||||||
{
|
{
|
||||||
@@ -34,33 +34,29 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal solution storage
|
/// Internal solution storage
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private SolutionComponent _stomachContents;
|
private SolutionComponent _stomachContents;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initial internal solution storage volume
|
/// Initial internal solution storage volume
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private ReagentUnit _initialMaxVolume;
|
private ReagentUnit _initialMaxVolume;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time in seconds between reagents being ingested and them being transferred to <see cref="BloodstreamComponent"/>
|
/// Time in seconds between reagents being ingested and them being transferred
|
||||||
|
/// to <see cref="BloodstreamComponent"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private float _digestionDelay;
|
private float _digestionDelay;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to track how long each reagent has been in the stomach
|
/// Used to track how long each reagent has been in the stomach
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly List<ReagentDelta> _reagentDeltas = new List<ReagentDelta>();
|
private readonly List<ReagentDelta> _reagentDeltas = new List<ReagentDelta>();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reference to bloodstream where digested reagents are transferred to
|
|
||||||
/// </summary>
|
|
||||||
private BloodstreamComponent _bloodstream;
|
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
{
|
{
|
||||||
base.ExposeData(serializer);
|
base.ExposeData(serializer);
|
||||||
@@ -70,14 +66,10 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
|
|
||||||
protected override void Startup()
|
protected override void Startup()
|
||||||
{
|
{
|
||||||
|
base.Startup();
|
||||||
|
|
||||||
_stomachContents = Owner.GetComponent<SolutionComponent>();
|
_stomachContents = Owner.GetComponent<SolutionComponent>();
|
||||||
_stomachContents.MaxVolume = _initialMaxVolume;
|
_stomachContents.MaxVolume = _initialMaxVolume;
|
||||||
if (!Owner.TryGetComponent<BloodstreamComponent>(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)
|
public bool TryTransferSolution(Solution solution)
|
||||||
@@ -88,9 +80,9 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add solution to _stomachContents
|
// Add solution to _stomachContents
|
||||||
_stomachContents.TryAddSolution(solution, false, true);
|
_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)
|
foreach (var reagent in solution.Contents)
|
||||||
{
|
{
|
||||||
_reagentDeltas.Add(new ReagentDelta(reagent.ReagentId, reagent.Quantity));
|
_reagentDeltas.Add(new ReagentDelta(reagent.ReagentId, reagent.Quantity));
|
||||||
@@ -100,23 +92,26 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates digestion status of ingested reagents. Once reagents surpass _digestionDelay
|
/// Updates digestion status of ingested reagents.
|
||||||
/// they are moved to the bloodstream, where they are then metabolized.
|
/// Once reagents surpass _digestionDelay they are moved to the bloodstream,
|
||||||
|
/// where they are then metabolized.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tickTime">The time since the last update in seconds.</param>
|
/// <param name="frameTime">The time since the last update in seconds.</param>
|
||||||
public void OnUpdate(float tickTime)
|
public void Update(float frameTime)
|
||||||
{
|
{
|
||||||
if (_bloodstream == null)
|
if (!Owner.TryGetComponent(out BloodstreamComponent bloodstream))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add reagents ready for transfer to bloodstream to transferSolution
|
// Add reagents ready for transfer to bloodstream to transferSolution
|
||||||
var transferSolution = new Solution();
|
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
|
//Increment lifetime of reagents
|
||||||
delta.Increment(tickTime);
|
delta.Increment(frameTime);
|
||||||
if (delta.Lifetime > _digestionDelay)
|
if (delta.Lifetime > _digestionDelay)
|
||||||
{
|
{
|
||||||
_stomachContents.TryRemoveReagent(delta.ReagentId, delta.Quantity);
|
_stomachContents.TryRemoveReagent(delta.ReagentId, delta.Quantity);
|
||||||
@@ -124,12 +119,13 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
_reagentDeltas.Remove(delta);
|
_reagentDeltas.Remove(delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Transfer digested reagents to bloodstream
|
|
||||||
_bloodstream.TryTransferSolution(transferSolution);
|
// Transfer digested reagents to bloodstream
|
||||||
|
bloodstream.TryTransferSolution(transferSolution);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to track quantity changes when ingesting & digesting reagents
|
/// Used to track quantity changes when ingesting & digesting reagents
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private class ReagentDelta
|
private class ReagentDelta
|
||||||
{
|
{
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Shared.Health.BodySystem;
|
|
||||||
using Content.Shared.Health.BodySystem.Surgery;
|
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.Interfaces;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
|
using Content.Server.Body;
|
||||||
|
using Content.Shared.Body.Surgery;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Server.GameObjects.Components.UserInterface;
|
using Robust.Server.GameObjects.Components.UserInterface;
|
||||||
using Robust.Server.Interfaces.Player;
|
using Robust.Server.Interfaces.Player;
|
||||||
@@ -14,98 +13,114 @@ using Robust.Shared.IoC;
|
|||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.Health.BodySystem.BodyPart
|
namespace Content.Server.GameObjects.Components.Body
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Component representing a dropped, tangible <see cref="BodyPart"/> entity.
|
/// Component representing a dropped, tangible <see cref="BodyPart"/> entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class DroppedBodyPartComponent : Component, IAfterInteract, IBodyPartContainer
|
public class DroppedBodyPartComponent : Component, IAfterInteract, IBodyPartContainer
|
||||||
{
|
{
|
||||||
|
|
||||||
#pragma warning disable 649
|
#pragma warning disable 649
|
||||||
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
|
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
|
||||||
#pragma warning restore 649
|
#pragma warning restore 649
|
||||||
|
|
||||||
public sealed override string Name => "DroppedBodyPart";
|
private readonly Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
|
||||||
|
private BodyManagerComponent _bodyManagerComponentCache;
|
||||||
[ViewVariables]
|
private int _idHash;
|
||||||
public BodyPart ContainedBodyPart { get; set; }
|
private IEntity _performerCache;
|
||||||
|
|
||||||
private BoundUserInterface _userInterface;
|
private BoundUserInterface _userInterface;
|
||||||
private Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
|
|
||||||
private IEntity _performerCache;
|
|
||||||
private BodyManagerComponent _bodyManagerComponentCache;
|
|
||||||
private int _idHash = 0;
|
|
||||||
|
|
||||||
public override void Initialize()
|
public sealed override string Name => "DroppedBodyPart";
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(GenericSurgeryUiKey.Key);
|
|
||||||
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void TransferBodyPartData(BodyPart data)
|
[ViewVariables] public BodyPart ContainedBodyPart { get; private set; }
|
||||||
{
|
|
||||||
ContainedBodyPart = data;
|
|
||||||
Owner.Name = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(ContainedBodyPart.Name);
|
|
||||||
if (Owner.TryGetComponent<SpriteComponent>(out SpriteComponent component))
|
|
||||||
{
|
|
||||||
component.LayerSetRSI(0, data.RSIPath);
|
|
||||||
component.LayerSetState(0, data.RSIState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (eventArgs.Target == null)
|
if (eventArgs.Target == null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CloseAllSurgeryUIs();
|
CloseAllSurgeryUIs();
|
||||||
_optionsCache.Clear();
|
_optionsCache.Clear();
|
||||||
_performerCache = null;
|
_performerCache = null;
|
||||||
_bodyManagerComponentCache = null;
|
_bodyManagerComponentCache = null;
|
||||||
|
|
||||||
if (eventArgs.Target.TryGetComponent<BodyManagerComponent>(out BodyManagerComponent bodyManager))
|
if (eventArgs.Target.TryGetComponent(out BodyManagerComponent bodyManager))
|
||||||
{
|
{
|
||||||
SendBodySlotListToUser(eventArgs, bodyManager);
|
SendBodySlotListToUser(eventArgs, bodyManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendBodySlotListToUser(AfterInteractEventArgs eventArgs, BodyManagerComponent bodyManager)
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
var toSend = new Dictionary<string, int>(); //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.
|
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
|
||||||
List<string> unoccupiedSlots = bodyManager.AllSlots.ToList().Except(bodyManager.OccupiedSlots.ToList()).ToList();
|
.GetBoundUserInterface(GenericSurgeryUiKey.Key);
|
||||||
foreach (string slot in unoccupiedSlots)
|
_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<BodyPart> bodypartResult))
|
component.LayerSetColor(0, data.RSIColor.Value);
|
||||||
{
|
|
||||||
foreach (BodyPart connectedPart in bodypartResult)
|
|
||||||
{
|
|
||||||
if (connectedPart.CanAttachBodyPart(ContainedBodyPart))
|
|
||||||
{
|
|
||||||
_optionsCache.Add(_idHash, slot);
|
|
||||||
toSend.Add(slot, _idHash++);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<string, int>();
|
||||||
|
|
||||||
|
// 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)
|
if (_optionsCache.Count > 0)
|
||||||
{
|
{
|
||||||
OpenSurgeryUI(eventArgs.User.GetComponent<BasicActorComponent>().playerSession);
|
OpenSurgeryUI(eventArgs.User.GetComponent<BasicActorComponent>().playerSession);
|
||||||
UpdateSurgeryUIBodyPartSlotRequest(eventArgs.User.GetComponent<BasicActorComponent>().playerSession, toSend);
|
UpdateSurgeryUIBodyPartSlotRequest(eventArgs.User.GetComponent<BasicActorComponent>().playerSession,
|
||||||
|
toSend);
|
||||||
_performerCache = eventArgs.User;
|
_performerCache = eventArgs.User;
|
||||||
_bodyManagerComponentCache = bodyManager;
|
_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)
|
private void HandleReceiveBodyPartSlot(int key)
|
||||||
{
|
{
|
||||||
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
|
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().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));
|
_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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OpenSurgeryUI(IPlayerSession session)
|
||||||
public void OpenSurgeryUI(IPlayerSession session)
|
|
||||||
{
|
{
|
||||||
_userInterface.Open(session);
|
_userInterface.Open(session);
|
||||||
}
|
}
|
||||||
public void UpdateSurgeryUIBodyPartSlotRequest(IPlayerSession session, Dictionary<string, int> options)
|
|
||||||
|
private void UpdateSurgeryUIBodyPartSlotRequest(IPlayerSession session, Dictionary<string, int> options)
|
||||||
{
|
{
|
||||||
_userInterface.SendMessage(new RequestBodyPartSlotSurgeryUIMessage(options), session);
|
_userInterface.SendMessage(new RequestBodyPartSlotSurgeryUIMessage(options), session);
|
||||||
}
|
}
|
||||||
public void CloseSurgeryUI(IPlayerSession session)
|
|
||||||
|
private void CloseSurgeryUI(IPlayerSession session)
|
||||||
{
|
{
|
||||||
_userInterface.Close(session);
|
_userInterface.Close(session);
|
||||||
}
|
}
|
||||||
public void CloseAllSurgeryUIs()
|
|
||||||
|
private void CloseAllSurgeryUIs()
|
||||||
{
|
{
|
||||||
_userInterface.CloseAll();
|
_userInterface.CloseAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
|
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
|
||||||
{
|
{
|
||||||
switch (message.Message)
|
switch (message.Message)
|
||||||
{
|
{
|
||||||
case ReceiveBodyPartSlotSurgeryUIMessage msg:
|
case ReceiveBodyPartSlotSurgeryUIMessage msg:
|
||||||
HandleReceiveBodyPartSlot(msg.SelectedOptionID);
|
HandleReceiveBodyPartSlot(msg.SelectedOptionId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using Content.Server.Body;
|
||||||
using Content.Server.Health.BodySystem.BodyPart;
|
using Content.Server.Body.Mechanisms;
|
||||||
using Content.Shared.Health.BodySystem.Mechanism;
|
using Content.Shared.Body.Mechanism;
|
||||||
using Content.Shared.Health.BodySystem.Surgery;
|
using Content.Shared.Body.Surgery;
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.Interfaces;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
@@ -18,15 +18,14 @@ using Robust.Shared.Prototypes;
|
|||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.Health.BodySystem.Mechanism {
|
namespace Content.Server.GameObjects.Components.Body
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Component representing a dropped, tangible <see cref="Mechanism"/> entity.
|
/// Component representing a dropped, tangible <see cref="Mechanism"/> entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class DroppedMechanismComponent : Component, IAfterInteract
|
public class DroppedMechanismComponent : Component, IAfterInteract
|
||||||
{
|
{
|
||||||
|
|
||||||
#pragma warning disable 649
|
#pragma warning disable 649
|
||||||
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
|
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
|
||||||
[Dependency] private IPrototypeManager _prototypeManager;
|
[Dependency] private IPrototypeManager _prototypeManager;
|
||||||
@@ -34,98 +33,120 @@ namespace Content.Server.Health.BodySystem.Mechanism {
|
|||||||
|
|
||||||
public sealed override string Name => "DroppedMechanism";
|
public sealed override string Name => "DroppedMechanism";
|
||||||
|
|
||||||
[ViewVariables]
|
private readonly Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
|
||||||
public Mechanism ContainedMechanism { get; private set; }
|
|
||||||
|
private BodyManagerComponent _bodyManagerComponentCache;
|
||||||
|
|
||||||
|
private int _idHash;
|
||||||
|
|
||||||
|
private IEntity _performerCache;
|
||||||
|
|
||||||
private BoundUserInterface _userInterface;
|
private BoundUserInterface _userInterface;
|
||||||
private Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
|
|
||||||
private IEntity _performerCache;
|
|
||||||
private BodyManagerComponent _bodyManagerComponentCache;
|
|
||||||
private int _idHash = 0;
|
|
||||||
|
|
||||||
public override void Initialize()
|
[ViewVariables] public Mechanism ContainedMechanism { get; private set; }
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(GenericSurgeryUiKey.Key);
|
|
||||||
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void InitializeDroppedMechanism(Mechanism data)
|
|
||||||
{
|
|
||||||
ContainedMechanism = data;
|
|
||||||
Owner.Name = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(ContainedMechanism.Name);
|
|
||||||
if (Owner.TryGetComponent<SpriteComponent>(out SpriteComponent component))
|
|
||||||
{
|
|
||||||
component.LayerSetRSI(0, data.RSIPath);
|
|
||||||
component.LayerSetState(0, data.RSIState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (eventArgs.Target == null)
|
if (eventArgs.Target == null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CloseAllSurgeryUIs();
|
CloseAllSurgeryUIs();
|
||||||
_optionsCache.Clear();
|
_optionsCache.Clear();
|
||||||
_performerCache = null;
|
_performerCache = null;
|
||||||
_bodyManagerComponentCache = null;
|
_bodyManagerComponentCache = null;
|
||||||
|
|
||||||
if (eventArgs.Target.TryGetComponent<BodyManagerComponent>(out BodyManagerComponent bodyManager))
|
if (eventArgs.Target.TryGetComponent<BodyManagerComponent>(out var bodyManager))
|
||||||
{
|
{
|
||||||
SendBodyPartListToUser(eventArgs, bodyManager);
|
SendBodyPartListToUser(eventArgs, bodyManager);
|
||||||
}
|
}
|
||||||
else if (eventArgs.Target.TryGetComponent<DroppedBodyPartComponent>(out DroppedBodyPartComponent droppedBodyPart))
|
else if (eventArgs.Target.TryGetComponent<DroppedBodyPartComponent>(out var droppedBodyPart))
|
||||||
{
|
{
|
||||||
if (droppedBodyPart.ContainedBodyPart == null)
|
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!");
|
throw new InvalidOperationException("A DroppedBodyPartComponent exists without a BodyPart in it!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!droppedBodyPart.ContainedBodyPart.TryInstallDroppedMechanism(this))
|
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<ServerUserInterfaceComponent>()
|
||||||
|
.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)
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
{
|
{
|
||||||
//This is a temporary way to have spawnable hard-coded DroppedMechanismComponent prototypes
|
// 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
|
// In the future (when it becomes possible) DroppedMechanismComponent should be auto-generated from
|
||||||
string debugLoadMechanismData = "";
|
// the Mechanism prototypes
|
||||||
|
var debugLoadMechanismData = "";
|
||||||
base.ExposeData(serializer);
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
serializer.DataField(ref debugLoadMechanismData, "debugLoadMechanismData", "");
|
serializer.DataField(ref debugLoadMechanismData, "debugLoadMechanismData", "");
|
||||||
|
|
||||||
if (serializer.Reading && debugLoadMechanismData != "")
|
if (serializer.Reading && debugLoadMechanismData != "")
|
||||||
{
|
{
|
||||||
_prototypeManager.TryIndex(debugLoadMechanismData, out MechanismPrototype data);
|
_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)
|
private void SendBodyPartListToUser(AfterInteractEventArgs eventArgs, BodyManagerComponent bodyManager)
|
||||||
{
|
{
|
||||||
var toSend = new Dictionary<string, int>(); //Create dictionary to send to client (text to be shown : data sent back if selected)
|
// Create dictionary to send to client (text to be shown : data sent back if selected)
|
||||||
foreach (var (key, value) in bodyManager.PartDictionary)
|
var toSend = new Dictionary<string, int>();
|
||||||
{ //For each limb in the target, add it to our cache if it is a valid option.
|
|
||||||
|
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))
|
if (value.CanInstallMechanism(ContainedMechanism))
|
||||||
{
|
{
|
||||||
_optionsCache.Add(_idHash, value);
|
_optionsCache.Add(_idHash, value);
|
||||||
toSend.Add(key + ": " + value.Name, _idHash++);
|
toSend.Add(key + ": " + value.Name, _idHash++);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_optionsCache.Count > 0)
|
if (_optionsCache.Count > 0)
|
||||||
{
|
{
|
||||||
OpenSurgeryUI(eventArgs.User.GetComponent<BasicActorComponent>().playerSession);
|
OpenSurgeryUI(eventArgs.User.GetComponent<BasicActorComponent>().playerSession);
|
||||||
UpdateSurgeryUIBodyPartRequest(eventArgs.User.GetComponent<BasicActorComponent>().playerSession, toSend);
|
UpdateSurgeryUIBodyPartRequest(eventArgs.User.GetComponent<BasicActorComponent>().playerSession,
|
||||||
|
toSend);
|
||||||
_performerCache = eventArgs.User;
|
_performerCache = eventArgs.User;
|
||||||
_bodyManagerComponentCache = bodyManager;
|
_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)
|
private void HandleReceiveBodyPart(int key)
|
||||||
{
|
{
|
||||||
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
|
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().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));
|
_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;
|
return;
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OpenSurgeryUI(IPlayerSession session)
|
||||||
|
|
||||||
|
|
||||||
public void OpenSurgeryUI(IPlayerSession session)
|
|
||||||
{
|
{
|
||||||
_userInterface.Open(session);
|
_userInterface.Open(session);
|
||||||
}
|
}
|
||||||
public void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary<string, int> options)
|
|
||||||
|
private void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary<string, int> options)
|
||||||
{
|
{
|
||||||
_userInterface.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
|
_userInterface.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
|
||||||
}
|
}
|
||||||
public void CloseSurgeryUI(IPlayerSession session)
|
|
||||||
|
private void CloseSurgeryUI(IPlayerSession session)
|
||||||
{
|
{
|
||||||
_userInterface.Close(session);
|
_userInterface.Close(session);
|
||||||
}
|
}
|
||||||
public void CloseAllSurgeryUIs()
|
|
||||||
|
private void CloseAllSurgeryUIs()
|
||||||
{
|
{
|
||||||
_userInterface.CloseAll();
|
_userInterface.CloseAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
|
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
|
||||||
{
|
{
|
||||||
switch (message.Message)
|
switch (message.Message)
|
||||||
{
|
{
|
||||||
case ReceiveBodyPartSurgeryUIMessage msg:
|
case ReceiveBodyPartSurgeryUIMessage msg:
|
||||||
HandleReceiveBodyPart(msg.SelectedOptionID);
|
HandleReceiveBodyPart(msg.SelectedOptionId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The pressure that this lung exerts on the air around it
|
||||||
|
/// </summary>
|
||||||
|
[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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.Health.BodySystem.BodyPart;
|
using Content.Server.Body;
|
||||||
using Content.Server.Health.BodySystem.Mechanism;
|
using Content.Server.Body.Mechanisms;
|
||||||
|
using Content.Server.Body.Surgery;
|
||||||
|
using Content.Shared.Body.Surgery;
|
||||||
using Content.Shared.GameObjects;
|
using Content.Shared.GameObjects;
|
||||||
using Content.Shared.Health.BodySystem;
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
using Content.Shared.Health.BodySystem.Surgery;
|
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.Interfaces;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Server.GameObjects.Components.UserInterface;
|
using Robust.Server.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Server.Interfaces.GameObjects;
|
||||||
using Robust.Server.Interfaces.Player;
|
using Robust.Server.Interfaces.Player;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
@@ -17,110 +19,136 @@ using Robust.Shared.Localization;
|
|||||||
using Robust.Shared.Log;
|
using Robust.Shared.Log;
|
||||||
using Robust.Shared.Serialization;
|
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.
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class SurgeryToolComponent : Component, ISurgeon, IAfterInteract
|
public class SurgeryToolComponent : Component, ISurgeon, IAfterInteract
|
||||||
{
|
{
|
||||||
public override string Name => "SurgeryTool";
|
|
||||||
public override uint? NetID => ContentNetIDs.SURGERY;
|
|
||||||
|
|
||||||
#pragma warning disable 649
|
#pragma warning disable 649
|
||||||
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
|
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
|
||||||
#pragma warning restore 649
|
#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<int, object> _optionsCache = new Dictionary<int, object>();
|
||||||
|
|
||||||
private float _baseOperateTime;
|
private float _baseOperateTime;
|
||||||
private SurgeryType _surgeryType;
|
|
||||||
private HashSet<IPlayerSession> _subscribedSessions = new HashSet<IPlayerSession>();
|
|
||||||
private BoundUserInterface _userInterface;
|
|
||||||
|
|
||||||
private Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
|
|
||||||
private IEntity _performerCache;
|
|
||||||
private BodyManagerComponent _bodyManagerComponentCache;
|
private BodyManagerComponent _bodyManagerComponentCache;
|
||||||
private ISurgeon.MechanismRequestCallback _callbackCache;
|
|
||||||
private int _idHash = 0;
|
|
||||||
|
|
||||||
public override void Initialize()
|
private ISurgeon.MechanismRequestCallback _callbackCache;
|
||||||
{
|
|
||||||
base.Initialize();
|
private int _idHash;
|
||||||
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(GenericSurgeryUiKey.Key);
|
|
||||||
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
private IEntity _performerCache;
|
||||||
}
|
|
||||||
|
private SurgeryType _surgeryType;
|
||||||
|
|
||||||
|
private BoundUserInterface _userInterface;
|
||||||
|
|
||||||
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (eventArgs.Target == null)
|
if (eventArgs.Target == null)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CloseAllSurgeryUIs();
|
CloseAllSurgeryUIs();
|
||||||
_optionsCache.Clear();
|
_optionsCache.Clear();
|
||||||
|
|
||||||
_performerCache = null;
|
_performerCache = null;
|
||||||
_bodyManagerComponentCache = null;
|
_bodyManagerComponentCache = null;
|
||||||
_callbackCache = null;
|
_callbackCache = null;
|
||||||
|
|
||||||
if (eventArgs.Target.TryGetComponent<BodyManagerComponent>(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<string, int>(); //Create dictionary to send to client (text to be shown : data sent back if selected)
|
// 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.
|
var toSend = new Dictionary<string, int>();
|
||||||
|
|
||||||
|
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))
|
if (value.SurgeryCheck(_surgeryType))
|
||||||
{
|
{
|
||||||
_optionsCache.Add(_idHash, value);
|
_optionsCache.Add(_idHash, value);
|
||||||
toSend.Add(key + ": " + value.Name, _idHash++);
|
toSend.Add(key + ": " + value.Name, _idHash++);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_optionsCache.Count > 0)
|
if (_optionsCache.Count > 0)
|
||||||
{
|
{
|
||||||
OpenSurgeryUI(eventArgs.User.GetComponent<BasicActorComponent>().playerSession);
|
OpenSurgeryUI(actor.playerSession);
|
||||||
UpdateSurgeryUIBodyPartRequest(eventArgs.User.GetComponent<BasicActorComponent>().playerSession, toSend);
|
UpdateSurgeryUIBodyPartRequest(actor.playerSession, toSend);
|
||||||
_performerCache = eventArgs.User; //Also, cache the data.
|
_performerCache = eventArgs.User; // Also, cache the data.
|
||||||
_bodyManagerComponentCache = bodyManager;
|
_bodyManagerComponentCache = body;
|
||||||
}
|
}
|
||||||
else //If surgery cannot be performed, show message saying so.
|
else // If surgery cannot be performed, show message saying so.
|
||||||
{
|
{
|
||||||
SendNoUsefulWayToUsePopup();
|
SendNoUsefulWayToUsePopup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (eventArgs.Target.TryGetComponent<DroppedBodyPartComponent>(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<DroppedBodyPartComponent>(out var droppedBodyPart))
|
||||||
{
|
{
|
||||||
|
// Attempt surgery on a DroppedBodyPart - there's only one possible target so no need for selection UI
|
||||||
_performerCache = eventArgs.User;
|
_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!");
|
throw new InvalidOperationException("A DroppedBodyPartComponent exists without a BodyPart in it!");
|
||||||
}
|
}
|
||||||
if (droppedBodyPart.ContainedBodyPart.SurgeryCheck(_surgeryType)) //If surgery can be performed...
|
|
||||||
{
|
// If surgery can be performed...
|
||||||
if (!droppedBodyPart.ContainedBodyPart.AttemptSurgery(_surgeryType, droppedBodyPart, this, eventArgs.User)) //...do the surgery.
|
if (!droppedBodyPart.ContainedBodyPart.SurgeryCheck(_surgeryType))
|
||||||
{
|
|
||||||
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.
|
|
||||||
{
|
{
|
||||||
SendNoUsefulWayToUsePopup();
|
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<Mechanism.Mechanism> options, ISurgeon.MechanismRequestCallback callback)
|
public float BaseOperationTime { get => _baseOperateTime; set => _baseOperateTime = value; }
|
||||||
|
|
||||||
|
public void RequestMechanism(IEnumerable<Mechanism> options, ISurgeon.MechanismRequestCallback callback)
|
||||||
{
|
{
|
||||||
var toSend = new Dictionary<string, int> ();
|
var toSend = new Dictionary<string, int>();
|
||||||
foreach (Mechanism.Mechanism mechanism in options)
|
foreach (var mechanism in options)
|
||||||
{
|
{
|
||||||
_optionsCache.Add(_idHash, mechanism);
|
_optionsCache.Add(_idHash, mechanism);
|
||||||
toSend.Add(mechanism.Name, _idHash++);
|
toSend.Add(mechanism.Name, _idHash++);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_optionsCache.Count > 0)
|
if (_optionsCache.Count > 0)
|
||||||
{
|
{
|
||||||
OpenSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
|
OpenSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
|
||||||
UpdateSurgeryUIMechanismRequest(_performerCache.GetComponent<BasicActorComponent>().playerSession, toSend);
|
UpdateSurgeryUIMechanismRequest(_performerCache.GetComponent<BasicActorComponent>().playerSession,
|
||||||
|
toSend);
|
||||||
_callbackCache = callback;
|
_callbackCache = callback;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -130,95 +158,112 @@ namespace Content.Server.Health.BodySystem.Surgery.Surgeon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
|
||||||
|
.GetBoundUserInterface(GenericSurgeryUiKey.Key);
|
||||||
|
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenSurgeryUI(IPlayerSession session)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void OpenSurgeryUI(IPlayerSession session)
|
|
||||||
{
|
{
|
||||||
_userInterface.Open(session);
|
_userInterface.Open(session);
|
||||||
}
|
}
|
||||||
public void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary<string, int> options)
|
|
||||||
|
private void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary<string, int> options)
|
||||||
{
|
{
|
||||||
_userInterface.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
|
_userInterface.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
|
||||||
}
|
}
|
||||||
public void UpdateSurgeryUIMechanismRequest(IPlayerSession session, Dictionary<string, int> options)
|
|
||||||
|
private void UpdateSurgeryUIMechanismRequest(IPlayerSession session, Dictionary<string, int> options)
|
||||||
{
|
{
|
||||||
_userInterface.SendMessage(new RequestMechanismSurgeryUIMessage(options), session);
|
_userInterface.SendMessage(new RequestMechanismSurgeryUIMessage(options), session);
|
||||||
}
|
}
|
||||||
public void CloseSurgeryUI(IPlayerSession session)
|
|
||||||
|
private void CloseSurgeryUI(IPlayerSession session)
|
||||||
{
|
{
|
||||||
_userInterface.Close(session);
|
_userInterface.Close(session);
|
||||||
}
|
}
|
||||||
public void CloseAllSurgeryUIs()
|
|
||||||
|
private void CloseAllSurgeryUIs()
|
||||||
{
|
{
|
||||||
_userInterface.CloseAll();
|
_userInterface.CloseAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
|
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
|
||||||
{
|
{
|
||||||
switch (message.Message)
|
switch (message.Message)
|
||||||
{
|
{
|
||||||
case ReceiveBodyPartSurgeryUIMessage msg:
|
case ReceiveBodyPartSurgeryUIMessage msg:
|
||||||
HandleReceiveBodyPart(msg.SelectedOptionID);
|
HandleReceiveBodyPart(msg.SelectedOptionId);
|
||||||
break;
|
break;
|
||||||
case ReceiveMechanismSurgeryUIMessage msg:
|
case ReceiveMechanismSurgeryUIMessage msg:
|
||||||
HandleReceiveMechanism(msg.SelectedOptionID);
|
HandleReceiveMechanism(msg.SelectedOptionId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called after the client chooses from a list of possible <see cref="BodyPart">BodyParts</see> that can be operated on.
|
/// Called after the client chooses from a list of possible
|
||||||
|
/// <see cref="BodyPart"/> that can be operated on.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void HandleReceiveBodyPart(int key)
|
private void HandleReceiveBodyPart(int key)
|
||||||
{
|
{
|
||||||
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
|
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
|
||||||
//TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc
|
// 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))
|
if (!_optionsCache.TryGetValue(key, out var targetObject))
|
||||||
{
|
|
||||||
SendNoUsefulWayToUseAnymorePopup();
|
|
||||||
}
|
|
||||||
BodyPart.BodyPart target = targetObject as BodyPart.BodyPart;
|
|
||||||
if (!target.AttemptSurgery(_surgeryType, _bodyManagerComponentCache, this, _performerCache))
|
|
||||||
{
|
{
|
||||||
SendNoUsefulWayToUseAnymorePopup();
|
SendNoUsefulWayToUseAnymorePopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
var target = targetObject as BodyPart;
|
||||||
/// <summary>
|
|
||||||
/// Called after the client chooses from a list of possible <see cref="Mechanism">Mechanisms</see> to choose from.
|
if (!target.AttemptSurgery(_surgeryType, _bodyManagerComponentCache, this, _performerCache))
|
||||||
/// </summary>
|
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
SendNoUsefulWayToUseAnymorePopup();
|
SendNoUsefulWayToUseAnymorePopup();
|
||||||
}
|
}
|
||||||
Mechanism.Mechanism target = targetObject as Mechanism.Mechanism;
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called after the client chooses from a list of possible
|
||||||
|
/// <see cref="Mechanism"/> to choose from.
|
||||||
|
/// </summary>
|
||||||
|
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<BasicActorComponent>().playerSession);
|
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
|
||||||
_callbackCache(target, _bodyManagerComponentCache, this, _performerCache);
|
_callbackCache(target, _bodyManagerComponentCache, this, _performerCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendNoUsefulWayToUsePopup()
|
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()
|
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)
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
{
|
{
|
||||||
base.ExposeData(serializer);
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
serializer.DataField(ref _surgeryType, "surgeryType", SurgeryType.Incision);
|
serializer.DataField(ref _surgeryType, "surgeryType", SurgeryType.Incision);
|
||||||
serializer.DataField(ref _baseOperateTime, "baseOperateTime", 5);
|
serializer.DataField(ref _baseOperateTime, "baseOperateTime", 5);
|
||||||
}
|
}
|
||||||
@@ -373,9 +373,9 @@ namespace Content.Server.GameObjects.Components.Buckle
|
|||||||
StandingStateHelper.Standing(Owner);
|
StandingStateHelper.Standing(Owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Owner.TryGetComponent(out SpeciesComponent species))
|
if (Owner.TryGetComponent(out MobStateManagerComponent stateManager))
|
||||||
{
|
{
|
||||||
species.CurrentDamageState.EnterState(Owner);
|
stateManager.CurrentMobState.EnterState(Owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
BuckleStatus();
|
BuckleStatus();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using Content.Server.GameObjects.Components.Metabolism;
|
using Content.Server.GameObjects.Components.Body.Circulatory;
|
||||||
using Content.Server.Interfaces;
|
using Content.Server.Interfaces;
|
||||||
using Content.Server.Utility;
|
using Content.Server.Utility;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
@@ -134,7 +134,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
|||||||
}
|
}
|
||||||
else //Handle injecting into bloodstream
|
else //Handle injecting into bloodstream
|
||||||
{
|
{
|
||||||
if (targetEntity.TryGetComponent<BloodstreamComponent>(out var bloodstream) && _toggleState == InjectorToggleMode.Inject)
|
if (targetEntity.TryGetComponent(out BloodstreamComponent bloodstream) &&
|
||||||
|
_toggleState == InjectorToggleMode.Inject)
|
||||||
{
|
{
|
||||||
TryInjectIntoBloodstream(bloodstream, eventArgs.User);
|
TryInjectIntoBloodstream(bloodstream, eventArgs.User);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.GameObjects.Components.Utensil;
|
||||||
using Content.Server.Utility;
|
using Content.Server.Utility;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
|||||||
/// ECS component that manages a liquid solution of reagents.
|
/// ECS component that manages a liquid solution of reagents.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
internal class SolutionComponent : SharedSolutionComponent, IExamine
|
public class SolutionComponent : SharedSolutionComponent, IExamine
|
||||||
{
|
{
|
||||||
#pragma warning disable 649
|
#pragma warning disable 649
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager;
|
[Dependency] private readonly IPrototypeManager _prototypeManager;
|
||||||
|
|||||||
@@ -1,39 +1,59 @@
|
|||||||
using System.Collections.Generic;
|
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;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.Interfaces.Random;
|
using Robust.Shared.Interfaces.Random;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Damage
|
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
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When attached to an <see cref="IEntity"/>, allows it to take damage and sets it to a "broken state" after taking
|
||||||
|
/// enough damage.
|
||||||
|
/// </summary>
|
||||||
[RegisterComponent]
|
[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;
|
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
|
||||||
#pragma warning restore 649
|
[Dependency] private readonly IRobustRandom _random;
|
||||||
/// <inheritdoc />
|
#pragma warning restore 649
|
||||||
public override string Name => "Breakable";
|
|
||||||
public DamageThreshold Threshold { get; private set; }
|
|
||||||
|
|
||||||
public DamageType damageType = DamageType.Total;
|
public override string Name => "Breakable";
|
||||||
public int damageValue = 0;
|
|
||||||
public bool broken = false;
|
|
||||||
|
|
||||||
private ActSystem _actSystem;
|
private ActSystem _actSystem;
|
||||||
|
private DamageState _currentDamageState;
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
public override List<DamageState> SupportedDamageStates =>
|
||||||
|
new List<DamageState> {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);
|
break;
|
||||||
serializer.DataField(ref damageType, "thresholdtype", DamageType.Total);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
@@ -42,38 +62,21 @@ namespace Content.Server.GameObjects.Components.Damage
|
|||||||
_actSystem = _entitySystemManager.GetEntitySystem<ActSystem>();
|
_actSystem = _entitySystemManager.GetEntitySystem<ActSystem>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<DamageThreshold> 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);
|
Heal();
|
||||||
return new List<DamageThreshold>() {Threshold};
|
_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;
|
var pos = Owner.Transform.GridPosition;
|
||||||
_actSystem.HandleBreakage(Owner);
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(DestroySound, pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnExplosion(ExplosionEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
var prob = IoCManager.Resolve<IRobustRandom>();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
using Content.Shared.Audio;
|
using Content.Shared.Audio;
|
||||||
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Robust.Server.GameObjects.EntitySystems;
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -23,7 +24,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
|||||||
|
|
||||||
public override string Name => "DamageOnHighSpeedImpact";
|
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 float MinimumSpeed { get; set; } = 20f;
|
||||||
public int BaseDamage { get; set; } = 5;
|
public int BaseDamage { get; set; } = 5;
|
||||||
public float Factor { get; set; } = 0.75f;
|
public float Factor { get; set; } = 0.75f;
|
||||||
@@ -38,7 +39,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
|||||||
{
|
{
|
||||||
base.ExposeData(serializer);
|
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 => MinimumSpeed, "minimumSpeed", 20f);
|
||||||
serializer.DataField(this, x => BaseDamage, "baseDamage", 5);
|
serializer.DataField(this, x => BaseDamage, "baseDamage", 5);
|
||||||
serializer.DataField(this, x => Factor, "factor", 1f);
|
serializer.DataField(this, x => Factor, "factor", 1f);
|
||||||
@@ -51,7 +52,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
|||||||
|
|
||||||
public void CollideWith(IEntity collidedWith)
|
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;
|
var speed = collidable.LinearVelocity.Length;
|
||||||
|
|
||||||
@@ -70,7 +71,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
|||||||
if (Owner.TryGetComponent(out StunnableComponent stun) && _robustRandom.Prob(StunChance))
|
if (Owner.TryGetComponent(out StunnableComponent stun) && _robustRandom.Prob(StunChance))
|
||||||
stun.Stun(StunSeconds);
|
stun.Stun(StunSeconds);
|
||||||
|
|
||||||
damageable.TakeDamage(Damage, damage, collidedWith, Owner);
|
damageable.ChangeDamage(Damage, damage, false, collidedWith);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.GameObjects.Components.Interactable;
|
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.GameObjects.Components.Interactable;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -9,7 +9,7 @@ using Robust.Shared.Serialization;
|
|||||||
namespace Content.Server.GameObjects.Components.Damage
|
namespace Content.Server.GameObjects.Components.Damage
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
class DamageOnToolInteractComponent : Component, IInteractUsing
|
public class DamageOnToolInteractComponent : Component, IInteractUsing
|
||||||
{
|
{
|
||||||
public override string Name => "DamageOnToolInteract";
|
public override string Name => "DamageOnToolInteract";
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
|||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
Owner.EnsureComponent<DamageableComponent>();
|
Owner.EnsureComponent<DestructibleComponent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
@@ -40,12 +40,12 @@ namespace Content.Server.GameObjects.Components.Damage
|
|||||||
{
|
{
|
||||||
if (tool.HasQuality(ToolQuality.Welding) && toolQuality == ToolQuality.Welding)
|
if (tool.HasQuality(ToolQuality.Welding) && toolQuality == ToolQuality.Welding)
|
||||||
{
|
{
|
||||||
if (eventArgs.Using.TryGetComponent<WelderComponent>(out WelderComponent welder))
|
if (eventArgs.Using.TryGetComponent(out WelderComponent welder))
|
||||||
{
|
{
|
||||||
if (welder.WelderLit) return CallDamage(eventArgs, tool);
|
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.
|
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);
|
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)
|
protected bool CallDamage(InteractUsingEventArgs eventArgs, ToolComponent tool)
|
||||||
{
|
{
|
||||||
if (eventArgs.Target.TryGetComponent<DamageableComponent>(out var damageable))
|
if (eventArgs.Target.TryGetComponent<DestructibleComponent>(out var damageable))
|
||||||
{
|
{
|
||||||
if(tool.HasQuality(ToolQuality.Welding)) damageable.TakeDamage(DamageType.Heat, Damage, eventArgs.Using, eventArgs.User);
|
damageable.ChangeDamage(tool.HasQuality(ToolQuality.Welding)
|
||||||
else
|
? DamageType.Heat
|
||||||
damageable.TakeDamage(DamageType.Brute, Damage, eventArgs.Using, eventArgs.User);
|
: DamageType.Blunt,
|
||||||
|
Damage, false, eventArgs.User);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Damage
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Triggers an event when values rise above or drop below this threshold
|
|
||||||
/// </summary>
|
|
||||||
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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Type of damage.
|
|
||||||
/// </summary>
|
|
||||||
public DamageType Type { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Change in damage.
|
|
||||||
/// </summary>
|
|
||||||
public int Damage { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The entity that damaged this one.
|
|
||||||
/// Could be null.
|
|
||||||
/// </summary>
|
|
||||||
public IEntity Source { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The mob entity that damaged this one.
|
|
||||||
/// Could be null.
|
|
||||||
/// </summary>
|
|
||||||
public IEntity SourceMob { get; }
|
|
||||||
|
|
||||||
public DamageEventArgs(DamageType type, int damage, IEntity source, IEntity sourceMob)
|
|
||||||
{
|
|
||||||
Type = type;
|
|
||||||
Damage = damage;
|
|
||||||
Source = source;
|
|
||||||
SourceMob = sourceMob;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A component that handles receiving damage and healing,
|
|
||||||
/// as well as informing other components of it.
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
|
||||||
public class DamageableComponent : SharedDamageableComponent, IDamageableComponent
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string Name => "Damageable";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The resistance set of this object.
|
|
||||||
/// Affects receiving damage of various types.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables]
|
|
||||||
public ResistanceSet Resistances { get; private set; }
|
|
||||||
|
|
||||||
[ViewVariables]
|
|
||||||
public IReadOnlyDictionary<DamageType, int> CurrentDamage => _currentDamage;
|
|
||||||
private Dictionary<DamageType, int> _currentDamage = new Dictionary<DamageType, int>();
|
|
||||||
|
|
||||||
[ViewVariables]
|
|
||||||
public Dictionary<DamageType, List<DamageThreshold>> Thresholds = new Dictionary<DamageType, List<DamageThreshold>>();
|
|
||||||
|
|
||||||
public event EventHandler<DamageThresholdPassedEventArgs> DamageThresholdPassed;
|
|
||||||
public event EventHandler<DamageEventArgs> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
InitializeDamageType(DamageType.Total);
|
|
||||||
|
|
||||||
foreach (var damagebehavior in Owner.GetAllComponents<IOnDamageBehavior>())
|
|
||||||
{
|
|
||||||
AddThresholdsFrom(damagebehavior);
|
|
||||||
Damaged += damagebehavior.OnDamaged;
|
|
||||||
}
|
|
||||||
|
|
||||||
RecalculateComponentThresholds();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
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<DamageType>();
|
|
||||||
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<IOnDamageBehavior>())
|
|
||||||
{
|
|
||||||
AddThresholdsFrom(onDamageBehaviorComponent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddThresholdsFrom(IOnDamageBehavior onDamageBehavior)
|
|
||||||
{
|
|
||||||
if (onDamageBehavior == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(onDamageBehavior));
|
|
||||||
}
|
|
||||||
|
|
||||||
List<DamageThreshold> 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<DamageThreshold>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,55 +1,49 @@
|
|||||||
using System.Collections.Generic;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Content.Server.Interfaces.GameObjects;
|
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
|
||||||
using Robust.Server.GameObjects.EntitySystems;
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.GameObjects.Systems;
|
using Robust.Shared.GameObjects.Systems;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.Interfaces.Random;
|
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Random;
|
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Damage
|
namespace Content.Server.GameObjects.Components.Damage
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the entity once a certain damage threshold has been reached.
|
/// When attached to an <see cref="IEntity"/>, allows it to take damage and deletes it after taking enough damage.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent]
|
[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;
|
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
|
||||||
#pragma warning restore 649
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
protected ActSystem _actSystem;
|
||||||
|
|
||||||
|
protected string _spawnOnDestroy;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string Name => "Destructible";
|
public override string Name => "Destructible";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Damage threshold calculated from the values
|
/// Entity spawned upon destruction.
|
||||||
/// given in the prototype declaration.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
public string SpawnOnDestroy => _spawnOnDestroy;
|
||||||
public DamageThreshold Threshold { get; private set; }
|
|
||||||
|
|
||||||
public DamageType damageType = DamageType.Total;
|
void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
|
||||||
public int damageValue = 0;
|
{
|
||||||
public string spawnOnDestroy = "";
|
if (!string.IsNullOrWhiteSpace(_spawnOnDestroy) && eventArgs.IsSpawnWreck)
|
||||||
public string destroySound = "";
|
{
|
||||||
public bool destroyed = false;
|
Owner.EntityManager.SpawnEntity(_spawnOnDestroy, Owner.Transform.GridPosition);
|
||||||
|
}
|
||||||
ActSystem _actSystem;
|
}
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
{
|
{
|
||||||
base.ExposeData(serializer);
|
base.ExposeData(serializer);
|
||||||
|
serializer.DataField(ref _spawnOnDestroy, "spawnondestroy", string.Empty);
|
||||||
serializer.DataField(ref damageValue, "thresholdvalue", 100);
|
|
||||||
serializer.DataField(ref damageType, "thresholdtype", DamageType.Total);
|
|
||||||
serializer.DataField(ref spawnOnDestroy, "spawnondestroy", "");
|
|
||||||
serializer.DataField(ref destroySound, "destroysound", "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
@@ -58,57 +52,18 @@ namespace Content.Server.GameObjects.Components.Damage
|
|||||||
_actSystem = _entitySystemManager.GetEntitySystem<ActSystem>();
|
_actSystem = _entitySystemManager.GetEntitySystem<ActSystem>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
List<DamageThreshold> IOnDamageBehavior.GetAllDamageThresholds()
|
|
||||||
{
|
|
||||||
Threshold = new DamageThreshold(damageType, damageValue, ThresholdType.Destruction);
|
|
||||||
return new List<DamageThreshold>() { Threshold };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
protected override void DestructionBehavior()
|
||||||
void IOnDamageBehavior.OnDamageThresholdPassed(object obj, DamageThresholdPassedEventArgs e)
|
|
||||||
{
|
{
|
||||||
if (e.Passed && e.DamageThreshold == Threshold && destroyed == false)
|
if (!Owner.Deleted)
|
||||||
{
|
{
|
||||||
destroyed = true;
|
|
||||||
var pos = Owner.Transform.GridPosition;
|
var pos = Owner.Transform.GridPosition;
|
||||||
_actSystem.HandleDestruction(Owner, true);
|
_actSystem.HandleDestruction(Owner,
|
||||||
if(destroySound != string.Empty)
|
true); //This will call IDestroyAct.OnDestroy on this component (and all other components on this entity)
|
||||||
|
if (DestroySound != string.Empty)
|
||||||
{
|
{
|
||||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(destroySound, pos);
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(DestroySound, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
var prob = IoCManager.Resolve<IRobustRandom>();
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Resistance set used by damageable objects.
|
|
||||||
/// For each damage type, has a coefficient, damage reduction and "included in total" value.
|
|
||||||
/// </summary>
|
|
||||||
public class ResistanceSet : IExposeData
|
|
||||||
{
|
|
||||||
public static ResistanceSet DefaultResistanceSet = new ResistanceSet();
|
|
||||||
|
|
||||||
[ViewVariables]
|
|
||||||
private readonly Dictionary<DamageType, ResistanceSetSettings> _resistances = new Dictionary<DamageType, ResistanceSetSettings>();
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adjusts input damage with the resistance set values.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="damageType">Type of the damage.</param>
|
|
||||||
/// <param name="amount">Incoming amount of the damage.</param>
|
|
||||||
/// <returns>Damage adjusted by the resistance set.</returns>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Settings for a specific damage type in a resistance set.
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// When attached to an <see cref="IEntity"/>, allows it to take damage and
|
||||||
|
/// "ruins" or "destroys" it after enough damage is taken.
|
||||||
|
/// </summary>
|
||||||
|
[ComponentReference(typeof(IDamageableComponent))]
|
||||||
|
public abstract class RuinableComponent : DamageableComponent
|
||||||
|
{
|
||||||
|
private DamageState _currentDamageState;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How much HP this component can sustain before triggering
|
||||||
|
/// <see cref="PerformDestruction"/>.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxHp { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sound played upon destruction.
|
||||||
|
/// </summary>
|
||||||
|
protected string DestroySound { get; private set; }
|
||||||
|
|
||||||
|
public override List<DamageState> SupportedDamageStates =>
|
||||||
|
new List<DamageState> {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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Destroys the Owner <see cref="IEntity"/>, setting
|
||||||
|
/// <see cref="IDamageableComponent.CurrentDamageState"/> to
|
||||||
|
/// <see cref="DamageState.Dead"/>
|
||||||
|
/// </summary>
|
||||||
|
protected void PerformDestruction()
|
||||||
|
{
|
||||||
|
_currentDamageState = DamageState.Dead;
|
||||||
|
|
||||||
|
if (!Owner.Deleted && DestroySound != string.Empty)
|
||||||
|
{
|
||||||
|
var pos = Owner.Transform.GridPosition;
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(DestroySound, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
DestructionBehavior();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void DestructionBehavior();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.GameObjects.Components.Items.Storage;
|
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.Server.GameObjects.Components.Container;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -49,7 +49,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
|||||||
}
|
}
|
||||||
|
|
||||||
return entity.HasComponent<ItemComponent>() ||
|
return entity.HasComponent<ItemComponent>() ||
|
||||||
entity.HasComponent<SpeciesComponent>();
|
entity.HasComponent<IBodyManagerComponent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryInsert(IEntity entity)
|
public bool TryInsert(IEntity entity)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
|
||||||
using Content.Shared.GameObjects.Components.Disposal;
|
using Content.Shared.GameObjects.Components.Disposal;
|
||||||
using Content.Shared.GameObjects.Verbs;
|
using Content.Shared.GameObjects.Verbs;
|
||||||
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.Interfaces;
|
||||||
using Robust.Server.Console;
|
using Robust.Server.Console;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Content.Server.GameObjects.Components.GUI;
|
using Content.Server.GameObjects.Components.GUI;
|
||||||
using Content.Server.GameObjects.Components.Items.Storage;
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
|
||||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||||
using Content.Server.Interfaces;
|
using Content.Server.Interfaces;
|
||||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
using Content.Shared.GameObjects.Components.Disposal;
|
using Content.Shared.GameObjects.Components.Disposal;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Content.Shared.GameObjects.Verbs;
|
using Content.Shared.GameObjects.Verbs;
|
||||||
@@ -122,7 +122,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!entity.HasComponent<ItemComponent>() &&
|
if (!entity.HasComponent<ItemComponent>() &&
|
||||||
!entity.HasComponent<SpeciesComponent>())
|
!entity.HasComponent<IBodyManagerComponent>())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Content.Server.GameObjects.Components.Access;
|
using Content.Server.GameObjects.Components.Access;
|
||||||
using Content.Server.GameObjects.Components.Atmos;
|
using Content.Server.GameObjects.Components.Atmos;
|
||||||
using Content.Server.GameObjects.Components.Damage;
|
|
||||||
using Content.Server.GameObjects.Components.GUI;
|
using Content.Server.GameObjects.Components.GUI;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
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.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Doors;
|
using Content.Shared.GameObjects.Components.Doors;
|
||||||
using Content.Shared.GameObjects.Components.Movement;
|
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.
|
// Disabled because it makes it suck hard to walk through double doors.
|
||||||
|
|
||||||
if (entity.HasComponent(typeof(SpeciesComponent)))
|
if (entity.HasComponent<IBodyManagerComponent>())
|
||||||
{
|
{
|
||||||
if (!entity.TryGetComponent<IMoverComponent>(out var mover)) return;
|
if (!entity.TryGetComponent<IMoverComponent>(out var mover)) return;
|
||||||
|
|
||||||
@@ -237,7 +238,7 @@ namespace Content.Server.GameObjects.Components.Doors
|
|||||||
foreach (var e in collidesWith)
|
foreach (var e in collidesWith)
|
||||||
{
|
{
|
||||||
if (!e.TryGetComponent(out StunnableComponent stun)
|
if (!e.TryGetComponent(out StunnableComponent stun)
|
||||||
|| !e.TryGetComponent(out DamageableComponent damage)
|
|| !e.TryGetComponent(out IDamageableComponent damage)
|
||||||
|| !e.TryGetComponent(out ICollidableComponent otherBody)
|
|| !e.TryGetComponent(out ICollidableComponent otherBody)
|
||||||
|| !Owner.TryGetComponent(out ICollidableComponent body))
|
|| !Owner.TryGetComponent(out ICollidableComponent body))
|
||||||
continue;
|
continue;
|
||||||
@@ -247,7 +248,7 @@ namespace Content.Server.GameObjects.Components.Doors
|
|||||||
if (percentage < 0.1f)
|
if (percentage < 0.1f)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
damage.TakeDamage(DamageType.Brute, DoorCrushDamage);
|
damage.ChangeDamage(DamageType.Blunt, DoorCrushDamage, false, Owner);
|
||||||
stun.Paralyze(DoorStunTime);
|
stun.Paralyze(DoorStunTime);
|
||||||
hitSomeone = true;
|
hitSomeone = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Content.Server.Explosions;
|
using Content.Server.Explosions;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Content.Server.GameObjects.Components.Items.Storage;
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
using Content.Server.GameObjects.Components.Weapon;
|
using Content.Server.GameObjects.Components.Weapon;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Robust.Server.GameObjects.EntitySystems;
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ using Content.Server.GameObjects.Components.Mobs;
|
|||||||
using Content.Server.GameObjects.Components.Movement;
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
using Content.Server.GameObjects.EntitySystems.Click;
|
using Content.Server.GameObjects.EntitySystems.Click;
|
||||||
using Content.Server.Interfaces.GameObjects.Components.Interaction;
|
using Content.Server.Interfaces.GameObjects.Components.Interaction;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||||
using Content.Shared.GameObjects.Components.Items;
|
using Content.Shared.GameObjects.Components.Items;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Content.Shared.Health.BodySystem;
|
|
||||||
using Content.Shared.Physics.Pull;
|
using Content.Shared.Physics.Pull;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Server.GameObjects.Components.Container;
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Content.Server.GameObjects.EntitySystems;
|
|||||||
using Content.Server.Interfaces;
|
using Content.Server.Interfaces;
|
||||||
using Content.Shared.GameObjects.Components.Gravity;
|
using Content.Shared.GameObjects.Components.Gravity;
|
||||||
using Content.Shared.GameObjects.Components.Interactable;
|
using Content.Shared.GameObjects.Components.Interactable;
|
||||||
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Server.GameObjects.Components.UserInterface;
|
using Robust.Server.GameObjects.Components.UserInterface;
|
||||||
@@ -18,7 +19,7 @@ using Robust.Shared.Serialization;
|
|||||||
namespace Content.Server.GameObjects.Components.Gravity
|
namespace Content.Server.GameObjects.Components.Gravity
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class GravityGeneratorComponent: SharedGravityGeneratorComponent, IInteractUsing, IBreakAct, IInteractHand
|
public class GravityGeneratorComponent : SharedGravityGeneratorComponent, IInteractUsing, IBreakAct, IInteractHand
|
||||||
{
|
{
|
||||||
private BoundUserInterface _userInterface;
|
private BoundUserInterface _userInterface;
|
||||||
|
|
||||||
@@ -106,10 +107,8 @@ namespace Content.Server.GameObjects.Components.Gravity
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Repair generator
|
// Repair generator
|
||||||
var damageable = Owner.GetComponent<DamageableComponent>();
|
|
||||||
var breakable = Owner.GetComponent<BreakableComponent>();
|
var breakable = Owner.GetComponent<BreakableComponent>();
|
||||||
damageable.HealAllDamage();
|
breakable.FixAllDamage();
|
||||||
breakable.broken = false;
|
|
||||||
_intact = true;
|
_intact = true;
|
||||||
|
|
||||||
var notifyManager = IoCManager.Resolve<IServerNotifyManager>();
|
var notifyManager = IoCManager.Resolve<IServerNotifyManager>();
|
||||||
@@ -130,13 +129,16 @@ namespace Content.Server.GameObjects.Components.Gravity
|
|||||||
if (!Intact)
|
if (!Intact)
|
||||||
{
|
{
|
||||||
MakeBroken();
|
MakeBroken();
|
||||||
} else if (!Powered)
|
}
|
||||||
|
else if (!Powered)
|
||||||
{
|
{
|
||||||
MakeUnpowered();
|
MakeUnpowered();
|
||||||
} else if (!SwitchedOn)
|
}
|
||||||
|
else if (!SwitchedOn)
|
||||||
{
|
{
|
||||||
MakeOff();
|
MakeOff();
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
MakeOn();
|
MakeOn();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
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;
|
return SuicideKind.Heat;
|
||||||
}
|
}
|
||||||
|
|
||||||
chat.EntityMe(victim, Loc.GetString("bashes {0:themselves} with the {1}!", victim, Owner.Name));
|
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)
|
public void SolutionChanged(SolutionChangeEventArgs eventArgs)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Content.Server.GameObjects.Components.Body;
|
||||||
using Content.Server.GameObjects.Components.GUI;
|
using Content.Server.GameObjects.Components.GUI;
|
||||||
using Content.Server.GameObjects.Components.Interactable;
|
using Content.Server.GameObjects.Components.Interactable;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
using Content.Shared.GameObjects.Components.Interactable;
|
using Content.Shared.GameObjects.Components.Interactable;
|
||||||
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
using Content.Shared.GameObjects.Components.Storage;
|
using Content.Shared.GameObjects.Components.Storage;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Content.Shared.GameObjects.Verbs;
|
using Content.Shared.GameObjects.Verbs;
|
||||||
@@ -168,7 +170,8 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// only items that can be stored in an inventory, or a mob, can be eaten by a locker
|
// only items that can be stored in an inventory, or a mob, can be eaten by a locker
|
||||||
if (!entity.HasComponent<StorableComponent>() && !entity.HasComponent<SpeciesComponent>())
|
if (!entity.HasComponent<StorableComponent>() &&
|
||||||
|
!entity.HasComponent<BodyManagerComponent>())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!AddToContents(entity))
|
if (!AddToContents(entity))
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Owner.TryGetComponent(out CollidableComponent physics) &&
|
if (Owner.TryGetComponent(out ICollidableComponent physics) &&
|
||||||
physics.Anchored)
|
physics.Anchored)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
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.Chemistry;
|
||||||
using Content.Server.GameObjects.Components.GUI;
|
using Content.Server.GameObjects.Components.GUI;
|
||||||
using Content.Server.GameObjects.Components.Items.Storage;
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
using Content.Server.Health.BodySystem;
|
|
||||||
using Content.Server.Interfaces;
|
using Content.Server.Interfaces;
|
||||||
using Content.Server.Interfaces.Chat;
|
using Content.Server.Interfaces.Chat;
|
||||||
using Content.Server.Interfaces.GameObjects;
|
using Content.Server.Interfaces.GameObjects;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
using Content.Shared.GameObjects.Components.Power;
|
using Content.Shared.GameObjects.Components.Power;
|
||||||
using Content.Shared.Health.BodySystem;
|
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.Interfaces;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
using Content.Shared.Kitchen;
|
using Content.Shared.Kitchen;
|
||||||
@@ -23,14 +25,12 @@ using Robust.Server.GameObjects.Components.UserInterface;
|
|||||||
using Robust.Server.GameObjects.EntitySystems;
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
using Robust.Server.Interfaces.GameObjects;
|
using Robust.Server.Interfaces.GameObjects;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.GameObjects.Systems;
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Timers;
|
using Robust.Shared.Timers;
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Kitchen
|
namespace Content.Server.GameObjects.Components.Kitchen
|
||||||
{
|
{
|
||||||
@@ -456,13 +456,19 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
|||||||
|
|
||||||
public SuicideKind Suicide(IEntity victim, IChatManager chat)
|
public SuicideKind Suicide(IEntity victim, IChatManager chat)
|
||||||
{
|
{
|
||||||
int headCount = 0;
|
var headCount = 0;
|
||||||
if (victim.TryGetComponent<BodyManagerComponent>(out var bodyManagerComponent))
|
if (victim.TryGetComponent<BodyManagerComponent>(out var bodyManagerComponent))
|
||||||
{
|
{
|
||||||
var heads = bodyManagerComponent.GetBodyPartsOfType(BodyPartType.Head);
|
var heads = bodyManagerComponent.GetBodyPartsOfType(BodyPartType.Head);
|
||||||
foreach (var head in heads)
|
foreach (var head in heads)
|
||||||
{
|
{
|
||||||
var droppedHead = bodyManagerComponent.DropBodyPart(head);
|
var droppedHead = bodyManagerComponent.DropPart(head);
|
||||||
|
|
||||||
|
if (droppedHead == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
_storage.Insert(droppedHead);
|
_storage.Insert(droppedHead);
|
||||||
headCount++;
|
headCount++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.GameObjects.Components.Damage;
|
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
|
||||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Medical;
|
using Content.Shared.GameObjects.Components.Medical;
|
||||||
@@ -15,7 +13,8 @@ using Robust.Server.Interfaces.GameObjects;
|
|||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
using Robust.Shared.Utility;
|
using Content.Shared.Damage;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Medical
|
namespace Content.Server.GameObjects.Components.Medical
|
||||||
{
|
{
|
||||||
@@ -35,19 +34,21 @@ namespace Content.Server.GameObjects.Components.Medical
|
|||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
_appearance = Owner.GetComponent<AppearanceComponent>();
|
_appearance = Owner.GetComponent<AppearanceComponent>();
|
||||||
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
|
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
|
||||||
.GetBoundUserInterface(MedicalScannerUiKey.Key);
|
.GetBoundUserInterface(MedicalScannerUiKey.Key);
|
||||||
_bodyContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-bodyContainer", Owner);
|
_bodyContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-bodyContainer", Owner);
|
||||||
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
|
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
|
||||||
|
|
||||||
UpdateUserInterface();
|
UpdateUserInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly MedicalScannerBoundUserInterfaceState EmptyUIState =
|
private static readonly MedicalScannerBoundUserInterfaceState EmptyUIState =
|
||||||
new MedicalScannerBoundUserInterfaceState(
|
new MedicalScannerBoundUserInterfaceState(
|
||||||
0,
|
null,
|
||||||
0,
|
new Dictionary<DamageClass, int>(),
|
||||||
null);
|
new Dictionary<DamageType, int>());
|
||||||
|
|
||||||
private MedicalScannerBoundUserInterfaceState GetUserInterfaceState()
|
private MedicalScannerBoundUserInterfaceState GetUserInterfaceState()
|
||||||
{
|
{
|
||||||
@@ -58,49 +59,36 @@ namespace Content.Server.GameObjects.Components.Medical
|
|||||||
return EmptyUIState;
|
return EmptyUIState;
|
||||||
}
|
}
|
||||||
|
|
||||||
var damageable = body.GetComponent<DamageableComponent>();
|
if (!body.TryGetComponent(out IDamageableComponent damageable) ||
|
||||||
var species = body.GetComponent<SpeciesComponent>();
|
damageable.CurrentDamageState == DamageState.Dead)
|
||||||
var deathThreshold =
|
|
||||||
species.DamageTemplate.DamageThresholds.FirstOrNull(x => x.ThresholdType == ThresholdType.Death);
|
|
||||||
if (!deathThreshold.HasValue)
|
|
||||||
{
|
{
|
||||||
return EmptyUIState;
|
return EmptyUIState;
|
||||||
}
|
}
|
||||||
|
|
||||||
var deathThresholdValue = deathThreshold.Value.Value;
|
var classes = new Dictionary<DamageClass, int>(damageable.DamageClasses);
|
||||||
var currentHealth = damageable.CurrentDamage[DamageType.Total];
|
var types = new Dictionary<DamageType, int>(damageable.DamageTypes);
|
||||||
|
|
||||||
var dmgDict = new Dictionary<string, int>();
|
return new MedicalScannerBoundUserInterfaceState(body.Uid, classes, types);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateUserInterface()
|
private void UpdateUserInterface()
|
||||||
{
|
{
|
||||||
if (!Powered)
|
if (!Powered)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var newState = GetUserInterfaceState();
|
var newState = GetUserInterfaceState();
|
||||||
_userInterface.SetState(newState);
|
_userInterface.SetState(newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MedicalScannerStatus GetStatusFromDamageState(IDamageState damageState)
|
private MedicalScannerStatus GetStatusFromDamageState(DamageState damageState)
|
||||||
{
|
{
|
||||||
switch (damageState)
|
switch (damageState)
|
||||||
{
|
{
|
||||||
case NormalState _: return MedicalScannerStatus.Green;
|
case DamageState.Alive: return MedicalScannerStatus.Green;
|
||||||
case CriticalState _: return MedicalScannerStatus.Red;
|
case DamageState.Critical: return MedicalScannerStatus.Red;
|
||||||
case DeadState _: return MedicalScannerStatus.Death;
|
case DamageState.Dead: return MedicalScannerStatus.Death;
|
||||||
default: throw new ArgumentException(nameof(damageState));
|
default: throw new ArgumentException(nameof(damageState));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,7 +97,7 @@ namespace Content.Server.GameObjects.Components.Medical
|
|||||||
var body = _bodyContainer.ContainedEntity;
|
var body = _bodyContainer.ContainedEntity;
|
||||||
return body == null
|
return body == null
|
||||||
? MedicalScannerStatus.Open
|
? MedicalScannerStatus.Open
|
||||||
: GetStatusFromDamageState(body.GetComponent<SpeciesComponent>().CurrentDamageState);
|
: GetStatusFromDamageState(body.GetComponent<IDamageableComponent>().CurrentDamageState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateAppearance()
|
private void UpdateAppearance()
|
||||||
@@ -141,7 +129,7 @@ namespace Content.Server.GameObjects.Components.Medical
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
data.Text = "Enter";
|
data.Text = Loc.GetString("Enter");
|
||||||
data.Visibility = component.IsOccupied ? VerbVisibility.Invisible : VerbVisibility.Visible;
|
data.Visibility = component.IsOccupied ? VerbVisibility.Invisible : VerbVisibility.Visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +150,7 @@ namespace Content.Server.GameObjects.Components.Medical
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
data.Text = "Eject";
|
data.Text = Loc.GetString("Eject");
|
||||||
data.Visibility = component.IsOccupied ? VerbVisibility.Visible : VerbVisibility.Invisible;
|
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
|
// There's no need to update if there's no one inside
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateUserInterface();
|
UpdateUserInterface();
|
||||||
UpdateAppearance();
|
UpdateAppearance();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
|
||||||
public class BloodstreamComponent : Component
|
|
||||||
{
|
|
||||||
#pragma warning disable 649
|
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager;
|
|
||||||
#pragma warning restore 649
|
|
||||||
|
|
||||||
public override string Name => "Bloodstream";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Internal solution for reagent storage
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables]
|
|
||||||
private SolutionComponent _internalSolution;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Max volume of internal solution storage
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables]
|
|
||||||
private ReagentUnit _initialMaxVolume;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Empty volume of internal solution
|
|
||||||
/// </summary>
|
|
||||||
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<SolutionComponent>();
|
|
||||||
_internalSolution.MaxVolume = _initialMaxVolume;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempt to transfer provided solution to internal solution. Only supports complete transfers
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="solution">Solution to be transferred</param>
|
|
||||||
/// <returns>Whether or not transfer was a success</returns>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Loops through each reagent in _internalSolution, and calls the IMetabolizable for each of them./>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tickTime">The time since the last metabolism tick in seconds.</param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Triggers metabolism of the reagents inside _internalSolution. Called by <see cref="BloodstreamSystem"/>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tickTime">The time since the last metabolism tick in seconds.</param>
|
|
||||||
public void OnUpdate(float tickTime)
|
|
||||||
{
|
|
||||||
Metabolize(tickTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<Gas, float> NeedsGases { get; set; }
|
||||||
|
|
||||||
|
[ViewVariables] public Dictionary<Gas, float> ProducesGases { get; set; }
|
||||||
|
|
||||||
|
[ViewVariables] public Dictionary<Gas, float> 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<Gas, float>());
|
||||||
|
serializer.DataField(this, b => b.ProducesGases, "producesGases", new Dictionary<Gas, float>());
|
||||||
|
serializer.DataField(this, b => b.DeficitGases, "deficitGases", new Dictionary<Gas, float>());
|
||||||
|
serializer.DataField(ref _suffocationDamage, "suffocationDamage", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<Gas, float> NeedsAndDeficit(float frameTime)
|
||||||
|
{
|
||||||
|
var needs = new Dictionary<Gas, float>(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<Gas, float>(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<Gas, float> 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loops through each reagent in _internalSolution,
|
||||||
|
/// and calls <see cref="IMetabolizable.Metabolize"/> for each of them.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="frameTime">The time since the last metabolism tick in seconds.</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes gases in the bloodstream and triggers metabolism of the
|
||||||
|
/// reagents inside of it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="frameTime">
|
||||||
|
/// The time since the last metabolism tick in seconds.
|
||||||
|
/// </param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.GameObjects.Components.Damage;
|
||||||
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Server.GameObjects.EntitySystems;
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
@@ -35,7 +35,7 @@ namespace Content.Server.GameObjects.Components.Mining
|
|||||||
var item = eventArgs.Using;
|
var item = eventArgs.Using;
|
||||||
if (!item.TryGetComponent(out MeleeWeaponComponent meleeWeaponComponent)) return false;
|
if (!item.TryGetComponent(out MeleeWeaponComponent meleeWeaponComponent)) return false;
|
||||||
|
|
||||||
Owner.GetComponent<DamageableComponent>().TakeDamage(DamageType.Brute, meleeWeaponComponent.Damage, item, eventArgs.User);
|
Owner.GetComponent<IDamageableComponent>().ChangeDamage(DamageType.Blunt, meleeWeaponComponent.Damage, false, item);
|
||||||
|
|
||||||
if (!item.TryGetComponent(out PickaxeComponent pickaxeComponent)) return true;
|
if (!item.TryGetComponent(out PickaxeComponent pickaxeComponent)) return true;
|
||||||
if (!string.IsNullOrWhiteSpace(pickaxeComponent.MiningSound))
|
if (!string.IsNullOrWhiteSpace(pickaxeComponent.MiningSound))
|
||||||
|
|||||||
@@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the blocking effect of each damage state, and what effects to apply upon entering or exiting the state
|
|
||||||
/// </summary>
|
|
||||||
public interface IDamageState : IActionBlocker
|
|
||||||
{
|
|
||||||
void EnterState(IEntity entity);
|
|
||||||
|
|
||||||
void ExitState(IEntity entity);
|
|
||||||
|
|
||||||
bool IsConscious { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Standard state that a species is at with no damage or negative effect
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A state in which you are disabled from acting due to damage
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A damage state which will allow ghosting out of mobs
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the threshold values for each damage state for any kind of species
|
|
||||||
/// </summary>
|
|
||||||
public abstract class DamageTemplates
|
|
||||||
{
|
|
||||||
public abstract List<DamageThreshold> HealthHudThresholds { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Changes the hud state when a threshold is reached
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="damage"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public abstract void ChangeHudState(DamageableComponent damage);
|
|
||||||
|
|
||||||
//public abstract ResistanceSet resistanceset { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Shows allowed states, ordered by priority, closest to last value to have threshold reached is preferred
|
|
||||||
/// </summary>
|
|
||||||
public abstract List<(DamageType, int, ThresholdType)> AllowedStates { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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
|
|
||||||
/// </summary>
|
|
||||||
public static Dictionary<ThresholdType, IDamageState> StateThresholdMap = new Dictionary<ThresholdType, IDamageState>()
|
|
||||||
{
|
|
||||||
{ ThresholdType.None, new NormalState() },
|
|
||||||
{ ThresholdType.Critical, new CriticalState() },
|
|
||||||
{ ThresholdType.Death, new DeadState() }
|
|
||||||
};
|
|
||||||
|
|
||||||
public List<DamageThreshold> DamageThresholds
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
List<DamageThreshold> thresholds = new List<DamageThreshold>();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<DamageThreshold> HealthHudThresholds
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
List<DamageThreshold> thresholds = new List<DamageThreshold>();
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,10 +13,9 @@ namespace Content.Server.GameObjects.Components.Mobs
|
|||||||
|
|
||||||
public int GetHeatResistance()
|
public int GetHeatResistance()
|
||||||
{
|
{
|
||||||
if (Owner.GetComponent<InventoryComponent>().TryGetSlotItem(EquipmentSlotDefines.Slots.GLOVES, itemComponent: out ClothingComponent gloves)
|
if (Owner.GetComponent<InventoryComponent>().TryGetSlotItem(EquipmentSlotDefines.Slots.GLOVES, itemComponent: out ClothingComponent gloves))
|
||||||
| Owner.TryGetComponent(out SpeciesComponent speciesComponent))
|
|
||||||
{
|
{
|
||||||
return Math.Max(gloves?.HeatResistance ?? int.MinValue, speciesComponent?.HeatResistance ?? int.MinValue);
|
return gloves?.HeatResistance ?? int.MinValue;
|
||||||
}
|
}
|
||||||
return int.MinValue;
|
return int.MinValue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Content.Server.GameObjects.Components.Observer;
|
using Content.Server.GameObjects.Components.Observer;
|
||||||
using Content.Server.Interfaces.GameTicking;
|
using Content.Server.Interfaces.GameTicking;
|
||||||
using Content.Server.Mobs;
|
using Content.Server.Mobs;
|
||||||
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
@@ -126,8 +127,8 @@ namespace Content.Server.GameObjects.Components.Mobs
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dead =
|
var dead =
|
||||||
Owner.TryGetComponent<SpeciesComponent>(out var species) &&
|
Owner.TryGetComponent<IDamageableComponent>(out var damageable) &&
|
||||||
species.CurrentDamageState is DeadState;
|
damageable.CurrentDamageState == DamageState.Dead;
|
||||||
|
|
||||||
if (!HasMind)
|
if (!HasMind)
|
||||||
{
|
{
|
||||||
|
|||||||
488
Content.Server/GameObjects/Components/Mobs/MobStateManager.cs
Normal file
488
Content.Server/GameObjects/Components/Mobs/MobStateManager.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// When attacked to an <see cref="IDamageableComponent"/>, 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.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
internal class MobStateManagerComponent : Component, IOnHealthChangedBehavior, IActionBlocker
|
||||||
|
{
|
||||||
|
private readonly Dictionary<DamageState, IMobState> _behavior = new Dictionary<DamageState, IMobState>
|
||||||
|
{
|
||||||
|
{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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the blocking effects of an associated <see cref="DamageState"/>
|
||||||
|
/// (i.e. Normal, Critical, Dead) and what effects to apply upon entering or
|
||||||
|
/// exiting the state.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMobState : IActionBlocker
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Called when this state is entered.
|
||||||
|
/// </summary>
|
||||||
|
void EnterState(IEntity entity);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when this state is left for a different state.
|
||||||
|
/// </summary>
|
||||||
|
void ExitState(IEntity entity);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when this state is updated.
|
||||||
|
/// </summary>
|
||||||
|
void UpdateState(IEntity entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The standard state an entity is in; no negative effects.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A state in which an entity is disabled from acting due to sufficient damage (considered unconscious).
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The state representing a dead entity; allows for ghosting.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Damagestates are reached by reaching a certain damage threshold, they will block actions after being reached
|
|
||||||
/// </summary>
|
|
||||||
public IDamageState CurrentDamageState { get; private set; } = new NormalState();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Damage state enum for current health, set only via change damage state //TODO: SETTER
|
|
||||||
/// </summary>
|
|
||||||
private ThresholdType currentstate = ThresholdType.None;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Holds the damage template which controls the threshold and resistance settings for this species type
|
|
||||||
/// </summary>
|
|
||||||
public DamageTemplates DamageTemplate { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Variable for serialization
|
|
||||||
/// </summary>
|
|
||||||
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<DamageableComponent>();
|
|
||||||
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<DamageThreshold> 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<ServerStatusEffectsComponent>() &&
|
|
||||||
!user.HasComponent<ServerOverlayEffectsComponent>())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (user.HasComponent<DamageableComponent>())
|
|
||||||
{
|
|
||||||
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<DamageableComponent>().TakeDamage(DamageType.Brute, bruteDamage, null);
|
|
||||||
Owner.GetComponent<DamageableComponent>().TakeDamage(DamageType.Heat, burnDamage, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRelayMoveInput.MoveInputPressed(ICommonSession session)
|
|
||||||
{
|
|
||||||
if (CurrentDamageState is DeadState)
|
|
||||||
{
|
|
||||||
new Ghost().Execute(null, (IPlayerSession) session, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when <see cref="SpeciesComponent.CurrentDamageState"/> changes.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class MobDamageStateChangedMessage : EntitySystemMessage
|
|
||||||
{
|
|
||||||
public MobDamageStateChangedMessage(SpeciesComponent species)
|
|
||||||
{
|
|
||||||
Species = species;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The species component that was changed.
|
|
||||||
/// </summary>
|
|
||||||
public SpeciesComponent Species { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.Components.Fluids;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
using Content.Shared.Audio;
|
using Content.Shared.Audio;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.GameObjects.Components.Body.Digestive;
|
||||||
using Content.Server.GameObjects.Components.Chemistry;
|
using Content.Server.GameObjects.Components.Chemistry;
|
||||||
using Content.Server.GameObjects.Components.GUI;
|
using Content.Server.GameObjects.Components.GUI;
|
||||||
using Content.Server.GameObjects.Components.Items.Storage;
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.GameObjects.Components.Damage;
|
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
using Content.Shared.GameObjects.Components.Movement;
|
using Content.Shared.GameObjects.Components.Movement;
|
||||||
@@ -180,11 +180,11 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
}
|
}
|
||||||
if (_currentHungerThreshold == HungerThreshold.Dead)
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.GameObjects.Components.Damage;
|
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
using Content.Shared.GameObjects.Components.Movement;
|
using Content.Shared.GameObjects.Components.Movement;
|
||||||
@@ -181,11 +181,11 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
|
|
||||||
if (_currentThirstThreshold == ThirstThreshold.Dead)
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using Content.Server.GameObjects.Components.Damage;
|
|
||||||
using Content.Server.GameObjects.Components.GUI;
|
using Content.Server.GameObjects.Components.GUI;
|
||||||
using Content.Server.GameObjects.Components.Items.Storage;
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
using Content.Server.Interfaces;
|
using Content.Server.Interfaces;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Server.GameObjects.Components.Container;
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
@@ -76,7 +76,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
|
|||||||
|
|
||||||
public bool InteractHand(InteractHandEventArgs eventArgs)
|
public bool InteractHand(InteractHandEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (!eventArgs.User.TryGetComponent(out DamageableComponent damageableComponent))
|
if (!eventArgs.User.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||||
{
|
{
|
||||||
Eject();
|
Eject();
|
||||||
return false;
|
return false;
|
||||||
@@ -99,7 +99,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
|
|||||||
|
|
||||||
void Burn()
|
void Burn()
|
||||||
{
|
{
|
||||||
damageableComponent.TakeDamage(DamageType.Heat, 20, Owner);
|
damageableComponent.ChangeDamage(DamageType.Heat, 20, false, Owner);
|
||||||
var audioSystem = EntitySystem.Get<AudioSystem>();
|
var audioSystem = EntitySystem.Get<AudioSystem>();
|
||||||
audioSystem.PlayFromEntity("/Audio/Effects/lightburn.ogg", Owner);
|
audioSystem.PlayFromEntity("/Audio/Effects/lightburn.ogg", Owner);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Interfaces.Timing;
|
using Robust.Shared.Interfaces.Timing;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.Physics;
|
using Content.Shared.Physics;
|
||||||
using Robust.Server.GameObjects.EntitySystems;
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.GameObjects.Components.Damage;
|
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Projectiles;
|
using Content.Shared.GameObjects.Components.Projectiles;
|
||||||
using Robust.Server.GameObjects.EntitySystems;
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
@@ -71,9 +71,11 @@ namespace Content.Server.GameObjects.Components.Projectiles
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
_deleteOnCollide = true;
|
_deleteOnCollide = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (_soundHitSpecies != null && entity.HasComponent<SpeciesComponent>())
|
if (_soundHitSpecies != null && entity.HasComponent<IDamageableComponent>())
|
||||||
{
|
{
|
||||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundHitSpecies, entity.Transform.GridPosition);
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundHitSpecies, entity.Transform.GridPosition);
|
||||||
} else if (_soundHit != null)
|
} else if (_soundHit != null)
|
||||||
@@ -81,13 +83,13 @@ namespace Content.Server.GameObjects.Components.Projectiles
|
|||||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundHit, entity.Transform.GridPosition);
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundHit, entity.Transform.GridPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity.TryGetComponent(out DamageableComponent damage))
|
if (entity.TryGetComponent(out IDamageableComponent damage))
|
||||||
{
|
{
|
||||||
Owner.EntityManager.TryGetEntity(_shooter, out var shooter);
|
Owner.EntityManager.TryGetEntity(_shooter, out var shooter);
|
||||||
|
|
||||||
foreach (var (damageType, amount) in _damages)
|
foreach (var (damageType, amount) in _damages)
|
||||||
{
|
{
|
||||||
damage.TakeDamage(damageType, amount, Owner, shooter);
|
damage.ChangeDamage(damageType, amount, false, shooter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Content.Shared.Physics;
|
using Content.Shared.Physics;
|
||||||
@@ -40,9 +40,9 @@ namespace Content.Server.GameObjects.Components.Projectiles
|
|||||||
|
|
||||||
_shouldStop = true; // hit something hard => stop after this collision
|
_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
|
// Stop colliding with mobs, this mimics not having enough velocity to do damage
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user