Merge branch 'master' into 2020-08-19-firelocks

# Conflicts:
#	Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs
#	Content.Shared/Maps/TurfHelpers.cs
#	SpaceStation14.sln.DotSettings
This commit is contained in:
Víctor Aguilera Puerto
2020-09-06 00:17:48 +02:00
755 changed files with 53590 additions and 34170 deletions

View File

@@ -18,6 +18,7 @@ using Content.Shared.GameObjects.Components.Chemistry.ChemMaster;
using Content.Shared.GameObjects.Components.Chemistry.ReagentDispenser; using Content.Shared.GameObjects.Components.Chemistry.ReagentDispenser;
using Content.Shared.GameObjects.Components.Gravity; using Content.Shared.GameObjects.Components.Gravity;
using Content.Shared.GameObjects.Components.Markers; using Content.Shared.GameObjects.Components.Markers;
using Content.Shared.GameObjects.Components.Power.AME;
using Content.Shared.GameObjects.Components.Research; using Content.Shared.GameObjects.Components.Research;
using Content.Shared.GameObjects.Components.VendingMachines; using Content.Shared.GameObjects.Components.VendingMachines;
using Content.Shared.Kitchen; using Content.Shared.Kitchen;
@@ -72,6 +73,7 @@ namespace Content.Client
factory.Register<SharedChemMasterComponent>(); factory.Register<SharedChemMasterComponent>();
factory.Register<SharedMicrowaveComponent>(); factory.Register<SharedMicrowaveComponent>();
factory.Register<SharedGravityGeneratorComponent>(); factory.Register<SharedGravityGeneratorComponent>();
factory.Register<SharedAMEControllerComponent>();
prototypes.RegisterIgnore("material"); prototypes.RegisterIgnore("material");
prototypes.RegisterIgnore("reaction"); //Chemical reactions only needed by server. Reactions checks are server-side. prototypes.RegisterIgnore("reaction"); //Chemical reactions only needed by server. Reactions checks are server-side.

View File

@@ -0,0 +1,45 @@
using Content.Shared.GameObjects.Components;
using JetBrains.Annotations;
using Robust.Client.GameObjects.Components.UserInterface;
namespace Content.Client.GameObjects.Components
{
[UsedImplicitly]
public class AcceptCloningBoundUserInterface : BoundUserInterface
{
public AcceptCloningBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
{
}
private AcceptCloningWindow _window;
protected override void Open()
{
base.Open();
_window = new AcceptCloningWindow();
_window.OnClose += Close;
_window.DenyButton.OnPressed += _ => _window.Close();
_window.ConfirmButton.OnPressed += _ =>
{
SendMessage(
new SharedAcceptCloningComponent.UiButtonPressedMessage(
SharedAcceptCloningComponent.UiButton.Accept));
_window.Close();
};
_window.OpenCentered();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_window?.Dispose();
}
}
}
}

View File

@@ -0,0 +1,50 @@
#nullable enable
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Localization;
namespace Content.Client.GameObjects.Components
{
public sealed class AcceptCloningWindow : SS14Window
{
public readonly Button DenyButton;
public readonly Button ConfirmButton;
public AcceptCloningWindow()
{
Title = Loc.GetString("Cloning Machine");
Contents.AddChild(new VBoxContainer
{
Children =
{
new VBoxContainer
{
Children =
{
(new Label
{
Text = Loc.GetString("You are being cloned! Transfer your soul to the clone body?")
}),
new HBoxContainer
{
Children =
{
(ConfirmButton = new Button
{
Text = Loc.GetString("Yes"),
}),
(DenyButton = new Button
{
Text = Loc.GetString("No"),
})
}
},
}
},
}
});
}
}
}

View File

@@ -2,6 +2,7 @@
using System.Linq; using System.Linq;
using Content.Client.GameObjects.Components.Mobs; using Content.Client.GameObjects.Components.Mobs;
using Content.Client.UserInterface; using Content.Client.UserInterface;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.Input; using Content.Shared.Input;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Interfaces.Input; using Robust.Client.Interfaces.Input;

View File

@@ -0,0 +1,68 @@
using Content.Shared.GameObjects.Components.Atmos;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Client.Graphics;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects.Components.Renderable;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
using System;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Atmos
{
[UsedImplicitly]
public class PipeVisualizer : AppearanceVisualizer
{
private RSI _pipeRSI;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
var rsiString = node.GetNode("pipeRSI").ToString();
var rsiPath = SharedSpriteComponent.TextureRoot / rsiString;
try
{
var resourceCache = IoCManager.Resolve<IResourceCache>();
var resource = resourceCache.GetResource<RSIResource>(rsiPath);
_pipeRSI = resource.RSI;
}
catch (Exception e)
{
Logger.ErrorS("go.pipevisualizer", "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
}
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (!component.Owner.TryGetComponent(out ISpriteComponent sprite))
{
return;
}
if (!component.TryGetData(PipeVisuals.VisualState, out PipeVisualStateSet pipeVisualStateSet))
{
return;
}
for (var i = 0; i < pipeVisualStateSet.PipeVisualStates.Length; i++)
{
var pipeVisualState = pipeVisualStateSet.PipeVisualStates[i];
var rsiState = "pipe";
rsiState += pipeVisualState.PipeDirection.ToString();
rsiState += ((int) pipeVisualState.ConduitLayer).ToString();
var pipeLayerKey = "pipeLayer" + i.ToString();
sprite.LayerMapReserveBlank(pipeLayerKey);
var currentPipeLayer = sprite.LayerMapGet(pipeLayerKey);
sprite.LayerSetRSI(currentPipeLayer, _pipeRSI);
sprite.LayerSetState(currentPipeLayer, rsiState);
sprite.LayerSetVisible(currentPipeLayer, true);
}
}
}
}

View File

@@ -0,0 +1,85 @@
using Content.Shared.GameObjects.Atmos;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects.Components.Renderable;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
using System;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Atmos
{
[UsedImplicitly]
public class PumpVisualizer : AppearanceVisualizer
{
private RSI _pumpRSI;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
var rsiString = node.GetNode("pumpRSI").ToString();
var rsiPath = SharedSpriteComponent.TextureRoot / rsiString;
try
{
var resourceCache = IoCManager.Resolve<IResourceCache>();
var resource = resourceCache.GetResource<RSIResource>(rsiPath);
_pumpRSI = resource.RSI;
}
catch (Exception e)
{
Logger.ErrorS("go.pumpvisualizer", "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
}
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (!component.Owner.TryGetComponent(out ISpriteComponent sprite))
{
return;
}
if (!component.TryGetData(PumpVisuals.VisualState, out PumpVisualState pumpVisualState))
{
return;
}
var pumpBaseState = "pump";
pumpBaseState += pumpVisualState.InletDirection.ToString();
pumpBaseState += ((int) pumpVisualState.InletConduitLayer).ToString();
pumpBaseState += pumpVisualState.OutletDirection.ToString();
pumpBaseState += ((int) pumpVisualState.OutletConduitLayer).ToString();
sprite.LayerMapReserveBlank(Layer.PumpBase);
var basePumpLayer = sprite.LayerMapGet(Layer.PumpBase);
sprite.LayerSetRSI(basePumpLayer, _pumpRSI);
sprite.LayerSetState(basePumpLayer, pumpBaseState);
sprite.LayerSetVisible(basePumpLayer, true);
var pumpEnabledAnimationState = "pumpEnabled";
pumpEnabledAnimationState += pumpVisualState.InletDirection.ToString();
pumpEnabledAnimationState += ((int) pumpVisualState.InletConduitLayer).ToString();
pumpEnabledAnimationState += pumpVisualState.OutletDirection.ToString();
pumpEnabledAnimationState += ((int) pumpVisualState.OutletConduitLayer).ToString();
sprite.LayerMapReserveBlank(Layer.PumpEnabled);
var pumpEnabledAnimationLayer = sprite.LayerMapGet(Layer.PumpEnabled);
sprite.LayerSetRSI(pumpEnabledAnimationLayer, _pumpRSI);
sprite.LayerSetState(pumpEnabledAnimationLayer, pumpEnabledAnimationState);
sprite.LayerSetVisible(pumpEnabledAnimationLayer, pumpVisualState.PumpEnabled);
}
private enum Layer
{
PumpBase,
PumpEnabled,
}
}
}

View File

@@ -1,8 +1,10 @@
#nullable enable #nullable enable
using Content.Client.GameObjects.Components.Disposal; using Content.Client.GameObjects.Components.Disposal;
using Content.Client.GameObjects.Components.MedicalScanner;
using Content.Client.Interfaces.GameObjects.Components.Interaction; using Content.Client.Interfaces.GameObjects.Components.Interaction;
using Content.Shared.GameObjects.Components.Body; 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 Robust.Client.Interfaces.GameObjects.Components; using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
@@ -14,14 +16,20 @@ namespace Content.Client.GameObjects.Components.Body
{ {
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(IDamageableComponent))] [ComponentReference(typeof(IDamageableComponent))]
[ComponentReference(typeof(IBodyManagerComponent))] [ComponentReference(typeof(ISharedBodyManagerComponent))]
public class BodyManagerComponent : SharedBodyManagerComponent, IClientDraggable public class BodyManagerComponent : SharedBodyManagerComponent, IClientDraggable
{ {
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!;
public bool ClientCanDropOn(CanDropEventArgs eventArgs) public bool ClientCanDropOn(CanDropEventArgs eventArgs)
{ {
return eventArgs.Target.HasComponent<DisposalUnitComponent>(); if (
eventArgs.Target.HasComponent<DisposalUnitComponent>()||
eventArgs.Target.HasComponent<MedicalScannerComponent>())
{
return true;
}
return false;
} }
public bool ClientCanDrag(CanDragEventArgs eventArgs) public bool ClientCanDrag(CanDragEventArgs eventArgs)

View File

@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using Content.Shared.GameObjects.Components.Medical;
using JetBrains.Annotations;
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using static Content.Shared.GameObjects.Components.Medical.SharedCloningPodComponent;
namespace Content.Client.GameObjects.Components.CloningPod
{
[UsedImplicitly]
public class CloningPodBoundUserInterface : BoundUserInterface
{
public CloningPodBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
{
}
private CloningPodWindow _window;
protected override void Open()
{
base.Open();
_window = new CloningPodWindow(new Dictionary<int, string>());
_window.OnClose += Close;
_window.CloneButton.OnPressed += _ =>
{
if (_window.SelectedScan != null)
{
SendMessage(new CloningPodUiButtonPressedMessage(UiButton.Clone, (int) _window.SelectedScan));
}
};
_window.EjectButton.OnPressed += _ =>
{
SendMessage(new CloningPodUiButtonPressedMessage(UiButton.Eject, null));
};
_window.OpenCentered();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
_window.Populate((CloningPodBoundUserInterfaceState) state);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_window?.Dispose();
}
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
using Content.Shared.GameObjects.Components.Medical;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using static Content.Shared.GameObjects.Components.Medical.SharedCloningPodComponent;
using static Content.Shared.GameObjects.Components.Medical.SharedCloningPodComponent.CloningPodStatus;
namespace Content.Client.GameObjects.Components.CloningPod
{
public class CloningPodVisualizer : AppearanceVisualizer
{
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (!component.TryGetData(CloningPodVisuals.Status, out CloningPodStatus status)) return;
sprite.LayerSetState(CloningPodVisualLayers.Machine, StatusToMachineStateId(status));
}
private string StatusToMachineStateId(CloningPodStatus status)
{
//TODO: implement NoMind for if the mind is not yet in the body
//TODO: Find a use for GORE POD
switch (status)
{
case Cloning: return "pod_1";
case NoMind: return "pod_e";
case Gore: return "pod_g";
case Idle: return "pod_0";
default:
throw new ArgumentOutOfRangeException(nameof(status), status, "unknown CloningPodStatus");
}
}
public enum CloningPodVisualLayers
{
Machine,
}
}
}

View File

@@ -0,0 +1,442 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.Localization;
using static Content.Shared.GameObjects.Components.Medical.SharedCloningPodComponent;
namespace Content.Client.GameObjects.Components.CloningPod
{
public sealed class CloningPodWindow : SS14Window
{
private Dictionary<int, string> _scanManager;
private readonly VBoxContainer _mainVBox;
private readonly ScanListContainer _scanList;
private readonly LineEdit _searchBar;
private readonly Button _clearButton;
public readonly Button CloneButton;
public readonly Button EjectButton;
private readonly CloningScanButton _measureButton;
private CloningScanButton? _selectedButton;
private Label _progressLabel;
private readonly ProgressBar _cloningProgressBar;
private Label _mindState;
protected override Vector2 ContentsMinimumSize => _mainVBox?.CombinedMinimumSize ?? Vector2.Zero;
private CloningPodBoundUserInterfaceState _lastUpdate = null!;
// List of scans that are visible based on current filter criteria.
private readonly Dictionary<int, string> _filteredScans = new Dictionary<int, string>();
// The indices of the visible scans last time UpdateVisibleScans was ran.
// This is inclusive, so end is the index of the last scan, not right after it.
private (int start, int end) _lastScanIndices;
public int? SelectedScan;
protected override Vector2? CustomSize => (250, 300);
public CloningPodWindow(
Dictionary<int, string> scanManager)
{
_scanManager = scanManager;
Title = Loc.GetString("Cloning Machine");
Contents.AddChild(_mainVBox = new VBoxContainer
{
Children =
{
new HBoxContainer
{
Children =
{
(_searchBar = new LineEdit
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
PlaceHolder = Loc.GetString("Search")
}),
(_clearButton = new Button
{
Disabled = true,
Text = Loc.GetString("Clear"),
})
}
},
new ScrollContainer
{
CustomMinimumSize = new Vector2(200.0f, 0.0f),
SizeFlagsVertical = SizeFlags.FillExpand,
Children =
{
(_scanList = new ScanListContainer())
}
},
new VBoxContainer
{
Children =
{
(CloneButton = new Button
{
Text = Loc.GetString("Clone")
})
}
},
(_measureButton = new CloningScanButton {Visible = false}),
(_cloningProgressBar = new ProgressBar
{
CustomMinimumSize = (200, 20),
SizeFlagsHorizontal = SizeFlags.Fill,
MinValue = 0,
MaxValue = 10,
Page = 0,
Value = 0.5f,
Children =
{
(_progressLabel = new Label())
}
}),
(EjectButton = new Button
{
Text = Loc.GetString("Eject Body")
}),
new HBoxContainer
{
Children =
{
new Label()
{
Text = Loc.GetString("Neural Interface: ")
},
(_mindState = new Label()
{
Text = Loc.GetString("No Activity"),
FontColorOverride = Color.Red
}),
}
}
}
});
_searchBar.OnTextChanged += OnSearchBarTextChanged;
_clearButton.OnPressed += OnClearButtonPressed;
BuildEntityList();
_searchBar.GrabKeyboardFocus();
}
public void Populate(CloningPodBoundUserInterfaceState state)
{
//Ignore useless updates or we can't interact with the UI
//TODO: come up with a better comparision, probably write a comparator because '.Equals' doesn't work
if (_lastUpdate == null || _lastUpdate.MindIdName.Count != state.MindIdName.Count)
{
_scanManager = state.MindIdName;
BuildEntityList();
_lastUpdate = state;
}
var percentage = state.Progress / _cloningProgressBar.MaxValue * 100;
_progressLabel.Text = $"{percentage:0}%";
_cloningProgressBar.Value = state.Progress;
_mindState.Text = Loc.GetString(state.MindPresent ? "Consciousness Detected" : "No Activity");
_mindState.FontColorOverride = state.MindPresent ? Color.Green : Color.Red;
}
private void OnSearchBarTextChanged(LineEdit.LineEditEventArgs args)
{
BuildEntityList(args.Text);
_clearButton.Disabled = string.IsNullOrEmpty(args.Text);
}
private void OnClearButtonPressed(BaseButton.ButtonEventArgs args)
{
_searchBar.Clear();
BuildEntityList("");
}
private void BuildEntityList(string? searchStr = null)
{
_filteredScans.Clear();
_scanList.RemoveAllChildren();
// Reset last scan indices so it automatically updates the entire list.
_lastScanIndices = (0, -1);
_scanList.RemoveAllChildren();
_selectedButton = null;
searchStr = searchStr?.ToLowerInvariant();
foreach (var scan in _scanManager)
{
if (searchStr != null && !_doesScanMatchSearch(scan.Value, searchStr))
{
continue;
}
_filteredScans.Add(scan.Key, scan.Value);
}
//TODO: set up sort
//_filteredScans.Sort((a, b) => string.Compare(a.ToString(), b.ToString(), StringComparison.Ordinal));
_scanList.TotalItemCount = _filteredScans.Count;
}
private void UpdateVisibleScans()
{
// Update visible buttons in the scan list.
// Calculate index of first scan to render based on current scroll.
var height = _measureButton.CombinedMinimumSize.Y + ScanListContainer.Separation;
var offset = -_scanList.Position.Y;
var startIndex = (int) Math.Floor(offset / height);
_scanList.ItemOffset = startIndex;
var (prevStart, prevEnd) = _lastScanIndices;
// Calculate index of final one.
var endIndex = startIndex - 1;
var spaceUsed = -height; // -height instead of 0 because else it cuts off the last button.
while (spaceUsed < _scanList.Parent!.Height)
{
spaceUsed += height;
endIndex += 1;
}
endIndex = Math.Min(endIndex, _filteredScans.Count - 1);
if (endIndex == prevEnd && startIndex == prevStart)
{
// Nothing changed so bye.
return;
}
_lastScanIndices = (startIndex, endIndex);
// Delete buttons at the start of the list that are no longer visible (scrolling down).
for (var i = prevStart; i < startIndex && i <= prevEnd; i++)
{
var control = (CloningScanButton) _scanList.GetChild(0);
DebugTools.Assert(control.Index == i);
_scanList.RemoveChild(control);
}
// Delete buttons at the end of the list that are no longer visible (scrolling up).
for (var i = prevEnd; i > endIndex && i >= prevStart; i--)
{
var control = (CloningScanButton) _scanList.GetChild(_scanList.ChildCount - 1);
DebugTools.Assert(control.Index == i);
_scanList.RemoveChild(control);
}
var array = _filteredScans.ToArray();
// Create buttons at the start of the list that are now visible (scrolling up).
for (var i = Math.Min(prevStart - 1, endIndex); i >= startIndex; i--)
{
InsertEntityButton(array[i], true, i);
}
// Create buttons at the end of the list that are now visible (scrolling down).
for (var i = Math.Max(prevEnd + 1, startIndex); i <= endIndex; i++)
{
InsertEntityButton(array[i], false, i);
}
}
// Create a spawn button and insert it into the start or end of the list.
private void InsertEntityButton(KeyValuePair<int, string> scan, bool insertFirst, int index)
{
var button = new CloningScanButton
{
Scan = scan.Value,
Id = scan.Key,
Index = index // We track this index purely for debugging.
};
button.ActualButton.OnToggled += OnItemButtonToggled;
var entityLabelText = scan.Value;
button.EntityLabel.Text = entityLabelText;
if (scan.Key == SelectedScan)
{
_selectedButton = button;
_selectedButton.ActualButton.Pressed = true;
}
//TODO: replace with body's face
/*var tex = IconComponent.GetScanIcon(scan, resourceCache);
var rect = button.EntityTextureRect;
if (tex != null)
{
rect.Texture = tex.Default;
}
else
{
rect.Dispose();
}
rect.Dispose();
*/
_scanList.AddChild(button);
if (insertFirst)
{
button.SetPositionInParent(0);
}
}
private static bool _doesScanMatchSearch(string scan, string searchStr)
{
return scan.ToLowerInvariant().Contains(searchStr);
}
private void OnItemButtonToggled(BaseButton.ButtonToggledEventArgs args)
{
var item = (CloningScanButton) args.Button.Parent!;
if (_selectedButton == item)
{
_selectedButton = null;
SelectedScan = null;
return;
}
else if (_selectedButton != null)
{
_selectedButton.ActualButton.Pressed = false;
}
_selectedButton = null;
SelectedScan = null;
_selectedButton = item;
SelectedScan = item.Id;
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
UpdateVisibleScans();
}
private class ScanListContainer : Container
{
// Quick and dirty container to do virtualization of the list.
// Basically, get total item count and offset to put the current buttons at.
// Get a constant minimum height and move the buttons in the list up to match the scrollbar.
private int _totalItemCount;
private int _itemOffset;
public int TotalItemCount
{
get => _totalItemCount;
set
{
_totalItemCount = value;
MinimumSizeChanged();
}
}
public int ItemOffset
{
get => _itemOffset;
set
{
_itemOffset = value;
UpdateLayout();
}
}
public const float Separation = 2;
protected override Vector2 CalculateMinimumSize()
{
if (ChildCount == 0)
{
return Vector2.Zero;
}
var first = GetChild(0);
var (minX, minY) = first.CombinedMinimumSize;
return (minX, minY * TotalItemCount + (TotalItemCount - 1) * Separation);
}
protected override void LayoutUpdateOverride()
{
if (ChildCount == 0)
{
return;
}
var first = GetChild(0);
var height = first.CombinedMinimumSize.Y;
var offset = ItemOffset * height + (ItemOffset - 1) * Separation;
foreach (var child in Children)
{
FitChildInBox(child, UIBox2.FromDimensions(0, offset, Width, height));
offset += Separation + height;
}
}
}
[DebuggerDisplay("cloningbutton {" + nameof(Index) + "}")]
private class CloningScanButton : Control
{
public string Scan { get; set; } = default!;
public int Id { get; set; }
public Button ActualButton { get; private set; }
public Label EntityLabel { get; private set; }
public TextureRect EntityTextureRect { get; private set; }
public int Index { get; set; }
public CloningScanButton()
{
AddChild(ActualButton = new Button
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
SizeFlagsVertical = SizeFlags.FillExpand,
ToggleMode = true,
});
AddChild(new HBoxContainer
{
Children =
{
(EntityTextureRect = new TextureRect
{
CustomMinimumSize = (32, 32),
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
SizeFlagsVertical = SizeFlags.ShrinkCenter,
Stretch = TextureRect.StretchMode.KeepAspectCentered,
CanShrink = true
}),
(EntityLabel = new Label
{
SizeFlagsVertical = SizeFlags.ShrinkCenter,
SizeFlagsHorizontal = SizeFlags.FillExpand,
Text = "",
ClipText = true
})
}
});
}
}
}
}

View File

@@ -115,6 +115,7 @@ namespace Content.Client.GameObjects.Components.Doors
var unlitVisible = true; var unlitVisible = true;
var boltedVisible = false; var boltedVisible = false;
var weldedVisible = false;
switch (state) switch (state)
{ {
case DoorVisualState.Closed: case DoorVisualState.Closed:
@@ -145,6 +146,9 @@ namespace Content.Client.GameObjects.Components.Doors
animPlayer.Play(DenyAnimation, AnimationKey); animPlayer.Play(DenyAnimation, AnimationKey);
} }
break; break;
case DoorVisualState.Welded:
weldedVisible = true;
break;
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
@@ -159,6 +163,7 @@ namespace Content.Client.GameObjects.Components.Doors
} }
sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible); sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible);
sprite.LayerSetVisible(DoorVisualLayers.BaseWelded, weldedVisible);
sprite.LayerSetVisible(DoorVisualLayers.BaseBolted, unlitVisible && boltedVisible); sprite.LayerSetVisible(DoorVisualLayers.BaseBolted, unlitVisible && boltedVisible);
} }
} }
@@ -167,6 +172,7 @@ namespace Content.Client.GameObjects.Components.Doors
{ {
Base, Base,
BaseUnlit, BaseUnlit,
BaseBolted BaseWelded,
BaseBolted,
} }
} }

View File

@@ -0,0 +1,39 @@

using Content.Shared.GameObjects.Components;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using System;
namespace Content.Client.GameObjects.Components
{
[UsedImplicitly]
public class ExpendableLightVisualizer : AppearanceVisualizer
{
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (component.Deleted)
{
return;
}
if (component.TryGetData(ExpendableLightVisuals.State, out string lightBehaviourID))
{
if (component.Owner.TryGetComponent<LightBehaviourComponent>(out var lightBehaviour))
{
lightBehaviour.StopLightBehaviour();
if (lightBehaviourID != string.Empty)
{
lightBehaviour.StartLightBehaviour(lightBehaviourID);
}
else if (component.Owner.TryGetComponent<PointLightComponent>(out var light))
{
light.Enabled = false;
}
}
}
}
}
}

View File

@@ -0,0 +1,41 @@
using Content.Shared.GameObjects.Components;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
namespace Content.Client.GameObjects.Components
{
public class ExtinguisherCabinetVisualizer : AppearanceVisualizer
{
private string _prefix;
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (component.TryGetData(ExtinguisherCabinetVisuals.IsOpen, out bool isOpen))
{
if (isOpen)
{
if (component.TryGetData(ExtinguisherCabinetVisuals.ContainsExtinguisher, out bool contains))
{
if (contains)
{
sprite.LayerSetState(0, "extinguisher_full");
}
else
{
sprite.LayerSetState(0, "extinguisher_empty");
}
}
}
else
{
sprite.LayerSetState(0, "extinguisher_closed");
}
}
}
}
}

View File

@@ -0,0 +1,16 @@

using Content.Shared.GameObjects.Components;
using Robust.Shared.GameObjects;
using Robust.Client.GameObjects;
namespace Content.Client.GameObjects.Components.Interactable
{
/// <summary>
/// Component that represents a handheld expendable light which can be activated and eventually dies over time.
/// </summary>
[RegisterComponent]
public class ExpendableLightComponent : SharedExpendableLightComponent
{
}
}

View File

@@ -0,0 +1,539 @@

using System;
using System.Collections.Generic;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using Content.Shared.GameObjects.Components;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Interfaces.Serialization;
using Robust.Client.Animations;
using Robust.Shared.Interfaces.GameObjects;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Robust.Client.GameObjects.Components.Animations;
using System.Linq;
namespace Content.Client.GameObjects.Components
{
#region LIGHT_BEHAVIOURS
/// <summary>
/// Base class for all light behaviours to derive from.
/// This AnimationTrack derivative does not rely on keyframes since it often needs to have a randomized duration.
/// </summary>
[Serializable]
public abstract class LightBehaviourAnimationTrack : AnimationTrackProperty, IExposeData
{
[ViewVariables] public string ID { get; set; }
[ViewVariables] public string Property { get; protected set; }
[ViewVariables] public bool IsLooped { get; set; }
[ViewVariables] public bool Enabled { get; set; }
[ViewVariables] public float StartValue { get; set; }
[ViewVariables] public float EndValue { get; set; }
[ViewVariables] public float MinDuration { get; set; }
[ViewVariables] public float MaxDuration { get; set; }
[ViewVariables] public AnimationInterpolationMode InterpolateMode { get; set; }
[ViewVariables] protected float MaxTime { get; set; }
protected PointLightComponent Light = default;
protected IRobustRandom RobustRandom = default;
private float _maxTime = default;
public virtual void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(this, x => x.ID, "id", string.Empty);
serializer.DataField(this, x => x.IsLooped, "isLooped", false);
serializer.DataField(this, x => x.Enabled, "enabled", false);
serializer.DataField(this, x => x.StartValue, "startValue", 0f);
serializer.DataField(this, x => x.EndValue, "endValue", 2f);
serializer.DataField(this, x => x.MinDuration, "minDuration", -1f);
serializer.DataField(this, x => x.MaxDuration, "maxDuration", 2f);
serializer.DataField(this, x => x.Property, "property", "Radius");
serializer.DataField(this, x => x.InterpolateMode, "interpolate", AnimationInterpolationMode.Linear);
}
public void Initialize(PointLightComponent light)
{
Light = light;
RobustRandom = IoCManager.Resolve<IRobustRandom>();
if (Enabled)
{
Light.Enabled = true;
}
OnInitialize();
}
public void UpdatePlaybackValues(Animation owner)
{
Light.Enabled = true;
if (MinDuration > 0)
{
MaxTime = (float) RobustRandom.NextDouble() * (MaxDuration - MinDuration) + MinDuration;
}
else
{
MaxTime = MaxDuration;
}
owner.Length = TimeSpan.FromSeconds(MaxTime);
}
public override (int KeyFrameIndex, float FramePlayingTime) InitPlayback()
{
OnStart();
return (-1, _maxTime);
}
protected void ApplyProperty(object value)
{
if (Property == null)
{
throw new InvalidOperationException("Property parameter is null! Check the prototype!");
}
if (Light is IAnimationProperties properties)
{
properties.SetAnimatableProperty(Property, value);
}
else
{
AnimationHelper.SetAnimatableProperty(Light, Property, value);
}
}
protected override void ApplyProperty(object context, object value)
{
ApplyProperty(value);
}
public virtual void OnInitialize() { }
public virtual void OnStart() { }
}
/// <summary>
/// A light behaviour that alternates between StartValue and EndValue
/// </summary>
public class PulseBehaviour: LightBehaviourAnimationTrack
{
public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback(
object context, int prevKeyFrameIndex, float prevPlayingTime, float frameTime)
{
var playingTime = prevPlayingTime + frameTime;
var interpolateValue = playingTime / MaxTime;
if (Property == "Enabled") // special case for boolean
{
ApplyProperty(interpolateValue < 0.5f? true : false);
return (-1, playingTime);
}
if (interpolateValue < 0.5f)
{
switch (InterpolateMode)
{
case AnimationInterpolationMode.Linear:
ApplyProperty(InterpolateLinear(StartValue, EndValue, interpolateValue * 2f));
break;
case AnimationInterpolationMode.Cubic:
ApplyProperty(InterpolateCubic(EndValue, StartValue, EndValue, StartValue, interpolateValue * 2f));
break;
default:
case AnimationInterpolationMode.Nearest:
ApplyProperty(StartValue);
break;
}
}
else
{
switch (InterpolateMode)
{
case AnimationInterpolationMode.Linear:
ApplyProperty(InterpolateLinear(EndValue, StartValue, (interpolateValue - 0.5f) * 2f));
break;
case AnimationInterpolationMode.Cubic:
ApplyProperty(InterpolateCubic(StartValue, EndValue, StartValue, EndValue, (interpolateValue - 0.5f) * 2f));
break;
default:
case AnimationInterpolationMode.Nearest:
ApplyProperty(EndValue);
break;
}
}
return (-1, playingTime);
}
}
/// <summary>
/// A light behaviour that interpolates from StartValue to EndValue
/// </summary>
public class FadeBehaviour : LightBehaviourAnimationTrack
{
public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback(
object context, int prevKeyFrameIndex, float prevPlayingTime, float frameTime)
{
var playingTime = prevPlayingTime + frameTime;
var interpolateValue = playingTime / MaxTime;
if (Property == "Enabled") // special case for boolean
{
ApplyProperty(interpolateValue < EndValue? true : false);
return (-1, playingTime);
}
switch (InterpolateMode)
{
case AnimationInterpolationMode.Linear:
ApplyProperty(InterpolateLinear(StartValue, EndValue, interpolateValue));
break;
case AnimationInterpolationMode.Cubic:
ApplyProperty(InterpolateCubic(EndValue, StartValue, EndValue, StartValue, interpolateValue));
break;
default:
case AnimationInterpolationMode.Nearest:
ApplyProperty(interpolateValue < 0.5f ? StartValue : EndValue);
break;
}
return (-1, playingTime);
}
}
/// <summary>
/// A light behaviour that interpolates using random values chosen between StartValue and EndValue.
/// </summary>
public class RandomizeBehaviour : LightBehaviourAnimationTrack
{
private object _randomValue1 = default;
private object _randomValue2 = default;
private object _randomValue3 = default;
private object _randomValue4 = default;
public override void OnInitialize()
{
_randomValue2 = InterpolateLinear(StartValue, EndValue, (float) RobustRandom.NextDouble());
_randomValue3 = InterpolateLinear(StartValue, EndValue, (float) RobustRandom.NextDouble());
_randomValue4 = InterpolateLinear(StartValue, EndValue, (float) RobustRandom.NextDouble());
}
public override void OnStart()
{
if (Property == "Enabled") // special case for boolean, we randomize it
{
ApplyProperty(RobustRandom.NextDouble() < 0.5 ? true : false);
return;
}
if (InterpolateMode == AnimationInterpolationMode.Cubic)
{
_randomValue1 = _randomValue2;
_randomValue2 = _randomValue3;
}
_randomValue3 = _randomValue4;
_randomValue4 = InterpolateLinear(StartValue, EndValue, (float) RobustRandom.NextDouble());
}
public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback(
object context, int prevKeyFrameIndex, float prevPlayingTime, float frameTime)
{
var playingTime = prevPlayingTime + frameTime;
var interpolateValue = playingTime / MaxTime;
if (Property == "Enabled")
{
return (-1, playingTime);
}
switch (InterpolateMode)
{
case AnimationInterpolationMode.Linear:
ApplyProperty(InterpolateLinear(_randomValue3, _randomValue4, interpolateValue));
break;
case AnimationInterpolationMode.Cubic:
ApplyProperty(InterpolateCubic(_randomValue1, _randomValue2, _randomValue3, _randomValue4, interpolateValue));
break;
default:
case AnimationInterpolationMode.Nearest:
ApplyProperty(interpolateValue < 0.5f ? _randomValue3 : _randomValue4);
break;
}
return (-1, playingTime);
}
}
/// <summary>
/// A light behaviour that cycles through a list of colors.
/// </summary>
public class ColorCycleBehaviour : LightBehaviourAnimationTrack
{
public List<Color> ColorsToCycle { get; set; }
private int _colorIndex = 0;
public override void OnStart()
{
_colorIndex++;
if (_colorIndex > ColorsToCycle.Count - 1)
{
_colorIndex = 0;
}
}
public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback(
object context, int prevKeyFrameIndex, float prevPlayingTime, float frameTime)
{
var playingTime = prevPlayingTime + frameTime;
var interpolateValue = playingTime / MaxTime;
switch (InterpolateMode)
{
case AnimationInterpolationMode.Linear:
ApplyProperty(InterpolateLinear(ColorsToCycle[(_colorIndex - 1) % ColorsToCycle.Count],
ColorsToCycle[_colorIndex],
interpolateValue));
break;
case AnimationInterpolationMode.Cubic:
ApplyProperty(InterpolateCubic(ColorsToCycle[_colorIndex],
ColorsToCycle[(_colorIndex + 1) % ColorsToCycle.Count],
ColorsToCycle[(_colorIndex + 2) % ColorsToCycle.Count],
ColorsToCycle[(_colorIndex + 3) % ColorsToCycle.Count],
interpolateValue));
break;
default:
case AnimationInterpolationMode.Nearest:
ApplyProperty(ColorsToCycle[_colorIndex]);
break;
}
return (-1, playingTime);
}
public override void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(this, x => x.ID, "id", string.Empty);
serializer.DataField(this, x => x.IsLooped, "isLooped", false);
serializer.DataField(this, x => x.Enabled, "enabled", false);
serializer.DataField(this, x => x.MinDuration, "minDuration", -1f);
serializer.DataField(this, x => x.MaxDuration, "maxDuration", 2f);
serializer.DataField(this, x => x.InterpolateMode, "interpolate", AnimationInterpolationMode.Linear);
ColorsToCycle = serializer.ReadDataField("colors", new List<Color>());
Property = "Color";
if (ColorsToCycle.Count < 2)
{
throw new InvalidOperationException($"{nameof(ColorCycleBehaviour)} has less than 2 colors to cycle");
}
}
}
#endregion
/// <summary>
/// A component which applies a specific behaviour to a PointLightComponent on its owner.
/// </summary>
[RegisterComponent]
public class LightBehaviourComponent : SharedLightBehaviourComponent
{
private const string KeyPrefix = nameof(LightBehaviourComponent);
private class AnimationContainer
{
public AnimationContainer(int key, Animation animation, LightBehaviourAnimationTrack track)
{
Key = key;
Animation = animation;
LightBehaviour = track;
}
public string FullKey => KeyPrefix + Key;
public int Key { get; set; }
public Animation Animation { get; set; }
public LightBehaviourAnimationTrack LightBehaviour { get; set; }
}
[ViewVariables(VVAccess.ReadOnly)]
private List<AnimationContainer> _animations = new List<AnimationContainer>();
private float _originalRadius = default;
private float _originalEnergy = default;
private Angle _originalRotation = default;
private Color _originalColor = default;
private bool _originalEnabled = default;
private PointLightComponent _lightComponent = default;
private AnimationPlayerComponent _animationPlayer = default;
protected override void Startup()
{
base.Startup();
CopyLightSettings();
_animationPlayer = Owner.EnsureComponent<AnimationPlayerComponent>();
_animationPlayer.AnimationCompleted += s => OnAnimationCompleted(s);
foreach (var container in _animations)
{
container.LightBehaviour.Initialize(_lightComponent);
}
// we need to initialize all behaviours before starting any
foreach (var container in _animations)
{
if (container.LightBehaviour.Enabled)
{
StartLightBehaviour(container.LightBehaviour.ID);
}
}
}
private void OnAnimationCompleted(string key)
{
var container = _animations.FirstOrDefault(x => x.FullKey == key);
if (container.LightBehaviour.IsLooped)
{
container.LightBehaviour.UpdatePlaybackValues(container.Animation);
_animationPlayer.Play(container.Animation, container.FullKey);
}
}
/// <summary>
/// If we disable all the light behaviours we want to be able to revert the light to its original state.
/// </summary>
private void CopyLightSettings()
{
if (Owner.TryGetComponent(out _lightComponent))
{
_originalColor = _lightComponent.Color;
_originalEnabled = _lightComponent.Enabled;
_originalEnergy = _lightComponent.Energy;
_originalRadius = _lightComponent.Radius;
_originalRotation = _lightComponent.Rotation;
}
else
{
Logger.Warning($"{Owner.Name} has a {nameof(LightBehaviourComponent)} but it has no {nameof(PointLightComponent)}! Check the prototype!");
}
}
/// <summary>
/// Start animating a light behaviour with the specified ID. If the specified ID is empty, it will start animating all light behaviour entries.
/// If specified light behaviours are already animating, calling this does nothing.
/// Multiple light behaviours can have the same ID.
/// </summary>
public void StartLightBehaviour(string id = "")
{
foreach (var container in _animations)
{
if (container.LightBehaviour.ID == id || id == string.Empty)
{
if (!_animationPlayer.HasRunningAnimation(KeyPrefix + container.Key))
{
container.LightBehaviour.UpdatePlaybackValues(container.Animation);
_animationPlayer.Play(container.Animation, KeyPrefix + container.Key);
}
}
}
}
/// <summary>
/// If any light behaviour with the specified ID is animating, then stop it.
/// If no ID is specified then all light behaviours will be stopped.
/// Multiple light behaviours can have the same ID.
/// </summary>
/// <param name="id"></param>
/// <param name="removeBehaviour">Should the behaviour(s) also be removed permanently?</param>
/// <param name="resetToOriginalSettings">Should the light have its original settings applied?</param>
public void StopLightBehaviour(string id = "", bool removeBehaviour = false, bool resetToOriginalSettings = false)
{
var toRemove = new List<AnimationContainer>();
foreach (var container in _animations)
{
if (container.LightBehaviour.ID == id || id == string.Empty)
{
if (_animationPlayer.HasRunningAnimation(KeyPrefix + container.Key))
{
_animationPlayer.Stop(KeyPrefix + container.Key);
}
if (removeBehaviour)
{
toRemove.Add(container);
}
}
}
foreach (var container in toRemove)
{
_animations.Remove(container);
}
if (resetToOriginalSettings)
{
_lightComponent.Color = _originalColor;
_lightComponent.Enabled = _originalEnabled;
_lightComponent.Energy = _originalEnergy;
_lightComponent.Radius = _originalRadius;
_lightComponent.Rotation = _originalRotation;
}
}
/// <summary>
/// Add a new light behaviour to the component and start it immediately unless otherwise specified.
/// </summary>
public void AddNewLightBehaviour(LightBehaviourAnimationTrack behaviour, bool playImmediately = true)
{
int key = 0;
while (_animations.Any(x => x.Key == key))
{
key++;
}
var animation = new Animation()
{
AnimationTracks = { behaviour }
};
behaviour.Initialize(_lightComponent);
var container = new AnimationContainer(key, animation, behaviour);
_animations.Add(container);
if (playImmediately)
{
StartLightBehaviour(behaviour.ID);
}
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
var behaviours = serializer.ReadDataField("behaviours", new List<LightBehaviourAnimationTrack>());
var key = 0;
foreach (LightBehaviourAnimationTrack behaviour in behaviours)
{
var animation = new Animation()
{
AnimationTracks = { behaviour }
};
_animations.Add(new AnimationContainer(key, animation, behaviour));
key++;
}
}
}
}

View File

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

View File

@@ -12,6 +12,11 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
{ {
base.OnChangeData(component); base.OnChangeData(component);
if (component.Owner.Deleted)
{
return;
}
var sprite = component.Owner.GetComponent<ISpriteComponent>(); var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (!component.TryGetData(MedicalScannerVisuals.Status, out MedicalScannerStatus status)) return; if (!component.TryGetData(MedicalScannerVisuals.Status, out MedicalScannerStatus status)) return;
sprite.LayerSetState(MedicalScannerVisualLayers.Machine, StatusToMachineStateId(status)); sprite.LayerSetState(MedicalScannerVisualLayers.Machine, StatusToMachineStateId(status));

View File

@@ -24,7 +24,7 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
{ {
(ScanButton = new Button (ScanButton = new Button
{ {
Text = "Scan and Save DNA" Text = Loc.GetString("Scan and Save DNA")
}), }),
(_diagnostics = new Label (_diagnostics = new Label
{ {

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Client.UserInterface; using Content.Client.UserInterface;
@@ -100,7 +100,10 @@ namespace Content.Client.GameObjects.Components.Mobs
foreach (var (key, effect) in _status.OrderBy(x => (int) x.Key)) foreach (var (key, effect) in _status.OrderBy(x => (int) x.Key))
{ {
var texture = _resourceCache.GetTexture(effect.Icon); var texture = _resourceCache.GetTexture(effect.Icon);
var status = new StatusControl(key, texture); var status = new StatusControl(key, texture)
{
ToolTip = key.ToString()
};
if (effect.Cooldown.HasValue) if (effect.Cooldown.HasValue)
{ {

View File

@@ -0,0 +1,30 @@
using Content.Client.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.Components.Mobs.State;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Client.GameObjects.Components.Mobs.State
{
public class CriticalState : SharedCriticalState
{
public override void EnterState(IEntity entity)
{
if (entity.TryGetComponent(out AppearanceComponent appearance))
{
appearance.SetData(DamageStateVisuals.State, DamageState.Critical);
}
EntitySystem.Get<StandingStateSystem>().Down(entity);
}
public override void ExitState(IEntity entity)
{
EntitySystem.Get<StandingStateSystem>().Standing(entity);
}
public override void UpdateState(IEntity entity) { }
}
}

View File

@@ -0,0 +1,41 @@
using Content.Client.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.Components.Mobs.State;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Client.GameObjects.Components.Mobs.State
{
public class DeadState : SharedDeadState
{
public override void EnterState(IEntity entity)
{
if (entity.TryGetComponent(out AppearanceComponent appearance))
{
appearance.SetData(DamageStateVisuals.State, DamageState.Dead);
}
EntitySystem.Get<StandingStateSystem>().Down(entity);
if (entity.TryGetComponent(out CollidableComponent collidable))
{
collidable.CanCollide = false;
}
}
public override void ExitState(IEntity entity)
{
EntitySystem.Get<StandingStateSystem>().Standing(entity);
if (entity.TryGetComponent(out CollidableComponent collidable))
{
collidable.CanCollide = true;
}
}
public override void UpdateState(IEntity entity) { }
}
}

View File

@@ -0,0 +1,62 @@
#nullable enable
using System.Collections.Generic;
using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.GameObjects.Components.Mobs.State;
using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects.Components.Mobs.State
{
[RegisterComponent]
[ComponentReference(typeof(SharedMobStateManagerComponent))]
public class MobStateManagerComponent : SharedMobStateManagerComponent
{
private readonly Dictionary<DamageState, IMobState> _behavior = new Dictionary<DamageState, IMobState>
{
{DamageState.Alive, new NormalState()},
{DamageState.Critical, new CriticalState()},
{DamageState.Dead, new DeadState()}
};
private DamageState _currentDamageState;
protected override IReadOnlyDictionary<DamageState, IMobState> Behavior => _behavior;
public override DamageState CurrentDamageState
{
get => _currentDamageState;
protected set
{
if (_currentDamageState == value)
{
return;
}
if (_currentDamageState != DamageState.Invalid)
{
CurrentMobState.ExitState(Owner);
}
_currentDamageState = value;
CurrentMobState = Behavior[CurrentDamageState];
CurrentMobState.EnterState(Owner);
Dirty();
}
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
base.HandleComponentState(curState, nextState);
if (!(curState is MobStateManagerComponentState state))
{
return;
}
_currentDamageState = state.DamageState;
CurrentMobState?.ExitState(Owner);
CurrentMobState = Behavior[CurrentDamageState];
CurrentMobState.EnterState(Owner);
}
}
}

View File

@@ -0,0 +1,25 @@
using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.Components.Mobs.State;
using Robust.Client.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Client.GameObjects.Components.Mobs.State
{
public class NormalState : SharedNormalState
{
public override void EnterState(IEntity entity)
{
if (entity.TryGetComponent(out AppearanceComponent appearance))
{
appearance.SetData(DamageStateVisuals.State, DamageState.Alive);
}
UpdateState(entity);
}
public override void ExitState(IEntity entity) { }
public override void UpdateState(IEntity entity) { }
}
}

View File

@@ -18,8 +18,7 @@ namespace Content.Client.GameObjects.Components.Observer
private GhostGui _gui; private GhostGui _gui;
[ViewVariables(VVAccess.ReadOnly)] [ViewVariables(VVAccess.ReadOnly)] public bool CanReturnToBody { get; private set; } = true;
public bool CanReturnToBody { get; private set; } = true;
private bool _isAttached; private bool _isAttached;
@@ -51,7 +50,8 @@ namespace Content.Client.GameObjects.Components.Observer
base.Initialize(); base.Initialize();
if (Owner.TryGetComponent(out SpriteComponent component)) if (Owner.TryGetComponent(out SpriteComponent component))
component.Visible = _playerManager.LocalPlayer.ControlledEntity?.HasComponent<GhostComponent>() ?? false; component.Visible =
_playerManager.LocalPlayer.ControlledEntity?.HasComponent<GhostComponent>() ?? false;
} }
public override void HandleMessage(ComponentMessage message, IComponent component) public override void HandleMessage(ComponentMessage message, IComponent component)
@@ -98,7 +98,6 @@ namespace Content.Client.GameObjects.Components.Observer
{ {
_gui?.Update(); _gui?.Update();
} }
} }
} }
} }

View File

@@ -0,0 +1,62 @@
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Shared.GameObjects.Components.UserInterface;
using static Content.Shared.GameObjects.Components.Power.AME.SharedAMEControllerComponent;
namespace Content.Client.GameObjects.Components.Power.AME
{
public class AMEControllerBoundUserInterface : BoundUserInterface
{
private AMEWindow _window;
public AMEControllerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_window = new AMEWindow();
_window.OnClose += Close;
_window.OpenCentered();
_window.EjectButton.OnPressed += _ => ButtonPressed(UiButton.Eject);
_window.ToggleInjection.OnPressed += _ => ButtonPressed(UiButton.ToggleInjection);
_window.IncreaseFuelButton.OnPressed += _ => ButtonPressed(UiButton.IncreaseFuel);
_window.DecreaseFuelButton.OnPressed += _ => ButtonPressed(UiButton.DecreaseFuel);
_window.RefreshPartsButton.OnPressed += _ => ButtonPressed(UiButton.RefreshParts);
}
/// <summary>
/// Update the ui each time new state data is sent from the server.
/// </summary>
/// <param name="state">
/// Data of the <see cref="SharedReagentDispenserComponent"/> that this ui represents.
/// Sent from the server.
/// </param>
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
var castState = (AMEControllerBoundUserInterfaceState) state;
_window?.UpdateState(castState); //Update window state
}
private void ButtonPressed(UiButton button, int dispenseIndex = -1)
{
SendMessage(new UiButtonPressedMessage(button));
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_window.Dispose();
}
}
}
}

View File

@@ -0,0 +1,57 @@
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using System;
using System.Collections.Generic;
using System.Text;
using static Content.Shared.GameObjects.Components.Power.AME.SharedAMEControllerComponent;
namespace Content.Client.GameObjects.Components.Power.AME
{
public class AMEControllerVisualizer : AppearanceVisualizer
{
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
var sprite = entity.GetComponent<ISpriteComponent>();
sprite.LayerMapSet(Layers.Display, sprite.AddLayerState("control_on"));
sprite.LayerSetVisible(Layers.Display, false);
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (component.TryGetData<string>(AMEControllerVisuals.DisplayState, out var state))
{
switch (state)
{
case "on":
sprite.LayerSetState(Layers.Display, "control_on");
sprite.LayerSetVisible(Layers.Display, true);
break;
case "critical":
sprite.LayerSetState(Layers.Display, "control_critical");
sprite.LayerSetVisible(Layers.Display, true);
break;
case "fuck":
sprite.LayerSetState(Layers.Display, "control_fuck");
sprite.LayerSetVisible(Layers.Display, true);
break;
case "off":
sprite.LayerSetVisible(Layers.Display, false);
break;
default:
sprite.LayerSetVisible(Layers.Display, false);
break;
}
}
}
enum Layers
{
Display,
}
}
}

View File

@@ -0,0 +1,63 @@
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using System;
using System.Collections.Generic;
using System.Text;
using static Content.Shared.GameObjects.Components.Power.AME.SharedAMEShieldComponent;
namespace Content.Client.GameObjects.Components.Power.AME
{
public class AMEVisualizer : AppearanceVisualizer
{
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
var sprite = entity.GetComponent<ISpriteComponent>();
sprite.LayerMapSet(Layers.Core, sprite.AddLayerState("core"));
sprite.LayerSetVisible(Layers.Core, false);
sprite.LayerMapSet(Layers.CoreState, sprite.AddLayerState("core_weak"));
sprite.LayerSetVisible(Layers.CoreState, false);
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (component.TryGetData<string>(AMEShieldVisuals.Core, out var core))
{
if (core == "isCore")
{
sprite.LayerSetState(Layers.Core, "core");
sprite.LayerSetVisible(Layers.Core, true);
}
else
{
sprite.LayerSetVisible(Layers.Core, false);
}
}
if (component.TryGetData<string>(AMEShieldVisuals.CoreState, out var coreState))
switch (coreState)
{
case "weak":
sprite.LayerSetState(Layers.CoreState, "core_weak");
sprite.LayerSetVisible(Layers.CoreState, true);
break;
case "strong":
sprite.LayerSetState(Layers.CoreState, "core_strong");
sprite.LayerSetVisible(Layers.CoreState, true);
break;
case "off":
sprite.LayerSetVisible(Layers.CoreState, false);
break;
}
}
}
enum Layers
{
Core,
CoreState,
}
}

View File

@@ -0,0 +1,162 @@
using Content.Client.UserInterface.Stylesheets;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using System;
using System.Collections.Generic;
using System.Text;
using static Content.Shared.GameObjects.Components.Power.AME.SharedAMEControllerComponent;
namespace Content.Client.GameObjects.Components.Power.AME
{
public class AMEWindow : SS14Window
{
public Label InjectionStatus { get; set; }
public Button EjectButton { get; set; }
public Button ToggleInjection { get; set; }
public Button IncreaseFuelButton { get; set; }
public Button DecreaseFuelButton { get; set; }
public Button RefreshPartsButton { get; set; }
public ProgressBar FuelMeter { get; set; }
public Label FuelAmount { get; set; }
public Label InjectionAmount { get; set; }
public Label CoreCount { get; set; }
public AMEWindow()
{
IoCManager.InjectDependencies(this);
Title = "Antimatter Control Unit";
Contents.AddChild(new VBoxContainer
{
Children =
{
new HBoxContainer
{
Children =
{
new Label {Text = Loc.GetString("Engine Status") + ": "},
(InjectionStatus = new Label {Text = "Not Injecting"})
}
},
new HBoxContainer
{
Children =
{
(ToggleInjection = new Button {Text = "Toggle Injection", StyleClasses = {StyleBase.ButtonOpenBoth}, Disabled = true}),
}
},
new HBoxContainer
{
Children =
{
new Label {Text = Loc.GetString("Fuel Status") + ": "},
(FuelAmount = new Label {Text = "No fuel inserted"})
}
},
new HBoxContainer
{
Children =
{
(EjectButton = new Button {Text = "Eject", StyleClasses = {StyleBase.ButtonOpenBoth}, Disabled = true}),
}
},
new HBoxContainer
{
Children =
{
new Label {Text = Loc.GetString("Injection amount") + ": "},
(InjectionAmount = new Label {Text = "0"})
}
},
new HBoxContainer
{
Children =
{
(IncreaseFuelButton = new Button {Text = "Increase", StyleClasses = {StyleBase.ButtonOpenRight}}),
(DecreaseFuelButton = new Button {Text = "Decrease", StyleClasses = {StyleBase.ButtonOpenLeft}}),
}
},
new HBoxContainer
{
Children =
{
(RefreshPartsButton = new Button {Text = "Refresh Parts", StyleClasses = {StyleBase.ButtonOpenBoth }, Disabled = true }),
new Label { Text = Loc.GetString("Core count") + ": "},
(CoreCount = new Label { Text = "0"}),
}
}
}
});
}
/// <summary>
/// This searches recursively through all the children of "parent"
/// and sets the Disabled value of any buttons found to "val"
/// </summary>
/// <param name="parent">The control which childrens get searched</param>
/// <param name="val">The value to which disabled gets set</param>
private void SetButtonDisabledRecursive(Control parent, bool val)
{
foreach (var child in parent.Children)
{
if (child is Button but)
{
but.Disabled = val;
continue;
}
if (child.Children != null)
{
SetButtonDisabledRecursive(child, val);
}
}
}
/// <summary>
/// Update the UI state when new state data is received from the server.
/// </summary>
/// <param name="state">State data sent by the server.</param>
public void UpdateState(BoundUserInterfaceState state)
{
var castState = (AMEControllerBoundUserInterfaceState) state;
// Disable all buttons if not powered
if (Contents.Children != null)
{
SetButtonDisabledRecursive(Contents, !castState.HasPower);
EjectButton.Disabled = false;
}
if(!castState.HasFuelJar)
{
EjectButton.Disabled = true;
ToggleInjection.Disabled = true;
FuelAmount.Text = Loc.GetString("No fuel inserted");
}
else
{
EjectButton.Disabled = false;
ToggleInjection.Disabled = false;
FuelAmount.Text = $"{castState.FuelAmount}";
}
if(!castState.IsMaster)
{
ToggleInjection.Disabled = true;
}
RefreshPartsButton.Disabled = castState.Injecting;
CoreCount.Text = $"{castState.CoreCount}";
InjectionAmount.Text = $"{castState.InjectionAmount}";
}
}
}

View File

@@ -142,8 +142,7 @@ namespace Content.Client.GameObjects.EntitySystems
if (_entityManager.TryGetEntity(args.EntityUid, out var entity)) if (_entityManager.TryGetEntity(args.EntityUid, out var entity))
{ {
// check if the entity is reachable // check if the entity is reachable
if (_interactionSystem.InRangeUnobstructed(dragger.Transform.MapPosition, if (!_interactionSystem.InRangeUnobstructed(dragger, entity))
entity.Transform.MapPosition, ignoredEnt: dragger) == false)
{ {
return false; return false;
} }
@@ -193,8 +192,8 @@ namespace Content.Client.GameObjects.EntitySystems
// tell the server we are dropping if we are over a valid drop target in range. // tell the server we are dropping if we are over a valid drop target in range.
// We don't use args.EntityUid here because drag interactions generally should // We don't use args.EntityUid here because drag interactions generally should
// work even if there's something "on top" of the drop target // work even if there's something "on top" of the drop target
if (_interactionSystem.InRangeUnobstructed(_dragger.Transform.MapPosition, if (!_interactionSystem.InRangeUnobstructed(_dragger,
args.Coordinates.ToMap(_mapManager), ignoredEnt: _dragger, ignoreInsideBlocker: true) == false) args.Coordinates, ignoreInsideBlocker: true))
{ {
CancelDrag(false, null); CancelDrag(false, null);
return false; return false;
@@ -288,8 +287,7 @@ namespace Content.Client.GameObjects.EntitySystems
if (anyValidDraggable) if (anyValidDraggable)
{ {
// highlight depending on whether its in or out of range // highlight depending on whether its in or out of range
var inRange = _interactionSystem.InRangeUnobstructed(_dragger.Transform.MapPosition, var inRange = _interactionSystem.InRangeUnobstructed(_dragger, pvsEntity);
pvsEntity.Transform.MapPosition, ignoredEnt: _dragger);
inRangeSprite.PostShader = inRange ? _dropTargetInRangeShader : _dropTargetOutOfRangeShader; inRangeSprite.PostShader = inRange ? _dropTargetInRangeShader : _dropTargetOutOfRangeShader;
inRangeSprite.RenderOrder = EntityManager.CurrentTick.Value; inRangeSprite.RenderOrder = EntityManager.CurrentTick.Value;
highlightedSprites.Add(inRangeSprite); highlightedSprites.Add(inRangeSprite);
@@ -377,8 +375,7 @@ namespace Content.Client.GameObjects.EntitySystems
return; return;
} }
// still in range of the thing we are dragging? // still in range of the thing we are dragging?
if (_interactionSystem.InRangeUnobstructed(_dragger.Transform.MapPosition, if (!_interactionSystem.InRangeUnobstructed(_dragger, _draggedEntity))
_draggedEntity.Transform.MapPosition, ignoredEnt: _dragger) == false)
{ {
CancelDrag(false, null); CancelDrag(false, null);
return; return;

View File

@@ -1,10 +1,14 @@
using Content.Client.GameObjects.Components.Mobs; using System;
using Content.Client.GameObjects.Components.Mobs;
using Content.Client.GameObjects.Components.Weapons.Melee; using Content.Client.GameObjects.Components.Weapons.Melee;
using Content.Shared.GameObjects.Components.Weapons.Melee; using Content.Shared.GameObjects.Components.Weapons.Melee;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components; using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.EntitySystemMessages;
using Robust.Shared.GameObjects.Systems; using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Log; using Robust.Shared.Log;
using Robust.Shared.Maths; using Robust.Shared.Maths;
@@ -18,6 +22,7 @@ namespace Content.Client.GameObjects.EntitySystems
public sealed class MeleeWeaponSystem : EntitySystem public sealed class MeleeWeaponSystem : EntitySystem
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -53,6 +58,26 @@ namespace Content.Client.GameObjects.EntitySystems
var weaponArcAnimation = entity.GetComponent<MeleeWeaponArcAnimationComponent>(); var weaponArcAnimation = entity.GetComponent<MeleeWeaponArcAnimationComponent>();
weaponArcAnimation.SetData(weaponArc, msg.Angle, attacker); weaponArcAnimation.SetData(weaponArc, msg.Angle, attacker);
// Due to ISpriteComponent limitations, weapons that don't use an RSI won't have this effect.
if (EntityManager.TryGetEntity(msg.Source, out var source) && msg.TextureEffect && source.TryGetComponent(out ISpriteComponent sourceSprite)
&& sourceSprite.BaseRSI?.Path != null)
{
var sys = Get<EffectSystem>();
var curTime = _gameTiming.CurTime;
var effect = new EffectSystemMessage
{
EffectSprite = sourceSprite.BaseRSI.Path.ToString(),
RsiState = sourceSprite.LayerGetState(0).Name,
Coordinates = attacker.Transform.GridPosition,
Color = Vector4.Multiply(new Vector4(255, 255, 255, 125), 1.0f),
ColorDelta = Vector4.Multiply(new Vector4(0, 0, 0, -10), 1.0f),
Velocity = msg.Angle.ToVec(),
Acceleration = msg.Angle.ToVec() * 5f,
Born = curTime,
DeathTime = curTime.Add(TimeSpan.FromMilliseconds(300f)),
};
sys.CreateEffect(effect);
}
foreach (var uid in msg.Hits) foreach (var uid in msg.Hits)
{ {

View File

@@ -0,0 +1,50 @@
using Content.Shared.Audio;
using Content.Shared.GameObjects.Components.Rotation;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Client.GameObjects;
using Robust.Client.GameObjects.EntitySystems;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Client.GameObjects.EntitySystems
{
public class StandingStateSystem : SharedStandingStateSystem
{
protected override bool OnDown(IEntity entity, bool playSound = true, bool dropItems = true, bool force = false)
{
if (!entity.TryGetComponent(out AppearanceComponent appearance))
{
return false;
}
var newState = RotationState.Horizontal;
appearance.TryGetData<RotationState>(RotationVisuals.RotationState, out var oldState);
if (newState != oldState)
{
appearance.SetData(RotationVisuals.RotationState, newState);
}
if (playSound)
{
var file = AudioHelpers.GetRandomFileFromSoundCollection("bodyfall");
Get<AudioSystem>().Play(file, entity, AudioHelpers.WithVariation(0.25f));
}
return true;
}
protected override bool OnStand(IEntity entity)
{
if (!entity.TryGetComponent(out AppearanceComponent appearance)) return false;
appearance.TryGetData<RotationState>(RotationVisuals.RotationState, out var oldState);
var newState = RotationState.Vertical;
if (newState == oldState) return false;
appearance.SetData(RotationVisuals.RotationState, newState);
return true;
}
}
}

View File

@@ -7,13 +7,16 @@ using Content.Client.State;
using Content.Client.UserInterface; using Content.Client.UserInterface;
using Content.Client.Utility; using Content.Client.Utility;
using Content.Shared.GameObjects.EntitySystemMessages; using Content.Shared.GameObjects.EntitySystemMessages;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Verbs; using Content.Shared.GameObjects.Verbs;
using Content.Shared.Input; using Content.Shared.Input;
using Content.Shared.Physics;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects.EntitySystems; using Robust.Client.GameObjects.EntitySystems;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.Graphics.Drawing; using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.GameObjects.Components; using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Client.Interfaces.Graphics.ClientEye;
using Robust.Client.Interfaces.Input; using Robust.Client.Interfaces.Input;
using Robust.Client.Interfaces.ResourceManagement; using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.Interfaces.State; using Robust.Client.Interfaces.State;
@@ -29,6 +32,7 @@ using Robust.Shared.Input;
using Robust.Shared.Input.Binding; using Robust.Shared.Input.Binding;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Physics;
using Robust.Shared.Interfaces.Timing; using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Log; using Robust.Shared.Log;
@@ -40,7 +44,7 @@ using Timer = Robust.Shared.Timers.Timer;
namespace Content.Client.GameObjects.EntitySystems namespace Content.Client.GameObjects.EntitySystems
{ {
[UsedImplicitly] [UsedImplicitly]
public sealed class VerbSystem : EntitySystem public sealed class VerbSystem : SharedVerbSystem
{ {
[Dependency] private readonly IStateManager _stateManager = default!; [Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!;
@@ -116,10 +120,9 @@ namespace Content.Client.GameObjects.EntitySystems
} }
var mapCoordinates = args.Coordinates.ToMap(_mapManager); var mapCoordinates = args.Coordinates.ToMap(_mapManager);
var entities = _entityManager.GetEntitiesIntersecting(mapCoordinates.MapId, var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
Box2.CenteredAround(mapCoordinates.Position, (0.5f, 0.5f))).ToList();
if (entities.Count == 0) if (playerEntity == null || !TryGetContextEntities(playerEntity, mapCoordinates, out var entities))
{ {
return false; return false;
} }

View File

@@ -9,6 +9,7 @@
"Breakable", "Breakable",
"Pickaxe", "Pickaxe",
"Interactable", "Interactable",
"CloningPod",
"Destructible", "Destructible",
"Temperature", "Temperature",
"Explosive", "Explosive",
@@ -58,7 +59,6 @@
"AccessReader", "AccessReader",
"IdCardConsole", "IdCardConsole",
"Airlock", "Airlock",
"MedicalScanner",
"WirePlacer", "WirePlacer",
"Drink", "Drink",
"Food", "Food",
@@ -156,13 +156,25 @@
"Barotrauma", "Barotrauma",
"GasSprayer", "GasSprayer",
"GasVapor", "GasVapor",
"MobStateManager",
"Metabolism", "Metabolism",
"AiFactionTag", "AiFactionTag",
"PressureProtection", "PressureProtection",
"AMEPart",
"AMEFuelContainer",
"AMEShield",
"DebugPump", "DebugPump",
"PressurePump",
"VolumePump",
"DebugVent", "DebugVent",
"DebugSiphon", "DebugSiphon",
"SignalReceiver",
"SignalSwitch",
"SignalTransmitter",
"SignalButton",
"SignalLinker",
"ExtinguisherCabinet",
"ExtinguisherCabinetFilled",
"FireExtinguisher",
}; };
} }
} }

View File

@@ -1,6 +1,7 @@
using Content.Client.GameObjects.Components.Instruments; using Content.Client.GameObjects.Components.Instruments;
using Content.Client.UserInterface.Stylesheets; using Content.Client.UserInterface.Stylesheets;
using Content.Shared.GameObjects.EntitySystems; using Content.Client.Utility;
using Content.Shared.Utility;
using Robust.Client.Audio.Midi; using Robust.Client.Audio.Midi;
using Robust.Client.Graphics.Drawing; using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.UserInterface; using Robust.Client.Interfaces.UserInterface;
@@ -9,7 +10,6 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Log; using Robust.Shared.Log;
@@ -197,9 +197,7 @@ namespace Content.Client.Instruments
|| conMan.Owner != localPlayer.ControlledEntity))) return; || conMan.Owner != localPlayer.ControlledEntity))) return;
// We check that we're in range unobstructed just in case. // We check that we're in range unobstructed just in case.
if(!EntitySystem.Get<SharedInteractionSystem>() if (!localPlayer.InRangeUnobstructed(instrumentEnt)) return;
.InRangeUnobstructed(localPlayer.ControlledEntity.Transform.MapPosition,
instrumentEnt.Transform.MapPosition, ignoredEnt:instrumentEnt)) return;
if (!_midiManager.IsMidiFile(filename)) if (!_midiManager.IsMidiFile(filename))
{ {

View File

@@ -57,7 +57,7 @@ namespace Content.Client
} }
await using var file = await using var file =
_resourceManager.UserData.Open(BaseScreenshotPath / $"{filename}.png", FileMode.CreateNew, FileAccess.Read, FileShare.None); _resourceManager.UserData.Open(BaseScreenshotPath / $"{filename}.png", FileMode.CreateNew, FileAccess.Write, FileShare.None);
await Task.Run(() => await Task.Run(() =>
{ {

View File

@@ -2,7 +2,8 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using Content.Client.GameObjects.Components; using Content.Client.GameObjects.Components;
using Content.Shared.GameObjects.EntitySystems; using Content.Client.Utility;
using Content.Shared.Utility;
using Robust.Client.GameObjects.EntitySystems; using Robust.Client.GameObjects.EntitySystems;
using Robust.Client.Interfaces.GameObjects; using Robust.Client.Interfaces.GameObjects;
using Robust.Client.Interfaces.Graphics.ClientEye; using Robust.Client.Interfaces.Graphics.ClientEye;
@@ -67,13 +68,7 @@ namespace Content.Client.State
var inRange = false; var inRange = false;
if (localPlayer.ControlledEntity != null && entityToClick != null) if (localPlayer.ControlledEntity != null && entityToClick != null)
{ {
var playerPos = localPlayer.ControlledEntity.Transform.MapPosition; inRange = localPlayer.InRangeUnobstructed(entityToClick, ignoreInsideBlocker: true);
var entityPos = entityToClick.Transform.MapPosition;
inRange = EntitySystemManager.GetEntitySystem<SharedInteractionSystem>()
.InRangeUnobstructed(playerPos, entityPos,
predicate: entity =>
entity == localPlayer.ControlledEntity || entity == entityToClick,
ignoreInsideBlocker: true);
} }
InteractionOutlineComponent outline; InteractionOutlineComponent outline;

View File

@@ -1,11 +1,9 @@
#nullable enable #nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Client.GameObjects.EntitySystems; using Content.Client.GameObjects.EntitySystems;
using Content.Client.StationEvents; using Content.Client.StationEvents;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Robust.Client.Console; using Robust.Client.Console;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.Placement; using Robust.Client.Interfaces.Placement;
using Robust.Client.Interfaces.ResourceManagement; using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.Player; using Robust.Client.Player;
@@ -17,8 +15,12 @@ using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using static Robust.Client.UserInterface.Controls.BaseButton; using static Robust.Client.UserInterface.Controls.BaseButton;
namespace Content.Client.UserInterface.AdminMenu namespace Content.Client.UserInterface.AdminMenu
@@ -27,6 +29,9 @@ namespace Content.Client.UserInterface.AdminMenu
{ {
public TabContainer MasterTabContainer; public TabContainer MasterTabContainer;
public VBoxContainer PlayerList; public VBoxContainer PlayerList;
public Label PlayerCount;
protected override Vector2? CustomSize => (500, 250);
private List<CommandButton> _adminButtons = new List<CommandButton> private List<CommandButton> _adminButtons = new List<CommandButton>
{ {
@@ -57,51 +62,107 @@ namespace Content.Client.UserInterface.AdminMenu
new DirectCommandButton("Shutdown", "shutdown"), new DirectCommandButton("Shutdown", "shutdown"),
}; };
private static readonly Color SeparatorColor = Color.FromHex("#3D4059");
private class HSeperator : Control
{
public HSeperator()
{
AddChild(new PanelContainer {
PanelOverride = new StyleBoxFlat
{
BackgroundColor = SeparatorColor,
ContentMarginBottomOverride = 2, ContentMarginLeftOverride = 2
}
});
}
}
private class VSeperator : PanelContainer
{
public VSeperator()
{
CustomMinimumSize = (2, 5);
AddChild(new PanelContainer {
PanelOverride = new StyleBoxFlat {
BackgroundColor = SeparatorColor
}
});
}
}
private void RefreshPlayerList(ButtonEventArgs args) private void RefreshPlayerList(ButtonEventArgs args)
{ {
PlayerList.RemoveAllChildren(); PlayerList.RemoveAllChildren();
var sessions = IoCManager.Resolve<IPlayerManager>().Sessions; var playerManager = IoCManager.Resolve<IPlayerManager>();
var sessions = playerManager.Sessions;
PlayerCount.Text = $"Players: {playerManager.PlayerCount}";
Color altColor = Color.FromHex("#292B38");
Color defaultColor = Color.FromHex("#2F2F3B");
var header = new HBoxContainer var header = new HBoxContainer
{ {
SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsHorizontal = SizeFlags.FillExpand,
SeparationOverride = 4,
Children = Children =
{ {
new Label { Text = "Name", new Label { Text = "Name",
SizeFlagsStretchRatio = 2f, SizeFlagsStretchRatio = 2f,
SizeFlagsHorizontal = SizeFlags.FillExpand }, SizeFlagsHorizontal = SizeFlags.FillExpand },
new VSeperator(),
new Label { Text = "Player", new Label { Text = "Player",
SizeFlagsStretchRatio = 2f, SizeFlagsStretchRatio = 2f,
SizeFlagsHorizontal = SizeFlags.FillExpand }, SizeFlagsHorizontal = SizeFlags.FillExpand },
new VSeperator(),
new Label { Text = "Status", new Label { Text = "Status",
SizeFlagsStretchRatio = 1f, SizeFlagsStretchRatio = 1f,
SizeFlagsHorizontal = SizeFlags.FillExpand }, SizeFlagsHorizontal = SizeFlags.FillExpand },
new VSeperator(),
new Label { Text = "Ping", new Label { Text = "Ping",
SizeFlagsStretchRatio = 1f, SizeFlagsStretchRatio = 1f,
SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsHorizontal = SizeFlags.FillExpand,
Align = Label.AlignMode.Right }, Align = Label.AlignMode.Right },
} }
}; };
PlayerList.AddChild(header); PlayerList.AddChild(new PanelContainer
PlayerList.AddChild(new Controls.HighDivider()); {
PanelOverride = new StyleBoxFlat
{
BackgroundColor = altColor,
},
Children =
{
header
}
});
PlayerList.AddChild(new HSeperator());
var useAltColor = false;
foreach (var player in sessions) foreach (var player in sessions)
{ {
var hbox = new HBoxContainer var hbox = new HBoxContainer
{ {
SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsHorizontal = SizeFlags.FillExpand,
SeparationOverride = 4,
Children = Children =
{ {
new Label { new Label {
Text = player.Name, Text = player.Name,
SizeFlagsStretchRatio = 2f, SizeFlagsStretchRatio = 2f,
SizeFlagsHorizontal = SizeFlags.FillExpand }, SizeFlagsHorizontal = SizeFlags.FillExpand,
ClipText = true },
new VSeperator(),
new Label { new Label {
Text = player.AttachedEntity?.Name, Text = player.AttachedEntity?.Name,
SizeFlagsStretchRatio = 2f, SizeFlagsStretchRatio = 2f,
SizeFlagsHorizontal = SizeFlags.FillExpand }, SizeFlagsHorizontal = SizeFlags.FillExpand,
ClipText = true },
new VSeperator(),
new Label { new Label {
Text = player.Status.ToString(), Text = player.Status.ToString(),
SizeFlagsStretchRatio = 1f, SizeFlagsStretchRatio = 1f,
SizeFlagsHorizontal = SizeFlags.FillExpand }, SizeFlagsHorizontal = SizeFlags.FillExpand },
new VSeperator(),
new Label { new Label {
Text = player.Ping.ToString(), Text = player.Ping.ToString(),
SizeFlagsStretchRatio = 1f, SizeFlagsStretchRatio = 1f,
@@ -109,7 +170,18 @@ namespace Content.Client.UserInterface.AdminMenu
Align = Label.AlignMode.Right }, Align = Label.AlignMode.Right },
} }
}; };
PlayerList.AddChild(hbox); PlayerList.AddChild(new PanelContainer
{
PanelOverride = new StyleBoxFlat
{
BackgroundColor = useAltColor ? altColor : defaultColor,
},
Children =
{
hbox
}
});
useAltColor ^= true;
} }
} }
@@ -133,7 +205,6 @@ namespace Content.Client.UserInterface.AdminMenu
public AdminMenuWindow() //TODO: search for buttons? public AdminMenuWindow() //TODO: search for buttons?
{ {
CustomMinimumSize = (415,0);
Title = Loc.GetString("Admin Menu"); Title = Loc.GetString("Admin Menu");
#region PlayerList #region PlayerList
@@ -146,22 +217,50 @@ namespace Content.Client.UserInterface.AdminMenu
MarginBottomOverride = 4, MarginBottomOverride = 4,
CustomMinimumSize = (50, 50), CustomMinimumSize = (50, 50),
}; };
PlayerList = new VBoxContainer();
PlayerCount = new Label
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
SizeFlagsStretchRatio = 0.7f,
};
var refreshButton = new Button var refreshButton = new Button
{ {
Text = "Refresh" SizeFlagsHorizontal = SizeFlags.FillExpand,
SizeFlagsStretchRatio = 0.3f,
Text = "Refresh",
}; };
refreshButton.OnPressed += RefreshPlayerList; refreshButton.OnPressed += RefreshPlayerList;
RefreshPlayerList(null!);
PlayerList = new VBoxContainer();
var playerVBox = new VBoxContainer var playerVBox = new VBoxContainer
{ {
SizeFlagsVertical = SizeFlags.FillExpand,
Children = Children =
{ {
refreshButton, new HBoxContainer
PlayerList {
SizeFlagsHorizontal = SizeFlags.FillExpand,
Children =
{
PlayerCount,
refreshButton,
}
},
new Control { CustomMinimumSize = (0, 5) },
new ScrollContainer
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
SizeFlagsVertical = SizeFlags.FillExpand,
Children =
{
PlayerList
},
},
} }
}; };
playerTabContainer.AddChild(playerVBox); playerTabContainer.AddChild(playerVBox);
RefreshPlayerList(null!);
#endregion PlayerList #endregion PlayerList
#region Admin Tab #region Admin Tab

View File

@@ -2,12 +2,14 @@ using Content.Client.GameObjects.Components.Observer;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Content.Client.UserInterface namespace Content.Client.UserInterface
{ {
public class GhostGui : Control public class GhostGui : Control
{ {
public Button ReturnToBody = new Button(){Text = "Return to body"};
public readonly Button ReturnToBody = new Button() {Text = Loc.GetString("Return to body")};
private GhostComponent _owner; private GhostComponent _owner;
public GhostGui(GhostComponent owner) public GhostGui(GhostComponent owner)

View File

@@ -8,13 +8,12 @@ namespace Content.Client.UserInterface
/// </summary> /// </summary>
public sealed class StatusEffectsUI : Control public sealed class StatusEffectsUI : Control
{ {
public VBoxContainer VBox => _vBox; public VBoxContainer VBox { get; }
private readonly VBoxContainer _vBox;
public StatusEffectsUI() public StatusEffectsUI()
{ {
_vBox = new VBoxContainer(); VBox = new VBoxContainer();
AddChild(_vBox); AddChild(VBox);
LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.Begin); LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.Begin);
LayoutContainer.SetAnchorAndMarginPreset(this, LayoutContainer.LayoutPreset.TopRight, margin: 10); LayoutContainer.SetAnchorAndMarginPreset(this, LayoutContainer.LayoutPreset.TopRight, margin: 10);

View File

@@ -66,7 +66,7 @@ namespace Content.Client.UserInterface.Suspicion
_ => throw new ArgumentException($"Invalid number of allies: {role.Allies.Count}") _ => throw new ArgumentException($"Invalid number of allies: {role.Allies.Count}")
}; };
role.Owner.PopupMessage(role.Owner, message); role.Owner.PopupMessage(message);
} }
private bool TryGetComponent(out SuspicionRoleComponent suspicion) private bool TryGetComponent(out SuspicionRoleComponent suspicion)

View File

@@ -0,0 +1,93 @@
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Physics;
using Robust.Client.Player;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using static Content.Shared.GameObjects.EntitySystems.SharedInteractionSystem;
namespace Content.Client.Utility
{
public static class RangeExtensions
{
private static SharedInteractionSystem SharedInteractionSystem => EntitySystem.Get<SharedInteractionSystem>();
public static bool InRangeUnobstructed(
this LocalPlayer origin,
IEntity other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var otherPosition = other.Transform.MapPosition;
return origin.InRangeUnobstructed(otherPosition, range, collisionMask, predicate, ignoreInsideBlocker,
popup);
}
public static bool InRangeUnobstructed(
this LocalPlayer origin,
IComponent other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
return origin.InRangeUnobstructed(other.Owner, range, collisionMask, predicate, ignoreInsideBlocker, popup);
}
public static bool InRangeUnobstructed(
this LocalPlayer origin,
IContainer other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
return origin.InRangeUnobstructed(other.Owner, range, collisionMask, predicate, ignoreInsideBlocker, popup);
}
public static bool InRangeUnobstructed(
this LocalPlayer origin,
GridCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var mapManager = IoCManager.Resolve<IMapManager>();
var otherPosition = other.ToMap(mapManager);
return origin.InRangeUnobstructed(otherPosition, range, collisionMask, predicate, ignoreInsideBlocker,
popup);
}
public static bool InRangeUnobstructed(
this LocalPlayer origin,
MapCoordinates other,
float range = InteractionRange,
CollisionGroup collisionMask = CollisionGroup.Impassable,
Ignored predicate = null,
bool ignoreInsideBlocker = false,
bool popup = false)
{
var originEntity = origin.ControlledEntity;
if (originEntity == null)
{
// TODO: Take into account the player's camera position?
return false;
}
return SharedInteractionSystem.InRangeUnobstructed(originEntity, other, range, collisionMask, predicate,
ignoreInsideBlocker, popup);
}
}
}

View File

@@ -1,10 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
using Robust.Server.Interfaces.Maps;
using Robust.Server.Interfaces.Timing; using Robust.Server.Interfaces.Timing;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
@@ -26,11 +24,13 @@ namespace Content.IntegrationTests.Tests
{ {
var server = StartServerDummyTicker(); var server = StartServerDummyTicker();
await server.WaitIdleAsync(); await server.WaitIdleAsync();
var mapMan = server.ResolveDependency<IMapManager>();
var mapManager = server.ResolveDependency<IMapManager>();
var entityMan = server.ResolveDependency<IEntityManager>(); var entityMan = server.ResolveDependency<IEntityManager>();
var prototypeMan = server.ResolveDependency<IPrototypeManager>(); var prototypeMan = server.ResolveDependency<IPrototypeManager>();
var mapLoader = server.ResolveDependency<IMapLoader>(); var pauseManager = server.ResolveDependency<IPauseManager>();
var pauseMan = server.ResolveDependency<IPauseManager>(); var tileDefinitionManager = server.ResolveDependency<ITileDefinitionManager>();
var prototypes = new List<EntityPrototype>(); var prototypes = new List<EntityPrototype>();
IMapGrid grid = default; IMapGrid grid = default;
IEntity testEntity; IEntity testEntity;
@@ -38,9 +38,25 @@ namespace Content.IntegrationTests.Tests
//Build up test environment //Build up test environment
server.Post(() => server.Post(() =>
{ {
var mapId = mapMan.CreateMap(); // Create a one tile grid to stave off the grid 0 monsters
pauseMan.AddUninitializedMap(mapId); var mapId = mapManager.CreateMap();
grid = mapLoader.LoadBlueprint(mapId, "Maps/stationstation.yml");
pauseManager.AddUninitializedMap(mapId);
var gridId = new GridId(1);
if (!mapManager.TryGetGrid(gridId, out grid))
{
grid = mapManager.CreateGrid(mapId, gridId);
}
var tileDefinition = tileDefinitionManager["underplating"];
var tile = new Tile(tileDefinition.TileId);
var coordinates = new GridCoordinates(0, 0, gridId);
grid.SetTile(coordinates, tile);
pauseManager.DoMapInitialize(mapId);
}); });
server.Assert(() => server.Assert(() =>
@@ -54,6 +70,7 @@ namespace Content.IntegrationTests.Tests
{ {
continue; continue;
} }
prototypes.Add(prototype); prototypes.Add(prototype);
} }
@@ -91,7 +108,8 @@ namespace Content.IntegrationTests.Tests
continue; continue;
} }
Assert.That(prototype.Components.ContainsKey("Icon"), $"Entity {prototype.ID} does not have an Icon component, but is not abstract"); Assert.That(prototype.Components.ContainsKey("Icon"),
$"Entity {prototype.ID} does not have an Icon component, but is not abstract");
} }
}); });
@@ -115,28 +133,37 @@ namespace Content.IntegrationTests.Tests
- type: entity - type: entity
id: AllComponentsOneToOneDeleteTestEntity"; id: AllComponentsOneToOneDeleteTestEntity";
var server = StartServerDummyTicker(); var server = StartServerDummyTicker(new ServerContentIntegrationOption {ExtraPrototypes = testEntity});
await server.WaitIdleAsync(); await server.WaitIdleAsync();
var mapManager = server.ResolveDependency<IMapManager>(); var mapManager = server.ResolveDependency<IMapManager>();
var entityManager = server.ResolveDependency<IEntityManager>(); var entityManager = server.ResolveDependency<IEntityManager>();
var mapLoader = server.ResolveDependency<IMapLoader>();
var pauseManager = server.ResolveDependency<IPauseManager>(); var pauseManager = server.ResolveDependency<IPauseManager>();
var componentFactory = server.ResolveDependency<IComponentFactory>(); var componentFactory = server.ResolveDependency<IComponentFactory>();
var prototypeManager = server.ResolveDependency<IPrototypeManager>(); var tileDefinitionManager = server.ResolveDependency<ITileDefinitionManager>();
IMapGrid grid = default; IMapGrid grid = default;
server.Post(() => server.Post(() =>
{ {
// Load test entity // Create a one tile grid to stave off the grid 0 monsters
using var reader = new StringReader(testEntity);
prototypeManager.LoadFromStream(reader);
// Load test map
var mapId = mapManager.CreateMap(); var mapId = mapManager.CreateMap();
pauseManager.AddUninitializedMap(mapId); pauseManager.AddUninitializedMap(mapId);
grid = mapLoader.LoadBlueprint(mapId, "Maps/stationstation.yml");
var gridId = new GridId(1);
if (!mapManager.TryGetGrid(gridId, out grid))
{
grid = mapManager.CreateGrid(mapId, gridId);
}
var tileDefinition = tileDefinitionManager["underplating"];
var tile = new Tile(tileDefinition.TileId);
var coordinates = new GridCoordinates(0, 0, gridId);
grid.SetTile(coordinates, tile);
pauseManager.DoMapInitialize(mapId); pauseManager.DoMapInitialize(mapId);
}); });
@@ -201,28 +228,37 @@ namespace Content.IntegrationTests.Tests
- type: entity - type: entity
id: AllComponentsOneEntityDeleteTestEntity"; id: AllComponentsOneEntityDeleteTestEntity";
var server = StartServerDummyTicker(); var server = StartServerDummyTicker(new ServerContentIntegrationOption {ExtraPrototypes = testEntity});
await server.WaitIdleAsync(); await server.WaitIdleAsync();
var mapManager = server.ResolveDependency<IMapManager>(); var mapManager = server.ResolveDependency<IMapManager>();
var entityManager = server.ResolveDependency<IEntityManager>(); var entityManager = server.ResolveDependency<IEntityManager>();
var mapLoader = server.ResolveDependency<IMapLoader>();
var pauseManager = server.ResolveDependency<IPauseManager>(); var pauseManager = server.ResolveDependency<IPauseManager>();
var componentFactory = server.ResolveDependency<IComponentFactory>(); var componentFactory = server.ResolveDependency<IComponentFactory>();
var prototypeManager = server.ResolveDependency<IPrototypeManager>(); var tileDefinitionManager = server.ResolveDependency<ITileDefinitionManager>();
IMapGrid grid = default; IMapGrid grid = default;
server.Post(() => server.Post(() =>
{ {
// Load test entity // Create a one tile grid to stave off the grid 0 monsters
using var reader = new StringReader(testEntity);
prototypeManager.LoadFromStream(reader);
// Load test map
var mapId = mapManager.CreateMap(); var mapId = mapManager.CreateMap();
pauseManager.AddUninitializedMap(mapId); pauseManager.AddUninitializedMap(mapId);
grid = mapLoader.LoadBlueprint(mapId, "Maps/stationstation.yml");
var gridId = new GridId(1);
if (!mapManager.TryGetGrid(gridId, out grid))
{
grid = mapManager.CreateGrid(mapId, gridId);
}
var tileDefinition = tileDefinitionManager["underplating"];
var tile = new Tile(tileDefinition.TileId);
var coordinates = new GridCoordinates(0, 0, gridId);
grid.SetTile(coordinates, tile);
pauseManager.DoMapInitialize(mapId); pauseManager.DoMapInitialize(mapId);
}); });

View File

@@ -0,0 +1,102 @@
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Fluids;
using Content.Shared.Chemistry;
using NUnit.Framework;
using Robust.Server.Interfaces.Timing;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Map;
namespace Content.IntegrationTests.Tests.Fluids
{
[TestFixture]
[TestOf(typeof(PuddleComponent))]
public class PuddleTest : ContentIntegrationTest
{
[Test]
public async Task TilePuddleTest()
{
var server = StartServerDummyTicker();
await server.WaitIdleAsync();
var mapManager = server.ResolveDependency<IMapManager>();
var pauseManager = server.ResolveDependency<IPauseManager>();
var tileDefinitionManager = server.ResolveDependency<ITileDefinitionManager>();
GridCoordinates coordinates = default;
// Build up test environment
server.Post(() =>
{
// Create a one tile grid to spill onto
var mapId = mapManager.CreateMap();
pauseManager.AddUninitializedMap(mapId);
var gridId = new GridId(1);
if (!mapManager.TryGetGrid(gridId, out var grid))
{
grid = mapManager.CreateGrid(mapId, gridId);
}
var tileDefinition = tileDefinitionManager["underplating"];
var tile = new Tile(tileDefinition.TileId);
coordinates = new GridCoordinates(0, 0, gridId);
grid.SetTile(coordinates, tile);
pauseManager.DoMapInitialize(mapId);
});
await server.WaitIdleAsync();
server.Assert(() =>
{
var solution = new Solution("water", ReagentUnit.New(20));
var puddle = solution.SpillAt(coordinates, "PuddleSmear");
Assert.NotNull(puddle);
});
await server.WaitIdleAsync();
}
[Test]
public async Task SpaceNoPuddleTest()
{
var server = StartServerDummyTicker();
await server.WaitIdleAsync();
var mapManager = server.ResolveDependency<IMapManager>();
var pauseManager = server.ResolveDependency<IPauseManager>();
// Build up test environment
server.Post(() =>
{
var mapId = mapManager.CreateMap();
pauseManager.AddUninitializedMap(mapId);
var gridId = new GridId(1);
if (!mapManager.GridExists(gridId))
{
mapManager.CreateGrid(mapId, gridId);
}
});
await server.WaitIdleAsync();
server.Assert(() =>
{
var gridId = new GridId(1);
var coordinates = new GridCoordinates(0, 0, gridId);
var solution = new Solution("water", ReagentUnit.New(20));
var puddle = solution.SpillAt(coordinates, "PuddleSmear");
Assert.Null(puddle);
});
await server.WaitIdleAsync();
}
}
}

View File

@@ -87,7 +87,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
var slot = part.GetHashCode().ToString(); var slot = part.GetHashCode().ToString();
body.Template.Slots.Add(slot, BodyPartType.Hand); body.Template.Slots.Add(slot, BodyPartType.Hand);
body.InstallBodyPart(part, slot); body.TryAddPart(slot, part, true);
} }
} }
} }

View File

@@ -0,0 +1,159 @@
using System.Threading.Tasks;
using Content.Client.Utility;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Utility;
using NUnit.Framework;
using Robust.Server.GameObjects.Components.Container;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Map;
namespace Content.IntegrationTests.Tests.Interaction
{
[TestFixture]
[TestOf(typeof(SharedInteractionSystem))]
[TestOf(typeof(SharedRangeExtensions))]
[TestOf(typeof(RangeExtensions))]
public class InRangeUnobstructed : ContentIntegrationTest
{
private const string HumanId = "BaseHumanMob_Content";
private const float InteractionRange = SharedInteractionSystem.InteractionRange;
private const float InteractionRangeDivided15 = InteractionRange / 1.5f;
private readonly (float, float) _interactionRangeDivided15X = (InteractionRangeDivided15, 0f);
private const float InteractionRangeDivided15Times3 = InteractionRangeDivided15 * 3;
[Test]
public async Task EntityEntityTest()
{
var server = StartServerDummyTicker();
await server.WaitIdleAsync();
var entityManager = server.ResolveDependency<IEntityManager>();
var mapManager = server.ResolveDependency<IMapManager>();
IEntity origin = null;
IEntity other = null;
IContainer container = null;
IComponent component = null;
GridCoordinates gridCoordinates = default;
MapCoordinates mapCoordinates = default;
server.Assert(() =>
{
mapManager.CreateNewMapEntity(MapId.Nullspace);
var coordinates = MapCoordinates.Nullspace;
origin = entityManager.SpawnEntity(HumanId, coordinates);
other = entityManager.SpawnEntity(HumanId, coordinates);
container = ContainerManagerComponent.Ensure<Container>("InRangeUnobstructedTestOtherContainer", other);
component = other.Transform;
gridCoordinates = other.Transform.GridPosition;
mapCoordinates = other.Transform.MapPosition;
});
await server.WaitIdleAsync();
server.Assert(() =>
{
// Entity <-> Entity
Assert.True(origin.InRangeUnobstructed(other));
Assert.True(other.InRangeUnobstructed(origin));
// Entity <-> Component
Assert.True(origin.InRangeUnobstructed(component));
Assert.True(component.InRangeUnobstructed(origin));
// Entity <-> Container
Assert.True(origin.InRangeUnobstructed(container));
Assert.True(container.InRangeUnobstructed(origin));
// Entity <-> GridCoordinates
Assert.True(origin.InRangeUnobstructed(gridCoordinates));
Assert.True(gridCoordinates.InRangeUnobstructed(origin));
// Entity <-> MapCoordinates
Assert.True(origin.InRangeUnobstructed(mapCoordinates));
Assert.True(mapCoordinates.InRangeUnobstructed(origin));
// Move them slightly apart
origin.Transform.LocalPosition += _interactionRangeDivided15X;
// Entity <-> Entity
Assert.True(origin.InRangeUnobstructed(other));
Assert.True(other.InRangeUnobstructed(origin));
// Entity <-> Component
Assert.True(origin.InRangeUnobstructed(component));
Assert.True(component.InRangeUnobstructed(origin));
// Entity <-> Container
Assert.True(origin.InRangeUnobstructed(container));
Assert.True(container.InRangeUnobstructed(origin));
// Entity <-> GridCoordinates
Assert.True(origin.InRangeUnobstructed(gridCoordinates));
Assert.True(gridCoordinates.InRangeUnobstructed(origin));
// Entity <-> MapCoordinates
Assert.True(origin.InRangeUnobstructed(mapCoordinates));
Assert.True(mapCoordinates.InRangeUnobstructed(origin));
// Move them out of range
origin.Transform.LocalPosition += _interactionRangeDivided15X;
// Entity <-> Entity
Assert.False(origin.InRangeUnobstructed(other));
Assert.False(other.InRangeUnobstructed(origin));
// Entity <-> Component
Assert.False(origin.InRangeUnobstructed(component));
Assert.False(component.InRangeUnobstructed(origin));
// Entity <-> Container
Assert.False(origin.InRangeUnobstructed(container));
Assert.False(container.InRangeUnobstructed(origin));
// Entity <-> GridCoordinates
Assert.False(origin.InRangeUnobstructed(gridCoordinates));
Assert.False(gridCoordinates.InRangeUnobstructed(origin));
// Entity <-> MapCoordinates
Assert.False(origin.InRangeUnobstructed(mapCoordinates));
Assert.False(mapCoordinates.InRangeUnobstructed(origin));
// Checks with increased range
// Entity <-> Entity
Assert.True(origin.InRangeUnobstructed(other, InteractionRangeDivided15Times3));
Assert.True(other.InRangeUnobstructed(origin, InteractionRangeDivided15Times3));
// Entity <-> Component
Assert.True(origin.InRangeUnobstructed(component, InteractionRangeDivided15Times3));
Assert.True(component.InRangeUnobstructed(origin, InteractionRangeDivided15Times3));
// Entity <-> Container
Assert.True(origin.InRangeUnobstructed(container, InteractionRangeDivided15Times3));
Assert.True(container.InRangeUnobstructed(origin, InteractionRangeDivided15Times3));
// Entity <-> GridCoordinates
Assert.True(origin.InRangeUnobstructed(gridCoordinates, InteractionRangeDivided15Times3));
Assert.True(gridCoordinates.InRangeUnobstructed(origin, InteractionRangeDivided15Times3));
// Entity <-> MapCoordinates
Assert.True(origin.InRangeUnobstructed(mapCoordinates, InteractionRangeDivided15Times3));
Assert.True(mapCoordinates.InRangeUnobstructed(origin, InteractionRangeDivided15Times3));
});
await server.WaitIdleAsync();
}
}
}

View File

@@ -0,0 +1,60 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using Robust.Shared.Interfaces.Resources;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.IntegrationTests.Tests
{
[TestFixture]
public class PostMapInitTest : ContentIntegrationTest
{
public readonly string[] SkippedMaps =
{
"/Maps/Pathfinding/simple.yml"
};
[Test]
public async Task NoSavedPostMapInitTest()
{
var server = StartServerDummyTicker();
await server.WaitIdleAsync();
var resourceManager = server.ResolveDependency<IResourceManager>();
var mapFolder = new ResourcePath("/Maps");
var maps = resourceManager
.ContentFindFiles(mapFolder)
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith("."))
.ToArray();
foreach (var map in maps)
{
var rootedPath = map.ToRootedPath();
if (SkippedMaps.Contains(rootedPath.ToString()))
{
continue;
}
if (!resourceManager.TryContentFileRead(rootedPath, out var fileStream))
{
Assert.Fail($"Map not found: {rootedPath}");
}
using var reader = new StreamReader(fileStream);
var yamlStream = new YamlStream();
yamlStream.Load(reader);
var root = yamlStream.Documents[0].RootNode;
var meta = root["meta"];
var postMapInit = meta["postmapinit"].AsBool();
Assert.False(postMapInit);
}
}
}
}

View File

@@ -3,6 +3,7 @@ using Content.Server.AI.WorldState.States.Inventory;
using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.Utility; using Content.Server.Utility;
using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.AI.Operators.Inventory namespace Content.Server.AI.Operators.Inventory
@@ -50,7 +51,7 @@ namespace Content.Server.AI.Operators.Inventory
public override Outcome Execute(float frameTime) public override Outcome Execute(float frameTime)
{ {
if (!InteractionChecks.InRangeUnobstructed(_owner, _target.Transform.MapPosition)) if (!_owner.InRangeUnobstructed(_target, popup: true))
{ {
return Outcome.Failed; return Outcome.Failed;
} }

View File

@@ -1,6 +1,7 @@
using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.EntitySystems.Click; using Content.Server.GameObjects.EntitySystems.Click;
using Content.Server.Utility; using Content.Server.Utility;
using Content.Shared.Utility;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
@@ -28,7 +29,7 @@ namespace Content.Server.AI.Operators.Inventory
return Outcome.Failed; return Outcome.Failed;
} }
if (!InteractionChecks.InRangeUnobstructed(_owner, _useTarget.Transform.MapPosition)) if (!_owner.InRangeUnobstructed(_useTarget, popup: true))
{ {
return Outcome.Failed; return Outcome.Failed;
} }

View File

@@ -4,6 +4,7 @@ using Content.Server.AI.WorldState.States.Inventory;
using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.Utility; using Content.Server.Utility;
using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
@@ -30,7 +31,7 @@ namespace Content.Server.AI.Operators.Inventory
return Outcome.Success; return Outcome.Success;
} }
if (!InteractionChecks.InRangeUnobstructed(_owner, container.Owner.Transform.MapPosition, ignoredEnt: container.Owner)) if (!_owner.InRangeUnobstructed(container, popup: true))
{ {
return Outcome.Failed; return Outcome.Failed;
} }

View File

@@ -3,6 +3,7 @@ using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.EntitySystems.Click; using Content.Server.GameObjects.EntitySystems.Click;
using Content.Server.Utility; using Content.Server.Utility;
using Content.Shared.Utility;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
@@ -26,7 +27,7 @@ namespace Content.Server.AI.Operators.Inventory
if (_target.Deleted || if (_target.Deleted ||
!_target.HasComponent<ItemComponent>() || !_target.HasComponent<ItemComponent>() ||
ContainerHelpers.IsInContainer(_target) || ContainerHelpers.IsInContainer(_target) ||
!InteractionChecks.InRangeUnobstructed(_owner, _target.Transform.MapPosition)) !_owner.InRangeUnobstructed(_target, popup: true))
{ {
return Outcome.Failed; return Outcome.Failed;
} }

View File

@@ -15,7 +15,7 @@ namespace Content.Server.AI.Utility.Considerations.Combat.Melee
return 0.0f; return 0.0f;
} }
return meleeWeaponComponent.CooldownTime / 10.0f; return meleeWeaponComponent.ArcCooldownTime / 10.0f;
} }
} }
} }

View File

@@ -21,7 +21,7 @@ namespace Content.Server.AI.WorldState.States.Mobs
return result; return result;
} }
foreach (var entity in Visibility.GetEntitiesInRange(Owner.Transform.GridPosition, typeof(IBodyManagerComponent), controller.VisionRadius)) foreach (var entity in Visibility.GetEntitiesInRange(Owner.Transform.GridPosition, typeof(ISharedBodyManagerComponent), controller.VisionRadius))
{ {
if (entity == Owner) continue; if (entity == Owner) continue;
result.Add(entity); result.Add(entity);

View File

@@ -0,0 +1,39 @@
#nullable enable
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.Administration
{
public class DeleteEntitiesWithId : IClientCommand
{
public string Command => "deleteewi";
public string Description => "Deletes entities with the specified prototype ID.";
public string Help => $"Usage: {Command} <prototypeID>";
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
if (args.Length != 1)
{
shell.SendText(player, Help);
return;
}
var id = args[0].ToLower();
var entityManager = IoCManager.Resolve<IEntityManager>();
var query = new PredicateEntityQuery(e => e.Prototype?.ID.ToLower() == id);
var entities = entityManager.GetEntities(query);
var i = 0;
foreach (var entity in entities)
{
entity.Delete();
i++;
}
shell.SendText(player, $"Deleted all entities with id {id}. Occurrences: {i}");
}
}
}

View File

@@ -17,11 +17,21 @@ namespace Content.Server.Atmos
{ {
public string Command => "addatmos"; public string Command => "addatmos";
public string Description => "Adds atmos support to a grid."; public string Description => "Adds atmos support to a grid.";
public string Help => "addatmos <GridId>"; public string Help => $"{Command} <GridId>";
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args) public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{ {
if (args.Length < 1) return; if (args.Length < 1)
if(!int.TryParse(args[0], out var id)) return; {
shell.SendText(player, Help);
return;
}
if (!int.TryParse(args[0], out var id))
{
shell.SendText(player, $"{args[0]} is not a valid integer.");
return;
}
var gridId = new GridId(id); var gridId = new GridId(id);
@@ -29,7 +39,7 @@ namespace Content.Server.Atmos
if (!gridId.IsValid() || !mapMan.TryGetGrid(gridId, out var gridComp)) if (!gridId.IsValid() || !mapMan.TryGetGrid(gridId, out var gridComp))
{ {
shell.SendText(player, "Invalid grid ID."); shell.SendText(player, $"{gridId} is not a valid grid id.");
return; return;
} }
@@ -41,7 +51,7 @@ namespace Content.Server.Atmos
return; return;
} }
if (grid.HasComponent<GridAtmosphereComponent>()) if (grid.HasComponent<IGridAtmosphereComponent>())
{ {
shell.SendText(player, "Grid already has an atmosphere."); shell.SendText(player, "Grid already has an atmosphere.");
return; return;
@@ -53,6 +63,56 @@ namespace Content.Server.Atmos
} }
} }
public class AddUnsimulatedAtmos : IClientCommand
{
public string Command => "addunsimulatedatmos";
public string Description => "Adds unimulated atmos support to a grid.";
public string Help => $"{Command} <GridId>";
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
if (args.Length < 1)
{
shell.SendText(player, Help);
return;
}
if (!int.TryParse(args[0], out var id))
{
shell.SendText(player, $"{args[0]} is not a valid integer.");
return;
}
var gridId = new GridId(id);
var mapMan = IoCManager.Resolve<IMapManager>();
if (!gridId.IsValid() || !mapMan.TryGetGrid(gridId, out var gridComp))
{
shell.SendText(player, $"{gridId} is not a valid grid id.");
return;
}
var entMan = IoCManager.Resolve<IEntityManager>();
if (!entMan.TryGetEntity(gridComp.GridEntityId, out var grid))
{
shell.SendText(player, "Failed to get grid entity.");
return;
}
if (grid.HasComponent<IGridAtmosphereComponent>())
{
shell.SendText(player, "Grid already has an atmosphere.");
return;
}
grid.AddComponent<UnsimulatedGridAtmosphereComponent>();
shell.SendText(player, $"Added unsimulated atmosphere to grid {id}.");
}
}
public class ListGases : IClientCommand public class ListGases : IClientCommand
{ {
public string Command => "listgases"; public string Command => "listgases";

View File

@@ -105,6 +105,7 @@ namespace Content.Server.Atmos
{ {
if (tile?.Air == null) continue; if (tile?.Air == null) continue;
tile.Air.CopyFromMutable(combined); tile.Air.CopyFromMutable(combined);
tile.AtmosCooldown = 0;
tile.UpdateVisuals(); tile.UpdateVisuals();
} }
@@ -131,7 +132,7 @@ namespace Content.Server.Atmos
_disposed = true; _disposed = true;
_gridAtmosphereComponent.RemoveExcitedGroup(this); _gridAtmosphereComponent.RemoveExcitedGroup(this);
Dismantle(); Dismantle(false);
_gridAtmosphereComponent = null; _gridAtmosphereComponent = null;
} }

View File

@@ -1,7 +1,7 @@
using Content.Server.GameObjects.Components.Chemistry; using Content.Server.GameObjects.Components.Chemistry;
using Content.Server.Interfaces;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.Components;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems; using Robust.Server.GameObjects.EntitySystems;
@@ -13,13 +13,11 @@ using Robust.Shared.Localization;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Server.Atmos namespace Content.Server.Atmos
{ {
[RegisterComponent] [RegisterComponent]
public class GasSprayerComponent : Component, IAfterInteract public class GasSprayerComponent : Component, IAfterInteract
{ {
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IServerEntityManager _serverEntityManager = default!; [Dependency] private readonly IServerEntityManager _serverEntityManager = default!;
//TODO: create a function that can create a gas based on a solution mix //TODO: create a function that can create a gas based on a solution mix
@@ -48,7 +46,7 @@ namespace Content.Server.Atmos
if (tank.Solution.GetReagentQuantity(_fuelType) == 0) if (tank.Solution.GetReagentQuantity(_fuelType) == 0)
{ {
_notifyManager.PopupMessage(Owner, eventArgs.User, Owner.PopupMessage(eventArgs.User,
Loc.GetString("{0:theName} is out of {1}!", Owner, _fuelName)); Loc.GetString("{0:theName} is out of {1}!", Owner, _fuelName));
} }
else else

View File

@@ -9,6 +9,7 @@ using Content.Server.Interfaces;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.Maps; using Content.Shared.Maps;
using JetBrains.Annotations;
using Robust.Server.GameObjects.EntitySystems; using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -40,6 +41,9 @@ namespace Content.Server.Atmos
[ViewVariables] [ViewVariables]
private static GasTileOverlaySystem _gasTileOverlaySystem; private static GasTileOverlaySystem _gasTileOverlaySystem;
[ViewVariables]
public int AtmosCooldown { get; set; } = 0;
[ViewVariables] [ViewVariables]
private float _temperature = Atmospherics.T20C; private float _temperature = Atmospherics.T20C;
@@ -73,9 +77,11 @@ namespace Content.Server.Atmos
[ViewVariables] [ViewVariables]
private readonly TileAtmosphere[] _adjacentTiles = new TileAtmosphere[Atmospherics.Directions]; private readonly TileAtmosphere[] _adjacentTiles = new TileAtmosphere[Atmospherics.Directions];
[ViewVariables]
private AtmosDirection _adjacentBits = AtmosDirection.Invalid; private AtmosDirection _adjacentBits = AtmosDirection.Invalid;
[ViewVariables, UsedImplicitly]
private int AdjacentBitsInt => (int)_adjacentBits;
[ViewVariables] [ViewVariables]
private TileAtmosInfo _tileAtmosInfo; private TileAtmosInfo _tileAtmosInfo;
@@ -84,6 +90,9 @@ namespace Content.Server.Atmos
private AtmosDirection _pressureDirection; private AtmosDirection _pressureDirection;
[ViewVariables, UsedImplicitly]
private int PressureDirectionInt => (int)_pressureDirection;
[ViewVariables] [ViewVariables]
public GridId GridIndex { get; } public GridId GridIndex { get; }
@@ -635,6 +644,15 @@ namespace Content.Server.Atmos
_currentCycle = fireCount; _currentCycle = fireCount;
var adjacentTileLength = 0; var adjacentTileLength = 0;
AtmosCooldown++;
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
if(_adjacentBits.HasFlag(direction))
adjacentTileLength++;
}
for(var i = 0; i < Atmospherics.Directions; i++) for(var i = 0; i < Atmospherics.Directions; i++)
{ {
var direction = (AtmosDirection) (1 << i); var direction = (AtmosDirection) (1 << i);
@@ -643,7 +661,6 @@ namespace Content.Server.Atmos
// If the tile is null or has no air, we don't do anything for it. // If the tile is null or has no air, we don't do anything for it.
if(enemyTile?.Air == null) continue; if(enemyTile?.Air == null) continue;
adjacentTileLength++;
if (fireCount <= enemyTile._currentCycle) continue; if (fireCount <= enemyTile._currentCycle) continue;
enemyTile.Archive(fireCount); enemyTile.Archive(fireCount);
@@ -703,7 +720,13 @@ namespace Content.Server.Atmos
React(); React();
UpdateVisuals(); UpdateVisuals();
if((!(Air.Temperature > Atmospherics.MinimumTemperatureStartSuperConduction && ConsiderSuperconductivity(true))) && ExcitedGroup == null) var remove = true;
if(Air.Temperature > Atmospherics.MinimumTemperatureStartSuperConduction)
if (ConsiderSuperconductivity(true))
remove = false;
if((ExcitedGroup == null && remove) || (AtmosCooldown > (Atmospherics.ExcitedGroupsDismantleCycles * 2)))
_gridAtmosphereComponent.RemoveActiveTile(this); _gridAtmosphereComponent.RemoveActiveTile(this);
} }
@@ -1143,9 +1166,11 @@ namespace Content.Server.Atmos
if (lastShare > Atmospherics.MinimumAirToSuspend) if (lastShare > Atmospherics.MinimumAirToSuspend)
{ {
ExcitedGroup.ResetCooldowns(); ExcitedGroup.ResetCooldowns();
AtmosCooldown = 0;
} else if (lastShare > Atmospherics.MinimumMolesDeltaToMove) } else if (lastShare > Atmospherics.MinimumMolesDeltaToMove)
{ {
ExcitedGroup.DismantleCooldown = 0; ExcitedGroup.DismantleCooldown = 0;
AtmosCooldown = 0;
} }
} }

View File

@@ -1,14 +1,19 @@
#nullable enable #nullable enable
using System.Linq;
using Content.Server.GameObjects.Components.Body; using Content.Server.GameObjects.Components.Body;
using Content.Shared.Body.Part; using Content.Shared.Body.Part;
using Content.Shared.Damage;
using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Body;
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;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random; using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using System;
using System.Linq;
namespace Content.Server.Body namespace Content.Server.Body
{ {
@@ -48,7 +53,7 @@ namespace Content.Server.Body
var slot = part.GetHashCode().ToString(); var slot = part.GetHashCode().ToString();
body.Template.Slots.Add(slot, BodyPartType.Hand); body.Template.Slots.Add(slot, BodyPartType.Hand);
body.InstallBodyPart(part, slot); body.TryAddPart(slot, part, true);
} }
} }
@@ -144,4 +149,89 @@ namespace Content.Server.Body
shell.SendText(player, $"No mechanism was found with name {mechanismName}."); shell.SendText(player, $"No mechanism was found with name {mechanismName}.");
} }
} }
class HurtCommand : IClientCommand
{
public string Command => "hurt";
public string Description => "Ouch";
public string Help => $"Usage: {Command} <type> <amount> (<entity uid/_>) (<ignoreResistance>)";
private void SendDamageTypes(IConsoleShell shell, IPlayerSession? player)
{
var msg = "";
foreach (var dClass in Enum.GetNames(typeof(DamageClass)))
{
msg += $"\n{dClass}";
var types = Enum.Parse<DamageClass>(dClass).ToTypes();
foreach (var dType in types)
{
msg += $"\n - {dType}";
}
}
shell.SendText(player, $"Damage Types:{msg}");
}
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
// Check if we have enough for the dmg types to show
if (args.Length > 0 && args[0] == "?")
{
SendDamageTypes(shell, player);
return;
}
// Not enough args
if (args.Length < 2)
{
shell.SendText(player, Help);
return;
}
var ignoreResistance = false;
var entityUid = player != null && player.AttachedEntityUid.HasValue ? player.AttachedEntityUid.Value : EntityUid.Invalid;
if (!int.TryParse(args[1], out var amount) ||
args.Length >= 3 && args[2] != "_" && !EntityUid.TryParse(args[2], out entityUid) ||
args.Length >= 4 && !bool.TryParse(args[3], out ignoreResistance))
{
shell.SendText(player, Help);
return;
}
if (entityUid == EntityUid.Invalid)
{
shell.SendText(player, "Not a valid entity.");
return;
}
if (!IoCManager.Resolve<IEntityManager>().TryGetEntity(entityUid, out var ent))
{
shell.SendText(player, "Entity couldn't be found.");
return;
}
if (!ent.TryGetComponent(out IDamageableComponent? damageable))
{
shell.SendText(player, "Entity can't be damaged.");
return;
}
if (Enum.TryParse<DamageClass>(args[0], true, out var dmgClass))
{
if (!damageable.ChangeDamage(dmgClass, amount, ignoreResistance))
shell.SendText(player, "Something went wrong!");
return;
}
// Fall back to DamageType
else if (Enum.TryParse<DamageType>(args[0], true, out var dmgType))
{
if (!damageable.ChangeDamage(dmgType, amount, ignoreResistance))
shell.SendText(player, "Something went wrong!");
return;
}
else
{
SendDamageTypes(shell, player);
}
}
}
} }

View File

@@ -14,13 +14,11 @@ using Content.Shared.Damage.DamageContainer;
using Content.Shared.Damage.ResistanceSet; using Content.Shared.Damage.ResistanceSet;
using Content.Shared.GameObjects.Components.Body; using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Damage;
using Robust.Server.GameObjects;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Reflection; using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.Interfaces.Serialization; using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Log; using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -34,20 +32,11 @@ namespace Content.Server.Body
/// which coordinates functions between BodyParts, or a /// which coordinates functions between BodyParts, or a
/// <see cref="DroppedBodyPartComponent"/>. /// <see cref="DroppedBodyPartComponent"/>.
/// </summary> /// </summary>
public class BodyPart public class BodyPart : IBodyPart
{ {
/// <summary> private IBodyManagerComponent? _body;
/// The body that this body part is in, if any.
/// </summary>
private BodyManagerComponent? _body;
/// <summary> private readonly HashSet<IMechanism> _mechanisms = new HashSet<IMechanism>();
/// Set of all <see cref="Mechanism"/> currently inside this
/// <see cref="BodyPart"/>.
/// To add and remove from this list see <see cref="AddMechanism"/> and
/// <see cref="RemoveMechanism"/>
/// </summary>
private readonly HashSet<Mechanism> _mechanisms = new HashSet<Mechanism>();
public BodyPart(BodyPartPrototype data) public BodyPart(BodyPartPrototype data)
{ {
@@ -64,11 +53,8 @@ namespace Content.Server.Body
LoadFromPrototype(data); LoadFromPrototype(data);
} }
/// <summary>
/// The body that this body part is in, if any.
/// </summary>
[ViewVariables] [ViewVariables]
public BodyManagerComponent? Body public IBodyManagerComponent? Body
{ {
get => _body; get => _body;
set set
@@ -111,91 +97,48 @@ namespace Content.Server.Body
[ViewVariables] [ViewVariables]
private HashSet<IExposeData> Properties { get; } private HashSet<IExposeData> Properties { get; }
/// <summary> [ViewVariables] public string Name { get; private set; }
/// The name of this <see cref="BodyPart"/>, often displayed to the user.
/// For example, it could be named "advanced robotic arm".
/// </summary>
[ViewVariables]
public string Name { get; private set; }
/// <summary> [ViewVariables] public string Plural { get; private set; }
/// Plural version of this <see cref="BodyPart"/> name.
/// </summary>
[ViewVariables]
public string Plural { get; private set; }
/// <summary> [ViewVariables] public string RSIPath { get; private set; }
/// Path to the RSI that represents this <see cref="BodyPart"/>.
/// </summary>
[ViewVariables]
public string RSIPath { get; private set; }
/// <summary> [ViewVariables] public string RSIState { get; private set; }
/// RSI state that represents this <see cref="BodyPart"/>.
/// </summary>
[ViewVariables]
public string RSIState { get; private set; }
/// <summary> [ViewVariables] public Enum? RSIMap { get; set; }
/// RSI map keys that this body part changes on the sprite.
/// </summary>
[ViewVariables]
public Enum? RSIMap { get; set; }
/// <summary>
/// RSI color of this body part.
/// </summary>
// TODO: SpriteComponent rework // TODO: SpriteComponent rework
public Color? RSIColor { get; set; } [ViewVariables] public Color? RSIColor { get; set; }
/// <summary> [ViewVariables] public BodyPartType PartType { get; private set; }
/// <see cref="BodyPartType"/> that this <see cref="BodyPart"/> is considered
/// to be.
/// For example, <see cref="BodyPartType.Arm"/>.
/// </summary>
[ViewVariables]
public BodyPartType PartType { get; private set; }
/// <summary> [ViewVariables] public int Size { get; private set; }
/// Determines many things: how many mechanisms can be fit inside this
/// <see cref="BodyPart"/>, whether a body can fit through tiny crevices, etc.
/// </summary>
[ViewVariables]
private int Size { get; set; }
/// <summary> [ViewVariables] public int MaxDurability { get; private set; }
/// Max HP of this <see cref="BodyPart"/>.
/// </summary>
[ViewVariables]
public int MaxDurability { get; private set; }
/// <summary> [ViewVariables] public int CurrentDurability => MaxDurability - Damage.TotalDamage;
/// Current HP of this <see cref="BodyPart"/> based on sum of all damage types.
/// </summary>
[ViewVariables]
public int CurrentDurability => MaxDurability - Damage.TotalDamage;
// TODO: Individual body part damage // TODO: Individual body part damage
/// <summary> /// <summary>
/// Current damage dealt to this <see cref="BodyPart"/>. /// Current damage dealt to this <see cref="IBodyPart"/>.
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public DamageContainer Damage { get; private set; } public DamageContainer Damage { get; private set; }
/// <summary> /// <summary>
/// Armor of this <see cref="BodyPart"/> against damages. /// Armor of this <see cref="IBodyPart"/> against damages.
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public ResistanceSet Resistances { get; private set; } public ResistanceSet Resistances { get; private set; }
/// <summary> /// <summary>
/// At what HP this <see cref="BodyPart"/> destroyed. /// At what HP this <see cref="IBodyPart"/> destroyed.
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public int DestroyThreshold { get; private set; } public int DestroyThreshold { get; private set; }
/// <summary> /// <summary>
/// What types of BodyParts this <see cref="BodyPart"/> can easily attach to. /// 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 /// For the most part, most limbs aren't universal and require extra work to
/// attach between types. /// attach between types.
/// </summary> /// </summary>
@@ -203,15 +146,23 @@ namespace Content.Server.Body
public BodyPartCompatibility Compatibility { get; private set; } public BodyPartCompatibility Compatibility { get; private set; }
/// <summary> /// <summary>
/// Set of all <see cref="Mechanism"/> currently inside this /// Set of all <see cref="IMechanism"/> currently inside this
/// <see cref="BodyPart"/>. /// <see cref="IBodyPart"/>.
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public IReadOnlyCollection<Mechanism> Mechanisms => _mechanisms; public IReadOnlyCollection<IMechanism> Mechanisms => _mechanisms;
/// <summary> /// <summary>
/// This method is called by <see cref="BodyManagerComponent.Update"/> /// Represents if body part is vital for creature.
/// before <see cref="MetabolismComponent.Update"/> is called. /// 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> /// </summary>
public void PreMetabolism(float frameTime) public void PreMetabolism(float frameTime)
{ {
@@ -222,8 +173,9 @@ namespace Content.Server.Body
} }
/// <summary> /// <summary>
/// This method is called by <see cref="BodyManagerComponent.Update"/> /// This method is called by
/// after <see cref="MetabolismComponent.Update"/> is called. /// <see cref="IBodyManagerComponent.PostMetabolism"/> after
/// <see cref="MetabolismComponent.Update"/> is called.
/// </summary> /// </summary>
public void PostMetabolism(float frameTime) public void PostMetabolism(float frameTime)
{ {
@@ -258,9 +210,10 @@ namespace Content.Server.Body
/// <param name="property">The property if found, null otherwise.</param> /// <param name="property">The property if found, null otherwise.</param>
/// <typeparam name="T">The type of the property to find.</typeparam> /// <typeparam name="T">The type of the property to find.</typeparam>
/// <returns>True if successful, false otherwise.</returns> /// <returns>True if successful, false otherwise.</returns>
public bool TryGetProperty<T>(out T property) public bool TryGetProperty<T>([NotNullWhen(true)] out T? property) where T : BodyPartProperty
{ {
property = (T) Properties.First(x => x.GetType() == typeof(T)); property = (T?) Properties.FirstOrDefault(x => x.GetType() == typeof(T));
return property != null; return property != null;
} }
@@ -269,20 +222,21 @@ namespace Content.Server.Body
/// The resulting <see cref="BodyPartProperty"/> will be null if unsuccessful. /// The resulting <see cref="BodyPartProperty"/> will be null if unsuccessful.
/// </summary> /// </summary>
/// <returns>True if successful, false otherwise.</returns> /// <returns>True if successful, false otherwise.</returns>
public bool TryGetProperty(Type propertyType, out BodyPartProperty property) public bool TryGetProperty(Type propertyType, [NotNullWhen(true)] out BodyPartProperty? property)
{ {
property = (BodyPartProperty) Properties.First(x => x.GetType() == propertyType); property = (BodyPartProperty?) Properties.First(x => x.GetType() == propertyType);
return property != null; return property != null;
} }
/// <summary> /// <summary>
/// Checks if the given type <see cref="T"/> is on this <see cref="BodyPart"/>. /// Checks if the given type <see cref="T"/> is on this <see cref="IBodyPart"/>.
/// </summary> /// </summary>
/// <typeparam name="T"> /// <typeparam name="T">
/// The subtype of <see cref="BodyPartProperty"/> to look for. /// The subtype of <see cref="BodyPartProperty"/> to look for.
/// </typeparam> /// </typeparam>
/// <returns> /// <returns>
/// True if this <see cref="BodyPart"/> has a property of type /// True if this <see cref="IBodyPart"/> has a property of type
/// <see cref="T"/>, false otherwise. /// <see cref="T"/>, false otherwise.
/// </returns> /// </returns>
public bool HasProperty<T>() where T : BodyPartProperty public bool HasProperty<T>() where T : BodyPartProperty
@@ -292,13 +246,13 @@ namespace Content.Server.Body
/// <summary> /// <summary>
/// Checks if a subtype of <see cref="BodyPartProperty"/> is on this /// Checks if a subtype of <see cref="BodyPartProperty"/> is on this
/// <see cref="BodyPart"/>. /// <see cref="IBodyPart"/>.
/// </summary> /// </summary>
/// <param name="propertyType"> /// <param name="propertyType">
/// The subtype of <see cref="BodyPartProperty"/> to look for. /// The subtype of <see cref="BodyPartProperty"/> to look for.
/// </param> /// </param>
/// <returns> /// <returns>
/// True if this <see cref="BodyPart"/> has a property of type /// True if this <see cref="IBodyPart"/> has a property of type
/// <see cref="propertyType"/>, false otherwise. /// <see cref="propertyType"/>, false otherwise.
/// </returns> /// </returns>
public bool HasProperty(Type propertyType) public bool HasProperty(Type propertyType)
@@ -306,22 +260,12 @@ namespace Content.Server.Body
return Properties.Count(x => x.GetType() == propertyType) > 0; return Properties.Count(x => x.GetType() == propertyType) > 0;
} }
/// <summary> public bool CanAttachPart(IBodyPart part)
/// Checks if another <see cref="BodyPart"/> can be connected to this one.
/// </summary>
/// <param name="toBeConnected">The part to connect.</param>
/// <returns>True if it can be connected, false otherwise.</returns>
public bool CanAttachBodyPart(BodyPart toBeConnected)
{ {
return SurgeryData.CanAttachBodyPart(toBeConnected); return SurgeryData.CanAttachBodyPart(part);
} }
/// <summary> public bool CanInstallMechanism(IMechanism mechanism)
/// Checks if a <see cref="Mechanism"/> can be installed on this
/// <see cref="BodyPart"/>.
/// </summary>
/// <returns>True if it can be installed, false otherwise.</returns>
public bool CanInstallMechanism(Mechanism mechanism)
{ {
return SizeUsed + mechanism.Size <= Size && return SizeUsed + mechanism.Size <= Size &&
SurgeryData.CanInstallMechanism(mechanism); SurgeryData.CanInstallMechanism(mechanism);
@@ -336,9 +280,9 @@ namespace Content.Server.Body
/// <param name="mechanism">The mechanism to try to install.</param> /// <param name="mechanism">The mechanism to try to install.</param>
/// <returns> /// <returns>
/// True if successful, false if there was an error /// True if successful, false if there was an error
/// (e.g. not enough room in <see cref="BodyPart"/>). /// (e.g. not enough room in <see cref="IBodyPart"/>).
/// </returns> /// </returns>
private bool TryInstallMechanism(Mechanism mechanism) private bool TryInstallMechanism(IMechanism mechanism)
{ {
if (!CanInstallMechanism(mechanism)) if (!CanInstallMechanism(mechanism))
{ {
@@ -352,7 +296,7 @@ namespace Content.Server.Body
/// <summary> /// <summary>
/// Tries to install a <see cref="DroppedMechanismComponent"/> into this /// Tries to install a <see cref="DroppedMechanismComponent"/> into this
/// <see cref="BodyPart"/>, potentially deleting the dropped /// <see cref="IBodyPart"/>, potentially deleting the dropped
/// <see cref="IEntity"/>. /// <see cref="IEntity"/>.
/// </summary> /// </summary>
/// <param name="droppedMechanism">The mechanism to install.</param> /// <param name="droppedMechanism">The mechanism to install.</param>
@@ -364,22 +308,14 @@ namespace Content.Server.Body
{ {
if (!TryInstallMechanism(droppedMechanism.ContainedMechanism)) if (!TryInstallMechanism(droppedMechanism.ContainedMechanism))
{ {
return false; //Installing the mechanism failed for some reason. return false; // Installing the mechanism failed for some reason.
} }
droppedMechanism.Owner.Delete(); droppedMechanism.Owner.Delete();
return true; return true;
} }
/// <summary> public bool TryDropMechanism(IEntity dropLocation, IMechanism mechanismTarget,
/// Tries to remove the given <see cref="Mechanism"/> reference from
/// this <see cref="BodyPart"/>.
/// </summary>
/// <returns>
/// The newly spawned <see cref="DroppedMechanismComponent"/>, or null
/// if there was an error in spawning the entity or removing the mechanism.
/// </returns>
public bool TryDropMechanism(IEntity dropLocation, Mechanism mechanismTarget,
[NotNullWhen(true)] out DroppedMechanismComponent dropped) [NotNullWhen(true)] out DroppedMechanismComponent dropped)
{ {
dropped = null!; dropped = null!;
@@ -402,16 +338,16 @@ namespace Content.Server.Body
} }
/// <summary> /// <summary>
/// Tries to destroy the given <see cref="Mechanism"/> in this /// Tries to destroy the given <see cref="IMechanism"/> in this
/// <see cref="BodyPart"/>. Does NOT spawn a dropped entity. /// <see cref="IBodyPart"/>. Does NOT spawn a dropped entity.
/// </summary> /// </summary>
/// <summary> /// <summary>
/// Tries to destroy the given <see cref="Mechanism"/> in this /// Tries to destroy the given <see cref="IMechanism"/> in this
/// <see cref="BodyPart"/>. /// <see cref="IBodyPart"/>.
/// </summary> /// </summary>
/// <param name="mechanismTarget">The mechanism to destroy.</param> /// <param name="mechanismTarget">The mechanism to destroy.</param>
/// <returns>True if successful, false otherwise.</returns> /// <returns>True if successful, false otherwise.</returns>
public bool DestroyMechanism(Mechanism mechanismTarget) public bool DestroyMechanism(IMechanism mechanismTarget)
{ {
if (!RemoveMechanism(mechanismTarget)) if (!RemoveMechanism(mechanismTarget))
{ {
@@ -421,18 +357,13 @@ namespace Content.Server.Body
return true; return true;
} }
/// <summary> public bool SurgeryCheck(SurgeryType surgery)
/// Checks if the given <see cref="SurgeryType"/> can be used on
/// the current state of this <see cref="BodyPart"/>.
/// </summary>
/// <returns>True if it can be used, false otherwise.</returns>
public bool SurgeryCheck(SurgeryType toolType)
{ {
return SurgeryData.CheckSurgery(toolType); return SurgeryData.CheckSurgery(surgery);
} }
/// <summary> /// <summary>
/// Attempts to perform surgery on this <see cref="BodyPart"/> with the given /// Attempts to perform surgery on this <see cref="IBodyPart"/> with the given
/// tool. /// tool.
/// </summary> /// </summary>
/// <returns>True if successful, false if there was an error.</returns> /// <returns>True if successful, false if there was an error.</returns>
@@ -441,7 +372,7 @@ namespace Content.Server.Body
return SurgeryData.PerformSurgery(toolType, target, surgeon, performer); return SurgeryData.PerformSurgery(toolType, target, surgeon, performer);
} }
private void AddMechanism(Mechanism mechanism) private void AddMechanism(IMechanism mechanism)
{ {
DebugTools.AssertNotNull(mechanism); DebugTools.AssertNotNull(mechanism);
@@ -474,11 +405,11 @@ namespace Content.Server.Body
/// <summary> /// <summary>
/// Tries to remove the given <see cref="mechanism"/> from this /// Tries to remove the given <see cref="mechanism"/> from this
/// <see cref="BodyPart"/>. /// <see cref="IBodyPart"/>.
/// </summary> /// </summary>
/// <param name="mechanism">The mechanism to remove.</param> /// <param name="mechanism">The mechanism to remove.</param>
/// <returns>True if it was removed, false otherwise.</returns> /// <returns>True if it was removed, false otherwise.</returns>
private bool RemoveMechanism(Mechanism mechanism) private bool RemoveMechanism(IMechanism mechanism)
{ {
DebugTools.AssertNotNull(mechanism); DebugTools.AssertNotNull(mechanism);
@@ -515,7 +446,7 @@ namespace Content.Server.Body
/// <summary> /// <summary>
/// Loads the given <see cref="BodyPartPrototype"/>. /// Loads the given <see cref="BodyPartPrototype"/>.
/// Current data on this <see cref="BodyPart"/> will be overwritten! /// Current data on this <see cref="IBodyPart"/> will be overwritten!
/// </summary> /// </summary>
protected virtual void LoadFromPrototype(BodyPartPrototype data) protected virtual void LoadFromPrototype(BodyPartPrototype data)
{ {
@@ -527,6 +458,7 @@ namespace Content.Server.Body
RSIPath = data.RSIPath; RSIPath = data.RSIPath;
RSIState = data.RSIState; RSIState = data.RSIState;
MaxDurability = data.Durability; MaxDurability = data.Durability;
IsVital = data.IsVital;
if (!prototypeManager.TryIndex(data.DamageContainerPresetId, if (!prototypeManager.TryIndex(data.DamageContainerPresetId,
out DamageContainerPrototype damageContainerData)) out DamageContainerPrototype damageContainerData))

View File

@@ -21,7 +21,7 @@ namespace Content.Server.Body
[ViewVariables] public string Name { get; private set; } [ViewVariables] public string Name { get; private set; }
/// <summary> /// <summary>
/// Maps a template slot to the ID of the <see cref="BodyPart"/> that should /// Maps a template slot to the ID of the <see cref="IBodyPart"/> that should
/// fill it. E.g. "right arm" : "BodyPart.arm.basic_human". /// fill it. E.g. "right arm" : "BodyPart.arm.basic_human".
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]

View File

@@ -0,0 +1,136 @@
#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);
}
}

View File

@@ -7,11 +7,11 @@ namespace Content.Server.Body
/// Making a class inherit from this interface allows you to do many things with /// Making a class inherit from this interface allows you to do many things with
/// it in the <see cref="SurgeryData"/> class. /// it in the <see cref="SurgeryData"/> class.
/// This includes passing it as an argument to a /// This includes passing it as an argument to a
/// <see cref="SurgeryData.SurgeryAction"/> delegate, as to later typecast it back /// <see cref="SurgeryData.SurgeryAction"/> delegate, as to later typecast
/// to the original class type. /// it back to the original class type.
/// Every BodyPart also needs an <see cref="IBodyPartContainer"/> to be its parent /// Every BodyPart also needs an <see cref="IBodyPartContainer"/> to be
/// (i.e. the <see cref="BodyManagerComponent"/> holds many <see cref="BodyPart"/>, /// its parent (i.e. the <see cref="BodyManagerComponent"/> holds many
/// each of which have an upward reference to it). /// <see cref="IBodyPart"/>, each of which have an upward reference to it).
/// </summary> /// </summary>
public interface IBodyPartContainer public interface IBodyPartContainer
{ {

View File

@@ -70,7 +70,7 @@ namespace Content.Server.Body.Mechanisms.Behaviors
} }
/// <summary> /// <summary>
/// Called when the containing <see cref="BodyPart"/> is attached to a /// Called when the containing <see cref="IBodyPart"/> is attached to a
/// <see cref="BodyManagerComponent"/>. /// <see cref="BodyManagerComponent"/>.
/// For instance, attaching a head to a body will call this on the brain inside. /// For instance, attaching a head to a body will call this on the brain inside.
/// </summary> /// </summary>
@@ -82,7 +82,7 @@ namespace Content.Server.Body.Mechanisms.Behaviors
/// <summary> /// <summary>
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is /// Called when the parent <see cref="Mechanisms.Mechanism"/> is
/// installed into a <see cref="BodyPart"/>. /// installed into a <see cref="IBodyPart"/>.
/// For instance, putting a brain into an empty head. /// For instance, putting a brain into an empty head.
/// </summary> /// </summary>
public void InstalledIntoPart() public void InstalledIntoPart()
@@ -92,22 +92,22 @@ namespace Content.Server.Body.Mechanisms.Behaviors
} }
/// <summary> /// <summary>
/// Called when the containing <see cref="BodyPart"/> is removed from a /// Called when the containing <see cref="IBodyPart"/> is removed from a
/// <see cref="BodyManagerComponent"/>. /// <see cref="BodyManagerComponent"/>.
/// For instance, cutting off ones head will call this on the brain inside. /// For instance, cutting off ones head will call this on the brain inside.
/// </summary> /// </summary>
public void RemovedFromBody(BodyManagerComponent old) public void RemovedFromBody(IBodyManagerComponent old)
{ {
OnRemovedFromBody(old); OnRemovedFromBody(old);
TryRemoveNetwork(old); TryRemoveNetwork(old);
} }
/// <summary> /// <summary>
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is removed from a /// Called when the parent <see cref="Mechanisms.Mechanism"/> is
/// <see cref="BodyPart"/>. /// removed from a <see cref="IBodyPart"/>.
/// For instance, taking a brain out of ones head. /// For instance, taking a brain out of ones head.
/// </summary> /// </summary>
public void RemovedFromPart(BodyPart old) public void RemovedFromPart(IBodyPart old)
{ {
OnRemovedFromPart(old); OnRemovedFromPart(old);
TryRemoveNetwork(old.Body); TryRemoveNetwork(old.Body);
@@ -121,7 +121,7 @@ namespace Content.Server.Body.Mechanisms.Behaviors
} }
} }
private void TryRemoveNetwork(BodyManagerComponent? body) private void TryRemoveNetwork(IBodyManagerComponent? body)
{ {
if (Network != null) if (Network != null)
{ {
@@ -137,7 +137,7 @@ namespace Content.Server.Body.Mechanisms.Behaviors
protected virtual void OnRemove() { } protected virtual void OnRemove() { }
/// <summary> /// <summary>
/// Called when the containing <see cref="BodyPart"/> is attached to a /// Called when the containing <see cref="IBodyPart"/> is attached to a
/// <see cref="BodyManagerComponent"/>. /// <see cref="BodyManagerComponent"/>.
/// For instance, attaching a head to a body will call this on the brain inside. /// For instance, attaching a head to a body will call this on the brain inside.
/// </summary> /// </summary>
@@ -145,24 +145,24 @@ namespace Content.Server.Body.Mechanisms.Behaviors
/// <summary> /// <summary>
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is /// Called when the parent <see cref="Mechanisms.Mechanism"/> is
/// installed into a <see cref="BodyPart"/>. /// installed into a <see cref="IBodyPart"/>.
/// For instance, putting a brain into an empty head. /// For instance, putting a brain into an empty head.
/// </summary> /// </summary>
protected virtual void OnInstalledIntoPart() { } protected virtual void OnInstalledIntoPart() { }
/// <summary> /// <summary>
/// Called when the containing <see cref="BodyPart"/> is removed from a /// Called when the containing <see cref="IBodyPart"/> is removed from a
/// <see cref="BodyManagerComponent"/>. /// <see cref="BodyManagerComponent"/>.
/// For instance, cutting off ones head will call this on the brain inside. /// For instance, cutting off ones head will call this on the brain inside.
/// </summary> /// </summary>
protected virtual void OnRemovedFromBody(BodyManagerComponent old) { } protected virtual void OnRemovedFromBody(IBodyManagerComponent old) { }
/// <summary> /// <summary>
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is removed from a /// Called when the parent <see cref="Mechanisms.Mechanism"/> is
/// <see cref="BodyPart"/>. /// removed from a <see cref="IBodyPart"/>.
/// For instance, taking a brain out of ones head. /// For instance, taking a brain out of ones head.
/// </summary> /// </summary>
protected virtual void OnRemovedFromPart(BodyPart old) { } protected virtual void OnRemovedFromPart(IBodyPart old) { }
/// <summary> /// <summary>
/// Called every update when this behavior is connected to a /// Called every update when this behavior is connected to a

View File

@@ -0,0 +1,103 @@
#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);
}
}

View File

@@ -12,13 +12,13 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.Body.Mechanisms namespace Content.Server.Body.Mechanisms
{ {
/// <summary> /// <summary>
/// Data class representing a persistent item inside a <see cref="BodyPart"/>. /// Data class representing a persistent item inside a <see cref="IBodyPart"/>.
/// This includes livers, eyes, cameras, brains, explosive implants, /// This includes livers, eyes, cameras, brains, explosive implants,
/// binary communicators, and other things. /// binary communicators, and other things.
/// </summary> /// </summary>
public class Mechanism public class Mechanism : IMechanism
{ {
private BodyPart? _part; private IBodyPart? _part;
public Mechanism(MechanismPrototype data) public Mechanism(MechanismPrototype data)
{ {
@@ -29,7 +29,7 @@ namespace Content.Server.Body.Mechanisms
ExamineMessage = null!; ExamineMessage = null!;
RSIPath = null!; RSIPath = null!;
RSIState = null!; RSIState = null!;
Behaviors = new List<MechanismBehavior>(); _behaviors = new List<MechanismBehavior>();
} }
[ViewVariables] private bool Initialized { get; set; } [ViewVariables] private bool Initialized { get; set; }
@@ -40,78 +40,33 @@ namespace Content.Server.Body.Mechanisms
[ViewVariables] public string Name { get; set; } [ViewVariables] public string Name { get; set; }
/// <summary> [ViewVariables] public string Description { get; set; }
/// Professional description of the <see cref="Mechanism"/>.
/// </summary>
[ViewVariables]
public string Description { get; set; }
/// <summary> [ViewVariables] public string ExamineMessage { get; set; }
/// The message to display upon examining a mob with this Mechanism installed.
/// If the string is empty (""), no message will be displayed.
/// </summary>
[ViewVariables]
public string ExamineMessage { get; set; }
/// <summary> [ViewVariables] public string RSIPath { get; set; }
/// Path to the RSI that represents this <see cref="Mechanism"/>.
/// </summary>
[ViewVariables]
public string RSIPath { get; set; }
/// <summary> [ViewVariables] public string RSIState { get; set; }
/// RSI state that represents this <see cref="Mechanism"/>.
/// </summary>
[ViewVariables]
public string RSIState { get; set; }
/// <summary> [ViewVariables] public int MaxDurability { get; set; }
/// Max HP of this <see cref="Mechanism"/>.
/// </summary>
[ViewVariables]
public int MaxDurability { get; set; }
/// <summary> [ViewVariables] public int CurrentDurability { get; set; }
/// Current HP of this <see cref="Mechanism"/>.
/// </summary>
[ViewVariables]
public int CurrentDurability { get; set; }
/// <summary> [ViewVariables] public int DestroyThreshold { get; set; }
/// At what HP this <see cref="Mechanism"/> is completely destroyed.
/// </summary>
[ViewVariables]
public int DestroyThreshold { get; set; }
/// <summary> [ViewVariables] public int Resistance { get; set; }
/// Armor of this <see cref="Mechanism"/> against attacks.
/// </summary>
[ViewVariables]
public int Resistance { get; set; }
/// <summary> [ViewVariables] public int Size { get; set; }
/// Determines a handful of things - mostly whether this
/// <see cref="Mechanism"/> can fit into a <see cref="BodyPart"/>.
/// </summary>
[ViewVariables]
public int Size { get; set; }
/// <summary> [ViewVariables] public BodyPartCompatibility Compatibility { get; set; }
/// What kind of <see cref="BodyPart"/> this <see cref="Mechanism"/> can be
/// easily installed into.
/// </summary>
[ViewVariables]
public BodyPartCompatibility Compatibility { get; set; }
/// <summary> private readonly List<MechanismBehavior> _behaviors;
/// The behaviors that this <see cref="Mechanism"/> performs.
/// </summary>
[ViewVariables]
private List<MechanismBehavior> Behaviors { get; }
public BodyManagerComponent? Body => Part?.Body; [ViewVariables] public IReadOnlyList<MechanismBehavior> Behaviors => _behaviors;
public BodyPart? Part public IBodyManagerComponent? Body => Part?.Body;
public IBodyPart? Part
{ {
get => _part; get => _part;
set set
@@ -167,7 +122,7 @@ namespace Content.Server.Body.Mechanisms
Size = data.Size; Size = data.Size;
Compatibility = data.Compatibility; Compatibility = data.Compatibility;
foreach (var behavior in Behaviors.ToArray()) foreach (var behavior in _behaviors.ToArray())
{ {
RemoveBehavior(behavior); RemoveBehavior(behavior);
} }
@@ -202,7 +157,7 @@ namespace Content.Server.Body.Mechanisms
} }
} }
public void RemovedFromBody(BodyManagerComponent old) public void RemovedFromBody(IBodyManagerComponent old)
{ {
foreach (var behavior in Behaviors) foreach (var behavior in Behaviors)
{ {
@@ -210,10 +165,6 @@ namespace Content.Server.Body.Mechanisms
} }
} }
/// <summary>
/// This method is called by <see cref="BodyPart.PreMetabolism"/> before
/// <see cref="MetabolismComponent.Update"/> is called.
/// </summary>
public void PreMetabolism(float frameTime) public void PreMetabolism(float frameTime)
{ {
foreach (var behavior in Behaviors) foreach (var behavior in Behaviors)
@@ -222,10 +173,6 @@ namespace Content.Server.Body.Mechanisms
} }
} }
/// <summary>
/// This method is called by <see cref="BodyPart.PostMetabolism"/> after
/// <see cref="MetabolismComponent.Update"/> is called.
/// </summary>
public void PostMetabolism(float frameTime) public void PostMetabolism(float frameTime)
{ {
foreach (var behavior in Behaviors) foreach (var behavior in Behaviors)
@@ -234,16 +181,21 @@ namespace Content.Server.Body.Mechanisms
} }
} }
private void AddBehavior(MechanismBehavior behavior) public void AddBehavior(MechanismBehavior behavior)
{ {
Behaviors.Add(behavior); _behaviors.Add(behavior);
behavior.Initialize(this); behavior.Initialize(this);
} }
private bool RemoveBehavior(MechanismBehavior behavior) public bool RemoveBehavior(MechanismBehavior behavior)
{ {
behavior.Remove(); if (_behaviors.Remove(behavior))
return Behaviors.Remove(behavior); {
behavior.Remove();
return true;
}
return false;
} }
} }
} }

View File

@@ -17,13 +17,13 @@ namespace Content.Server.Body.Surgery
[UsedImplicitly] [UsedImplicitly]
public class BiologicalSurgeryData : SurgeryData public class BiologicalSurgeryData : SurgeryData
{ {
private readonly List<Mechanism> _disconnectedOrgans = new List<Mechanism>(); private readonly List<IMechanism> _disconnectedOrgans = new List<IMechanism>();
private bool _skinOpened; private bool _skinOpened;
private bool _skinRetracted; private bool _skinRetracted;
private bool _vesselsClamped; private bool _vesselsClamped;
public BiologicalSurgeryData(BodyPart parent) : base(parent) { } public BiologicalSurgeryData(IBodyPart parent) : base(parent) { }
protected override SurgeryAction? GetSurgeryStep(SurgeryType toolType) protected override SurgeryAction? GetSurgeryStep(SurgeryType toolType)
{ {
@@ -118,12 +118,12 @@ namespace Content.Server.Body.Surgery
return toReturn; return toReturn;
} }
public override bool CanInstallMechanism(Mechanism mechanism) public override bool CanInstallMechanism(IMechanism mechanism)
{ {
return _skinOpened && _vesselsClamped && _skinRetracted; return _skinOpened && _vesselsClamped && _skinRetracted;
} }
public override bool CanAttachBodyPart(BodyPart part) public override bool CanAttachBodyPart(IBodyPart part)
{ {
return true; return true;
// TODO: if a bodypart is disconnected, you should have to do some surgery to allow another bodypart to be attached. // TODO: if a bodypart is disconnected, you should have to do some surgery to allow another bodypart to be attached.
@@ -131,7 +131,7 @@ namespace Content.Server.Body.Surgery
private void OpenSkinSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) private void OpenSkinSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
{ {
performer.PopupMessage(performer, Loc.GetString("Cut open the skin...")); performer.PopupMessage(Loc.GetString("Cut open the skin..."));
// TODO do_after: Delay // TODO do_after: Delay
_skinOpened = true; _skinOpened = true;
@@ -139,7 +139,7 @@ namespace Content.Server.Body.Surgery
private void ClampVesselsSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) private void ClampVesselsSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
{ {
performer.PopupMessage(performer, Loc.GetString("Clamp the vessels...")); performer.PopupMessage(Loc.GetString("Clamp the vessels..."));
// TODO do_after: Delay // TODO do_after: Delay
_vesselsClamped = true; _vesselsClamped = true;
@@ -147,7 +147,7 @@ namespace Content.Server.Body.Surgery
private void RetractSkinSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) private void RetractSkinSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
{ {
performer.PopupMessage(performer, Loc.GetString("Retract the skin...")); performer.PopupMessage(Loc.GetString("Retract the skin..."));
// TODO do_after: Delay // TODO do_after: Delay
_skinRetracted = true; _skinRetracted = true;
@@ -155,7 +155,7 @@ namespace Content.Server.Body.Surgery
private void CauterizeIncisionSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer) private void CauterizeIncisionSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
{ {
performer.PopupMessage(performer, Loc.GetString("Cauterize the incision...")); performer.PopupMessage(Loc.GetString("Cauterize the incision..."));
// TODO do_after: Delay // TODO do_after: Delay
_skinOpened = false; _skinOpened = false;
@@ -170,7 +170,7 @@ namespace Content.Server.Body.Surgery
return; return;
} }
var toSend = new List<Mechanism>(); var toSend = new List<IMechanism>();
foreach (var mechanism in Parent.Mechanisms) foreach (var mechanism in Parent.Mechanisms)
{ {
if (!_disconnectedOrgans.Contains(mechanism)) if (!_disconnectedOrgans.Contains(mechanism))
@@ -185,7 +185,7 @@ namespace Content.Server.Body.Surgery
} }
} }
private void LoosenOrganSurgeryCallback(Mechanism target, IBodyPartContainer container, ISurgeon surgeon, private void LoosenOrganSurgeryCallback(IMechanism target, IBodyPartContainer container, ISurgeon surgeon,
IEntity performer) IEntity performer)
{ {
if (target == null || !Parent.Mechanisms.Contains(target)) if (target == null || !Parent.Mechanisms.Contains(target))
@@ -193,7 +193,7 @@ namespace Content.Server.Body.Surgery
return; return;
} }
performer.PopupMessage(performer, Loc.GetString("Loosen the organ...")); performer.PopupMessage(Loc.GetString("Loosen the organ..."));
// TODO do_after: Delay // TODO do_after: Delay
_disconnectedOrgans.Add(target); _disconnectedOrgans.Add(target);
@@ -216,8 +216,7 @@ namespace Content.Server.Body.Surgery
} }
} }
private void RemoveOrganSurgeryCallback(Mechanism target, IBodyPartContainer container, private void RemoveOrganSurgeryCallback(IMechanism target, IBodyPartContainer container, ISurgeon surgeon,
ISurgeon surgeon,
IEntity performer) IEntity performer)
{ {
if (target == null || !Parent.Mechanisms.Contains(target)) if (target == null || !Parent.Mechanisms.Contains(target))
@@ -225,7 +224,7 @@ namespace Content.Server.Body.Surgery
return; return;
} }
performer.PopupMessage(performer, Loc.GetString("Remove the organ...")); performer.PopupMessage(Loc.GetString("Remove the organ..."));
// TODO do_after: Delay // TODO do_after: Delay
Parent.TryDropMechanism(performer, target, out _); Parent.TryDropMechanism(performer, target, out _);
@@ -241,7 +240,7 @@ namespace Content.Server.Body.Surgery
} }
var bmTarget = (BodyManagerComponent) container; var bmTarget = (BodyManagerComponent) container;
performer.PopupMessage(performer, Loc.GetString("Saw off the limb!")); performer.PopupMessage(Loc.GetString("Saw off the limb!"));
// TODO do_after: Delay // TODO do_after: Delay
bmTarget.DisconnectBodyPart(Parent, true); bmTarget.DisconnectBodyPart(Parent, true);

View File

@@ -13,7 +13,7 @@ namespace Content.Server.Body.Surgery
public interface ISurgeon public interface ISurgeon
{ {
public delegate void MechanismRequestCallback( public delegate void MechanismRequestCallback(
Mechanism target, IMechanism target,
IBodyPartContainer container, IBodyPartContainer container,
ISurgeon surgeon, ISurgeon surgeon,
IEntity performer); IEntity performer);
@@ -29,6 +29,6 @@ namespace Content.Server.Body.Surgery
/// This function is called in that scenario, and it is expected that you call the callback with one mechanism from the /// This function is called in that scenario, and it is expected that you call the callback with one mechanism from the
/// provided list. /// provided list.
/// </summary> /// </summary>
public void RequestMechanism(IEnumerable<Mechanism> options, MechanismRequestCallback callback); public void RequestMechanism(IEnumerable<IMechanism> options, MechanismRequestCallback callback);
} }
} }

View File

@@ -6,7 +6,7 @@ using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.Body.Surgery namespace Content.Server.Body.Surgery
{ {
/// <summary> /// <summary>
/// This data class represents the state of a <see cref="BodyPart"/> in regards to everything surgery related - /// This data class represents the state of a <see cref="IBodyPart"/> in regards to everything surgery related -
/// whether there's an incision on it, whether the bone is broken, etc. /// whether there's an incision on it, whether the bone is broken, etc.
/// </summary> /// </summary>
public abstract class SurgeryData public abstract class SurgeryData
@@ -14,40 +14,40 @@ namespace Content.Server.Body.Surgery
protected delegate void SurgeryAction(IBodyPartContainer container, ISurgeon surgeon, IEntity performer); protected delegate void SurgeryAction(IBodyPartContainer container, ISurgeon surgeon, IEntity performer);
/// <summary> /// <summary>
/// The <see cref="BodyPart"/> this surgeryData is attached to. /// The <see cref="IBodyPart"/> this surgeryData is attached to.
/// The <see cref="SurgeryData"/> class should not exist without a /// The <see cref="SurgeryData"/> class should not exist without a
/// <see cref="BodyPart"/> that it represents, and will throw errors if it /// <see cref="IBodyPart"/> that it represents, and will throw errors if it
/// is null. /// is null.
/// </summary> /// </summary>
protected readonly BodyPart Parent; protected readonly IBodyPart Parent;
protected SurgeryData(BodyPart parent) protected SurgeryData(IBodyPart parent)
{ {
Parent = parent; Parent = parent;
} }
/// <summary> /// <summary>
/// The <see cref="BodyPartType"/> of the parent <see cref="BodyPart"/>. /// The <see cref="BodyPartType"/> of the parent <see cref="IBodyPart"/>.
/// </summary> /// </summary>
protected BodyPartType ParentType => Parent.PartType; protected BodyPartType ParentType => Parent.PartType;
/// <summary> /// <summary>
/// Returns the description of this current <see cref="BodyPart"/> to be shown /// Returns the description of this current <see cref="IBodyPart"/> to be shown
/// upon observing the given entity. /// upon observing the given entity.
/// </summary> /// </summary>
public abstract string GetDescription(IEntity target); public abstract string GetDescription(IEntity target);
/// <summary> /// <summary>
/// Returns whether a <see cref="Mechanism"/> can be installed into the /// Returns whether a <see cref="IMechanism"/> can be installed into the
/// <see cref="BodyPart"/> this <see cref="SurgeryData"/> represents. /// <see cref="IBodyPart"/> this <see cref="SurgeryData"/> represents.
/// </summary> /// </summary>
public abstract bool CanInstallMechanism(Mechanism mechanism); public abstract bool CanInstallMechanism(IMechanism mechanism);
/// <summary> /// <summary>
/// Returns whether the given <see cref="BodyPart"/> can be connected to the /// Returns whether the given <see cref="IBodyPart"/> can be connected to the
/// <see cref="BodyPart"/> this <see cref="SurgeryData"/> represents. /// <see cref="IBodyPart"/> this <see cref="SurgeryData"/> represents.
/// </summary> /// </summary>
public abstract bool CanAttachBodyPart(BodyPart part); public abstract bool CanAttachBodyPart(IBodyPart part);
/// <summary> /// <summary>
/// Gets the delegate corresponding to the surgery step using the given /// Gets the delegate corresponding to the surgery step using the given

View File

@@ -7,7 +7,9 @@ using Content.Server.Interfaces.Chat;
using Content.Server.Interfaces.GameObjects; using Content.Server.Interfaces.GameObjects;
using Content.Server.Observer; using Content.Server.Observer;
using Content.Server.Players; using Content.Server.Players;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.Interfaces;
using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player; using Robust.Server.Interfaces.Player;
using Robust.Shared.Enums; using Robust.Shared.Enums;
@@ -116,6 +118,8 @@ namespace Content.Server.Chat
internal class SuicideCommand : IClientCommand internal class SuicideCommand : IClientCommand
{ {
[Dependency] private readonly IPlayerManager _playerManager = default!;
public string Command => "suicide"; public string Command => "suicide";
public string Description => "Commits suicide"; public string Description => "Commits suicide";
@@ -133,12 +137,15 @@ namespace Content.Server.Chat
damageableComponent.ChangeDamage(kind switch damageableComponent.ChangeDamage(kind switch
{ {
SuicideKind.Blunt => DamageType.Blunt, SuicideKind.Blunt => DamageType.Blunt,
SuicideKind.Slash => DamageType.Slash,
SuicideKind.Piercing => DamageType.Piercing, SuicideKind.Piercing => DamageType.Piercing,
SuicideKind.Heat => DamageType.Heat, SuicideKind.Heat => DamageType.Heat,
SuicideKind.Disintegration => DamageType.Disintegration, SuicideKind.Shock => DamageType.Shock,
SuicideKind.Cellular => DamageType.Cellular, SuicideKind.Cold => DamageType.Cold,
SuicideKind.DNA => DamageType.DNA, SuicideKind.Poison => DamageType.Poison,
SuicideKind.Radiation => DamageType.Radiation,
SuicideKind.Asphyxiation => DamageType.Asphyxiation, SuicideKind.Asphyxiation => DamageType.Asphyxiation,
SuicideKind.Bloodloss => DamageType.Bloodloss,
_ => DamageType.Blunt _ => DamageType.Blunt
}, },
500, 500,
@@ -185,8 +192,14 @@ namespace Content.Server.Chat
} }
} }
} }
// Default suicide, bite your tongue // Default suicide, bite your tongue
chat.EntityMe(owner, Loc.GetString("is attempting to bite {0:their} own tongue, looks like {0:theyre} trying to commit suicide!", owner)); //TODO: theyre macro var othersMessage = Loc.GetString("{0:theName} is attempting to bite {0:their} own tongue!", owner);
owner.PopupMessageOtherClients(othersMessage);
var selfMessage = Loc.GetString("You attempt to bite your own tongue!");
owner.PopupMessage(selfMessage);
dmgComponent.ChangeDamage(DamageType.Piercing, 500, true, owner); dmgComponent.ChangeDamage(DamageType.Piercing, 500, true, owner);
// Prevent the player from returning to the body. Yes, this is an ugly hack. // Prevent the player from returning to the body. Yes, this is an ugly hack.

View File

@@ -109,6 +109,9 @@ namespace Content.Server.Chat
message = handler(source, message); message = handler(source, message);
} }
// Ensure the first letter inside the message string is always a capital letter
message = message[0].ToString().ToUpper() + message.Remove(0,1);
var pos = source.Transform.GridPosition; var pos = source.Transform.GridPosition;
var clients = _playerManager.GetPlayersInRange(pos, VoiceRange).Select(p => p.ConnectedClient); var clients = _playerManager.GetPlayersInRange(pos, VoiceRange).Select(p => p.ConnectedClient);

View File

@@ -2,11 +2,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility; using Content.Server.Utility;
using Content.Shared.Access; using Content.Shared.Access;
using Content.Shared.GameObjects.Components.Access; using Content.Shared.GameObjects.Components.Access;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.Components.Container;
using Robust.Server.GameObjects.Components.UserInterface; using Robust.Server.GameObjects.Components.UserInterface;
@@ -25,7 +25,6 @@ namespace Content.Server.GameObjects.Components.Access
[ComponentReference(typeof(IActivate))] [ComponentReference(typeof(IActivate))]
public class IdCardConsoleComponent : SharedIdCardConsoleComponent, IActivate public class IdCardConsoleComponent : SharedIdCardConsoleComponent, IActivate
{ {
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private ContainerSlot _privilegedIdContainer = default!; private ContainerSlot _privilegedIdContainer = default!;
@@ -132,7 +131,7 @@ namespace Content.Server.GameObjects.Components.Access
{ {
if (!user.TryGetComponent(out IHandsComponent? hands)) if (!user.TryGetComponent(out IHandsComponent? hands))
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("You have no hands.")); Owner.PopupMessage(user, Loc.GetString("You have no hands."));
return; return;
} }
@@ -161,7 +160,7 @@ namespace Content.Server.GameObjects.Components.Access
if (!hands.Drop(hands.ActiveHand, container)) if (!hands.Drop(hands.ActiveHand, container))
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("You can't let go of the ID card!")); Owner.PopupMessage(user, Loc.GetString("You can't let go of the ID card!"));
return; return;
} }
UpdateUserInterface(); UpdateUserInterface();

View File

@@ -1,11 +1,9 @@
 using Robust.Server.GameObjects;
using Robust.Server.GameObjects;
using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces; using Content.Shared.Interfaces;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems; using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Content.Server.GameObjects.EntitySystems.DoAfter; using Content.Server.GameObjects.EntitySystems.DoAfter;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -22,6 +20,7 @@ using Content.Shared.GameObjects.Components.Mobs;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Content.Shared.Utility;
using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.GUI;
namespace Content.Server.GameObjects.Components.ActionBlocking namespace Content.Server.GameObjects.Components.ActionBlocking
@@ -29,9 +28,6 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
[RegisterComponent] [RegisterComponent]
public class CuffableComponent : SharedCuffableComponent public class CuffableComponent : SharedCuffableComponent
{ {
[Dependency]
private readonly ISharedNotifyManager _notifyManager;
/// <summary> /// <summary>
/// How many of this entity's hands are currently cuffed. /// How many of this entity's hands are currently cuffed.
/// </summary> /// </summary>
@@ -109,11 +105,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
return; return;
} }
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed( if (!handcuff.InRangeUnobstructed(Owner, _interactRange))
handcuff.Transform.MapPosition,
Owner.Transform.MapPosition,
_interactRange,
ignoredEnt: Owner))
{ {
Logger.Warning("Handcuffs being applied to player are obstructed or too far away! This should not happen!"); Logger.Warning("Handcuffs being applied to player are obstructed or too far away! This should not happen!");
return; return;
@@ -193,8 +185,14 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
{ {
if (Owner.TryGetComponent(out ServerStatusEffectsComponent status)) if (Owner.TryGetComponent(out ServerStatusEffectsComponent status))
{ {
status.ChangeStatusEffectIcon(StatusEffect.Cuffed, if (CanStillInteract)
CanStillInteract ? "/Textures/Interface/StatusEffects/Handcuffed/Uncuffed.png" : "/Textures/Interface/StatusEffects/Handcuffed/Handcuffed.png"); {
status.RemoveStatusEffect(StatusEffect.Cuffed);
}
else
{
status.ChangeStatusEffectIcon(StatusEffect.Cuffed, "/Textures/Interface/StatusEffects/Handcuffed/Handcuffed.png");
}
} }
} }
@@ -228,32 +226,23 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
if (!isOwner && !ActionBlockerSystem.CanInteract(user)) if (!isOwner && !ActionBlockerSystem.CanInteract(user))
{ {
user.PopupMessage(user, Loc.GetString("You can't do that!")); user.PopupMessage(Loc.GetString("You can't do that!"));
return; return;
} }
if (!isOwner && if (!isOwner && user.InRangeUnobstructed(Owner, _interactRange))
!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
user.Transform.MapPosition,
Owner.Transform.MapPosition,
_interactRange,
ignoredEnt: Owner))
{ {
user.PopupMessage(user, Loc.GetString("You are too far away to remove the cuffs.")); user.PopupMessage(Loc.GetString("You are too far away to remove the cuffs."));
return; return;
} }
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed( if (!cuffsToRemove.InRangeUnobstructed(Owner, _interactRange))
cuffsToRemove.Transform.MapPosition,
Owner.Transform.MapPosition,
_interactRange,
ignoredEnt: Owner))
{ {
Logger.Warning("Handcuffs being removed from player are obstructed or too far away! This should not happen!"); Logger.Warning("Handcuffs being removed from player are obstructed or too far away! This should not happen!");
return; return;
} }
user.PopupMessage(user, Loc.GetString("You start removing the cuffs.")); user.PopupMessage(Loc.GetString("You start removing the cuffs."));
var audio = EntitySystem.Get<AudioSystem>(); var audio = EntitySystem.Get<AudioSystem>();
audio.PlayFromEntity(isOwner ? cuff.StartBreakoutSound : cuff.StartUncuffSound, Owner); audio.PlayFromEntity(isOwner ? cuff.StartBreakoutSound : cuff.StartUncuffSound, Owner);
@@ -298,29 +287,29 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
if (CuffedHandCount == 0) if (CuffedHandCount == 0)
{ {
_notifyManager.PopupMessage(user, user, Loc.GetString("You successfully remove the cuffs.")); user.PopupMessage(Loc.GetString("You successfully remove the cuffs."));
if (!isOwner) if (!isOwner)
{ {
_notifyManager.PopupMessage(user, Owner, Loc.GetString("{0:theName} uncuffs your hands.", user)); user.PopupMessage(Owner, Loc.GetString("{0:theName} uncuffs your hands.", user));
} }
} }
else else
{ {
if (!isOwner) if (!isOwner)
{ {
_notifyManager.PopupMessage(user, user, Loc.GetString("You successfully remove the cuffs. {0} of {1:theName}'s hands remain cuffed.", CuffedHandCount, user)); user.PopupMessage(Loc.GetString("You successfully remove the cuffs. {0} of {1:theName}'s hands remain cuffed.", CuffedHandCount, user));
_notifyManager.PopupMessage(user, Owner, Loc.GetString("{0:theName} removes your cuffs. {1} of your hands remain cuffed.", user, CuffedHandCount)); user.PopupMessage(Owner, Loc.GetString("{0:theName} removes your cuffs. {1} of your hands remain cuffed.", user, CuffedHandCount));
} }
else else
{ {
_notifyManager.PopupMessage(user, user, Loc.GetString("You successfully remove the cuffs. {0} of your hands remain cuffed.", CuffedHandCount)); user.PopupMessage(Loc.GetString("You successfully remove the cuffs. {0} of your hands remain cuffed.", CuffedHandCount));
} }
} }
} }
else else
{ {
_notifyManager.PopupMessage(user, user, Loc.GetString("You fail to remove the cuffs.")); user.PopupMessage(Loc.GetString("You fail to remove the cuffs."));
} }
return; return;

View File

@@ -16,15 +16,13 @@ using Content.Shared.GameObjects.Components.ActionBlocking;
using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Mobs;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using System; using System;
using Content.Shared.Utility;
namespace Content.Server.GameObjects.Components.ActionBlocking namespace Content.Server.GameObjects.Components.ActionBlocking
{ {
[RegisterComponent] [RegisterComponent]
public class HandcuffComponent : SharedHandcuffComponent, IAfterInteract public class HandcuffComponent : SharedHandcuffComponent, IAfterInteract
{ {
[Dependency]
private readonly ISharedNotifyManager _notifyManager;
/// <summary> /// <summary>
/// The time it takes to apply a <see cref="CuffedComponent"/> to an entity. /// The time it takes to apply a <see cref="CuffedComponent"/> to an entity.
/// </summary> /// </summary>
@@ -160,40 +158,36 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
if (eventArgs.Target == eventArgs.User) if (eventArgs.Target == eventArgs.User)
{ {
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't cuff yourself!")); eventArgs.User.PopupMessage(Loc.GetString("You can't cuff yourself!"));
return; return;
} }
if (Broken) if (Broken)
{ {
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("The cuffs are broken!")); eventArgs.User.PopupMessage(Loc.GetString("The cuffs are broken!"));
return; return;
} }
if (!eventArgs.Target.TryGetComponent<HandsComponent>(out var hands)) if (!eventArgs.Target.TryGetComponent<HandsComponent>(out var hands))
{ {
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("{0:theName} has no hands!", eventArgs.Target)); eventArgs.User.PopupMessage(Loc.GetString("{0:theName} has no hands!", eventArgs.Target));
return; return;
} }
if (cuffed.CuffedHandCount == hands.Count) if (cuffed.CuffedHandCount == hands.Count)
{ {
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("{0:theName} has no free hands to handcuff!", eventArgs.Target)); eventArgs.User.PopupMessage(Loc.GetString("{0:theName} has no free hands to handcuff!", eventArgs.Target));
return; return;
} }
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed( if (!eventArgs.InRangeUnobstructed(_interactRange, ignoreInsideBlocker: true))
eventArgs.User.Transform.MapPosition,
eventArgs.Target.Transform.MapPosition,
_interactRange,
ignoredEnt: Owner))
{ {
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You are too far away to use the cuffs!")); eventArgs.User.PopupMessage(Loc.GetString("You are too far away to use the cuffs!"));
return; return;
} }
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You start cuffing {0:theName}.", eventArgs.Target)); eventArgs.User.PopupMessage(Loc.GetString("You start cuffing {0:theName}.", eventArgs.Target));
_notifyManager.PopupMessage(eventArgs.User, eventArgs.Target, Loc.GetString("{0:theName} starts cuffing you!", eventArgs.User)); eventArgs.User.PopupMessage(eventArgs.Target, Loc.GetString("{0:theName} starts cuffing you!", eventArgs.User));
_audioSystem.PlayFromEntity(StartCuffSound, Owner); _audioSystem.PlayFromEntity(StartCuffSound, Owner);
TryUpdateCuff(eventArgs.User, eventArgs.Target, cuffed); TryUpdateCuff(eventArgs.User, eventArgs.Target, cuffed);
@@ -225,8 +219,8 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
if (result != DoAfterStatus.Cancelled) if (result != DoAfterStatus.Cancelled)
{ {
_audioSystem.PlayFromEntity(EndCuffSound, Owner); _audioSystem.PlayFromEntity(EndCuffSound, Owner);
_notifyManager.PopupMessage(user, user, Loc.GetString("You successfully cuff {0:theName}.", target)); user.PopupMessage(Loc.GetString("You successfully cuff {0:theName}.", target));
_notifyManager.PopupMessage(target, target, Loc.GetString("You have been cuffed by {0:theName}!", user)); target.PopupMessage(Loc.GetString("You have been cuffed by {0:theName}!", user));
if (user.TryGetComponent<HandsComponent>(out var hands)) if (user.TryGetComponent<HandsComponent>(out var hands))
{ {
@@ -240,8 +234,8 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
} }
else else
{ {
user.PopupMessage(user, Loc.GetString("You were interrupted while cuffing {0:theName}!", target)); user.PopupMessage(Loc.GetString("You were interrupted while cuffing {0:theName}!", target));
target.PopupMessage(target, Loc.GetString("You interrupt {0:theName} while they are cuffing you!", user)); target.PopupMessage(Loc.GetString("You interrupt {0:theName} while they are cuffing you!", user));
} }
} }
} }

View File

@@ -1,12 +1,12 @@
#nullable enable #nullable enable
using System.Collections.Generic; using System.Collections.Generic;
using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility; using Content.Server.Utility;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.Components;
using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
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;
@@ -24,7 +24,6 @@ namespace Content.Server.GameObjects.Components.Atmos
[RegisterComponent] [RegisterComponent]
public class GasAnalyzerComponent : SharedGasAnalyzerComponent, IAfterInteract, IDropped, IUse public class GasAnalyzerComponent : SharedGasAnalyzerComponent, IAfterInteract, IDropped, IUse
{ {
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
private GasAnalyzerDanger _pressureDanger; private GasAnalyzerDanger _pressureDanger;
@@ -207,17 +206,14 @@ namespace Content.Server.GameObjects.Components.Atmos
if (!player.TryGetComponent(out IHandsComponent? handsComponent)) if (!player.TryGetComponent(out IHandsComponent? handsComponent))
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, player, Owner.PopupMessage(player, Loc.GetString("You have no hands."));
Loc.GetString("You have no hands."));
return; return;
} }
var activeHandEntity = handsComponent.GetActiveHand?.Owner; var activeHandEntity = handsComponent.GetActiveHand?.Owner;
if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent? gasAnalyzer)) if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent? gasAnalyzer))
{ {
_notifyManager.PopupMessage(serverMsg.Session.AttachedEntity, serverMsg.Session.AttachedEntity.PopupMessage(Loc.GetString("You need a Gas Analyzer in your hand!"));
serverMsg.Session.AttachedEntity,
Loc.GetString("You need a Gas Analyzer in your hand!"));
return; return;
} }
@@ -231,7 +227,7 @@ namespace Content.Server.GameObjects.Components.Atmos
{ {
if (!eventArgs.CanReach) if (!eventArgs.CanReach)
{ {
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't reach there!")); eventArgs.User.PopupMessage(Loc.GetString("You can't reach there!"));
return; return;
} }

View File

@@ -14,10 +14,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Map; using Robust.Shared.GameObjects.Components.Map;
using Robust.Shared.GameObjects.Components.Transform; using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -32,6 +29,8 @@ namespace Content.Server.GameObjects.Components.Atmos
public class GridAtmosphereComponent : Component, IGridAtmosphereComponent public class GridAtmosphereComponent : Component, IGridAtmosphereComponent
{ {
[Robust.Shared.IoC.Dependency] private IMapManager _mapManager = default!; [Robust.Shared.IoC.Dependency] private IMapManager _mapManager = default!;
[Robust.Shared.IoC.Dependency] private ITileDefinitionManager _tileDefinitionManager = default!;
[Robust.Shared.IoC.Dependency] private IServerEntityManager _serverEntityManager = default!;
/// <summary> /// <summary>
/// Check current execution time every n instances processed. /// Check current execution time every n instances processed.
@@ -70,7 +69,7 @@ namespace Content.Server.GameObjects.Components.Atmos
private double _excitedGroupLastProcess; private double _excitedGroupLastProcess;
[ViewVariables] [ViewVariables]
private readonly Dictionary<MapIndices, TileAtmosphere> _tiles = new Dictionary<MapIndices, TileAtmosphere>(1000); protected readonly Dictionary<MapIndices, TileAtmosphere> Tiles = new Dictionary<MapIndices, TileAtmosphere>(1000);
[ViewVariables] [ViewVariables]
private readonly HashSet<TileAtmosphere> _activeTiles = new HashSet<TileAtmosphere>(1000); private readonly HashSet<TileAtmosphere> _activeTiles = new HashSet<TileAtmosphere>(1000);
@@ -154,23 +153,13 @@ namespace Content.Server.GameObjects.Components.Atmos
} }
/// <inheritdoc /> /// <inheritdoc />
public void PryTile(MapIndices indices) public virtual void PryTile(MapIndices indices)
{ {
if (!Owner.TryGetComponent(out IMapGridComponent? mapGridComponent)) return; if (!Owner.TryGetComponent(out IMapGridComponent? mapGridComponent)) return;
if (IsSpace(indices) || IsAirBlocked(indices)) return; if (IsSpace(indices) || IsAirBlocked(indices)) return;
var mapGrid = mapGridComponent.Grid; var mapGrid = mapGridComponent.Grid;
var tile = mapGrid.GetTileRef(indices).Tile; indices.PryTile(mapGrid.Index, _mapManager, _tileDefinitionManager, _serverEntityManager);
var tileDefinitionManager = IoCManager.Resolve<ITileDefinitionManager>();
var tileDef = (ContentTileDefinition)tileDefinitionManager[tile.TypeId];
var underplating = tileDefinitionManager["underplating"];
mapGrid.SetTile(indices, new Tile(underplating.TileId));
//Actually spawn the relevant tile item at the right position and give it some offset to the corner.
var tileItem = IoCManager.Resolve<IServerEntityManager>().SpawnEntity(tileDef.ItemDropPrototypeName, new GridCoordinates(indices.X, indices.Y, mapGrid));
tileItem.Transform.WorldPosition += (0.2f, 0.2f);
} }
public override void Initialize() public override void Initialize()
@@ -185,17 +174,17 @@ namespace Content.Server.GameObjects.Components.Atmos
RepopulateTiles(); RepopulateTiles();
} }
public void RepopulateTiles() public virtual void RepopulateTiles()
{ {
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return; if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
foreach (var tile in mapGrid.Grid.GetAllTiles()) foreach (var tile in mapGrid.Grid.GetAllTiles())
{ {
if(!_tiles.ContainsKey(tile.GridIndices)) if(!Tiles.ContainsKey(tile.GridIndices))
_tiles.Add(tile.GridIndices, new TileAtmosphere(this, tile.GridIndex, tile.GridIndices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C})); Tiles.Add(tile.GridIndices, new TileAtmosphere(this, tile.GridIndex, tile.GridIndices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}));
} }
foreach (var (_, tile) in _tiles.ToArray()) foreach (var (_, tile) in Tiles.ToArray())
{ {
tile.UpdateAdjacent(); tile.UpdateAdjacent();
tile.UpdateVisuals(); tile.UpdateVisuals();
@@ -203,12 +192,12 @@ namespace Content.Server.GameObjects.Components.Atmos
} }
/// <inheritdoc /> /// <inheritdoc />
public void Invalidate(MapIndices indices) public virtual void Invalidate(MapIndices indices)
{ {
_invalidatedCoords.Add(indices); _invalidatedCoords.Add(indices);
} }
private void Revalidate() protected virtual void Revalidate()
{ {
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return; if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
@@ -219,14 +208,14 @@ namespace Content.Server.GameObjects.Components.Atmos
if (tile == null) if (tile == null)
{ {
tile = new TileAtmosphere(this, mapGrid.Grid.Index, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}); tile = new TileAtmosphere(this, mapGrid.Grid.Index, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C});
_tiles[indices] = tile; Tiles[indices] = tile;
} }
if (IsSpace(indices)) if (IsSpace(indices))
{ {
tile.Air = new GasMixture(GetVolumeForCells(1)); tile.Air = new GasMixture(GetVolumeForCells(1));
tile.Air.MarkImmutable(); tile.Air.MarkImmutable();
_tiles[indices] = tile; Tiles[indices] = tile;
} else if (IsAirBlocked(indices)) } else if (IsAirBlocked(indices))
{ {
@@ -271,18 +260,18 @@ namespace Content.Server.GameObjects.Components.Atmos
} }
/// <inheritdoc /> /// <inheritdoc />
public void FixVacuum(MapIndices indices) public virtual void FixVacuum(MapIndices indices)
{ {
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return; if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
var tile = GetTile(indices); var tile = GetTile(indices);
if (tile?.GridIndex != mapGrid.Grid.Index) return; if (tile?.GridIndex != mapGrid.Grid.Index) return;
var adjacent = GetAdjacentTiles(indices); var adjacent = GetAdjacentTiles(indices);
tile.Air = new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}; tile.Air = new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C};
_tiles[indices] = tile; Tiles[indices] = tile;
var ratio = 1f / adjacent.Count; var ratio = 1f / adjacent.Count;
foreach (var (direction, adj) in adjacent) foreach (var (_, adj) in adjacent)
{ {
var mix = adj.Air.RemoveRatio(ratio); var mix = adj.Air.RemoveRatio(ratio);
tile.Air.Merge(mix); tile.Air.Merge(mix);
@@ -292,17 +281,17 @@ namespace Content.Server.GameObjects.Components.Atmos
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddActiveTile(TileAtmosphere? tile) public virtual void AddActiveTile(TileAtmosphere? tile)
{ {
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return; if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
if (tile?.GridIndex != mapGrid.Grid.Index || tile?.Air == null) return; if (tile?.GridIndex != mapGrid.Grid.Index) return;
tile.Excited = true; tile.Excited = true;
_activeTiles.Add(tile); _activeTiles.Add(tile);
} }
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveActiveTile(TileAtmosphere? tile) public virtual void RemoveActiveTile(TileAtmosphere? tile)
{ {
if (tile == null) return; if (tile == null) return;
_activeTiles.Remove(tile); _activeTiles.Remove(tile);
@@ -312,7 +301,7 @@ namespace Content.Server.GameObjects.Components.Atmos
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHotspotTile(TileAtmosphere? tile) public virtual void AddHotspotTile(TileAtmosphere? tile)
{ {
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return; if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
if (tile?.GridIndex != mapGrid.Grid.Index || tile?.Air == null) return; if (tile?.GridIndex != mapGrid.Grid.Index || tile?.Air == null) return;
@@ -321,20 +310,20 @@ namespace Content.Server.GameObjects.Components.Atmos
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveHotspotTile(TileAtmosphere? tile) public virtual void RemoveHotspotTile(TileAtmosphere? tile)
{ {
if (tile == null) return; if (tile == null) return;
_hotspotTiles.Remove(tile); _hotspotTiles.Remove(tile);
} }
public void AddSuperconductivityTile(TileAtmosphere? tile) public virtual void AddSuperconductivityTile(TileAtmosphere? tile)
{ {
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return; if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
if (tile?.GridIndex != mapGrid.Grid.Index) return; if (tile?.GridIndex != mapGrid.Grid.Index) return;
_superconductivityTiles.Add(tile); _superconductivityTiles.Add(tile);
} }
public void RemoveSuperconductivityTile(TileAtmosphere? tile) public virtual void RemoveSuperconductivityTile(TileAtmosphere? tile)
{ {
if (tile == null) return; if (tile == null) return;
_superconductivityTiles.Remove(tile); _superconductivityTiles.Remove(tile);
@@ -342,7 +331,7 @@ namespace Content.Server.GameObjects.Components.Atmos
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHighPressureDelta(TileAtmosphere? tile) public virtual void AddHighPressureDelta(TileAtmosphere? tile)
{ {
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return; if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
if (tile?.GridIndex != mapGrid.Grid.Index) return; if (tile?.GridIndex != mapGrid.Grid.Index) return;
@@ -351,21 +340,21 @@ namespace Content.Server.GameObjects.Components.Atmos
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasHighPressureDelta(TileAtmosphere tile) public virtual bool HasHighPressureDelta(TileAtmosphere tile)
{ {
return _highPressureDelta.Contains(tile); return _highPressureDelta.Contains(tile);
} }
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddExcitedGroup(ExcitedGroup excitedGroup) public virtual void AddExcitedGroup(ExcitedGroup excitedGroup)
{ {
_excitedGroups.Add(excitedGroup); _excitedGroups.Add(excitedGroup);
} }
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveExcitedGroup(ExcitedGroup excitedGroup) public virtual void RemoveExcitedGroup(ExcitedGroup excitedGroup)
{ {
_excitedGroups.Remove(excitedGroup); _excitedGroups.Remove(excitedGroup);
} }
@@ -401,7 +390,7 @@ namespace Content.Server.GameObjects.Components.Atmos
{ {
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return null; if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return null;
if (_tiles.TryGetValue(indices, out var tile)) return tile; if (Tiles.TryGetValue(indices, out var tile)) return tile;
// We don't have that tile! // We don't have that tile!
if (IsSpace(indices) && createSpace) if (IsSpace(indices) && createSpace)
@@ -454,7 +443,7 @@ namespace Content.Server.GameObjects.Components.Atmos
} }
/// <inheritdoc /> /// <inheritdoc />
public void Update(float frameTime) public virtual void Update(float frameTime)
{ {
_timer += frameTime; _timer += frameTime;
@@ -554,7 +543,7 @@ namespace Content.Server.GameObjects.Components.Atmos
UpdateCounter++; UpdateCounter++;
} }
public bool ProcessTileEqualize(bool resumed = false) public virtual bool ProcessTileEqualize(bool resumed = false)
{ {
_stopwatch.Restart(); _stopwatch.Restart();
@@ -581,7 +570,7 @@ namespace Content.Server.GameObjects.Components.Atmos
return true; return true;
} }
public bool ProcessActiveTiles(bool resumed = false) public virtual bool ProcessActiveTiles(bool resumed = false)
{ {
_stopwatch.Restart(); _stopwatch.Restart();
@@ -608,7 +597,7 @@ namespace Content.Server.GameObjects.Components.Atmos
return true; return true;
} }
public bool ProcessExcitedGroups(bool resumed = false) public virtual bool ProcessExcitedGroups(bool resumed = false)
{ {
_stopwatch.Restart(); _stopwatch.Restart();
@@ -642,7 +631,7 @@ namespace Content.Server.GameObjects.Components.Atmos
return true; return true;
} }
public bool ProcessHighPressureDelta(bool resumed = false) public virtual bool ProcessHighPressureDelta(bool resumed = false)
{ {
_stopwatch.Restart(); _stopwatch.Restart();
@@ -672,7 +661,7 @@ namespace Content.Server.GameObjects.Components.Atmos
return true; return true;
} }
private bool ProcessHotspots(bool resumed = false) protected virtual bool ProcessHotspots(bool resumed = false)
{ {
_stopwatch.Restart(); _stopwatch.Restart();
@@ -699,7 +688,7 @@ namespace Content.Server.GameObjects.Components.Atmos
return true; return true;
} }
private bool ProcessSuperconductivity(bool resumed = false) protected virtual bool ProcessSuperconductivity(bool resumed = false)
{ {
_stopwatch.Restart(); _stopwatch.Restart();
@@ -726,7 +715,7 @@ namespace Content.Server.GameObjects.Components.Atmos
return true; return true;
} }
private bool ProcessPipeNets(bool resumed = false) protected virtual bool ProcessPipeNets(bool resumed = false)
{ {
_stopwatch.Restart(); _stopwatch.Restart();
@@ -753,7 +742,7 @@ namespace Content.Server.GameObjects.Components.Atmos
return true; return true;
} }
private bool ProcessPipeNetDevices(bool resumed = false) protected virtual bool ProcessPipeNetDevices(bool resumed = false)
{ {
_stopwatch.Restart(); _stopwatch.Restart();
@@ -761,7 +750,7 @@ namespace Content.Server.GameObjects.Components.Atmos
_currentRunPipeNetDevice = new Queue<PipeNetDeviceComponent>(_pipeNetDevices); _currentRunPipeNetDevice = new Queue<PipeNetDeviceComponent>(_pipeNetDevices);
var number = 0; var number = 0;
while (_currentRunPipeNet.Count > 0) while (_currentRunPipeNetDevice.Count > 0)
{ {
var device = _currentRunPipeNetDevice.Dequeue(); var device = _currentRunPipeNetDevice.Dequeue();
device.Update(); device.Update();
@@ -810,11 +799,11 @@ namespace Content.Server.GameObjects.Components.Atmos
!serializer.TryReadDataField("tiles", out Dictionary<MapIndices, int>? tiles)) !serializer.TryReadDataField("tiles", out Dictionary<MapIndices, int>? tiles))
return; return;
_tiles.Clear(); Tiles.Clear();
foreach (var (indices, mix) in tiles!) foreach (var (indices, mix) in tiles!)
{ {
_tiles.Add(indices, new TileAtmosphere(this, gridId, indices, (GasMixture)uniqueMixes![mix].Clone())); Tiles.Add(indices, new TileAtmosphere(this, gridId, indices, (GasMixture)uniqueMixes![mix].Clone()));
Invalidate(indices); Invalidate(indices);
} }
} }
@@ -823,7 +812,7 @@ namespace Content.Server.GameObjects.Components.Atmos
var uniqueMixes = new List<GasMixture>(); var uniqueMixes = new List<GasMixture>();
var uniqueMixHash = new Dictionary<GasMixture, int>(); var uniqueMixHash = new Dictionary<GasMixture, int>();
var tiles = new Dictionary<MapIndices, int>(); var tiles = new Dictionary<MapIndices, int>();
foreach (var (indices, tile) in _tiles) foreach (var (indices, tile) in Tiles)
{ {
if (tile.Air == null) continue; if (tile.Air == null) continue;
@@ -846,7 +835,7 @@ namespace Content.Server.GameObjects.Components.Atmos
public IEnumerator<TileAtmosphere> GetEnumerator() public IEnumerator<TileAtmosphere> GetEnumerator()
{ {
return _tiles.Values.GetEnumerator(); return Tiles.Values.GetEnumerator();
} }
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
@@ -855,7 +844,7 @@ namespace Content.Server.GameObjects.Components.Atmos
} }
/// <inheritdoc /> /// <inheritdoc />
public void BurnTile(MapIndices gridIndices) public virtual void BurnTile(MapIndices gridIndices)
{ {
// TODO ATMOS // TODO ATMOS
} }

View File

@@ -1,6 +1,9 @@
using Content.Server.Atmos; using Content.Server.Atmos;
using Content.Server.GameObjects.Components.NodeContainer; using Content.Server.GameObjects.Components.NodeContainer;
using Content.Server.GameObjects.Components.NodeContainer.Nodes; using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Shared.GameObjects.Components.Atmos;
using Content.Shared.GameObjects.Atmos;
using Robust.Server.GameObjects;
using Robust.Shared.Log; using Robust.Shared.Log;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -13,6 +16,21 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping
/// </summary> /// </summary>
public abstract class BasePumpComponent : PipeNetDeviceComponent public abstract class BasePumpComponent : PipeNetDeviceComponent
{ {
/// <summary>
/// If the pump is currently pumping.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public bool PumpEnabled
{
get => _pumpEnabled;
set
{
_pumpEnabled = value;
UpdateAppearance();
}
}
private bool _pumpEnabled = true;
/// <summary> /// <summary>
/// Needs to be same <see cref="PipeDirection"/> as that of a <see cref="Pipe"/> on this entity. /// Needs to be same <see cref="PipeDirection"/> as that of a <see cref="Pipe"/> on this entity.
/// </summary> /// </summary>
@@ -31,6 +49,8 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping
[ViewVariables] [ViewVariables]
private PipeNode _outletPipe; private PipeNode _outletPipe;
private AppearanceComponent _appearance;
public override void ExposeData(ObjectSerializer serializer) public override void ExposeData(ObjectSerializer serializer)
{ {
base.ExposeData(serializer); base.ExposeData(serializer);
@@ -56,13 +76,23 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping
Logger.Error($"{typeof(BasePumpComponent)} on entity {Owner.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); Logger.Error($"{typeof(BasePumpComponent)} on entity {Owner.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}.");
return; return;
} }
Owner.TryGetComponent(out _appearance);
UpdateAppearance();
} }
public override void Update() public override void Update()
{ {
if (!PumpEnabled)
return;
PumpGas(_inletPipe.Air, _outletPipe.Air); PumpGas(_inletPipe.Air, _outletPipe.Air);
} }
protected abstract void PumpGas(GasMixture inletGas, GasMixture outletGas); protected abstract void PumpGas(GasMixture inletGas, GasMixture outletGas);
private void UpdateAppearance()
{
_appearance?.SetData(PumpVisuals.VisualState, new PumpVisualState(_inletDirection, _outletDirection, _inletPipe.ConduitLayer, _outletPipe.ConduitLayer, PumpEnabled));
}
} }
} }

View File

@@ -1,21 +0,0 @@
using Content.Server.Atmos;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Atmos.Piping
{
/// <summary>
/// Placeholder example of pump functionality.
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(BasePumpComponent))]
public class DebugPumpComponent : BasePumpComponent
{
public override string Name => "DebugPump";
protected override void PumpGas(GasMixture inletGas, GasMixture outletGas)
{
outletGas.Merge(inletGas);
inletGas.Clear();
}
}
}

View File

@@ -0,0 +1,65 @@
using Content.Server.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using System;
namespace Content.Server.GameObjects.Components.Atmos.Piping.Pumps
{
[RegisterComponent]
[ComponentReference(typeof(BasePumpComponent))]
public class PressurePumpComponent : BasePumpComponent
{
public override string Name => "PressurePump";
/// <summary>
/// The pressure this pump will try to bring its oulet too.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int PressurePumpTarget
{
get => _pressurePumpTarget;
set => _pressurePumpTarget = Math.Clamp(value, 0, MaxPressurePumpTarget);
}
private int _pressurePumpTarget;
/// <summary>
/// Max value <see cref="PressurePumpTarget"/> can be set to.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int MaxPressurePumpTarget
{
get => _maxPressurePumpTarget;
set => Math.Max(value, 0);
}
private int _maxPressurePumpTarget;
/// <summary>
/// Every upate, this pump will only increase the outlet pressure by this fraction of the amount needed to reach the <see cref="PressurePumpTarget"/>.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float TransferRatio
{
get => _transferRatio;
set => _transferRatio = Math.Clamp(value, 0, 1);
}
private float _transferRatio;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _pressurePumpTarget, "startingPressurePumpTarget", 0);
serializer.DataField(ref _maxPressurePumpTarget, "maxPressurePumpTarget", 100);
serializer.DataField(ref _transferRatio, "transferRatio", 0.5f);
}
protected override void PumpGas(GasMixture inletGas, GasMixture outletGas)
{
var goalDiff = PressurePumpTarget - outletGas.Pressure;
var realGoalPressureDiff = goalDiff * TransferRatio;
var realTargetPressure = outletGas.Pressure + realGoalPressureDiff;
var realCappedTargetPressure = Math.Max(realTargetPressure, outletGas.Pressure); //no lowering the outlet's pressure
inletGas.PumpGasTo(outletGas, realCappedTargetPressure);
}
}
}

View File

@@ -0,0 +1,46 @@
using Content.Server.Atmos;
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using System;
using System.Diagnostics;
namespace Content.Server.GameObjects.Components.Atmos.Piping.Pumps
{
[RegisterComponent]
[ComponentReference(typeof(BasePumpComponent))]
public class VolumePumpComponent : BasePumpComponent
{
[ViewVariables(VVAccess.ReadWrite)]
public int VolumePumpRate
{
get => _volumePumpRate;
set => _volumePumpRate = Math.Clamp(value, 0, MaxVolumePumpRate);
}
private int _volumePumpRate;
[ViewVariables(VVAccess.ReadWrite)]
public int MaxVolumePumpRate
{
get => _maxVolumePumpRate;
set => Math.Max(value, 0);
}
private int _maxVolumePumpRate;
public override string Name => "VolumePump";
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _volumePumpRate, "startingVolumePumpRate", 0);
serializer.DataField(ref _maxVolumePumpRate, "maxVolumePumpRate", 100);
}
protected override void PumpGas(GasMixture inletGas, GasMixture outletGas)
{
var volumeRatio = Math.Clamp(VolumePumpRate / inletGas.Volume, 0, 1);
outletGas.Merge(inletGas.RemoveRatio(volumeRatio));
}
}
}

View File

@@ -0,0 +1,103 @@
#nullable enable
using System;
using Content.Server.Atmos;
using Content.Shared.Atmos;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Map;
using Robust.Shared.Map;
namespace Content.Server.GameObjects.Components.Atmos
{
[RegisterComponent]
[ComponentReference(typeof(IGridAtmosphereComponent))]
[ComponentReference(typeof(GridAtmosphereComponent))]
[Serializable]
public class UnsimulatedGridAtmosphereComponent : GridAtmosphereComponent, IGridAtmosphereComponent
{
public override string Name => "UnsimulatedGridAtmosphere";
public override void PryTile(MapIndices indices) { }
public override void RepopulateTiles()
{
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
foreach (var tile in mapGrid.Grid.GetAllTiles())
{
if(!Tiles.ContainsKey(tile.GridIndices))
Tiles.Add(tile.GridIndices, new TileAtmosphere(this, tile.GridIndex, tile.GridIndices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}));
}
}
public override void Invalidate(MapIndices indices) { }
protected override void Revalidate() { }
public override void FixVacuum(MapIndices indices) { }
public override void AddActiveTile(TileAtmosphere? tile) { }
public override void RemoveActiveTile(TileAtmosphere? tile) { }
public override void AddHotspotTile(TileAtmosphere? tile) { }
public override void RemoveHotspotTile(TileAtmosphere? tile) { }
public override void AddSuperconductivityTile(TileAtmosphere? tile) { }
public override void RemoveSuperconductivityTile(TileAtmosphere? tile) { }
public override void AddHighPressureDelta(TileAtmosphere? tile) { }
public override bool HasHighPressureDelta(TileAtmosphere tile)
{
return false;
}
public override void AddExcitedGroup(ExcitedGroup excitedGroup) { }
public override void RemoveExcitedGroup(ExcitedGroup excitedGroup) { }
public override void Update(float frameTime) { }
public override bool ProcessTileEqualize(bool resumed = false)
{
return false;
}
public override bool ProcessActiveTiles(bool resumed = false)
{
return false;
}
public override bool ProcessExcitedGroups(bool resumed = false)
{
return false;
}
public override bool ProcessHighPressureDelta(bool resumed = false)
{
return false;
}
protected override bool ProcessHotspots(bool resumed = false)
{
return false;
}
protected override bool ProcessSuperconductivity(bool resumed = false)
{
return false;
}
protected override bool ProcessPipeNets(bool resumed = false)
{
return false;
}
protected override bool ProcessPipeNetDevices(bool resumed = false)
{
return false;
}
}
}

View File

@@ -8,7 +8,6 @@ using Content.Server.Body.Network;
using Content.Server.GameObjects.Components.Metabolism; using Content.Server.GameObjects.Components.Metabolism;
using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameObjects.Components.Interaction; using Content.Server.Interfaces.GameObjects.Components.Interaction;
using Content.Server.Mobs;
using Content.Server.Observer; using Content.Server.Observer;
using Content.Shared.Body.Part; using Content.Shared.Body.Part;
using Content.Shared.Body.Part.Properties.Movement; using Content.Shared.Body.Part.Properties.Movement;
@@ -20,6 +19,7 @@ using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.GameObjects.Components.Movement; using Content.Shared.GameObjects.Components.Movement;
using Robust.Server.Interfaces.Player; using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Reflection; using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.IoC; using Robust.Shared.IoC;
@@ -33,13 +33,14 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Body namespace Content.Server.GameObjects.Components.Body
{ {
/// <summary> /// <summary>
/// Component representing a collection of <see cref="BodyPart"></see> /// Component representing a collection of <see cref="IBodyPart"></see>
/// attached to each other. /// attached to each other.
/// </summary> /// </summary>
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(IDamageableComponent))] [ComponentReference(typeof(IDamageableComponent))]
[ComponentReference(typeof(ISharedBodyManagerComponent))]
[ComponentReference(typeof(IBodyManagerComponent))] [ComponentReference(typeof(IBodyManagerComponent))]
public class BodyManagerComponent : SharedBodyManagerComponent, IBodyPartContainer, IRelayMoveInput public class BodyManagerComponent : SharedBodyManagerComponent, IBodyPartContainer, IRelayMoveInput, IBodyManagerComponent
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IBodyNetworkFactory _bodyNetworkFactory = default!; [Dependency] private readonly IBodyNetworkFactory _bodyNetworkFactory = default!;
@@ -47,38 +48,28 @@ namespace Content.Server.GameObjects.Components.Body
[ViewVariables] private string _presetName = default!; [ViewVariables] private string _presetName = default!;
private readonly Dictionary<string, BodyPart> _parts = new Dictionary<string, BodyPart>(); private readonly Dictionary<string, IBodyPart> _parts = new Dictionary<string, IBodyPart>();
[ViewVariables] private readonly Dictionary<Type, BodyNetwork> _networks = new Dictionary<Type, BodyNetwork>(); [ViewVariables] private readonly Dictionary<Type, BodyNetwork> _networks = new Dictionary<Type, BodyNetwork>();
/// <summary> /// <summary>
/// All <see cref="BodyPart"></see> with <see cref="LegProperty"></see> /// All <see cref="IBodyPart"></see> with <see cref="LegProperty"></see>
/// that are currently affecting move speed, mapped to how big that leg /// that are currently affecting move speed, mapped to how big that leg
/// they're on is. /// they're on is.
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
private readonly Dictionary<BodyPart, float> _activeLegs = new Dictionary<BodyPart, float>(); private readonly Dictionary<IBodyPart, float> _activeLegs = new Dictionary<IBodyPart, float>();
[ViewVariables] public BodyTemplate Template { get; private set; } = default!;
[ViewVariables] public BodyPreset Preset { get; private set; } = default!;
/// <summary> /// <summary>
/// The <see cref="BodyTemplate"/> that this <see cref="BodyManagerComponent"/> /// Maps <see cref="BodyTemplate"/> slot name to the <see cref="IBodyPart"/>
/// is adhering to.
/// </summary>
[ViewVariables]
public BodyTemplate Template { get; private set; } = default!;
/// <summary>
/// The <see cref="BodyPreset"/> that this <see cref="BodyManagerComponent"/>
/// is adhering to.
/// </summary>
[ViewVariables]
public BodyPreset Preset { get; private set; } = default!;
/// <summary>
/// Maps <see cref="BodyTemplate"/> slot name to the <see cref="BodyPart"/>
/// object filling it (if there is one). /// object filling it (if there is one).
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public IReadOnlyDictionary<string, BodyPart> Parts => _parts; public IReadOnlyDictionary<string, IBodyPart> Parts => _parts;
/// <summary> /// <summary>
/// List of all slots in this body, taken from the keys of /// List of all slots in this body, taken from the keys of
@@ -170,7 +161,7 @@ namespace Content.Server.GameObjects.Components.Body
// Add a new BodyPart with the BodyPartPrototype as a baseline to our // Add a new BodyPart with the BodyPartPrototype as a baseline to our
// BodyComponent. // BodyComponent.
var addedPart = new BodyPart(newPartData); var addedPart = new BodyPart(newPartData);
AddBodyPart(addedPart, slotName); TryAddPart(slotName, addedPart);
} }
OnBodyChanged(); // TODO: Duplicate code OnBodyChanged(); // TODO: Duplicate code
@@ -179,8 +170,8 @@ namespace Content.Server.GameObjects.Components.Body
/// <summary> /// <summary>
/// Changes the current <see cref="BodyTemplate"/> to the given /// Changes the current <see cref="BodyTemplate"/> to the given
/// <see cref="BodyTemplate"/>. /// <see cref="BodyTemplate"/>.
/// Attempts to keep previous <see cref="BodyPart"/> if there is a slot for /// Attempts to keep previous <see cref="IBodyPart"/> if there is a
/// them in both <see cref="BodyTemplate"/>. /// slot for them in both <see cref="BodyTemplate"/>.
/// </summary> /// </summary>
public void ChangeBodyTemplate(BodyTemplatePrototype newTemplate) public void ChangeBodyTemplate(BodyTemplatePrototype newTemplate)
{ {
@@ -279,25 +270,20 @@ namespace Content.Server.GameObjects.Components.Body
foreach (var (key, value) in _activeLegs) foreach (var (key, value) in _activeLegs)
{ {
if (key.TryGetProperty(out LegProperty legProperty)) if (key.TryGetProperty(out LegProperty? leg))
{ {
// Speed of a leg = base speed * (1+log1024(leg length)) // Speed of a leg = base speed * (1+log1024(leg length))
speedSum += legProperty.Speed * (1 + (float) Math.Log(value, 1024.0)); speedSum += leg.Speed * (1 + (float) Math.Log(value, 1024.0));
} }
} }
if (speedSum <= 0.001f || _activeLegs.Count <= 0) if (speedSum <= 0.001f || _activeLegs.Count <= 0)
{ {
// Case: no way of moving. Fall down.
StandingStateHelper.Down(Owner);
playerMover.BaseWalkSpeed = 0.8f; playerMover.BaseWalkSpeed = 0.8f;
playerMover.BaseSprintSpeed = 2.0f; playerMover.BaseSprintSpeed = 2.0f;
} }
else else
{ {
// Case: have at least one leg. Set move speed.
StandingStateHelper.Standing(Owner);
// Extra legs stack diminishingly. // Extra legs stack diminishingly.
// Final speed = speed sum/(leg count-log4(leg count)) // Final speed = speed sum/(leg count-log4(leg count))
playerMover.BaseWalkSpeed = playerMover.BaseWalkSpeed =
@@ -320,11 +306,11 @@ namespace Content.Server.GameObjects.Components.Body
/// <summary> /// <summary>
/// Recursively searches for if <see cref="target"/> is connected to /// Recursively searches for if <see cref="target"/> is connected to
/// the center. Not efficient (O(n^2)), but most bodies don't have a ton /// the center. Not efficient (O(n^2)), but most bodies don't have a ton
/// of <see cref="BodyPart"/>s. /// of <see cref="IBodyPart"/>s.
/// </summary> /// </summary>
/// <param name="target">The body part to find the center for.</param> /// <param name="target">The body part to find the center for.</param>
/// <returns>True if it is connected to the center, false otherwise.</returns> /// <returns>True if it is connected to the center, false otherwise.</returns>
private bool ConnectedToCenterPart(BodyPart target) private bool ConnectedToCenterPart(IBodyPart target)
{ {
var searchedSlots = new List<string>(); var searchedSlots = new List<string>();
@@ -334,9 +320,7 @@ namespace Content.Server.GameObjects.Components.Body
private bool ConnectedToCenterPartRecursion(ICollection<string> searchedSlots, string slotName) private bool ConnectedToCenterPartRecursion(ICollection<string> searchedSlots, string slotName)
{ {
TryGetBodyPart(slotName, out var part); if (!TryGetBodyPart(slotName, out var part))
if (part == null)
{ {
return false; return false;
} }
@@ -366,14 +350,14 @@ namespace Content.Server.GameObjects.Components.Body
} }
/// <summary> /// <summary>
/// Finds the central <see cref="BodyPart"/>, if any, of this body based on /// Finds the central <see cref="IBodyPart"/>, if any, of this body based on
/// the <see cref="BodyTemplate"/>. For humans, this is the torso. /// the <see cref="BodyTemplate"/>. For humans, this is the torso.
/// </summary> /// </summary>
/// <returns>The <see cref="BodyPart"/> if one exists, null otherwise.</returns> /// <returns>The <see cref="BodyPart"/> if one exists, null otherwise.</returns>
private BodyPart? GetCenterBodyPart() private IBodyPart? GetCenterBodyPart()
{ {
Parts.TryGetValue(Template.CenterSlot, out var center); Parts.TryGetValue(Template.CenterSlot, out var center);
return center!; return center;
} }
/// <summary> /// <summary>
@@ -386,29 +370,31 @@ namespace Content.Server.GameObjects.Components.Body
} }
/// <summary> /// <summary>
/// Finds the <see cref="BodyPart"/> in the given <see cref="slotName"/> if /// Finds the <see cref="IBodyPart"/> in the given <see cref="slotName"/> if
/// one exists. /// one exists.
/// </summary> /// </summary>
/// <param name="slotName">The slot to search in.</param> /// <param name="slotName">The slot to search in.</param>
/// <param name="result">The body part in that slot, if any.</param> /// <param name="result">The body part in that slot, if any.</param>
/// <returns>True if found, false otherwise.</returns> /// <returns>True if found, false otherwise.</returns>
private bool TryGetBodyPart(string slotName, [NotNullWhen(true)] out BodyPart result) private bool TryGetBodyPart(string slotName, [NotNullWhen(true)] out IBodyPart? result)
{ {
return Parts.TryGetValue(slotName, out result!); return Parts.TryGetValue(slotName, out result!);
} }
/// <summary> /// <summary>
/// Finds the slotName that the given <see cref="BodyPart"/> resides in. /// Finds the slotName that the given <see cref="IBodyPart"/> resides in.
/// </summary> /// </summary>
/// <param name="part">The <see cref="BodyPart"/> to find the slot for.</param> /// <param name="part">The <see cref="IBodyPart"/> to find the slot for.</param>
/// <param name="result">The slot found, if any.</param> /// <param name="result">The slot found, if any.</param>
/// <returns>True if a slot was found, false otherwise</returns> /// <returns>True if a slot was found, false otherwise</returns>
private bool TryGetSlotName(BodyPart part, [NotNullWhen(true)] out string result) private bool TryGetSlotName(IBodyPart part, [NotNullWhen(true)] out string result)
{ {
// We enforce that there is only one of each value in the dictionary, // 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. // so we can iterate through the dictionary values to get the key from there.
result = Parts.FirstOrDefault(x => x.Value == part).Key; var pair = Parts.FirstOrDefault(x => x.Value == part);
return result != null; result = pair.Key;
return !pair.Equals(default);
} }
/// <summary> /// <summary>
@@ -447,7 +433,7 @@ namespace Content.Server.GameObjects.Components.Body
/// True if successful, false if there was an error or no connected /// True if successful, false if there was an error or no connected
/// <see cref="BodyPart"/>s were found. /// <see cref="BodyPart"/>s were found.
/// </returns> /// </returns>
public bool TryGetBodyPartConnections(string slotName, [NotNullWhen(true)] out List<BodyPart> result) public bool TryGetBodyPartConnections(string slotName, [NotNullWhen(true)] out List<IBodyPart> result)
{ {
result = null!; result = null!;
@@ -456,7 +442,7 @@ namespace Content.Server.GameObjects.Components.Body
return false; return false;
} }
var toReturn = new List<BodyPart>(); var toReturn = new List<IBodyPart>();
foreach (var connection in connections) foreach (var connection in connections)
{ {
if (TryGetBodyPart(connection, out var bodyPartResult)) if (TryGetBodyPart(connection, out var bodyPartResult))
@@ -480,9 +466,9 @@ namespace Content.Server.GameObjects.Components.Body
/// </summary> /// </summary>
/// <returns> /// <returns>
/// True if successful, false if there was an error or no connected /// True if successful, false if there was an error or no connected
/// <see cref="BodyPart"/>s were found. /// <see cref="IBodyPart"/>s were found.
/// </returns> /// </returns>
private bool TryGetBodyPartConnections(BodyPart part, [NotNullWhen(true)] out List<BodyPart> result) private bool TryGetBodyPartConnections(IBodyPart part, [NotNullWhen(true)] out List<IBodyPart> result)
{ {
result = null!; result = null!;
@@ -491,11 +477,11 @@ namespace Content.Server.GameObjects.Components.Body
} }
/// <summary> /// <summary>
/// Grabs all <see cref="BodyPart"/> of the given type in this body. /// Grabs all <see cref="IBodyPart"/> of the given type in this body.
/// </summary> /// </summary>
public List<BodyPart> GetBodyPartsOfType(BodyPartType type) public List<IBodyPart> GetBodyPartsOfType(BodyPartType type)
{ {
var toReturn = new List<BodyPart>(); var toReturn = new List<IBodyPart>();
foreach (var part in Parts.Values) foreach (var part in Parts.Values)
{ {
@@ -508,32 +494,6 @@ namespace Content.Server.GameObjects.Components.Body
return toReturn; return toReturn;
} }
/// <summary>
/// Installs the given <see cref="BodyPart"/> into the given slot.
/// </summary>
/// <returns>True if successful, false otherwise.</returns>
public bool InstallBodyPart(BodyPart part, string slotName)
{
DebugTools.AssertNotNull(part);
// Make sure the given slot exists
if (!SlotExists(slotName))
{
return false;
}
// And that nothing is in it
if (TryGetBodyPart(slotName, out _))
{
return false;
}
AddBodyPart(part, slotName); // TODO: Sort this duplicate out
OnBodyChanged();
return true;
}
/// <summary> /// <summary>
/// Installs the given <see cref="DroppedBodyPartComponent"/> into the /// Installs the given <see cref="DroppedBodyPartComponent"/> into the
/// given slot, deleting the <see cref="IEntity"/> afterwards. /// given slot, deleting the <see cref="IEntity"/> afterwards.
@@ -543,7 +503,7 @@ namespace Content.Server.GameObjects.Components.Body
{ {
DebugTools.AssertNotNull(part); DebugTools.AssertNotNull(part);
if (!InstallBodyPart(part.ContainedBodyPart, slotName)) if (!TryAddPart(slotName, part.ContainedBodyPart))
{ {
return false; return false;
} }
@@ -553,15 +513,15 @@ namespace Content.Server.GameObjects.Components.Body
} }
/// <summary> /// <summary>
/// Disconnects the given <see cref="BodyPart"/> reference, potentially /// Disconnects the given <see cref="IBodyPart"/> reference, potentially
/// dropping other <see cref="BodyPart">BodyParts</see> if they were hanging /// dropping other <see cref="IBodyPart">BodyParts</see> if they were hanging
/// off of it. /// off of it.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// The <see cref="IEntity"/> representing the dropped /// The <see cref="IEntity"/> representing the dropped
/// <see cref="BodyPart"/>, or null if none was dropped. /// <see cref="IBodyPart"/>, or null if none was dropped.
/// </returns> /// </returns>
public IEntity? DropPart(BodyPart part) public IEntity? DropPart(IBodyPart part)
{ {
DebugTools.AssertNotNull(part); DebugTools.AssertNotNull(part);
@@ -595,36 +555,18 @@ namespace Content.Server.GameObjects.Components.Body
} }
/// <summary> /// <summary>
/// Disconnects the given <see cref="BodyPart"/> reference, potentially /// Disconnects the given <see cref="IBodyPart"/> reference, potentially
/// dropping other <see cref="BodyPart">BodyParts</see> if they were hanging /// dropping other <see cref="IBodyPart">BodyParts</see> if they were hanging
/// off of it. /// off of it.
/// </summary> /// </summary>
public void DisconnectBodyPart(BodyPart part, bool dropEntity) public void DisconnectBodyPart(IBodyPart part, bool dropEntity)
{ {
DebugTools.AssertNotNull(part); DebugTools.AssertNotNull(part);
if (!_parts.ContainsValue(part)) var slotName = _parts.FirstOrDefault(x => x.Value == part).Key;
{ if (string.IsNullOrEmpty(slotName)) return;
return; DisconnectBodyPart(slotName, dropEntity);
}
var slotName = Parts.FirstOrDefault(x => x.Value == part).Key;
RemoveBodyPart(slotName, dropEntity);
// Call disconnect on all limbs that were hanging off this limb
if (TryGetBodyPartConnections(slotName, out List<string> connections))
{
// TODO: Optimize
foreach (var connectionName in connections)
{
if (TryGetBodyPart(connectionName, out var result) && !ConnectedToCenterPart(result))
{
DisconnectBodyPart(connectionName, dropEntity);
}
}
}
OnBodyChanged();
} }
/// <summary> /// <summary>
@@ -639,12 +581,7 @@ namespace Content.Server.GameObjects.Components.Body
{ {
DebugTools.AssertNotNull(slotName); DebugTools.AssertNotNull(slotName);
if (!TryGetBodyPart(slotName, out var part)) if (!HasPart(slotName))
{
return;
}
if (part == null)
{ {
return; return;
} }
@@ -665,27 +602,47 @@ namespace Content.Server.GameObjects.Components.Body
OnBodyChanged(); OnBodyChanged();
} }
private void AddBodyPart(BodyPart part, string slotName) public bool TryAddPart(string slot, IBodyPart part, bool force = false)
{ {
DebugTools.AssertNotNull(part); DebugTools.AssertNotNull(part);
DebugTools.AssertNotNull(slotName); DebugTools.AssertNotNull(slot);
_parts.Add(slotName, part); // Make sure the given slot exists
if (!force)
{
if (!SlotExists(slot))
{
return false;
}
// And that nothing is in it
if (!_parts.TryAdd(slot, part))
{
return false;
}
}
else
{
_parts[slot] = part;
}
part.Body = this; part.Body = this;
var argsAdded = new BodyPartAddedEventArgs(part, slotName); var argsAdded = new BodyPartAddedEventArgs(part, slot);
foreach (var component in Owner.GetAllComponents<IBodyPartAdded>().ToArray()) foreach (var component in Owner.GetAllComponents<IBodyPartAdded>().ToArray())
{ {
component.BodyPartAdded(argsAdded); component.BodyPartAdded(argsAdded);
} }
if (!Template.Layers.TryGetValue(slotName, out var partMap) || // TODO: Sort this duplicate out
OnBodyChanged();
if (!Template.Layers.TryGetValue(slot, out var partMap) ||
!_reflectionManager.TryParseEnumReference(partMap, out var partEnum)) !_reflectionManager.TryParseEnumReference(partMap, out var partEnum))
{ {
Logger.Warning($"Template {Template.Name} has an invalid RSI map key {partMap} for body part {part.Name}."); Logger.Warning($"Template {Template.Name} has an invalid RSI map key {partMap} for body part {part.Name}.");
return; return false;
} }
part.RSIMap = partEnum; part.RSIMap = partEnum;
@@ -711,6 +668,13 @@ namespace Content.Server.GameObjects.Components.Body
SendNetworkMessage(mechanismMessage); SendNetworkMessage(mechanismMessage);
} }
return true;
}
public bool HasPart(string slot)
{
return _parts.ContainsKey(slot);
} }
/// <summary> /// <summary>
@@ -719,7 +683,7 @@ namespace Content.Server.GameObjects.Components.Body
/// </summary> /// </summary>
/// <param name="slotName">The slot to remove it from.</param> /// <param name="slotName">The slot to remove it from.</param>
/// <param name="drop"> /// <param name="drop">
/// Whether or not to drop the removed <see cref="BodyPart"/>. /// Whether or not to drop the removed <see cref="IBodyPart"/>.
/// </param> /// </param>
/// <returns></returns> /// <returns></returns>
private bool RemoveBodyPart(string slotName, bool drop) private bool RemoveBodyPart(string slotName, bool drop)
@@ -770,6 +734,21 @@ namespace Content.Server.GameObjects.Components.Body
SendNetworkMessage(mechanismMessage); 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();
}
return true; return true;
} }
@@ -779,17 +758,20 @@ namespace Content.Server.GameObjects.Components.Body
/// <param name="part">The part to remove from this body.</param> /// <param name="part">The part to remove from this body.</param>
/// <param name="slotName">The slot that the part was in, if any.</param> /// <param name="slotName">The slot that the part was in, if any.</param>
/// <returns>True if <see cref="part"/> was removed, false otherwise.</returns> /// <returns>True if <see cref="part"/> was removed, false otherwise.</returns>
private bool RemoveBodyPart(BodyPart part, [NotNullWhen(true)] out string slotName) private bool RemoveBodyPart(IBodyPart part, [NotNullWhen(true)] out string? slotName)
{ {
DebugTools.AssertNotNull(part); DebugTools.AssertNotNull(part);
slotName = _parts.FirstOrDefault(pair => pair.Value == part).Key; var pair = _parts.FirstOrDefault(kvPair => kvPair.Value == part);
if (slotName == null) if (pair.Equals(default))
{ {
slotName = null;
return false; return false;
} }
slotName = pair.Key;
return RemoveBodyPart(slotName, false); return RemoveBodyPart(slotName, false);
} }
@@ -803,13 +785,13 @@ namespace Content.Server.GameObjects.Components.Body
if (_networks.ContainsKey(network.GetType())) if (_networks.ContainsKey(network.GetType()))
{ {
return false; return true;
} }
_networks.Add(network.GetType(), network); _networks.Add(network.GetType(), network);
network.OnAdd(Owner); network.OnAdd(Owner);
return true; return false;
} }
/// <summary> /// <summary>
@@ -843,62 +825,21 @@ namespace Content.Server.GameObjects.Components.Body
return EnsureNetwork(typeof(T)); return EnsureNetwork(typeof(T));
} }
/// <summary> public void RemoveNetwork(Type networkType)
/// Attempts to add a <see cref="BodyNetwork"/> of the given name 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>
private bool EnsureNetwork(string networkName)
{ {
DebugTools.AssertNotNull(networkName); DebugTools.AssertNotNull(networkType);
var network = _bodyNetworkFactory.GetNetwork(networkName); if (_networks.Remove(networkType, out var network))
return EnsureNetwork(network);
}
/// <summary>
/// Removes the <see cref="BodyNetwork"/> of the given type in this body,
/// if there is one.
/// </summary>
/// <param name="type">The type of the network to remove.</param>
public void RemoveNetwork(Type type)
{
DebugTools.AssertNotNull(type);
if (_networks.Remove(type, out var network))
{ {
network.OnRemove(); network.OnRemove();
} }
} }
/// <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>
public void RemoveNetwork<T>() where T : BodyNetwork public void RemoveNetwork<T>() where T : BodyNetwork
{ {
RemoveNetwork(typeof(T)); RemoveNetwork(typeof(T));
} }
/// <summary>
/// Removes the <see cref="BodyNetwork"/> with the given name in this body,
/// if there is one.
/// </summary>
private void RemoveNetwork(string networkName)
{
var type = _bodyNetworkFactory.GetNetwork(networkName).GetType();
if (_networks.Remove(type, out var network))
{
network.OnRemove();
}
}
/// <summary> /// <summary>
/// Attempts to get the <see cref="BodyNetwork"/> of the given type in this body. /// Attempts to get the <see cref="BodyNetwork"/> of the given type in this body.
/// </summary> /// </summary>
@@ -923,7 +864,7 @@ namespace Content.Server.GameObjects.Components.Body
/// a foot node from the given node. It can /// a foot node from the given node. It can
/// only search through BodyParts with <see cref="ExtensionProperty"/>. /// only search through BodyParts with <see cref="ExtensionProperty"/>.
/// </summary> /// </summary>
private static float DistanceToNearestFoot(BodyManagerComponent body, BodyPart source) private static float DistanceToNearestFoot(BodyManagerComponent body, IBodyPart source)
{ {
if (source.HasProperty<FootProperty>() && source.TryGetProperty<ExtensionProperty>(out var property)) if (source.HasProperty<FootProperty>() && source.TryGetProperty<ExtensionProperty>(out var property))
{ {
@@ -933,7 +874,8 @@ namespace Content.Server.GameObjects.Components.Body
return LookForFootRecursion(body, source, new List<BodyPart>()); return LookForFootRecursion(body, source, new List<BodyPart>());
} }
private static float LookForFootRecursion(BodyManagerComponent body, BodyPart current, // TODO: Make this not static and not keep me up at night
private static float LookForFootRecursion(BodyManagerComponent body, IBodyPart current,
ICollection<BodyPart> searchedParts) ICollection<BodyPart> searchedParts)
{ {
if (!current.TryGetProperty<ExtensionProperty>(out var extProperty)) if (!current.TryGetProperty<ExtensionProperty>(out var extProperty))

View File

@@ -56,7 +56,7 @@ 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, BodyPart> bodyParts) private BodyScannerInterfaceState InterfaceState(BodyTemplate template, IReadOnlyDictionary<string, IBodyPart> bodyParts)
{ {
var partsData = new Dictionary<string, BodyScannerBodyPartData>(); var partsData = new Dictionary<string, BodyScannerBodyPartData>();

View File

@@ -1,18 +1,17 @@
#nullable enable #nullable enable
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components;
using Content.Server.Body; using Content.Server.Body;
using Content.Server.Utility; using Content.Server.Utility;
using Content.Shared.Body.Surgery; using Content.Shared.Body.Surgery;
using Content.Shared.Interfaces;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.UserInterface; using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.Interfaces.GameObjects; using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player; using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -24,8 +23,6 @@ namespace Content.Server.GameObjects.Components.Body
[RegisterComponent] [RegisterComponent]
public class DroppedBodyPartComponent : Component, IAfterInteract, IBodyPartContainer public class DroppedBodyPartComponent : Component, IAfterInteract, IBodyPartContainer
{ {
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager = default!;
private readonly Dictionary<int, object> _optionsCache = new Dictionary<int, object>(); private readonly Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
private BodyManagerComponent? _bodyManagerComponentCache; private BodyManagerComponent? _bodyManagerComponentCache;
private int _idHash; private int _idHash;
@@ -101,7 +98,7 @@ namespace Content.Server.GameObjects.Components.Body
foreach (var connectedPart in parts) foreach (var connectedPart in parts)
{ {
if (!connectedPart.CanAttachBodyPart(ContainedBodyPart)) if (!connectedPart.CanAttachPart(ContainedBodyPart))
{ {
continue; continue;
} }
@@ -121,7 +118,7 @@ namespace Content.Server.GameObjects.Components.Body
} }
else // If surgery cannot be performed, show message saying so. else // If surgery cannot be performed, show message saying so.
{ {
_sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User, eventArgs.Target.PopupMessage(eventArgs.User,
Loc.GetString("You see no way to install {0:theName}.", Owner)); Loc.GetString("You see no way to install {0:theName}.", Owner));
} }
} }
@@ -147,7 +144,7 @@ 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))
{ {
_sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, _bodyManagerComponentCache.Owner.PopupMessage(_performerCache,
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));
} }
@@ -163,10 +160,7 @@ namespace Content.Server.GameObjects.Components.Body
message = Loc.GetString("You can't attach it!"); message = Loc.GetString("You can't attach it!");
} }
_sharedNotifyManager.PopupMessage( _bodyManagerComponentCache.Owner.PopupMessage(_performerCache, message);
_bodyManagerComponentCache.Owner,
_performerCache,
message);
} }
private void OpenSurgeryUI(IPlayerSession session) private void OpenSurgeryUI(IPlayerSession session)

View File

@@ -28,7 +28,6 @@ namespace Content.Server.GameObjects.Components.Body
[RegisterComponent] [RegisterComponent]
public class DroppedMechanismComponent : Component, IAfterInteract public class DroppedMechanismComponent : Component, IAfterInteract
{ {
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public sealed override string Name => "DroppedMechanism"; public sealed override string Name => "DroppedMechanism";
@@ -41,7 +40,7 @@ namespace Content.Server.GameObjects.Components.Body
private IEntity? _performerCache; private IEntity? _performerCache;
[ViewVariables] public Mechanism ContainedMechanism { get; private set; } = default!; [ViewVariables] public IMechanism ContainedMechanism { get; private set; } = default!;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GenericSurgeryUiKey.Key); [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GenericSurgeryUiKey.Key);
@@ -67,8 +66,7 @@ namespace Content.Server.GameObjects.Components.Body
if (!droppedBodyPart.ContainedBodyPart.TryInstallDroppedMechanism(this)) if (!droppedBodyPart.ContainedBodyPart.TryInstallDroppedMechanism(this))
{ {
_sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User, eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("You can't fit it in!"));
Loc.GetString("You can't fit it in!"));
} }
} }
} }
@@ -83,7 +81,7 @@ namespace Content.Server.GameObjects.Components.Body
} }
} }
public void InitializeDroppedMechanism(Mechanism data) public void InitializeDroppedMechanism(IMechanism data)
{ {
ContainedMechanism = data; ContainedMechanism = data;
Owner.Name = Loc.GetString(ContainedMechanism.Name); Owner.Name = Loc.GetString(ContainedMechanism.Name);
@@ -141,7 +139,7 @@ namespace Content.Server.GameObjects.Components.Body
} }
else // If surgery cannot be performed, show message saying so. else // If surgery cannot be performed, show message saying so.
{ {
_sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User, eventArgs.Target.PopupMessage(eventArgs.User,
Loc.GetString("You see no way to install the {0}.", Owner.Name)); Loc.GetString("You see no way to install the {0}.", Owner.Name));
} }
} }
@@ -167,7 +165,7 @@ 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))
{ {
_sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache, _bodyManagerComponentCache.Owner.PopupMessage(_performerCache,
Loc.GetString("You see no useful way to use the {0} anymore.", Owner.Name)); Loc.GetString("You see no useful way to use the {0} anymore.", Owner.Name));
return; return;
} }
@@ -177,10 +175,7 @@ namespace Content.Server.GameObjects.Components.Body
? Loc.GetString("You jam the {0} inside {1:them}.", ContainedMechanism.Name, _performerCache) ? Loc.GetString("You jam the {0} inside {1:them}.", ContainedMechanism.Name, _performerCache)
: Loc.GetString("You can't fit it in!"); : Loc.GetString("You can't fit it in!");
_sharedNotifyManager.PopupMessage( _bodyManagerComponentCache.Owner.PopupMessage(_performerCache, message);
_bodyManagerComponentCache.Owner,
_performerCache,
message);
// TODO: {1:theName} // TODO: {1:theName}
} }

View File

@@ -0,0 +1,67 @@
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
{
/// <summary>
/// The <see cref="BodyTemplate"/> that this <see cref="BodyManagerComponent"/>
/// is adhering to.
/// </summary>
public BodyTemplate Template { get; }
/// <summary>
/// The <see cref="BodyPreset"/> that this <see cref="BodyManagerComponent"/>
/// is adhering to.
/// </summary>
public BodyPreset Preset { 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);
}
}

View File

@@ -16,7 +16,6 @@ using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player; using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Log; using Robust.Shared.Log;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
@@ -34,8 +33,6 @@ namespace Content.Server.GameObjects.Components.Body
[RegisterComponent] [RegisterComponent]
public class SurgeryToolComponent : Component, ISurgeon, IAfterInteract public class SurgeryToolComponent : Component, ISurgeon, IAfterInteract
{ {
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager = default!;
public override string Name => "SurgeryTool"; public override string Name => "SurgeryTool";
public override uint? NetID => ContentNetIDs.SURGERY; public override uint? NetID => ContentNetIDs.SURGERY;
@@ -131,7 +128,7 @@ namespace Content.Server.GameObjects.Components.Body
public float BaseOperationTime { get => _baseOperateTime; set => _baseOperateTime = value; } public float BaseOperationTime { get => _baseOperateTime; set => _baseOperateTime = value; }
public void RequestMechanism(IEnumerable<Mechanism> options, ISurgeon.MechanismRequestCallback callback) public void RequestMechanism(IEnumerable<IMechanism> options, ISurgeon.MechanismRequestCallback callback)
{ {
var toSend = new Dictionary<string, int>(); var toSend = new Dictionary<string, int>();
foreach (var mechanism in options) foreach (var mechanism in options)
@@ -233,7 +230,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="Mechanism"/> to choose from. /// <see cref="IMechanism"/> to choose from.
/// </summary> /// </summary>
private void HandleReceiveMechanism(int key) private void HandleReceiveMechanism(int key)
{ {
@@ -254,27 +251,13 @@ namespace Content.Server.GameObjects.Components.Body
private void SendNoUsefulWayToUsePopup() private void SendNoUsefulWayToUsePopup()
{ {
if (_bodyManagerComponentCache == null) _bodyManagerComponentCache?.Owner.PopupMessage(_performerCache,
{
return;
}
_sharedNotifyManager.PopupMessage(
_bodyManagerComponentCache.Owner,
_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 SendNoUsefulWayToUseAnymorePopup()
{ {
if (_bodyManagerComponentCache == null) _bodyManagerComponentCache?.Owner.PopupMessage(_performerCache,
{
return;
}
_sharedNotifyManager.PopupMessage(
_bodyManagerComponentCache.Owner,
_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));
} }

View File

@@ -3,16 +3,17 @@ using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Mobs.State;
using Content.Server.GameObjects.Components.Strap; using Content.Server.GameObjects.Components.Strap;
using Content.Server.Interfaces; using Content.Server.GameObjects.EntitySystems;
using Content.Server.Mobs;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Buckle; using Content.Shared.GameObjects.Components.Buckle;
using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.Components.Strap; using Content.Shared.GameObjects.Components.Strap;
using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Verbs; using Content.Shared.GameObjects.Verbs;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystemMessages; using Robust.Server.GameObjects.EntitySystemMessages;
using Robust.Server.GameObjects.EntitySystems; using Robust.Server.GameObjects.EntitySystems;
@@ -37,7 +38,6 @@ namespace Content.Server.GameObjects.Components.Buckle
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!; [Dependency] private readonly IEntitySystemManager _entitySystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
private int _size; private int _size;
@@ -112,10 +112,14 @@ namespace Content.Server.GameObjects.Components.Buckle
{ {
if (Owner.TryGetComponent(out ServerStatusEffectsComponent? status)) if (Owner.TryGetComponent(out ServerStatusEffectsComponent? status))
{ {
status.ChangeStatusEffectIcon(StatusEffect.Buckled, if (Buckled)
Buckled {
? BuckledTo!.BuckledIcon status.ChangeStatusEffectIcon(StatusEffect.Buckled, BuckledTo!.BuckledIcon);
: "/Textures/Interface/StatusEffects/Buckle/unbuckled.png"); }
else
{
status.RemoveStatusEffect(StatusEffect.Buckled);
}
} }
} }
@@ -136,11 +140,11 @@ namespace Content.Server.GameObjects.Components.Buckle
ownTransform.WorldRotation = strapTransform.WorldRotation; ownTransform.WorldRotation = strapTransform.WorldRotation;
break; break;
case StrapPosition.Stand: case StrapPosition.Stand:
StandingStateHelper.Standing(Owner); EntitySystem.Get<StandingStateSystem>().Standing(Owner);
ownTransform.WorldRotation = strapTransform.WorldRotation; ownTransform.WorldRotation = strapTransform.WorldRotation;
break; break;
case StrapPosition.Down: case StrapPosition.Down:
StandingStateHelper.Down(Owner, force: true); EntitySystem.Get<StandingStateSystem>().Down(Owner, force: true);
ownTransform.WorldRotation = Angle.South; ownTransform.WorldRotation = Angle.South;
break; break;
} }
@@ -158,7 +162,7 @@ namespace Content.Server.GameObjects.Components.Buckle
} }
} }
private bool CanBuckle(IEntity user, IEntity to, [MaybeNullWhen(false)] out StrapComponent strap) private bool CanBuckle(IEntity? user, IEntity to, [MaybeNullWhen(false)] out StrapComponent strap)
{ {
strap = null; strap = null;
@@ -169,32 +173,26 @@ namespace Content.Server.GameObjects.Components.Buckle
if (!ActionBlockerSystem.CanInteract(user)) if (!ActionBlockerSystem.CanInteract(user))
{ {
_notifyManager.PopupMessage(user, user, user.PopupMessage(Loc.GetString("You can't do that!"));
Loc.GetString("You can't do that!"));
return false; return false;
} }
if (!to.TryGetComponent(out strap)) if (!to.TryGetComponent(out strap))
{ {
_notifyManager.PopupMessage(Owner, user, var message = Loc.GetString(Owner == user
Loc.GetString(Owner == user ? "You can't buckle yourself there!"
? "You can't buckle yourself there!" : "You can't buckle {0:them} there!", Owner);
: "You can't buckle {0:them} there!", Owner)); Owner.PopupMessage(user, message);
return false; return false;
} }
var ownerPosition = Owner.Transform.MapPosition;
var strapPosition = strap.Owner.Transform.MapPosition;
var interaction = EntitySystem.Get<SharedInteractionSystem>();
var component = strap; var component = strap;
bool Ignored(IEntity entity) => entity == Owner || entity == user || entity == component.Owner; bool Ignored(IEntity entity) => entity == Owner || entity == user || entity == component.Owner;
if (!interaction.InRangeUnobstructed(ownerPosition, strapPosition, _range, predicate: Ignored)) if (!Owner.InRangeUnobstructed(strap, _range, predicate: Ignored, popup: true))
{ {
_notifyManager.PopupMessage(strap.Owner, user, strap.Owner.PopupMessage(user, Loc.GetString("You can't reach there!"));
Loc.GetString("You can't reach there!"));
return false; return false;
} }
@@ -206,7 +204,7 @@ namespace Content.Server.GameObjects.Components.Buckle
if (!ContainerHelpers.TryGetContainer(strap.Owner, out var strapContainer) || if (!ContainerHelpers.TryGetContainer(strap.Owner, out var strapContainer) ||
ownerContainer != strapContainer) ownerContainer != strapContainer)
{ {
_notifyManager.PopupMessage(strap.Owner, user, Loc.GetString("You can't reach there!")); strap.Owner.PopupMessage(user, Loc.GetString("You can't reach there!"));
return false; return false;
} }
@@ -214,18 +212,16 @@ namespace Content.Server.GameObjects.Components.Buckle
if (!user.HasComponent<HandsComponent>()) if (!user.HasComponent<HandsComponent>())
{ {
_notifyManager.PopupMessage(user, user, user.PopupMessage(Loc.GetString("You don't have hands!"));
Loc.GetString("You don't have hands!"));
return false; return false;
} }
if (Buckled) if (Buckled)
{ {
_notifyManager.PopupMessage(Owner, user, var message = Loc.GetString(Owner == user
Loc.GetString(Owner == user ? "You are already buckled in!"
? "You are already buckled in!" : "{0:They} are already buckled in!", Owner);
: "{0:They} are already buckled in!", Owner)); Owner.PopupMessage(user, message);
return false; return false;
} }
@@ -235,10 +231,10 @@ namespace Content.Server.GameObjects.Components.Buckle
{ {
if (parent == user.Transform) if (parent == user.Transform)
{ {
_notifyManager.PopupMessage(Owner, user, var message = Loc.GetString(Owner == user
Loc.GetString(Owner == user ? "You can't buckle yourself there!"
? "You can't buckle yourself there!" : "You can't buckle {0:them} there!", Owner);
: "You can't buckle {0:them} there!", Owner)); Owner.PopupMessage(user, message);
return false; return false;
} }
@@ -248,10 +244,10 @@ namespace Content.Server.GameObjects.Components.Buckle
if (!strap.HasSpace(this)) if (!strap.HasSpace(this))
{ {
_notifyManager.PopupMessage(Owner, user, var message = Loc.GetString(Owner == user
Loc.GetString(Owner == user ? "You can't fit there!"
? "You can't fit there!" : "{0:They} can't fit there!", Owner);
: "{0:They} can't fit there!", Owner)); Owner.PopupMessage(user, message);
return false; return false;
} }
@@ -282,10 +278,10 @@ namespace Content.Server.GameObjects.Components.Buckle
if (!strap.TryAdd(this)) if (!strap.TryAdd(this))
{ {
_notifyManager.PopupMessage(Owner, user, var message = Loc.GetString(Owner == user
Loc.GetString(Owner == user ? "You can't buckle yourself there!"
? "You can't buckle yourself there!" : "You can't buckle {0:them} there!", Owner);
: "You can't buckle {0:them} there!", Owner)); Owner.PopupMessage(user, message);
return false; return false;
} }
@@ -336,14 +332,11 @@ namespace Content.Server.GameObjects.Components.Buckle
if (!ActionBlockerSystem.CanInteract(user)) if (!ActionBlockerSystem.CanInteract(user))
{ {
_notifyManager.PopupMessage(user, user, user.PopupMessage(Loc.GetString("You can't do that!"));
Loc.GetString("You can't do that!"));
return false; return false;
} }
var strapPosition = Owner.Transform.MapPosition; if (!user.InRangeUnobstructed(oldBuckledTo, _range, popup: true))
if (!InteractionChecks.InRangeUnobstructed(user, strapPosition, _range))
{ {
return false; return false;
} }
@@ -364,11 +357,11 @@ namespace Content.Server.GameObjects.Components.Buckle
if (Owner.TryGetComponent(out StunnableComponent? stunnable) && stunnable.KnockedDown) if (Owner.TryGetComponent(out StunnableComponent? stunnable) && stunnable.KnockedDown)
{ {
StandingStateHelper.Down(Owner); EntitySystem.Get<StandingStateSystem>().Down(Owner);
} }
else else
{ {
StandingStateHelper.Standing(Owner); EntitySystem.Get<StandingStateSystem>().Standing(Owner);
} }
if (Owner.TryGetComponent(out MobStateManagerComponent? stateManager)) if (Owner.TryGetComponent(out MobStateManagerComponent? stateManager))

View File

@@ -7,13 +7,14 @@ using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility; using Content.Server.Utility;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Chemistry.ChemMaster; using Content.Shared.GameObjects.Components.Chemistry.ChemMaster;
using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.Components.Container;
using Robust.Server.GameObjects.Components.UserInterface; using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.GameObjects.EntitySystems; using Robust.Server.GameObjects.EntitySystems;
@@ -22,11 +23,7 @@ using Robust.Shared.Audio;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems; using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Random;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -43,8 +40,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
[ComponentReference(typeof(IInteractUsing))] [ComponentReference(typeof(IInteractUsing))]
public class ChemMasterComponent : SharedChemMasterComponent, IActivate, IInteractUsing, ISolutionChange public class ChemMasterComponent : SharedChemMasterComponent, IActivate, IInteractUsing, ISolutionChange
{ {
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[ViewVariables] private ContainerSlot _beakerContainer = default!; [ViewVariables] private ContainerSlot _beakerContainer = default!;
[ViewVariables] private string _packPrototypeId = ""; [ViewVariables] private string _packPrototypeId = "";
@@ -272,8 +267,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
private void TryCreatePackage(IEntity user, UiAction action, int pillAmount, int bottleAmount) private void TryCreatePackage(IEntity user, UiAction action, int pillAmount, int bottleAmount)
{ {
var random = IoCManager.Resolve<IRobustRandom>();
if (BufferSolution.CurrentVolume == 0) if (BufferSolution.CurrentVolume == 0)
return; return;
@@ -302,15 +295,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
hands.PutInHand(item); hands.PutInHand(item);
continue; continue;
} }
} }
//Put it on the floor //Put it on the floor
bottle.Transform.GridPosition = user.Transform.GridPosition; bottle.Transform.GridPosition = user.Transform.GridPosition;
//Give it an offset //Give it an offset
var x_negative = random.Prob(0.5f) ? -1 : 1; bottle.RandomOffset(0.2f);
var y_negative = random.Prob(0.5f) ? -1 : 1;
bottle.Transform.LocalPosition += new Vector2(random.NextFloat() * 0.2f * x_negative, random.NextFloat() * 0.2f * y_negative);
} }
} }
@@ -345,9 +335,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
//Put it on the floor //Put it on the floor
pill.Transform.GridPosition = user.Transform.GridPosition; pill.Transform.GridPosition = user.Transform.GridPosition;
//Give it an offset //Give it an offset
var x_negative = random.Prob(0.5f) ? -1 : 1; pill.RandomOffset(0.2f);
var y_negative = random.Prob(0.5f) ? -1 : 1;
pill.Transform.LocalPosition += new Vector2(random.NextFloat() * 0.2f * x_negative, random.NextFloat() * 0.2f * y_negative);
} }
} }
@@ -367,8 +355,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
if (!args.User.TryGetComponent(out IHandsComponent? hands)) if (!args.User.TryGetComponent(out IHandsComponent? hands))
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, Owner.PopupMessage(args.User, Loc.GetString("You have no hands."));
Loc.GetString("You have no hands."));
return; return;
} }
@@ -390,15 +377,13 @@ namespace Content.Server.GameObjects.Components.Chemistry
{ {
if (!args.User.TryGetComponent(out IHandsComponent? hands)) if (!args.User.TryGetComponent(out IHandsComponent? hands))
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, Owner.PopupMessage(args.User, Loc.GetString("You have no hands."));
Loc.GetString("You have no hands."));
return true; return true;
} }
if (hands.GetActiveHand == null) if (hands.GetActiveHand == null)
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, Owner.PopupMessage(args.User, Loc.GetString("You have nothing on your hand."));
Loc.GetString("You have nothing on your hand."));
return false; return false;
} }
@@ -407,14 +392,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
{ {
if (HasBeaker) if (HasBeaker)
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, Owner.PopupMessage(args.User, Loc.GetString("This ChemMaster already has a container in it."));
Loc.GetString("This ChemMaster already has a container in it."));
} }
else if ((solution.Capabilities & SolutionCaps.FitsInDispenser) == 0) //Close enough to a chem master... else if ((solution.Capabilities & SolutionCaps.FitsInDispenser) == 0) //Close enough to a chem master...
{ {
//If it can't fit in the chem master, don't put it in. For example, buckets and mop buckets can't fit. //If it can't fit in the chem master, don't put it in. For example, buckets and mop buckets can't fit.
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, Owner.PopupMessage(args.User, Loc.GetString("That can't fit in the ChemMaster."));
Loc.GetString("That can't fit in the ChemMaster."));
} }
else else
{ {
@@ -424,8 +407,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
} }
else else
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, Owner.PopupMessage(args.User, Loc.GetString("You can't put this in the ChemMaster."));
Loc.GetString("You can't put this in the ChemMaster."));
} }
return true; return true;
@@ -435,7 +417,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
private void ClickSound() private void ClickSound()
{ {
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f)); EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
} }
} }

View File

@@ -1,14 +1,13 @@
#nullable enable #nullable enable
using System; using System;
using Content.Server.GameObjects.Components.Body.Circulatory; using Content.Server.GameObjects.Components.Body.Circulatory;
using Content.Server.Interfaces;
using Content.Server.Utility;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Chemistry; using Content.Shared.GameObjects.Components.Chemistry;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Utility;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -23,8 +22,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
[RegisterComponent] [RegisterComponent]
public class InjectorComponent : SharedInjectorComponent, IAfterInteract, IUse public class InjectorComponent : SharedInjectorComponent, IAfterInteract, IUse
{ {
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
/// <summary> /// <summary>
/// Whether or not the injector is able to draw from containers or if it's a single use /// Whether or not the injector is able to draw from containers or if it's a single use
/// device that can only inject. /// device that can only inject.
@@ -100,7 +97,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
_notifyManager.PopupMessage(Owner, user, Loc.GetString(msg)); Owner.PopupMessage(user, Loc.GetString(msg));
Dirty(); Dirty();
} }
@@ -111,7 +108,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <param name="eventArgs"></param> /// <param name="eventArgs"></param>
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{ {
if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return; if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return;
//Make sure we have the attacking entity //Make sure we have the attacking entity
if (eventArgs.Target == null || !Owner.TryGetComponent(out SolutionComponent? solution) || !solution.Injector) if (eventArgs.Target == null || !Owner.TryGetComponent(out SolutionComponent? solution) || !solution.Injector)
@@ -165,8 +162,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetBloodstream.EmptyVolume); var realTransferAmount = ReagentUnit.Min(_transferAmount, targetBloodstream.EmptyVolume);
if (realTransferAmount <= 0) if (realTransferAmount <= 0)
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Owner.PopupMessage(user, Loc.GetString("Container full."));
Loc.GetString("Container full."));
return; return;
} }
@@ -177,8 +173,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
return; return;
} }
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Owner.PopupMessage(user, Loc.GetString("Injected {0}u", removedSolution.TotalVolume));
Loc.GetString("Injected {0}u", removedSolution.TotalVolume));
Dirty(); Dirty();
} }
@@ -194,8 +189,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.EmptyVolume); var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.EmptyVolume);
if (realTransferAmount <= 0) if (realTransferAmount <= 0)
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Owner.PopupMessage(user, Loc.GetString("Container full."));
Loc.GetString("Container full."));
return; return;
} }
@@ -206,8 +200,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
return; return;
} }
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Owner.PopupMessage(user, Loc.GetString("Injected {0}u", removedSolution.TotalVolume));
Loc.GetString("Injected {0}u", removedSolution.TotalVolume));
Dirty(); Dirty();
} }
@@ -223,8 +216,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.CurrentVolume); var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.CurrentVolume);
if (realTransferAmount <= 0) if (realTransferAmount <= 0)
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Owner.PopupMessage(user, Loc.GetString("Container empty"));
Loc.GetString("Container empty"));
return; return;
} }
@@ -235,8 +227,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
return; return;
} }
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Owner.PopupMessage(user, Loc.GetString("Drew {0}u", removedSolution.TotalVolume));
Loc.GetString("Drew {0}u", removedSolution.TotalVolume));
Dirty(); Dirty();
} }

View File

@@ -1,10 +1,10 @@
using Content.Server.GameObjects.Components.Body.Digestive; using Content.Server.GameObjects.Components.Body.Digestive;
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.Server.Utility;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
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 Robust.Server.GameObjects.EntitySystems; using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -46,8 +46,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
{ {
base.Initialize(); base.Initialize();
_contents = Owner.GetComponent<SolutionComponent>(); _contents = Owner.GetComponent<SolutionComponent>();
_transferAmount = _contents.CurrentVolume;
} }
bool IUse.UseEntity(UseEntityEventArgs eventArgs) bool IUse.UseEntity(UseEntityEventArgs eventArgs)
@@ -80,7 +78,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
return false; return false;
} }
if (!InteractionChecks.InRangeUnobstructed(user, trueTarget.Transform.MapPosition)) if (!user.InRangeUnobstructed(trueTarget, popup: true))
{ {
return false; return false;
} }

View File

@@ -1,9 +1,8 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Interfaces;
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 Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
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;
@@ -19,8 +18,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
[RegisterComponent] [RegisterComponent]
class PourableComponent : Component, IInteractUsing class PourableComponent : Component, IInteractUsing
{ {
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
public override string Name => "Pourable"; public override string Name => "Pourable";
private ReagentUnit _transferAmount; private ReagentUnit _transferAmount;
@@ -87,8 +84,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
var realTransferAmount = ReagentUnit.Min(fromPourable.TransferAmount, toSolution.EmptyVolume); var realTransferAmount = ReagentUnit.Min(fromPourable.TransferAmount, toSolution.EmptyVolume);
if (realTransferAmount <= 0) //Special message if container is full if (realTransferAmount <= 0) //Special message if container is full
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User, Owner.PopupMessage(eventArgs.User, Loc.GetString("Container is full"));
Loc.GetString("Container is full"));
return false; return false;
} }
@@ -97,8 +93,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
if (!toSolution.TryAddSolution(removedSolution)) if (!toSolution.TryAddSolution(removedSolution))
return false; return false;
_notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User, Owner.PopupMessage(eventArgs.User, Loc.GetString("Transferred {0}u", removedSolution.TotalVolume));
Loc.GetString("Transferred {0}u", removedSolution.TotalVolume));
return true; return true;
} }

View File

@@ -6,12 +6,12 @@ using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility; using Content.Server.Utility;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Chemistry.ReagentDispenser; using Content.Shared.GameObjects.Components.Chemistry.ReagentDispenser;
using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.Components.Container;
using Robust.Server.GameObjects.Components.UserInterface; using Robust.Server.GameObjects.Components.UserInterface;
@@ -40,7 +40,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
[ComponentReference(typeof(IInteractUsing))] [ComponentReference(typeof(IInteractUsing))]
public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate, IInteractUsing, ISolutionChange public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate, IInteractUsing, ISolutionChange
{ {
[Dependency] private readonly IServerNotifyManager _notifyManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[ViewVariables] private ContainerSlot _beakerContainer = default!; [ViewVariables] private ContainerSlot _beakerContainer = default!;
[ViewVariables] private string _packPrototypeId = ""; [ViewVariables] private string _packPrototypeId = "";
@@ -99,8 +99,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
{ {
if (string.IsNullOrEmpty(_packPrototypeId)) return; if (string.IsNullOrEmpty(_packPrototypeId)) return;
var prototypeManager = IoCManager.Resolve<IPrototypeManager>(); if (!_prototypeManager.TryIndex(_packPrototypeId, out ReagentDispenserInventoryPrototype packPrototype))
if (!prototypeManager.TryIndex(_packPrototypeId, out ReagentDispenserInventoryPrototype packPrototype))
{ {
return; return;
} }
@@ -280,8 +279,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
if (!args.User.TryGetComponent(out IHandsComponent? hands)) if (!args.User.TryGetComponent(out IHandsComponent? hands))
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, Owner.PopupMessage(args.User, Loc.GetString("You have no hands."));
Loc.GetString("You have no hands."));
return; return;
} }
@@ -303,15 +301,13 @@ namespace Content.Server.GameObjects.Components.Chemistry
{ {
if (!args.User.TryGetComponent(out IHandsComponent? hands)) if (!args.User.TryGetComponent(out IHandsComponent? hands))
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, Owner.PopupMessage(args.User, Loc.GetString("You have no hands."));
Loc.GetString("You have no hands."));
return true; return true;
} }
if (hands.GetActiveHand == null) if (hands.GetActiveHand == null)
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, Owner.PopupMessage(args.User, Loc.GetString("You have nothing on your hand."));
Loc.GetString("You have nothing on your hand."));
return false; return false;
} }
@@ -320,14 +316,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
{ {
if (HasBeaker) if (HasBeaker)
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, Owner.PopupMessage(args.User, Loc.GetString("This dispenser already has a container in it."));
Loc.GetString("This dispenser already has a container in it."));
} }
else if ((solution.Capabilities & SolutionCaps.FitsInDispenser) == 0) else if ((solution.Capabilities & SolutionCaps.FitsInDispenser) == 0)
{ {
//If it can't fit in the dispenser, don't put it in. For example, buckets and mop buckets can't fit. //If it can't fit in the dispenser, don't put it in. For example, buckets and mop buckets can't fit.
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, Owner.PopupMessage(args.User, Loc.GetString("That can't fit in the dispenser."));
Loc.GetString("That can't fit in the dispenser."));
} }
else else
{ {
@@ -337,8 +331,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
} }
else else
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, Owner.PopupMessage(args.User, Loc.GetString("You can't put this in the dispenser."));
Loc.GetString("You can't put this in the dispenser."));
} }
return true; return true;
@@ -348,11 +341,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
private void ClickSound() private void ClickSound()
{ {
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f)); EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
} }
} }
} }

View File

@@ -234,7 +234,10 @@ namespace Content.Server.GameObjects.Components.Chemistry
var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? "<Item>"; var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? "<Item>";
var myName = component.Owner.Prototype?.Name ?? "<Item>"; var myName = component.Owner.Prototype?.Name ?? "<Item>";
data.Text= $"Transfer liquid from [{heldEntityName}] to [{myName}]."; var locHeldEntityName = Loc.GetString(heldEntityName);
var locMyName = Loc.GetString(myName);
data.Text = Loc.GetString("Transfer liquid from [{0}] to [{1}].", locHeldEntityName, locMyName);
return; return;
} }
@@ -334,7 +337,10 @@ namespace Content.Server.GameObjects.Components.Chemistry
var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? "<Item>"; var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? "<Item>";
var myName = component.Owner.Prototype?.Name ?? "<Item>"; var myName = component.Owner.Prototype?.Name ?? "<Item>";
data.Text = $"Transfer liquid from [{myName}] to [{heldEntityName}]."; var locHeldEntityName = Loc.GetString(heldEntityName);
var locMyName = Loc.GetString(myName);
data.Text = Loc.GetString("Transfer liquid from [{0}] to [{1}].", locMyName, locHeldEntityName);
return; return;
} }

View File

@@ -76,7 +76,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
foreach (var tile in tiles) foreach (var tile in tiles)
{ {
var pos = tile.GridIndices.ToGridCoordinates(_mapManager, tile.GridIndex); var pos = tile.GridIndices.ToGridCoordinates(_mapManager, tile.GridIndex);
SpillHelper.SpillAt(pos, contents.SplitSolution(amount), "PuddleSmear", false); //make non PuddleSmear? contents.SplitSolution(amount).SpillAt(pos, "PuddleSmear", false); // TODO: Make non PuddleSmear?
} }
} }

View File

@@ -10,6 +10,7 @@ using Content.Shared.GameObjects.Components.Conveyor;
using Content.Shared.GameObjects.Components.Interactable; using Content.Shared.GameObjects.Components.Interactable;
using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Physics; using Content.Shared.Physics;
using Content.Shared.Utility;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -170,7 +171,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
Owner.AddComponent<ItemComponent>(); Owner.AddComponent<ItemComponent>();
_group?.RemoveConveyor(this); _group?.RemoveConveyor(this);
Owner.Transform.WorldPosition += (_random.NextFloat() * 0.4f - 0.2f, _random.NextFloat() * 0.4f - 0.2f); Owner.RandomOffset(0.2f);
return true; return true;
} }

View File

@@ -81,7 +81,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
} }
_group.AddConveyor(conveyor); _group.AddConveyor(conveyor);
user?.PopupMessage(user, Loc.GetString("Conveyor linked.")); user?.PopupMessage(Loc.GetString("Conveyor linked."));
} }
/// <summary> /// <summary>

View File

@@ -0,0 +1,214 @@
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using Content.Server.GameObjects.Components.Atmos;
using Content.Shared.GameObjects.Components.Damage;
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.GameObjects.Components.Damage
{
public abstract class DamageFlagCommand : IClientCommand
{
public abstract string Command { get; }
public abstract string Description { get; }
public abstract string Help { get; }
public abstract void Execute(IConsoleShell shell, IPlayerSession? player, string[] args);
public bool TryGetEntity(
IConsoleShell shell,
IPlayerSession? player,
string[] args,
bool adding,
[NotNullWhen(true)] out IEntity? entity,
out DamageFlag flag,
[NotNullWhen(true)] out IDamageableComponent? damageable)
{
entity = null;
flag = DamageFlag.None;
damageable = null;
IEntity? parsedEntity;
DamageFlag parsedFlag;
IDamageableComponent? parsedDamageable;
switch (args.Length)
{
case 1:
{
if (player == null)
{
shell.SendText(player, "An entity needs to be specified when the command isn't used by a player.");
return false;
}
if (player.AttachedEntity == null)
{
shell.SendText(player, "An entity needs to be specified when you aren't attached to an entity.");
return false;
}
if (!Enum.TryParse(args[0], true, out parsedFlag))
{
shell.SendText(player, $"{args[0]} is not a valid damage flag.");
return false;
}
parsedEntity = player.AttachedEntity;
flag = parsedFlag;
break;
}
case 2:
{
if (!EntityUid.TryParse(args[0], out var id))
{
shell.SendText(player, $"{args[0]} isn't a valid entity id.");
return false;
}
var entityManager = IoCManager.Resolve<IEntityManager>();
if (!entityManager.TryGetEntity(id, out parsedEntity))
{
shell.SendText(player, $"No entity found with id {id}.");
return false;
}
if (!Enum.TryParse(args[1], true, out parsedFlag))
{
shell.SendText(player, $"{args[1]} is not a valid damage flag.");
return false;
}
break;
}
default:
shell.SendText(player, Help);
return false;
}
if (!parsedEntity.TryGetComponent(out parsedDamageable))
{
shell.SendText(player, $"Entity {parsedEntity.Name} doesn't have a {nameof(IDamageableComponent)}");
return false;
}
if (parsedDamageable.HasFlag(parsedFlag) && adding)
{
shell.SendText(player, $"Entity {parsedEntity.Name} already has damage flag {parsedFlag}.");
return false;
}
else if (!parsedDamageable.HasFlag(parsedFlag) && !adding)
{
shell.SendText(player, $"Entity {parsedEntity.Name} doesn't have damage flag {parsedFlag}.");
return false;
}
entity = parsedEntity;
flag = parsedFlag;
damageable = parsedDamageable;
return true;
}
}
public class AddDamageFlagCommand : DamageFlagCommand
{
public override string Command => "adddamageflag";
public override string Description => "Adds a damage flag to your entity or another.";
public override string Help => $"Usage: {Command} <flag> / {Command} <entityUid> <flag>";
public override void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
if (!TryGetEntity(shell, player, args, true, out var entity, out var flag, out var damageable))
{
return;
}
damageable.AddFlag(flag);
shell.SendText(player, $"Added damage flag {flag} to entity {entity.Name}");
}
}
public class RemoveDamageFlagCommand : DamageFlagCommand
{
public override string Command => "removedamageflag";
public override string Description => "Removes a damage flag from your entity or another.";
public override string Help => $"Usage: {Command} <flag> / {Command} <entityUid> <flag>";
public override void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
if (!TryGetEntity(shell, player, args, false, out var entity, out var flag, out var damageable))
{
return;
}
damageable.RemoveFlag(flag);
shell.SendText(player, $"Removed damage flag {flag} from entity {entity.Name}");
}
}
public class GodModeCommand : IClientCommand
{
public string Command => "godmode";
public string Description => "Makes your entity or another invulnerable to almost anything. May have irreversible changes.";
public string Help => $"Usage: {Command} / {Command} <entityUid>";
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
{
IEntity entity;
switch (args.Length)
{
case 0:
if (player == null)
{
shell.SendText(player, "An entity needs to be specified when the command isn't used by a player.");
return;
}
if (player.AttachedEntity == null)
{
shell.SendText(player, "An entity needs to be specified when you aren't attached to an entity.");
return;
}
entity = player.AttachedEntity;
break;
case 1:
if (!EntityUid.TryParse(args[0], out var id))
{
shell.SendText(player, $"{args[0]} isn't a valid entity id.");
return;
}
var entityManager = IoCManager.Resolve<IEntityManager>();
if (!entityManager.TryGetEntity(id, out var parsedEntity))
{
shell.SendText(player, $"No entity found with id {id}.");
return;
}
entity = parsedEntity;
break;
default:
shell.SendText(player, Help);
return;
}
if (entity.HasComponent<MovedByPressureComponent>())
{
entity.RemoveComponent<MovedByPressureComponent>();
}
if (entity.TryGetComponent(out IDamageableComponent? damageable))
{
damageable.AddFlag(DamageFlag.Invulnerable);
}
shell.SendText(player, $"Enabled godmode for entity {entity.Name}");
}
}
}

View File

@@ -63,7 +63,7 @@ namespace Content.Server.GameObjects.Components.Disposal
} }
return entity.HasComponent<ItemComponent>() || return entity.HasComponent<ItemComponent>() ||
entity.HasComponent<IBodyManagerComponent>(); entity.HasComponent<ISharedBodyManagerComponent>();
} }
public bool TryInsert(IEntity entity) public bool TryInsert(IEntity entity)

View File

@@ -1,5 +1,4 @@
#nullable enable #nullable enable
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components;
@@ -11,13 +10,13 @@ using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components; using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Systems; using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Content.Server.Utility; using Content.Server.Utility;
using Content.Shared.Interfaces;
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalRouterComponent; using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalRouterComponent;
namespace Content.Server.GameObjects.Components.Disposal namespace Content.Server.GameObjects.Components.Disposal
@@ -27,7 +26,6 @@ namespace Content.Server.GameObjects.Components.Disposal
[ComponentReference(typeof(IDisposalTubeComponent))] [ComponentReference(typeof(IDisposalTubeComponent))]
public class DisposalRouterComponent : DisposalJunctionComponent, IActivate public class DisposalRouterComponent : DisposalJunctionComponent, IActivate
{ {
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
public override string Name => "DisposalRouter"; public override string Name => "DisposalRouter";
[ViewVariables] [ViewVariables]
@@ -160,8 +158,7 @@ namespace Content.Server.GameObjects.Components.Disposal
if (!args.User.TryGetComponent(out IHandsComponent? hands)) if (!args.User.TryGetComponent(out IHandsComponent? hands))
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, Owner.PopupMessage(args.User, Loc.GetString("You have no hands."));
Loc.GetString("You have no hands."));
return; return;
} }

View File

@@ -1,8 +1,8 @@
#nullable enable #nullable enable
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility; using Content.Server.Utility;
using Content.Shared.GameObjects.EntitySystems; using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
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.GameObjects.EntitySystems; using Robust.Server.GameObjects.EntitySystems;
@@ -12,7 +12,6 @@ using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components; using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Systems; using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -25,7 +24,6 @@ namespace Content.Server.GameObjects.Components.Disposal
[ComponentReference(typeof(IDisposalTubeComponent))] [ComponentReference(typeof(IDisposalTubeComponent))]
public class DisposalTaggerComponent : DisposalTransitComponent, IActivate public class DisposalTaggerComponent : DisposalTransitComponent, IActivate
{ {
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
public override string Name => "DisposalTagger"; public override string Name => "DisposalTagger";
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
@@ -128,8 +126,7 @@ namespace Content.Server.GameObjects.Components.Disposal
if (!args.User.TryGetComponent(out IHandsComponent? hands)) if (!args.User.TryGetComponent(out IHandsComponent? hands))
{ {
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User, Owner.PopupMessage(args.User, Loc.GetString("You have no hands."));
Loc.GetString("You have no hands."));
return; return;
} }

Some files were not shown because too many files have changed in this diff Show More