Change all of body system to use entities and components (#2074)
* Early commit * Early commit 2 * merging master broke my git * does anyone even read these * life is fleeting * it just works * this time passing integration tests * Remove hashset yaml serialization for now * You got a license for those nullables? * No examine, no context menu, part and mechanism parenting and visibility * Fix wrong brain sprite state * Removing layers was a mistake * just tear body system a new one and see if it still breathes * Remove redundant code * Add that comment back * Separate damage and body, component states, stomach rework * Add containers for body parts * Bring layers back pls * Fix parts magically changing color * Reimplement sprite layer visibility * Fix tests * Add leg test * Active legs is gone Crab rave * Merge fixes, rename DamageState to CurrentState * Remove IShowContextMenu and ICanExamine
This commit is contained in:
@@ -63,9 +63,6 @@ namespace Content.Client
|
|||||||
factory.Register<SharedResearchConsoleComponent>();
|
factory.Register<SharedResearchConsoleComponent>();
|
||||||
factory.Register<SharedLatheComponent>();
|
factory.Register<SharedLatheComponent>();
|
||||||
factory.Register<SharedSpawnPointComponent>();
|
factory.Register<SharedSpawnPointComponent>();
|
||||||
|
|
||||||
factory.Register<SharedSolutionContainerComponent>();
|
|
||||||
|
|
||||||
factory.Register<SharedVendingMachineComponent>();
|
factory.Register<SharedVendingMachineComponent>();
|
||||||
factory.Register<SharedWiresComponent>();
|
factory.Register<SharedWiresComponent>();
|
||||||
factory.Register<SharedCargoConsoleComponent>();
|
factory.Register<SharedCargoConsoleComponent>();
|
||||||
|
|||||||
30
Content.Client/GameObjects/Components/Body/BodyComponent.cs
Normal file
30
Content.Client/GameObjects/Components/Body/BodyComponent.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Content.Client.GameObjects.Components.Disposal;
|
||||||
|
using Content.Client.GameObjects.Components.MedicalScanner;
|
||||||
|
using Content.Client.Interfaces.GameObjects.Components.Interaction;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Body
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(IBody))]
|
||||||
|
public class BodyComponent : SharedBodyComponent, IClientDraggable
|
||||||
|
{
|
||||||
|
public bool ClientCanDropOn(CanDropEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (eventArgs.Target.HasComponent<DisposalUnitComponent>() ||
|
||||||
|
eventArgs.Target.HasComponent<MedicalScannerComponent>())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ClientCanDrag(CanDragEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
#nullable enable
|
|
||||||
using Content.Client.GameObjects.Components.Disposal;
|
|
||||||
using Content.Client.GameObjects.Components.MedicalScanner;
|
|
||||||
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(ISharedBodyManagerComponent))]
|
|
||||||
public class BodyManagerComponent : SharedBodyManagerComponent, IClientDraggable
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
|
||||||
|
|
||||||
public bool ClientCanDropOn(CanDropEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
if (
|
|
||||||
eventArgs.Target.HasComponent<DisposalUnitComponent>()||
|
|
||||||
eventArgs.Target.HasComponent<MedicalScannerComponent>())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Body.Behavior;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Body.Mechanism
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedHeartBehaviorComponent))]
|
||||||
|
public class HeartBehaviorComponent : SharedHeartBehaviorComponent
|
||||||
|
{
|
||||||
|
public override void Update(float frameTime) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Mechanism;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Part;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Body.Mechanism
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedMechanismComponent))]
|
||||||
|
[ComponentReference(typeof(IMechanism))]
|
||||||
|
public class MechanismComponent : SharedMechanismComponent
|
||||||
|
{
|
||||||
|
protected override void OnPartAdd(IBodyPart? old, IBodyPart current)
|
||||||
|
{
|
||||||
|
base.OnPartAdd(old, current);
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out ISpriteComponent? sprite))
|
||||||
|
{
|
||||||
|
sprite.Visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPartRemove(IBodyPart old)
|
||||||
|
{
|
||||||
|
base.OnPartRemove(old);
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out ISpriteComponent? sprite))
|
||||||
|
{
|
||||||
|
sprite.Visible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Part;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Body.Part
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedBodyPartComponent))]
|
||||||
|
[ComponentReference(typeof(IBodyPart))]
|
||||||
|
public class BodyPartComponent : SharedBodyPartComponent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
using Content.Shared.Body.Scanner;
|
using Content.Shared.GameObjects.Components.Body.Scanner;
|
||||||
using JetBrains.Annotations;
|
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.Interfaces.GameObjects;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Client.GameObjects.Components.Body.Scanner
|
namespace Content.Client.GameObjects.Components.Body.Scanner
|
||||||
@@ -14,10 +15,7 @@ namespace Content.Client.GameObjects.Components.Body.Scanner
|
|||||||
private BodyScannerDisplay _display;
|
private BodyScannerDisplay _display;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private BodyScannerTemplateData _template;
|
private IEntity _entity;
|
||||||
|
|
||||||
[ViewVariables]
|
|
||||||
private Dictionary<string, BodyScannerBodyPartData> _parts;
|
|
||||||
|
|
||||||
public BodyScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
|
public BodyScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
|
||||||
|
|
||||||
@@ -33,15 +31,17 @@ namespace Content.Client.GameObjects.Components.Body.Scanner
|
|||||||
{
|
{
|
||||||
base.UpdateState(state);
|
base.UpdateState(state);
|
||||||
|
|
||||||
if (!(state is BodyScannerInterfaceState scannerState))
|
if (!(state is BodyScannerUIState scannerState))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_template = scannerState.Template;
|
if (!Owner.Owner.EntityManager.TryGetEntity(scannerState.Uid, out _entity))
|
||||||
_parts = scannerState.Parts;
|
{
|
||||||
|
throw new ArgumentException($"Received an invalid entity with id {scannerState.Uid} for body scanner with id {Owner.Owner.Uid} at {Owner.Owner.Transform.MapPosition}");
|
||||||
|
}
|
||||||
|
|
||||||
_display.UpdateDisplay(_template, _parts);
|
_display.UpdateDisplay(_entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
@@ -51,8 +51,6 @@ namespace Content.Client.GameObjects.Components.Body.Scanner
|
|||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
_display?.Dispose();
|
_display?.Dispose();
|
||||||
_template = null;
|
|
||||||
_parts.Clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
using System.Collections.Generic;
|
#nullable enable
|
||||||
using Content.Shared.Body.Scanner;
|
using System.Linq;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Mechanism;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Part;
|
||||||
|
using Content.Shared.GameObjects.Components.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.IoC;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
@@ -11,13 +16,10 @@ namespace Content.Client.GameObjects.Components.Body.Scanner
|
|||||||
{
|
{
|
||||||
public sealed class BodyScannerDisplay : SS14Window
|
public sealed class BodyScannerDisplay : SS14Window
|
||||||
{
|
{
|
||||||
private BodyScannerTemplateData _template;
|
private IEntity? _currentEntity;
|
||||||
|
private IBodyPart? _currentBodyPart;
|
||||||
|
|
||||||
private Dictionary<string, BodyScannerBodyPartData> _parts;
|
private IBody? CurrentBody => _currentEntity?.GetBody();
|
||||||
|
|
||||||
private List<string> _slots;
|
|
||||||
|
|
||||||
private BodyScannerBodyPartData _currentBodyPart;
|
|
||||||
|
|
||||||
public BodyScannerDisplay(BodyScannerBoundUserInterface owner)
|
public BodyScannerDisplay(BodyScannerBoundUserInterface owner)
|
||||||
{
|
{
|
||||||
@@ -102,51 +104,70 @@ namespace Content.Client.GameObjects.Components.Body.Scanner
|
|||||||
|
|
||||||
private RichTextLabel MechanismInfoLabel { get; }
|
private RichTextLabel MechanismInfoLabel { get; }
|
||||||
|
|
||||||
public void UpdateDisplay(BodyScannerTemplateData template, Dictionary<string, BodyScannerBodyPartData> parts)
|
public void UpdateDisplay(IEntity entity)
|
||||||
{
|
{
|
||||||
_template = template;
|
_currentEntity = entity;
|
||||||
_parts = parts;
|
|
||||||
_slots = new List<string>();
|
|
||||||
BodyPartList.Clear();
|
BodyPartList.Clear();
|
||||||
|
|
||||||
foreach (var slotName in _parts.Keys)
|
var body = CurrentBody;
|
||||||
{
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
|
if (body == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var slotName in body.Parts.Keys)
|
||||||
|
{
|
||||||
BodyPartList.AddItem(Loc.GetString(slotName));
|
BodyPartList.AddItem(Loc.GetString(slotName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BodyPartOnItemSelected(ItemListSelectedEventArgs args)
|
public void BodyPartOnItemSelected(ItemListSelectedEventArgs args)
|
||||||
{
|
{
|
||||||
if (_parts.TryGetValue(_slots[args.ItemIndex], out _currentBodyPart)) {
|
var body = CurrentBody;
|
||||||
UpdateBodyPartBox(_currentBodyPart, _slots[args.ItemIndex]);
|
|
||||||
|
if (body == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var slot = body.SlotAt(args.ItemIndex).Key;
|
||||||
|
_currentBodyPart = body.PartAt(args.ItemIndex).Value;
|
||||||
|
|
||||||
|
if (body.Parts.TryGetValue(slot, out var part))
|
||||||
|
{
|
||||||
|
UpdateBodyPartBox(part, slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateBodyPartBox(BodyScannerBodyPartData part, string slotName)
|
private void UpdateBodyPartBox(IBodyPart part, string slotName)
|
||||||
{
|
{
|
||||||
BodyPartLabel.Text = $"{Loc.GetString(slotName)}: {Loc.GetString(part.Name)}";
|
BodyPartLabel.Text = $"{Loc.GetString(slotName)}: {Loc.GetString(part.Owner.Name)}";
|
||||||
BodyPartHealth.Text = $"{part.CurrentDurability}/{part.MaxDurability}";
|
|
||||||
|
// TODO BODY Make dead not be the destroy threshold for a body part
|
||||||
|
if (part.Owner.TryGetComponent(out IDamageableComponent? damageable) &&
|
||||||
|
damageable.TryHealth(DamageState.Critical, out var health))
|
||||||
|
{
|
||||||
|
BodyPartHealth.Text = $"{health.current} / {health.max}";
|
||||||
|
}
|
||||||
|
|
||||||
MechanismList.Clear();
|
MechanismList.Clear();
|
||||||
foreach (var mechanism in part.Mechanisms) {
|
|
||||||
|
foreach (var mechanism in part.Mechanisms)
|
||||||
|
{
|
||||||
MechanismList.AddItem(mechanism.Name);
|
MechanismList.AddItem(mechanism.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO BODY Guaranteed this is going to crash when a part's mechanisms change. This part is left as an exercise for the reader.
|
||||||
public void MechanismOnItemSelected(ItemListSelectedEventArgs args)
|
public void MechanismOnItemSelected(ItemListSelectedEventArgs args)
|
||||||
{
|
{
|
||||||
UpdateMechanismBox(_currentBodyPart.Mechanisms[args.ItemIndex]);
|
UpdateMechanismBox(_currentBodyPart?.Mechanisms.ElementAt(args.ItemIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateMechanismBox(BodyScannerMechanismData mechanism)
|
private void UpdateMechanismBox(IMechanism? mechanism)
|
||||||
{
|
{
|
||||||
// TODO: Improve UI
|
// TODO BODY Improve UI
|
||||||
if (mechanism == null)
|
if (mechanism == null)
|
||||||
{
|
{
|
||||||
MechanismInfoLabel.SetMessage("");
|
MechanismInfoLabel.SetMessage("");
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using Content.Shared.Body.Surgery;
|
using Content.Shared.GameObjects.Components.Body.Surgery;
|
||||||
using JetBrains.Annotations;
|
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.GameObjects.Components.Body.Surgery
|
namespace Content.Client.GameObjects.Components.Body.Surgery
|
||||||
{
|
{
|
||||||
// TODO : Make window close if target or surgery tool gets too far away from user.
|
// TODO BODY 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
|
/// Generic client-side UI list popup that allows users to choose from an option
|
||||||
/// of limbs or organs to operate on.
|
/// of limbs or organs to operate on.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public class GenericSurgeryBoundUserInterface : BoundUserInterface
|
public class SurgeryBoundUserInterface : BoundUserInterface
|
||||||
{
|
{
|
||||||
private GenericSurgeryWindow? _window;
|
private SurgeryWindow? _window;
|
||||||
|
|
||||||
public GenericSurgeryBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
|
public SurgeryBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
|
||||||
|
|
||||||
protected override void Open()
|
protected override void Open()
|
||||||
{
|
{
|
||||||
_window = new GenericSurgeryWindow();
|
_window = new SurgeryWindow();
|
||||||
|
|
||||||
_window.OpenCentered();
|
_window.OpenCentered();
|
||||||
_window.OnClose += Close;
|
_window.OnClose += Close;
|
||||||
@@ -8,7 +8,7 @@ using Robust.Shared.Maths;
|
|||||||
|
|
||||||
namespace Content.Client.GameObjects.Components.Body.Surgery
|
namespace Content.Client.GameObjects.Components.Body.Surgery
|
||||||
{
|
{
|
||||||
public class GenericSurgeryWindow : SS14Window
|
public class SurgeryWindow : SS14Window
|
||||||
{
|
{
|
||||||
public delegate void OptionSelectedCallback(int selectedOptionData);
|
public delegate void OptionSelectedCallback(int selectedOptionData);
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ namespace Content.Client.GameObjects.Components.Body.Surgery
|
|||||||
|
|
||||||
protected override Vector2? CustomSize => (300, 400);
|
protected override Vector2? CustomSize => (300, 400);
|
||||||
|
|
||||||
public GenericSurgeryWindow()
|
public SurgeryWindow()
|
||||||
{
|
{
|
||||||
Title = Loc.GetString("Select surgery target...");
|
Title = Loc.GetString("Select surgery target...");
|
||||||
RectClipContent = true;
|
RectClipContent = true;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Content.Shared.Chemistry;
|
||||||
|
using Content.Shared.GameObjects.Components.Chemistry;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Chemistry
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedSolutionContainerComponent))]
|
||||||
|
public class SolutionContainerComponent : SharedSolutionContainerComponent
|
||||||
|
{
|
||||||
|
public override bool CanAddSolution(Solution solution)
|
||||||
|
{
|
||||||
|
// TODO CLIENT
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool TryAddSolution(Solution solution, bool skipReactionCheck = false, bool skipColor = false)
|
||||||
|
{
|
||||||
|
// TODO CLIENT
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool TryRemoveReagent(string reagentId, ReagentUnit quantity)
|
||||||
|
{
|
||||||
|
// TODO CLIENT
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,11 +3,9 @@ using Robust.Shared.GameObjects;
|
|||||||
|
|
||||||
namespace Content.Client.GameObjects.Components.MedicalScanner
|
namespace Content.Client.GameObjects.Components.MedicalScanner
|
||||||
{
|
{
|
||||||
|
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
[ComponentReference(typeof(SharedMedicalScannerComponent))]
|
[ComponentReference(typeof(SharedMedicalScannerComponent))]
|
||||||
public class MedicalScannerComponent : SharedMedicalScannerComponent
|
public class MedicalScannerComponent : SharedMedicalScannerComponent
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using Content.Client.GameObjects.Components.ActionBlocking;
|
using Content.Client.GameObjects.Components.ActionBlocking;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Part;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Preferences.Appearance;
|
using Content.Shared.Preferences.Appearance;
|
||||||
@@ -8,7 +10,7 @@ using Robust.Shared.GameObjects;
|
|||||||
namespace Content.Client.GameObjects.Components.Mobs
|
namespace Content.Client.GameObjects.Components.Mobs
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed class HumanoidAppearanceComponent : SharedHumanoidAppearanceComponent
|
public sealed class HumanoidAppearanceComponent : SharedHumanoidAppearanceComponent, IBodyPartAdded, IBodyPartRemoved
|
||||||
{
|
{
|
||||||
public override HumanoidCharacterAppearance Appearance
|
public override HumanoidCharacterAppearance Appearance
|
||||||
{
|
{
|
||||||
@@ -39,8 +41,24 @@ namespace Content.Client.GameObjects.Components.Mobs
|
|||||||
|
|
||||||
private void UpdateLooks()
|
private void UpdateLooks()
|
||||||
{
|
{
|
||||||
if (Appearance is null) return;
|
if (Appearance is null ||
|
||||||
var sprite = Owner.GetComponent<SpriteComponent>();
|
!Owner.TryGetComponent(out SpriteComponent sprite))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Owner.TryGetBody(out var body))
|
||||||
|
{
|
||||||
|
foreach (var part in body.Parts.Values)
|
||||||
|
{
|
||||||
|
if (!part.Owner.TryGetComponent(out SpriteComponent partSprite))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
partSprite.Color = Appearance.SkinColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sprite.LayerSetColor(HumanoidVisualLayers.Hair, Appearance.HairColor);
|
sprite.LayerSetColor(HumanoidVisualLayers.Hair, Appearance.HairColor);
|
||||||
sprite.LayerSetColor(HumanoidVisualLayers.FacialHair, Appearance.FacialHairColor);
|
sprite.LayerSetColor(HumanoidVisualLayers.FacialHair, Appearance.FacialHairColor);
|
||||||
@@ -71,5 +89,51 @@ namespace Content.Client.GameObjects.Components.Mobs
|
|||||||
sprite.LayerSetState(HumanoidVisualLayers.FacialHair,
|
sprite.LayerSetState(HumanoidVisualLayers.FacialHair,
|
||||||
HairStyles.FacialHairStylesMap[facialHairStyle]);
|
HairStyles.FacialHairStylesMap[facialHairStyle]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void BodyPartAdded(BodyPartAddedEventArgs args)
|
||||||
|
{
|
||||||
|
if (!Owner.TryGetComponent(out SpriteComponent sprite))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args.Part.Owner.TryGetComponent(out SpriteComponent partSprite))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var layer = args.Part.ToHumanoidLayer();
|
||||||
|
|
||||||
|
if (layer == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO BODY Layer color, sprite and state
|
||||||
|
sprite.LayerSetVisible(layer, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BodyPartRemoved(BodyPartRemovedEventArgs args)
|
||||||
|
{
|
||||||
|
if (!Owner.TryGetComponent(out SpriteComponent sprite))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args.Part.Owner.TryGetComponent(out SpriteComponent partSprite))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var layer = args.Part.ToHumanoidLayer();
|
||||||
|
|
||||||
|
if (layer == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO BODY Layer color, sprite and state
|
||||||
|
sprite.LayerSetVisible(layer, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,6 +182,7 @@
|
|||||||
"BreakableConstruction",
|
"BreakableConstruction",
|
||||||
"GasCanister",
|
"GasCanister",
|
||||||
"GasCanisterPort",
|
"GasCanisterPort",
|
||||||
|
"Lung",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
59
Content.IntegrationTests/Tests/Body/LegTest.cs
Normal file
59
Content.IntegrationTests/Tests/Body/LegTest.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Part;
|
||||||
|
using Content.Shared.GameObjects.Components.Rotation;
|
||||||
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Map;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
|
namespace Content.IntegrationTests.Tests.Body
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
[TestOf(typeof(SharedBodyComponent))]
|
||||||
|
[TestOf(typeof(BodyComponent))]
|
||||||
|
public class LegTest : ContentIntegrationTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public async Task RemoveLegsFallTest()
|
||||||
|
{
|
||||||
|
var server = StartServerDummyTicker();
|
||||||
|
|
||||||
|
AppearanceComponent appearance = null;
|
||||||
|
|
||||||
|
await server.WaitAssertion(() =>
|
||||||
|
{
|
||||||
|
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||||
|
|
||||||
|
var mapId = new MapId(0);
|
||||||
|
mapManager.CreateNewMapEntity(mapId);
|
||||||
|
|
||||||
|
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||||
|
var human = entityManager.SpawnEntity("HumanMob_Content", MapCoordinates.Nullspace);
|
||||||
|
|
||||||
|
Assert.That(human.TryGetBody(out var body));
|
||||||
|
Assert.That(human.TryGetComponent(out appearance));
|
||||||
|
|
||||||
|
Assert.That(!appearance.TryGetData(RotationVisuals.RotationState, out RotationState _));
|
||||||
|
|
||||||
|
var legs = body.GetPartsOfType(BodyPartType.Leg);
|
||||||
|
|
||||||
|
foreach (var leg in legs)
|
||||||
|
{
|
||||||
|
body.RemovePart(leg, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.WaitAssertion(() =>
|
||||||
|
{
|
||||||
|
Assert.That(appearance.TryGetData(RotationVisuals.RotationState, out RotationState state));
|
||||||
|
Assert.That(state, Is.EqualTo(RotationState.Horizontal));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
using System.Linq;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.Atmos;
|
using Content.Server.Atmos;
|
||||||
|
using Content.Server.GameObjects.Components.Body.Behavior;
|
||||||
using Content.Server.GameObjects.Components.Body.Circulatory;
|
using Content.Server.GameObjects.Components.Body.Circulatory;
|
||||||
using Content.Server.GameObjects.Components.Body.Respiratory;
|
|
||||||
using Content.Server.GameObjects.Components.Metabolism;
|
using Content.Server.GameObjects.Components.Metabolism;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Mechanism;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Robust.Server.Interfaces.Maps;
|
using Robust.Server.Interfaces.Maps;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
@@ -13,10 +15,10 @@ using Robust.Shared.IoC;
|
|||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
namespace Content.IntegrationTests.Tests
|
namespace Content.IntegrationTests.Tests.Body
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
[TestOf(typeof(LungComponent))]
|
[TestOf(typeof(LungBehaviorComponent))]
|
||||||
public class LungTest : ContentIntegrationTest
|
public class LungTest : ContentIntegrationTest
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
@@ -34,7 +36,8 @@ namespace Content.IntegrationTests.Tests
|
|||||||
|
|
||||||
var human = entityManager.SpawnEntity("HumanMob_Content", MapCoordinates.Nullspace);
|
var human = entityManager.SpawnEntity("HumanMob_Content", MapCoordinates.Nullspace);
|
||||||
|
|
||||||
Assert.True(human.TryGetComponent(out LungComponent lung));
|
Assert.True(human.TryGetMechanismBehaviors(out List<LungBehaviorComponent> lungs));
|
||||||
|
Assert.That(lungs.Count, Is.EqualTo(1));
|
||||||
Assert.True(human.TryGetComponent(out BloodstreamComponent bloodstream));
|
Assert.True(human.TryGetComponent(out BloodstreamComponent bloodstream));
|
||||||
|
|
||||||
var gas = new GasMixture(1);
|
var gas = new GasMixture(1);
|
||||||
@@ -46,6 +49,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
gas.AdjustMoles(Gas.Oxygen, originalOxygen);
|
gas.AdjustMoles(Gas.Oxygen, originalOxygen);
|
||||||
gas.AdjustMoles(Gas.Nitrogen, originalNitrogen);
|
gas.AdjustMoles(Gas.Nitrogen, originalNitrogen);
|
||||||
|
|
||||||
|
var lung = lungs[0];
|
||||||
lung.Inhale(1, gas);
|
lung.Inhale(1, gas);
|
||||||
|
|
||||||
var lungOxygen = originalOxygen * breathedPercentage;
|
var lungOxygen = originalOxygen * breathedPercentage;
|
||||||
@@ -114,7 +118,6 @@ namespace Content.IntegrationTests.Tests
|
|||||||
|
|
||||||
MapId mapId;
|
MapId mapId;
|
||||||
IMapGrid grid = null;
|
IMapGrid grid = null;
|
||||||
LungComponent lung = null;
|
|
||||||
MetabolismComponent metabolism = null;
|
MetabolismComponent metabolism = null;
|
||||||
IEntity human = null;
|
IEntity human = null;
|
||||||
|
|
||||||
@@ -134,7 +137,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
var coordinates = new EntityCoordinates(grid.GridEntityId, center);
|
var coordinates = new EntityCoordinates(grid.GridEntityId, center);
|
||||||
human = entityManager.SpawnEntity("HumanMob_Content", coordinates);
|
human = entityManager.SpawnEntity("HumanMob_Content", coordinates);
|
||||||
|
|
||||||
Assert.True(human.TryGetComponent(out lung));
|
Assert.True(human.HasMechanismBehavior<LungBehaviorComponent>());
|
||||||
Assert.True(human.TryGetComponent(out metabolism));
|
Assert.True(human.TryGetComponent(out metabolism));
|
||||||
Assert.False(metabolism.Suffocating);
|
Assert.False(metabolism.Suffocating);
|
||||||
});
|
});
|
||||||
@@ -4,6 +4,8 @@ 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.Strap;
|
using Content.Server.GameObjects.Components.Strap;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Part;
|
||||||
using Content.Shared.GameObjects.Components.Buckle;
|
using Content.Shared.GameObjects.Components.Buckle;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
@@ -182,7 +184,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
BuckleComponent buckle = null;
|
BuckleComponent buckle = null;
|
||||||
StrapComponent strap = null;
|
StrapComponent strap = null;
|
||||||
HandsComponent hands = null;
|
HandsComponent hands = null;
|
||||||
IDamageableComponent humanDamageable = null;
|
IBody body = null;
|
||||||
|
|
||||||
server.Assert(() =>
|
server.Assert(() =>
|
||||||
{
|
{
|
||||||
@@ -208,7 +210,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
Assert.True(human.TryGetComponent(out buckle));
|
Assert.True(human.TryGetComponent(out buckle));
|
||||||
Assert.True(chair.TryGetComponent(out strap));
|
Assert.True(chair.TryGetComponent(out strap));
|
||||||
Assert.True(human.TryGetComponent(out hands));
|
Assert.True(human.TryGetComponent(out hands));
|
||||||
Assert.True(human.TryGetComponent(out humanDamageable));
|
Assert.True(human.TryGetBody(out body));
|
||||||
|
|
||||||
// Buckle
|
// Buckle
|
||||||
Assert.True(buckle.TryBuckle(human, chair));
|
Assert.True(buckle.TryBuckle(human, chair));
|
||||||
@@ -239,8 +241,13 @@ namespace Content.IntegrationTests.Tests
|
|||||||
Assert.NotNull(hands.GetItem(slot));
|
Assert.NotNull(hands.GetItem(slot));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Banish our guy into the shadow realm
|
var legs = body.GetPartsOfType(BodyPartType.Leg);
|
||||||
humanDamageable.ChangeDamage(DamageClass.Brute, 1000000, true);
|
|
||||||
|
// Break our guy's kneecaps
|
||||||
|
foreach (var leg in legs)
|
||||||
|
{
|
||||||
|
body.RemovePart(leg, false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.RunTicks(10);
|
server.RunTicks(10);
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Client.GameObjects.Components.Items;
|
using Content.Client.GameObjects.Components.Items;
|
||||||
using Content.Server.Body;
|
|
||||||
using Content.Server.GameObjects.Components.ActionBlocking;
|
using Content.Server.GameObjects.Components.ActionBlocking;
|
||||||
using Content.Server.GameObjects.Components.Body;
|
using Content.Server.GameObjects.Components.Body;
|
||||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||||
using Content.Shared.Body.Part;
|
|
||||||
using Content.Shared.GameObjects.Components.Body;
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Robust.Server.Interfaces.Console;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.Interfaces.Map;
|
using Robust.Shared.Interfaces.Map;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
|
namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
|
||||||
{
|
{
|
||||||
@@ -36,7 +33,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
|
|||||||
HandcuffComponent handcuff;
|
HandcuffComponent handcuff;
|
||||||
CuffableComponent cuffed;
|
CuffableComponent cuffed;
|
||||||
IHandsComponent hands;
|
IHandsComponent hands;
|
||||||
BodyManagerComponent body;
|
IBody body;
|
||||||
|
|
||||||
server.Assert(() =>
|
server.Assert(() =>
|
||||||
{
|
{
|
||||||
@@ -56,7 +53,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
|
|||||||
// Test for components existing
|
// Test for components existing
|
||||||
Assert.True(human.TryGetComponent(out cuffed!), $"Human has no {nameof(CuffableComponent)}");
|
Assert.True(human.TryGetComponent(out cuffed!), $"Human has no {nameof(CuffableComponent)}");
|
||||||
Assert.True(human.TryGetComponent(out hands!), $"Human has no {nameof(HandsComponent)}");
|
Assert.True(human.TryGetComponent(out hands!), $"Human has no {nameof(HandsComponent)}");
|
||||||
Assert.True(human.TryGetComponent(out body!), $"Human has no {nameof(BodyManagerComponent)}");
|
Assert.True(human.TryGetBody(out body!), $"Human has no {nameof(IBody)}");
|
||||||
Assert.True(cuffs.TryGetComponent(out handcuff!), $"Handcuff has no {nameof(HandcuffComponent)}");
|
Assert.True(cuffs.TryGetComponent(out handcuff!), $"Handcuff has no {nameof(HandcuffComponent)}");
|
||||||
Assert.True(cables.TryGetComponent(out cableHandcuff!), $"Cablecuff has no {nameof(HandcuffComponent)}");
|
Assert.True(cables.TryGetComponent(out cableHandcuff!), $"Cablecuff has no {nameof(HandcuffComponent)}");
|
||||||
|
|
||||||
@@ -65,8 +62,8 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
|
|||||||
Assert.True(cuffed.CuffedHandCount > 0, "Handcuffing a player did not result in their hands being cuffed");
|
Assert.True(cuffed.CuffedHandCount > 0, "Handcuffing a player did not result in their hands being cuffed");
|
||||||
|
|
||||||
// Test to ensure a player with 4 hands will still only have 2 hands cuffed
|
// Test to ensure a player with 4 hands will still only have 2 hands cuffed
|
||||||
AddHand(body);
|
AddHand(cuffed.Owner);
|
||||||
AddHand(body);
|
AddHand(cuffed.Owner);
|
||||||
Assert.True(cuffed.CuffedHandCount == 2 && hands.Hands.Count() == 4, "Player doesn't have correct amount of hands cuffed");
|
Assert.True(cuffed.CuffedHandCount == 2 && hands.Hands.Count() == 4, "Player doesn't have correct amount of hands cuffed");
|
||||||
|
|
||||||
// Test to give a player with 4 hands 2 sets of cuffs
|
// Test to give a player with 4 hands 2 sets of cuffs
|
||||||
@@ -78,16 +75,10 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
|
|||||||
await server.WaitIdleAsync();
|
await server.WaitIdleAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddHand(BodyManagerComponent body)
|
private void AddHand(IEntity to)
|
||||||
{
|
{
|
||||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
var shell = IoCManager.Resolve<IConsoleShell>();
|
||||||
prototypeManager.TryIndex("bodyPart.LHand.BasicHuman", out BodyPartPrototype prototype);
|
shell.ExecuteCommand($"addhand {to.Uid}");
|
||||||
|
|
||||||
var part = new BodyPart(prototype);
|
|
||||||
var slot = part.GetHashCode().ToString();
|
|
||||||
|
|
||||||
body.Template.Slots.Add(slot, BodyPartType.Hand);
|
|
||||||
body.TryAddPart(slot, part, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ namespace Content.IntegrationTests.Tests
|
|||||||
entities = tileLookup.GetEntitiesIntersecting(gridOne.Index, new MapIndices(1000, 1000)).ToList();
|
entities = tileLookup.GetEntitiesIntersecting(gridOne.Index, new MapIndices(1000, 1000)).ToList();
|
||||||
Assert.That(entities.Count, Is.EqualTo(0));
|
Assert.That(entities.Count, Is.EqualTo(0));
|
||||||
|
|
||||||
var entityOne = entityManager.SpawnEntity("HumanMob_Content", new EntityCoordinates(gridOne.GridEntityId, Vector2.Zero));
|
var entityOne = entityManager.SpawnEntity("Food4NoRaisins", new EntityCoordinates(gridOne.GridEntityId, Vector2.Zero));
|
||||||
entityManager.SpawnEntity("HumanMob_Content", new EntityCoordinates(gridOne.GridEntityId, Vector2.One));
|
entityManager.SpawnEntity("Food4NoRaisins", new EntityCoordinates(gridOne.GridEntityId, Vector2.One));
|
||||||
|
|
||||||
var entityTiles = tileLookup.GetIndices(entityOne);
|
var entityTiles = tileLookup.GetIndices(entityOne);
|
||||||
Assert.That(entityTiles.Count, Is.EqualTo(2));
|
Assert.That(entityTiles.Count, Is.EqualTo(2));
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ namespace Content.Server.AI.Utility.AiLogic
|
|||||||
private void DeathHandle(HealthChangedEventArgs eventArgs)
|
private void DeathHandle(HealthChangedEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
var oldDeadState = _isDead;
|
var oldDeadState = _isDead;
|
||||||
_isDead = eventArgs.Damageable.CurrentDamageState == DamageState.Dead || eventArgs.Damageable.CurrentDamageState == DamageState.Critical;
|
_isDead = eventArgs.Damageable.CurrentState == DamageState.Dead || eventArgs.Damageable.CurrentState == DamageState.Critical;
|
||||||
|
|
||||||
if (oldDeadState != _isDead)
|
if (oldDeadState != _isDead)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Content.Server.AI.Utility.Considerations.Combat
|
|||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (damageableComponent.CurrentDamageState == DamageState.Critical)
|
if (damageableComponent.CurrentState == DamageState.Critical)
|
||||||
{
|
{
|
||||||
return 1.0f;
|
return 1.0f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Content.Server.AI.Utility.Considerations.Combat
|
|||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (damageableComponent.CurrentDamageState == DamageState.Dead)
|
if (damageableComponent.CurrentState == DamageState.Dead)
|
||||||
{
|
{
|
||||||
return 1.0f;
|
return 1.0f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace Content.Server.AI.WorldState.States.Mobs
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var entity in Visibility.GetEntitiesInRange(Owner.Transform.Coordinates, typeof(ISharedBodyManagerComponent), controller.VisionRadius))
|
foreach (var entity in Visibility.GetEntitiesInRange(Owner.Transform.Coordinates, typeof(IBody), controller.VisionRadius))
|
||||||
{
|
{
|
||||||
if (entity == Owner) continue;
|
if (entity == Owner) continue;
|
||||||
result.Add(entity);
|
result.Add(entity);
|
||||||
|
|||||||
@@ -1,534 +0,0 @@
|
|||||||
#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.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.Reflection;
|
|
||||||
using Robust.Shared.Interfaces.Serialization;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Log;
|
|
||||||
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 : IBodyPart
|
|
||||||
{
|
|
||||||
private IBodyManagerComponent? _body;
|
|
||||||
|
|
||||||
private readonly HashSet<IMechanism> _mechanisms = new HashSet<IMechanism>();
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ViewVariables]
|
|
||||||
public IBodyManagerComponent? 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; }
|
|
||||||
|
|
||||||
[ViewVariables] public string Name { get; private set; }
|
|
||||||
|
|
||||||
[ViewVariables] public string Plural { get; private set; }
|
|
||||||
|
|
||||||
[ViewVariables] public string RSIPath { get; private set; }
|
|
||||||
|
|
||||||
[ViewVariables] public string RSIState { get; private set; }
|
|
||||||
|
|
||||||
[ViewVariables] public Enum? RSIMap { get; set; }
|
|
||||||
|
|
||||||
// TODO: SpriteComponent rework
|
|
||||||
[ViewVariables] public Color? RSIColor { get; set; }
|
|
||||||
|
|
||||||
[ViewVariables] public BodyPartType PartType { get; private set; }
|
|
||||||
|
|
||||||
[ViewVariables] public int Size { get; private set; }
|
|
||||||
|
|
||||||
[ViewVariables] public int MaxDurability { get; private set; }
|
|
||||||
|
|
||||||
[ViewVariables] public int CurrentDurability => MaxDurability - Damage.TotalDamage;
|
|
||||||
|
|
||||||
// TODO: Individual body part damage
|
|
||||||
/// <summary>
|
|
||||||
/// Current damage dealt to this <see cref="IBodyPart"/>.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables]
|
|
||||||
public DamageContainer Damage { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Armor of this <see cref="IBodyPart"/> against damages.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables]
|
|
||||||
public ResistanceSet Resistances { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// At what HP this <see cref="IBodyPart"/> destroyed.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables]
|
|
||||||
public int DestroyThreshold { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// What types of BodyParts this <see cref="IBodyPart"/> 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="IMechanism"/> currently inside this
|
|
||||||
/// <see cref="IBodyPart"/>.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables]
|
|
||||||
public IReadOnlyCollection<IMechanism> Mechanisms => _mechanisms;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents if body part is vital for creature.
|
|
||||||
/// If the last vital body part is removed creature dies
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables]
|
|
||||||
public bool IsVital { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This method is called by
|
|
||||||
/// <see cref="IBodyManagerComponent.PreMetabolism"/> 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="IBodyManagerComponent.PostMetabolism"/> 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>([NotNullWhen(true)] out T? property) where T : BodyPartProperty
|
|
||||||
{
|
|
||||||
property = (T?) Properties.FirstOrDefault(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, [NotNullWhen(true)] 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="IBodyPart"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">
|
|
||||||
/// The subtype of <see cref="BodyPartProperty"/> to look for.
|
|
||||||
/// </typeparam>
|
|
||||||
/// <returns>
|
|
||||||
/// True if this <see cref="IBodyPart"/> 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="IBodyPart"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propertyType">
|
|
||||||
/// The subtype of <see cref="BodyPartProperty"/> to look for.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// True if this <see cref="IBodyPart"/> has a property of type
|
|
||||||
/// <see cref="propertyType"/>, false otherwise.
|
|
||||||
/// </returns>
|
|
||||||
public bool HasProperty(Type propertyType)
|
|
||||||
{
|
|
||||||
return Properties.Count(x => x.GetType() == propertyType) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CanAttachPart(IBodyPart part)
|
|
||||||
{
|
|
||||||
return SurgeryData.CanAttachBodyPart(part);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CanInstallMechanism(IMechanism 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="IBodyPart"/>).
|
|
||||||
/// </returns>
|
|
||||||
private bool TryInstallMechanism(IMechanism mechanism)
|
|
||||||
{
|
|
||||||
if (!CanInstallMechanism(mechanism))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddMechanism(mechanism);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tries to install a <see cref="DroppedMechanismComponent"/> into this
|
|
||||||
/// <see cref="IBodyPart"/>, 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryDropMechanism(IEntity dropLocation, IMechanism 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.Coordinates;
|
|
||||||
var mechanismEntity = entityManager.SpawnEntity("BaseDroppedMechanism", position);
|
|
||||||
|
|
||||||
dropped = mechanismEntity.GetComponent<DroppedMechanismComponent>();
|
|
||||||
dropped.InitializeDroppedMechanism(mechanismTarget);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tries to destroy the given <see cref="IMechanism"/> in this
|
|
||||||
/// <see cref="IBodyPart"/>. Does NOT spawn a dropped entity.
|
|
||||||
/// </summary>
|
|
||||||
/// <summary>
|
|
||||||
/// Tries to destroy the given <see cref="IMechanism"/> in this
|
|
||||||
/// <see cref="IBodyPart"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mechanismTarget">The mechanism to destroy.</param>
|
|
||||||
/// <returns>True if successful, false otherwise.</returns>
|
|
||||||
public bool DestroyMechanism(IMechanism mechanismTarget)
|
|
||||||
{
|
|
||||||
if (!RemoveMechanism(mechanismTarget))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool SurgeryCheck(SurgeryType surgery)
|
|
||||||
{
|
|
||||||
return SurgeryData.CheckSurgery(surgery);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to perform surgery on this <see cref="IBodyPart"/> 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(IMechanism 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="IBodyPart"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mechanism">The mechanism to remove.</param>
|
|
||||||
/// <returns>True if it was removed, false otherwise.</returns>
|
|
||||||
private bool RemoveMechanism(IMechanism 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="IBodyPart"/> 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;
|
|
||||||
IsVital = data.IsVital;
|
|
||||||
|
|
||||||
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.Coordinates);
|
|
||||||
|
|
||||||
dropped.GetComponent<DroppedBodyPartComponent>().TransferBodyPartData(this);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Shared.Body.Part;
|
|
||||||
using Content.Shared.Body.Preset;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
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
|
|
||||||
{
|
|
||||||
[ViewVariables] public bool Initialized { get; private set; }
|
|
||||||
|
|
||||||
[ViewVariables] public string Name { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Maps a template slot to the ID of the <see cref="IBodyPart"/>
|
|
||||||
/// that should fill it. E.g. "right arm" : "BodyPart.arm.basic_human".
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables]
|
|
||||||
public Dictionary<string, string> PartIDs { get; protected set; }
|
|
||||||
|
|
||||||
public virtual void Initialize(BodyPresetPrototype prototype)
|
|
||||||
{
|
|
||||||
DebugTools.Assert(!Initialized, $"{nameof(BodyPreset)} {Name} has already been initialized!");
|
|
||||||
|
|
||||||
Name = prototype.Name;
|
|
||||||
PartIDs = prototype.PartIDs;
|
|
||||||
|
|
||||||
Initialized = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
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.Utility;
|
|
||||||
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
|
|
||||||
{
|
|
||||||
[ViewVariables] public bool Initialized { get; private set; }
|
|
||||||
|
|
||||||
[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; } = new Dictionary<string, BodyPartType>();
|
|
||||||
|
|
||||||
/// <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; } = new Dictionary<string, List<string>>();
|
|
||||||
|
|
||||||
[ViewVariables]
|
|
||||||
public Dictionary<string, string> Layers { get; private set; } = new Dictionary<string, string>();
|
|
||||||
|
|
||||||
[ViewVariables]
|
|
||||||
public Dictionary<string, string> MechanismLayers { get; private set; } = new Dictionary<string, string>();
|
|
||||||
|
|
||||||
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 HasSlot(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Initialize(BodyTemplatePrototype prototype)
|
|
||||||
{
|
|
||||||
DebugTools.Assert(!Initialized, $"{nameof(BodyTemplate)} {Name} has already been initialized!");
|
|
||||||
|
|
||||||
Name = prototype.Name;
|
|
||||||
CenterSlot = prototype.CenterSlot;
|
|
||||||
Slots = new Dictionary<string, BodyPartType>(prototype.Slots);
|
|
||||||
Connections = new Dictionary<string, List<string>>(prototype.Connections);
|
|
||||||
Layers = new Dictionary<string, string>(prototype.Layers);
|
|
||||||
MechanismLayers = new Dictionary<string, string>(prototype.MechanismLayers);
|
|
||||||
|
|
||||||
Initialized = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
#nullable enable
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Content.Server.Body.Mechanisms;
|
|
||||||
using Content.Server.GameObjects.Components.Body;
|
|
||||||
using Content.Shared.Body.Part.Properties;
|
|
||||||
using Content.Shared.GameObjects.Components.Body;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
|
|
||||||
namespace Content.Server.Body
|
|
||||||
{
|
|
||||||
public interface IBodyPart
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The body that this body part is currently in, if any.
|
|
||||||
/// </summary>
|
|
||||||
IBodyManagerComponent? Body { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <see cref="BodyPartType"/> that this <see cref="IBodyPart"/> is considered
|
|
||||||
/// to be.
|
|
||||||
/// For example, <see cref="BodyPartType.Arm"/>.
|
|
||||||
/// </summary>
|
|
||||||
BodyPartType PartType { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The name of this <see cref="IBodyPart"/>, often displayed to the user.
|
|
||||||
/// For example, it could be named "advanced robotic arm".
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Plural version of this <see cref="IBodyPart"/> name.
|
|
||||||
/// </summary>
|
|
||||||
public string Plural { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines many things: how many mechanisms can be fit inside this
|
|
||||||
/// <see cref="IBodyPart"/>, whether a body can fit through tiny crevices,
|
|
||||||
/// etc.
|
|
||||||
/// </summary>
|
|
||||||
int Size { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Max HP of this <see cref="IBodyPart"/>.
|
|
||||||
/// </summary>
|
|
||||||
int MaxDurability { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Current HP of this <see cref="IBodyPart"/> based on sum of all damage types.
|
|
||||||
/// </summary>
|
|
||||||
int CurrentDurability { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Collection of all <see cref="IMechanism"/>s currently inside this
|
|
||||||
/// <see cref="IBodyPart"/>.
|
|
||||||
/// To add and remove from this list see <see cref="AddMechanism"/> and
|
|
||||||
/// <see cref="RemoveMechanism"/>
|
|
||||||
/// </summary>
|
|
||||||
IReadOnlyCollection<IMechanism> Mechanisms { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Path to the RSI that represents this <see cref="IBodyPart"/>.
|
|
||||||
/// </summary>
|
|
||||||
public string RSIPath { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// RSI state that represents this <see cref="IBodyPart"/>.
|
|
||||||
/// </summary>
|
|
||||||
public string RSIState { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// RSI map keys that this body part changes on the sprite.
|
|
||||||
/// </summary>
|
|
||||||
public Enum? RSIMap { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// RSI color of this body part.
|
|
||||||
/// </summary>
|
|
||||||
// TODO: SpriteComponent rework
|
|
||||||
public Color? RSIColor { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If body part is vital
|
|
||||||
/// </summary>
|
|
||||||
public bool IsVital { get; }
|
|
||||||
|
|
||||||
bool HasProperty<T>() where T : BodyPartProperty;
|
|
||||||
|
|
||||||
bool HasProperty(Type type);
|
|
||||||
|
|
||||||
bool TryGetProperty<T>([NotNullWhen(true)] out T? property) where T : BodyPartProperty;
|
|
||||||
|
|
||||||
void PreMetabolism(float frameTime);
|
|
||||||
|
|
||||||
void PostMetabolism(float frameTime);
|
|
||||||
|
|
||||||
bool SpawnDropped([NotNullWhen(true)] out IEntity? dropped);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the given <see cref="SurgeryType"/> can be used on
|
|
||||||
/// the current state of this <see cref="IBodyPart"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if it can be used, false otherwise.</returns>
|
|
||||||
bool SurgeryCheck(SurgeryType surgery);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if another <see cref="IBodyPart"/> can be connected to this one.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="part">The part to connect.</param>
|
|
||||||
/// <returns>True if it can be connected, false otherwise.</returns>
|
|
||||||
bool CanAttachPart(IBodyPart part);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if a <see cref="IMechanism"/> can be installed on this
|
|
||||||
/// <see cref="IBodyPart"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if it can be installed, false otherwise.</returns>
|
|
||||||
bool CanInstallMechanism(IMechanism mechanism);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tries to remove the given <see cref="IMechanism"/> reference from
|
|
||||||
/// this <see cref="IBodyPart"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// The newly spawned <see cref="DroppedMechanismComponent"/>, or null
|
|
||||||
/// if there was an error in spawning the entity or removing the mechanism.
|
|
||||||
/// </returns>
|
|
||||||
bool TryDropMechanism(IEntity dropLocation, IMechanism mechanismTarget,
|
|
||||||
[NotNullWhen(true)] out DroppedMechanismComponent dropped);
|
|
||||||
|
|
||||||
bool DestroyMechanism(IMechanism mechanism);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace Content.Server.Body.Mechanisms.Behaviors
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The behaviors of a brain, inhabitable by a player.
|
|
||||||
/// </summary>
|
|
||||||
public class BrainBehavior : MechanismBehavior
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
#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="IBodyPart"/> 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="IBodyPart"/>.
|
|
||||||
/// For instance, putting a brain into an empty head.
|
|
||||||
/// </summary>
|
|
||||||
public void InstalledIntoPart()
|
|
||||||
{
|
|
||||||
TryAddNetwork();
|
|
||||||
OnInstalledIntoPart();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the containing <see cref="IBodyPart"/> is removed from a
|
|
||||||
/// <see cref="BodyManagerComponent"/>.
|
|
||||||
/// For instance, cutting off ones head will call this on the brain inside.
|
|
||||||
/// </summary>
|
|
||||||
public void RemovedFromBody(IBodyManagerComponent old)
|
|
||||||
{
|
|
||||||
OnRemovedFromBody(old);
|
|
||||||
TryRemoveNetwork(old);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is
|
|
||||||
/// removed from a <see cref="IBodyPart"/>.
|
|
||||||
/// For instance, taking a brain out of ones head.
|
|
||||||
/// </summary>
|
|
||||||
public void RemovedFromPart(IBodyPart old)
|
|
||||||
{
|
|
||||||
OnRemovedFromPart(old);
|
|
||||||
TryRemoveNetwork(old.Body);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TryAddNetwork()
|
|
||||||
{
|
|
||||||
if (Network != null)
|
|
||||||
{
|
|
||||||
Mechanism.Body?.EnsureNetwork(Network);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TryRemoveNetwork(IBodyManagerComponent? 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="IBodyPart"/> 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="IBodyPart"/>.
|
|
||||||
/// For instance, putting a brain into an empty head.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnInstalledIntoPart() { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the containing <see cref="IBodyPart"/> 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(IBodyManagerComponent old) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is
|
|
||||||
/// removed from a <see cref="IBodyPart"/>.
|
|
||||||
/// For instance, taking a brain out of ones head.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void OnRemovedFromPart(IBodyPart 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) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
#nullable enable
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.Body.Mechanisms.Behaviors;
|
|
||||||
using Content.Server.GameObjects.Components.Body;
|
|
||||||
using Content.Shared.GameObjects.Components.Body;
|
|
||||||
|
|
||||||
namespace Content.Server.Body.Mechanisms
|
|
||||||
{
|
|
||||||
public interface IMechanism
|
|
||||||
{
|
|
||||||
string Id { get; }
|
|
||||||
|
|
||||||
string Name { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Professional description of the <see cref="IMechanism"/>.
|
|
||||||
/// </summary>
|
|
||||||
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>
|
|
||||||
string ExamineMessage { get; set; }
|
|
||||||
|
|
||||||
// TODO: Make RSI properties sane
|
|
||||||
/// <summary>
|
|
||||||
/// Path to the RSI that represents this <see cref="IMechanism"/>.
|
|
||||||
/// </summary>
|
|
||||||
string RSIPath { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// RSI state that represents this <see cref="IMechanism"/>.
|
|
||||||
/// </summary>
|
|
||||||
string RSIState { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Max HP of this <see cref="IMechanism"/>.
|
|
||||||
/// </summary>
|
|
||||||
int MaxDurability { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Current HP of this <see cref="IMechanism"/>.
|
|
||||||
/// </summary>
|
|
||||||
int CurrentDurability { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// At what HP this <see cref="IMechanism"/> is completely destroyed.
|
|
||||||
/// </summary>
|
|
||||||
int DestroyThreshold { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Armor of this <see cref="IMechanism"/> against attacks.
|
|
||||||
/// </summary>
|
|
||||||
int Resistance { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines a handful of things - mostly whether this
|
|
||||||
/// <see cref="IMechanism"/> can fit into a <see cref="IBodyPart"/>.
|
|
||||||
/// </summary>
|
|
||||||
// TODO: OnSizeChanged
|
|
||||||
int Size { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// What kind of <see cref="IBodyPart"/> this <see cref="IMechanism"/> can be
|
|
||||||
/// easily installed into.
|
|
||||||
/// </summary>
|
|
||||||
BodyPartCompatibility Compatibility { get; set; }
|
|
||||||
|
|
||||||
IReadOnlyList<MechanismBehavior> Behaviors { get; }
|
|
||||||
|
|
||||||
IBodyManagerComponent? Body { get; }
|
|
||||||
|
|
||||||
IBodyPart? Part { get; set; }
|
|
||||||
|
|
||||||
void EnsureInitialize();
|
|
||||||
|
|
||||||
void InstalledIntoBody();
|
|
||||||
|
|
||||||
void RemovedFromBody(IBodyManagerComponent old);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This method is called by <see cref="IBodyPart.PreMetabolism"/> before
|
|
||||||
/// <see cref="MetabolismComponent.Update"/> is called.
|
|
||||||
/// </summary>
|
|
||||||
void PreMetabolism(float frameTime);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This method is called by <see cref="IBodyPart.PostMetabolism"/> after
|
|
||||||
/// <see cref="MetabolismComponent.Update"/> is called.
|
|
||||||
/// </summary>
|
|
||||||
void PostMetabolism(float frameTime);
|
|
||||||
|
|
||||||
void AddBehavior(MechanismBehavior behavior);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes a behavior from this mechanism.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="behavior">The behavior to remove.</param>
|
|
||||||
/// <returns>True if it was removed, false otherwise.</returns>
|
|
||||||
bool RemoveBehavior(MechanismBehavior behavior);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
#nullable enable
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.Body.Mechanisms.Behaviors;
|
|
||||||
using Content.Server.GameObjects.Components.Body;
|
|
||||||
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="IBodyPart"/>.
|
|
||||||
/// This includes livers, eyes, cameras, brains, explosive implants,
|
|
||||||
/// binary communicators, and other things.
|
|
||||||
/// </summary>
|
|
||||||
public class Mechanism : IMechanism
|
|
||||||
{
|
|
||||||
private IBodyPart? _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; }
|
|
||||||
|
|
||||||
[ViewVariables] public string Description { get; set; }
|
|
||||||
|
|
||||||
[ViewVariables] public string ExamineMessage { get; set; }
|
|
||||||
|
|
||||||
[ViewVariables] public string RSIPath { get; set; }
|
|
||||||
|
|
||||||
[ViewVariables] public string RSIState { get; set; }
|
|
||||||
|
|
||||||
[ViewVariables] public int MaxDurability { get; set; }
|
|
||||||
|
|
||||||
[ViewVariables] public int CurrentDurability { get; set; }
|
|
||||||
|
|
||||||
[ViewVariables] public int DestroyThreshold { get; set; }
|
|
||||||
|
|
||||||
[ViewVariables] public int Resistance { get; set; }
|
|
||||||
|
|
||||||
[ViewVariables] public int Size { get; set; }
|
|
||||||
|
|
||||||
[ViewVariables] public BodyPartCompatibility Compatibility { get; set; }
|
|
||||||
|
|
||||||
private readonly List<MechanismBehavior> _behaviors;
|
|
||||||
|
|
||||||
[ViewVariables] public IReadOnlyList<MechanismBehavior> Behaviors => _behaviors;
|
|
||||||
|
|
||||||
public IBodyManagerComponent? Body => Part?.Body;
|
|
||||||
|
|
||||||
public IBodyPart? 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(IBodyManagerComponent old)
|
|
||||||
{
|
|
||||||
foreach (var behavior in Behaviors)
|
|
||||||
{
|
|
||||||
behavior.RemovedFromBody(old);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PreMetabolism(float frameTime)
|
|
||||||
{
|
|
||||||
foreach (var behavior in Behaviors)
|
|
||||||
{
|
|
||||||
behavior.PreMetabolism(frameTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PostMetabolism(float frameTime)
|
|
||||||
{
|
|
||||||
foreach (var behavior in Behaviors)
|
|
||||||
{
|
|
||||||
behavior.PostMetabolism(frameTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddBehavior(MechanismBehavior behavior)
|
|
||||||
{
|
|
||||||
_behaviors.Add(behavior);
|
|
||||||
behavior.Initialize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool RemoveBehavior(MechanismBehavior behavior)
|
|
||||||
{
|
|
||||||
if (_behaviors.Remove(behavior))
|
|
||||||
{
|
|
||||||
behavior.Remove();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
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.PreMetabolism"/>.
|
|
||||||
/// </summary>
|
|
||||||
public virtual void PreMetabolism(float frameTime) { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called every update by
|
|
||||||
/// <see cref="BodyManagerComponent.PostMetabolism"/>.
|
|
||||||
/// </summary>
|
|
||||||
public virtual void PostMetabolism(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
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>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
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>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Content.Server.Body.Network
|
|
||||||
{
|
|
||||||
public interface IBodyNetworkFactory
|
|
||||||
{
|
|
||||||
void DoAutoRegistrations();
|
|
||||||
|
|
||||||
BodyNetwork GetNetwork(string name);
|
|
||||||
|
|
||||||
BodyNetwork GetNetwork(Type type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
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>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
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.Body.Network;
|
|
||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
using Content.Server.GameObjects.Components.Mobs.Speech;
|
using Content.Server.GameObjects.Components.Mobs.Speech;
|
||||||
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
|
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
|
||||||
@@ -49,8 +48,6 @@ 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();
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Body.Behavior;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Networks;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Body.Behavior
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedHeartBehaviorComponent))]
|
||||||
|
public class HeartBehaviorComponent : SharedHeartBehaviorComponent
|
||||||
|
{
|
||||||
|
private float _accumulatedFrameTime;
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
// TODO BODY do between pre and metabolism
|
||||||
|
if (Mechanism?.Body == null ||
|
||||||
|
!Mechanism.Body.Owner.HasComponent<SharedBloodstreamComponent>())
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,29 +1,28 @@
|
|||||||
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Atmos;
|
using Content.Server.Atmos;
|
||||||
using Content.Server.GameObjects.Components.Body.Circulatory;
|
using Content.Server.GameObjects.Components.Body.Circulatory;
|
||||||
using Content.Server.Interfaces;
|
|
||||||
using Content.Server.Utility;
|
using Content.Server.Utility;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.GameObjects.Components.Body.Behavior;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Body.Respiratory
|
namespace Content.Server.GameObjects.Components.Body.Behavior
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class LungComponent : Component, IGasMixtureHolder
|
[ComponentReference(typeof(SharedLungBehaviorComponent))]
|
||||||
|
public class LungBehaviorComponent : SharedLungBehaviorComponent
|
||||||
{
|
{
|
||||||
public override string Name => "Lung";
|
|
||||||
|
|
||||||
private float _accumulatedFrameTime;
|
private float _accumulatedFrameTime;
|
||||||
|
|
||||||
[ViewVariables] public GasMixture Air { get; set; }
|
[ViewVariables] public GasMixture Air { get; set; } = default!;
|
||||||
|
|
||||||
[ViewVariables] public LungStatus Status { get; set; }
|
[ViewVariables] public override float Temperature => Air.Temperature;
|
||||||
|
|
||||||
[ViewVariables] public float CycleDelay { get; set; }
|
[ViewVariables] public override float Volume => Air.Volume;
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
{
|
{
|
||||||
@@ -42,11 +41,52 @@ namespace Content.Server.GameObjects.Components.Body.Respiratory
|
|||||||
Atmospherics.NormalBodyTemperature,
|
Atmospherics.NormalBodyTemperature,
|
||||||
temp => Air.Temperature = temp,
|
temp => Air.Temperature = temp,
|
||||||
() => Air.Temperature);
|
() => Air.Temperature);
|
||||||
|
|
||||||
serializer.DataField(this, l => l.CycleDelay, "cycleDelay", 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(float frameTime)
|
public override void Gasp()
|
||||||
|
{
|
||||||
|
Owner.PopupMessageEveryone("Gasp");
|
||||||
|
Inhale(CycleDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Transfer(GasMixture from, GasMixture to, float ratio)
|
||||||
|
{
|
||||||
|
var removed = from.RemoveRatio(ratio);
|
||||||
|
var toOld = to.Gases.ToArray();
|
||||||
|
|
||||||
|
to.Merge(removed);
|
||||||
|
|
||||||
|
for (var gas = 0; gas < Atmospherics.TotalNumberOfGases; gas++)
|
||||||
|
{
|
||||||
|
var newAmount = to.GetMoles(gas);
|
||||||
|
var oldAmount = toOld[gas];
|
||||||
|
var delta = newAmount - oldAmount;
|
||||||
|
|
||||||
|
removed.AdjustMoles(gas, -delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
from.Merge(removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToBloodstream(GasMixture mixture)
|
||||||
|
{
|
||||||
|
if (Body == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var to = bloodstream.Air;
|
||||||
|
|
||||||
|
to.Merge(mixture);
|
||||||
|
mixture.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
if (Status == LungStatus.None)
|
if (Status == LungStatus.None)
|
||||||
{
|
{
|
||||||
@@ -85,39 +125,7 @@ namespace Content.Server.GameObjects.Components.Body.Respiratory
|
|||||||
_accumulatedFrameTime = absoluteTime - delay;
|
_accumulatedFrameTime = absoluteTime - delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Transfer(GasMixture from, GasMixture to, float ratio)
|
public override void Inhale(float frameTime)
|
||||||
{
|
|
||||||
var removed = from.RemoveRatio(ratio);
|
|
||||||
var toOld = to.Gases.ToArray();
|
|
||||||
|
|
||||||
to.Merge(removed);
|
|
||||||
|
|
||||||
for (var gas = 0; gas < Atmospherics.TotalNumberOfGases; gas++)
|
|
||||||
{
|
|
||||||
var newAmount = to.GetMoles(gas);
|
|
||||||
var oldAmount = toOld[gas];
|
|
||||||
var delta = newAmount - oldAmount;
|
|
||||||
|
|
||||||
removed.AdjustMoles(gas, -delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
from.Merge(removed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ToBloodstream(GasMixture mixture)
|
|
||||||
{
|
|
||||||
if (!Owner.TryGetComponent(out BloodstreamComponent bloodstream))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var to = bloodstream.Air;
|
|
||||||
|
|
||||||
to.Merge(mixture);
|
|
||||||
mixture.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Inhale(float frameTime)
|
|
||||||
{
|
{
|
||||||
if (!Owner.Transform.Coordinates.TryGetTileAir(out var tileAir))
|
if (!Owner.Transform.Coordinates.TryGetTileAir(out var tileAir))
|
||||||
{
|
{
|
||||||
@@ -135,7 +143,7 @@ namespace Content.Server.GameObjects.Components.Body.Respiratory
|
|||||||
ToBloodstream(Air);
|
ToBloodstream(Air);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Exhale(float frameTime)
|
public override void Exhale(float frameTime)
|
||||||
{
|
{
|
||||||
if (!Owner.Transform.Coordinates.TryGetTileAir(out var tileAir))
|
if (!Owner.Transform.Coordinates.TryGetTileAir(out var tileAir))
|
||||||
{
|
{
|
||||||
@@ -148,7 +156,12 @@ namespace Content.Server.GameObjects.Components.Body.Respiratory
|
|||||||
public void Exhale(float frameTime, GasMixture to)
|
public void Exhale(float frameTime, GasMixture to)
|
||||||
{
|
{
|
||||||
// TODO: Make the bloodstream separately pump toxins into the lungs, making the lungs' only job to empty.
|
// TODO: Make the bloodstream separately pump toxins into the lungs, making the lungs' only job to empty.
|
||||||
if (!Owner.TryGetComponent(out BloodstreamComponent bloodstream))
|
if (Body == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -171,18 +184,5 @@ namespace Content.Server.GameObjects.Components.Body.Respiratory
|
|||||||
|
|
||||||
Air.Merge(lungRemoved);
|
Air.Merge(lungRemoved);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Gasp()
|
|
||||||
{
|
|
||||||
Owner.PopupMessageEveryone("Gasp");
|
|
||||||
Inhale(CycleDelay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum LungStatus
|
|
||||||
{
|
|
||||||
None = 0,
|
|
||||||
Inhaling,
|
|
||||||
Exhaling
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Content.Server.GameObjects.Components.Chemistry;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Behavior;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Body.Behavior
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedStomachBehaviorComponent))]
|
||||||
|
public class StomachBehaviorComponent : SharedStomachBehaviorComponent
|
||||||
|
{
|
||||||
|
protected override void Startup()
|
||||||
|
{
|
||||||
|
base.Startup();
|
||||||
|
|
||||||
|
if (!Owner.EnsureComponent(out SolutionContainerComponent solution))
|
||||||
|
{
|
||||||
|
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionContainerComponent)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
solution.MaxVolume = InitialMaxVolume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.GameObjects.Components.Body;
|
|
||||||
using Content.Shared.Body.Part;
|
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Body;
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Part;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Robust.Server.Interfaces.Console;
|
using Robust.Server.Interfaces.Console;
|
||||||
using Robust.Server.Interfaces.Player;
|
using Robust.Server.Interfaces.Player;
|
||||||
@@ -15,29 +14,118 @@ using Robust.Shared.IoC;
|
|||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
namespace Content.Server.Body
|
namespace Content.Server.GameObjects.Components.Body
|
||||||
{
|
{
|
||||||
class AddHandCommand : IClientCommand
|
class AddHandCommand : IClientCommand
|
||||||
{
|
{
|
||||||
|
public const string DefaultHandPrototype = "LeftHandHuman";
|
||||||
|
|
||||||
public string Command => "addhand";
|
public string Command => "addhand";
|
||||||
public string Description => "Adds a hand to your entity.";
|
public string Description => "Adds a hand to your entity.";
|
||||||
public string Help => $"Usage: {Command}";
|
public string Help => $"Usage: {Command} <entityUid> <handPrototypeId> / {Command} <entityUid> / {Command} <handPrototypeId> / {Command}";
|
||||||
|
|
||||||
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
|
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length > 1)
|
||||||
|
{
|
||||||
|
shell.SendText(player, Help);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||||
|
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||||
|
|
||||||
|
IEntity entity;
|
||||||
|
IEntity hand;
|
||||||
|
|
||||||
|
switch (args.Length)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
{
|
{
|
||||||
if (player == null)
|
if (player == null)
|
||||||
{
|
{
|
||||||
shell.SendText(player, "Only a player can run this command.");
|
shell.SendText(player, "Only a player can run this command without arguments.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.AttachedEntity == null)
|
if (player.AttachedEntity == null)
|
||||||
{
|
{
|
||||||
shell.SendText(player, "You have no entity.");
|
shell.SendText(player, "You don't have an entity to add a hand to.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent? body))
|
entity = player.AttachedEntity;
|
||||||
|
hand = entityManager.SpawnEntity(DefaultHandPrototype, entity.Transform.Coordinates);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
if (EntityUid.TryParse(args[0], out var uid))
|
||||||
|
{
|
||||||
|
if (!entityManager.TryGetEntity(uid, out var parsedEntity))
|
||||||
|
{
|
||||||
|
shell.SendText(player, $"No entity found with uid {uid}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity = parsedEntity;
|
||||||
|
hand = entityManager.SpawnEntity(DefaultHandPrototype, entity.Transform.Coordinates);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (player == null)
|
||||||
|
{
|
||||||
|
shell.SendText(player,
|
||||||
|
"You must specify an entity to add a hand to when using this command from the server terminal.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.AttachedEntity == null)
|
||||||
|
{
|
||||||
|
shell.SendText(player, "You don't have an entity to add a hand to.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity = player.AttachedEntity;
|
||||||
|
hand = entityManager.SpawnEntity(args[0], entity.Transform.Coordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
{
|
||||||
|
if (!EntityUid.TryParse(args[0], out var uid))
|
||||||
|
{
|
||||||
|
shell.SendText(player, $"{args[0]} is not a valid entity uid.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entityManager.TryGetEntity(uid, out var parsedEntity))
|
||||||
|
{
|
||||||
|
shell.SendText(player, $"No entity exists with uid {uid}.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity = parsedEntity;
|
||||||
|
|
||||||
|
if (!prototypeManager.HasIndex<EntityPrototype>(args[1]))
|
||||||
|
{
|
||||||
|
shell.SendText(player, $"No hand entity exists with id {args[1]}.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hand = entityManager.SpawnEntity(args[1], entity.Transform.Coordinates);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
shell.SendText(player, Help);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entity.TryGetComponent(out IBody? body))
|
||||||
{
|
{
|
||||||
var random = IoCManager.Resolve<IRobustRandom>();
|
var random = IoCManager.Resolve<IRobustRandom>();
|
||||||
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
|
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
|
||||||
@@ -46,14 +134,18 @@ namespace Content.Server.Body
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
if (!hand.TryGetComponent(out IBodyPart? part))
|
||||||
prototypeManager.TryIndex("bodyPart.LHand.BasicHuman", out BodyPartPrototype prototype);
|
{
|
||||||
|
shell.SendText(player, $"Hand entity {hand} does not have a {nameof(IBodyPart)} component.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var part = new BodyPart(prototype);
|
|
||||||
var slot = part.GetHashCode().ToString();
|
var slot = part.GetHashCode().ToString();
|
||||||
|
var response = body.TryAddPart(slot, part, true)
|
||||||
|
? $"Added hand to entity {entity.Name}"
|
||||||
|
: $"Error occurred trying to add a hand to entity {entity.Name}";
|
||||||
|
|
||||||
body.Template.Slots.Add(slot, BodyPartType.Hand);
|
shell.SendText(player, response);
|
||||||
body.TryAddPart(slot, part, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +169,7 @@ namespace Content.Server.Body
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent? body))
|
if (!player.AttachedEntity.TryGetBody(out var body))
|
||||||
{
|
{
|
||||||
var random = IoCManager.Resolve<IRobustRandom>();
|
var random = IoCManager.Resolve<IRobustRandom>();
|
||||||
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
|
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
|
||||||
@@ -87,7 +179,7 @@ namespace Content.Server.Body
|
|||||||
}
|
}
|
||||||
|
|
||||||
var hand = body.Parts.FirstOrDefault(x => x.Value.PartType == BodyPartType.Hand);
|
var hand = body.Parts.FirstOrDefault(x => x.Value.PartType == BodyPartType.Hand);
|
||||||
if (hand.Value == null)
|
if (hand.Value.Equals(default))
|
||||||
{
|
{
|
||||||
shell.SendText(player, "You have no hands.");
|
shell.SendText(player, "You have no hands.");
|
||||||
}
|
}
|
||||||
@@ -124,7 +216,7 @@ namespace Content.Server.Body
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent? body))
|
if (!player.AttachedEntity.TryGetBody(out var body))
|
||||||
{
|
{
|
||||||
var random = IoCManager.Resolve<IRobustRandom>();
|
var random = IoCManager.Resolve<IRobustRandom>();
|
||||||
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
|
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
|
||||||
@@ -140,7 +232,7 @@ namespace Content.Server.Body
|
|||||||
{
|
{
|
||||||
if (mechanism.Name.ToLowerInvariant() == mechanismName)
|
if (mechanism.Name.ToLowerInvariant() == mechanismName)
|
||||||
{
|
{
|
||||||
part.DestroyMechanism(mechanism);
|
part.DeleteMechanism(mechanism);
|
||||||
shell.SendText(player, $"Mechanism with name {mechanismName} has been destroyed.");
|
shell.SendText(player, $"Mechanism with name {mechanismName} has been destroyed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
85
Content.Server/GameObjects/Components/Body/BodyComponent.cs
Normal file
85
Content.Server/GameObjects/Components/Body/BodyComponent.cs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Content.Server.Observer;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Part;
|
||||||
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
|
using Content.Shared.GameObjects.Components.Movement;
|
||||||
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
|
using Robust.Server.Interfaces.Player;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Players;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Body
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedBodyComponent))]
|
||||||
|
[ComponentReference(typeof(IBody))]
|
||||||
|
public class BodyComponent : SharedBodyComponent, IRelayMoveInput
|
||||||
|
{
|
||||||
|
private Container _container = default!;
|
||||||
|
|
||||||
|
protected override bool CanAddPart(string slot, IBodyPart part)
|
||||||
|
{
|
||||||
|
return base.CanAddPart(slot, part) && _container.CanInsert(part.Owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAddPart(string slot, IBodyPart part)
|
||||||
|
{
|
||||||
|
base.OnAddPart(slot, part);
|
||||||
|
|
||||||
|
_container.Insert(part.Owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnRemovePart(string slot, IBodyPart part)
|
||||||
|
{
|
||||||
|
base.OnRemovePart(slot, part);
|
||||||
|
|
||||||
|
_container.ForceRemove(part.Owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
_container = ContainerManagerComponent.Ensure<Container>($"{Name}-{nameof(BodyComponent)}", Owner);
|
||||||
|
|
||||||
|
foreach (var (slot, partId) in PartIds)
|
||||||
|
{
|
||||||
|
// Using MapPosition instead of Coordinates here prevents
|
||||||
|
// a crash within the character preview menu in the lobby
|
||||||
|
var entity = Owner.EntityManager.SpawnEntity(partId, Owner.Transform.MapPosition);
|
||||||
|
|
||||||
|
if (!entity.TryGetComponent(out IBodyPart? part))
|
||||||
|
{
|
||||||
|
Logger.Error($"Entity {partId} does not have a {nameof(IBodyPart)} component.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TryAddPart(slot, part, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Startup()
|
||||||
|
{
|
||||||
|
base.Startup();
|
||||||
|
|
||||||
|
// This is ran in Startup as entities spawned in Initialize
|
||||||
|
// are not synced to the client since they are assumed to be
|
||||||
|
// identical on it
|
||||||
|
foreach (var part in Parts.Values)
|
||||||
|
{
|
||||||
|
part.Dirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRelayMoveInput.MoveInputPressed(ICommonSession session)
|
||||||
|
{
|
||||||
|
if (Owner.TryGetComponent(out IDamageableComponent? damageable) &&
|
||||||
|
damageable.CurrentState == DamageState.Dead)
|
||||||
|
{
|
||||||
|
new Ghost().Execute(null, (IPlayerSession) session, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,543 +0,0 @@
|
|||||||
#nullable enable
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
using Content.Server.Body;
|
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
|
||||||
using Content.Server.Interfaces.GameObjects.Components.Interaction;
|
|
||||||
using Content.Shared.Body.Part.Properties.Movement;
|
|
||||||
using Content.Shared.Body.Part.Properties.Other;
|
|
||||||
using Content.Shared.GameObjects.Components.Body;
|
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
|
||||||
using Content.Shared.GameObjects.Components.Movement;
|
|
||||||
using Robust.Shared.GameObjects.Systems;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.Log;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Body
|
|
||||||
{
|
|
||||||
public partial class BodyManagerComponent
|
|
||||||
{
|
|
||||||
private readonly Dictionary<string, IBodyPart> _parts = new Dictionary<string, IBodyPart>();
|
|
||||||
|
|
||||||
[ViewVariables] public BodyPreset Preset { get; private set; } = default!;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// All <see cref="IBodyPart"></see> with <see cref="LegProperty"></see>
|
|
||||||
/// that are currently affecting move speed, mapped to how big that leg
|
|
||||||
/// they're on is.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables]
|
|
||||||
private readonly Dictionary<IBodyPart, float> _activeLegs = new Dictionary<IBodyPart, float>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Maps <see cref="BodyTemplate"/> slot name to the <see cref="IBodyPart"/>
|
|
||||||
/// object filling it (if there is one).
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables]
|
|
||||||
public IReadOnlyDictionary<string, IBodyPart> Parts => _parts;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// List of all occupied slots in this body, taken from the values of
|
|
||||||
/// <see cref="Parts"/>.
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<string> OccupiedSlots => Parts.Keys;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// List of all slots in this body, taken from the keys of
|
|
||||||
/// <see cref="Template"/> slots.
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<string> AllSlots => Template.Slots.Keys;
|
|
||||||
|
|
||||||
public bool TryAddPart(string slot, DroppedBodyPartComponent part, bool force = false)
|
|
||||||
{
|
|
||||||
DebugTools.AssertNotNull(part);
|
|
||||||
|
|
||||||
if (!TryAddPart(slot, part.ContainedBodyPart, force))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
part.Owner.Delete();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryAddPart(string slot, IBodyPart part, bool force = false)
|
|
||||||
{
|
|
||||||
DebugTools.AssertNotNull(part);
|
|
||||||
DebugTools.AssertNotNull(slot);
|
|
||||||
|
|
||||||
// Make sure the given slot exists
|
|
||||||
if (!force)
|
|
||||||
{
|
|
||||||
if (!HasSlot(slot))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// And that nothing is in it
|
|
||||||
if (!_parts.TryAdd(slot, part))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_parts[slot] = part;
|
|
||||||
}
|
|
||||||
|
|
||||||
part.Body = this;
|
|
||||||
|
|
||||||
var argsAdded = new BodyPartAddedEventArgs(part, slot);
|
|
||||||
|
|
||||||
foreach (var component in Owner.GetAllComponents<IBodyPartAdded>().ToArray())
|
|
||||||
{
|
|
||||||
component.BodyPartAdded(argsAdded);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Sort this duplicate out
|
|
||||||
OnBodyChanged();
|
|
||||||
|
|
||||||
if (!Template.Layers.TryGetValue(slot, out var partMap) ||
|
|
||||||
!_reflectionManager.TryParseEnumReference(partMap, out var partEnum))
|
|
||||||
{
|
|
||||||
Logger.Warning($"Template {Template.Name} has an invalid RSI map key {partMap} for body part {part.Name}.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
part.RSIMap = partEnum;
|
|
||||||
|
|
||||||
var partMessage = new BodyPartAddedMessage(part.RSIPath, part.RSIState, partEnum);
|
|
||||||
|
|
||||||
SendNetworkMessage(partMessage);
|
|
||||||
|
|
||||||
foreach (var mechanism in part.Mechanisms)
|
|
||||||
{
|
|
||||||
if (!Template.MechanismLayers.TryGetValue(mechanism.Id, out var mechanismMap))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_reflectionManager.TryParseEnumReference(mechanismMap, out var mechanismEnum))
|
|
||||||
{
|
|
||||||
Logger.Warning($"Template {Template.Name} has an invalid RSI map key {mechanismMap} for mechanism {mechanism.Id}.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mechanismMessage = new MechanismSpriteAddedMessage(mechanismEnum);
|
|
||||||
|
|
||||||
SendNetworkMessage(mechanismMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasPart(string slot)
|
|
||||||
{
|
|
||||||
return _parts.ContainsKey(slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemovePart(IBodyPart part, bool drop)
|
|
||||||
{
|
|
||||||
DebugTools.AssertNotNull(part);
|
|
||||||
|
|
||||||
var slotName = _parts.FirstOrDefault(x => x.Value == part).Key;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(slotName)) return;
|
|
||||||
|
|
||||||
RemovePart(slotName, drop);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool RemovePart(string slot, bool drop)
|
|
||||||
{
|
|
||||||
DebugTools.AssertNotNull(slot);
|
|
||||||
|
|
||||||
if (!_parts.Remove(slot, out var part))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
IEntity? dropped = null;
|
|
||||||
if (drop)
|
|
||||||
{
|
|
||||||
part.SpawnDropped(out dropped);
|
|
||||||
}
|
|
||||||
|
|
||||||
part.Body = null;
|
|
||||||
|
|
||||||
var args = new BodyPartRemovedEventArgs(part, slot);
|
|
||||||
|
|
||||||
foreach (var component in Owner.GetAllComponents<IBodyPartRemoved>())
|
|
||||||
{
|
|
||||||
component.BodyPartRemoved(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.RSIMap != null)
|
|
||||||
{
|
|
||||||
var message = new BodyPartRemovedMessage(part.RSIMap, dropped?.Uid);
|
|
||||||
SendNetworkMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var mechanism in part.Mechanisms)
|
|
||||||
{
|
|
||||||
if (!Template.MechanismLayers.TryGetValue(mechanism.Id, out var mechanismMap))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_reflectionManager.TryParseEnumReference(mechanismMap, out var mechanismEnum))
|
|
||||||
{
|
|
||||||
Logger.Warning($"Template {Template.Name} has an invalid RSI map key {mechanismMap} for mechanism {mechanism.Id}.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mechanismMessage = new MechanismSpriteRemovedMessage(mechanismEnum);
|
|
||||||
|
|
||||||
SendNetworkMessage(mechanismMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CurrentDamageState == DamageState.Dead) return true;
|
|
||||||
|
|
||||||
// creadth: fall down if no legs
|
|
||||||
if (part.PartType == BodyPartType.Leg && Parts.Count(x => x.Value.PartType == BodyPartType.Leg) == 0)
|
|
||||||
{
|
|
||||||
EntitySystem.Get<StandingStateSystem>().Down(Owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
// creadth: immediately kill entity if last vital part removed
|
|
||||||
if (part.IsVital && Parts.Count(x => x.Value.PartType == part.PartType) == 0)
|
|
||||||
{
|
|
||||||
CurrentDamageState = DamageState.Dead;
|
|
||||||
ForceHealthChangedEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryGetSlotConnections(slot, out var connections))
|
|
||||||
{
|
|
||||||
foreach (var connectionName in connections)
|
|
||||||
{
|
|
||||||
if (TryGetPart(connectionName, out var result) && !ConnectedToCenter(result))
|
|
||||||
{
|
|
||||||
RemovePart(connectionName, drop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OnBodyChanged();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool RemovePart(IBodyPart part, [NotNullWhen(true)] out string? slot)
|
|
||||||
{
|
|
||||||
DebugTools.AssertNotNull(part);
|
|
||||||
|
|
||||||
var pair = _parts.FirstOrDefault(kvPair => kvPair.Value == part);
|
|
||||||
|
|
||||||
if (pair.Equals(default))
|
|
||||||
{
|
|
||||||
slot = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
slot = pair.Key;
|
|
||||||
|
|
||||||
return RemovePart(slot, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEntity? DropPart(IBodyPart part)
|
|
||||||
{
|
|
||||||
DebugTools.AssertNotNull(part);
|
|
||||||
|
|
||||||
if (!_parts.ContainsValue(part))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!RemovePart(part, out var slotName))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call disconnect on all limbs that were hanging off this limb.
|
|
||||||
if (TryGetSlotConnections(slotName, out var connections))
|
|
||||||
{
|
|
||||||
// This loop is an unoptimized travesty. TODO: optimize to be less shit
|
|
||||||
foreach (var connectionName in connections)
|
|
||||||
{
|
|
||||||
if (TryGetPart(connectionName, out var result) && !ConnectedToCenter(result))
|
|
||||||
{
|
|
||||||
RemovePart(connectionName, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
part.SpawnDropped(out var dropped);
|
|
||||||
|
|
||||||
OnBodyChanged();
|
|
||||||
return dropped;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ConnectedToCenter(IBodyPart part)
|
|
||||||
{
|
|
||||||
var searchedSlots = new List<string>();
|
|
||||||
|
|
||||||
return TryGetSlot(part, out var result) &&
|
|
||||||
ConnectedToCenterPartRecursion(searchedSlots, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ConnectedToCenterPartRecursion(ICollection<string> searchedSlots, string slotName)
|
|
||||||
{
|
|
||||||
if (!TryGetPart(slotName, out var part))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part == CenterPart())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
searchedSlots.Add(slotName);
|
|
||||||
|
|
||||||
if (!TryGetSlotConnections(slotName, out var connections))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var connection in connections)
|
|
||||||
{
|
|
||||||
if (!searchedSlots.Contains(connection) &&
|
|
||||||
ConnectedToCenterPartRecursion(searchedSlots, connection))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IBodyPart? CenterPart()
|
|
||||||
{
|
|
||||||
Parts.TryGetValue(Template.CenterSlot, out var center);
|
|
||||||
return center;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasSlot(string slot)
|
|
||||||
{
|
|
||||||
return Template.HasSlot(slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetPart(string slot, [NotNullWhen(true)] out IBodyPart? result)
|
|
||||||
{
|
|
||||||
return Parts.TryGetValue(slot, out result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetSlot(IBodyPart part, [NotNullWhen(true)] out string? slot)
|
|
||||||
{
|
|
||||||
// We enforce that there is only one of each value in the dictionary,
|
|
||||||
// so we can iterate through the dictionary values to get the key from there.
|
|
||||||
var pair = Parts.FirstOrDefault(x => x.Value == part);
|
|
||||||
slot = pair.Key;
|
|
||||||
|
|
||||||
return !pair.Equals(default);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetSlotType(string slot, out BodyPartType result)
|
|
||||||
{
|
|
||||||
return Template.Slots.TryGetValue(slot, out result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetSlotConnections(string slot, [NotNullWhen(true)] out List<string>? connections)
|
|
||||||
{
|
|
||||||
return Template.Connections.TryGetValue(slot, out connections);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetPartConnections(string slot, [NotNullWhen(true)] out List<IBodyPart>? result)
|
|
||||||
{
|
|
||||||
result = null;
|
|
||||||
|
|
||||||
if (!Template.Connections.TryGetValue(slot, out var connections))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var toReturn = new List<IBodyPart>();
|
|
||||||
foreach (var connection in connections)
|
|
||||||
{
|
|
||||||
if (TryGetPart(connection, out var partResult))
|
|
||||||
{
|
|
||||||
toReturn.Add(partResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toReturn.Count <= 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = toReturn;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetPartConnections(IBodyPart part, [NotNullWhen(true)] out List<IBodyPart>? connections)
|
|
||||||
{
|
|
||||||
connections = null;
|
|
||||||
|
|
||||||
return TryGetSlot(part, out var slotName) &&
|
|
||||||
TryGetPartConnections(slotName, out connections);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<IBodyPart> GetPartsOfType(BodyPartType type)
|
|
||||||
{
|
|
||||||
var toReturn = new List<IBodyPart>();
|
|
||||||
|
|
||||||
foreach (var part in Parts.Values)
|
|
||||||
{
|
|
||||||
if (part.PartType == type)
|
|
||||||
{
|
|
||||||
toReturn.Add(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CalculateSpeed()
|
|
||||||
{
|
|
||||||
if (!Owner.TryGetComponent(out MovementSpeedModifierComponent? playerMover))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float speedSum = 0;
|
|
||||||
foreach (var part in _activeLegs.Keys)
|
|
||||||
{
|
|
||||||
if (!part.HasProperty<LegProperty>())
|
|
||||||
{
|
|
||||||
_activeLegs.Remove(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (key, value) in _activeLegs)
|
|
||||||
{
|
|
||||||
if (key.TryGetProperty(out LegProperty? leg))
|
|
||||||
{
|
|
||||||
// Speed of a leg = base speed * (1+log1024(leg length))
|
|
||||||
speedSum += leg.Speed * (1 + (float) Math.Log(value, 1024.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (speedSum <= 0.001f || _activeLegs.Count <= 0)
|
|
||||||
{
|
|
||||||
playerMover.BaseWalkSpeed = 0.8f;
|
|
||||||
playerMover.BaseSprintSpeed = 2.0f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Extra legs stack diminishingly.
|
|
||||||
// Final speed = speed sum/(leg count-log4(leg count))
|
|
||||||
playerMover.BaseWalkSpeed =
|
|
||||||
speedSum / (_activeLegs.Count - (float) Math.Log(_activeLegs.Count, 4.0));
|
|
||||||
|
|
||||||
playerMover.BaseSprintSpeed = playerMover.BaseWalkSpeed * 1.75f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the layout of this body changes.
|
|
||||||
/// </summary>
|
|
||||||
private void OnBodyChanged()
|
|
||||||
{
|
|
||||||
// Calculate move speed based on this body.
|
|
||||||
if (Owner.HasComponent<MovementSpeedModifierComponent>())
|
|
||||||
{
|
|
||||||
_activeLegs.Clear();
|
|
||||||
var legParts = Parts.Values.Where(x => x.HasProperty(typeof(LegProperty)));
|
|
||||||
|
|
||||||
foreach (var part in legParts)
|
|
||||||
{
|
|
||||||
var footDistance = DistanceToNearestFoot(part);
|
|
||||||
|
|
||||||
if (Math.Abs(footDistance - float.MinValue) > 0.001f)
|
|
||||||
{
|
|
||||||
_activeLegs.Add(part, footDistance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CalculateSpeed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the combined length of the distance to the nearest <see cref="BodyPart"/> with a
|
|
||||||
/// <see cref="FootProperty"/>. Returns <see cref="float.MinValue"/>
|
|
||||||
/// if there is no foot found. If you consider a <see cref="BodyManagerComponent"/> a node map, then it will look for
|
|
||||||
/// a foot node from the given node. It can
|
|
||||||
/// only search through BodyParts with <see cref="ExtensionProperty"/>.
|
|
||||||
/// </summary>
|
|
||||||
public float DistanceToNearestFoot(IBodyPart source)
|
|
||||||
{
|
|
||||||
if (source.HasProperty<FootProperty>() && source.TryGetProperty<ExtensionProperty>(out var property))
|
|
||||||
{
|
|
||||||
return property.ReachDistance;
|
|
||||||
}
|
|
||||||
|
|
||||||
return LookForFootRecursion(source, new List<BodyPart>());
|
|
||||||
}
|
|
||||||
|
|
||||||
private float LookForFootRecursion(IBodyPart current,
|
|
||||||
ICollection<BodyPart> searchedParts)
|
|
||||||
{
|
|
||||||
if (!current.TryGetProperty<ExtensionProperty>(out var extProperty))
|
|
||||||
{
|
|
||||||
return float.MinValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all connected parts if the current part has an extension property
|
|
||||||
if (!TryGetPartConnections(current, out var connections))
|
|
||||||
{
|
|
||||||
return float.MinValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a connected BodyPart is a foot, return this BodyPart's length.
|
|
||||||
foreach (var connection in connections)
|
|
||||||
{
|
|
||||||
if (!searchedParts.Contains(connection) && connection.HasProperty<FootProperty>())
|
|
||||||
{
|
|
||||||
return extProperty.ReachDistance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, get the recursion values of all connected BodyParts and
|
|
||||||
// store them in a list.
|
|
||||||
var distances = new List<float>();
|
|
||||||
foreach (var connection in connections)
|
|
||||||
{
|
|
||||||
if (!searchedParts.Contains(connection))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = LookForFootRecursion(connection, searchedParts);
|
|
||||||
|
|
||||||
if (Math.Abs(result - float.MinValue) > 0.001f)
|
|
||||||
{
|
|
||||||
distances.Add(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If one or more of the searches found a foot, return the smallest one
|
|
||||||
// and add this ones length.
|
|
||||||
if (distances.Count > 0)
|
|
||||||
{
|
|
||||||
return distances.Min<float>() + extProperty.ReachDistance;
|
|
||||||
}
|
|
||||||
|
|
||||||
return float.MinValue;
|
|
||||||
|
|
||||||
// No extension property, no go.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,296 +0,0 @@
|
|||||||
#nullable enable
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Content.Server.Body;
|
|
||||||
using Content.Server.Body.Network;
|
|
||||||
using Content.Server.GameObjects.Components.Metabolism;
|
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
|
||||||
using Content.Server.Observer;
|
|
||||||
using Content.Shared.Body.Part;
|
|
||||||
using Content.Shared.Body.Preset;
|
|
||||||
using Content.Shared.Body.Template;
|
|
||||||
using Content.Shared.GameObjects.Components.Body;
|
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
|
||||||
using Content.Shared.GameObjects.Components.Movement;
|
|
||||||
using Robust.Server.Interfaces.Player;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.Reflection;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Players;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Body
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Component representing a collection of <see cref="IBodyPart"></see>
|
|
||||||
/// attached to each other.
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
|
||||||
[ComponentReference(typeof(IDamageableComponent))]
|
|
||||||
[ComponentReference(typeof(ISharedBodyManagerComponent))]
|
|
||||||
[ComponentReference(typeof(IBodyPartManager))]
|
|
||||||
[ComponentReference(typeof(IBodyManagerComponent))]
|
|
||||||
public partial class BodyManagerComponent : SharedBodyManagerComponent, IBodyPartContainer, IRelayMoveInput, IBodyManagerComponent
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
|
||||||
[Dependency] private readonly IBodyNetworkFactory _bodyNetworkFactory = default!;
|
|
||||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
|
||||||
|
|
||||||
[ViewVariables] private string _presetName = default!;
|
|
||||||
|
|
||||||
[ViewVariables] private readonly Dictionary<Type, BodyNetwork> _networks = new Dictionary<Type, BodyNetwork>();
|
|
||||||
|
|
||||||
[ViewVariables] public BodyTemplate Template { get; private set; } = default!;
|
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
|
||||||
{
|
|
||||||
base.ExposeData(serializer);
|
|
||||||
|
|
||||||
serializer.DataReadWriteFunction(
|
|
||||||
"baseTemplate",
|
|
||||||
"bodyTemplate.Humanoid",
|
|
||||||
template =>
|
|
||||||
{
|
|
||||||
if (!_prototypeManager.TryIndex(template, out BodyTemplatePrototype prototype))
|
|
||||||
{
|
|
||||||
// Invalid prototype
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"No {nameof(BodyTemplatePrototype)} found with name {template}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Template = new BodyTemplate();
|
|
||||||
Template.Initialize(prototype);
|
|
||||||
},
|
|
||||||
() => Template.Name);
|
|
||||||
|
|
||||||
serializer.DataReadWriteFunction(
|
|
||||||
"basePreset",
|
|
||||||
"bodyPreset.BasicHuman",
|
|
||||||
preset =>
|
|
||||||
{
|
|
||||||
if (!_prototypeManager.TryIndex(preset, out BodyPresetPrototype prototype))
|
|
||||||
{
|
|
||||||
// Invalid prototype
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"No {nameof(BodyPresetPrototype)} found with name {preset}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Preset = new BodyPreset();
|
|
||||||
Preset.Initialize(prototype);
|
|
||||||
},
|
|
||||||
() => _presetName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
LoadBodyPreset(Preset);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Startup()
|
|
||||||
{
|
|
||||||
base.Startup();
|
|
||||||
|
|
||||||
// Just in case something activates at default health.
|
|
||||||
ForceHealthChangedEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadBodyPreset(BodyPreset preset)
|
|
||||||
{
|
|
||||||
_presetName = preset.Name;
|
|
||||||
|
|
||||||
foreach (var slotName in Template.Slots.Keys)
|
|
||||||
{
|
|
||||||
// For each slot in our BodyManagerComponent's template,
|
|
||||||
// try and grab what the ID of what the preset says should be inside it.
|
|
||||||
if (!preset.PartIDs.TryGetValue(slotName, out var partId))
|
|
||||||
{
|
|
||||||
// If the preset doesn't define anything for it, continue.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the BodyPartPrototype corresponding to the BodyPart ID we grabbed.
|
|
||||||
if (!_prototypeManager.TryIndex(partId, out BodyPartPrototype newPartData))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"No {nameof(BodyPartPrototype)} prototype found with ID {partId}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try and remove an existing limb if that exists.
|
|
||||||
RemovePart(slotName, false);
|
|
||||||
|
|
||||||
// Add a new BodyPart with the BodyPartPrototype as a baseline to our
|
|
||||||
// BodyComponent.
|
|
||||||
var addedPart = new BodyPart(newPartData);
|
|
||||||
TryAddPart(slotName, addedPart);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnBodyChanged(); // TODO: Duplicate code
|
|
||||||
}
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// Changes the current <see cref="BodyTemplate"/> to the given
|
|
||||||
// /// <see cref="BodyTemplate"/>.
|
|
||||||
// /// Attempts to keep previous <see cref="IBodyPart"/> if there is a
|
|
||||||
// /// slot for them in both <see cref="BodyTemplate"/>.
|
|
||||||
// /// </summary>
|
|
||||||
// public void ChangeBodyTemplate(BodyTemplatePrototype newTemplate)
|
|
||||||
// {
|
|
||||||
// foreach (var part in Parts)
|
|
||||||
// {
|
|
||||||
// // TODO: Make this work.
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// OnBodyChanged();
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This method is called by <see cref="BodySystem.Update"/> before
|
|
||||||
/// <see cref="MetabolismComponent.Update"/> is called.
|
|
||||||
/// </summary>
|
|
||||||
public void PreMetabolism(float frameTime)
|
|
||||||
{
|
|
||||||
if (CurrentDamageState == DamageState.Dead)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var part in Parts.Values)
|
|
||||||
{
|
|
||||||
part.PreMetabolism(frameTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var network in _networks.Values)
|
|
||||||
{
|
|
||||||
network.PreMetabolism(frameTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This method is called by <see cref="BodySystem.Update"/> after
|
|
||||||
/// <see cref="MetabolismComponent.Update"/> is called.
|
|
||||||
/// </summary>
|
|
||||||
public void PostMetabolism(float frameTime)
|
|
||||||
{
|
|
||||||
if (CurrentDamageState == DamageState.Dead)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var part in Parts.Values)
|
|
||||||
{
|
|
||||||
part.PostMetabolism(frameTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var network in _networks.Values)
|
|
||||||
{
|
|
||||||
network.PostMetabolism(frameTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRelayMoveInput.MoveInputPressed(ICommonSession session)
|
|
||||||
{
|
|
||||||
if (CurrentDamageState == DamageState.Dead)
|
|
||||||
{
|
|
||||||
new Ghost().Execute(null, (IPlayerSession) session, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region BodyNetwork Functions
|
|
||||||
|
|
||||||
private bool EnsureNetwork(BodyNetwork network)
|
|
||||||
{
|
|
||||||
DebugTools.AssertNotNull(network);
|
|
||||||
|
|
||||||
if (_networks.ContainsKey(network.GetType()))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_networks.Add(network.GetType(), network);
|
|
||||||
network.OnAdd(Owner);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to add a <see cref="BodyNetwork"/> of the given type to this body.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// True if successful, false if there was an error
|
|
||||||
/// (such as passing in an invalid type or a network of that type already
|
|
||||||
/// existing).
|
|
||||||
/// </returns>
|
|
||||||
public bool EnsureNetwork(Type networkType)
|
|
||||||
{
|
|
||||||
DebugTools.Assert(networkType.IsSubclassOf(typeof(BodyNetwork)));
|
|
||||||
|
|
||||||
var network = _bodyNetworkFactory.GetNetwork(networkType);
|
|
||||||
return EnsureNetwork(network);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to add a <see cref="BodyNetwork"/> of the given type to
|
|
||||||
/// this body.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of network to add.</typeparam>
|
|
||||||
/// <returns>
|
|
||||||
/// True if successful, false if there was an error
|
|
||||||
/// (such as passing in an invalid type or a network of that type already
|
|
||||||
/// existing).
|
|
||||||
/// </returns>
|
|
||||||
public bool EnsureNetwork<T>() where T : BodyNetwork
|
|
||||||
{
|
|
||||||
return EnsureNetwork(typeof(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveNetwork(Type networkType)
|
|
||||||
{
|
|
||||||
DebugTools.AssertNotNull(networkType);
|
|
||||||
|
|
||||||
if (_networks.Remove(networkType, out var network))
|
|
||||||
{
|
|
||||||
network.OnRemove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveNetwork<T>() where T : BodyNetwork
|
|
||||||
{
|
|
||||||
RemoveNetwork(typeof(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attempts to get the <see cref="BodyNetwork"/> of the given type in this body.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="networkType">The type to search for.</param>
|
|
||||||
/// <param name="result">
|
|
||||||
/// The <see cref="BodyNetwork"/> if found, null otherwise.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>True if found, false otherwise.</returns>
|
|
||||||
public bool TryGetNetwork(Type networkType, [NotNullWhen(true)] out BodyNetwork result)
|
|
||||||
{
|
|
||||||
return _networks.TryGetValue(networkType, out result!);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IBodyManagerHealthChangeParams
|
|
||||||
{
|
|
||||||
BodyPartType Part { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BodyManagerHealthChangeParams : HealthChangeParams, IBodyManagerHealthChangeParams
|
|
||||||
{
|
|
||||||
public BodyManagerHealthChangeParams(BodyPartType part)
|
|
||||||
{
|
|
||||||
Part = part;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BodyPartType Part { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Body.Part;
|
||||||
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Body
|
||||||
|
{
|
||||||
|
public interface IBodyHealthChangeParams
|
||||||
|
{
|
||||||
|
BodyPartType Part { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BodyHealthChangeParams : HealthChangeParams, IBodyHealthChangeParams
|
||||||
|
{
|
||||||
|
public BodyHealthChangeParams(BodyPartType part)
|
||||||
|
{
|
||||||
|
Part = part;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BodyPartType Part { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.Body;
|
|
||||||
using Content.Server.Utility;
|
using Content.Server.Utility;
|
||||||
using Content.Shared.Body.Scanner;
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Scanner;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
using Robust.Server.GameObjects.Components.UserInterface;
|
using Robust.Server.GameObjects.Components.UserInterface;
|
||||||
using Robust.Server.Interfaces.GameObjects;
|
using Robust.Server.Interfaces.GameObjects;
|
||||||
@@ -14,27 +13,32 @@ namespace Content.Server.GameObjects.Components.Body
|
|||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
[ComponentReference(typeof(IActivate))]
|
[ComponentReference(typeof(IActivate))]
|
||||||
public class BodyScannerComponent : Component, IActivate
|
[ComponentReference(typeof(SharedBodyScannerComponent))]
|
||||||
|
public class BodyScannerComponent : SharedBodyScannerComponent, IActivate
|
||||||
{
|
{
|
||||||
public sealed override string Name => "BodyScanner";
|
|
||||||
|
|
||||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(BodyScannerUiKey.Key);
|
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(BodyScannerUiKey.Key);
|
||||||
|
|
||||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor) ||
|
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
|
||||||
actor.playerSession.AttachedEntity == null)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actor.playerSession.AttachedEntity.TryGetComponent(out BodyManagerComponent? attempt))
|
var session = actor.playerSession;
|
||||||
|
|
||||||
|
if (session.AttachedEntity == null)
|
||||||
{
|
{
|
||||||
var state = InterfaceState(attempt.Template, attempt.Parts);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.AttachedEntity.TryGetComponent(out IBody? body))
|
||||||
|
{
|
||||||
|
var state = InterfaceState(body);
|
||||||
UserInterface?.SetState(state);
|
UserInterface?.SetState(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
UserInterface?.Open(actor.playerSession);
|
UserInterface?.Open(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
@@ -56,29 +60,9 @@ namespace Content.Server.GameObjects.Components.Body
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Copy BodyTemplate and BodyPart data into a common data class that the client can read.
|
/// Copy BodyTemplate and BodyPart data into a common data class that the client can read.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private BodyScannerInterfaceState InterfaceState(BodyTemplate template, IReadOnlyDictionary<string, IBodyPart> bodyParts)
|
private BodyScannerUIState InterfaceState(IBody body)
|
||||||
{
|
{
|
||||||
var partsData = new Dictionary<string, BodyScannerBodyPartData>();
|
return new BodyScannerUIState(body.Owner.Uid);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Content.Server.GameObjects.Components.Metabolism;
|
|||||||
using Content.Server.Interfaces;
|
using Content.Server.Interfaces;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Networks;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
@@ -12,7 +13,8 @@ using Robust.Shared.ViewVariables;
|
|||||||
namespace Content.Server.GameObjects.Components.Body.Circulatory
|
namespace Content.Server.GameObjects.Components.Body.Circulatory
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class BloodstreamComponent : Component, IGasMixtureHolder
|
[ComponentReference(typeof(SharedBloodstreamComponent))]
|
||||||
|
public class BloodstreamComponent : SharedBloodstreamComponent, IGasMixtureHolder
|
||||||
{
|
{
|
||||||
public override string Name => "Bloodstream";
|
public override string Name => "Bloodstream";
|
||||||
|
|
||||||
@@ -58,7 +60,7 @@ namespace Content.Server.GameObjects.Components.Body.Circulatory
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="solution">Solution to be transferred</param>
|
/// <param name="solution">Solution to be transferred</param>
|
||||||
/// <returns>Whether or not transfer was a success</returns>
|
/// <returns>Whether or not transfer was a success</returns>
|
||||||
public bool TryTransferSolution(Solution solution)
|
public override bool TryTransferSolution(Solution solution)
|
||||||
{
|
{
|
||||||
// For now doesn't support partial transfers
|
// For now doesn't support partial transfers
|
||||||
if (solution.TotalVolume + _internalSolution.CurrentVolume > _internalSolution.MaxVolume)
|
if (solution.TotalVolume + _internalSolution.CurrentVolume > _internalSolution.MaxVolume)
|
||||||
|
|||||||
@@ -1,213 +0,0 @@
|
|||||||
#nullable enable
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.Body;
|
|
||||||
using Content.Server.Body.Mechanisms;
|
|
||||||
using Content.Server.Utility;
|
|
||||||
using Content.Shared.Body.Mechanism;
|
|
||||||
using Content.Shared.Body.Surgery;
|
|
||||||
using Content.Shared.Interfaces;
|
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Server.GameObjects.Components.UserInterface;
|
|
||||||
using Robust.Server.Interfaces.GameObjects;
|
|
||||||
using Robust.Server.Interfaces.Player;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Localization;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Body
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Component representing a dropped, tangible <see cref="Mechanism"/> entity.
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
|
||||||
public class DroppedMechanismComponent : Component, IAfterInteract
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
|
||||||
|
|
||||||
public sealed override string Name => "DroppedMechanism";
|
|
||||||
|
|
||||||
private readonly Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
|
|
||||||
|
|
||||||
private BodyManagerComponent? _bodyManagerComponentCache;
|
|
||||||
|
|
||||||
private int _idHash;
|
|
||||||
|
|
||||||
private IEntity? _performerCache;
|
|
||||||
|
|
||||||
[ViewVariables] public IMechanism ContainedMechanism { get; private set; } = default!;
|
|
||||||
|
|
||||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GenericSurgeryUiKey.Key);
|
|
||||||
|
|
||||||
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
if (eventArgs.Target == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CloseAllSurgeryUIs();
|
|
||||||
_optionsCache.Clear();
|
|
||||||
_performerCache = null;
|
|
||||||
_bodyManagerComponentCache = null;
|
|
||||||
|
|
||||||
if (eventArgs.Target.TryGetComponent<BodyManagerComponent>(out var bodyManager))
|
|
||||||
{
|
|
||||||
SendBodyPartListToUser(eventArgs, bodyManager);
|
|
||||||
}
|
|
||||||
else if (eventArgs.Target.TryGetComponent<DroppedBodyPartComponent>(out var droppedBodyPart))
|
|
||||||
{
|
|
||||||
DebugTools.AssertNotNull(droppedBodyPart.ContainedBodyPart);
|
|
||||||
|
|
||||||
if (!droppedBodyPart.ContainedBodyPart.TryInstallDroppedMechanism(this))
|
|
||||||
{
|
|
||||||
eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("You can't fit it in!"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
if (UserInterface != null)
|
|
||||||
{
|
|
||||||
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void InitializeDroppedMechanism(IMechanism data)
|
|
||||||
{
|
|
||||||
ContainedMechanism = data;
|
|
||||||
Owner.Name = Loc.GetString(ContainedMechanism.Name);
|
|
||||||
|
|
||||||
if (Owner.TryGetComponent(out SpriteComponent? component))
|
|
||||||
{
|
|
||||||
component.LayerSetRSI(0, data.RSIPath);
|
|
||||||
component.LayerSetState(0, data.RSIState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
|
||||||
{
|
|
||||||
// This is a temporary way to have spawnable hard-coded DroppedMechanismComponent prototypes
|
|
||||||
// In the future (when it becomes possible) DroppedMechanismComponent should be auto-generated from
|
|
||||||
// the Mechanism prototypes
|
|
||||||
var debugLoadMechanismData = "";
|
|
||||||
base.ExposeData(serializer);
|
|
||||||
|
|
||||||
serializer.DataField(ref debugLoadMechanismData, "debugLoadMechanismData", "");
|
|
||||||
|
|
||||||
if (serializer.Reading && debugLoadMechanismData != "")
|
|
||||||
{
|
|
||||||
_prototypeManager.TryIndex(debugLoadMechanismData!, out MechanismPrototype data);
|
|
||||||
|
|
||||||
var mechanism = new Mechanism(data);
|
|
||||||
mechanism.EnsureInitialize();
|
|
||||||
|
|
||||||
InitializeDroppedMechanism(mechanism);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SendBodyPartListToUser(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>();
|
|
||||||
|
|
||||||
foreach (var (key, value) in bodyManager.Parts)
|
|
||||||
{
|
|
||||||
// For each limb in the target, add it to our cache if it is a valid option.
|
|
||||||
if (value.CanInstallMechanism(ContainedMechanism))
|
|
||||||
{
|
|
||||||
_optionsCache.Add(_idHash, value);
|
|
||||||
toSend.Add(key + ": " + value.Name, _idHash++);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_optionsCache.Count > 0)
|
|
||||||
{
|
|
||||||
OpenSurgeryUI(eventArgs.User.GetComponent<BasicActorComponent>().playerSession);
|
|
||||||
UpdateSurgeryUIBodyPartRequest(eventArgs.User.GetComponent<BasicActorComponent>().playerSession,
|
|
||||||
toSend);
|
|
||||||
_performerCache = eventArgs.User;
|
|
||||||
_bodyManagerComponentCache = bodyManager;
|
|
||||||
}
|
|
||||||
else // If surgery cannot be performed, show message saying so.
|
|
||||||
{
|
|
||||||
eventArgs.Target.PopupMessage(eventArgs.User,
|
|
||||||
Loc.GetString("You see no way to install the {0}.", Owner.Name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called after the client chooses from a list of possible BodyParts that can be operated on.
|
|
||||||
/// </summary>
|
|
||||||
private void HandleReceiveBodyPart(int key)
|
|
||||||
{
|
|
||||||
if (_performerCache == null ||
|
|
||||||
!_performerCache.TryGetComponent(out IActorComponent? actor))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CloseSurgeryUI(actor.playerSession);
|
|
||||||
|
|
||||||
if (_bodyManagerComponentCache == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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))
|
|
||||||
{
|
|
||||||
_bodyManagerComponentCache.Owner.PopupMessage(_performerCache,
|
|
||||||
Loc.GetString("You see no useful way to use the {0} anymore.", Owner.Name));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var target = (BodyPart) targetObject;
|
|
||||||
var message = target.TryInstallDroppedMechanism(this)
|
|
||||||
? Loc.GetString("You jam the {0} inside {1:them}.", ContainedMechanism.Name, _performerCache)
|
|
||||||
: Loc.GetString("You can't fit it in!");
|
|
||||||
|
|
||||||
_bodyManagerComponentCache.Owner.PopupMessage(_performerCache, message);
|
|
||||||
|
|
||||||
// TODO: {1:theName}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OpenSurgeryUI(IPlayerSession session)
|
|
||||||
{
|
|
||||||
UserInterface?.Open(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary<string, int> options)
|
|
||||||
{
|
|
||||||
UserInterface?.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CloseSurgeryUI(IPlayerSession session)
|
|
||||||
{
|
|
||||||
UserInterface?.Close(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CloseAllSurgeryUIs()
|
|
||||||
{
|
|
||||||
UserInterface?.CloseAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
|
|
||||||
{
|
|
||||||
switch (message.Message)
|
|
||||||
{
|
|
||||||
case ReceiveBodyPartSurgeryUIMessage msg:
|
|
||||||
HandleReceiveBodyPart(msg.SelectedOptionId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Content.Server.Body;
|
|
||||||
using Content.Server.Body.Network;
|
|
||||||
using Content.Shared.GameObjects.Components.Body;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Body
|
|
||||||
{
|
|
||||||
// TODO: Merge with ISharedBodyManagerComponent
|
|
||||||
public interface IBodyManagerComponent : ISharedBodyManagerComponent, IBodyPartManager
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="BodyTemplate"/> that this
|
|
||||||
/// <see cref="BodyManagerComponent"/> is adhering to.
|
|
||||||
/// </summary>
|
|
||||||
public BodyTemplate Template { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Installs the given <see cref="IBodyPart"/> into the given slot.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if successful, false otherwise.</returns>
|
|
||||||
bool TryAddPart(string slot, IBodyPart part, bool force = false);
|
|
||||||
|
|
||||||
bool HasPart(string slot);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ensures that this body has the specified network.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of the network to ensure.</typeparam>
|
|
||||||
/// <returns>
|
|
||||||
/// True if the network already existed, false if it had to be created.
|
|
||||||
/// </returns>
|
|
||||||
bool EnsureNetwork<T>() where T : BodyNetwork;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ensures that this body has the specified network.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="networkType">The type of the network to ensure.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// True if the network already existed, false if it had to be created.
|
|
||||||
/// </returns>
|
|
||||||
bool EnsureNetwork(Type networkType);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes the <see cref="BodyNetwork"/> of the given type in this body,
|
|
||||||
/// if one exists.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of the network to remove.</typeparam>
|
|
||||||
void RemoveNetwork<T>() where T : BodyNetwork;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes the <see cref="BodyNetwork"/> of the given type in this body,
|
|
||||||
/// if there is one.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="networkType">The type of the network to remove.</param>
|
|
||||||
void RemoveNetwork(Type networkType);
|
|
||||||
|
|
||||||
void PreMetabolism(float frameTime);
|
|
||||||
|
|
||||||
void PostMetabolism(float frameTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
181
Content.Server/GameObjects/Components/Body/MechanismComponent.cs
Normal file
181
Content.Server/GameObjects/Components/Body/MechanismComponent.cs
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.Utility;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Mechanism;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Part;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Surgery;
|
||||||
|
using Content.Shared.Interfaces;
|
||||||
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Server.Interfaces.GameObjects;
|
||||||
|
using Robust.Server.Interfaces.Player;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Body
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedMechanismComponent))]
|
||||||
|
[ComponentReference(typeof(IMechanism))]
|
||||||
|
public class MechanismComponent : SharedMechanismComponent, IAfterInteract
|
||||||
|
{
|
||||||
|
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(SurgeryUIKey.Key);
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
if (UserInterface != null)
|
||||||
|
{
|
||||||
|
UserInterface.OnReceiveMessage += OnUIMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (eventArgs.Target == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseAllSurgeryUIs();
|
||||||
|
OptionsCache.Clear();
|
||||||
|
PerformerCache = null;
|
||||||
|
BodyCache = null;
|
||||||
|
|
||||||
|
if (eventArgs.Target.TryGetBody(out var body))
|
||||||
|
{
|
||||||
|
SendBodyPartListToUser(eventArgs, body);
|
||||||
|
}
|
||||||
|
else if (eventArgs.Target.TryGetComponent<IBodyPart>(out var part))
|
||||||
|
{
|
||||||
|
DebugTools.AssertNotNull(part);
|
||||||
|
|
||||||
|
if (!part.TryAddMechanism(this))
|
||||||
|
{
|
||||||
|
eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("You can't fit it in!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendBodyPartListToUser(AfterInteractEventArgs eventArgs, IBody body)
|
||||||
|
{
|
||||||
|
// Create dictionary to send to client (text to be shown : data sent back if selected)
|
||||||
|
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.CanAddMechanism(this))
|
||||||
|
{
|
||||||
|
OptionsCache.Add(IdHash, value);
|
||||||
|
toSend.Add(key + ": " + value.Name, IdHash++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OptionsCache.Count > 0 &&
|
||||||
|
eventArgs.User.TryGetComponent(out IActorComponent? actor))
|
||||||
|
{
|
||||||
|
OpenSurgeryUI(actor.playerSession);
|
||||||
|
UpdateSurgeryUIBodyPartRequest(actor.playerSession, toSend);
|
||||||
|
PerformerCache = eventArgs.User;
|
||||||
|
BodyCache = body;
|
||||||
|
}
|
||||||
|
else // If surgery cannot be performed, show message saying so.
|
||||||
|
{
|
||||||
|
eventArgs.Target.PopupMessage(eventArgs.User,
|
||||||
|
Loc.GetString("You see no way to install the {0}.", Owner.Name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called after the client chooses from a list of possible BodyParts that can be operated on.
|
||||||
|
/// </summary>
|
||||||
|
private void HandleReceiveBodyPart(int key)
|
||||||
|
{
|
||||||
|
if (PerformerCache == null ||
|
||||||
|
!PerformerCache.TryGetComponent(out IActorComponent? actor))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseSurgeryUI(actor.playerSession);
|
||||||
|
|
||||||
|
if (BodyCache == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
{
|
||||||
|
BodyCache.Owner.PopupMessage(PerformerCache,
|
||||||
|
Loc.GetString("You see no useful way to use the {0} anymore.", Owner.Name));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var target = (IBodyPart) targetObject;
|
||||||
|
var message = target.TryAddMechanism(this)
|
||||||
|
? Loc.GetString("You jam {0:theName} inside {1:them}.", Owner, PerformerCache)
|
||||||
|
: Loc.GetString("You can't fit it in!");
|
||||||
|
|
||||||
|
BodyCache.Owner.PopupMessage(PerformerCache, message);
|
||||||
|
|
||||||
|
// TODO: {1:theName}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenSurgeryUI(IPlayerSession session)
|
||||||
|
{
|
||||||
|
UserInterface?.Open(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary<string, int> options)
|
||||||
|
{
|
||||||
|
UserInterface?.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseSurgeryUI(IPlayerSession session)
|
||||||
|
{
|
||||||
|
UserInterface?.Close(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseAllSurgeryUIs()
|
||||||
|
{
|
||||||
|
UserInterface?.CloseAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUIMessage(ServerBoundUserInterfaceMessage message)
|
||||||
|
{
|
||||||
|
switch (message.Message)
|
||||||
|
{
|
||||||
|
case ReceiveBodyPartSurgeryUIMessage msg:
|
||||||
|
HandleReceiveBodyPart(msg.SelectedOptionId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPartAdd(IBodyPart? old, IBodyPart current)
|
||||||
|
{
|
||||||
|
base.OnPartAdd(old, current);
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out SpriteComponent? sprite))
|
||||||
|
{
|
||||||
|
sprite.Visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPartRemove(IBodyPart old)
|
||||||
|
{
|
||||||
|
base.OnPartRemove(old);
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out SpriteComponent? sprite))
|
||||||
|
{
|
||||||
|
sprite.Visible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Body;
|
|
||||||
using Content.Server.Utility;
|
using Content.Server.Utility;
|
||||||
using Content.Shared.Body.Surgery;
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Mechanism;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Part;
|
||||||
|
using Content.Shared.GameObjects.Components.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;
|
||||||
@@ -13,42 +15,43 @@ using Robust.Server.Interfaces.Player;
|
|||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Log;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Body
|
namespace Content.Server.GameObjects.Components.Body.Part
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Component representing a dropped, tangible <see cref="BodyPart"/> entity.
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class DroppedBodyPartComponent : Component, IAfterInteract, IBodyPartContainer
|
[ComponentReference(typeof(SharedBodyPartComponent))]
|
||||||
|
[ComponentReference(typeof(IBodyPart))]
|
||||||
|
public class BodyPartComponent : SharedBodyPartComponent, IAfterInteract
|
||||||
{
|
{
|
||||||
private readonly Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
|
private readonly Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
|
||||||
private BodyManagerComponent? _bodyManagerComponentCache;
|
|
||||||
|
private IBody? _owningBodyCache;
|
||||||
|
|
||||||
private int _idHash;
|
private int _idHash;
|
||||||
private IEntity? _performerCache;
|
|
||||||
|
|
||||||
public sealed override string Name => "DroppedBodyPart";
|
private IEntity? _surgeonCache;
|
||||||
|
|
||||||
[ViewVariables] public BodyPart ContainedBodyPart { get; private set; } = default!;
|
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(SurgeryUIKey.Key);
|
||||||
|
|
||||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GenericSurgeryUiKey.Key);
|
protected override void OnAddMechanism(IMechanism mechanism)
|
||||||
|
|
||||||
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
|
||||||
{
|
{
|
||||||
if (eventArgs.Target == null)
|
base.OnAddMechanism(mechanism);
|
||||||
|
|
||||||
|
if (mechanism.Owner.TryGetComponent(out SpriteComponent? sprite))
|
||||||
{
|
{
|
||||||
return;
|
sprite.Visible = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseAllSurgeryUIs();
|
protected override void OnRemoveMechanism(IMechanism mechanism)
|
||||||
_optionsCache.Clear();
|
|
||||||
_performerCache = null;
|
|
||||||
_bodyManagerComponentCache = null;
|
|
||||||
|
|
||||||
if (eventArgs.Target.TryGetComponent(out BodyManagerComponent? bodyManager))
|
|
||||||
{
|
{
|
||||||
SendBodySlotListToUser(eventArgs, bodyManager);
|
base.OnRemoveMechanism(mechanism);
|
||||||
|
|
||||||
|
if (mechanism.Owner.TryGetComponent(out SpriteComponent? sprite))
|
||||||
|
{
|
||||||
|
sprite.Visible = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,49 +59,77 @@ namespace Content.Server.GameObjects.Components.Body
|
|||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
|
// This is ran in Startup as entities spawned in Initialize
|
||||||
|
// are not synced to the client since they are assumed to be
|
||||||
|
// identical on it
|
||||||
|
foreach (var mechanismId in MechanismIds)
|
||||||
|
{
|
||||||
|
var entity = Owner.EntityManager.SpawnEntity(mechanismId, Owner.Transform.MapPosition);
|
||||||
|
|
||||||
|
if (!entity.TryGetComponent(out IMechanism? mechanism))
|
||||||
|
{
|
||||||
|
Logger.Error($"Entity {mechanismId} does not have a {nameof(IMechanism)} component.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TryAddMechanism(mechanism, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Startup()
|
||||||
|
{
|
||||||
|
base.Startup();
|
||||||
|
|
||||||
if (UserInterface != null)
|
if (UserInterface != null)
|
||||||
{
|
{
|
||||||
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
UserInterface.OnReceiveMessage += OnUIMessage;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TransferBodyPartData(BodyPart data)
|
foreach (var mechanism in Mechanisms)
|
||||||
{
|
{
|
||||||
ContainedBodyPart = data;
|
mechanism.Dirty();
|
||||||
Owner.Name = Loc.GetString(ContainedBodyPart.Name);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Owner.TryGetComponent(out SpriteComponent? component))
|
public void AfterInteract(AfterInteractEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
component.LayerSetRSI(0, data.RSIPath);
|
// TODO BODY
|
||||||
component.LayerSetState(0, data.RSIState);
|
if (eventArgs.Target == null)
|
||||||
|
|
||||||
if (data.RSIColor.HasValue)
|
|
||||||
{
|
{
|
||||||
component.LayerSetColor(0, data.RSIColor.Value);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CloseAllSurgeryUIs();
|
||||||
|
_optionsCache.Clear();
|
||||||
|
_surgeonCache = null;
|
||||||
|
_owningBodyCache = null;
|
||||||
|
|
||||||
|
if (eventArgs.Target.TryGetBody(out var body))
|
||||||
|
{
|
||||||
|
SendSlots(eventArgs, body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendBodySlotListToUser(AfterInteractEventArgs eventArgs, BodyManagerComponent bodyManager)
|
private void SendSlots(AfterInteractEventArgs eventArgs, IBody body)
|
||||||
{
|
{
|
||||||
// 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)
|
||||||
var toSend = new Dictionary<string, int>();
|
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
|
// 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.
|
// 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();
|
var unoccupiedSlots = body.Slots.Keys.ToList().Except(body.Parts.Keys.ToList()).ToList();
|
||||||
foreach (var slot in unoccupiedSlots)
|
foreach (var slot in unoccupiedSlots)
|
||||||
{
|
{
|
||||||
if (!bodyManager.TryGetSlotType(slot, out var typeResult) ||
|
if (!body.TryGetSlotType(slot, out var typeResult) ||
|
||||||
typeResult != ContainedBodyPart?.PartType ||
|
typeResult != PartType ||
|
||||||
!bodyManager.TryGetPartConnections(slot, out var parts))
|
!body.TryGetPartConnections(slot, out var parts))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var connectedPart in parts)
|
foreach (var connectedPart in parts)
|
||||||
{
|
{
|
||||||
if (!connectedPart.CanAttachPart(ContainedBodyPart))
|
if (!connectedPart.CanAttachPart(this))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -111,10 +142,10 @@ namespace Content.Server.GameObjects.Components.Body
|
|||||||
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,
|
BodyPartSlotRequest(eventArgs.User.GetComponent<BasicActorComponent>().playerSession,
|
||||||
toSend);
|
toSend);
|
||||||
_performerCache = eventArgs.User;
|
_surgeonCache = eventArgs.User;
|
||||||
_bodyManagerComponentCache = bodyManager;
|
_owningBodyCache = body;
|
||||||
}
|
}
|
||||||
else // If surgery cannot be performed, show message saying so.
|
else // If surgery cannot be performed, show message saying so.
|
||||||
{
|
{
|
||||||
@@ -124,19 +155,20 @@ namespace Content.Server.GameObjects.Components.Body
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called after the client chooses from a list of possible BodyPartSlots to install the limb on.
|
/// Called after the client chooses from a list of possible
|
||||||
|
/// BodyPartSlots to install the limb on.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void HandleReceiveBodyPartSlot(int key)
|
private void ReceiveBodyPartSlot(int key)
|
||||||
{
|
{
|
||||||
if (_performerCache == null ||
|
if (_surgeonCache == null ||
|
||||||
!_performerCache.TryGetComponent(out IActorComponent? actor))
|
!_surgeonCache.TryGetComponent(out IActorComponent? actor))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseSurgeryUI(actor.playerSession);
|
CloseSurgeryUI(actor.playerSession);
|
||||||
|
|
||||||
if (_bodyManagerComponentCache == null)
|
if (_owningBodyCache == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -144,23 +176,16 @@ namespace Content.Server.GameObjects.Components.Body
|
|||||||
// 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 var targetObject))
|
if (!_optionsCache.TryGetValue(key, out var targetObject))
|
||||||
{
|
{
|
||||||
_bodyManagerComponentCache.Owner.PopupMessage(_performerCache,
|
_owningBodyCache.Owner.PopupMessage(_surgeonCache,
|
||||||
Loc.GetString("You see no useful way to attach {0:theName} anymore.", Owner));
|
Loc.GetString("You see no useful way to attach {0:theName} anymore.", Owner));
|
||||||
}
|
}
|
||||||
|
|
||||||
var target = (string) targetObject!;
|
var target = (string) targetObject!;
|
||||||
string message;
|
var message = _owningBodyCache.TryAddPart(target, this)
|
||||||
|
? Loc.GetString("You attach {0:theName}.", Owner)
|
||||||
|
: Loc.GetString("You can't attach {0:theName}!", Owner);
|
||||||
|
|
||||||
if (_bodyManagerComponentCache.TryAddPart(target, this))
|
_owningBodyCache.Owner.PopupMessage(_surgeonCache, message);
|
||||||
{
|
|
||||||
message = Loc.GetString("You attach {0:theName}.", ContainedBodyPart);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
message = Loc.GetString("You can't attach it!");
|
|
||||||
}
|
|
||||||
|
|
||||||
_bodyManagerComponentCache.Owner.PopupMessage(_performerCache, message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenSurgeryUI(IPlayerSession session)
|
private void OpenSurgeryUI(IPlayerSession session)
|
||||||
@@ -168,7 +193,7 @@ namespace Content.Server.GameObjects.Components.Body
|
|||||||
UserInterface?.Open(session);
|
UserInterface?.Open(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateSurgeryUIBodyPartSlotRequest(IPlayerSession session, Dictionary<string, int> options)
|
private void BodyPartSlotRequest(IPlayerSession session, Dictionary<string, int> options)
|
||||||
{
|
{
|
||||||
UserInterface?.SendMessage(new RequestBodyPartSlotSurgeryUIMessage(options), session);
|
UserInterface?.SendMessage(new RequestBodyPartSlotSurgeryUIMessage(options), session);
|
||||||
}
|
}
|
||||||
@@ -183,12 +208,12 @@ namespace Content.Server.GameObjects.Components.Body
|
|||||||
UserInterface?.CloseAll();
|
UserInterface?.CloseAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
|
private void OnUIMessage(ServerBoundUserInterfaceMessage message)
|
||||||
{
|
{
|
||||||
switch (message.Message)
|
switch (message.Message)
|
||||||
{
|
{
|
||||||
case ReceiveBodyPartSlotSurgeryUIMessage msg:
|
case ReceiveBodyPartSlotSurgeryUIMessage msg:
|
||||||
HandleReceiveBodyPartSlot(msg.SelectedOptionId);
|
ReceiveBodyPartSlot(msg.SelectedOptionId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.Body;
|
|
||||||
using Content.Server.Body.Mechanisms;
|
|
||||||
using Content.Server.Body.Surgery;
|
|
||||||
using Content.Server.Utility;
|
using Content.Server.Utility;
|
||||||
using Content.Shared.Body.Surgery;
|
|
||||||
using Content.Shared.GameObjects;
|
using Content.Shared.GameObjects;
|
||||||
using Content.Shared.GameObjects.Components.Body;
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Mechanism;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Part;
|
||||||
|
using Content.Shared.GameObjects.Components.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;
|
||||||
@@ -19,13 +18,10 @@ using Robust.Shared.Interfaces.GameObjects;
|
|||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Log;
|
using Robust.Shared.Log;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Utility;
|
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Body
|
namespace Content.Server.GameObjects.Components.Body
|
||||||
{
|
{
|
||||||
// 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.
|
/// Server-side component representing a generic tool capable of performing surgery.
|
||||||
/// For instance, the scalpel.
|
/// For instance, the scalpel.
|
||||||
@@ -40,7 +36,7 @@ namespace Content.Server.GameObjects.Components.Body
|
|||||||
|
|
||||||
private float _baseOperateTime;
|
private float _baseOperateTime;
|
||||||
|
|
||||||
private BodyManagerComponent? _bodyManagerComponentCache;
|
private IBody? _bodyCache;
|
||||||
|
|
||||||
private ISurgeon.MechanismRequestCallback? _callbackCache;
|
private ISurgeon.MechanismRequestCallback? _callbackCache;
|
||||||
|
|
||||||
@@ -50,7 +46,7 @@ namespace Content.Server.GameObjects.Components.Body
|
|||||||
|
|
||||||
private SurgeryType _surgeryType;
|
private SurgeryType _surgeryType;
|
||||||
|
|
||||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GenericSurgeryUiKey.Key);
|
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(SurgeryUIKey.Key);
|
||||||
|
|
||||||
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
@@ -68,11 +64,11 @@ namespace Content.Server.GameObjects.Components.Body
|
|||||||
_optionsCache.Clear();
|
_optionsCache.Clear();
|
||||||
|
|
||||||
_performerCache = null;
|
_performerCache = null;
|
||||||
_bodyManagerComponentCache = null;
|
_bodyCache = null;
|
||||||
_callbackCache = null;
|
_callbackCache = null;
|
||||||
|
|
||||||
// Attempt surgery on a BodyManagerComponent by sending a list of operable BodyParts to the client to choose from
|
// Attempt surgery on a body by sending a list of operable parts for the client to choose from
|
||||||
if (eventArgs.Target.TryGetComponent(out BodyManagerComponent? body))
|
if (eventArgs.Target.TryGetBody(out var body))
|
||||||
{
|
{
|
||||||
// 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)
|
||||||
var toSend = new Dictionary<string, int>();
|
var toSend = new Dictionary<string, int>();
|
||||||
@@ -92,36 +88,34 @@ namespace Content.Server.GameObjects.Components.Body
|
|||||||
OpenSurgeryUI(actor.playerSession);
|
OpenSurgeryUI(actor.playerSession);
|
||||||
UpdateSurgeryUIBodyPartRequest(actor.playerSession, toSend);
|
UpdateSurgeryUIBodyPartRequest(actor.playerSession, toSend);
|
||||||
_performerCache = eventArgs.User; // Also, cache the data.
|
_performerCache = eventArgs.User; // Also, cache the data.
|
||||||
_bodyManagerComponentCache = body;
|
_bodyCache = body;
|
||||||
}
|
}
|
||||||
else // If surgery cannot be performed, show message saying so.
|
else // If surgery cannot be performed, show message saying so.
|
||||||
{
|
{
|
||||||
SendNoUsefulWayToUsePopup();
|
NotUsefulPopup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (eventArgs.Target.TryGetComponent<DroppedBodyPartComponent>(out var droppedBodyPart))
|
else if (eventArgs.Target.TryGetComponent<IBodyPart>(out var part))
|
||||||
{
|
{
|
||||||
// Attempt surgery on a DroppedBodyPart - there's only one possible target so no need for selection UI
|
// Attempt surgery on a DroppedBodyPart - there's only one possible target so no need for selection UI
|
||||||
_performerCache = eventArgs.User;
|
_performerCache = eventArgs.User;
|
||||||
|
|
||||||
DebugTools.AssertNotNull(droppedBodyPart.ContainedBodyPart);
|
|
||||||
|
|
||||||
// If surgery can be performed...
|
// If surgery can be performed...
|
||||||
if (!droppedBodyPart.ContainedBodyPart.SurgeryCheck(_surgeryType))
|
if (!part.SurgeryCheck(_surgeryType))
|
||||||
{
|
{
|
||||||
SendNoUsefulWayToUsePopup();
|
NotUsefulPopup();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...do the surgery.
|
// ...do the surgery.
|
||||||
if (droppedBodyPart.ContainedBodyPart.AttemptSurgery(_surgeryType, droppedBodyPart, this,
|
if (part.AttemptSurgery(_surgeryType, part, this,
|
||||||
eventArgs.User))
|
eventArgs.User))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log error if the surgery fails somehow.
|
// Log error if the surgery fails somehow.
|
||||||
Logger.Debug($"Error when trying to perform surgery on ${nameof(BodyPart)} {eventArgs.User.Name}");
|
Logger.Debug($"Error when trying to perform surgery on ${nameof(IBodyPart)} {eventArgs.User.Name}");
|
||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,6 +155,7 @@ namespace Content.Server.GameObjects.Components.Body
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO BODY add checks to close UI if user walks too far away from tool or target.
|
||||||
private void OpenSurgeryUI(IPlayerSession session)
|
private void OpenSurgeryUI(IPlayerSession session)
|
||||||
{
|
{
|
||||||
UserInterface?.Open(session);
|
UserInterface?.Open(session);
|
||||||
@@ -201,7 +196,7 @@ namespace Content.Server.GameObjects.Components.Body
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called after the client chooses from a list of possible
|
/// Called after the client chooses from a list of possible
|
||||||
/// <see cref="BodyPart"/> that can be operated on.
|
/// <see cref="IBodyPart"/> that can be operated on.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void HandleReceiveBodyPart(int key)
|
private void HandleReceiveBodyPart(int key)
|
||||||
{
|
{
|
||||||
@@ -214,17 +209,18 @@ namespace Content.Server.GameObjects.Components.Body
|
|||||||
CloseSurgeryUI(actor.playerSession);
|
CloseSurgeryUI(actor.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 var targetObject) ||
|
if (!_optionsCache.TryGetValue(key, out var targetObject) ||
|
||||||
_bodyManagerComponentCache == null)
|
_bodyCache == null)
|
||||||
{
|
{
|
||||||
SendNoUsefulWayToUseAnymorePopup();
|
NotUsefulAnymorePopup();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var target = (BodyPart) targetObject!;
|
var target = (IBodyPart) targetObject!;
|
||||||
|
|
||||||
if (!target.AttemptSurgery(_surgeryType, _bodyManagerComponentCache, this, _performerCache))
|
// TODO BODY Reconsider
|
||||||
|
if (!target.AttemptSurgery(_surgeryType, _bodyCache, this, _performerCache))
|
||||||
{
|
{
|
||||||
SendNoUsefulWayToUseAnymorePopup();
|
NotUsefulAnymorePopup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,25 +235,25 @@ namespace Content.Server.GameObjects.Components.Body
|
|||||||
_performerCache == null ||
|
_performerCache == null ||
|
||||||
!_performerCache.TryGetComponent(out IActorComponent? actor))
|
!_performerCache.TryGetComponent(out IActorComponent? actor))
|
||||||
{
|
{
|
||||||
SendNoUsefulWayToUseAnymorePopup();
|
NotUsefulAnymorePopup();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var target = targetObject as Mechanism;
|
var target = targetObject as MechanismComponent;
|
||||||
|
|
||||||
CloseSurgeryUI(actor.playerSession);
|
CloseSurgeryUI(actor.playerSession);
|
||||||
_callbackCache?.Invoke(target, _bodyManagerComponentCache, this, _performerCache);
|
_callbackCache?.Invoke(target, _bodyCache, this, _performerCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendNoUsefulWayToUsePopup()
|
private void NotUsefulPopup()
|
||||||
{
|
{
|
||||||
_bodyManagerComponentCache?.Owner.PopupMessage(_performerCache,
|
_bodyCache?.Owner.PopupMessage(_performerCache,
|
||||||
Loc.GetString("You see no useful way to use {0:theName}.", Owner));
|
Loc.GetString("You see no useful way to use {0:theName}.", Owner));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendNoUsefulWayToUseAnymorePopup()
|
private void NotUsefulAnymorePopup()
|
||||||
{
|
{
|
||||||
_bodyManagerComponentCache?.Owner.PopupMessage(_performerCache,
|
_bodyCache?.Owner.PopupMessage(_performerCache,
|
||||||
Loc.GetString("You see no useful way to use {0:theName} anymore.", Owner));
|
Loc.GetString("You see no useful way to use {0:theName} anymore.", Owner));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using Content.Server.GameObjects.Components.Body.Digestive;
|
using System.Linq;
|
||||||
|
using Content.Server.GameObjects.Components.Body.Behavior;
|
||||||
using Content.Server.GameObjects.Components.Nutrition;
|
using Content.Server.GameObjects.Components.Nutrition;
|
||||||
using Content.Server.GameObjects.Components.Utensil;
|
using Content.Server.GameObjects.Components.Utensil;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Mechanism;
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.Interfaces;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
using Content.Shared.Utility;
|
using Content.Shared.Utility;
|
||||||
@@ -80,7 +82,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
|||||||
|
|
||||||
var trueTarget = target ?? user;
|
var trueTarget = target ?? user;
|
||||||
|
|
||||||
if (!trueTarget.TryGetComponent(out StomachComponent stomach))
|
if (!trueTarget.TryGetMechanismBehaviors<StomachBehaviorComponent>(out var stomachs))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -93,7 +95,9 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
|||||||
var transferAmount = ReagentUnit.Min(_transferAmount, _contents.CurrentVolume);
|
var transferAmount = ReagentUnit.Min(_transferAmount, _contents.CurrentVolume);
|
||||||
var split = _contents.SplitSolution(transferAmount);
|
var split = _contents.SplitSolution(transferAmount);
|
||||||
|
|
||||||
if (!stomach.CanTransferSolution(split))
|
var firstStomach = stomachs.FirstOrDefault(stomach => stomach.CanTransferSolution(split));
|
||||||
|
|
||||||
|
if (firstStomach == null)
|
||||||
{
|
{
|
||||||
_contents.TryAddSolution(split);
|
_contents.TryAddSolution(split);
|
||||||
trueTarget.PopupMessage(user, Loc.GetString("You can't eat any more!"));
|
trueTarget.PopupMessage(user, Loc.GetString("You can't eat any more!"));
|
||||||
@@ -108,7 +112,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
|||||||
split.RemoveReagent(reagentId, reagent.ReactionEntity(trueTarget, ReactionMethod.Ingestion, quantity));
|
split.RemoveReagent(reagentId, reagent.ReactionEntity(trueTarget, ReactionMethod.Ingestion, quantity));
|
||||||
}
|
}
|
||||||
|
|
||||||
stomach.TryTransferSolution(split);
|
firstStomach.TryTransferSolution(split);
|
||||||
|
|
||||||
if (_useSound != null)
|
if (_useSound != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,29 +1,12 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.GameObjects.Components.Body.Digestive;
|
|
||||||
using Content.Server.GameObjects.Components.Chemistry;
|
|
||||||
using Content.Server.GameObjects.Components.GUI;
|
|
||||||
using Content.Server.GameObjects.Components.Items.Storage;
|
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
using Content.Server.Utility;
|
using Content.Server.Utility;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
using Content.Shared.Interfaces;
|
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
using Content.Shared.Utility;
|
|
||||||
using Robust.Server.GameObjects.EntitySystems;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects.Components;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
using Robust.Shared.Log;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Chemistry
|
namespace Content.Server.GameObjects.Components.Chemistry
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,6 +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]
|
||||||
|
[ComponentReference(typeof(SharedSolutionContainerComponent))]
|
||||||
public class SolutionContainerComponent : SharedSolutionContainerComponent, IExamine
|
public class SolutionContainerComponent : SharedSolutionContainerComponent, IExamine
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
@@ -42,42 +43,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
|||||||
private ChemistrySystem _chemistrySystem;
|
private ChemistrySystem _chemistrySystem;
|
||||||
private SpriteComponent _spriteComponent;
|
private SpriteComponent _spriteComponent;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The total volume of all the of the reagents in the container.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables]
|
|
||||||
public ReagentUnit CurrentVolume => Solution.TotalVolume;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The volume without reagents remaining in the container.
|
/// The volume without reagents remaining in the container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public ReagentUnit EmptyVolume => MaxVolume - CurrentVolume;
|
public ReagentUnit EmptyVolume => MaxVolume - CurrentVolume;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The current blended color of all the reagents in the container.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public Color SubstanceColor { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The current capabilities of this container (is the top open to pour? can I inject it into another object?).
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public SolutionContainerCaps Capabilities { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The contained solution.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables]
|
|
||||||
public Solution Solution { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The maximum volume of the container.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public ReagentUnit MaxVolume { get; set; }
|
|
||||||
|
|
||||||
public IReadOnlyList<Solution.ReagentQuantity> ReagentList => Solution.Contents;
|
public IReadOnlyList<Solution.ReagentQuantity> ReagentList => Solution.Contents;
|
||||||
public bool CanExamineContents => (Capabilities & SolutionContainerCaps.NoExamine) == 0;
|
public bool CanExamineContents => (Capabilities & SolutionContainerCaps.NoExamine) == 0;
|
||||||
public bool CanUseWithChemDispenser => (Capabilities & SolutionContainerCaps.FitsInDispenser) != 0;
|
public bool CanUseWithChemDispenser => (Capabilities & SolutionContainerCaps.FitsInDispenser) != 0;
|
||||||
@@ -124,7 +95,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
|||||||
OnSolutionChanged(false);
|
OnSolutionChanged(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryRemoveReagent(string reagentId, ReagentUnit quantity)
|
public override bool TryRemoveReagent(string reagentId, ReagentUnit quantity)
|
||||||
{
|
{
|
||||||
if (!ContainsReagent(reagentId, out var currentQuantity))
|
if (!ContainsReagent(reagentId, out var currentQuantity))
|
||||||
{
|
{
|
||||||
@@ -393,12 +364,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanAddSolution(Solution solution)
|
public override bool CanAddSolution(Solution solution)
|
||||||
{
|
{
|
||||||
return solution.TotalVolume <= (MaxVolume - Solution.TotalVolume);
|
return solution.TotalVolume <= (MaxVolume - Solution.TotalVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryAddSolution(Solution solution, bool skipReactionCheck = false, bool skipColor = false)
|
public override bool TryAddSolution(Solution solution, bool skipReactionCheck = false, bool skipColor = false)
|
||||||
{
|
{
|
||||||
if (!CanAddSolution(solution))
|
if (!CanAddSolution(solution))
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -27,13 +27,10 @@ namespace Content.Server.GameObjects.Components.Damage
|
|||||||
public override string Name => "Breakable";
|
public override string Name => "Breakable";
|
||||||
|
|
||||||
private ActSystem _actSystem;
|
private ActSystem _actSystem;
|
||||||
private DamageState _currentDamageState;
|
|
||||||
|
|
||||||
public override List<DamageState> SupportedDamageStates =>
|
public override List<DamageState> SupportedDamageStates =>
|
||||||
new List<DamageState> {DamageState.Alive, DamageState.Dead};
|
new List<DamageState> {DamageState.Alive, DamageState.Dead};
|
||||||
|
|
||||||
public override DamageState CurrentDamageState => _currentDamageState;
|
|
||||||
|
|
||||||
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
|
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
switch (eventArgs.Severity)
|
switch (eventArgs.Severity)
|
||||||
@@ -62,7 +59,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
|||||||
public void FixAllDamage()
|
public void FixAllDamage()
|
||||||
{
|
{
|
||||||
Heal();
|
Heal();
|
||||||
_currentDamageState = DamageState.Alive;
|
CurrentState = DamageState.Alive;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DestructionBehavior()
|
protected override void DestructionBehavior()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Damage
|
namespace Content.Server.GameObjects.Components.Damage
|
||||||
{
|
{
|
||||||
@@ -15,18 +16,15 @@ namespace Content.Server.GameObjects.Components.Damage
|
|||||||
[ComponentReference(typeof(IDamageableComponent))]
|
[ComponentReference(typeof(IDamageableComponent))]
|
||||||
public abstract class RuinableComponent : DamageableComponent
|
public abstract class RuinableComponent : DamageableComponent
|
||||||
{
|
{
|
||||||
private DamageState _currentDamageState;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sound played upon destruction.
|
/// Sound played upon destruction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
protected string DestroySound { get; private set; }
|
protected string DestroySound { get; private set; }
|
||||||
|
|
||||||
public override List<DamageState> SupportedDamageStates =>
|
public override List<DamageState> SupportedDamageStates =>
|
||||||
new List<DamageState> {DamageState.Alive, DamageState.Dead};
|
new List<DamageState> {DamageState.Alive, DamageState.Dead};
|
||||||
|
|
||||||
public override DamageState CurrentDamageState => _currentDamageState;
|
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
{
|
{
|
||||||
base.ExposeData(serializer);
|
base.ExposeData(serializer);
|
||||||
@@ -34,8 +32,16 @@ namespace Content.Server.GameObjects.Components.Damage
|
|||||||
serializer.DataReadWriteFunction(
|
serializer.DataReadWriteFunction(
|
||||||
"deadThreshold",
|
"deadThreshold",
|
||||||
100,
|
100,
|
||||||
t => DeadThreshold = t ,
|
t =>
|
||||||
() => DeadThreshold ?? -1);
|
{
|
||||||
|
if (t == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Thresholds[DamageState.Dead] = t.Value;
|
||||||
|
},
|
||||||
|
() => Thresholds.TryGetValue(DamageState.Dead, out var value) ? value : (int?) null);
|
||||||
|
|
||||||
serializer.DataField(this, ruinable => ruinable.DestroySound, "destroySound", string.Empty);
|
serializer.DataField(this, ruinable => ruinable.DestroySound, "destroySound", string.Empty);
|
||||||
}
|
}
|
||||||
@@ -52,12 +58,12 @@ namespace Content.Server.GameObjects.Components.Damage
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Destroys the Owner <see cref="IEntity"/>, setting
|
/// Destroys the Owner <see cref="IEntity"/>, setting
|
||||||
/// <see cref="IDamageableComponent.CurrentDamageState"/> to
|
/// <see cref="IDamageableComponent.CurrentState"/> to
|
||||||
/// <see cref="DamageState.Dead"/>
|
/// <see cref="Shared.GameObjects.Components.Damage.DamageState.Dead"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected void PerformDestruction()
|
protected void PerformDestruction()
|
||||||
{
|
{
|
||||||
_currentDamageState = DamageState.Dead;
|
CurrentState = DamageState.Dead;
|
||||||
|
|
||||||
if (!Owner.Deleted && DestroySound != string.Empty)
|
if (!Owner.Deleted && DestroySound != string.Empty)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
|||||||
}
|
}
|
||||||
|
|
||||||
return entity.HasComponent<ItemComponent>() ||
|
return entity.HasComponent<ItemComponent>() ||
|
||||||
entity.HasComponent<ISharedBodyManagerComponent>();
|
entity.HasComponent<IBody>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryInsert(IEntity entity)
|
public bool TryInsert(IEntity entity)
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!entity.HasComponent<ItemComponent>() &&
|
if (!entity.HasComponent<ItemComponent>() &&
|
||||||
!entity.HasComponent<ISharedBodyManagerComponent>())
|
!entity.HasComponent<IBody>())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,7 +147,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<ISharedBodyManagerComponent>())
|
if (entity.HasComponent<IBody>())
|
||||||
{
|
{
|
||||||
if (!entity.TryGetComponent<IMoverComponent>(out var mover)) return;
|
if (!entity.TryGetComponent<IMoverComponent>(out var mover)) return;
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ using Content.Server.GameObjects.Components.Items.Storage;
|
|||||||
using Content.Server.GameObjects.Components.Mobs;
|
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.Items;
|
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||||
using Content.Shared.GameObjects.Components.Body;
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Part;
|
||||||
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;
|
||||||
@@ -725,24 +725,24 @@ namespace Content.Server.GameObjects.Components.GUI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IBodyPartAdded.BodyPartAdded(BodyPartAddedEventArgs eventArgs)
|
void IBodyPartAdded.BodyPartAdded(BodyPartAddedEventArgs args)
|
||||||
{
|
{
|
||||||
if (eventArgs.Part.PartType != BodyPartType.Hand)
|
if (args.Part.PartType != BodyPartType.Hand)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AddHand(eventArgs.SlotName);
|
AddHand(args.Slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IBodyPartRemoved.BodyPartRemoved(BodyPartRemovedEventArgs eventArgs)
|
void IBodyPartRemoved.BodyPartRemoved(BodyPartRemovedEventArgs args)
|
||||||
{
|
{
|
||||||
if (eventArgs.Part.PartType != BodyPartType.Hand)
|
if (args.Part.PartType != BodyPartType.Hand)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoveHand(eventArgs.SlotName);
|
RemoveHand(args.Slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ namespace Content.Server.GameObjects.Components.Interactable
|
|||||||
public string? WeldSoundCollection { get; set; }
|
public string? WeldSoundCollection { get; set; }
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public float Fuel => _solutionComponent?.Solution.GetReagentQuantity("chem.WeldingFuel").Float() ?? 0f;
|
public float Fuel => _solutionComponent?.Solution?.GetReagentQuantity("chem.WeldingFuel").Float() ?? 0f;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public float FuelCapacity => _solutionComponent?.MaxVolume.Float() ?? 0f;
|
public float FuelCapacity => _solutionComponent?.MaxVolume.Float() ?? 0f;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
|||||||
using Content.Server.GameObjects.Components.Body;
|
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.Shared.GameObjects.Components.Body;
|
||||||
using Content.Shared.GameObjects.Components.Interactable;
|
using Content.Shared.GameObjects.Components.Interactable;
|
||||||
using Content.Shared.GameObjects.Components.Storage;
|
using Content.Shared.GameObjects.Components.Storage;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
@@ -171,7 +172,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
|||||||
|
|
||||||
// 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>() &&
|
if (!entity.HasComponent<StorableComponent>() &&
|
||||||
!entity.HasComponent<BodyManagerComponent>())
|
!entity.HasComponent<IBody>())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!AddToContents(entity))
|
if (!AddToContents(entity))
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.GameObjects.Components.Body;
|
|
||||||
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;
|
||||||
@@ -14,6 +13,7 @@ using Content.Server.Interfaces.GameObjects;
|
|||||||
using Content.Server.Utility;
|
using Content.Server.Utility;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
using Content.Shared.GameObjects.Components.Body;
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Part;
|
||||||
using Content.Shared.GameObjects.Components.Power;
|
using Content.Shared.GameObjects.Components.Power;
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.Interfaces;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
@@ -477,27 +477,37 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
|||||||
public SuicideKind Suicide(IEntity victim, IChatManager chat)
|
public SuicideKind Suicide(IEntity victim, IChatManager chat)
|
||||||
{
|
{
|
||||||
var headCount = 0;
|
var headCount = 0;
|
||||||
if (victim.TryGetComponent<BodyManagerComponent>(out var bodyManagerComponent))
|
|
||||||
|
if (victim.TryGetComponent<IBody>(out var body))
|
||||||
{
|
{
|
||||||
var heads = bodyManagerComponent.GetPartsOfType(BodyPartType.Head);
|
var heads = body.GetPartsOfType(BodyPartType.Head);
|
||||||
foreach (var head in heads)
|
foreach (var head in heads)
|
||||||
{
|
{
|
||||||
var droppedHead = bodyManagerComponent.DropPart(head);
|
if (!body.TryDropPart(head, out var dropped))
|
||||||
|
|
||||||
if (droppedHead == null)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_storage.Insert(droppedHead);
|
var droppedHeads = dropped.Where(p => p.PartType == BodyPartType.Head);
|
||||||
|
|
||||||
|
foreach (var droppedHead in droppedHeads)
|
||||||
|
{
|
||||||
|
_storage.Insert(droppedHead.Owner);
|
||||||
headCount++;
|
headCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var othersMessage = headCount > 1
|
||||||
|
? Loc.GetString("{0:theName} is trying to cook {0:their} heads!", victim)
|
||||||
|
: Loc.GetString("{0:theName} is trying to cook {0:their} head!", victim);
|
||||||
|
|
||||||
var othersMessage = Loc.GetString("{0:theName} is trying to cook {0:their} head!", victim);
|
|
||||||
victim.PopupMessageOtherClients(othersMessage);
|
victim.PopupMessageOtherClients(othersMessage);
|
||||||
|
|
||||||
var selfMessage = Loc.GetString("You cook your head!");
|
var selfMessage = headCount > 1
|
||||||
|
? Loc.GetString("You cook your heads!")
|
||||||
|
: Loc.GetString("You cook your head!");
|
||||||
|
|
||||||
victim.PopupMessage(selfMessage);
|
victim.PopupMessage(selfMessage);
|
||||||
|
|
||||||
_currentCookTimerTime = 10;
|
_currentCookTimerTime = 10;
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ namespace Content.Server.GameObjects.Components.Medical
|
|||||||
|
|
||||||
var dead =
|
var dead =
|
||||||
mind.OwnedEntity.TryGetComponent<IDamageableComponent>(out var damageable) &&
|
mind.OwnedEntity.TryGetComponent<IDamageableComponent>(out var damageable) &&
|
||||||
damageable.CurrentDamageState == DamageState.Dead;
|
damageable.CurrentState == DamageState.Dead;
|
||||||
if (!dead) return;
|
if (!dead) return;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.GameObjects.Components.Stack;
|
using Content.Server.GameObjects.Components.Stack;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Body;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
using Content.Shared.Utility;
|
using Content.Shared.Utility;
|
||||||
@@ -31,7 +31,7 @@ namespace Content.Server.GameObjects.Components.Medical
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!eventArgs.Target.TryGetComponent(out ISharedBodyManagerComponent body))
|
if (!eventArgs.Target.TryGetComponent(out IDamageableComponent damageable))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ namespace Content.Server.GameObjects.Components.Medical
|
|||||||
|
|
||||||
foreach (var (type, amount) in Heal)
|
foreach (var (type, amount) in Heal)
|
||||||
{
|
{
|
||||||
body.ChangeDamage(type, -amount, true);
|
damageable.ChangeDamage(type, -amount, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using Content.Server.GameObjects.EntitySystems;
|
|||||||
using Content.Server.Players;
|
using Content.Server.Players;
|
||||||
using Content.Server.Utility;
|
using Content.Server.Utility;
|
||||||
using Content.Shared.Damage;
|
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.Medical;
|
using Content.Shared.GameObjects.Components.Medical;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
@@ -128,7 +129,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<IDamageableComponent>().CurrentDamageState);
|
: GetStatusFromDamageState(body.GetComponent<IDamageableComponent>().CurrentState);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MedicalScannerStatus.Off;
|
return MedicalScannerStatus.Off;
|
||||||
@@ -249,7 +250,7 @@ namespace Content.Server.GameObjects.Components.Medical
|
|||||||
|
|
||||||
public bool CanDragDropOn(DragDropEventArgs eventArgs)
|
public bool CanDragDropOn(DragDropEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
return eventArgs.Dropped.HasComponent<BodyManagerComponent>();
|
return eventArgs.Dropped.HasComponent<IBody>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DragDropOn(DragDropEventArgs eventArgs)
|
public bool DragDropOn(DragDropEventArgs eventArgs)
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Atmos;
|
using Content.Server.Atmos;
|
||||||
|
using Content.Server.GameObjects.Components.Body.Behavior;
|
||||||
using Content.Server.GameObjects.Components.Body.Circulatory;
|
using Content.Server.GameObjects.Components.Body.Circulatory;
|
||||||
using Content.Server.GameObjects.Components.Body.Respiratory;
|
|
||||||
using Content.Server.GameObjects.Components.Temperature;
|
using Content.Server.GameObjects.Components.Temperature;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Mechanism;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.Interfaces;
|
||||||
@@ -190,9 +191,13 @@ namespace Content.Server.GameObjects.Components.Metabolism
|
|||||||
if (bloodstreamAmount < amountNeeded)
|
if (bloodstreamAmount < amountNeeded)
|
||||||
{
|
{
|
||||||
// Panic inhale
|
// Panic inhale
|
||||||
if (Owner.TryGetComponent(out LungComponent lung))
|
if (Owner.TryGetMechanismBehaviors(out List<LungBehaviorComponent> lungs))
|
||||||
|
{
|
||||||
|
foreach (var lung in lungs)
|
||||||
{
|
{
|
||||||
lung.Gasp();
|
lung.Gasp();
|
||||||
|
}
|
||||||
|
|
||||||
bloodstreamAmount = bloodstream.Air.GetMoles(gas);
|
bloodstreamAmount = bloodstream.Air.GetMoles(gas);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,7 +346,7 @@ namespace Content.Server.GameObjects.Components.Metabolism
|
|||||||
public void Update(float frameTime)
|
public void Update(float frameTime)
|
||||||
{
|
{
|
||||||
if (!Owner.TryGetComponent<IDamageableComponent>(out var damageable) ||
|
if (!Owner.TryGetComponent<IDamageableComponent>(out var damageable) ||
|
||||||
damageable.CurrentDamageState == DamageState.Dead)
|
damageable.CurrentState == DamageState.Dead)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Mobs;
|
||||||
|
using Content.Shared.Preferences;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Mobs
|
namespace Content.Server.GameObjects.Components.Mobs
|
||||||
@@ -6,6 +9,44 @@ namespace Content.Server.GameObjects.Components.Mobs
|
|||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed class HumanoidAppearanceComponent : SharedHumanoidAppearanceComponent
|
public sealed class HumanoidAppearanceComponent : SharedHumanoidAppearanceComponent
|
||||||
{
|
{
|
||||||
|
public override HumanoidCharacterAppearance Appearance
|
||||||
|
{
|
||||||
|
get => base.Appearance;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
base.Appearance = value;
|
||||||
|
|
||||||
|
if (Owner.TryGetBody(out var body))
|
||||||
|
{
|
||||||
|
foreach (var part in body.Parts.Values)
|
||||||
|
{
|
||||||
|
if (!part.Owner.TryGetComponent(out SpriteComponent sprite))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprite.Color = value.SkinColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Startup()
|
||||||
|
{
|
||||||
|
base.Startup();
|
||||||
|
|
||||||
|
if (Appearance != null && Owner.TryGetBody(out var body))
|
||||||
|
{
|
||||||
|
foreach (var part in body.Parts.Values)
|
||||||
|
{
|
||||||
|
if (!part.Owner.TryGetComponent(out SpriteComponent sprite))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprite.Color = Appearance.SkinColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ namespace Content.Server.GameObjects.Components.Mobs
|
|||||||
|
|
||||||
var dead =
|
var dead =
|
||||||
Owner.TryGetComponent<IDamageableComponent>(out var damageable) &&
|
Owner.TryGetComponent<IDamageableComponent>(out var damageable) &&
|
||||||
damageable.CurrentDamageState == DamageState.Dead;
|
damageable.CurrentState == DamageState.Dead;
|
||||||
|
|
||||||
if (!HasMind)
|
if (!HasMind)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Content.Server.GameObjects.Components.Body;
|
using Content.Server.GameObjects.Components.Body;
|
||||||
using Content.Server.GameObjects.Components.Damage;
|
using Content.Server.GameObjects.Components.Damage;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
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.Mobs.State;
|
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||||
@@ -41,26 +42,12 @@ namespace Content.Server.GameObjects.Components.Mobs.State
|
|||||||
{
|
{
|
||||||
case RuinableComponent ruinable:
|
case RuinableComponent ruinable:
|
||||||
{
|
{
|
||||||
if (ruinable.DeadThreshold == null)
|
if (!ruinable.Thresholds.TryGetValue(DamageState.Dead, out var threshold))
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var modifier = (int) (ruinable.TotalDamage / (ruinable.DeadThreshold / 7f));
|
|
||||||
|
|
||||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
|
||||||
"/Textures/Interface/StatusEffects/Human/human" + modifier + ".png");
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case BodyManagerComponent body:
|
|
||||||
{
|
|
||||||
if (body.CriticalThreshold == null)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var modifier = (int) (body.TotalDamage / (body.CriticalThreshold / 7f));
|
var modifier = (int) (ruinable.TotalDamage / (threshold / 7f));
|
||||||
|
|
||||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
||||||
"/Textures/Interface/StatusEffects/Human/human" + modifier + ".png");
|
"/Textures/Interface/StatusEffects/Human/human" + modifier + ".png");
|
||||||
@@ -69,8 +56,15 @@ namespace Content.Server.GameObjects.Components.Mobs.State
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
|
if (!damageable.Thresholds.TryGetValue(DamageState.Critical, out var threshold))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var modifier = (int) (damageable.TotalDamage / (threshold / 7f));
|
||||||
|
|
||||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
||||||
"/Textures/Interface/StatusEffects/Human/human0.png");
|
"/Textures/Interface/StatusEffects/Human/human" + modifier + ".png");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Content.Server.GameObjects.Components.Body;
|
|||||||
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||||
using Content.Server.Utility;
|
using Content.Server.Utility;
|
||||||
using Content.Shared.GameObjects.Components.Body;
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Part;
|
||||||
using Content.Shared.GameObjects.Components.Movement;
|
using Content.Shared.GameObjects.Components.Movement;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Content.Shared.GameObjects.Verbs;
|
using Content.Shared.GameObjects.Verbs;
|
||||||
@@ -92,16 +93,15 @@ namespace Content.Server.GameObjects.Components.Movement
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.HasComponent<ClimbingComponent>())
|
if (!user.HasComponent<ClimbingComponent>() ||
|
||||||
|
!user.TryGetComponent(out IBody body))
|
||||||
{
|
{
|
||||||
reason = Loc.GetString("You are incapable of climbing!");
|
reason = Loc.GetString("You are incapable of climbing!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var bodyManager = user.GetComponent<BodyManagerComponent>();
|
if (body.GetPartsOfType(BodyPartType.Leg).Count == 0 ||
|
||||||
|
body.GetPartsOfType(BodyPartType.Foot).Count == 0)
|
||||||
if (bodyManager.GetPartsOfType(BodyPartType.Leg).Count == 0 ||
|
|
||||||
bodyManager.GetPartsOfType(BodyPartType.Foot).Count == 0)
|
|
||||||
{
|
{
|
||||||
reason = Loc.GetString("You are unable to climb!");
|
reason = Loc.GetString("You are unable to climb!");
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
using Content.Server.GameObjects.Components.Body.Digestive;
|
using System.Linq;
|
||||||
|
using Content.Server.GameObjects.Components.Body.Behavior;
|
||||||
using Content.Server.GameObjects.Components.Chemistry;
|
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;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Mechanism;
|
||||||
using Content.Shared.GameObjects.Components.Nutrition;
|
using Content.Shared.GameObjects.Components.Nutrition;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.Interfaces;
|
||||||
@@ -149,22 +151,29 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!target.TryGetComponent(out StomachComponent stomachComponent))
|
if (!target.TryGetMechanismBehaviors<StomachBehaviorComponent>(out var stomachs))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var transferAmount = ReagentUnit.Min(TransferAmount, _contents.CurrentVolume);
|
var transferAmount = ReagentUnit.Min(TransferAmount, _contents.CurrentVolume);
|
||||||
var split = _contents.SplitSolution(transferAmount);
|
var split = _contents.SplitSolution(transferAmount);
|
||||||
|
var firstStomach = stomachs.FirstOrDefault(stomach => stomach.CanTransferSolution(split));
|
||||||
|
|
||||||
if (stomachComponent.CanTransferSolution(split))
|
|
||||||
{
|
// All stomach are full or can't handle whatever solution we have.
|
||||||
if (_useSound == null)
|
if (firstStomach == null)
|
||||||
{
|
{
|
||||||
|
_contents.TryAddSolution(split);
|
||||||
|
target.PopupMessage(Loc.GetString("You've had enough {0:theName}!", Owner));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_useSound != null)
|
||||||
|
{
|
||||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(_useSound, target, AudioParams.Default.WithVolume(-2f));
|
EntitySystem.Get<AudioSystem>().PlayFromEntity(_useSound, target, AudioParams.Default.WithVolume(-2f));
|
||||||
|
}
|
||||||
|
|
||||||
target.PopupMessage(Loc.GetString("Slurp"));
|
target.PopupMessage(Loc.GetString("Slurp"));
|
||||||
UpdateAppearance();
|
UpdateAppearance();
|
||||||
|
|
||||||
@@ -176,17 +185,11 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
split.RemoveReagent(reagentId, reagent.ReactionEntity(target, ReactionMethod.Ingestion, quantity));
|
split.RemoveReagent(reagentId, reagent.ReactionEntity(target, ReactionMethod.Ingestion, quantity));
|
||||||
}
|
}
|
||||||
|
|
||||||
stomachComponent.TryTransferSolution(split);
|
firstStomach.TryTransferSolution(split);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stomach was full or can't handle whatever solution we have.
|
|
||||||
_contents.TryAddSolution(split);
|
|
||||||
target.PopupMessage(Loc.GetString("You've had enough {0:theName}!", Owner));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ILand.Land(LandEventArgs eventArgs)
|
void ILand.Land(LandEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (_pressurized &&
|
if (_pressurized &&
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.GameObjects.Components.Body.Digestive;
|
using System.Linq;
|
||||||
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.Utensil;
|
using Content.Server.GameObjects.Components.Utensil;
|
||||||
using Content.Shared.Chemistry;
|
using Content.Shared.Chemistry;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Behavior;
|
||||||
|
using Content.Shared.GameObjects.Components.Body.Mechanism;
|
||||||
using Content.Shared.GameObjects.Components.Utensil;
|
using Content.Shared.GameObjects.Components.Utensil;
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.Interfaces;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
@@ -129,7 +131,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
|
|
||||||
var trueTarget = target ?? user;
|
var trueTarget = target ?? user;
|
||||||
|
|
||||||
if (!trueTarget.TryGetComponent(out StomachComponent? stomach))
|
if (!trueTarget.TryGetMechanismBehaviors<SharedStomachBehaviorComponent>(out var stomachs))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -171,7 +173,9 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
|
|
||||||
var transferAmount = ReagentUnit.Min(_transferAmount, solution.CurrentVolume);
|
var transferAmount = ReagentUnit.Min(_transferAmount, solution.CurrentVolume);
|
||||||
var split = solution.SplitSolution(transferAmount);
|
var split = solution.SplitSolution(transferAmount);
|
||||||
if (!stomach.CanTransferSolution(split))
|
var firstStomach = stomachs.FirstOrDefault(stomach => stomach.CanTransferSolution(split));
|
||||||
|
|
||||||
|
if (firstStomach == null)
|
||||||
{
|
{
|
||||||
trueTarget.PopupMessage(user, Loc.GetString("You can't eat any more!"));
|
trueTarget.PopupMessage(user, Loc.GetString("You can't eat any more!"));
|
||||||
return false;
|
return false;
|
||||||
@@ -185,7 +189,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
split.RemoveReagent(reagentId, reagent.ReactionEntity(target, ReactionMethod.Ingestion, quantity));
|
split.RemoveReagent(reagentId, reagent.ReactionEntity(target, ReactionMethod.Ingestion, quantity));
|
||||||
}
|
}
|
||||||
|
|
||||||
stomach.TryTransferSolution(split);
|
firstStomach.TryTransferSolution(split);
|
||||||
|
|
||||||
_entitySystem.GetEntitySystem<AudioSystem>()
|
_entitySystem.GetEntitySystem<AudioSystem>()
|
||||||
.PlayFromEntity(_useSound, trueTarget, AudioParams.Default.WithVolume(-1f));
|
.PlayFromEntity(_useSound, trueTarget, AudioParams.Default.WithVolume(-1f));
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
{
|
{
|
||||||
if (Owner.TryGetComponent(out IDamageableComponent damageable))
|
if (Owner.TryGetComponent(out IDamageableComponent damageable))
|
||||||
{
|
{
|
||||||
if (damageable.CurrentDamageState != DamageState.Dead)
|
if (damageable.CurrentState != DamageState.Dead)
|
||||||
{
|
{
|
||||||
damageable.ChangeDamage(DamageType.Blunt, 2, true, null);
|
damageable.ChangeDamage(DamageType.Blunt, 2, true, null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
|||||||
{
|
{
|
||||||
if (Owner.TryGetComponent(out IDamageableComponent damageable))
|
if (Owner.TryGetComponent(out IDamageableComponent damageable))
|
||||||
{
|
{
|
||||||
if (damageable.CurrentDamageState != DamageState.Dead)
|
if (damageable.CurrentState != DamageState.Dead)
|
||||||
{
|
{
|
||||||
damageable.ChangeDamage(DamageType.Blunt, 2, true, null);
|
damageable.ChangeDamage(DamageType.Blunt, 2, true, null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ namespace Content.Server.GameObjects.Components.Recycling
|
|||||||
|
|
||||||
private bool CanGib(IEntity entity)
|
private bool CanGib(IEntity entity)
|
||||||
{
|
{
|
||||||
return entity.HasComponent<ISharedBodyManagerComponent>() && !_safe && Powered;
|
return entity.HasComponent<IBody>() && !_safe && Powered;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanRecycle(IEntity entity, [MaybeNullWhen(false)] out ConstructionPrototype prototype)
|
private bool CanRecycle(IEntity entity, [MaybeNullWhen(false)] out ConstructionPrototype prototype)
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ namespace Content.Server.GameObjects.Components.Suspicion
|
|||||||
public bool IsDead()
|
public bool IsDead()
|
||||||
{
|
{
|
||||||
return Owner.TryGetComponent(out IDamageableComponent? damageable) &&
|
return Owner.TryGetComponent(out IDamageableComponent? damageable) &&
|
||||||
damageable.CurrentDamageState == DamageState.Dead;
|
damageable.CurrentState == DamageState.Dead;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsInnocent()
|
public bool IsInnocent()
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
using Content.Server.GameObjects.Components.Body;
|
|
||||||
using Content.Server.GameObjects.Components.Metabolism;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Shared.GameObjects.Systems;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.EntitySystems
|
|
||||||
{
|
|
||||||
[UsedImplicitly]
|
|
||||||
public class BodySystem : EntitySystem
|
|
||||||
{
|
|
||||||
public override void Update(float frameTime)
|
|
||||||
{
|
|
||||||
foreach (var body in ComponentManager.EntityQuery<BodyManagerComponent>())
|
|
||||||
{
|
|
||||||
body.PreMetabolism(frameTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var metabolism in ComponentManager.EntityQuery<MetabolismComponent>())
|
|
||||||
{
|
|
||||||
metabolism.Update(frameTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var body in ComponentManager.EntityQuery<BodyManagerComponent>())
|
|
||||||
{
|
|
||||||
body.PostMetabolism(frameTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
28
Content.Server/GameObjects/EntitySystems/HeartSystem.cs
Normal file
28
Content.Server/GameObjects/EntitySystems/HeartSystem.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Body.Behavior;
|
||||||
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.EntitySystems
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class HeartSystem : EntitySystem
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
UpdatesBefore.Add(typeof(SharedMetabolismSystem));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
foreach (var heart in ComponentManager.EntityQuery<SharedHeartBehaviorComponent>())
|
||||||
|
{
|
||||||
|
heart.Update(frameTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Content.Server/GameObjects/EntitySystems/LungSystem.cs
Normal file
28
Content.Server/GameObjects/EntitySystems/LungSystem.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Body.Behavior;
|
||||||
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.EntitySystems
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class LungSystem : EntitySystem
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
UpdatesBefore.Add(typeof(SharedMetabolismSystem));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
foreach (var lung in ComponentManager.EntityQuery<SharedLungBehaviorComponent>())
|
||||||
|
{
|
||||||
|
lung.Update(frameTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Server.GameObjects.Components.StationEvents;
|
using Content.Server.GameObjects.Components.StationEvents;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.GameObjects.Systems;
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ namespace Content.Server.GameTicking.GameRules
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (damageable.CurrentDamageState != DamageState.Alive)
|
if (damageable.CurrentState != DamageState.Alive)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ namespace Content.Server.GameTicking.GameRules
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (damageable.CurrentDamageState != DamageState.Alive)
|
if (damageable.CurrentState != DamageState.Alive)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Content.Server.Body;
|
|
||||||
|
|
||||||
namespace Content.Server.Interfaces.GameObjects.Components.Interaction
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This interface gives components behavior when a body part
|
|
||||||
/// is added to their owning entity.
|
|
||||||
/// </summary>
|
|
||||||
public interface IBodyPartAdded
|
|
||||||
{
|
|
||||||
void BodyPartAdded(BodyPartAddedEventArgs eventArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BodyPartAddedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public BodyPartAddedEventArgs(IBodyPart part, string slotName)
|
|
||||||
{
|
|
||||||
Part = part;
|
|
||||||
SlotName = slotName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IBodyPart Part { get; }
|
|
||||||
|
|
||||||
public string SlotName { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This interface gives components behavior when a body part
|
|
||||||
/// is removed from their owning entity.
|
|
||||||
/// </summary>
|
|
||||||
public interface IBodyPartRemoved
|
|
||||||
{
|
|
||||||
void BodyPartRemoved(BodyPartRemovedEventArgs eventArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BodyPartRemovedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public BodyPartRemovedEventArgs(IBodyPart part, string slotName)
|
|
||||||
{
|
|
||||||
Part = part;
|
|
||||||
SlotName = slotName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IBodyPart Part { get; }
|
|
||||||
|
|
||||||
public string SlotName { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -50,7 +50,7 @@ namespace Content.Server.Observer
|
|||||||
|
|
||||||
if (canReturn && player.AttachedEntity.TryGetComponent(out IDamageableComponent damageable))
|
if (canReturn && player.AttachedEntity.TryGetComponent(out IDamageableComponent damageable))
|
||||||
{
|
{
|
||||||
switch (damageable.CurrentDamageState)
|
switch (damageable.CurrentState)
|
||||||
{
|
{
|
||||||
case DamageState.Dead:
|
case DamageState.Dead:
|
||||||
canReturn = true;
|
canReturn = true;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
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.Body.Network;
|
|
||||||
using Content.Server.Cargo;
|
using Content.Server.Cargo;
|
||||||
using Content.Server.Chat;
|
using Content.Server.Chat;
|
||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
@@ -43,7 +42,6 @@ namespace Content.Server
|
|||||||
IoCManager.Register<IPowerNetManager, PowerNetManager>();
|
IoCManager.Register<IPowerNetManager, PowerNetManager>();
|
||||||
IoCManager.Register<BlackboardManager, BlackboardManager>();
|
IoCManager.Register<BlackboardManager, BlackboardManager>();
|
||||||
IoCManager.Register<ConsiderationsManager, ConsiderationsManager>();
|
IoCManager.Register<ConsiderationsManager, ConsiderationsManager>();
|
||||||
IoCManager.Register<IBodyNetworkFactory, BodyNetworkFactory>();
|
|
||||||
IoCManager.Register<IAccentManager, AccentManager>();
|
IoCManager.Register<IAccentManager, AccentManager>();
|
||||||
IoCManager.Register<IConnectionManager, ConnectionManager>();
|
IoCManager.Register<IConnectionManager, ConnectionManager>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Content.Server.StationEvents
|
|||||||
if (inventory.TryGetSlotItem(EquipmentSlotDefines.Slots.BELT, out ItemComponent? item)
|
if (inventory.TryGetSlotItem(EquipmentSlotDefines.Slots.BELT, out ItemComponent? item)
|
||||||
&& item?.Owner.Prototype?.ID == "UtilityBeltClothingFilledEvent") return;
|
&& item?.Owner.Prototype?.ID == "UtilityBeltClothingFilledEvent") return;
|
||||||
if (player.AttachedEntity.TryGetComponent<IDamageableComponent>(out var damageable)
|
if (player.AttachedEntity.TryGetComponent<IDamageableComponent>(out var damageable)
|
||||||
&& damageable.CurrentDamageState == DamageState.Dead) return;
|
&& damageable.CurrentState == DamageState.Dead) return;
|
||||||
|
|
||||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||||
var playerPos = player.AttachedEntity.Transform.Coordinates;
|
var playerPos = player.AttachedEntity.Transform.Coordinates;
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Shared.GameObjects.Components.Body;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
using YamlDotNet.RepresentationModel;
|
|
||||||
|
|
||||||
namespace Content.Shared.Body.Mechanism
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Prototype for the Mechanism class.
|
|
||||||
/// </summary>
|
|
||||||
[Prototype("mechanism")]
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public class MechanismPrototype : IPrototype, IIndexedPrototype
|
|
||||||
{
|
|
||||||
private List<string> _behaviorClasses;
|
|
||||||
private BodyPartCompatibility _compatibility;
|
|
||||||
private string _description;
|
|
||||||
private int _destroyThreshold;
|
|
||||||
private int _durability;
|
|
||||||
private string _examineMessage;
|
|
||||||
private string _id;
|
|
||||||
private string _name;
|
|
||||||
private int _resistance;
|
|
||||||
private string _rsiPath;
|
|
||||||
private string _rsiState;
|
|
||||||
private int _size;
|
|
||||||
|
|
||||||
[ViewVariables] public string Name => _name;
|
|
||||||
|
|
||||||
[ViewVariables] public string Description => _description;
|
|
||||||
|
|
||||||
[ViewVariables] public string ExamineMessage => _examineMessage;
|
|
||||||
|
|
||||||
[ViewVariables] public string RSIPath => _rsiPath;
|
|
||||||
|
|
||||||
[ViewVariables] public string RSIState => _rsiState;
|
|
||||||
|
|
||||||
[ViewVariables] public int Durability => _durability;
|
|
||||||
|
|
||||||
[ViewVariables] public int DestroyThreshold => _destroyThreshold;
|
|
||||||
|
|
||||||
[ViewVariables] public int Resistance => _resistance;
|
|
||||||
|
|
||||||
[ViewVariables] public int Size => _size;
|
|
||||||
|
|
||||||
[ViewVariables] public BodyPartCompatibility Compatibility => _compatibility;
|
|
||||||
|
|
||||||
[ViewVariables] public List<string> BehaviorClasses => _behaviorClasses;
|
|
||||||
|
|
||||||
[ViewVariables] public string ID => _id;
|
|
||||||
|
|
||||||
public virtual void LoadFrom(YamlMappingNode mapping)
|
|
||||||
{
|
|
||||||
var serializer = YamlObjectSerializer.NewReader(mapping);
|
|
||||||
|
|
||||||
serializer.DataField(ref _id, "id", string.Empty);
|
|
||||||
serializer.DataField(ref _name, "name", string.Empty);
|
|
||||||
serializer.DataField(ref _description, "description", string.Empty);
|
|
||||||
serializer.DataField(ref _examineMessage, "examineMessage", string.Empty);
|
|
||||||
serializer.DataField(ref _rsiPath, "rsiPath", string.Empty);
|
|
||||||
serializer.DataField(ref _rsiState, "rsiState", string.Empty);
|
|
||||||
serializer.DataField(ref _durability, "durability", 0);
|
|
||||||
serializer.DataField(ref _destroyThreshold, "destroyThreshold", 0);
|
|
||||||
serializer.DataField(ref _resistance, "resistance", 0);
|
|
||||||
serializer.DataField(ref _size, "size", 2);
|
|
||||||
serializer.DataField(ref _compatibility, "compatibility", BodyPartCompatibility.Universal);
|
|
||||||
serializer.DataField(ref _behaviorClasses, "behaviors", new List<string>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Content.Shared.GameObjects.Components.Body;
|
|
||||||
using Robust.Shared.Interfaces.Serialization;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
using YamlDotNet.RepresentationModel;
|
|
||||||
|
|
||||||
namespace Content.Shared.Body.Part
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Prototype for the BodyPart class.
|
|
||||||
/// </summary>
|
|
||||||
[Prototype("bodyPart")]
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public class BodyPartPrototype : IPrototype, IIndexedPrototype
|
|
||||||
{
|
|
||||||
private BodyPartCompatibility _compatibility;
|
|
||||||
private string _damageContainerPresetId;
|
|
||||||
private int _destroyThreshold;
|
|
||||||
private int _durability;
|
|
||||||
private string _id;
|
|
||||||
private List<string> _mechanisms;
|
|
||||||
private string _name;
|
|
||||||
private BodyPartType _partType;
|
|
||||||
private string _plural;
|
|
||||||
private List<IExposeData> _properties;
|
|
||||||
private float _resistance;
|
|
||||||
private string _resistanceSetId;
|
|
||||||
private string _rsiPath;
|
|
||||||
private string _rsiState;
|
|
||||||
private int _size;
|
|
||||||
private string _surgeryDataName;
|
|
||||||
private bool _isVital;
|
|
||||||
|
|
||||||
|
|
||||||
[ViewVariables] public string Name => _name;
|
|
||||||
|
|
||||||
[ViewVariables] public string Plural => _plural;
|
|
||||||
|
|
||||||
[ViewVariables] public string RSIPath => _rsiPath;
|
|
||||||
|
|
||||||
[ViewVariables] public string RSIState => _rsiState;
|
|
||||||
|
|
||||||
[ViewVariables] public BodyPartType PartType => _partType;
|
|
||||||
|
|
||||||
[ViewVariables] public int Durability => _durability;
|
|
||||||
|
|
||||||
[ViewVariables] public int DestroyThreshold => _destroyThreshold;
|
|
||||||
|
|
||||||
[ViewVariables] public float Resistance => _resistance;
|
|
||||||
|
|
||||||
[ViewVariables] public int Size => _size;
|
|
||||||
|
|
||||||
[ViewVariables] public BodyPartCompatibility Compatibility => _compatibility;
|
|
||||||
|
|
||||||
[ViewVariables] public string DamageContainerPresetId => _damageContainerPresetId;
|
|
||||||
|
|
||||||
[ViewVariables] public string ResistanceSetId => _resistanceSetId;
|
|
||||||
|
|
||||||
[ViewVariables] public string SurgeryDataName => _surgeryDataName;
|
|
||||||
|
|
||||||
[ViewVariables] public List<IExposeData> Properties => _properties;
|
|
||||||
|
|
||||||
[ViewVariables] public List<string> Mechanisms => _mechanisms;
|
|
||||||
|
|
||||||
[ViewVariables] public string ID => _id;
|
|
||||||
|
|
||||||
[ViewVariables] public bool IsVital => _isVital;
|
|
||||||
|
|
||||||
public virtual void LoadFrom(YamlMappingNode mapping)
|
|
||||||
{
|
|
||||||
var serializer = YamlObjectSerializer.NewReader(mapping);
|
|
||||||
|
|
||||||
serializer.DataField(ref _name, "name", string.Empty);
|
|
||||||
serializer.DataField(ref _id, "id", string.Empty);
|
|
||||||
serializer.DataField(ref _plural, "plural", string.Empty);
|
|
||||||
serializer.DataField(ref _rsiPath, "rsiPath", string.Empty);
|
|
||||||
serializer.DataField(ref _rsiState, "rsiState", string.Empty);
|
|
||||||
serializer.DataField(ref _partType, "partType", BodyPartType.Other);
|
|
||||||
serializer.DataField(ref _surgeryDataName, "surgeryDataType", "BiologicalSurgeryData");
|
|
||||||
serializer.DataField(ref _durability, "durability", 50);
|
|
||||||
serializer.DataField(ref _destroyThreshold, "destroyThreshold", -50);
|
|
||||||
serializer.DataField(ref _resistance, "resistance", 0f);
|
|
||||||
serializer.DataField(ref _size, "size", 0);
|
|
||||||
serializer.DataField(ref _compatibility, "compatibility", BodyPartCompatibility.Universal);
|
|
||||||
serializer.DataField(ref _damageContainerPresetId, "damageContainer", string.Empty);
|
|
||||||
serializer.DataField(ref _resistanceSetId, "resistances", string.Empty);
|
|
||||||
serializer.DataField(ref _properties, "properties", new List<IExposeData>());
|
|
||||||
serializer.DataField(ref _mechanisms, "mechanisms", new List<string>());
|
|
||||||
serializer.DataField(ref _isVital, "isVital", false);
|
|
||||||
|
|
||||||
foreach (var property in _properties)
|
|
||||||
{
|
|
||||||
if (_properties.Count(x => x.GetType() == property.GetType()) > 1)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
$"Multiple {nameof(BodyPartPrototype)} of the same type were defined in prototype {ID}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using Robust.Shared.Interfaces.Serialization;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
|
|
||||||
namespace Content.Shared.Body.Part.Properties
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Property attachable to a <see cref="BodyPart"/>.
|
|
||||||
/// For example, this is used to define the speed capabilities of a
|
|
||||||
/// leg. The movement system will look for a LegProperty on all BodyParts.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class BodyPartProperty : IExposeData
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this property is currently active.
|
|
||||||
/// </summary>
|
|
||||||
public bool Active;
|
|
||||||
|
|
||||||
public virtual void ExposeData(ObjectSerializer serializer)
|
|
||||||
{
|
|
||||||
serializer.DataField(ref Active, "active", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
namespace Content.Shared.Body.Part.Properties.Movement
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the leg-based locomotion ability of a BodyPart. Required for humanoid-like movement. Must be connected to a
|
|
||||||
/// <see cref="BodyPart" /> with
|
|
||||||
/// <see cref="LegProperty" /> and <see cref="ExtensionProperty" /> to work.
|
|
||||||
/// </summary>
|
|
||||||
public class FootProperty : BodyPartProperty
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using Robust.Shared.Serialization;
|
|
||||||
|
|
||||||
namespace Content.Shared.Body.Part.Properties.Movement
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the speed of humanoid-like movement. Must be connected to a
|
|
||||||
/// <see cref="BodyPart" /> with <see cref="FootProperty" /> and have
|
|
||||||
/// <see cref="ExtensionProperty" /> on the same organ and down to the foot to work.
|
|
||||||
/// </summary>
|
|
||||||
public class LegProperty : BodyPartProperty
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Speed (in tiles per second).
|
|
||||||
/// </summary>
|
|
||||||
public float Speed;
|
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
|
||||||
{
|
|
||||||
base.ExposeData(serializer);
|
|
||||||
serializer.DataField(ref Speed, "speed", 1f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
using Robust.Shared.Serialization;
|
|
||||||
|
|
||||||
namespace Content.Shared.Body.Part.Properties.Other
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the extension ability of a BodyPart. Used to determine things like reach distance and running speed.
|
|
||||||
/// </summary>
|
|
||||||
public class ExtensionProperty : BodyPartProperty
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Current reach distance (in tiles).
|
|
||||||
/// </summary>
|
|
||||||
public float ReachDistance;
|
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
|
||||||
{
|
|
||||||
base.ExposeData(serializer);
|
|
||||||
serializer.DataField(ref ReachDistance, "reachDistance", 2f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace Content.Shared.Body.Part.Properties.Other
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the item grasping ability of a BodyPart. Distance is determined by the
|
|
||||||
/// <see cref="ExtensionProperty">ExtensionProperties</see> of the current BodyPart or attached BodyParts.
|
|
||||||
/// </summary>
|
|
||||||
public class GraspProperty : BodyPartProperty
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Shared.GameObjects.Components.Body;
|
|
||||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
|
|
||||||
namespace Content.Shared.Body.Scanner
|
|
||||||
{
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public enum BodyScannerUiKey
|
|
||||||
{
|
|
||||||
Key
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public class BodyScannerInterfaceState : BoundUserInterfaceState
|
|
||||||
{
|
|
||||||
public readonly Dictionary<string, BodyScannerBodyPartData> Parts;
|
|
||||||
public readonly BodyScannerTemplateData Template;
|
|
||||||
|
|
||||||
public BodyScannerInterfaceState(Dictionary<string, BodyScannerBodyPartData> parts,
|
|
||||||
BodyScannerTemplateData template)
|
|
||||||
{
|
|
||||||
Template = template;
|
|
||||||
Parts = parts;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public class BodyScannerBodyPartData
|
|
||||||
{
|
|
||||||
public readonly int CurrentDurability;
|
|
||||||
public readonly int MaxDurability;
|
|
||||||
public readonly List<BodyScannerMechanismData> Mechanisms;
|
|
||||||
public readonly string Name;
|
|
||||||
public readonly string RSIPath;
|
|
||||||
public readonly string RSIState;
|
|
||||||
|
|
||||||
public BodyScannerBodyPartData(string name, string rsiPath, string rsiState, int maxDurability,
|
|
||||||
int currentDurability, List<BodyScannerMechanismData> mechanisms)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
RSIPath = rsiPath;
|
|
||||||
RSIState = rsiState;
|
|
||||||
MaxDurability = maxDurability;
|
|
||||||
CurrentDurability = currentDurability;
|
|
||||||
Mechanisms = mechanisms;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public class BodyScannerMechanismData
|
|
||||||
{
|
|
||||||
public readonly int CurrentDurability;
|
|
||||||
public readonly string Description;
|
|
||||||
public readonly int MaxDurability;
|
|
||||||
public readonly string Name;
|
|
||||||
public readonly string RSIPath;
|
|
||||||
public readonly string RSIState;
|
|
||||||
|
|
||||||
public BodyScannerMechanismData(string name, string description, string rsiPath, string rsiState,
|
|
||||||
int maxDurability, int currentDurability)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
Description = description;
|
|
||||||
RSIPath = rsiPath;
|
|
||||||
RSIState = rsiState;
|
|
||||||
MaxDurability = maxDurability;
|
|
||||||
CurrentDurability = currentDurability;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public class BodyScannerTemplateData
|
|
||||||
{
|
|
||||||
public readonly string Name;
|
|
||||||
public readonly Dictionary<string, BodyPartType> Slots;
|
|
||||||
|
|
||||||
public BodyScannerTemplateData(string name, Dictionary<string, BodyPartType> slots)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
Slots = slots;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user