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:
@@ -18,6 +18,7 @@ using Content.Shared.GameObjects.Components.Chemistry.ChemMaster;
|
||||
using Content.Shared.GameObjects.Components.Chemistry.ReagentDispenser;
|
||||
using Content.Shared.GameObjects.Components.Gravity;
|
||||
using Content.Shared.GameObjects.Components.Markers;
|
||||
using Content.Shared.GameObjects.Components.Power.AME;
|
||||
using Content.Shared.GameObjects.Components.Research;
|
||||
using Content.Shared.GameObjects.Components.VendingMachines;
|
||||
using Content.Shared.Kitchen;
|
||||
@@ -72,6 +73,7 @@ namespace Content.Client
|
||||
factory.Register<SharedChemMasterComponent>();
|
||||
factory.Register<SharedMicrowaveComponent>();
|
||||
factory.Register<SharedGravityGeneratorComponent>();
|
||||
factory.Register<SharedAMEControllerComponent>();
|
||||
|
||||
prototypes.RegisterIgnore("material");
|
||||
prototypes.RegisterIgnore("reaction"); //Chemical reactions only needed by server. Reactions checks are server-side.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
50
Content.Client/GameObjects/Components/AcceptCloningWindow.cs
Normal file
50
Content.Client/GameObjects/Components/AcceptCloningWindow.cs
Normal 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"),
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Linq;
|
||||
using Content.Client.GameObjects.Components.Mobs;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.Input;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
#nullable enable
|
||||
using Content.Client.GameObjects.Components.Disposal;
|
||||
using Content.Client.GameObjects.Components.MedicalScanner;
|
||||
using Content.Client.Interfaces.GameObjects.Components.Interaction;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Medical;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
@@ -14,14 +16,20 @@ namespace Content.Client.GameObjects.Components.Body
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
[ComponentReference(typeof(IBodyManagerComponent))]
|
||||
[ComponentReference(typeof(ISharedBodyManagerComponent))]
|
||||
public class BodyManagerComponent : SharedBodyManagerComponent, IClientDraggable
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,6 +115,7 @@ namespace Content.Client.GameObjects.Components.Doors
|
||||
|
||||
var unlitVisible = true;
|
||||
var boltedVisible = false;
|
||||
var weldedVisible = false;
|
||||
switch (state)
|
||||
{
|
||||
case DoorVisualState.Closed:
|
||||
@@ -145,6 +146,9 @@ namespace Content.Client.GameObjects.Components.Doors
|
||||
animPlayer.Play(DenyAnimation, AnimationKey);
|
||||
}
|
||||
break;
|
||||
case DoorVisualState.Welded:
|
||||
weldedVisible = true;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
@@ -159,6 +163,7 @@ namespace Content.Client.GameObjects.Components.Doors
|
||||
}
|
||||
|
||||
sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible);
|
||||
sprite.LayerSetVisible(DoorVisualLayers.BaseWelded, weldedVisible);
|
||||
sprite.LayerSetVisible(DoorVisualLayers.BaseBolted, unlitVisible && boltedVisible);
|
||||
}
|
||||
}
|
||||
@@ -167,6 +172,7 @@ namespace Content.Client.GameObjects.Components.Doors
|
||||
{
|
||||
Base,
|
||||
BaseUnlit,
|
||||
BaseBolted
|
||||
BaseWelded,
|
||||
BaseBolted,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
539
Content.Client/GameObjects/Components/LightBehaviourComponent.cs
Normal file
539
Content.Client/GameObjects/Components/LightBehaviourComponent.cs
Normal 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,11 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (component.Owner.Deleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||
if (!component.TryGetData(MedicalScannerVisuals.Status, out MedicalScannerStatus status)) return;
|
||||
sprite.LayerSetState(MedicalScannerVisualLayers.Machine, StatusToMachineStateId(status));
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
|
||||
{
|
||||
(ScanButton = new Button
|
||||
{
|
||||
Text = "Scan and Save DNA"
|
||||
Text = Loc.GetString("Scan and Save DNA")
|
||||
}),
|
||||
(_diagnostics = new Label
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
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))
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -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) { }
|
||||
}
|
||||
}
|
||||
@@ -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) { }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) { }
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,7 @@ namespace Content.Client.GameObjects.Components.Observer
|
||||
|
||||
private GhostGui _gui;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public bool CanReturnToBody { get; private set; } = true;
|
||||
[ViewVariables(VVAccess.ReadOnly)] public bool CanReturnToBody { get; private set; } = true;
|
||||
|
||||
private bool _isAttached;
|
||||
|
||||
@@ -51,7 +50,8 @@ namespace Content.Client.GameObjects.Components.Observer
|
||||
base.Initialize();
|
||||
|
||||
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)
|
||||
@@ -98,7 +98,6 @@ namespace Content.Client.GameObjects.Components.Observer
|
||||
{
|
||||
_gui?.Update();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
162
Content.Client/GameObjects/Components/Power/AME/AMEWindow.cs
Normal file
162
Content.Client/GameObjects/Components/Power/AME/AMEWindow.cs
Normal 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}";
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,8 +142,7 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
if (_entityManager.TryGetEntity(args.EntityUid, out var entity))
|
||||
{
|
||||
// check if the entity is reachable
|
||||
if (_interactionSystem.InRangeUnobstructed(dragger.Transform.MapPosition,
|
||||
entity.Transform.MapPosition, ignoredEnt: dragger) == false)
|
||||
if (!_interactionSystem.InRangeUnobstructed(dragger, entity))
|
||||
{
|
||||
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.
|
||||
// We don't use args.EntityUid here because drag interactions generally should
|
||||
// work even if there's something "on top" of the drop target
|
||||
if (_interactionSystem.InRangeUnobstructed(_dragger.Transform.MapPosition,
|
||||
args.Coordinates.ToMap(_mapManager), ignoredEnt: _dragger, ignoreInsideBlocker: true) == false)
|
||||
if (!_interactionSystem.InRangeUnobstructed(_dragger,
|
||||
args.Coordinates, ignoreInsideBlocker: true))
|
||||
{
|
||||
CancelDrag(false, null);
|
||||
return false;
|
||||
@@ -288,8 +287,7 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
if (anyValidDraggable)
|
||||
{
|
||||
// highlight depending on whether its in or out of range
|
||||
var inRange = _interactionSystem.InRangeUnobstructed(_dragger.Transform.MapPosition,
|
||||
pvsEntity.Transform.MapPosition, ignoredEnt: _dragger);
|
||||
var inRange = _interactionSystem.InRangeUnobstructed(_dragger, pvsEntity);
|
||||
inRangeSprite.PostShader = inRange ? _dropTargetInRangeShader : _dropTargetOutOfRangeShader;
|
||||
inRangeSprite.RenderOrder = EntityManager.CurrentTick.Value;
|
||||
highlightedSprites.Add(inRangeSprite);
|
||||
@@ -377,8 +375,7 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
return;
|
||||
}
|
||||
// still in range of the thing we are dragging?
|
||||
if (_interactionSystem.InRangeUnobstructed(_dragger.Transform.MapPosition,
|
||||
_draggedEntity.Transform.MapPosition, ignoredEnt: _dragger) == false)
|
||||
if (!_interactionSystem.InRangeUnobstructed(_dragger, _draggedEntity))
|
||||
{
|
||||
CancelDrag(false, null);
|
||||
return;
|
||||
|
||||
@@ -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.Shared.GameObjects.Components.Weapons.Melee;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.EntitySystemMessages;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -18,6 +22,7 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
public sealed class MeleeWeaponSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -53,6 +58,26 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
var weaponArcAnimation = entity.GetComponent<MeleeWeaponArcAnimationComponent>();
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,16 @@ using Content.Client.State;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Client.Utility;
|
||||
using Content.Shared.GameObjects.EntitySystemMessages;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Physics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects.EntitySystems;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||
using Robust.Client.Interfaces.Input;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.Interfaces.State;
|
||||
@@ -29,6 +32,7 @@ using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Interfaces.Physics;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
@@ -40,7 +44,7 @@ using Timer = Robust.Shared.Timers.Timer;
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class VerbSystem : EntitySystem
|
||||
public sealed class VerbSystem : SharedVerbSystem
|
||||
{
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
@@ -116,10 +120,9 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
}
|
||||
|
||||
var mapCoordinates = args.Coordinates.ToMap(_mapManager);
|
||||
var entities = _entityManager.GetEntitiesIntersecting(mapCoordinates.MapId,
|
||||
Box2.CenteredAround(mapCoordinates.Position, (0.5f, 0.5f))).ToList();
|
||||
var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
|
||||
if (entities.Count == 0)
|
||||
if (playerEntity == null || !TryGetContextEntities(playerEntity, mapCoordinates, out var entities))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"Breakable",
|
||||
"Pickaxe",
|
||||
"Interactable",
|
||||
"CloningPod",
|
||||
"Destructible",
|
||||
"Temperature",
|
||||
"Explosive",
|
||||
@@ -58,7 +59,6 @@
|
||||
"AccessReader",
|
||||
"IdCardConsole",
|
||||
"Airlock",
|
||||
"MedicalScanner",
|
||||
"WirePlacer",
|
||||
"Drink",
|
||||
"Food",
|
||||
@@ -156,13 +156,25 @@
|
||||
"Barotrauma",
|
||||
"GasSprayer",
|
||||
"GasVapor",
|
||||
"MobStateManager",
|
||||
"Metabolism",
|
||||
"AiFactionTag",
|
||||
"PressureProtection",
|
||||
"AMEPart",
|
||||
"AMEFuelContainer",
|
||||
"AMEShield",
|
||||
"DebugPump",
|
||||
"PressurePump",
|
||||
"VolumePump",
|
||||
"DebugVent",
|
||||
"DebugSiphon",
|
||||
"SignalReceiver",
|
||||
"SignalSwitch",
|
||||
"SignalTransmitter",
|
||||
"SignalButton",
|
||||
"SignalLinker",
|
||||
"ExtinguisherCabinet",
|
||||
"ExtinguisherCabinetFilled",
|
||||
"FireExtinguisher",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Client.GameObjects.Components.Instruments;
|
||||
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.Graphics.Drawing;
|
||||
using Robust.Client.Interfaces.UserInterface;
|
||||
@@ -9,7 +10,6 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
@@ -197,9 +197,7 @@ namespace Content.Client.Instruments
|
||||
|| conMan.Owner != localPlayer.ControlledEntity))) return;
|
||||
|
||||
// We check that we're in range unobstructed just in case.
|
||||
if(!EntitySystem.Get<SharedInteractionSystem>()
|
||||
.InRangeUnobstructed(localPlayer.ControlledEntity.Transform.MapPosition,
|
||||
instrumentEnt.Transform.MapPosition, ignoredEnt:instrumentEnt)) return;
|
||||
if (!localPlayer.InRangeUnobstructed(instrumentEnt)) return;
|
||||
|
||||
if (!_midiManager.IsMidiFile(filename))
|
||||
{
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Content.Client
|
||||
}
|
||||
|
||||
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(() =>
|
||||
{
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
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.Interfaces.GameObjects;
|
||||
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||
@@ -67,13 +68,7 @@ namespace Content.Client.State
|
||||
var inRange = false;
|
||||
if (localPlayer.ControlledEntity != null && entityToClick != null)
|
||||
{
|
||||
var playerPos = localPlayer.ControlledEntity.Transform.MapPosition;
|
||||
var entityPos = entityToClick.Transform.MapPosition;
|
||||
inRange = EntitySystemManager.GetEntitySystem<SharedInteractionSystem>()
|
||||
.InRangeUnobstructed(playerPos, entityPos,
|
||||
predicate: entity =>
|
||||
entity == localPlayer.ControlledEntity || entity == entityToClick,
|
||||
ignoreInsideBlocker: true);
|
||||
inRange = localPlayer.InRangeUnobstructed(entityToClick, ignoreInsideBlocker: true);
|
||||
}
|
||||
|
||||
InteractionOutlineComponent outline;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Client.GameObjects.EntitySystems;
|
||||
using Content.Client.StationEvents;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.Interfaces.Placement;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.Player;
|
||||
@@ -17,8 +15,12 @@ using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Content.Client.UserInterface.AdminMenu
|
||||
@@ -27,6 +29,9 @@ namespace Content.Client.UserInterface.AdminMenu
|
||||
{
|
||||
public TabContainer MasterTabContainer;
|
||||
public VBoxContainer PlayerList;
|
||||
public Label PlayerCount;
|
||||
|
||||
protected override Vector2? CustomSize => (500, 250);
|
||||
|
||||
private List<CommandButton> _adminButtons = new List<CommandButton>
|
||||
{
|
||||
@@ -57,51 +62,107 @@ namespace Content.Client.UserInterface.AdminMenu
|
||||
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)
|
||||
{
|
||||
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
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SeparationOverride = 4,
|
||||
Children =
|
||||
{
|
||||
new Label { Text = "Name",
|
||||
SizeFlagsStretchRatio = 2f,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand },
|
||||
new VSeperator(),
|
||||
new Label { Text = "Player",
|
||||
SizeFlagsStretchRatio = 2f,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand },
|
||||
new VSeperator(),
|
||||
new Label { Text = "Status",
|
||||
SizeFlagsStretchRatio = 1f,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand },
|
||||
new VSeperator(),
|
||||
new Label { Text = "Ping",
|
||||
SizeFlagsStretchRatio = 1f,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
Align = Label.AlignMode.Right },
|
||||
}
|
||||
};
|
||||
PlayerList.AddChild(header);
|
||||
PlayerList.AddChild(new Controls.HighDivider());
|
||||
PlayerList.AddChild(new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = altColor,
|
||||
},
|
||||
Children =
|
||||
{
|
||||
header
|
||||
}
|
||||
});
|
||||
PlayerList.AddChild(new HSeperator());
|
||||
|
||||
var useAltColor = false;
|
||||
foreach (var player in sessions)
|
||||
{
|
||||
var hbox = new HBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SeparationOverride = 4,
|
||||
Children =
|
||||
{
|
||||
new Label {
|
||||
Text = player.Name,
|
||||
SizeFlagsStretchRatio = 2f,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand },
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
ClipText = true },
|
||||
new VSeperator(),
|
||||
new Label {
|
||||
Text = player.AttachedEntity?.Name,
|
||||
SizeFlagsStretchRatio = 2f,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand },
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
ClipText = true },
|
||||
new VSeperator(),
|
||||
new Label {
|
||||
Text = player.Status.ToString(),
|
||||
SizeFlagsStretchRatio = 1f,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand },
|
||||
new VSeperator(),
|
||||
new Label {
|
||||
Text = player.Ping.ToString(),
|
||||
SizeFlagsStretchRatio = 1f,
|
||||
@@ -109,7 +170,18 @@ namespace Content.Client.UserInterface.AdminMenu
|
||||
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?
|
||||
{
|
||||
CustomMinimumSize = (415,0);
|
||||
Title = Loc.GetString("Admin Menu");
|
||||
|
||||
#region PlayerList
|
||||
@@ -146,22 +217,50 @@ namespace Content.Client.UserInterface.AdminMenu
|
||||
MarginBottomOverride = 4,
|
||||
CustomMinimumSize = (50, 50),
|
||||
};
|
||||
PlayerList = new VBoxContainer();
|
||||
|
||||
PlayerCount = new Label
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SizeFlagsStretchRatio = 0.7f,
|
||||
};
|
||||
var refreshButton = new Button
|
||||
{
|
||||
Text = "Refresh"
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SizeFlagsStretchRatio = 0.3f,
|
||||
Text = "Refresh",
|
||||
};
|
||||
refreshButton.OnPressed += RefreshPlayerList;
|
||||
RefreshPlayerList(null!);
|
||||
|
||||
PlayerList = new VBoxContainer();
|
||||
|
||||
var playerVBox = new VBoxContainer
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
refreshButton,
|
||||
PlayerList
|
||||
new HBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
PlayerCount,
|
||||
refreshButton,
|
||||
}
|
||||
},
|
||||
new Control { CustomMinimumSize = (0, 5) },
|
||||
new ScrollContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
PlayerList
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
playerTabContainer.AddChild(playerVBox);
|
||||
RefreshPlayerList(null!);
|
||||
#endregion PlayerList
|
||||
|
||||
#region Admin Tab
|
||||
|
||||
@@ -2,12 +2,14 @@ using Content.Client.GameObjects.Components.Observer;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Client.UserInterface
|
||||
{
|
||||
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;
|
||||
|
||||
public GhostGui(GhostComponent owner)
|
||||
|
||||
@@ -8,13 +8,12 @@ namespace Content.Client.UserInterface
|
||||
/// </summary>
|
||||
public sealed class StatusEffectsUI : Control
|
||||
{
|
||||
public VBoxContainer VBox => _vBox;
|
||||
private readonly VBoxContainer _vBox;
|
||||
public VBoxContainer VBox { get; }
|
||||
|
||||
public StatusEffectsUI()
|
||||
{
|
||||
_vBox = new VBoxContainer();
|
||||
AddChild(_vBox);
|
||||
VBox = new VBoxContainer();
|
||||
AddChild(VBox);
|
||||
|
||||
LayoutContainer.SetGrowHorizontal(this, LayoutContainer.GrowDirection.Begin);
|
||||
LayoutContainer.SetAnchorAndMarginPreset(this, LayoutContainer.LayoutPreset.TopRight, margin: 10);
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace Content.Client.UserInterface.Suspicion
|
||||
_ => 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)
|
||||
|
||||
93
Content.Client/Utility/RangeExtensions.cs
Normal file
93
Content.Client/Utility/RangeExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Server.Interfaces.Maps;
|
||||
using Robust.Server.Interfaces.Timing;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
@@ -26,11 +24,13 @@ namespace Content.IntegrationTests.Tests
|
||||
{
|
||||
var server = StartServerDummyTicker();
|
||||
await server.WaitIdleAsync();
|
||||
var mapMan = server.ResolveDependency<IMapManager>();
|
||||
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var entityMan = server.ResolveDependency<IEntityManager>();
|
||||
var prototypeMan = server.ResolveDependency<IPrototypeManager>();
|
||||
var mapLoader = server.ResolveDependency<IMapLoader>();
|
||||
var pauseMan = server.ResolveDependency<IPauseManager>();
|
||||
var pauseManager = server.ResolveDependency<IPauseManager>();
|
||||
var tileDefinitionManager = server.ResolveDependency<ITileDefinitionManager>();
|
||||
|
||||
var prototypes = new List<EntityPrototype>();
|
||||
IMapGrid grid = default;
|
||||
IEntity testEntity;
|
||||
@@ -38,9 +38,25 @@ namespace Content.IntegrationTests.Tests
|
||||
//Build up test environment
|
||||
server.Post(() =>
|
||||
{
|
||||
var mapId = mapMan.CreateMap();
|
||||
pauseMan.AddUninitializedMap(mapId);
|
||||
grid = mapLoader.LoadBlueprint(mapId, "Maps/stationstation.yml");
|
||||
// Create a one tile grid to stave off the grid 0 monsters
|
||||
var mapId = mapManager.CreateMap();
|
||||
|
||||
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(() =>
|
||||
@@ -54,6 +70,7 @@ namespace Content.IntegrationTests.Tests
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
prototypes.Add(prototype);
|
||||
}
|
||||
|
||||
@@ -91,7 +108,8 @@ namespace Content.IntegrationTests.Tests
|
||||
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
|
||||
id: AllComponentsOneToOneDeleteTestEntity";
|
||||
|
||||
var server = StartServerDummyTicker();
|
||||
var server = StartServerDummyTicker(new ServerContentIntegrationOption {ExtraPrototypes = testEntity});
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapLoader = server.ResolveDependency<IMapLoader>();
|
||||
var pauseManager = server.ResolveDependency<IPauseManager>();
|
||||
var componentFactory = server.ResolveDependency<IComponentFactory>();
|
||||
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var tileDefinitionManager = server.ResolveDependency<ITileDefinitionManager>();
|
||||
|
||||
IMapGrid grid = default;
|
||||
|
||||
server.Post(() =>
|
||||
{
|
||||
// Load test entity
|
||||
using var reader = new StringReader(testEntity);
|
||||
prototypeManager.LoadFromStream(reader);
|
||||
|
||||
// Load test map
|
||||
// Create a one tile grid to stave off the grid 0 monsters
|
||||
var mapId = mapManager.CreateMap();
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -201,28 +228,37 @@ namespace Content.IntegrationTests.Tests
|
||||
- type: entity
|
||||
id: AllComponentsOneEntityDeleteTestEntity";
|
||||
|
||||
var server = StartServerDummyTicker();
|
||||
var server = StartServerDummyTicker(new ServerContentIntegrationOption {ExtraPrototypes = testEntity});
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapLoader = server.ResolveDependency<IMapLoader>();
|
||||
var pauseManager = server.ResolveDependency<IPauseManager>();
|
||||
var componentFactory = server.ResolveDependency<IComponentFactory>();
|
||||
var prototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
var tileDefinitionManager = server.ResolveDependency<ITileDefinitionManager>();
|
||||
|
||||
IMapGrid grid = default;
|
||||
|
||||
server.Post(() =>
|
||||
{
|
||||
// Load test entity
|
||||
using var reader = new StringReader(testEntity);
|
||||
prototypeManager.LoadFromStream(reader);
|
||||
|
||||
// Load test map
|
||||
// Create a one tile grid to stave off the grid 0 monsters
|
||||
var mapId = mapManager.CreateMap();
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
|
||||
102
Content.IntegrationTests/Tests/Fluids/PuddleTest.cs
Normal file
102
Content.IntegrationTests/Tests/Fluids/PuddleTest.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.ActionBlocking
|
||||
var slot = part.GetHashCode().ToString();
|
||||
|
||||
body.Template.Slots.Add(slot, BodyPartType.Hand);
|
||||
body.InstallBodyPart(part, slot);
|
||||
body.TryAddPart(slot, part, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Content.IntegrationTests/Tests/PostMapInitTest.cs
Normal file
60
Content.IntegrationTests/Tests/PostMapInitTest.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using Content.Server.AI.WorldState.States.Inventory;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.AI.Operators.Inventory
|
||||
@@ -50,7 +51,7 @@ namespace Content.Server.AI.Operators.Inventory
|
||||
|
||||
public override Outcome Execute(float frameTime)
|
||||
{
|
||||
if (!InteractionChecks.InRangeUnobstructed(_owner, _target.Transform.MapPosition))
|
||||
if (!_owner.InRangeUnobstructed(_target, popup: true))
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.EntitySystems.Click;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
@@ -28,7 +29,7 @@ namespace Content.Server.AI.Operators.Inventory
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
if (!InteractionChecks.InRangeUnobstructed(_owner, _useTarget.Transform.MapPosition))
|
||||
if (!_owner.InRangeUnobstructed(_useTarget, popup: true))
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.AI.WorldState.States.Inventory;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
@@ -30,7 +31,7 @@ namespace Content.Server.AI.Operators.Inventory
|
||||
return Outcome.Success;
|
||||
}
|
||||
|
||||
if (!InteractionChecks.InRangeUnobstructed(_owner, container.Owner.Transform.MapPosition, ignoredEnt: container.Owner))
|
||||
if (!_owner.InRangeUnobstructed(container, popup: true))
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.EntitySystems.Click;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -26,7 +27,7 @@ namespace Content.Server.AI.Operators.Inventory
|
||||
if (_target.Deleted ||
|
||||
!_target.HasComponent<ItemComponent>() ||
|
||||
ContainerHelpers.IsInContainer(_target) ||
|
||||
!InteractionChecks.InRangeUnobstructed(_owner, _target.Transform.MapPosition))
|
||||
!_owner.InRangeUnobstructed(_target, popup: true))
|
||||
{
|
||||
return Outcome.Failed;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Content.Server.AI.Utility.Considerations.Combat.Melee
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return meleeWeaponComponent.CooldownTime / 10.0f;
|
||||
return meleeWeaponComponent.ArcCooldownTime / 10.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Content.Server.AI.WorldState.States.Mobs
|
||||
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;
|
||||
result.Add(entity);
|
||||
|
||||
39
Content.Server/Administration/DeleteEntitiesWithId.cs
Normal file
39
Content.Server/Administration/DeleteEntitiesWithId.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,21 @@ namespace Content.Server.Atmos
|
||||
{
|
||||
public string Command => "addatmos";
|
||||
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)
|
||||
{
|
||||
if (args.Length < 1) return;
|
||||
if(!int.TryParse(args[0], out var id)) return;
|
||||
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);
|
||||
|
||||
@@ -29,7 +39,7 @@ namespace Content.Server.Atmos
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -41,7 +51,7 @@ namespace Content.Server.Atmos
|
||||
return;
|
||||
}
|
||||
|
||||
if (grid.HasComponent<GridAtmosphereComponent>())
|
||||
if (grid.HasComponent<IGridAtmosphereComponent>())
|
||||
{
|
||||
shell.SendText(player, "Grid already has an atmosphere.");
|
||||
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 string Command => "listgases";
|
||||
|
||||
@@ -105,6 +105,7 @@ namespace Content.Server.Atmos
|
||||
{
|
||||
if (tile?.Air == null) continue;
|
||||
tile.Air.CopyFromMutable(combined);
|
||||
tile.AtmosCooldown = 0;
|
||||
tile.UpdateVisuals();
|
||||
}
|
||||
|
||||
@@ -131,7 +132,7 @@ namespace Content.Server.Atmos
|
||||
_disposed = true;
|
||||
_gridAtmosphereComponent.RemoveExcitedGroup(this);
|
||||
|
||||
Dismantle();
|
||||
Dismantle(false);
|
||||
|
||||
_gridAtmosphereComponent = null;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
@@ -13,13 +13,11 @@ using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
|
||||
namespace Content.Server.Atmos
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class GasSprayerComponent : Component, IAfterInteract
|
||||
{
|
||||
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
|
||||
[Dependency] private readonly IServerEntityManager _serverEntityManager = default!;
|
||||
|
||||
//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)
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner, eventArgs.User,
|
||||
Owner.PopupMessage(eventArgs.User,
|
||||
Loc.GetString("{0:theName} is out of {1}!", Owner, _fuelName));
|
||||
}
|
||||
else
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Server.Interfaces;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Maps;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -40,6 +41,9 @@ namespace Content.Server.Atmos
|
||||
[ViewVariables]
|
||||
private static GasTileOverlaySystem _gasTileOverlaySystem;
|
||||
|
||||
[ViewVariables]
|
||||
public int AtmosCooldown { get; set; } = 0;
|
||||
|
||||
[ViewVariables]
|
||||
private float _temperature = Atmospherics.T20C;
|
||||
|
||||
@@ -73,9 +77,11 @@ namespace Content.Server.Atmos
|
||||
[ViewVariables]
|
||||
private readonly TileAtmosphere[] _adjacentTiles = new TileAtmosphere[Atmospherics.Directions];
|
||||
|
||||
[ViewVariables]
|
||||
private AtmosDirection _adjacentBits = AtmosDirection.Invalid;
|
||||
|
||||
[ViewVariables, UsedImplicitly]
|
||||
private int AdjacentBitsInt => (int)_adjacentBits;
|
||||
|
||||
[ViewVariables]
|
||||
private TileAtmosInfo _tileAtmosInfo;
|
||||
|
||||
@@ -84,6 +90,9 @@ namespace Content.Server.Atmos
|
||||
|
||||
private AtmosDirection _pressureDirection;
|
||||
|
||||
[ViewVariables, UsedImplicitly]
|
||||
private int PressureDirectionInt => (int)_pressureDirection;
|
||||
|
||||
[ViewVariables]
|
||||
public GridId GridIndex { get; }
|
||||
|
||||
@@ -635,6 +644,15 @@ namespace Content.Server.Atmos
|
||||
|
||||
_currentCycle = fireCount;
|
||||
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++)
|
||||
{
|
||||
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(enemyTile?.Air == null) continue;
|
||||
adjacentTileLength++;
|
||||
if (fireCount <= enemyTile._currentCycle) continue;
|
||||
enemyTile.Archive(fireCount);
|
||||
|
||||
@@ -703,7 +720,13 @@ namespace Content.Server.Atmos
|
||||
React();
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1143,9 +1166,11 @@ namespace Content.Server.Atmos
|
||||
if (lastShare > Atmospherics.MinimumAirToSuspend)
|
||||
{
|
||||
ExcitedGroup.ResetCooldowns();
|
||||
AtmosCooldown = 0;
|
||||
} else if (lastShare > Atmospherics.MinimumMolesDeltaToMove)
|
||||
{
|
||||
ExcitedGroup.DismantleCooldown = 0;
|
||||
AtmosCooldown = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
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.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Body
|
||||
{
|
||||
@@ -48,7 +53,7 @@ namespace Content.Server.Body
|
||||
var slot = part.GetHashCode().ToString();
|
||||
|
||||
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}.");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,11 @@ using Content.Shared.Damage.DamageContainer;
|
||||
using Content.Shared.Damage.ResistanceSet;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Reflection;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -34,20 +32,11 @@ namespace Content.Server.Body
|
||||
/// which coordinates functions between BodyParts, or a
|
||||
/// <see cref="DroppedBodyPartComponent"/>.
|
||||
/// </summary>
|
||||
public class BodyPart
|
||||
public class BodyPart : IBodyPart
|
||||
{
|
||||
/// <summary>
|
||||
/// The body that this body part is in, if any.
|
||||
/// </summary>
|
||||
private BodyManagerComponent? _body;
|
||||
private IBodyManagerComponent? _body;
|
||||
|
||||
/// <summary>
|
||||
/// Set of all <see cref="Mechanism"/> currently inside this
|
||||
/// <see cref="BodyPart"/>.
|
||||
/// To add and remove from this list see <see cref="AddMechanism"/> and
|
||||
/// <see cref="RemoveMechanism"/>
|
||||
/// </summary>
|
||||
private readonly HashSet<Mechanism> _mechanisms = new HashSet<Mechanism>();
|
||||
private readonly HashSet<IMechanism> _mechanisms = new HashSet<IMechanism>();
|
||||
|
||||
public BodyPart(BodyPartPrototype data)
|
||||
{
|
||||
@@ -64,11 +53,8 @@ namespace Content.Server.Body
|
||||
LoadFromPrototype(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The body that this body part is in, if any.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public BodyManagerComponent? Body
|
||||
public IBodyManagerComponent? Body
|
||||
{
|
||||
get => _body;
|
||||
set
|
||||
@@ -111,91 +97,48 @@ namespace Content.Server.Body
|
||||
[ViewVariables]
|
||||
private HashSet<IExposeData> Properties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of this <see cref="BodyPart"/>, often displayed to the user.
|
||||
/// For example, it could be named "advanced robotic arm".
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string Name { get; private set; }
|
||||
[ViewVariables] public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plural version of this <see cref="BodyPart"/> name.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string Plural { get; private set; }
|
||||
[ViewVariables] public string Plural { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to the RSI that represents this <see cref="BodyPart"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string RSIPath { get; private set; }
|
||||
[ViewVariables] public string RSIPath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// RSI state that represents this <see cref="BodyPart"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string RSIState { get; private set; }
|
||||
[ViewVariables] public string RSIState { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// RSI map keys that this body part changes on the sprite.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Enum? RSIMap { get; set; }
|
||||
[ViewVariables] public Enum? RSIMap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// RSI color of this body part.
|
||||
/// </summary>
|
||||
// TODO: SpriteComponent rework
|
||||
public Color? RSIColor { get; set; }
|
||||
[ViewVariables] public Color? RSIColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="BodyPartType"/> that this <see cref="BodyPart"/> is considered
|
||||
/// to be.
|
||||
/// For example, <see cref="BodyPartType.Arm"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public BodyPartType PartType { get; private set; }
|
||||
[ViewVariables] public BodyPartType PartType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines many things: how many mechanisms can be fit inside this
|
||||
/// <see cref="BodyPart"/>, whether a body can fit through tiny crevices, etc.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private int Size { get; set; }
|
||||
[ViewVariables] public int Size { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Max HP of this <see cref="BodyPart"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int MaxDurability { get; private set; }
|
||||
[ViewVariables] public int MaxDurability { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current HP of this <see cref="BodyPart"/> based on sum of all damage types.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int CurrentDurability => MaxDurability - Damage.TotalDamage;
|
||||
[ViewVariables] public int CurrentDurability => MaxDurability - Damage.TotalDamage;
|
||||
|
||||
// TODO: Individual body part damage
|
||||
/// <summary>
|
||||
/// Current damage dealt to this <see cref="BodyPart"/>.
|
||||
/// Current damage dealt to this <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public DamageContainer Damage { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Armor of this <see cref="BodyPart"/> against damages.
|
||||
/// Armor of this <see cref="IBodyPart"/> against damages.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public ResistanceSet Resistances { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// At what HP this <see cref="BodyPart"/> destroyed.
|
||||
/// At what HP this <see cref="IBodyPart"/> destroyed.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int DestroyThreshold { get; private set; }
|
||||
|
||||
/// <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
|
||||
/// attach between types.
|
||||
/// </summary>
|
||||
@@ -203,15 +146,23 @@ namespace Content.Server.Body
|
||||
public BodyPartCompatibility Compatibility { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set of all <see cref="Mechanism"/> currently inside this
|
||||
/// <see cref="BodyPart"/>.
|
||||
/// Set of all <see cref="IMechanism"/> currently inside this
|
||||
/// <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public IReadOnlyCollection<Mechanism> Mechanisms => _mechanisms;
|
||||
public IReadOnlyCollection<IMechanism> Mechanisms => _mechanisms;
|
||||
|
||||
/// <summary>
|
||||
/// This method is called by <see cref="BodyManagerComponent.Update"/>
|
||||
/// before <see cref="MetabolismComponent.Update"/> is called.
|
||||
/// Represents if body part is vital for creature.
|
||||
/// If the last vital body part is removed creature dies
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool IsVital { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// This method is called by
|
||||
/// <see cref="IBodyManagerComponent.PreMetabolism"/> before
|
||||
/// <see cref="MetabolismComponent.Update"/> is called.
|
||||
/// </summary>
|
||||
public void PreMetabolism(float frameTime)
|
||||
{
|
||||
@@ -222,8 +173,9 @@ namespace Content.Server.Body
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is called by <see cref="BodyManagerComponent.Update"/>
|
||||
/// after <see cref="MetabolismComponent.Update"/> is called.
|
||||
/// This method is called by
|
||||
/// <see cref="IBodyManagerComponent.PostMetabolism"/> after
|
||||
/// <see cref="MetabolismComponent.Update"/> is called.
|
||||
/// </summary>
|
||||
public void PostMetabolism(float frameTime)
|
||||
{
|
||||
@@ -258,9 +210,10 @@ namespace Content.Server.Body
|
||||
/// <param name="property">The property if found, null otherwise.</param>
|
||||
/// <typeparam name="T">The type of the property to find.</typeparam>
|
||||
/// <returns>True if successful, false otherwise.</returns>
|
||||
public bool TryGetProperty<T>(out T property)
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -269,20 +222,21 @@ namespace Content.Server.Body
|
||||
/// The resulting <see cref="BodyPartProperty"/> will be null if unsuccessful.
|
||||
/// </summary>
|
||||
/// <returns>True if successful, false otherwise.</returns>
|
||||
public bool TryGetProperty(Type propertyType, out BodyPartProperty property)
|
||||
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;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <typeparam name="T">
|
||||
/// The subtype of <see cref="BodyPartProperty"/> to look for.
|
||||
/// </typeparam>
|
||||
/// <returns>
|
||||
/// True if this <see cref="BodyPart"/> has a property of type
|
||||
/// True if this <see cref="IBodyPart"/> has a property of type
|
||||
/// <see cref="T"/>, false otherwise.
|
||||
/// </returns>
|
||||
public bool HasProperty<T>() where T : BodyPartProperty
|
||||
@@ -292,13 +246,13 @@ namespace Content.Server.Body
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a subtype of <see cref="BodyPartProperty"/> is on this
|
||||
/// <see cref="BodyPart"/>.
|
||||
/// <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
/// <param name="propertyType">
|
||||
/// The subtype of <see cref="BodyPartProperty"/> to look for.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if this <see cref="BodyPart"/> has a property of type
|
||||
/// True if this <see cref="IBodyPart"/> has a property of type
|
||||
/// <see cref="propertyType"/>, false otherwise.
|
||||
/// </returns>
|
||||
public bool HasProperty(Type propertyType)
|
||||
@@ -306,22 +260,12 @@ namespace Content.Server.Body
|
||||
return Properties.Count(x => x.GetType() == propertyType) > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if another <see cref="BodyPart"/> can be connected to this one.
|
||||
/// </summary>
|
||||
/// <param name="toBeConnected">The part to connect.</param>
|
||||
/// <returns>True if it can be connected, false otherwise.</returns>
|
||||
public bool CanAttachBodyPart(BodyPart toBeConnected)
|
||||
public bool CanAttachPart(IBodyPart part)
|
||||
{
|
||||
return SurgeryData.CanAttachBodyPart(toBeConnected);
|
||||
return SurgeryData.CanAttachBodyPart(part);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a <see cref="Mechanism"/> can be installed on this
|
||||
/// <see cref="BodyPart"/>.
|
||||
/// </summary>
|
||||
/// <returns>True if it can be installed, false otherwise.</returns>
|
||||
public bool CanInstallMechanism(Mechanism mechanism)
|
||||
public bool CanInstallMechanism(IMechanism mechanism)
|
||||
{
|
||||
return SizeUsed + mechanism.Size <= Size &&
|
||||
SurgeryData.CanInstallMechanism(mechanism);
|
||||
@@ -336,9 +280,9 @@ namespace Content.Server.Body
|
||||
/// <param name="mechanism">The mechanism to try to install.</param>
|
||||
/// <returns>
|
||||
/// True if successful, false if there was an error
|
||||
/// (e.g. not enough room in <see cref="BodyPart"/>).
|
||||
/// (e.g. not enough room in <see cref="IBodyPart"/>).
|
||||
/// </returns>
|
||||
private bool TryInstallMechanism(Mechanism mechanism)
|
||||
private bool TryInstallMechanism(IMechanism mechanism)
|
||||
{
|
||||
if (!CanInstallMechanism(mechanism))
|
||||
{
|
||||
@@ -352,7 +296,7 @@ namespace Content.Server.Body
|
||||
|
||||
/// <summary>
|
||||
/// 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"/>.
|
||||
/// </summary>
|
||||
/// <param name="droppedMechanism">The mechanism to install.</param>
|
||||
@@ -364,22 +308,14 @@ namespace Content.Server.Body
|
||||
{
|
||||
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();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to remove the given <see cref="Mechanism"/> reference from
|
||||
/// this <see cref="BodyPart"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The newly spawned <see cref="DroppedMechanismComponent"/>, or null
|
||||
/// if there was an error in spawning the entity or removing the mechanism.
|
||||
/// </returns>
|
||||
public bool TryDropMechanism(IEntity dropLocation, Mechanism mechanismTarget,
|
||||
public bool TryDropMechanism(IEntity dropLocation, IMechanism mechanismTarget,
|
||||
[NotNullWhen(true)] out DroppedMechanismComponent dropped)
|
||||
{
|
||||
dropped = null!;
|
||||
@@ -402,16 +338,16 @@ namespace Content.Server.Body
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to destroy the given <see cref="Mechanism"/> in this
|
||||
/// <see cref="BodyPart"/>. Does NOT spawn a dropped entity.
|
||||
/// Tries to destroy the given <see cref="IMechanism"/> in this
|
||||
/// <see cref="IBodyPart"/>. Does NOT spawn a dropped entity.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Tries to destroy the given <see cref="Mechanism"/> in this
|
||||
/// <see cref="BodyPart"/>.
|
||||
/// Tries to destroy the given <see cref="IMechanism"/> in this
|
||||
/// <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
/// <param name="mechanismTarget">The mechanism to destroy.</param>
|
||||
/// <returns>True if successful, false otherwise.</returns>
|
||||
public bool DestroyMechanism(Mechanism mechanismTarget)
|
||||
public bool DestroyMechanism(IMechanism mechanismTarget)
|
||||
{
|
||||
if (!RemoveMechanism(mechanismTarget))
|
||||
{
|
||||
@@ -421,18 +357,13 @@ namespace Content.Server.Body
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given <see cref="SurgeryType"/> can be used on
|
||||
/// the current state of this <see cref="BodyPart"/>.
|
||||
/// </summary>
|
||||
/// <returns>True if it can be used, false otherwise.</returns>
|
||||
public bool SurgeryCheck(SurgeryType toolType)
|
||||
public bool SurgeryCheck(SurgeryType surgery)
|
||||
{
|
||||
return SurgeryData.CheckSurgery(toolType);
|
||||
return SurgeryData.CheckSurgery(surgery);
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
/// <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);
|
||||
}
|
||||
|
||||
private void AddMechanism(Mechanism mechanism)
|
||||
private void AddMechanism(IMechanism mechanism)
|
||||
{
|
||||
DebugTools.AssertNotNull(mechanism);
|
||||
|
||||
@@ -474,11 +405,11 @@ namespace Content.Server.Body
|
||||
|
||||
/// <summary>
|
||||
/// Tries to remove the given <see cref="mechanism"/> from this
|
||||
/// <see cref="BodyPart"/>.
|
||||
/// <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
/// <param name="mechanism">The mechanism to remove.</param>
|
||||
/// <returns>True if it was removed, false otherwise.</returns>
|
||||
private bool RemoveMechanism(Mechanism mechanism)
|
||||
private bool RemoveMechanism(IMechanism mechanism)
|
||||
{
|
||||
DebugTools.AssertNotNull(mechanism);
|
||||
|
||||
@@ -515,7 +446,7 @@ namespace Content.Server.Body
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
protected virtual void LoadFromPrototype(BodyPartPrototype data)
|
||||
{
|
||||
@@ -527,6 +458,7 @@ namespace Content.Server.Body
|
||||
RSIPath = data.RSIPath;
|
||||
RSIState = data.RSIState;
|
||||
MaxDurability = data.Durability;
|
||||
IsVital = data.IsVital;
|
||||
|
||||
if (!prototypeManager.TryIndex(data.DamageContainerPresetId,
|
||||
out DamageContainerPrototype damageContainerData))
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Content.Server.Body
|
||||
[ViewVariables] public string Name { get; private set; }
|
||||
|
||||
/// <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".
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
|
||||
136
Content.Server/Body/IBodyPart.cs
Normal file
136
Content.Server/Body/IBodyPart.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,11 @@ namespace Content.Server.Body
|
||||
/// Making a class inherit from this interface allows you to do many things with
|
||||
/// it in the <see cref="SurgeryData"/> class.
|
||||
/// This includes passing it as an argument to a
|
||||
/// <see cref="SurgeryData.SurgeryAction"/> delegate, as to later typecast it back
|
||||
/// to the original class type.
|
||||
/// Every BodyPart also needs an <see cref="IBodyPartContainer"/> to be its parent
|
||||
/// (i.e. the <see cref="BodyManagerComponent"/> holds many <see cref="BodyPart"/>,
|
||||
/// each of which have an upward reference to it).
|
||||
/// <see cref="SurgeryData.SurgeryAction"/> delegate, as to later typecast
|
||||
/// it back to the original class type.
|
||||
/// Every BodyPart also needs an <see cref="IBodyPartContainer"/> to be
|
||||
/// its parent (i.e. the <see cref="BodyManagerComponent"/> holds many
|
||||
/// <see cref="IBodyPart"/>, each of which have an upward reference to it).
|
||||
/// </summary>
|
||||
public interface IBodyPartContainer
|
||||
{
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace Content.Server.Body.Mechanisms.Behaviors
|
||||
}
|
||||
|
||||
/// <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"/>.
|
||||
/// For instance, attaching a head to a body will call this on the brain inside.
|
||||
/// </summary>
|
||||
@@ -82,7 +82,7 @@ namespace Content.Server.Body.Mechanisms.Behaviors
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public void InstalledIntoPart()
|
||||
@@ -92,22 +92,22 @@ namespace Content.Server.Body.Mechanisms.Behaviors
|
||||
}
|
||||
|
||||
/// <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"/>.
|
||||
/// For instance, cutting off ones head will call this on the brain inside.
|
||||
/// </summary>
|
||||
public void RemovedFromBody(BodyManagerComponent old)
|
||||
public void RemovedFromBody(IBodyManagerComponent old)
|
||||
{
|
||||
OnRemovedFromBody(old);
|
||||
TryRemoveNetwork(old);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is removed from a
|
||||
/// <see cref="BodyPart"/>.
|
||||
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is
|
||||
/// removed from a <see cref="IBodyPart"/>.
|
||||
/// For instance, taking a brain out of ones head.
|
||||
/// </summary>
|
||||
public void RemovedFromPart(BodyPart old)
|
||||
public void RemovedFromPart(IBodyPart old)
|
||||
{
|
||||
OnRemovedFromPart(old);
|
||||
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)
|
||||
{
|
||||
@@ -137,7 +137,7 @@ namespace Content.Server.Body.Mechanisms.Behaviors
|
||||
protected virtual void OnRemove() { }
|
||||
|
||||
/// <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"/>.
|
||||
/// For instance, attaching a head to a body will call this on the brain inside.
|
||||
/// </summary>
|
||||
@@ -145,24 +145,24 @@ namespace Content.Server.Body.Mechanisms.Behaviors
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
protected virtual void OnInstalledIntoPart() { }
|
||||
|
||||
/// <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"/>.
|
||||
/// For instance, cutting off ones head will call this on the brain inside.
|
||||
/// </summary>
|
||||
protected virtual void OnRemovedFromBody(BodyManagerComponent old) { }
|
||||
protected virtual void OnRemovedFromBody(IBodyManagerComponent old) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is removed from a
|
||||
/// <see cref="BodyPart"/>.
|
||||
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is
|
||||
/// removed from a <see cref="IBodyPart"/>.
|
||||
/// For instance, taking a brain out of ones head.
|
||||
/// </summary>
|
||||
protected virtual void OnRemovedFromPart(BodyPart old) { }
|
||||
protected virtual void OnRemovedFromPart(IBodyPart old) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called every update when this behavior is connected to a
|
||||
|
||||
103
Content.Server/Body/Mechanisms/IMechanism.cs
Normal file
103
Content.Server/Body/Mechanisms/IMechanism.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -12,13 +12,13 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Content.Server.Body.Mechanisms
|
||||
{
|
||||
/// <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,
|
||||
/// binary communicators, and other things.
|
||||
/// </summary>
|
||||
public class Mechanism
|
||||
public class Mechanism : IMechanism
|
||||
{
|
||||
private BodyPart? _part;
|
||||
private IBodyPart? _part;
|
||||
|
||||
public Mechanism(MechanismPrototype data)
|
||||
{
|
||||
@@ -29,7 +29,7 @@ namespace Content.Server.Body.Mechanisms
|
||||
ExamineMessage = null!;
|
||||
RSIPath = null!;
|
||||
RSIState = null!;
|
||||
Behaviors = new List<MechanismBehavior>();
|
||||
_behaviors = new List<MechanismBehavior>();
|
||||
}
|
||||
|
||||
[ViewVariables] private bool Initialized { get; set; }
|
||||
@@ -40,78 +40,33 @@ namespace Content.Server.Body.Mechanisms
|
||||
|
||||
[ViewVariables] public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Professional description of the <see cref="Mechanism"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string Description { get; set; }
|
||||
[ViewVariables] public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The message to display upon examining a mob with this Mechanism installed.
|
||||
/// If the string is empty (""), no message will be displayed.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string ExamineMessage { get; set; }
|
||||
[ViewVariables] public string ExamineMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to the RSI that represents this <see cref="Mechanism"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string RSIPath { get; set; }
|
||||
[ViewVariables] public string RSIPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// RSI state that represents this <see cref="Mechanism"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string RSIState { get; set; }
|
||||
[ViewVariables] public string RSIState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Max HP of this <see cref="Mechanism"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int MaxDurability { get; set; }
|
||||
[ViewVariables] public int MaxDurability { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current HP of this <see cref="Mechanism"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int CurrentDurability { get; set; }
|
||||
[ViewVariables] public int CurrentDurability { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// At what HP this <see cref="Mechanism"/> is completely destroyed.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int DestroyThreshold { get; set; }
|
||||
[ViewVariables] public int DestroyThreshold { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Armor of this <see cref="Mechanism"/> against attacks.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int Resistance { get; set; }
|
||||
[ViewVariables] public int Resistance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines a handful of things - mostly whether this
|
||||
/// <see cref="Mechanism"/> can fit into a <see cref="BodyPart"/>.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int Size { get; set; }
|
||||
[ViewVariables] public int Size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// What kind of <see cref="BodyPart"/> this <see cref="Mechanism"/> can be
|
||||
/// easily installed into.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public BodyPartCompatibility Compatibility { get; set; }
|
||||
[ViewVariables] public BodyPartCompatibility Compatibility { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The behaviors that this <see cref="Mechanism"/> performs.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private List<MechanismBehavior> Behaviors { get; }
|
||||
private readonly List<MechanismBehavior> _behaviors;
|
||||
|
||||
public BodyManagerComponent? Body => Part?.Body;
|
||||
[ViewVariables] public IReadOnlyList<MechanismBehavior> Behaviors => _behaviors;
|
||||
|
||||
public BodyPart? Part
|
||||
public IBodyManagerComponent? Body => Part?.Body;
|
||||
|
||||
public IBodyPart? Part
|
||||
{
|
||||
get => _part;
|
||||
set
|
||||
@@ -167,7 +122,7 @@ namespace Content.Server.Body.Mechanisms
|
||||
Size = data.Size;
|
||||
Compatibility = data.Compatibility;
|
||||
|
||||
foreach (var behavior in Behaviors.ToArray())
|
||||
foreach (var behavior in _behaviors.ToArray())
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
private bool RemoveBehavior(MechanismBehavior behavior)
|
||||
public bool RemoveBehavior(MechanismBehavior behavior)
|
||||
{
|
||||
behavior.Remove();
|
||||
return Behaviors.Remove(behavior);
|
||||
if (_behaviors.Remove(behavior))
|
||||
{
|
||||
behavior.Remove();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,13 @@ namespace Content.Server.Body.Surgery
|
||||
[UsedImplicitly]
|
||||
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 _skinRetracted;
|
||||
private bool _vesselsClamped;
|
||||
|
||||
public BiologicalSurgeryData(BodyPart parent) : base(parent) { }
|
||||
public BiologicalSurgeryData(IBodyPart parent) : base(parent) { }
|
||||
|
||||
protected override SurgeryAction? GetSurgeryStep(SurgeryType toolType)
|
||||
{
|
||||
@@ -118,12 +118,12 @@ namespace Content.Server.Body.Surgery
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
public override bool CanInstallMechanism(Mechanism mechanism)
|
||||
public override bool CanInstallMechanism(IMechanism mechanism)
|
||||
{
|
||||
return _skinOpened && _vesselsClamped && _skinRetracted;
|
||||
}
|
||||
|
||||
public override bool CanAttachBodyPart(BodyPart part)
|
||||
public override bool CanAttachBodyPart(IBodyPart part)
|
||||
{
|
||||
return true;
|
||||
// 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)
|
||||
{
|
||||
performer.PopupMessage(performer, Loc.GetString("Cut open the skin..."));
|
||||
performer.PopupMessage(Loc.GetString("Cut open the skin..."));
|
||||
|
||||
// TODO do_after: Delay
|
||||
_skinOpened = true;
|
||||
@@ -139,7 +139,7 @@ namespace Content.Server.Body.Surgery
|
||||
|
||||
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
|
||||
_vesselsClamped = true;
|
||||
@@ -147,7 +147,7 @@ namespace Content.Server.Body.Surgery
|
||||
|
||||
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
|
||||
_skinRetracted = true;
|
||||
@@ -155,7 +155,7 @@ namespace Content.Server.Body.Surgery
|
||||
|
||||
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
|
||||
_skinOpened = false;
|
||||
@@ -170,7 +170,7 @@ namespace Content.Server.Body.Surgery
|
||||
return;
|
||||
}
|
||||
|
||||
var toSend = new List<Mechanism>();
|
||||
var toSend = new List<IMechanism>();
|
||||
foreach (var mechanism in Parent.Mechanisms)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (target == null || !Parent.Mechanisms.Contains(target))
|
||||
@@ -193,7 +193,7 @@ namespace Content.Server.Body.Surgery
|
||||
return;
|
||||
}
|
||||
|
||||
performer.PopupMessage(performer, Loc.GetString("Loosen the organ..."));
|
||||
performer.PopupMessage(Loc.GetString("Loosen the organ..."));
|
||||
|
||||
// TODO do_after: Delay
|
||||
_disconnectedOrgans.Add(target);
|
||||
@@ -216,8 +216,7 @@ namespace Content.Server.Body.Surgery
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveOrganSurgeryCallback(Mechanism target, IBodyPartContainer container,
|
||||
ISurgeon surgeon,
|
||||
private void RemoveOrganSurgeryCallback(IMechanism target, IBodyPartContainer container, ISurgeon surgeon,
|
||||
IEntity performer)
|
||||
{
|
||||
if (target == null || !Parent.Mechanisms.Contains(target))
|
||||
@@ -225,7 +224,7 @@ namespace Content.Server.Body.Surgery
|
||||
return;
|
||||
}
|
||||
|
||||
performer.PopupMessage(performer, Loc.GetString("Remove the organ..."));
|
||||
performer.PopupMessage(Loc.GetString("Remove the organ..."));
|
||||
|
||||
// TODO do_after: Delay
|
||||
Parent.TryDropMechanism(performer, target, out _);
|
||||
@@ -241,7 +240,7 @@ namespace Content.Server.Body.Surgery
|
||||
}
|
||||
|
||||
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
|
||||
bmTarget.DisconnectBodyPart(Parent, true);
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Content.Server.Body.Surgery
|
||||
public interface ISurgeon
|
||||
{
|
||||
public delegate void MechanismRequestCallback(
|
||||
Mechanism target,
|
||||
IMechanism target,
|
||||
IBodyPartContainer container,
|
||||
ISurgeon surgeon,
|
||||
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
|
||||
/// provided list.
|
||||
/// </summary>
|
||||
public void RequestMechanism(IEnumerable<Mechanism> options, MechanismRequestCallback callback);
|
||||
public void RequestMechanism(IEnumerable<IMechanism> options, MechanismRequestCallback callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using Robust.Shared.Interfaces.GameObjects;
|
||||
namespace Content.Server.Body.Surgery
|
||||
{
|
||||
/// <summary>
|
||||
/// This data class represents the state of a <see cref="BodyPart"/> in regards to everything surgery related -
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public abstract class SurgeryData
|
||||
@@ -14,40 +14,40 @@ namespace Content.Server.Body.Surgery
|
||||
protected delegate void SurgeryAction(IBodyPartContainer container, ISurgeon surgeon, IEntity performer);
|
||||
|
||||
/// <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
|
||||
/// <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.
|
||||
/// </summary>
|
||||
protected readonly BodyPart Parent;
|
||||
protected readonly IBodyPart Parent;
|
||||
|
||||
protected SurgeryData(BodyPart parent)
|
||||
protected SurgeryData(IBodyPart parent)
|
||||
{
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="BodyPartType"/> of the parent <see cref="BodyPart"/>.
|
||||
/// The <see cref="BodyPartType"/> of the parent <see cref="IBodyPart"/>.
|
||||
/// </summary>
|
||||
protected BodyPartType ParentType => Parent.PartType;
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
public abstract string GetDescription(IEntity target);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a <see cref="Mechanism"/> can be installed into the
|
||||
/// <see cref="BodyPart"/> this <see cref="SurgeryData"/> represents.
|
||||
/// Returns whether a <see cref="IMechanism"/> can be installed into the
|
||||
/// <see cref="IBodyPart"/> this <see cref="SurgeryData"/> represents.
|
||||
/// </summary>
|
||||
public abstract bool CanInstallMechanism(Mechanism mechanism);
|
||||
public abstract bool CanInstallMechanism(IMechanism mechanism);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the given <see cref="BodyPart"/> can be connected to the
|
||||
/// <see cref="BodyPart"/> this <see cref="SurgeryData"/> represents.
|
||||
/// Returns whether the given <see cref="IBodyPart"/> can be connected to the
|
||||
/// <see cref="IBodyPart"/> this <see cref="SurgeryData"/> represents.
|
||||
/// </summary>
|
||||
public abstract bool CanAttachBodyPart(BodyPart part);
|
||||
public abstract bool CanAttachBodyPart(IBodyPart part);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delegate corresponding to the surgery step using the given
|
||||
|
||||
@@ -7,7 +7,9 @@ using Content.Server.Interfaces.Chat;
|
||||
using Content.Server.Interfaces.GameObjects;
|
||||
using Content.Server.Observer;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.Interfaces;
|
||||
using Robust.Server.Interfaces.Console;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.Enums;
|
||||
@@ -116,6 +118,8 @@ namespace Content.Server.Chat
|
||||
|
||||
internal class SuicideCommand : IClientCommand
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public string Command => "suicide";
|
||||
|
||||
public string Description => "Commits suicide";
|
||||
@@ -133,12 +137,15 @@ namespace Content.Server.Chat
|
||||
damageableComponent.ChangeDamage(kind switch
|
||||
{
|
||||
SuicideKind.Blunt => DamageType.Blunt,
|
||||
SuicideKind.Slash => DamageType.Slash,
|
||||
SuicideKind.Piercing => DamageType.Piercing,
|
||||
SuicideKind.Heat => DamageType.Heat,
|
||||
SuicideKind.Disintegration => DamageType.Disintegration,
|
||||
SuicideKind.Cellular => DamageType.Cellular,
|
||||
SuicideKind.DNA => DamageType.DNA,
|
||||
SuicideKind.Shock => DamageType.Shock,
|
||||
SuicideKind.Cold => DamageType.Cold,
|
||||
SuicideKind.Poison => DamageType.Poison,
|
||||
SuicideKind.Radiation => DamageType.Radiation,
|
||||
SuicideKind.Asphyxiation => DamageType.Asphyxiation,
|
||||
SuicideKind.Bloodloss => DamageType.Bloodloss,
|
||||
_ => DamageType.Blunt
|
||||
},
|
||||
500,
|
||||
@@ -185,8 +192,14 @@ namespace Content.Server.Chat
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default suicide, bite your tongue
|
||||
chat.EntityMe(owner, Loc.GetString("is attempting to bite {0:their} own tongue, looks like {0:theyre} trying to commit suicide!", owner)); //TODO: theyre macro
|
||||
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);
|
||||
|
||||
// Prevent the player from returning to the body. Yes, this is an ugly hack.
|
||||
|
||||
@@ -109,6 +109,9 @@ namespace Content.Server.Chat
|
||||
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 clients = _playerManager.GetPlayersInRange(pos, VoiceRange).Select(p => p.ConnectedClient);
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Access;
|
||||
using Content.Shared.GameObjects.Components.Access;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects.Components.Container;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
@@ -25,7 +25,6 @@ namespace Content.Server.GameObjects.Components.Access
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
public class IdCardConsoleComponent : SharedIdCardConsoleComponent, IActivate
|
||||
{
|
||||
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private ContainerSlot _privilegedIdContainer = default!;
|
||||
@@ -132,7 +131,7 @@ namespace Content.Server.GameObjects.Components.Access
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -161,7 +160,7 @@ namespace Content.Server.GameObjects.Components.Access
|
||||
|
||||
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;
|
||||
}
|
||||
UpdateUserInterface();
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -22,6 +20,7 @@ using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Robust.Shared.Maths;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Utility;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
@@ -29,9 +28,6 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
[RegisterComponent]
|
||||
public class CuffableComponent : SharedCuffableComponent
|
||||
{
|
||||
[Dependency]
|
||||
private readonly ISharedNotifyManager _notifyManager;
|
||||
|
||||
/// <summary>
|
||||
/// How many of this entity's hands are currently cuffed.
|
||||
/// </summary>
|
||||
@@ -109,11 +105,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
|
||||
handcuff.Transform.MapPosition,
|
||||
Owner.Transform.MapPosition,
|
||||
_interactRange,
|
||||
ignoredEnt: Owner))
|
||||
if (!handcuff.InRangeUnobstructed(Owner, _interactRange))
|
||||
{
|
||||
Logger.Warning("Handcuffs being applied to player are obstructed or too far away! This should not happen!");
|
||||
return;
|
||||
@@ -193,8 +185,14 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
{
|
||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent status))
|
||||
{
|
||||
status.ChangeStatusEffectIcon(StatusEffect.Cuffed,
|
||||
CanStillInteract ? "/Textures/Interface/StatusEffects/Handcuffed/Uncuffed.png" : "/Textures/Interface/StatusEffects/Handcuffed/Handcuffed.png");
|
||||
if (CanStillInteract)
|
||||
{
|
||||
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))
|
||||
{
|
||||
user.PopupMessage(user, Loc.GetString("You can't do that!"));
|
||||
user.PopupMessage(Loc.GetString("You can't do that!"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isOwner &&
|
||||
!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
|
||||
user.Transform.MapPosition,
|
||||
Owner.Transform.MapPosition,
|
||||
_interactRange,
|
||||
ignoredEnt: Owner))
|
||||
if (!isOwner && user.InRangeUnobstructed(Owner, _interactRange))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
|
||||
cuffsToRemove.Transform.MapPosition,
|
||||
Owner.Transform.MapPosition,
|
||||
_interactRange,
|
||||
ignoredEnt: Owner))
|
||||
if (!cuffsToRemove.InRangeUnobstructed(Owner, _interactRange))
|
||||
{
|
||||
Logger.Warning("Handcuffs being removed from player are obstructed or too far away! This should not happen!");
|
||||
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>();
|
||||
audio.PlayFromEntity(isOwner ? cuff.StartBreakoutSound : cuff.StartUncuffSound, Owner);
|
||||
@@ -298,29 +287,29 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
|
||||
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)
|
||||
{
|
||||
_notifyManager.PopupMessage(user, Owner, Loc.GetString("{0:theName} uncuffs your hands.", user));
|
||||
user.PopupMessage(Owner, Loc.GetString("{0:theName} uncuffs your hands.", user));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isOwner)
|
||||
{
|
||||
_notifyManager.PopupMessage(user, user, 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(Loc.GetString("You successfully remove the cuffs. {0} of {1:theName}'s hands remain cuffed.", CuffedHandCount, user));
|
||||
user.PopupMessage(Owner, Loc.GetString("{0:theName} removes your cuffs. {1} of your hands remain cuffed.", user, CuffedHandCount));
|
||||
}
|
||||
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
|
||||
{
|
||||
_notifyManager.PopupMessage(user, user, Loc.GetString("You fail to remove the cuffs."));
|
||||
user.PopupMessage(Loc.GetString("You fail to remove the cuffs."));
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
@@ -16,15 +16,13 @@ using Content.Shared.GameObjects.Components.ActionBlocking;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Robust.Shared.Maths;
|
||||
using System;
|
||||
using Content.Shared.Utility;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class HandcuffComponent : SharedHandcuffComponent, IAfterInteract
|
||||
{
|
||||
[Dependency]
|
||||
private readonly ISharedNotifyManager _notifyManager;
|
||||
|
||||
/// <summary>
|
||||
/// The time it takes to apply a <see cref="CuffedComponent"/> to an entity.
|
||||
/// </summary>
|
||||
@@ -160,40 +158,36 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (Broken)
|
||||
{
|
||||
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("The cuffs are broken!"));
|
||||
eventArgs.User.PopupMessage(Loc.GetString("The cuffs are broken!"));
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
|
||||
eventArgs.User.Transform.MapPosition,
|
||||
eventArgs.Target.Transform.MapPosition,
|
||||
_interactRange,
|
||||
ignoredEnt: Owner))
|
||||
if (!eventArgs.InRangeUnobstructed(_interactRange, ignoreInsideBlocker: true))
|
||||
{
|
||||
_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;
|
||||
}
|
||||
|
||||
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, 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(Loc.GetString("You start cuffing {0:theName}.", eventArgs.Target));
|
||||
eventArgs.User.PopupMessage(eventArgs.Target, Loc.GetString("{0:theName} starts cuffing you!", eventArgs.User));
|
||||
_audioSystem.PlayFromEntity(StartCuffSound, Owner);
|
||||
|
||||
TryUpdateCuff(eventArgs.User, eventArgs.Target, cuffed);
|
||||
@@ -225,8 +219,8 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
if (result != DoAfterStatus.Cancelled)
|
||||
{
|
||||
_audioSystem.PlayFromEntity(EndCuffSound, Owner);
|
||||
_notifyManager.PopupMessage(user, user, Loc.GetString("You successfully cuff {0:theName}.", target));
|
||||
_notifyManager.PopupMessage(target, target, Loc.GetString("You have been cuffed by {0:theName}!", user));
|
||||
user.PopupMessage(Loc.GetString("You successfully cuff {0:theName}.", target));
|
||||
target.PopupMessage(Loc.GetString("You have been cuffed by {0:theName}!", user));
|
||||
|
||||
if (user.TryGetComponent<HandsComponent>(out var hands))
|
||||
{
|
||||
@@ -240,8 +234,8 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
}
|
||||
else
|
||||
{
|
||||
user.PopupMessage(user, 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));
|
||||
user.PopupMessage(Loc.GetString("You were interrupted while cuffing {0:theName}!", target));
|
||||
target.PopupMessage(Loc.GetString("You interrupt {0:theName} while they are cuffing you!", user));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
@@ -24,7 +24,6 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
[RegisterComponent]
|
||||
public class GasAnalyzerComponent : SharedGasAnalyzerComponent, IAfterInteract, IDropped, IUse
|
||||
{
|
||||
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private GasAnalyzerDanger _pressureDanger;
|
||||
@@ -207,17 +206,14 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
|
||||
if (!player.TryGetComponent(out IHandsComponent? handsComponent))
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, player,
|
||||
Loc.GetString("You have no hands."));
|
||||
Owner.PopupMessage(player, Loc.GetString("You have no hands."));
|
||||
return;
|
||||
}
|
||||
|
||||
var activeHandEntity = handsComponent.GetActiveHand?.Owner;
|
||||
if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent? gasAnalyzer))
|
||||
{
|
||||
_notifyManager.PopupMessage(serverMsg.Session.AttachedEntity,
|
||||
serverMsg.Session.AttachedEntity,
|
||||
Loc.GetString("You need a Gas Analyzer in your hand!"));
|
||||
serverMsg.Session.AttachedEntity.PopupMessage(Loc.GetString("You need a Gas Analyzer in your hand!"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -231,7 +227,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,10 +14,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.Map;
|
||||
using Robust.Shared.GameObjects.Components.Transform;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -32,6 +29,8 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
public class GridAtmosphereComponent : Component, IGridAtmosphereComponent
|
||||
{
|
||||
[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>
|
||||
/// Check current execution time every n instances processed.
|
||||
@@ -70,7 +69,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
private double _excitedGroupLastProcess;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<MapIndices, TileAtmosphere> _tiles = new Dictionary<MapIndices, TileAtmosphere>(1000);
|
||||
protected readonly Dictionary<MapIndices, TileAtmosphere> Tiles = new Dictionary<MapIndices, TileAtmosphere>(1000);
|
||||
|
||||
[ViewVariables]
|
||||
private readonly HashSet<TileAtmosphere> _activeTiles = new HashSet<TileAtmosphere>(1000);
|
||||
@@ -154,23 +153,13 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PryTile(MapIndices indices)
|
||||
public virtual void PryTile(MapIndices indices)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out IMapGridComponent? mapGridComponent)) return;
|
||||
if (IsSpace(indices) || IsAirBlocked(indices)) return;
|
||||
|
||||
var mapGrid = mapGridComponent.Grid;
|
||||
var tile = mapGrid.GetTileRef(indices).Tile;
|
||||
|
||||
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);
|
||||
indices.PryTile(mapGrid.Index, _mapManager, _tileDefinitionManager, _serverEntityManager);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
@@ -185,17 +174,17 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
RepopulateTiles();
|
||||
}
|
||||
|
||||
public void RepopulateTiles()
|
||||
public virtual 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}));
|
||||
if(!Tiles.ContainsKey(tile.GridIndices))
|
||||
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.UpdateVisuals();
|
||||
@@ -203,12 +192,12 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Invalidate(MapIndices indices)
|
||||
public virtual void Invalidate(MapIndices indices)
|
||||
{
|
||||
_invalidatedCoords.Add(indices);
|
||||
}
|
||||
|
||||
private void Revalidate()
|
||||
protected virtual void Revalidate()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
|
||||
|
||||
@@ -219,14 +208,14 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
if (tile == null)
|
||||
{
|
||||
tile = new TileAtmosphere(this, mapGrid.Grid.Index, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C});
|
||||
_tiles[indices] = tile;
|
||||
Tiles[indices] = tile;
|
||||
}
|
||||
|
||||
if (IsSpace(indices))
|
||||
{
|
||||
tile.Air = new GasMixture(GetVolumeForCells(1));
|
||||
tile.Air.MarkImmutable();
|
||||
_tiles[indices] = tile;
|
||||
Tiles[indices] = tile;
|
||||
|
||||
} else if (IsAirBlocked(indices))
|
||||
{
|
||||
@@ -271,18 +260,18 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void FixVacuum(MapIndices indices)
|
||||
public virtual void FixVacuum(MapIndices indices)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
|
||||
var tile = GetTile(indices);
|
||||
if (tile?.GridIndex != mapGrid.Grid.Index) return;
|
||||
var adjacent = GetAdjacentTiles(indices);
|
||||
tile.Air = new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C};
|
||||
_tiles[indices] = tile;
|
||||
Tiles[indices] = tile;
|
||||
|
||||
var ratio = 1f / adjacent.Count;
|
||||
|
||||
foreach (var (direction, adj) in adjacent)
|
||||
foreach (var (_, adj) in adjacent)
|
||||
{
|
||||
var mix = adj.Air.RemoveRatio(ratio);
|
||||
tile.Air.Merge(mix);
|
||||
@@ -292,17 +281,17 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddActiveTile(TileAtmosphere? tile)
|
||||
public virtual void AddActiveTile(TileAtmosphere? tile)
|
||||
{
|
||||
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;
|
||||
_activeTiles.Add(tile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RemoveActiveTile(TileAtmosphere? tile)
|
||||
public virtual void RemoveActiveTile(TileAtmosphere? tile)
|
||||
{
|
||||
if (tile == null) return;
|
||||
_activeTiles.Remove(tile);
|
||||
@@ -312,7 +301,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddHotspotTile(TileAtmosphere? tile)
|
||||
public virtual void AddHotspotTile(TileAtmosphere? tile)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
|
||||
if (tile?.GridIndex != mapGrid.Grid.Index || tile?.Air == null) return;
|
||||
@@ -321,20 +310,20 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RemoveHotspotTile(TileAtmosphere? tile)
|
||||
public virtual void RemoveHotspotTile(TileAtmosphere? tile)
|
||||
{
|
||||
if (tile == null) return;
|
||||
_hotspotTiles.Remove(tile);
|
||||
}
|
||||
|
||||
public void AddSuperconductivityTile(TileAtmosphere? tile)
|
||||
public virtual void AddSuperconductivityTile(TileAtmosphere? tile)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
|
||||
if (tile?.GridIndex != mapGrid.Grid.Index) return;
|
||||
_superconductivityTiles.Add(tile);
|
||||
}
|
||||
|
||||
public void RemoveSuperconductivityTile(TileAtmosphere? tile)
|
||||
public virtual void RemoveSuperconductivityTile(TileAtmosphere? tile)
|
||||
{
|
||||
if (tile == null) return;
|
||||
_superconductivityTiles.Remove(tile);
|
||||
@@ -342,7 +331,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddHighPressureDelta(TileAtmosphere? tile)
|
||||
public virtual void AddHighPressureDelta(TileAtmosphere? tile)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
|
||||
if (tile?.GridIndex != mapGrid.Grid.Index) return;
|
||||
@@ -351,21 +340,21 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool HasHighPressureDelta(TileAtmosphere tile)
|
||||
public virtual bool HasHighPressureDelta(TileAtmosphere tile)
|
||||
{
|
||||
return _highPressureDelta.Contains(tile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddExcitedGroup(ExcitedGroup excitedGroup)
|
||||
public virtual void AddExcitedGroup(ExcitedGroup excitedGroup)
|
||||
{
|
||||
_excitedGroups.Add(excitedGroup);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RemoveExcitedGroup(ExcitedGroup excitedGroup)
|
||||
public virtual void RemoveExcitedGroup(ExcitedGroup excitedGroup)
|
||||
{
|
||||
_excitedGroups.Remove(excitedGroup);
|
||||
}
|
||||
@@ -401,7 +390,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
{
|
||||
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!
|
||||
if (IsSpace(indices) && createSpace)
|
||||
@@ -454,7 +443,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update(float frameTime)
|
||||
public virtual void Update(float frameTime)
|
||||
{
|
||||
_timer += frameTime;
|
||||
|
||||
@@ -554,7 +543,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
UpdateCounter++;
|
||||
}
|
||||
|
||||
public bool ProcessTileEqualize(bool resumed = false)
|
||||
public virtual bool ProcessTileEqualize(bool resumed = false)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
@@ -581,7 +570,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ProcessActiveTiles(bool resumed = false)
|
||||
public virtual bool ProcessActiveTiles(bool resumed = false)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
@@ -608,7 +597,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ProcessExcitedGroups(bool resumed = false)
|
||||
public virtual bool ProcessExcitedGroups(bool resumed = false)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
@@ -642,7 +631,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ProcessHighPressureDelta(bool resumed = false)
|
||||
public virtual bool ProcessHighPressureDelta(bool resumed = false)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
@@ -672,7 +661,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ProcessHotspots(bool resumed = false)
|
||||
protected virtual bool ProcessHotspots(bool resumed = false)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
@@ -699,7 +688,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ProcessSuperconductivity(bool resumed = false)
|
||||
protected virtual bool ProcessSuperconductivity(bool resumed = false)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
@@ -726,7 +715,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ProcessPipeNets(bool resumed = false)
|
||||
protected virtual bool ProcessPipeNets(bool resumed = false)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
@@ -753,7 +742,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ProcessPipeNetDevices(bool resumed = false)
|
||||
protected virtual bool ProcessPipeNetDevices(bool resumed = false)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
|
||||
@@ -761,7 +750,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
_currentRunPipeNetDevice = new Queue<PipeNetDeviceComponent>(_pipeNetDevices);
|
||||
|
||||
var number = 0;
|
||||
while (_currentRunPipeNet.Count > 0)
|
||||
while (_currentRunPipeNetDevice.Count > 0)
|
||||
{
|
||||
var device = _currentRunPipeNetDevice.Dequeue();
|
||||
device.Update();
|
||||
@@ -810,11 +799,11 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
!serializer.TryReadDataField("tiles", out Dictionary<MapIndices, int>? tiles))
|
||||
return;
|
||||
|
||||
_tiles.Clear();
|
||||
Tiles.Clear();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -823,7 +812,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
var uniqueMixes = new List<GasMixture>();
|
||||
var uniqueMixHash = new Dictionary<GasMixture, int>();
|
||||
var tiles = new Dictionary<MapIndices, int>();
|
||||
foreach (var (indices, tile) in _tiles)
|
||||
foreach (var (indices, tile) in Tiles)
|
||||
{
|
||||
if (tile.Air == null) continue;
|
||||
|
||||
@@ -846,7 +835,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
|
||||
public IEnumerator<TileAtmosphere> GetEnumerator()
|
||||
{
|
||||
return _tiles.Values.GetEnumerator();
|
||||
return Tiles.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
@@ -855,7 +844,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void BurnTile(MapIndices gridIndices)
|
||||
public virtual void BurnTile(MapIndices gridIndices)
|
||||
{
|
||||
// TODO ATMOS
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.GameObjects.Components.NodeContainer;
|
||||
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.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -13,6 +16,21 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping
|
||||
/// </summary>
|
||||
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>
|
||||
/// Needs to be same <see cref="PipeDirection"/> as that of a <see cref="Pipe"/> on this entity.
|
||||
/// </summary>
|
||||
@@ -31,6 +49,8 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping
|
||||
[ViewVariables]
|
||||
private PipeNode _outletPipe;
|
||||
|
||||
private AppearanceComponent _appearance;
|
||||
|
||||
public override void ExposeData(ObjectSerializer 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)}.");
|
||||
return;
|
||||
}
|
||||
Owner.TryGetComponent(out _appearance);
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (!PumpEnabled)
|
||||
return;
|
||||
|
||||
PumpGas(_inletPipe.Air, _outletPipe.Air);
|
||||
}
|
||||
|
||||
protected abstract void PumpGas(GasMixture inletGas, GasMixture outletGas);
|
||||
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
_appearance?.SetData(PumpVisuals.VisualState, new PumpVisualState(_inletDirection, _outletDirection, _inletPipe.ConduitLayer, _outletPipe.ConduitLayer, PumpEnabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ using Content.Server.Body.Network;
|
||||
using Content.Server.GameObjects.Components.Metabolism;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Interaction;
|
||||
using Content.Server.Mobs;
|
||||
using Content.Server.Observer;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Body.Part.Properties.Movement;
|
||||
@@ -20,6 +19,7 @@ using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Reflection;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -33,13 +33,14 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Content.Server.GameObjects.Components.Body
|
||||
{
|
||||
/// <summary>
|
||||
/// Component representing a collection of <see cref="BodyPart"></see>
|
||||
/// Component representing a collection of <see cref="IBodyPart"></see>
|
||||
/// attached to each other.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
[ComponentReference(typeof(ISharedBodyManagerComponent))]
|
||||
[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 IBodyNetworkFactory _bodyNetworkFactory = default!;
|
||||
@@ -47,38 +48,28 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
|
||||
[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>();
|
||||
|
||||
/// <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
|
||||
/// they're on is.
|
||||
/// </summary>
|
||||
[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>
|
||||
/// The <see cref="BodyTemplate"/> that this <see cref="BodyManagerComponent"/>
|
||||
/// 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"/>
|
||||
/// Maps <see cref="BodyTemplate"/> slot name to the <see cref="IBodyPart"/>
|
||||
/// object filling it (if there is one).
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public IReadOnlyDictionary<string, BodyPart> Parts => _parts;
|
||||
public IReadOnlyDictionary<string, IBodyPart> Parts => _parts;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
// BodyComponent.
|
||||
var addedPart = new BodyPart(newPartData);
|
||||
AddBodyPart(addedPart, slotName);
|
||||
TryAddPart(slotName, addedPart);
|
||||
}
|
||||
|
||||
OnBodyChanged(); // TODO: Duplicate code
|
||||
@@ -179,8 +170,8 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
/// <summary>
|
||||
/// Changes the current <see cref="BodyTemplate"/> to the given
|
||||
/// <see cref="BodyTemplate"/>.
|
||||
/// Attempts to keep previous <see cref="BodyPart"/> if there is a slot for
|
||||
/// them in both <see cref="BodyTemplate"/>.
|
||||
/// Attempts to keep previous <see cref="IBodyPart"/> if there is a
|
||||
/// slot for them in both <see cref="BodyTemplate"/>.
|
||||
/// </summary>
|
||||
public void ChangeBodyTemplate(BodyTemplatePrototype newTemplate)
|
||||
{
|
||||
@@ -279,25 +270,20 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
|
||||
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))
|
||||
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)
|
||||
{
|
||||
// Case: no way of moving. Fall down.
|
||||
StandingStateHelper.Down(Owner);
|
||||
playerMover.BaseWalkSpeed = 0.8f;
|
||||
playerMover.BaseSprintSpeed = 2.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Case: have at least one leg. Set move speed.
|
||||
StandingStateHelper.Standing(Owner);
|
||||
|
||||
// Extra legs stack diminishingly.
|
||||
// Final speed = speed sum/(leg count-log4(leg count))
|
||||
playerMover.BaseWalkSpeed =
|
||||
@@ -320,11 +306,11 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// of <see cref="BodyPart"/>s.
|
||||
/// of <see cref="IBodyPart"/>s.
|
||||
/// </summary>
|
||||
/// <param name="target">The body part to find the center for.</param>
|
||||
/// <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>();
|
||||
|
||||
@@ -334,9 +320,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
|
||||
private bool ConnectedToCenterPartRecursion(ICollection<string> searchedSlots, string slotName)
|
||||
{
|
||||
TryGetBodyPart(slotName, out var part);
|
||||
|
||||
if (part == null)
|
||||
if (!TryGetBodyPart(slotName, out var part))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -366,14 +350,14 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="BodyPart"/> if one exists, null otherwise.</returns>
|
||||
private BodyPart? GetCenterBodyPart()
|
||||
private IBodyPart? GetCenterBodyPart()
|
||||
{
|
||||
Parts.TryGetValue(Template.CenterSlot, out var center);
|
||||
return center!;
|
||||
return center;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -386,29 +370,31 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
/// <param name="slotName">The slot to search in.</param>
|
||||
/// <param name="result">The body part in that slot, if any.</param>
|
||||
/// <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!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the slotName that the given <see cref="BodyPart"/> resides in.
|
||||
/// Finds the slotName that the given <see cref="IBodyPart"/> resides in.
|
||||
/// </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>
|
||||
/// <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,
|
||||
// so we can iterate through the dictionary values to get the key from there.
|
||||
result = Parts.FirstOrDefault(x => x.Value == part).Key;
|
||||
return result != null;
|
||||
var pair = Parts.FirstOrDefault(x => x.Value == part);
|
||||
result = pair.Key;
|
||||
|
||||
return !pair.Equals(default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -447,7 +433,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
/// True if successful, false if there was an error or no connected
|
||||
/// <see cref="BodyPart"/>s were found.
|
||||
/// </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!;
|
||||
|
||||
@@ -456,7 +442,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
return false;
|
||||
}
|
||||
|
||||
var toReturn = new List<BodyPart>();
|
||||
var toReturn = new List<IBodyPart>();
|
||||
foreach (var connection in connections)
|
||||
{
|
||||
if (TryGetBodyPart(connection, out var bodyPartResult))
|
||||
@@ -480,9 +466,9 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// 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>
|
||||
private bool TryGetBodyPartConnections(BodyPart part, [NotNullWhen(true)] out List<BodyPart> result)
|
||||
private bool TryGetBodyPartConnections(IBodyPart part, [NotNullWhen(true)] out List<IBodyPart> result)
|
||||
{
|
||||
result = null!;
|
||||
|
||||
@@ -491,11 +477,11 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
}
|
||||
|
||||
/// <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>
|
||||
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)
|
||||
{
|
||||
@@ -508,32 +494,6 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
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>
|
||||
/// Installs the given <see cref="DroppedBodyPartComponent"/> into the
|
||||
/// given slot, deleting the <see cref="IEntity"/> afterwards.
|
||||
@@ -543,7 +503,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
{
|
||||
DebugTools.AssertNotNull(part);
|
||||
|
||||
if (!InstallBodyPart(part.ContainedBodyPart, slotName))
|
||||
if (!TryAddPart(slotName, part.ContainedBodyPart))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -553,15 +513,15 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects the given <see cref="BodyPart"/> reference, potentially
|
||||
/// dropping other <see cref="BodyPart">BodyParts</see> if they were hanging
|
||||
/// Disconnects the given <see cref="IBodyPart"/> reference, potentially
|
||||
/// dropping other <see cref="IBodyPart">BodyParts</see> if they were hanging
|
||||
/// off of it.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// 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>
|
||||
public IEntity? DropPart(BodyPart part)
|
||||
public IEntity? DropPart(IBodyPart part)
|
||||
{
|
||||
DebugTools.AssertNotNull(part);
|
||||
|
||||
@@ -595,36 +555,18 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects the given <see cref="BodyPart"/> reference, potentially
|
||||
/// dropping other <see cref="BodyPart">BodyParts</see> if they were hanging
|
||||
/// Disconnects the given <see cref="IBodyPart"/> reference, potentially
|
||||
/// dropping other <see cref="IBodyPart">BodyParts</see> if they were hanging
|
||||
/// off of it.
|
||||
/// </summary>
|
||||
public void DisconnectBodyPart(BodyPart part, bool dropEntity)
|
||||
public void DisconnectBodyPart(IBodyPart part, bool dropEntity)
|
||||
{
|
||||
DebugTools.AssertNotNull(part);
|
||||
|
||||
if (!_parts.ContainsValue(part))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var slotName = _parts.FirstOrDefault(x => x.Value == part).Key;
|
||||
if (string.IsNullOrEmpty(slotName)) 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>
|
||||
@@ -639,12 +581,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
{
|
||||
DebugTools.AssertNotNull(slotName);
|
||||
|
||||
if (!TryGetBodyPart(slotName, out var part))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (part == null)
|
||||
if (!HasPart(slotName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -665,27 +602,47 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
OnBodyChanged();
|
||||
}
|
||||
|
||||
private void AddBodyPart(BodyPart part, string slotName)
|
||||
public bool TryAddPart(string slot, IBodyPart part, bool force = false)
|
||||
{
|
||||
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;
|
||||
|
||||
var argsAdded = new BodyPartAddedEventArgs(part, slotName);
|
||||
var argsAdded = new BodyPartAddedEventArgs(part, slot);
|
||||
|
||||
foreach (var component in Owner.GetAllComponents<IBodyPartAdded>().ToArray())
|
||||
{
|
||||
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))
|
||||
{
|
||||
Logger.Warning($"Template {Template.Name} has an invalid RSI map key {partMap} for body part {part.Name}.");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
part.RSIMap = partEnum;
|
||||
@@ -711,6 +668,13 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
|
||||
SendNetworkMessage(mechanismMessage);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool HasPart(string slot)
|
||||
{
|
||||
return _parts.ContainsKey(slot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -719,7 +683,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
/// </summary>
|
||||
/// <param name="slotName">The slot to remove it from.</param>
|
||||
/// <param name="drop">
|
||||
/// Whether or not to drop the removed <see cref="BodyPart"/>.
|
||||
/// Whether or not to drop the removed <see cref="IBodyPart"/>.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
private bool RemoveBodyPart(string slotName, bool drop)
|
||||
@@ -770,6 +734,21 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -779,17 +758,20 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
/// <param name="part">The part to remove from this body.</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>
|
||||
private bool RemoveBodyPart(BodyPart part, [NotNullWhen(true)] out string slotName)
|
||||
private bool RemoveBodyPart(IBodyPart part, [NotNullWhen(true)] out string? slotName)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
slotName = pair.Key;
|
||||
|
||||
return RemoveBodyPart(slotName, false);
|
||||
}
|
||||
|
||||
@@ -803,13 +785,13 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
|
||||
if (_networks.ContainsKey(network.GetType()))
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
_networks.Add(network.GetType(), network);
|
||||
network.OnAdd(Owner);
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -843,62 +825,21 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
return EnsureNetwork(typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
public void RemoveNetwork(Type networkType)
|
||||
{
|
||||
DebugTools.AssertNotNull(networkName);
|
||||
DebugTools.AssertNotNull(networkType);
|
||||
|
||||
var network = _bodyNetworkFactory.GetNetwork(networkName);
|
||||
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))
|
||||
if (_networks.Remove(networkType, out var network))
|
||||
{
|
||||
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
|
||||
{
|
||||
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>
|
||||
/// Attempts to get the <see cref="BodyNetwork"/> of the given type in this body.
|
||||
/// </summary>
|
||||
@@ -923,7 +864,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
/// a foot node from the given node. It can
|
||||
/// only search through BodyParts with <see cref="ExtensionProperty"/>.
|
||||
/// </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))
|
||||
{
|
||||
@@ -933,7 +874,8 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
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)
|
||||
{
|
||||
if (!current.TryGetProperty<ExtensionProperty>(out var extProperty))
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
/// <summary>
|
||||
/// Copy BodyTemplate and BodyPart data into a common data class that the client can read.
|
||||
/// </summary>
|
||||
private BodyScannerInterfaceState InterfaceState(BodyTemplate template, IReadOnlyDictionary<string, BodyPart> bodyParts)
|
||||
private BodyScannerInterfaceState InterfaceState(BodyTemplate template, IReadOnlyDictionary<string, IBodyPart> bodyParts)
|
||||
{
|
||||
var partsData = new Dictionary<string, BodyScannerBodyPartData>();
|
||||
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Server.Body;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Body.Surgery;
|
||||
using Content.Shared.Interfaces;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -24,8 +23,6 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
[RegisterComponent]
|
||||
public class DroppedBodyPartComponent : Component, IAfterInteract, IBodyPartContainer
|
||||
{
|
||||
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager = default!;
|
||||
|
||||
private readonly Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
|
||||
private BodyManagerComponent? _bodyManagerComponentCache;
|
||||
private int _idHash;
|
||||
@@ -101,7 +98,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
|
||||
foreach (var connectedPart in parts)
|
||||
{
|
||||
if (!connectedPart.CanAttachBodyPart(ContainedBodyPart))
|
||||
if (!connectedPart.CanAttachPart(ContainedBodyPart))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -121,7 +118,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -163,10 +160,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
message = Loc.GetString("You can't attach it!");
|
||||
}
|
||||
|
||||
_sharedNotifyManager.PopupMessage(
|
||||
_bodyManagerComponentCache.Owner,
|
||||
_performerCache,
|
||||
message);
|
||||
_bodyManagerComponentCache.Owner.PopupMessage(_performerCache, message);
|
||||
}
|
||||
|
||||
private void OpenSurgeryUI(IPlayerSession session)
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
[RegisterComponent]
|
||||
public class DroppedMechanismComponent : Component, IAfterInteract
|
||||
{
|
||||
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public sealed override string Name => "DroppedMechanism";
|
||||
@@ -41,7 +40,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
|
||||
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);
|
||||
|
||||
@@ -67,8 +66,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
|
||||
if (!droppedBodyPart.ContainedBodyPart.TryInstallDroppedMechanism(this))
|
||||
{
|
||||
_sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User,
|
||||
Loc.GetString("You can't fit it in!"));
|
||||
eventArgs.Target.PopupMessage(eventArgs.User, 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;
|
||||
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.
|
||||
{
|
||||
_sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User,
|
||||
eventArgs.Target.PopupMessage(eventArgs.User,
|
||||
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
|
||||
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));
|
||||
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 can't fit it in!");
|
||||
|
||||
_sharedNotifyManager.PopupMessage(
|
||||
_bodyManagerComponentCache.Owner,
|
||||
_performerCache,
|
||||
message);
|
||||
_bodyManagerComponentCache.Owner.PopupMessage(_performerCache, message);
|
||||
|
||||
// TODO: {1:theName}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -34,8 +33,6 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
[RegisterComponent]
|
||||
public class SurgeryToolComponent : Component, ISurgeon, IAfterInteract
|
||||
{
|
||||
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager = default!;
|
||||
|
||||
public override string Name => "SurgeryTool";
|
||||
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 void RequestMechanism(IEnumerable<Mechanism> options, ISurgeon.MechanismRequestCallback callback)
|
||||
public void RequestMechanism(IEnumerable<IMechanism> options, ISurgeon.MechanismRequestCallback callback)
|
||||
{
|
||||
var toSend = new Dictionary<string, int>();
|
||||
foreach (var mechanism in options)
|
||||
@@ -233,7 +230,7 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
|
||||
/// <summary>
|
||||
/// Called after the client chooses from a list of possible
|
||||
/// <see cref="Mechanism"/> to choose from.
|
||||
/// <see cref="IMechanism"/> to choose from.
|
||||
/// </summary>
|
||||
private void HandleReceiveMechanism(int key)
|
||||
{
|
||||
@@ -254,27 +251,13 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
|
||||
private void SendNoUsefulWayToUsePopup()
|
||||
{
|
||||
if (_bodyManagerComponentCache == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_sharedNotifyManager.PopupMessage(
|
||||
_bodyManagerComponentCache.Owner,
|
||||
_performerCache,
|
||||
_bodyManagerComponentCache?.Owner.PopupMessage(_performerCache,
|
||||
Loc.GetString("You see no useful way to use {0:theName}.", Owner));
|
||||
}
|
||||
|
||||
private void SendNoUsefulWayToUseAnymorePopup()
|
||||
{
|
||||
if (_bodyManagerComponentCache == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_sharedNotifyManager.PopupMessage(
|
||||
_bodyManagerComponentCache.Owner,
|
||||
_performerCache,
|
||||
_bodyManagerComponentCache?.Owner.PopupMessage(_performerCache,
|
||||
Loc.GetString("You see no useful way to use {0:theName} anymore.", Owner));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,17 @@ using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.Components.Mobs.State;
|
||||
using Content.Server.GameObjects.Components.Strap;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.Mobs;
|
||||
using Content.Server.Utility;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components.Buckle;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Strap;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.EntitySystemMessages;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
@@ -37,7 +38,6 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
private int _size;
|
||||
@@ -112,10 +112,14 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
{
|
||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent? status))
|
||||
{
|
||||
status.ChangeStatusEffectIcon(StatusEffect.Buckled,
|
||||
Buckled
|
||||
? BuckledTo!.BuckledIcon
|
||||
: "/Textures/Interface/StatusEffects/Buckle/unbuckled.png");
|
||||
if (Buckled)
|
||||
{
|
||||
status.ChangeStatusEffectIcon(StatusEffect.Buckled, BuckledTo!.BuckledIcon);
|
||||
}
|
||||
else
|
||||
{
|
||||
status.RemoveStatusEffect(StatusEffect.Buckled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,11 +140,11 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
ownTransform.WorldRotation = strapTransform.WorldRotation;
|
||||
break;
|
||||
case StrapPosition.Stand:
|
||||
StandingStateHelper.Standing(Owner);
|
||||
EntitySystem.Get<StandingStateSystem>().Standing(Owner);
|
||||
ownTransform.WorldRotation = strapTransform.WorldRotation;
|
||||
break;
|
||||
case StrapPosition.Down:
|
||||
StandingStateHelper.Down(Owner, force: true);
|
||||
EntitySystem.Get<StandingStateSystem>().Down(Owner, force: true);
|
||||
ownTransform.WorldRotation = Angle.South;
|
||||
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;
|
||||
|
||||
@@ -169,32 +173,26 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
|
||||
if (!ActionBlockerSystem.CanInteract(user))
|
||||
{
|
||||
_notifyManager.PopupMessage(user, user,
|
||||
Loc.GetString("You can't do that!"));
|
||||
|
||||
user.PopupMessage(Loc.GetString("You can't do that!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!to.TryGetComponent(out strap))
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner, user,
|
||||
Loc.GetString(Owner == user
|
||||
? "You can't buckle yourself there!"
|
||||
: "You can't buckle {0:them} there!", Owner));
|
||||
var message = Loc.GetString(Owner == user
|
||||
? "You can't buckle yourself there!"
|
||||
: "You can't buckle {0:them} there!", Owner);
|
||||
Owner.PopupMessage(user, message);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var ownerPosition = Owner.Transform.MapPosition;
|
||||
var strapPosition = strap.Owner.Transform.MapPosition;
|
||||
var interaction = EntitySystem.Get<SharedInteractionSystem>();
|
||||
var component = strap;
|
||||
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,
|
||||
Loc.GetString("You can't reach there!"));
|
||||
strap.Owner.PopupMessage(user, Loc.GetString("You can't reach there!"));
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -206,7 +204,7 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
if (!ContainerHelpers.TryGetContainer(strap.Owner, out var 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;
|
||||
}
|
||||
@@ -214,18 +212,16 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
|
||||
if (!user.HasComponent<HandsComponent>())
|
||||
{
|
||||
_notifyManager.PopupMessage(user, user,
|
||||
Loc.GetString("You don't have hands!"));
|
||||
|
||||
user.PopupMessage(Loc.GetString("You don't have hands!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Buckled)
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner, user,
|
||||
Loc.GetString(Owner == user
|
||||
? "You are already buckled in!"
|
||||
: "{0:They} are already buckled in!", Owner));
|
||||
var message = Loc.GetString(Owner == user
|
||||
? "You are already buckled in!"
|
||||
: "{0:They} are already buckled in!", Owner);
|
||||
Owner.PopupMessage(user, message);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -235,10 +231,10 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
{
|
||||
if (parent == user.Transform)
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner, user,
|
||||
Loc.GetString(Owner == user
|
||||
? "You can't buckle yourself there!"
|
||||
: "You can't buckle {0:them} there!", Owner));
|
||||
var message = Loc.GetString(Owner == user
|
||||
? "You can't buckle yourself there!"
|
||||
: "You can't buckle {0:them} there!", Owner);
|
||||
Owner.PopupMessage(user, message);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -248,10 +244,10 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
|
||||
if (!strap.HasSpace(this))
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner, user,
|
||||
Loc.GetString(Owner == user
|
||||
? "You can't fit there!"
|
||||
: "{0:They} can't fit there!", Owner));
|
||||
var message = Loc.GetString(Owner == user
|
||||
? "You can't fit there!"
|
||||
: "{0:They} can't fit there!", Owner);
|
||||
Owner.PopupMessage(user, message);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -282,10 +278,10 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
|
||||
if (!strap.TryAdd(this))
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner, user,
|
||||
Loc.GetString(Owner == user
|
||||
? "You can't buckle yourself there!"
|
||||
: "You can't buckle {0:them} there!", Owner));
|
||||
var message = Loc.GetString(Owner == user
|
||||
? "You can't buckle yourself there!"
|
||||
: "You can't buckle {0:them} there!", Owner);
|
||||
Owner.PopupMessage(user, message);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -336,14 +332,11 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
|
||||
if (!ActionBlockerSystem.CanInteract(user))
|
||||
{
|
||||
_notifyManager.PopupMessage(user, user,
|
||||
Loc.GetString("You can't do that!"));
|
||||
user.PopupMessage(Loc.GetString("You can't do that!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
var strapPosition = Owner.Transform.MapPosition;
|
||||
|
||||
if (!InteractionChecks.InRangeUnobstructed(user, strapPosition, _range))
|
||||
if (!user.InRangeUnobstructed(oldBuckledTo, _range, popup: true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -364,11 +357,11 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
|
||||
if (Owner.TryGetComponent(out StunnableComponent? stunnable) && stunnable.KnockedDown)
|
||||
{
|
||||
StandingStateHelper.Down(Owner);
|
||||
EntitySystem.Get<StandingStateSystem>().Down(Owner);
|
||||
}
|
||||
else
|
||||
{
|
||||
StandingStateHelper.Standing(Owner);
|
||||
EntitySystem.Get<StandingStateSystem>().Standing(Owner);
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out MobStateManagerComponent? stateManager))
|
||||
|
||||
@@ -7,13 +7,14 @@ using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Chemistry.ChemMaster;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Server.GameObjects.Components.Container;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
@@ -22,11 +23,7 @@ using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -43,8 +40,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
[ComponentReference(typeof(IInteractUsing))]
|
||||
public class ChemMasterComponent : SharedChemMasterComponent, IActivate, IInteractUsing, ISolutionChange
|
||||
{
|
||||
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
|
||||
|
||||
[ViewVariables] private ContainerSlot _beakerContainer = default!;
|
||||
[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)
|
||||
{
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
|
||||
if (BufferSolution.CurrentVolume == 0)
|
||||
return;
|
||||
|
||||
@@ -302,15 +295,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
hands.PutInHand(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Put it on the floor
|
||||
bottle.Transform.GridPosition = user.Transform.GridPosition;
|
||||
//Give it an offset
|
||||
var x_negative = random.Prob(0.5f) ? -1 : 1;
|
||||
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);
|
||||
bottle.RandomOffset(0.2f);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -345,9 +335,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
//Put it on the floor
|
||||
pill.Transform.GridPosition = user.Transform.GridPosition;
|
||||
//Give it an offset
|
||||
var x_negative = random.Prob(0.5f) ? -1 : 1;
|
||||
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);
|
||||
pill.RandomOffset(0.2f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,8 +355,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
if (!args.User.TryGetComponent(out IHandsComponent? hands))
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
|
||||
Loc.GetString("You have no hands."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("You have no hands."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -390,15 +377,13 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
if (!args.User.TryGetComponent(out IHandsComponent? hands))
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
|
||||
Loc.GetString("You have no hands."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("You have no hands."));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hands.GetActiveHand == null)
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
|
||||
Loc.GetString("You have nothing on your hand."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("You have nothing on your hand."));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -407,14 +392,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
if (HasBeaker)
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
|
||||
Loc.GetString("This ChemMaster already has a container in it."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("This ChemMaster already has a container in it."));
|
||||
}
|
||||
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.
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
|
||||
Loc.GetString("That can't fit in the ChemMaster."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("That can't fit in the ChemMaster."));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -424,8 +407,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
}
|
||||
else
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
|
||||
Loc.GetString("You can't put this in the ChemMaster."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("You can't put this in the ChemMaster."));
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -435,7 +417,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
private void ClickSound()
|
||||
{
|
||||
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Body.Circulatory;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -23,8 +22,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
[RegisterComponent]
|
||||
public class InjectorComponent : SharedInjectorComponent, IAfterInteract, IUse
|
||||
{
|
||||
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the injector is able to draw from containers or if it's a single use
|
||||
/// device that can only inject.
|
||||
@@ -100,7 +97,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
_notifyManager.PopupMessage(Owner, user, Loc.GetString(msg));
|
||||
Owner.PopupMessage(user, Loc.GetString(msg));
|
||||
|
||||
Dirty();
|
||||
}
|
||||
@@ -111,7 +108,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
/// <param name="eventArgs"></param>
|
||||
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
|
||||
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);
|
||||
if (realTransferAmount <= 0)
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,
|
||||
Loc.GetString("Container full."));
|
||||
Owner.PopupMessage(user, Loc.GetString("Container full."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -177,8 +173,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
return;
|
||||
}
|
||||
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,
|
||||
Loc.GetString("Injected {0}u", removedSolution.TotalVolume));
|
||||
Owner.PopupMessage(user, Loc.GetString("Injected {0}u", removedSolution.TotalVolume));
|
||||
Dirty();
|
||||
}
|
||||
|
||||
@@ -194,8 +189,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.EmptyVolume);
|
||||
if (realTransferAmount <= 0)
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,
|
||||
Loc.GetString("Container full."));
|
||||
Owner.PopupMessage(user, Loc.GetString("Container full."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -206,8 +200,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
return;
|
||||
}
|
||||
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,
|
||||
Loc.GetString("Injected {0}u", removedSolution.TotalVolume));
|
||||
Owner.PopupMessage(user, Loc.GetString("Injected {0}u", removedSolution.TotalVolume));
|
||||
Dirty();
|
||||
}
|
||||
|
||||
@@ -223,8 +216,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.CurrentVolume);
|
||||
if (realTransferAmount <= 0)
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,
|
||||
Loc.GetString("Container empty"));
|
||||
Owner.PopupMessage(user, Loc.GetString("Container empty"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -235,8 +227,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
return;
|
||||
}
|
||||
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,
|
||||
Loc.GetString("Drew {0}u", removedSolution.TotalVolume));
|
||||
Owner.PopupMessage(user, Loc.GetString("Drew {0}u", removedSolution.TotalVolume));
|
||||
Dirty();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using Content.Server.GameObjects.Components.Body.Digestive;
|
||||
using Content.Server.GameObjects.Components.Nutrition;
|
||||
using Content.Server.GameObjects.Components.Utensil;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -46,8 +46,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
base.Initialize();
|
||||
_contents = Owner.GetComponent<SolutionComponent>();
|
||||
_transferAmount = _contents.CurrentVolume;
|
||||
|
||||
}
|
||||
|
||||
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
|
||||
@@ -80,7 +78,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!InteractionChecks.InRangeUnobstructed(user, trueTarget.Transform.MapPosition))
|
||||
if (!user.InRangeUnobstructed(trueTarget, popup: true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -19,8 +18,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
[RegisterComponent]
|
||||
class PourableComponent : Component, IInteractUsing
|
||||
{
|
||||
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
|
||||
|
||||
public override string Name => "Pourable";
|
||||
|
||||
private ReagentUnit _transferAmount;
|
||||
@@ -87,8 +84,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
var realTransferAmount = ReagentUnit.Min(fromPourable.TransferAmount, toSolution.EmptyVolume);
|
||||
if (realTransferAmount <= 0) //Special message if container is full
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User,
|
||||
Loc.GetString("Container is full"));
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("Container is full"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -97,8 +93,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
if (!toSolution.TryAddSolution(removedSolution))
|
||||
return false;
|
||||
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User,
|
||||
Loc.GetString("Transferred {0}u", removedSolution.TotalVolume));
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("Transferred {0}u", removedSolution.TotalVolume));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Chemistry.ReagentDispenser;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects.Components.Container;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
@@ -40,7 +40,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
[ComponentReference(typeof(IInteractUsing))]
|
||||
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 string _packPrototypeId = "";
|
||||
@@ -99,8 +99,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
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;
|
||||
}
|
||||
@@ -280,8 +279,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
if (!args.User.TryGetComponent(out IHandsComponent? hands))
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
|
||||
Loc.GetString("You have no hands."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("You have no hands."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -303,15 +301,13 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
if (!args.User.TryGetComponent(out IHandsComponent? hands))
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
|
||||
Loc.GetString("You have no hands."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("You have no hands."));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hands.GetActiveHand == null)
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
|
||||
Loc.GetString("You have nothing on your hand."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("You have nothing on your hand."));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -320,14 +316,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
if (HasBeaker)
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
|
||||
Loc.GetString("This dispenser already has a container in it."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("This dispenser already has a container in it."));
|
||||
}
|
||||
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.
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
|
||||
Loc.GetString("That can't fit in the dispenser."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("That can't fit in the dispenser."));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -337,8 +331,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
}
|
||||
else
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
|
||||
Loc.GetString("You can't put this in the dispenser."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("You can't put this in the dispenser."));
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -348,11 +341,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
private void ClickSound()
|
||||
{
|
||||
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +234,10 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
var heldEntityName = hands.GetActiveHand.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;
|
||||
}
|
||||
|
||||
@@ -334,7 +337,10 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
var heldEntityName = hands.GetActiveHand.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;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
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?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using Content.Shared.GameObjects.Components.Conveyor;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -170,7 +171,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
|
||||
|
||||
Owner.AddComponent<ItemComponent>();
|
||||
_group?.RemoveConveyor(this);
|
||||
Owner.Transform.WorldPosition += (_random.NextFloat() * 0.4f - 0.2f, _random.NextFloat() * 0.4f - 0.2f);
|
||||
Owner.RandomOffset(0.2f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
|
||||
}
|
||||
|
||||
_group.AddConveyor(conveyor);
|
||||
user?.PopupMessage(user, Loc.GetString("Conveyor linked."));
|
||||
user?.PopupMessage(Loc.GetString("Conveyor linked."));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
214
Content.Server/GameObjects/Components/Damage/DamageCommands.cs
Normal file
214
Content.Server/GameObjects/Components/Damage/DamageCommands.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
}
|
||||
|
||||
return entity.HasComponent<ItemComponent>() ||
|
||||
entity.HasComponent<IBodyManagerComponent>();
|
||||
entity.HasComponent<ISharedBodyManagerComponent>();
|
||||
}
|
||||
|
||||
public bool TryInsert(IEntity entity)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#nullable enable
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
@@ -11,13 +10,13 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Interfaces;
|
||||
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalRouterComponent;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Disposal
|
||||
@@ -27,7 +26,6 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
[ComponentReference(typeof(IDisposalTubeComponent))]
|
||||
public class DisposalRouterComponent : DisposalJunctionComponent, IActivate
|
||||
{
|
||||
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
|
||||
public override string Name => "DisposalRouter";
|
||||
|
||||
[ViewVariables]
|
||||
@@ -160,8 +158,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
|
||||
if (!args.User.TryGetComponent(out IHandsComponent? hands))
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
|
||||
Loc.GetString("You have no hands."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("You have no hands."));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#nullable enable
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
@@ -12,7 +12,6 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -25,7 +24,6 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
[ComponentReference(typeof(IDisposalTubeComponent))]
|
||||
public class DisposalTaggerComponent : DisposalTransitComponent, IActivate
|
||||
{
|
||||
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
|
||||
public override string Name => "DisposalTagger";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
@@ -128,8 +126,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
|
||||
if (!args.User.TryGetComponent(out IHandsComponent? hands))
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
|
||||
Loc.GetString("You have no hands."));
|
||||
Owner.PopupMessage(args.User, Loc.GetString("You have no hands."));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user