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:
DrSmugleaf
2020-10-10 15:25:13 +02:00
committed by GitHub
parent 73c730d06c
commit dd385a0511
165 changed files with 4232 additions and 4650 deletions

View File

@@ -63,9 +63,6 @@ namespace Content.Client
factory.Register<SharedResearchConsoleComponent>();
factory.Register<SharedLatheComponent>();
factory.Register<SharedSpawnPointComponent>();
factory.Register<SharedSolutionContainerComponent>();
factory.Register<SharedVendingMachineComponent>();
factory.Register<SharedWiresComponent>();
factory.Register<SharedCargoConsoleComponent>();

View 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;
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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) { }
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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
{
}
}

View File

@@ -1,8 +1,9 @@
using System.Collections.Generic;
using Content.Shared.Body.Scanner;
using System;
using Content.Shared.GameObjects.Components.Body.Scanner;
using JetBrains.Annotations;
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.ViewVariables;
namespace Content.Client.GameObjects.Components.Body.Scanner
@@ -14,10 +15,7 @@ namespace Content.Client.GameObjects.Components.Body.Scanner
private BodyScannerDisplay _display;
[ViewVariables]
private BodyScannerTemplateData _template;
[ViewVariables]
private Dictionary<string, BodyScannerBodyPartData> _parts;
private IEntity _entity;
public BodyScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
@@ -33,15 +31,17 @@ namespace Content.Client.GameObjects.Components.Body.Scanner
{
base.UpdateState(state);
if (!(state is BodyScannerInterfaceState scannerState))
if (!(state is BodyScannerUIState scannerState))
{
return;
}
_template = scannerState.Template;
_parts = scannerState.Parts;
if (!Owner.Owner.EntityManager.TryGetEntity(scannerState.Uid, out _entity))
{
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)
@@ -51,8 +51,6 @@ namespace Content.Client.GameObjects.Components.Body.Scanner
if (disposing)
{
_display?.Dispose();
_template = null;
_parts.Clear();
}
}
}

View File

@@ -1,7 +1,12 @@
using System.Collections.Generic;
using Content.Shared.Body.Scanner;
#nullable enable
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.CustomControls;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
@@ -11,13 +16,10 @@ namespace Content.Client.GameObjects.Components.Body.Scanner
{
public sealed class BodyScannerDisplay : SS14Window
{
private BodyScannerTemplateData _template;
private IEntity? _currentEntity;
private IBodyPart? _currentBodyPart;
private Dictionary<string, BodyScannerBodyPartData> _parts;
private List<string> _slots;
private BodyScannerBodyPartData _currentBodyPart;
private IBody? CurrentBody => _currentEntity?.GetBody();
public BodyScannerDisplay(BodyScannerBoundUserInterface owner)
{
@@ -102,51 +104,70 @@ namespace Content.Client.GameObjects.Components.Body.Scanner
private RichTextLabel MechanismInfoLabel { get; }
public void UpdateDisplay(BodyScannerTemplateData template, Dictionary<string, BodyScannerBodyPartData> parts)
public void UpdateDisplay(IEntity entity)
{
_template = template;
_parts = parts;
_slots = new List<string>();
_currentEntity = entity;
BodyPartList.Clear();
foreach (var slotName in _parts.Keys)
{
// We have to do this since ItemLists only return the index of what item is
// selected and dictionaries don't allow you to explicitly grab things by index.
// So we put the contents of the dictionary into a list so
// that we can grab the list by index. I don't know either.
_slots.Add(slotName);
var body = CurrentBody;
if (body == null)
{
return;
}
foreach (var slotName in body.Parts.Keys)
{
BodyPartList.AddItem(Loc.GetString(slotName));
}
}
public void BodyPartOnItemSelected(ItemListSelectedEventArgs args)
{
if (_parts.TryGetValue(_slots[args.ItemIndex], out _currentBodyPart)) {
UpdateBodyPartBox(_currentBodyPart, _slots[args.ItemIndex]);
var body = CurrentBody;
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)}";
BodyPartHealth.Text = $"{part.CurrentDurability}/{part.MaxDurability}";
BodyPartLabel.Text = $"{Loc.GetString(slotName)}: {Loc.GetString(part.Owner.Name)}";
// 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();
foreach (var mechanism in part.Mechanisms) {
foreach (var mechanism in part.Mechanisms)
{
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)
{
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)
{
MechanismInfoLabel.SetMessage("");

View File

@@ -1,27 +1,27 @@
#nullable enable
using Content.Shared.Body.Surgery;
using Content.Shared.GameObjects.Components.Body.Surgery;
using JetBrains.Annotations;
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Shared.GameObjects.Components.UserInterface;
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>
/// Generic client-side UI list popup that allows users to choose from an option
/// of limbs or organs to operate on.
/// </summary>
[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()
{
_window = new GenericSurgeryWindow();
_window = new SurgeryWindow();
_window.OpenCentered();
_window.OnClose += Close;

View File

@@ -8,7 +8,7 @@ using Robust.Shared.Maths;
namespace Content.Client.GameObjects.Components.Body.Surgery
{
public class GenericSurgeryWindow : SS14Window
public class SurgeryWindow : SS14Window
{
public delegate void OptionSelectedCallback(int selectedOptionData);
@@ -17,7 +17,7 @@ namespace Content.Client.GameObjects.Components.Body.Surgery
protected override Vector2? CustomSize => (300, 400);
public GenericSurgeryWindow()
public SurgeryWindow()
{
Title = Loc.GetString("Select surgery target...");
RectClipContent = true;

View File

@@ -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;
}
}
}

View File

@@ -3,11 +3,9 @@ using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.MedicalScanner
{
[RegisterComponent]
[ComponentReference(typeof(SharedMedicalScannerComponent))]
public class MedicalScannerComponent : SharedMedicalScannerComponent
{
}
}

View File

@@ -1,4 +1,6 @@
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.Preferences;
using Content.Shared.Preferences.Appearance;
@@ -8,7 +10,7 @@ using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.Mobs
{
[RegisterComponent]
public sealed class HumanoidAppearanceComponent : SharedHumanoidAppearanceComponent
public sealed class HumanoidAppearanceComponent : SharedHumanoidAppearanceComponent, IBodyPartAdded, IBodyPartRemoved
{
public override HumanoidCharacterAppearance Appearance
{
@@ -39,8 +41,24 @@ namespace Content.Client.GameObjects.Components.Mobs
private void UpdateLooks()
{
if (Appearance is null) return;
var sprite = Owner.GetComponent<SpriteComponent>();
if (Appearance is null ||
!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.FacialHair, Appearance.FacialHairColor);
@@ -71,5 +89,51 @@ namespace Content.Client.GameObjects.Components.Mobs
sprite.LayerSetState(HumanoidVisualLayers.FacialHair,
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);
}
}
}

View File

@@ -182,6 +182,7 @@
"BreakableConstruction",
"GasCanister",
"GasCanisterPort",
"Lung",
};
}
}